blob: 68b5852596a666148e80f4409711dfdff931f481 [file] [log] [blame]
#include "rive/text_engine.hpp"
#include "rive/text/utf.hpp"
#include "rive/text/glyph_lookup.hpp"
#ifdef WITH_RIVE_TEXT
using namespace rive;
static void appendUnicode(std::vector<rive::Unichar>& unichars,
const char text[])
{
const uint8_t* ptr = (const uint8_t*)text;
while (*ptr)
{
unichars.push_back(rive::UTF::NextUTF8(&ptr));
}
}
static void reverseRuns(const GlyphRun** runs, int count)
{
int halfCount = count / 2;
int finalIndex = count - 1;
for (int index = 0; index < halfCount; index++)
{
int tieIndex = finalIndex - index;
const GlyphRun* tempRun = runs[index];
runs[index] = runs[tieIndex];
runs[tieIndex] = tempRun;
}
}
OrderedLine::OrderedLine(const Paragraph& paragraph,
const GlyphLine& line,
float lineWidth,
bool wantEllipsis,
bool isEllipsisLineLast,
GlyphRun* ellipsisRun,
float y) :
m_startGlyphIndex(line.startGlyphIndex),
m_endGlyphIndex(line.endGlyphIndex),
m_glyphLine(&line),
m_y(y)
{
std::vector<const GlyphRun*> logicalRuns;
const SimpleArray<GlyphRun>& glyphRuns = paragraph.runs;
if (!wantEllipsis || !buildEllipsisRuns(logicalRuns,
paragraph,
line,
lineWidth,
isEllipsisLineLast,
ellipsisRun))
{
for (uint32_t i = line.startRunIndex; i < line.endRunIndex + 1; i++)
{
logicalRuns.push_back(&glyphRuns[i]);
}
if (!logicalRuns.empty())
{
m_startLogical = logicalRuns.front();
m_endLogical = logicalRuns.back();
}
}
uint8_t maxLevel = 0;
for (auto run : logicalRuns)
{
if (run->level > maxLevel)
{
maxLevel = run->level;
}
}
for (uint8_t newLevel = maxLevel; newLevel > 0; newLevel--)
{
for (int start = (int)(logicalRuns.size()) - 1; start >= 0; start--)
{
if (logicalRuns[start]->level >= newLevel)
{
int count = 1;
for (; start > 0 && logicalRuns[start - 1]->level >= newLevel;
start--)
{
count++;
}
reverseRuns(logicalRuns.data() + start, count);
}
}
}
m_runs = std::move(logicalRuns);
}
uint32_t OrderedLine::firstCodePointIndex(const GlyphLookup& glyphLookup) const
{
GlyphItr index = begin();
auto glyphIndex = index.glyphIndex();
auto run = index.run();
uint32_t firstCodePointIndex = run->textIndices[glyphIndex];
if (run->dir() == TextDirection::rtl)
{
// If the final run is RTL we want to add the length of the final glyph
// to get the actual last available code unit in the line.
firstCodePointIndex += glyphLookup.count(run->textIndices[glyphIndex]);
}
// Clamp ignoring last zero width space for editing.
return std::min(firstCodePointIndex, glyphLookup.lastCodeUnitIndex() - 1);
}
uint32_t OrderedLine::lastCodePointIndex(const GlyphLookup& glyphLookup) const
{
GlyphItr index = begin();
GlyphItr lastIndex = index;
while (index != end())
{
lastIndex = index;
++index;
}
auto glyphIndex = lastIndex.glyphIndex();
auto run = lastIndex.run();
uint32_t lastCodePointIndex = run->textIndices[glyphIndex];
if (run->dir() == TextDirection::ltr)
{
lastCodePointIndex += glyphLookup.count(run->textIndices[glyphIndex]);
}
// Clamp ignoring last zero width space for editing.
return std::min(lastCodePointIndex, glyphLookup.lastCodeUnitIndex() - 1);
}
bool OrderedLine::containsCodePointIndex(const GlyphLookup& glyphLookup,
uint32_t codePointIndex) const
{
return codePointIndex >= firstCodePointIndex(glyphLookup) &&
codePointIndex <= lastCodePointIndex(glyphLookup);
}
void GlyphItr::tryAdvanceRun()
{
while (true)
{
auto run = *m_run;
if (m_glyphIndex == m_line->endGlyphIndex(run) &&
run != m_line->lastRun())
{
m_run++;
m_glyphIndex = m_line->startGlyphIndex(*m_run);
}
else
{
break;
}
}
}
GlyphItr& GlyphItr::operator++()
{
auto run = *m_run;
m_glyphIndex += run->dir() == TextDirection::ltr ? 1 : -1;
tryAdvanceRun();
return *this;
}
bool OrderedLine::buildEllipsisRuns(std::vector<const GlyphRun*>& logicalRuns,
const Paragraph& paragraph,
const GlyphLine& line,
float lineWidth,
bool isEllipsisLineLast,
GlyphRun* storedEllipsisRun)
{
float x = 0.0f;
const SimpleArray<GlyphRun>& glyphRuns = paragraph.runs;
uint32_t startGIndex = line.startGlyphIndex;
// If it's the last line we can actually early out if the whole things fits,
// so check that first with no extra shaping.
if (isEllipsisLineLast)
{
bool fits = true;
for (uint32_t i = line.startRunIndex; i < line.endRunIndex + 1; i++)
{
const GlyphRun& run = glyphRuns[i];
uint32_t endGIndex = i == line.endRunIndex
? line.endGlyphIndex
: (uint32_t)run.glyphs.size();
for (uint32_t j = startGIndex; j != endGIndex; j++)
{
x += run.advances[j];
if (x > lineWidth)
{
fits = false;
goto measured;
}
}
startGIndex = 0;
}
measured:
if (fits)
{
// It fits, just get the regular glyphs.
return false;
}
}
std::vector<Unichar> ellipsisCodePoints;
appendUnicode(ellipsisCodePoints, "...");
rcp<Font> ellipsisFont = nullptr;
float ellipsisFontSize = 0.0f;
GlyphRun ellipsisRun = {};
float ellipsisWidth = 0.0f;
bool ellipsisOverflowed = false;
startGIndex = line.startGlyphIndex;
x = 0.0f;
for (uint32_t i = line.startRunIndex; i < line.endRunIndex + 1; i++)
{
const GlyphRun& run = glyphRuns[i];
if (run.font != ellipsisFont && run.size != ellipsisFontSize)
{
// Track the latest we've checked (even if we discard it so we don't
// try to do this again for this ellipsis).
ellipsisFont = run.font;
ellipsisFontSize = run.size;
// Get the next shape so we can check if it fits, otherwise keep
// using the last one.
TextRun ellipsisRuns[] = {{
ellipsisFont,
ellipsisFontSize,
run.lineHeight,
run.letterSpacing,
(uint32_t)ellipsisCodePoints.size(),
0, // default, TextRun.script
run.styleId,
}};
auto nextEllipsisShape =
ellipsisFont->shapeText(ellipsisCodePoints,
Span<TextRun>(ellipsisRuns, 1));
// Hard assumption one run and para
const Paragraph& para = nextEllipsisShape[0];
const GlyphRun& nextEllipsisRun = para.runs.front();
float nextEllipsisWidth = 0;
for (size_t j = 0; j < nextEllipsisRun.glyphs.size(); j++)
{
nextEllipsisWidth += nextEllipsisRun.advances[j];
}
if (ellipsisRun.font == nullptr ||
x + nextEllipsisWidth <= lineWidth)
{
// This ellipsis still fits, go ahead and use it. Otherwise
// stick with the old one.
ellipsisWidth = nextEllipsisWidth;
ellipsisRun = std::move(para.runs.front());
}
}
uint32_t endGIndex = i == line.endRunIndex
? line.endGlyphIndex
: (uint32_t)run.glyphs.size();
for (uint32_t j = startGIndex; j != endGIndex; j++)
{
float advance = run.advances[j];
if (x + advance + ellipsisWidth > lineWidth)
{
m_endGlyphIndex = j;
ellipsisOverflowed = true;
break;
}
x += advance;
}
startGIndex = 0;
logicalRuns.push_back(&run);
m_endLogical = &run;
if (ellipsisOverflowed && ellipsisRun.font != nullptr)
{
*storedEllipsisRun = std::move(ellipsisRun);
logicalRuns.push_back(storedEllipsisRun);
break;
}
}
// There was enough space for it, so let's add the ellipsis (if we didn't
// already). Note that we already checked if this is the last line and found
// that the whole text didn't fit.
if (!ellipsisOverflowed && ellipsisRun.font != nullptr)
{
*storedEllipsisRun = std::move(ellipsisRun);
logicalRuns.push_back(storedEllipsisRun);
}
m_startLogical = storedEllipsisRun == logicalRuns.front()
? nullptr
: logicalRuns.front();
return true;
}
float OrderedLine::bottom() const
{
return m_y - m_glyphLine->baseline + m_glyphLine->bottom;
}
#endif