diff --git a/src/loaders/lottie/tvgLottieBuilder.cpp b/src/loaders/lottie/tvgLottieBuilder.cpp index b92a5db1..597a08b7 100644 --- a/src/loaders/lottie/tvgLottieBuilder.cpp +++ b/src/loaders/lottie/tvgLottieBuilder.cpp @@ -985,6 +985,7 @@ void LottieBuilder::updateImage(LottieGroup* layer) void LottieBuilder::updateText(LottieLayer* layer, float frameNo) { auto text = static_cast(layer->children.first()); + auto textGrouping = text->alignmentOption.grouping; auto& doc = text->doc(frameNo); auto p = doc.text; @@ -993,6 +994,7 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo) auto scale = doc.size; Point cursor = {0.0f, 0.0f}; auto scene = Scene::gen(); + auto textGroup = Scene::gen(); int line = 0; int space = 0; auto lineSpacing = 0.0f; @@ -1014,6 +1016,11 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo) if (doc.justify == 1) layout.x += doc.bbox.size.x - (cursor.x * scale); //right aligned else if (doc.justify == 2) layout.x += (doc.bbox.size.x * 0.5f) - (cursor.x * 0.5f * scale); //center aligned + //new text group, single scene based on text-grouping + scene->push(std::move(textGroup)); + textGroup = Scene::gen(); + textGroup->translate(cursor.x, cursor.y); + scene->translate(layout.x, layout.y); scene->scale(scale); @@ -1032,7 +1039,15 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo) continue; } - if (*p == ' ') ++space; + if (*p == ' ') { + ++space; + if (textGrouping == LottieText::AlignOption::Group::Word) { + //new text group, single scene for each word + scene->push(std::move(textGroup)); + textGroup = Scene::gen(); + textGroup->translate(cursor.x, cursor.y); + } + } //find the glyph bool found = false; @@ -1040,6 +1055,14 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo) auto glyph = *g; //draw matched glyphs if (!strncmp(glyph->code, p, glyph->len)) { + if (textGrouping == LottieText::AlignOption::Group::Chars || textGrouping == LottieText::AlignOption::Group::All) { + //new text group, single scene for each characters + scene->push(std::move(textGroup)); + textGroup = Scene::gen(); + textGroup->translate(cursor.x, cursor.y); + } + + auto textGroupMatrix = textGroup->transform(); auto shape = text->pooling(); shape->reset(); for (auto g = glyph->children.begin(); g < glyph->children.end(); ++g) { @@ -1051,7 +1074,7 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo) } } shape->fill(doc.color.rgb[0], doc.color.rgb[1], doc.color.rgb[2]); - shape->translate(cursor.x, cursor.y); + shape->translate(cursor.x - textGroupMatrix.e13, cursor.y - textGroupMatrix.e23); shape->opacity(255); if (doc.stroke.render) { @@ -1060,6 +1083,7 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo) shape->stroke(doc.stroke.color.rgb[0], doc.stroke.color.rgb[1], doc.stroke.color.rgb[2]); } + bool needGroup = false; if (!text->ranges.empty()) { Point scaling = {1.0f, 1.0f}; auto rotation = 0.0f; @@ -1076,6 +1100,7 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo) else if ((*s)->based == LottieTextRange::Based::Lines) basedIdx = line; if (basedIdx < start || basedIdx >= end) continue; + needGroup = true; translation = translation + (*s)->style.position(frameNo); auto temp = (*s)->style.scale(frameNo); @@ -1098,15 +1123,47 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo) auto spacing = (*s)->style.lineSpacing(frameNo); if (spacing > lineSpacing) lineSpacing = spacing; } + + // TextGroup transformation is performed once + if (textGroup->paints().size() == 0 && needGroup) { + identity(&textGroupMatrix); + translate(&textGroupMatrix, cursor.x, cursor.y); + + auto alignment = text->alignmentOption.anchor(frameNo); + + // center pivoting + textGroupMatrix.e13 += alignment.x; + textGroupMatrix.e23 += alignment.y; + + rotate(&textGroupMatrix, rotation); + + auto pivotX = alignment.x * -1; + auto pivotY = alignment.y * -1; + + //center pivoting + textGroupMatrix.e13 += (pivotX * textGroupMatrix.e11 + pivotX * textGroupMatrix.e12); + textGroupMatrix.e23 += (pivotY * textGroupMatrix.e21 + pivotY * textGroupMatrix.e22); + + textGroup->transform(textGroupMatrix); + } + Matrix matrix; identity(&matrix); - translate(&matrix, translation.x / scale + cursor.x, translation.y / scale + cursor.y); + translate(&matrix, (translation.x / scale + cursor.x) - textGroupMatrix.e13, (translation.y / scale + cursor.y) - textGroupMatrix.e23); tvg::scale(&matrix, scaling.x, scaling.y); - rotate(&matrix, rotation); shape->transform(matrix); } - scene->push(cast(shape)); + if (needGroup) { + textGroup->push(cast(shape)); + } else { + // When text isn't selected, exclude the shape from the text group + auto matrix = shape->transform(); + matrix.e13 = cursor.x; + matrix.e23 = cursor.y; + shape->transform(matrix); + scene->push(cast(shape)); + } p += glyph->len; idx += glyph->len; diff --git a/src/loaders/lottie/tvgLottieModel.h b/src/loaders/lottie/tvgLottieModel.h index 95c9c8b2..8efc8c6e 100644 --- a/src/loaders/lottie/tvgLottieModel.h +++ b/src/loaders/lottie/tvgLottieModel.h @@ -253,6 +253,13 @@ struct LottieMarker struct LottieText : LottieObject, LottieRenderPooler { + struct AlignOption + { + enum Group : uint8_t { Chars = 1, Word = 2, Line = 3, All = 4 }; + Group grouping = Chars; + LottiePoint anchor = Point{0.0f, 0.0f}; + }; + void prepare() { LottieObject::type = LottieObject::Text; @@ -273,6 +280,7 @@ struct LottieText : LottieObject, LottieRenderPooler LottieTextDoc doc; LottieFont* font; Array ranges; + AlignOption alignmentOption; ~LottieText() { diff --git a/src/loaders/lottie/tvgLottieParser.cpp b/src/loaders/lottie/tvgLottieParser.cpp index 5e2700fb..01ac3153 100644 --- a/src/loaders/lottie/tvgLottieParser.cpp +++ b/src/loaders/lottie/tvgLottieParser.cpp @@ -1138,6 +1138,17 @@ void LottieParser::parseShapes(Array& parent) } +void LottieParser::parseTextAlignmentOption(LottieText* text) +{ + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("g")) text->alignmentOption.grouping = (LottieText::AlignOption::Group) getInt(); + else if (KEY_AS("a")) parseProperty(text->alignmentOption.anchor); + else skip(key); + } +} + + void LottieParser::parseTextRange(LottieText* text) { enterArray(); @@ -1197,16 +1208,12 @@ void LottieParser::parseText(Array& parent) while (auto key = nextObjectKey()) { if (KEY_AS("d")) parseProperty(text->doc, text); else if (KEY_AS("a")) parseTextRange(text); + else if (KEY_AS("m")) parseTextAlignmentOption(text); else if (KEY_AS("p")) { TVGLOG("LOTTIE", "Text Follow Path (p) is not supported"); skip(key); } - else if (KEY_AS("m")) - { - TVGLOG("LOTTIE", "Text Alignment Option (m) is not supported"); - skip(key); - } else skip(key); } diff --git a/src/loaders/lottie/tvgLottieParser.h b/src/loaders/lottie/tvgLottieParser.h index 15f104ab..0a1746a5 100644 --- a/src/loaders/lottie/tvgLottieParser.h +++ b/src/loaders/lottie/tvgLottieParser.h @@ -112,6 +112,7 @@ private: void parseGradient(LottieGradient* gradient, const char* key); void parseColorStop(LottieGradient* gradient); void parseTextRange(LottieText* text); + void parseTextAlignmentOption(LottieText* text); void parseAssets(); void parseFonts(); void parseChars(Array& glyphs);