mirror of
https://github.com/thorvg/thorvg.git
synced 2025-07-25 15:48:58 +00:00
lottie: add support for text follow path
Added support for cases without text grouping and range selector. Co-Authored-By: Hemet Park <hermet@lottiefiles.com> @Issue: https://github.com/thorvg/thorvg/issues/2888
This commit is contained in:
parent
24378cc200
commit
ac080ffabc
5 changed files with 181 additions and 9 deletions
|
@ -974,6 +974,8 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo)
|
||||||
int space = 0;
|
int space = 0;
|
||||||
auto lineSpacing = 0.0f;
|
auto lineSpacing = 0.0f;
|
||||||
auto totalLineSpacing = 0.0f;
|
auto totalLineSpacing = 0.0f;
|
||||||
|
auto followPath = (text->followPath && ((uint32_t)text->followPath->maskIdx < layer->masks.count)) ? text->followPath : nullptr;
|
||||||
|
auto firstMargin = followPath ? followPath->prepare(layer->masks[followPath->maskIdx], frameNo, scale, tween, exps) : 0.0f;
|
||||||
|
|
||||||
//text string
|
//text string
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
|
@ -1163,10 +1165,23 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo)
|
||||||
textGroup->push(shape);
|
textGroup->push(shape);
|
||||||
} else {
|
} else {
|
||||||
// When text isn't selected, exclude the shape from the text group
|
// When text isn't selected, exclude the shape from the text group
|
||||||
|
// Cases with matrix scaling factors =! 1 handled in the 'needGroup' scenario
|
||||||
auto& matrix = shape->transform();
|
auto& matrix = shape->transform();
|
||||||
matrix.e13 = cursor.x;
|
|
||||||
matrix.e23 = cursor.y;
|
if (followPath) {
|
||||||
matrix.e11 = matrix.e22 = capScale; //cases with matrix scaling factors =! 1 handled in the 'needGroup' scenario
|
identity(&matrix);
|
||||||
|
auto angle = 0.0f;
|
||||||
|
auto halfGlyphWidth = glyph->width * 0.5f;
|
||||||
|
auto position = followPath->position(cursor.x + halfGlyphWidth + firstMargin, angle);
|
||||||
|
matrix.e11 = matrix.e22 = capScale;
|
||||||
|
matrix.e13 = position.x - halfGlyphWidth * matrix.e11;
|
||||||
|
matrix.e23 = position.y - halfGlyphWidth * matrix.e21;
|
||||||
|
} else {
|
||||||
|
matrix.e11 = matrix.e22 = capScale;
|
||||||
|
matrix.e13 = cursor.x;
|
||||||
|
matrix.e23 = cursor.y;
|
||||||
|
}
|
||||||
|
|
||||||
shape->transform(matrix);
|
shape->transform(matrix);
|
||||||
scene->push(shape);
|
scene->push(shape);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,12 +29,134 @@
|
||||||
/* Internal Class Implementation */
|
/* Internal Class Implementation */
|
||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
|
|
||||||
|
Point LottieTextFollowPath::split(float dLen, float lenSearched, float& angle)
|
||||||
|
{
|
||||||
|
switch (*cmds) {
|
||||||
|
case PathCommand::MoveTo: {
|
||||||
|
angle = 0.0f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PathCommand::LineTo: {
|
||||||
|
auto dp = *pts - *(pts - 1);
|
||||||
|
angle = tvg::atan2(dp.y, dp.x);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PathCommand::CubicTo: {
|
||||||
|
auto bz = Bezier{*(pts - 1), *pts, *(pts + 1), *(pts + 2)};
|
||||||
|
float t = bz.at(lenSearched - currentLen, dLen);
|
||||||
|
angle = deg2rad(bz.angle(t));
|
||||||
|
return bz.at(t);
|
||||||
|
}
|
||||||
|
case PathCommand::Close: {
|
||||||
|
auto dp = *start - *(pts - 1);
|
||||||
|
angle = tvg::atan2(dp.y, dp.x);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
/* External Class Implementation */
|
/* External Class Implementation */
|
||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
|
|
||||||
|
float LottieTextFollowPath::prepare(LottieMask* mask, float frameNo, float scale, Tween& tween, LottieExpressions* exps)
|
||||||
|
{
|
||||||
|
this->mask = mask;
|
||||||
|
Matrix m{1.0f / scale, 0.0f, 0.0f, 0.0f, 1.0f / scale, 0.0f, 0.0f, 0.0f, 1.0f};
|
||||||
|
mask->pathset(frameNo, path, &m, tween, exps);
|
||||||
|
|
||||||
|
pts = path.pts.data;
|
||||||
|
cmds = path.cmds.data;
|
||||||
|
cmdsCnt = path.cmds.count;
|
||||||
|
totalLen = tvg::length(cmds, cmdsCnt, pts, path.pts.count);
|
||||||
|
currentLen = 0.0f;
|
||||||
|
start = pts;
|
||||||
|
|
||||||
|
return firstMargin(frameNo, tween, exps) / scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
Point LottieTextFollowPath::position(float lenSearched, float& angle)
|
||||||
|
{
|
||||||
|
auto shift = [&]() -> void {
|
||||||
|
switch (*cmds) {
|
||||||
|
case PathCommand::MoveTo:
|
||||||
|
start = pts;
|
||||||
|
++pts;
|
||||||
|
break;
|
||||||
|
case PathCommand::LineTo:
|
||||||
|
++pts;
|
||||||
|
break;
|
||||||
|
case PathCommand::CubicTo:
|
||||||
|
pts += 3;
|
||||||
|
break;
|
||||||
|
case PathCommand::Close:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++cmds;
|
||||||
|
--cmdsCnt;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto length = [&]() -> float {
|
||||||
|
switch (*cmds) {
|
||||||
|
case PathCommand::MoveTo: return 0.0f;
|
||||||
|
case PathCommand::LineTo: return tvg::length(pts - 1, pts);
|
||||||
|
case PathCommand::CubicTo: return Bezier{*(pts - 1), *pts, *(pts + 1), *(pts + 2)}.length();
|
||||||
|
case PathCommand::Close: return tvg::length(pts - 1, start);
|
||||||
|
}
|
||||||
|
return 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
//beyond the curve
|
||||||
|
if (lenSearched > totalLen) {
|
||||||
|
//shape is closed -> wrapping
|
||||||
|
if (path.cmds.last() == PathCommand::Close) {
|
||||||
|
lenSearched -= totalLen;
|
||||||
|
pts = path.pts.data;
|
||||||
|
cmds = path.cmds.data;
|
||||||
|
cmdsCnt = path.cmds.count;
|
||||||
|
currentLen = 0.0f;
|
||||||
|
//linear interpolation
|
||||||
|
} else {
|
||||||
|
while (cmdsCnt > 1) shift();
|
||||||
|
switch (*cmds) {
|
||||||
|
case PathCommand::MoveTo:
|
||||||
|
angle = 0.0f;
|
||||||
|
return *pts;
|
||||||
|
case PathCommand::LineTo: {
|
||||||
|
auto len = lenSearched - totalLen;
|
||||||
|
auto dp = *pts - *(pts - 1);
|
||||||
|
angle = tvg::atan2(dp.y, dp.x);
|
||||||
|
return {pts->x + len * cos(angle), pts->y + len * sin(angle)};
|
||||||
|
}
|
||||||
|
case PathCommand::CubicTo: {
|
||||||
|
auto len = lenSearched - totalLen;
|
||||||
|
angle = deg2rad(Bezier{*(pts - 1), *pts, *(pts + 1), *(pts + 2)}.angle(0.999f));
|
||||||
|
return {(pts + 2)->x + len * cos(angle), (pts + 2)->y + len * sin(angle)};
|
||||||
|
}
|
||||||
|
case PathCommand::Close: {
|
||||||
|
auto len = lenSearched - totalLen;
|
||||||
|
auto dp = *start - *(pts - 1);
|
||||||
|
angle = tvg::atan2(dp.y, dp.x);
|
||||||
|
return {(pts - 1)->x + len * cos(angle), (pts - 1)->y + len * sin(angle)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (cmdsCnt > 0) {
|
||||||
|
auto dLen = length();
|
||||||
|
if (currentLen + dLen <= lenSearched) {
|
||||||
|
shift();
|
||||||
|
currentLen += dLen;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return split(dLen, lenSearched, angle);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void LottieSlot::reset()
|
void LottieSlot::reset()
|
||||||
{
|
{
|
||||||
if (!overridden) return;
|
if (!overridden) return;
|
||||||
|
|
|
@ -336,6 +336,29 @@ struct LottieMarker
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct LottieTextFollowPath
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
RenderPath path;
|
||||||
|
PathCommand* cmds;
|
||||||
|
uint32_t cmdsCnt;
|
||||||
|
Point* pts;
|
||||||
|
Point* start;
|
||||||
|
float totalLen;
|
||||||
|
float currentLen;
|
||||||
|
Point split(float dLen, float lenSearched, float& angle);
|
||||||
|
|
||||||
|
public:
|
||||||
|
LottieFloat firstMargin = 0.0f;
|
||||||
|
LottieMask* mask;
|
||||||
|
int8_t maskIdx = -1;
|
||||||
|
|
||||||
|
Point position(float lenSearched, float& angle);
|
||||||
|
float prepare(LottieMask* mask, float frameNo, float scale, Tween& tween, LottieExpressions* exps);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
struct LottieText : LottieObject, LottieRenderPooler<tvg::Shape>
|
struct LottieText : LottieObject, LottieRenderPooler<tvg::Shape>
|
||||||
{
|
{
|
||||||
struct AlignOption
|
struct AlignOption
|
||||||
|
@ -364,6 +387,7 @@ struct LottieText : LottieObject, LottieRenderPooler<tvg::Shape>
|
||||||
|
|
||||||
LottieTextDoc doc;
|
LottieTextDoc doc;
|
||||||
LottieFont* font;
|
LottieFont* font;
|
||||||
|
LottieTextFollowPath* followPath = nullptr;
|
||||||
Array<LottieTextRange*> ranges;
|
Array<LottieTextRange*> ranges;
|
||||||
|
|
||||||
~LottieText()
|
~LottieText()
|
||||||
|
|
|
@ -1178,6 +1178,20 @@ void LottieParser::parseTextRange(LottieText* text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LottieParser::parseTextFollowPath(LottieText* text)
|
||||||
|
{
|
||||||
|
enterObject();
|
||||||
|
auto key = nextObjectKey();
|
||||||
|
if (!key) return;
|
||||||
|
if (!text->followPath) text->followPath = new LottieTextFollowPath;
|
||||||
|
do {
|
||||||
|
if (KEY_AS("m")) text->followPath->maskIdx = getInt();
|
||||||
|
else if (KEY_AS("f")) parseProperty<LottieProperty::Type::Float>(text->followPath->firstMargin);
|
||||||
|
else skip();
|
||||||
|
} while ((key = nextObjectKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void LottieParser::parseText(Array<LottieObject*>& parent)
|
void LottieParser::parseText(Array<LottieObject*>& parent)
|
||||||
{
|
{
|
||||||
enterObject();
|
enterObject();
|
||||||
|
@ -1188,11 +1202,7 @@ void LottieParser::parseText(Array<LottieObject*>& parent)
|
||||||
if (KEY_AS("d")) parseProperty<LottieProperty::Type::TextDoc>(text->doc, text);
|
if (KEY_AS("d")) parseProperty<LottieProperty::Type::TextDoc>(text->doc, text);
|
||||||
else if (KEY_AS("a")) parseTextRange(text);
|
else if (KEY_AS("a")) parseTextRange(text);
|
||||||
else if (KEY_AS("m")) parseTextAlignmentOption(text);
|
else if (KEY_AS("m")) parseTextAlignmentOption(text);
|
||||||
else if (KEY_AS("p"))
|
else if (KEY_AS("p")) parseTextFollowPath(text);
|
||||||
{
|
|
||||||
TVGLOG("LOTTIE", "Text Follow Path (p) is not supported");
|
|
||||||
skip();
|
|
||||||
}
|
|
||||||
else skip();
|
else skip();
|
||||||
}
|
}
|
||||||
parent.push(text);
|
parent.push(text);
|
||||||
|
|
|
@ -120,6 +120,7 @@ private:
|
||||||
void parseColorStop(LottieGradient* gradient);
|
void parseColorStop(LottieGradient* gradient);
|
||||||
void parseTextRange(LottieText* text);
|
void parseTextRange(LottieText* text);
|
||||||
void parseTextAlignmentOption(LottieText* text);
|
void parseTextAlignmentOption(LottieText* text);
|
||||||
|
void parseTextFollowPath(LottieText* text);
|
||||||
void parseAssets();
|
void parseAssets();
|
||||||
void parseFonts();
|
void parseFonts();
|
||||||
void parseChars(Array<LottieGlyph*>& glyphs);
|
void parseChars(Array<LottieGlyph*>& glyphs);
|
||||||
|
|
Loading…
Add table
Reference in a new issue