blob: a2c0a4689454ed1620b60f4b1d600474502bd3b2 [file] [log] [blame]
#include "rive/text/text.hpp"
#include "rive/text/text_modifier_range.hpp"
#include "rive/text/text_modifier_group.hpp"
#include "rive/text/text_value_run.hpp"
#include "rive/text/glyph_lookup.hpp"
#include "rive/animation/cubic_interpolator_component.hpp"
#include "rive/core_context.hpp"
using namespace rive;
StatusCode TextModifierRange::onAddedDirty(CoreContext* context)
{
StatusCode code = Super::onAddedDirty(context);
if (code != StatusCode::Ok)
{
return code;
}
if (runId() != Core::emptyId)
{
auto coreObject = context->resolve(runId());
if (coreObject == nullptr || !coreObject->is<TextValueRun>())
{
return StatusCode::MissingObject;
}
m_run = static_cast<TextValueRun*>(coreObject);
}
if (parent() != nullptr && parent()->is<TextModifierGroup>())
{
parent()->as<TextModifierGroup>()->addModifierRange(this);
return StatusCode::Ok;
}
return StatusCode::MissingObject;
}
void TextModifierRange::addChild(Component* component)
{
Super::addChild(component);
if (component->is<CubicInterpolatorComponent>())
{
// mark this as the cubic interpolator.
m_interpolator = component->as<CubicInterpolatorComponent>();
}
}
void TextModifierRange::clearRangeMap() { m_rangeMapper.clear(); }
void TextModifierRange::computeRange(Span<const Unichar> text,
const SimpleArray<Paragraph>& shape,
const SimpleArray<SimpleArray<GlyphLine>>& lines,
const GlyphLookup& glyphLookup)
{
// Check if the range mapper is still valid.
if (!m_rangeMapper.empty())
{
return;
}
uint32_t start = 0;
uint32_t end = (uint32_t)text.size();
if (m_run != nullptr)
{
start = m_run->offset();
end = start + m_run->length();
}
switch (units())
{
case TextRangeUnits::charactersExcludingSpaces:
m_rangeMapper.fromCharacters(text, start, end, glyphLookup, true);
break;
case TextRangeUnits::words:
m_rangeMapper.fromWords(text, start, end);
break;
case TextRangeUnits::lines:
m_rangeMapper.fromLines(text, start, end, shape, lines, glyphLookup);
break;
default:
m_rangeMapper.fromCharacters(text, start, end, glyphLookup);
break;
}
}
float TextModifierRange::coverageAt(float t)
{
float c = 0.0f;
if (m_indexTo >= m_indexFrom)
{
if (t < m_indexFrom || t > m_indexTo)
{
c = 0.0f;
}
else if (t < m_indexFalloffFrom)
{
float range = std::max(0.0f, m_indexFalloffFrom - m_indexFrom);
c = range == 0.0f ? 1.0f : std::max(0.0f, t - m_indexFrom) / range;
if (m_interpolator != nullptr)
{
c = m_interpolator->transform(c);
}
}
else if (t > m_indexFalloffTo)
{
float range = std::max(0.0f, m_indexTo - m_indexFalloffTo);
c = range == 0.0f ? 1.0f : 1.0f - std::min(1.0f, (t - m_indexFalloffTo) / range);
if (m_interpolator != nullptr)
{
c = m_interpolator->transform(c);
}
}
else
{
c = 1.0f;
}
}
return c;
}
void TextModifierRange::computeCoverage(Span<float> coverage)
{
if (m_rangeMapper.empty())
{
return;
}
// Compute range mapped coverage.
uint32_t count = m_rangeMapper.unitCount();
switch (type())
{
case TextRangeType::unitIndex:
m_indexFrom = offsetModifyFrom();
m_indexTo = offsetModifyTo();
m_indexFalloffFrom = offsetFalloffFrom();
m_indexFalloffTo = offsetFalloffTo();
break;
case TextRangeType::percentage:
m_indexFrom = count * offsetModifyFrom();
m_indexTo = count * offsetModifyTo();
m_indexFalloffFrom = count * offsetFalloffFrom();
m_indexFalloffTo = count * offsetFalloffTo();
break;
}
TextRangeMode rangeMode = mode();
for (uint32_t unitIndex = 0; unitIndex < count; unitIndex++)
{
uint32_t unitLength = (uint32_t)m_rangeMapper.unitLength(unitIndex);
uint32_t characterIndex = m_rangeMapper.unitCharacterIndex(unitIndex);
float c = strength() * coverageAt(unitIndex + 0.5f);
for (uint32_t i = 0; i < unitLength; i++)
{
assert((characterIndex + i) < (uint32_t)coverage.size());
float current = coverage[characterIndex + i];
switch (rangeMode)
{
case TextRangeMode::add:
current += c;
break;
case TextRangeMode::subtract:
current -= c;
break;
case TextRangeMode::max:
current = std::max(current, c);
break;
case TextRangeMode::min:
current = std::min(current, c);
break;
case TextRangeMode::multiply:
current *= c;
break;
case TextRangeMode::difference:
current = std::abs(current - c);
break;
}
coverage[characterIndex + i] =
clamp() ? std::max(0.0f, std::min(1.0f, current)) : current;
}
// Set space between units coverage to 0.
if (unitIndex + 1 < m_rangeMapper.unitCharacterIndexCount())
{
uint32_t nextCharacterIndex = m_rangeMapper.unitCharacterIndex(unitIndex + 1);
for (uint32_t i = characterIndex + unitLength; i < nextCharacterIndex; i++)
{
coverage[i] = 0;
}
}
}
}
void TextModifierRange::modifyFromChanged() { parent()->as<TextModifierGroup>()->rangeChanged(); }
void TextModifierRange::modifyToChanged() { parent()->as<TextModifierGroup>()->rangeChanged(); }
void TextModifierRange::strengthChanged() { parent()->as<TextModifierGroup>()->rangeChanged(); }
void TextModifierRange::unitsValueChanged()
{
parent()->as<TextModifierGroup>()->rangeTypeChanged();
}
void TextModifierRange::typeValueChanged() { parent()->as<TextModifierGroup>()->rangeChanged(); }
void TextModifierRange::modeValueChanged() { parent()->as<TextModifierGroup>()->rangeChanged(); }
void TextModifierRange::clampChanged() { parent()->as<TextModifierGroup>()->rangeChanged(); }
void TextModifierRange::falloffFromChanged() { parent()->as<TextModifierGroup>()->rangeChanged(); }
void TextModifierRange::falloffToChanged() { parent()->as<TextModifierGroup>()->rangeChanged(); }
void TextModifierRange::offsetChanged() { parent()->as<TextModifierGroup>()->rangeChanged(); }
bool TextModifierRange::needsShape() const { return units() == TextRangeUnits::lines; }
void RangeMapper::clear()
{
m_unitCharacterIndices.clear();
m_unitLengths.clear();
}
float RangeMapper::unitToCharacterRange(float word) const
{
if (m_unitCharacterIndices.empty())
{
return 0.0f;
}
float clampedUnit = std::min(std::max(word, 0.0f), (float)(m_unitCharacterIndices.size() - 1));
size_t intUnit = (size_t)clampedUnit;
float characters = (float)m_unitCharacterIndices[intUnit];
if (intUnit < m_unitLengths.size())
{
float fraction = clampedUnit - intUnit;
characters += m_unitLengths[intUnit] * fraction;
}
return characters;
}
void RangeMapper::addRange(uint32_t indexFrom,
uint32_t indexTo,
uint32_t startOffset,
uint32_t endOffset)
{
if (indexTo > startOffset && endOffset > indexFrom)
{
uint32_t actualStart = std::max(startOffset, indexFrom);
uint32_t actualEnd = std::min(endOffset, indexTo);
if (actualEnd > actualStart)
{
m_unitCharacterIndices.push_back(actualStart);
m_unitLengths.push_back(actualEnd - actualStart);
}
}
}
void RangeMapper::fromWords(Span<const Unichar> text, uint32_t start, uint32_t end)
{
if (text.empty())
{
return;
}
bool wantWhiteSpace = false;
uint32_t characterCount = 0;
uint32_t index = 0;
uint32_t indexFrom = 0;
for (Unichar unit : text)
{
if (wantWhiteSpace == isWhiteSpace(unit))
{
if (!wantWhiteSpace)
{
indexFrom = index;
}
else
{
addRange(indexFrom, indexFrom + characterCount, start, end);
characterCount = 0;
}
wantWhiteSpace = !wantWhiteSpace;
}
if (wantWhiteSpace)
{
characterCount++;
}
index++;
}
if (characterCount > 0)
{
addRange(indexFrom, indexFrom + characterCount, start, end);
}
m_unitCharacterIndices.push_back(end);
}
void RangeMapper::fromCharacters(Span<const Unichar> text,
uint32_t start,
uint32_t end,
const GlyphLookup& glyphLookup,
bool withoutSpaces)
{
if (text.empty())
{
return;
}
for (uint32_t i = start; i < end;)
{
Unichar unit = text[i];
if (withoutSpaces && isWhiteSpace(unit))
{
i++;
continue;
}
// Don't break ligated glyphs.
uint32_t codePoints = glyphLookup.count(i);
m_unitCharacterIndices.push_back(i);
m_unitLengths.push_back(codePoints);
i += codePoints;
}
m_unitCharacterIndices.push_back(end);
}
void RangeMapper::fromLines(Span<const Unichar> text,
uint32_t start,
uint32_t end,
const SimpleArray<Paragraph>& shape,
const SimpleArray<SimpleArray<GlyphLine>>& lines,
const GlyphLookup& glyphLookup)
{
if (text.empty())
{
return;
}
uint32_t paragraphIndex = 0;
for (const SimpleArray<GlyphLine>& paragraphLines : lines)
{
const Paragraph& paragraph = shape[paragraphIndex++];
const SimpleArray<GlyphRun>& glyphRuns = paragraph.runs;
for (const GlyphLine& line : paragraphLines)
{
const GlyphRun& rf = glyphRuns[line.startRunIndex];
uint32_t indexFrom = rf.textIndices[line.startGlyphIndex];
const GlyphRun& rt = glyphRuns[line.endRunIndex];
uint32_t endGlyphIndex = line.endGlyphIndex == 0 ? 0 : line.endGlyphIndex - 1;
uint32_t indexTo = rt.textIndices[endGlyphIndex];
indexTo += glyphLookup.count(indexTo);
addRange(indexFrom, indexTo, start, end);
}
}
m_unitCharacterIndices.push_back(end);
}