blob: 14778a559e7b9615d8aef87299777d2c5085f037 [file] [log] [blame]
#include "rive/text/text.hpp"
#include "rive/text/text_modifier_group.hpp"
#include "rive/text/text_shape_modifier.hpp"
#include "rive/text/text_modifier_range.hpp"
#include "rive/text/glyph_lookup.hpp"
#include "rive/text/text_style.hpp"
#include "rive/artboard.hpp"
#include <limits>
using namespace rive;
StatusCode TextModifierGroup::onAddedDirty(CoreContext* context)
{
StatusCode code = Super::onAddedDirty(context);
if (code != StatusCode::Ok)
{
return code;
}
if (parent() != nullptr && parent()->is<Text>())
{
parent()->as<Text>()->addModifierGroup(this);
return StatusCode::Ok;
}
return StatusCode::MissingObject;
}
void TextModifierGroup::addModifierRange(TextModifierRange* range) { m_ranges.push_back(range); }
void TextModifierGroup::addModifier(TextModifier* modifier)
{
m_modifiers.push_back(modifier);
if (modifier->is<TextShapeModifier>())
{
m_shapeModifiers.push_back(modifier->as<TextShapeModifier>());
}
}
void TextModifierGroup::rangeTypeChanged()
{
parent()->as<Text>()->modifierShapeDirty();
addDirt(ComponentDirt::TextCoverage);
}
void TextModifierGroup::shapeModifierChanged() { parent()->as<Text>()->markShapeDirty(); }
void TextModifierGroup::rangeChanged()
{
/// Marking shape dirty should only be done if this modifer group changes
/// shaping properties (for now we're just testing and we're hardcoding a
/// shaping change).
if (!m_shapeModifiers.empty())
{
parent()->as<Text>()->modifierShapeDirty();
}
else
{
parent()->as<Text>()->markPaintDirty();
}
addDirt(ComponentDirt::TextCoverage);
}
/// Clear any cached selector range maps so they can be recomputed after next
/// shaping.
void TextModifierGroup::clearRangeMaps()
{
for (TextModifierRange* range : m_ranges)
{
range->clearRangeMap();
}
addDirt(ComponentDirt::TextCoverage);
}
void TextModifierGroup::computeRangeMap(Span<const Unichar> text,
const SimpleArray<Paragraph>& shape,
const SimpleArray<SimpleArray<GlyphLine>>& lines,
const GlyphLookup& glyphLookup)
{
for (TextModifierRange* range : m_ranges)
{
range->computeRange(text, shape, lines, glyphLookup);
}
}
void TextModifierGroup::computeCoverage(uint32_t textSize)
{
if (!hasDirt(ComponentDirt::TextCoverage))
{
return;
}
// Because we're not dependent on anything we need to reset our dirt
// ourselves. We're not in the DAG so we'll never get reset.
m_Dirt = ComponentDirt::None;
// When the text re-shapes, we udpate our coverage values.
m_coverage.resize(textSize);
std::fill(m_coverage.begin(), m_coverage.end(), 0);
for (TextModifierRange* range : m_ranges)
{
range->computeCoverage(m_coverage);
}
}
float TextModifierGroup::glyphCoverage(uint32_t textIndex, uint32_t codePointCount)
{
assert(codePointCount >= 1);
float c = coverage(textIndex);
for (uint32_t i = 1; i < codePointCount; i++)
{
c += coverage(textIndex + i);
}
return c /= (float)codePointCount;
}
void TextModifierGroup::transform(float amount, Mat2D& ctm)
{
if (amount == 0.0f || !modifiesTransform())
{
return;
}
float actualRotation = modifiesRotation() ? rotation() * amount : 0.0f;
Mat2D transform = actualRotation != 0.0f ? Mat2D::fromRotation(actualRotation) : Mat2D();
if (modifiesTranslation())
{
transform[4] = x() * amount;
transform[5] = y() * amount;
}
if (modifiesScale())
{
float iamount = 1.0f - amount;
transform.scaleByValues(iamount + scaleX() * amount, iamount + scaleY() * amount);
}
bool doesModifyOrigin = modifiesOrigin();
if (doesModifyOrigin)
{
ctm[4] += originX();
ctm[5] += originY();
}
ctm = transform * ctm;
if (doesModifyOrigin)
{
ctm[4] -= originX();
ctm[5] -= originY();
}
}
float TextModifierGroup::computeOpacity(float current, float t) const
{
if ((modifierFlags() & (uint32_t)TextModifierFlags::invertOpacity) != 0)
{
return current * (1.0f - t) + opacity() * t;
}
else
{
return current * opacity() * t;
}
}
void TextModifierGroup::modifierFlagsChanged() { parent()->as<Text>()->markPaintDirty(); }
void TextModifierGroup::originXChanged() { parent()->as<Text>()->markPaintDirty(); }
void TextModifierGroup::originYChanged() { parent()->as<Text>()->markPaintDirty(); }
void TextModifierGroup::opacityChanged() { parent()->as<Text>()->markPaintDirty(); }
void TextModifierGroup::xChanged() { parent()->as<Text>()->markPaintDirty(); }
void TextModifierGroup::yChanged() { parent()->as<Text>()->markPaintDirty(); }
void TextModifierGroup::rotationChanged() { parent()->as<Text>()->markPaintDirty(); }
void TextModifierGroup::scaleXChanged() { parent()->as<Text>()->markPaintDirty(); }
void TextModifierGroup::scaleYChanged() { parent()->as<Text>()->markPaintDirty(); }
static TextRun copyRun(const TextRun& source, uint32_t unicharCount)
{
return {
source.font,
source.size,
source.lineHeight,
source.letterSpacing,
unicharCount,
source.script,
source.styleId,
source.dir,
};
}
TextRun TextModifierGroup::modifyShape(const Text& text, TextRun run, float strength)
{
const TextStyle* style = text.styleFromShaperId(run.styleId);
if (style == nullptr || style->font() == nullptr)
{
return run;
}
Font* font = style->font().get();
std::unordered_map<uint32_t, float> variations;
float fontSize = run.size;
for (TextShapeModifier* shapeModifier : m_shapeModifiers)
{
fontSize = shapeModifier->modify(font, variations, fontSize, strength);
}
if (!variations.empty())
{
m_variationCoords.clear();
for (auto itr = variations.begin(); itr != variations.end(); itr++)
{
m_variationCoords.push_back({itr->first, itr->second});
}
m_variableFont = font->makeAtCoords(m_variationCoords);
return {
m_variableFont,
run.size,
run.lineHeight,
run.letterSpacing,
run.unicharCount,
run.script,
run.styleId,
run.dir,
};
}
else
{
m_variableFont = nullptr;
}
return run;
}
void TextModifierGroup::applyShapeModifiers(const Text& text, StyledText& styledText)
{
if (m_shapeModifiers.empty())
{
return;
}
m_nextTextRuns.clear();
m_nextTextRuns.reserve(styledText.runs().size());
uint32_t index = 0;
float lastCoverage = std::numeric_limits<float>::max();
uint32_t extractRunIndex = 0;
for (const TextRun& run : styledText.runs())
{
// Split the run into sub-runs as necessary based on equal coverage
// values.
uint32_t end = index + run.unicharCount;
while (index < end)
{
float coverage = m_coverage[index];
if (coverage != lastCoverage)
{
if (index - extractRunIndex != 0)
{
// Add new run from extractRunStart to index (exclusive)
if (lastCoverage == 0.0f)
{
m_nextTextRuns.push_back(copyRun(run, index - extractRunIndex));
}
else
{
m_nextTextRuns.push_back(
modifyShape(text, copyRun(run, index - extractRunIndex), lastCoverage));
}
}
lastCoverage = coverage;
extractRunIndex = index;
}
index++;
}
assert(extractRunIndex != end);
// Add new run from extractRunStart to index (exclusive)
if (lastCoverage == 0.0f)
{
m_nextTextRuns.push_back(copyRun(run, end - extractRunIndex));
}
else
{
m_nextTextRuns.push_back(
modifyShape(text, copyRun(run, end - extractRunIndex), lastCoverage));
}
extractRunIndex = end;
}
styledText.swapRuns(m_nextTextRuns);
// return nextTextRuns;
}
bool TextModifierGroup::needsShape() const
{
if (!m_shapeModifiers.empty())
{
return true;
}
for (const TextModifierRange* range : m_ranges)
{
if (range->needsShape())
{
return true;
}
}
return false;
}