| #include "rive/text/cursor.hpp" |
| #ifdef WITH_RIVE_TEXT |
| using namespace rive; |
| |
| static uint32_t absDiffUint(uint32_t a, uint32_t b) |
| { |
| return a > b ? a - b : b - a; |
| } |
| |
| static uint32_t subtractUint32(uint32_t a, uint32_t b) |
| { |
| return a > b ? a - b : 0; |
| } |
| |
| CursorVisualPosition CursorPosition::visualPosition( |
| const FullyShapedText& shape) const |
| { |
| const GlyphLookup& glyphLookup = shape.glyphLookup(); |
| const std::vector<OrderedLine>& orderedLines = shape.orderedLines(); |
| |
| uint32_t targetIndex = glyphLookup[m_codePointIndex]; |
| if (m_lineIndex < 0 || m_lineIndex >= orderedLines.size()) |
| { |
| return CursorVisualPosition::missing(); |
| } |
| const OrderedLine& orderedLine = orderedLines[m_lineIndex]; |
| |
| const GlyphLine& line = orderedLine.glyphLine(); |
| float x = line.startX; |
| |
| // Iterate each glyph and see if it's the one we're looking for. |
| bool haveFirstIndex = false; |
| uint32_t firstTextIndex = 0, lastTextIndex = 0; |
| for (auto glyphItr : orderedLine) |
| { |
| const GlyphRun* run = std::get<0>(glyphItr); |
| size_t glyphIndex = std::get<1>(glyphItr); |
| float advance = run->advances[glyphIndex]; |
| |
| if (advance != 0 && |
| targetIndex == glyphLookup[run->textIndices[glyphIndex]]) |
| { |
| x += advance * |
| glyphLookup.advanceFactor(m_codePointIndex, |
| run->dir() == TextDirection::rtl); |
| |
| const Font* font = run->font.get(); |
| return CursorVisualPosition( |
| x, |
| orderedLine.y() + font->ascent(run->size), |
| orderedLine.y() + font->descent(run->size)); |
| } |
| else |
| { |
| if (!haveFirstIndex) |
| { |
| firstTextIndex = lastTextIndex = run->textIndices[glyphIndex]; |
| haveFirstIndex = true; |
| } |
| else |
| { |
| lastTextIndex = run->textIndices[glyphIndex]; |
| } |
| x += advance; |
| } |
| } |
| |
| // Didn't find the glyph, we're at the end of the line. |
| const GlyphRun* run = orderedLine.lastRun(); |
| const Font* font = run->font.get(); |
| |
| return CursorVisualPosition( |
| absDiffUint(m_codePointIndex, firstTextIndex) < |
| absDiffUint(m_codePointIndex, lastTextIndex) |
| ? line.startX |
| : x, |
| orderedLine.y() + font->ascent(run->size), |
| orderedLine.y() + font->descent(run->size)); |
| } |
| |
| CursorPosition CursorPosition::fromTranslation(const Vec2D translation, |
| const FullyShapedText& shape) |
| { |
| const std::vector<OrderedLine>& orderedLines = shape.orderedLines(); |
| if (orderedLines.empty()) |
| { |
| return CursorPosition::zero(); |
| } |
| |
| uint32_t lineIndex = 0; |
| uint32_t maxLine = (uint32_t)(orderedLines.size() - 1); |
| for (const OrderedLine& orderedLine : orderedLines) |
| { |
| if (orderedLine.bottom() < translation.y && lineIndex != maxLine) |
| { |
| lineIndex++; |
| continue; |
| } |
| return fromOrderedLine(orderedLine, lineIndex, translation.x, shape); |
| } |
| |
| return CursorPosition::zero(); |
| } |
| |
| CursorPosition CursorPosition::fromOrderedLine(const OrderedLine& orderedLine, |
| uint32_t lineIndex, |
| float translationX, |
| const FullyShapedText& shape) |
| { |
| const GlyphLookup& glyphLookup = shape.glyphLookup(); |
| |
| float x = orderedLine.glyphLine().startX; |
| /// Iterate each glyph and see if it's the one we're looking for. |
| auto end = orderedLine.end(); |
| GlyphItr lastGlyphItr = orderedLine.begin(); |
| for (GlyphItr itr = orderedLine.begin(); itr != end; ++itr) |
| { |
| lastGlyphItr = itr; |
| const GlyphRun* run = itr.run(); |
| uint32_t glyphIndex = itr.glyphIndex(); |
| float advance = run->advances[glyphIndex]; |
| if (translationX <= x + advance) |
| { |
| float ratio = |
| advance == 0.0f |
| ? 1.0f |
| : std::max(0.0f, |
| std::min((translationX - x) / advance, 1.0f)); |
| uint32_t textIndex = run->textIndices[glyphIndex]; |
| uint32_t nextTextIndex = textIndex; |
| uint32_t absoluteGlyphIndex = glyphLookup[textIndex]; |
| while (nextTextIndex != |
| subtractUint32((uint32_t)glyphLookup.size(), 1) && |
| glyphLookup[nextTextIndex] == absoluteGlyphIndex) |
| { |
| nextTextIndex++; |
| } |
| uint32_t parts = nextTextIndex - textIndex; |
| uint32_t part = (uint32_t)std::round(ratio * (float)parts); |
| |
| return CursorPosition( |
| lineIndex, |
| run->dir() == TextDirection::ltr |
| ? textIndex + part |
| : (part > nextTextIndex ? 0 : nextTextIndex - part)) |
| .clamped(shape); |
| } |
| else |
| { |
| x += advance; |
| } |
| } |
| |
| const GlyphRun* run = lastGlyphItr.run(); |
| uint32_t glyphIndex = lastGlyphItr.glyphIndex(); |
| |
| uint32_t textIndex = run->textIndices[glyphIndex]; |
| uint32_t nextTextIndex = textIndex; |
| uint32_t absoluteGlyphIndex = glyphLookup[textIndex]; |
| while (nextTextIndex != subtractUint32((uint32_t)glyphLookup.size(), 1) && |
| glyphLookup[nextTextIndex] == absoluteGlyphIndex) |
| { |
| nextTextIndex++; |
| } |
| uint32_t parts = nextTextIndex - textIndex; |
| return CursorPosition(lineIndex, |
| run->dir() == TextDirection::ltr |
| ? textIndex + parts |
| : nextTextIndex - parts) |
| .clamped(shape); |
| } |
| |
| CursorPosition CursorPosition::fromLineX(uint32_t lineIndex, |
| float x, |
| const FullyShapedText& shape) |
| { |
| const std::vector<OrderedLine>& orderedLines = shape.orderedLines(); |
| if (lineIndex >= orderedLines.size()) |
| { |
| return CursorPosition::zero(); |
| } |
| |
| return fromOrderedLine(orderedLines[lineIndex], lineIndex, x, shape); |
| } |
| |
| uint32_t CursorPosition::lineIndex(int32_t inc) const |
| { |
| if (inc < 0 && (uint32_t)(-inc) > m_lineIndex) |
| { |
| return 0; |
| } |
| return m_lineIndex + inc; |
| } |
| |
| uint32_t CursorPosition::codePointIndex(int32_t inc) const |
| { |
| if (inc < 0 && (uint32_t)(-inc) > m_codePointIndex) |
| { |
| return 0; |
| } |
| return m_codePointIndex + inc; |
| } |
| |
| CursorPosition CursorPosition::clamped(const FullyShapedText& shape) const |
| { |
| return CursorPosition( |
| std::min(m_lineIndex, |
| subtractUint32((uint32_t)shape.orderedLines().size(), 1)), |
| std::min(m_codePointIndex, |
| subtractUint32(shape.glyphLookup().lastCodeUnitIndex(), 1))); |
| } |
| |
| CursorPosition CursorPosition::atIndex(uint32_t codePointIndex, |
| const FullyShapedText& shape) |
| { |
| // Don't go to actual last code unit index as we always insert a zero width |
| // space. |
| // https://en.wikipedia.org/wiki/Zero-width_space |
| if (codePointIndex >= |
| subtractUint32(shape.glyphLookup().lastCodeUnitIndex(), 1)) |
| { |
| return CursorPosition( |
| subtractUint32((uint32_t)shape.orderedLines().size(), 1), |
| subtractUint32(shape.glyphLookup().lastCodeUnitIndex(), 1)); |
| } |
| |
| const SimpleArray<Paragraph>& paragraphs = shape.paragraphs(); |
| const SimpleArray<SimpleArray<GlyphLine>>& paragraphLines = |
| shape.paragraphLines(); |
| |
| uint32_t paragraphIndex = 0; |
| uint32_t lineIndex = 0; |
| for (const SimpleArray<GlyphLine>& lines : paragraphLines) |
| { |
| const Paragraph& paragraph = paragraphs[paragraphIndex++]; |
| for (const GlyphLine& line : lines) |
| { |
| const GlyphRun& run = paragraph.runs[line.startRunIndex]; |
| uint32_t smallestTextIndexInLine = |
| run.textIndices[line.startGlyphIndex]; |
| |
| if (smallestTextIndexInLine <= codePointIndex) |
| { |
| lineIndex++; |
| continue; |
| } |
| return CursorPosition(lineIndex - 1, codePointIndex).clamped(shape); |
| } |
| } |
| |
| return CursorPosition(lineIndex - 1, codePointIndex).clamped(shape); |
| } |
| |
| void Cursor::selectionRects(std::vector<AABB>& rects, |
| const FullyShapedText& shape) const |
| { |
| auto firstPosition = first().clamped(shape); |
| auto lastPosition = last().clamped(shape); |
| |
| uint32_t firstLine = firstPosition.lineIndex(); |
| uint32_t lastLine = lastPosition.lineIndex(); |
| uint32_t firstCodePointIndex = firstPosition.codePointIndex(); |
| uint32_t lastCodePointIndex = lastPosition.codePointIndex(); |
| |
| const GlyphLookup& glyphLookup = shape.glyphLookup(); |
| const std::vector<OrderedLine>& orderedLines = shape.orderedLines(); |
| for (uint32_t lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) |
| { |
| const OrderedLine& orderedLine = orderedLines[lineIndex]; |
| const GlyphLine& glyphLine = orderedLine.glyphLine(); |
| float x = glyphLine.startX; |
| for (auto glyphItr : orderedLine) |
| { |
| const GlyphRun* run = std::get<0>(glyphItr); |
| size_t glyphIndex = std::get<1>(glyphItr); |
| float advance = run->advances[glyphIndex]; |
| uint32_t codePointIndex = run->textIndices[glyphIndex]; |
| uint32_t count = glyphLookup.count(codePointIndex); |
| uint32_t endCodePointIndex = codePointIndex + count; |
| float y = orderedLine.y(); |
| |
| // Check if this part of this glyph overlaps selection. |
| if (lastCodePointIndex > codePointIndex && |
| endCodePointIndex > firstCodePointIndex) |
| { |
| uint32_t after = |
| subtractUint32(firstCodePointIndex, codePointIndex); |
| uint32_t before = |
| subtractUint32(endCodePointIndex, lastCodePointIndex); |
| float startFactor = (float)after / (float)count; |
| float endFactor = (float)(count - before) / (float)count; |
| if (run->dir() == TextDirection::rtl) |
| { |
| startFactor = 1.0f - startFactor; |
| endFactor = 1.0f - endFactor; |
| } |
| |
| auto font = run->font; |
| float left = x + advance * startFactor; |
| float right = x + advance * endFactor; |
| if (left > right) |
| { |
| float swap = right; |
| right = left; |
| left = swap; |
| } |
| rects.push_back(AABB(left, |
| y + font->ascent(run->size), |
| right, |
| y + font->descent(run->size))); |
| } |
| x += advance; |
| } |
| } |
| } |
| |
| void Cursor::updateSelectionPath(ShapePaintPath& path, |
| const std::vector<AABB>& rects, |
| const FullyShapedText& shape) const |
| {} |
| |
| bool Cursor::resolveLinePositions(const FullyShapedText& shape) |
| { |
| bool resolved = false; |
| if (!m_start.hasLineIndex()) |
| { |
| m_start.resolveLine(shape); |
| resolved = true; |
| } |
| if (!m_end.hasLineIndex()) |
| { |
| m_end.resolveLine(shape); |
| resolved = true; |
| } |
| return resolved; |
| } |
| |
| void CursorPosition::resolveLine(const FullyShapedText& shape) |
| { |
| const GlyphLookup& glyphLookup = shape.glyphLookup(); |
| const std::vector<OrderedLine>& orderedLines = shape.orderedLines(); |
| |
| uint32_t lineIndex = 0; |
| for (const OrderedLine& orderedLine : orderedLines) |
| { |
| if (orderedLine.containsCodePointIndex(glyphLookup, m_codePointIndex)) |
| { |
| break; |
| } |
| lineIndex++; |
| } |
| m_lineIndex = lineIndex; |
| } |
| |
| bool Cursor::contains(uint32_t codePointIndex) const |
| { |
| return codePointIndex >= first().codePointIndex() && |
| codePointIndex < last().codePointIndex(); |
| } |
| #endif |