| #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 |