Skip to content

Commit 708e7c6

Browse files
authored
Merge pull request xbmc#27403 from sundermann/text-layout-optimization
[guilib] Optimize text layout rendering by reducing font width calculation
2 parents c7b4218 + 2d67aec commit 708e7c6

File tree

1 file changed

+93
-41
lines changed

1 file changed

+93
-41
lines changed

xbmc/guilib/GUITextLayout.cpp

Lines changed: 93 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#include "utils/StringUtils.h"
1717
#include "utils/log.h"
1818

19+
#include <algorithm>
20+
#include <functional>
1921
#include <limits>
2022

2123
CGUIString::CGUIString(iString start, iString end, bool carriageReturn)
@@ -579,63 +581,115 @@ void CGUITextLayout::WrapText(const vecText &text, float maxWidth)
579581
continue;
580582
}
581583

582-
vecText::const_iterator pos = line.m_text.begin();
583-
vecText::const_iterator lastBeginPos = line.m_text.begin();
584-
vecText::const_iterator lastSpacePos = line.m_text.end();
585-
vecText curLine;
586-
587-
while (pos < line.m_text.end())
584+
std::vector<character_t> tmp;
585+
tmp.reserve(line.m_text.size());
586+
auto widthOf = [&](const std::vector<character_t>::const_iterator start,
587+
const std::vector<character_t>::const_iterator end) -> float
588588
{
589-
// Get the current letter in the string
590-
const character_t& letter = *pos;
589+
if (start == end)
590+
return 0.0f;
591+
tmp.assign(start, end);
592+
return m_font->GetTextWidth(tmp);
593+
};
591594

592-
if (CanWrapAtLetter(letter)) // Check for a space char
593-
lastSpacePos = pos;
595+
auto skipLeadingSpaces = [&](vecText::const_iterator& it)
596+
{
597+
it = std::find_if_not(it, line.m_text.end(),
598+
std::bind_front(&CGUITextLayout::CanWrapAtLetter, this));
599+
};
594600

595-
curLine.emplace_back(letter);
601+
auto current = line.m_text.begin();
602+
skipLeadingSpaces(current);
596603

597-
const float currWidth = m_font->GetTextWidth(curLine);
604+
float currentWidth = 0.0f;
605+
std::vector<character_t>::const_iterator currentStart = current;
606+
// track end without trailing spaces
607+
std::vector<character_t>::const_iterator lastNonSpaceInLine = currentStart;
598608

599-
if (currWidth > maxWidth)
609+
while (current != line.m_text.end())
610+
{
611+
// Find next candidate wrap position
612+
auto wordEnd = std::find_if(current, line.m_text.end(),
613+
std::bind_front(&CGUITextLayout::CanWrapAtLetter, this));
614+
const bool hasSpace = (wordEnd != line.m_text.end());
615+
const float wordWidth = widthOf(current, wordEnd);
616+
const float spaceWidth = hasSpace ? widthOf(wordEnd, wordEnd + 1) : 0.0f;
617+
618+
// Try to include word + trailing space
619+
if (currentWidth + wordWidth + spaceWidth <= maxWidth)
600620
{
601-
if (lastSpacePos > pos) // No space char where split the line, so split by char
602-
{
603-
// If the pos is equal to lastBeginPos, maxWidth is not large enough to contain 1 character
604-
// Push a line with the single character and move on to the next character.
605-
if (pos == lastBeginPos)
606-
++pos;
607-
608-
CGUIString linePart{lastBeginPos, pos, false};
609-
m_lines.emplace_back(linePart);
610-
}
611-
else
612-
{
613-
CGUIString linePart{lastBeginPos, lastSpacePos, false};
614-
m_lines.emplace_back(linePart);
621+
currentWidth += wordWidth + spaceWidth;
622+
lastNonSpaceInLine = wordEnd; // exclude trailing space
623+
current = wordEnd;
624+
if (hasSpace)
625+
++current;
626+
continue;
627+
}
615628

616-
pos = lastSpacePos + 1;
617-
lastSpacePos = line.m_text.end();
618-
}
629+
// Try to include word without trailing space
630+
if (currentWidth + wordWidth <= maxWidth)
631+
{
632+
m_lines.emplace_back(currentStart, wordEnd, false);
633+
if (m_lines.size() >= nMaxLines)
634+
return;
619635

620-
curLine.clear();
621-
lastBeginPos = pos;
636+
current = wordEnd;
637+
skipLeadingSpaces(current);
638+
currentStart = current;
639+
currentWidth = 0.0f;
640+
lastNonSpaceInLine = currentStart;
641+
continue;
642+
}
622643

644+
// word itself doesn't fit after existing content: wrap before it (trim trailing spaces)
645+
if (currentWidth > 0.0f)
646+
{
647+
const std::vector<character_t>::const_iterator emitEnd =
648+
lastNonSpaceInLine > currentStart ? lastNonSpaceInLine : wordEnd;
649+
m_lines.emplace_back(currentStart, emitEnd, false);
623650
if (m_lines.size() >= nMaxLines)
624651
return;
625652

653+
// Start a new line; keep pos at start of the overflowing word
654+
skipLeadingSpaces(current);
655+
currentStart = current;
656+
currentWidth = 0.0f;
657+
lastNonSpaceInLine = currentStart;
626658
continue;
627659
}
628660

629-
++pos;
630-
}
661+
if (current == wordEnd)
662+
break;
631663

632-
// Add the remaining text part
633-
if (!curLine.empty())
634-
{
635-
CGUIString linePart{curLine.begin(), curLine.end(), false};
636-
m_lines.emplace_back(linePart);
664+
// current line is empty and word is too long: split by character using a safe linear scan.
665+
// Do not assume monotonic width because shaping/kerning can make width shrink or grow non-linearly.
666+
tmp.clear();
667+
size_t bestCount = 0;
668+
for (auto it = current; it != wordEnd; ++it)
669+
{
670+
tmp.push_back(*it);
671+
if (m_font->GetTextWidth(tmp) <= maxWidth)
672+
bestCount = tmp.size();
673+
}
674+
if (bestCount == 0)
675+
bestCount = 1; // ensure progress even if a single glyph is wider than maxWidth
676+
677+
const auto cut = current + static_cast<ptrdiff_t>(bestCount);
678+
m_lines.emplace_back(current, cut, false);
679+
if (m_lines.size() >= nMaxLines)
680+
return;
681+
682+
current = cut;
683+
skipLeadingSpaces(current);
684+
currentStart = current;
685+
currentWidth = 0.0f;
686+
lastNonSpaceInLine = currentStart;
637687
}
638688

689+
// Add remaining text part of this paragraph line
690+
if (currentStart < line.m_text.end())
691+
m_lines.emplace_back(currentStart, line.m_text.end(), false);
692+
639693
// Restore carriage return marker for the end of paragraph
640694
if (!m_lines.empty())
641695
m_lines.back().m_carriageReturn = line.m_carriageReturn;
@@ -767,5 +821,3 @@ void CGUITextLayout::Reset()
767821
m_lastUtf8Text.clear();
768822
m_textWidth = m_textHeight = 0;
769823
}
770-
771-

0 commit comments

Comments
 (0)