diff --git a/src/loaders/lottie/tvgLottieBuilder.cpp b/src/loaders/lottie/tvgLottieBuilder.cpp index 3ea3d2e9..40163a2c 100644 --- a/src/loaders/lottie/tvgLottieBuilder.cpp +++ b/src/loaders/lottie/tvgLottieBuilder.cpp @@ -1057,12 +1057,13 @@ static void _updateText(LottieLayer* layer, float frameNo) if (!p || !text->font) return; auto scale = doc.size * 0.01f; - float spacing = text->spacing(frameNo) / scale; Point cursor = {0.0f, 0.0f}; auto scene = Scene::gen(); int line = 0; //text string + int idx = 0; + auto totalChars = strlen(p); while (true) { //TODO: remove nested scenes. //end of text, new line of the cursor position @@ -1114,19 +1115,60 @@ static void _updateText(LottieLayer* layer, float frameNo) shape->stroke(doc.stroke.color.rgb[0], doc.stroke.color.rgb[1], doc.stroke.color.rgb[2]); } + //text range process + for (auto s = text->ranges.begin(); s < text->ranges.end(); ++s) { + float divisor = (*s)->rangeUnit == LottieTextRange::Unit::Percent ? (100.0f / totalChars) : 1; + auto offset = (*s)->offset(frameNo) / divisor; + auto start = round((*s)->start(frameNo) / divisor) + offset; + auto end = round((*s)->end(frameNo) / divisor) + offset; + + if (start > end) std::swap(start, end); + + if (idx < start || idx >= end) continue; + auto matrix = PP(shape.get())->transform(); + + shape->opacity((*s)->style.opacity(frameNo)); + + auto color = (*s)->style.fillColor(frameNo); + shape->fill(color.rgb[0], color.rgb[1], color.rgb[2], (*s)->style.fillOpacity(frameNo)); + + mathRotate(matrix, (*s)->style.rotation(frameNo)); + + auto glyphScale = (*s)->style.scale(frameNo) * 0.01f; + mathScale(matrix, glyphScale.x, glyphScale.y); + + auto position = (*s)->style.position(frameNo); + mathTranslate(matrix, position.x, position.y); + + shape->transform(*matrix); + + if (doc.stroke.render) { + auto strokeColor = (*s)->style.strokeColor(frameNo); + + shape->stroke((*s)->style.strokeWidth(frameNo) / scale); + shape->stroke(strokeColor.rgb[0], strokeColor.rgb[1], strokeColor.rgb[2], (*s)->style.strokeOpacity(frameNo)); + } + + cursor.x += (*s)->style.letterSpacing(frameNo); + } + scene->push(std::move(shape)); p += glyph->len; + idx += glyph->len; //advance the cursor position horizontally - cursor.x += glyph->width + spacing + doc.tracking; + cursor.x += glyph->width + doc.tracking; found = true; break; } } - if (!found) ++p; + if (!found) { + ++p; + ++idx; + } } } diff --git a/src/loaders/lottie/tvgLottieModel.h b/src/loaders/lottie/tvgLottieModel.h index 78719c3b..d2cd9a60 100644 --- a/src/loaders/lottie/tvgLottieModel.h +++ b/src/loaders/lottie/tvgLottieModel.h @@ -148,6 +148,43 @@ struct LottieGlyph }; +struct LottieTextStyle +{ + LottieColor fillColor = RGB24{255, 255, 255}; + LottieColor strokeColor = RGB24{255, 255, 255}; + LottiePosition position = Point{0, 0}; + LottiePoint scale = Point{100, 100}; + LottieFloat letterSpacing = 0.0f; + LottieFloat strokeWidth = 0.0f; + LottieFloat rotation = 0.0f; + LottieOpacity fillOpacity = 255; + LottieOpacity strokeOpacity = 255; + LottieOpacity opacity = 255; +}; + + +struct LottieTextRange +{ + enum Based : uint8_t { Chars = 1, CharsExcludingSpaces, Words, Lines }; + enum Shape : uint8_t { Square = 1, RampUp, RampDown, Triangle, Round, Smooth }; + enum Unit : uint8_t { Percent = 1, Index }; + + LottieTextStyle style; + LottieFloat offset = 0.0f; + LottieFloat maxEase = 0.0f; + LottieFloat minEase = 0.0f; + LottieFloat maxAmount = 0.0f; + LottieFloat smoothness = 0.0f; + LottieFloat start = 0.0f; + LottieFloat end = 0.0f; + Based based = Chars; + Shape shape = Square; + Unit rangeUnit = Percent; + bool expressible = false; + bool randomize = false; +}; + + struct LottieFont { enum Origin : uint8_t { Local = 0, CssURL, ScriptURL, FontURL, Embedded }; @@ -195,7 +232,12 @@ struct LottieText : LottieObject LottieTextDoc doc; LottieFont* font; - LottieFloat spacing = 0.0f; //letter spacing + Array ranges; + + ~LottieText() + { + for (auto r = ranges.begin(); r < ranges.end(); ++r) delete(*r); + } }; diff --git a/src/loaders/lottie/tvgLottieParser.cpp b/src/loaders/lottie/tvgLottieParser.cpp index e4698e74..83db95c2 100644 --- a/src/loaders/lottie/tvgLottieParser.cpp +++ b/src/loaders/lottie/tvgLottieParser.cpp @@ -1120,15 +1120,46 @@ void LottieParser::parseTextRange(LottieText* text) enterArray(); while (nextArrayValue()) { enterObject(); + + auto selector = new LottieTextRange; + while (auto key = nextObjectKey()) { - if (KEY_AS("a")) { //text style + if (KEY_AS("s")) { // text range selector enterObject(); while (auto key = nextObjectKey()) { - if (KEY_AS("t")) parseProperty(text->spacing); + if (KEY_AS("t")) selector->expressible = (bool) getInt(); + else if (KEY_AS("xe")) parseProperty(selector->maxEase); + else if (KEY_AS("ne")) parseProperty(selector->minEase); + else if (KEY_AS("a")) parseProperty(selector->maxAmount); + else if (KEY_AS("b")) selector->based = (LottieTextRange::Based) getInt(); + else if (KEY_AS("rn")) selector->randomize = (bool) getInt(); + else if (KEY_AS("sh")) selector->shape = (LottieTextRange::Shape) getInt(); + else if (KEY_AS("o")) parseProperty(selector->offset); + else if (KEY_AS("r")) selector->rangeUnit = (LottieTextRange::Unit) getInt(); + else if (KEY_AS("sm")) parseProperty(selector->smoothness); + else if (KEY_AS("s")) parseProperty(selector->start); + else if (KEY_AS("e")) parseProperty(selector->end); + else skip(key); + } + } else if (KEY_AS("a")) { // text style + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("t")) parseProperty(selector->style.letterSpacing); + else if (KEY_AS("fc")) parseProperty(selector->style.fillColor); + else if (KEY_AS("fo")) parseProperty(selector->style.fillOpacity); + else if (KEY_AS("sw")) parseProperty(selector->style.strokeWidth); + else if (KEY_AS("sc")) parseProperty(selector->style.strokeColor); + else if (KEY_AS("so")) parseProperty(selector->style.strokeOpacity); + else if (KEY_AS("o")) parseProperty(selector->style.opacity); + else if (KEY_AS("p")) parseProperty(selector->style.position); + else if (KEY_AS("s")) parseProperty(selector->style.scale); + else if (KEY_AS("r")) parseProperty(selector->style.rotation); else skip(key); } } else skip(key); } + + text->ranges.push(selector); } }