From b24e876a6737e1e695de1617116d6e4c0b40d7e6 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 | 48 +++++++++++++++++++++++-- src/loaders/lottie/tvgLottieModel.h | 44 ++++++++++++++++++++++- src/loaders/lottie/tvgLottieParser.cpp | 35 ++++++++++++++++-- 3 files changed, 121 insertions(+), 6 deletions(-) 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); } }