blob: e7440e87d6df1d9adf5ae407fa744891f7b6098e [file] [log] [blame]
#include "rive/core_context.hpp"
#include "rive/math/math_types.hpp"
#include "rive/shapes/path.hpp"
#include "rive/shapes/shape.hpp"
#include "rive/text/text.hpp"
#include "rive/text/text_follow_path_modifier.hpp"
#include "rive/text/text_modifier_group.hpp"
#include "rive/transform_component.hpp"
using namespace rive;
// Path -> this -> text
void TextFollowPathModifier::buildDependencies()
{
if (m_Target != nullptr && m_Target->is<Shape>())
{
Shape* shape = static_cast<Shape*>(m_Target);
shape->pathComposer()->addDependent(this);
}
else if (m_Target != nullptr && m_Target->is<Path>())
{
Path* path = static_cast<Path*>(m_Target);
path->addDependent(this);
}
Text* text = textComponent();
if (text != nullptr)
{
addDependent(text);
}
}
StatusCode TextFollowPathModifier::onAddedClean(CoreContext* context)
{
if (m_Target != nullptr)
{
if (m_Target->is<Shape>())
{
Shape* shape = static_cast<Shape*>(m_Target);
shape->addFlags(PathFlags::followPath);
}
else if (m_Target->is<Path>())
{
Path* path = static_cast<Path*>(m_Target);
path->addFlags(PathFlags::followPath);
}
}
return Super::onAddedClean(context);
}
void TextFollowPathModifier::update(ComponentDirt value)
{
std::vector<Path*> paths;
if (m_Target != nullptr && m_Target->is<Shape>())
{
auto shape = m_Target->as<Shape>();
for (auto path : shape->paths())
{
paths.push_back(path);
}
}
else if (m_Target != nullptr && m_Target->is<Path>())
{
paths.push_back(m_Target->as<Path>());
}
m_worldPath.rewind();
for (auto path : paths)
{
m_worldPath.addPath(path->rawPath(), &path->pathTransform());
}
}
void TextFollowPathModifier::radialChanged() { modifierShapeDirty(); }
void TextFollowPathModifier::orientChanged() { modifierShapeDirty(); }
void TextFollowPathModifier::startChanged() { modifierShapeDirty(); }
void TextFollowPathModifier::endChanged() { modifierShapeDirty(); }
void TextFollowPathModifier::offsetChanged() { modifierShapeDirty(); }
void TextFollowPathModifier::strengthChanged() { modifierShapeDirty(); }
void TextFollowPathModifier::modifierShapeDirty()
{
Text* text = textComponent();
if (text != nullptr)
{
text->modifierShapeDirty();
}
}
void TextFollowPathModifier::reset(const Mat2D* inverseText)
{
if (m_Target == nullptr)
{
m_pathMeasure = PathMeasure();
return;
}
m_localPath.rewind();
m_localPath.addPath(m_worldPath, inverseText);
m_pathMeasure = PathMeasure(&m_localPath, 0.1f);
}
TransformComponents TextFollowPathModifier::transformGlyph(
const TransformComponents& cur,
const TransformGlyphArg& arg)
{
float pathLength = m_pathMeasure.length();
if (pathLength == 0)
{
return cur;
}
const auto& paragraphLines = arg.paragraphLines;
int lineIndexInParagraph = arg.lineIndexInParagraph;
Vec2D positionOnPath = arg.originPosition + arg.offset;
// endPct >= startPct
float startPct = math::clamp(std::min(start(), end()), 0.0, 1.0);
float endPct = math::clamp(std::max(start(), end()), 0.0, 1.0);
bool canWrap = m_localPath.isClosed() && (endPct - startPct) == 1.0;
float validLength = (endPct - startPct) * pathLength;
// 0%-100%
auto offsetPct = std::fmod(std::fmod(offset(), 1.0f) + 1.0f, 1.0f);
// 0%-200%
startPct += offsetPct;
endPct += offsetPct;
Vec2D position, tangent;
// Render along the tangent if overflowing on either ends
if ((!canWrap && positionOnPath.x < 0) || startPct == endPct)
{
auto result = m_pathMeasure.atPercentage(startPct);
tangent = result.tan;
tangent = tangent.normalized();
Vec2D zeroPosition = result.pos;
float extra = -positionOnPath.x;
position = Vec2D(zeroPosition.x - tangent.x * extra,
zeroPosition.y - tangent.y * extra);
}
else if (!canWrap && positionOnPath.x > validLength)
{
auto result = m_pathMeasure.atPercentage(endPct);
tangent = result.tan;
tangent = tangent.normalized();
Vec2D termPosition = result.pos;
float extra = positionOnPath.x - validLength;
position = Vec2D(termPosition.x + tangent.x * extra,
termPosition.y + tangent.y * extra);
}
else
{
// Closed path, or in the range of the path.
// Use the percentage API to wrap around
auto result = m_pathMeasure.atPercentage(startPct +
positionOnPath.x / pathLength);
position = result.pos;
tangent = result.tan;
tangent = tangent.normalized();
}
// Find out the baseline to sit on
float lastBaseline = 0;
float curBaseline = 0;
auto validLine = [&](int i) -> bool {
return i >= 0 && i < paragraphLines.size();
};
int lastLineIndex = lineIndexInParagraph - 1;
if (validLine(lastLineIndex))
{
lastBaseline = paragraphLines[lastLineIndex].baseline;
}
if (validLine(lineIndexInParagraph))
{
curBaseline = paragraphLines[lineIndexInParagraph].baseline;
}
// We assume the glyph would have been rendered on the
// current line's baseline.
Vec2D translation;
if (radial())
{
float verticalSpacing = positionOnPath.y - curBaseline;
Vec2D perpendicular(-tangent.y, tangent.x);
translation = Vec2D(position.x + verticalSpacing * perpendicular.x,
position.y + verticalSpacing * perpendicular.y);
}
else
{
translation =
Vec2D(position.x,
positionOnPath.y - curBaseline + position.y + lastBaseline);
}
float rotation = orient() ? std::atan2(tangent.y, tangent.x) : 0;
TransformComponents tc;
float t = math::clamp(strength(), 0.0, 1.0);
float ti = 1.0f - t;
tc.x(translation.x * t + cur.x() * ti);
tc.y(translation.y * t + cur.y() * ti);
tc.rotation(rotation * t + cur.rotation() * ti);
return tc;
}