| #ifdef WITH_RIVE_TEXT |
| #include "rive/text/fully_shaped_text.hpp" |
| #include "rive/text/text.hpp" |
| #include <algorithm> |
| #include <limits> |
| |
| using namespace rive; |
| |
| void FullyShapedText::shape(Span<Unichar> text, |
| Span<TextRun> runs, |
| TextSizing sizing, |
| float maxWidth, |
| float maxHeight, |
| TextAlign alignment, |
| TextWrap wrap, |
| TextOrigin origin, |
| TextOverflow overflow, |
| float paragraphSpacing, |
| float alignWidth, |
| VerticalTextAlign verticalAlignment, |
| float alignHeight) |
| { |
| m_verticalOffset = 0.0f; |
| m_paragraphs = runs[0].font->shapeText(text, runs); |
| m_glyphLookup.compute(text, m_paragraphs); |
| |
| m_paragraphLines = |
| Text::BreakLines(m_paragraphs, |
| sizing == TextSizing::autoWidth ? -1.0f : maxWidth, |
| alignment, |
| wrap, |
| alignWidth); |
| m_orderedLines.clear(); |
| m_ellipsisRun = {}; |
| |
| // build render styles. |
| if (m_paragraphs.empty()) |
| { |
| m_bounds = AABB(0.0f, 0.0f, 0.0f, 0.0f); |
| return; |
| } |
| |
| // Build up ordered runs as we go. |
| int32_t paragraphIndex = 0; |
| float y = 0.0f; |
| float minY = 0.0f; |
| // Lines don't start at 0 when they're aligned center/right, so track the |
| // real horizontal extents rather than assuming [0, widest line]. |
| float minX = std::numeric_limits<float>::max(); |
| float maxX = -std::numeric_limits<float>::max(); |
| if (origin == TextOrigin::baseline && !m_paragraphLines.empty() && |
| !m_paragraphLines[0].empty()) |
| { |
| y -= m_paragraphLines[0][0].baseline; |
| minY = y; |
| } |
| |
| int ellipsisLine = -1; |
| bool isEllipsisLineLast = false; |
| // Find the line to put the ellipsis on (line before the one that |
| // overflows). |
| bool wantEllipsis = |
| overflow == TextOverflow::ellipsis && sizing == TextSizing::fixed; |
| |
| int lastLineIndex = -1; |
| for (const SimpleArray<GlyphLine>& paragraphLines : m_paragraphLines) |
| { |
| const Paragraph& paragraph = m_paragraphs[paragraphIndex++]; |
| for (const GlyphLine& line : paragraphLines) |
| { |
| const GlyphRun& endRun = paragraph.runs[line.endRunIndex]; |
| const GlyphRun& startRun = paragraph.runs[line.startRunIndex]; |
| float width = endRun.xpos[line.endGlyphIndex] - |
| startRun.xpos[line.startGlyphIndex]; |
| minX = std::min(minX, line.startX); |
| maxX = std::max(maxX, line.startX + width); |
| lastLineIndex++; |
| if (wantEllipsis && y + line.bottom <= maxHeight) |
| { |
| ellipsisLine++; |
| } |
| } |
| |
| if (!paragraphLines.empty()) |
| { |
| y += paragraphLines.back().bottom; |
| } |
| y += paragraphSpacing; |
| } |
| if (wantEllipsis && ellipsisLine == -1) |
| { |
| // Nothing fits, just show the first line and ellipse it. |
| ellipsisLine = 0; |
| } |
| isEllipsisLineLast = lastLineIndex == ellipsisLine; |
| |
| int32_t lineIndex = 0; |
| if (minX > maxX) |
| { |
| // Paragraphs but no lines. |
| minX = maxX = 0.0f; |
| } |
| float maxY = std::max(minY, y - paragraphSpacing); |
| |
| // Align the block within the field the same way lines align within it |
| // horizontally: only while it fits, so text taller than the field stays |
| // at the top and leaves vertical scrolling alone. |
| float slack = alignHeight - (maxY - minY); |
| if (slack > 0.0f) |
| { |
| switch (verticalAlignment) |
| { |
| case VerticalTextAlign::top: |
| break; |
| case VerticalTextAlign::middle: |
| m_verticalOffset = slack / 2.0f; |
| break; |
| case VerticalTextAlign::bottom: |
| m_verticalOffset = slack; |
| break; |
| } |
| } |
| |
| m_bounds = |
| AABB(minX, minY + m_verticalOffset, maxX, maxY + m_verticalOffset); |
| |
| // Left unshifted so the overflow checks below still measure the text |
| // against the box; only the line positions carry the alignment offset. |
| y = 0; |
| if (origin == TextOrigin::baseline && !m_paragraphLines.empty() && |
| !m_paragraphLines[0].empty()) |
| { |
| y -= m_paragraphLines[0][0].baseline; |
| } |
| paragraphIndex = 0; |
| |
| for (const SimpleArray<GlyphLine>& paragraphLines : m_paragraphLines) |
| { |
| const Paragraph& paragraph = m_paragraphs[paragraphIndex++]; |
| for (const GlyphLine& line : paragraphLines) |
| { |
| switch (overflow) |
| { |
| case TextOverflow::hidden: |
| if (sizing == TextSizing::fixed && |
| y + line.bottom > maxHeight) |
| { |
| return; |
| } |
| break; |
| case TextOverflow::clipped: |
| if (sizing == TextSizing::fixed && y + line.top > maxHeight) |
| { |
| return; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| m_orderedLines.emplace_back( |
| OrderedLine(paragraph, |
| line, |
| maxWidth, |
| ellipsisLine == lineIndex, |
| isEllipsisLineLast, |
| &m_ellipsisRun, |
| y + line.baseline + m_verticalOffset)); |
| |
| if (lineIndex == ellipsisLine) |
| { |
| return; |
| } |
| lineIndex++; |
| } |
| if (!paragraphLines.empty()) |
| { |
| y += paragraphLines.back().bottom; |
| } |
| y += paragraphSpacing; |
| } |
| return; |
| } |
| #endif |