lottie/text: Support text alignment options

Introduced new properties:
- group alignment
- text grouping (words, line)

Issue: #2178
This commit is contained in:
Jinny You 2024-11-01 19:12:34 +09:00 committed by Hermet Park
parent 4bfa7ce913
commit 4f2e725da0
4 changed files with 83 additions and 10 deletions

View file

@ -985,6 +985,7 @@ void LottieBuilder::updateImage(LottieGroup* layer)
void LottieBuilder::updateText(LottieLayer* layer, float frameNo)
{
auto text = static_cast<LottieText*>(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->strokeFill(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);
scaling = scaling * (*s)->style.scale(frameNo) * 0.01f;
@ -1096,15 +1121,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);
}
auto& matrix = shape->transform();
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;

View file

@ -253,6 +253,13 @@ struct LottieMarker
struct LottieText : LottieObject, LottieRenderPooler<tvg::Shape>
{
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<tvg::Shape>
LottieTextDoc doc;
LottieFont* font;
Array<LottieTextRange*> ranges;
AlignOption alignmentOption;
~LottieText()
{

View file

@ -1129,6 +1129,17 @@ void LottieParser::parseShapes(Array<LottieObject*>& 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<LottieProperty::Type::Point>(text->alignmentOption.anchor);
else skip(key);
}
}
void LottieParser::parseTextRange(LottieText* text)
{
enterArray();
@ -1188,16 +1199,12 @@ void LottieParser::parseText(Array<LottieObject*>& parent)
while (auto key = nextObjectKey()) {
if (KEY_AS("d")) parseProperty<LottieProperty::Type::TextDoc>(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);
}

View file

@ -108,6 +108,7 @@ private:
void parseStrokeDash(LottieStroke* stroke);
void parseGradient(LottieGradient* gradient, const char* key);
void parseTextRange(LottieText* text);
void parseTextAlignmentOption(LottieText* text);
void parseAssets();
void parseFonts();
void parseChars(Array<LottieGlyph*>& glyphs);