From 0953d000c14ea314e8c46e7eba6837fabb9f1571 Mon Sep 17 00:00:00 2001 From: Jinny You Date: Wed, 10 Jul 2024 17:44:22 +0800 Subject: [PATCH] lottie/text: Support text range selector (Phase 1) This patch provides initial support for the text range selector. Full support will be added gradually in subsequent steps. This is the first step. Text range selector with following properties: 1. Text Range Selector (Units) 2. Text Style - Fill Color - Fill Opacity - Stroke Color - Stroke Width - Stroke Opacity - Opacity - Rotation - Position - Scale --- src/loaders/lottie/tvgLottieBuilder.cpp | 47 +++++++++++++++++++++++-- src/loaders/lottie/tvgLottieModel.h | 44 ++++++++++++++++++++++- src/loaders/lottie/tvgLottieParser.cpp | 35 ++++++++++++++++-- 3 files changed, 120 insertions(+), 6 deletions(-) diff --git a/src/loaders/lottie/tvgLottieBuilder.cpp b/src/loaders/lottie/tvgLottieBuilder.cpp index 81d2a4c1..58554b7e 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,59 @@ 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)); + + rotate(matrix, (*s)->style.rotation(frameNo)); + + auto glyphScale = (*s)->style.scale(frameNo) * 0.01f; + tvg::scale(matrix, glyphScale.x, glyphScale.y); + + auto position = (*s)->style.position(frameNo); + translate(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 9790ea00..0e0449db 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 7478c767..bd157376 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); } }