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
This commit is contained in:
Jinny You 2024-07-10 17:44:22 +08:00 committed by Hermet Park
parent 318c76119a
commit b24e876a67
3 changed files with 121 additions and 6 deletions

View file

@ -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;
}
}
}

View file

@ -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<LottieTextRange*> ranges;
~LottieText()
{
for (auto r = ranges.begin(); r < ranges.end(); ++r) delete(*r);
}
};

View file

@ -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<LottieProperty::Type::Float>(text->spacing);
if (KEY_AS("t")) selector->expressible = (bool) getInt();
else if (KEY_AS("xe")) parseProperty<LottieProperty::Type::Float>(selector->maxEase);
else if (KEY_AS("ne")) parseProperty<LottieProperty::Type::Float>(selector->minEase);
else if (KEY_AS("a")) parseProperty<LottieProperty::Type::Float>(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<LottieProperty::Type::Float>(selector->offset);
else if (KEY_AS("r")) selector->rangeUnit = (LottieTextRange::Unit) getInt();
else if (KEY_AS("sm")) parseProperty<LottieProperty::Type::Float>(selector->smoothness);
else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Float>(selector->start);
else if (KEY_AS("e")) parseProperty<LottieProperty::Type::Float>(selector->end);
else skip(key);
}
} else if (KEY_AS("a")) { // text style
enterObject();
while (auto key = nextObjectKey()) {
if (KEY_AS("t")) parseProperty<LottieProperty::Type::Float>(selector->style.letterSpacing);
else if (KEY_AS("fc")) parseProperty<LottieProperty::Type::Color>(selector->style.fillColor);
else if (KEY_AS("fo")) parseProperty<LottieProperty::Type::Color>(selector->style.fillOpacity);
else if (KEY_AS("sw")) parseProperty<LottieProperty::Type::Float>(selector->style.strokeWidth);
else if (KEY_AS("sc")) parseProperty<LottieProperty::Type::Color>(selector->style.strokeColor);
else if (KEY_AS("so")) parseProperty<LottieProperty::Type::Opacity>(selector->style.strokeOpacity);
else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Opacity>(selector->style.opacity);
else if (KEY_AS("p")) parseProperty<LottieProperty::Type::Position>(selector->style.position);
else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Position>(selector->style.scale);
else if (KEY_AS("r")) parseProperty<LottieProperty::Type::Float>(selector->style.rotation);
else skip(key);
}
} else skip(key);
}
text->ranges.push(selector);
}
}