mirror of
https://github.com/thorvg/thorvg.git
synced 2025-07-24 07:08: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;
|
||||
auto lineSpacing = 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
|
||||
int idx = 0;
|
||||
|
@ -1163,10 +1165,23 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo)
|
|||
textGroup->push(shape);
|
||||
} else {
|
||||
// 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();
|
||||
matrix.e13 = cursor.x;
|
||||
matrix.e23 = cursor.y;
|
||||
matrix.e11 = matrix.e22 = capScale; //cases with matrix scaling factors =! 1 handled in the 'needGroup' scenario
|
||||
|
||||
if (followPath) {
|
||||
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);
|
||||
scene->push(shape);
|
||||
}
|
||||
|
|
|
@ -29,12 +29,134 @@
|
|||
/* 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 */
|
||||
/************************************************************************/
|
||||
|
||||
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()
|
||||
{
|
||||
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 AlignOption
|
||||
|
@ -364,6 +387,7 @@ struct LottieText : LottieObject, LottieRenderPooler<tvg::Shape>
|
|||
|
||||
LottieTextDoc doc;
|
||||
LottieFont* font;
|
||||
LottieTextFollowPath* followPath = nullptr;
|
||||
Array<LottieTextRange*> ranges;
|
||||
|
||||
~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)
|
||||
{
|
||||
enterObject();
|
||||
|
@ -1188,11 +1202,7 @@ void LottieParser::parseText(Array<LottieObject*>& parent)
|
|||
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();
|
||||
}
|
||||
else if (KEY_AS("p")) parseTextFollowPath(text);
|
||||
else skip();
|
||||
}
|
||||
parent.push(text);
|
||||
|
|
|
@ -120,6 +120,7 @@ private:
|
|||
void parseColorStop(LottieGradient* gradient);
|
||||
void parseTextRange(LottieText* text);
|
||||
void parseTextAlignmentOption(LottieText* text);
|
||||
void parseTextFollowPath(LottieText* text);
|
||||
void parseAssets();
|
||||
void parseFonts();
|
||||
void parseChars(Array<LottieGlyph*>& glyphs);
|
||||
|
|
Loading…
Add table
Reference in a new issue