Skip to content

Commit

Permalink
Merge pull request xbmc#24799 from CastagnaIT/truncate_text_edit
Browse files Browse the repository at this point in the history
[guilib] Decouple left/right text truncate from alignment
  • Loading branch information
CastagnaIT authored Mar 15, 2024
2 parents 33a42c6 + 17af1e2 commit 9901571
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 106 deletions.
3 changes: 1 addition & 2 deletions xbmc/guilib/GUIEditControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ void CGUIEditControl::DefaultConstructor()
m_smsLastKey = 0;
m_smsKeyIndex = 0;
m_label2.GetLabelInfo().offsetX = 0;
m_label2.SetReversedTruncate(true); // Truncate the text by default on the left
m_isMD5 = false;
m_invalidInput = false;
m_inputValidator = NULL;
Expand Down Expand Up @@ -538,7 +537,7 @@ void CGUIEditControl::ProcessText(unsigned int currentTime)
if (HasFocus() || leftTextWidth == 0)
changed |= m_label2.SetOverflow(CGUILabel::OVER_FLOW_CLIP);
else
changed |= m_label2.SetOverflow(CGUILabel::OVER_FLOW_TRUNCATE);
changed |= m_label2.SetOverflow(CGUILabel::OVER_FLOW_TRUNCATE_LEFT);

changed |= m_label2.Process(currentTime);
CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
Expand Down
6 changes: 2 additions & 4 deletions xbmc/guilib/GUIFadeLabelControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,7 @@ void CGUIFadeLabelControl::Render()
float posX = m_posX + m_label.offsetX;
if (m_label.align & XBFONT_CENTER_X)
posX = m_posX + m_width * 0.5f;
else if (m_label.align & XBFONT_RIGHT)
posX = m_posX + m_width - m_textLayout.GetTextWidth();

m_textLayout.Render(posX, posY, m_label.angle, m_label.textColor, m_label.shadowColor, m_label.align, m_width - m_label.offsetX);
CGUIControl::Render();
return;
Expand All @@ -198,8 +197,7 @@ void CGUIFadeLabelControl::Render()
float posX = m_posX + m_label.offsetX;
if (m_label.align & XBFONT_CENTER_X)
posX = m_posX + m_width * 0.5f;
else if (m_label.align & XBFONT_RIGHT)
posX = m_posX + m_width - m_textLayout.GetTextWidth();

m_textLayout.Render(posX, posY, 0, m_label.textColor, m_label.shadowColor, m_label.align, m_width);
}
else
Expand Down
5 changes: 4 additions & 1 deletion xbmc/guilib/GUIFont.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,15 @@ class CGraphicContext;
///
/// Flags are used as bits to have several together, e.g. `XBFONT_LEFT | XBFONT_CENTER_Y`
///
// clang-format off
constexpr int XBFONT_LEFT = 0; ///< Align X left
constexpr int XBFONT_RIGHT = (1 << 0); ///< Align X right
constexpr int XBFONT_CENTER_X = (1 << 1); ///< Align X center
constexpr int XBFONT_CENTER_Y = (1 << 2); ///< Align Y center
constexpr int XBFONT_TRUNCATED = (1 << 3); ///< Truncated text
constexpr int XBFONT_TRUNCATED = (1 << 3); ///< Truncated text from right (text end with ellipses)
constexpr int XBFONT_JUSTIFIED = (1 << 4); ///< Justify text
constexpr int XBFONT_TRUNCATED_LEFT = (1 << 5); ///< Truncated text from left (text start with ellipses)
// clang-format on
/// @}

// flags for font style. lower 16 bits are the unicode code
Expand Down
200 changes: 127 additions & 73 deletions xbmc/guilib/GUIFontTTF.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,36 @@ constexpr int CHAR_CHUNK = 64; // 64 chars allocated at a time (2048 bytes)
constexpr int GLYPH_STRENGTH_BOLD = 24;
constexpr int GLYPH_STRENGTH_LIGHT = -48;
constexpr int TAB_SPACE_LENGTH = 4;

// \brief Check for conflicting alignments
void ValidateAlignments(uint32_t& aligns)
{
// Validate the horizontal alignment (XBFONT_LEFT is implicit unless otherwise specified)
{
const uint32_t hAligns = XBFONT_RIGHT | XBFONT_CENTER_X | XBFONT_JUSTIFIED;
const uint32_t commonFlags = hAligns & aligns;
// Check if at least 2 bits are set, it means multiple aligns
if ((commonFlags & (commonFlags - 1)) != 0)
{
CLog::LogF(LOGERROR, "Text with invalid multiple horizontal alignments");
aligns &= ~commonFlags;
}
}

// Validate truncate alignment
{
const uint32_t truncateAligns = XBFONT_TRUNCATED | XBFONT_TRUNCATED_LEFT;
const uint32_t commonFlags = truncateAligns & aligns;
// Check if at least 2 bits are set, it means multiple aligns
if ((commonFlags & (commonFlags - 1)) != 0)
{
CLog::LogF(LOGERROR, "Text with invalid multiple truncate alignments");
aligns &= ~commonFlags;
aligns |= XBFONT_TRUNCATED;
}
}
}

} /* namespace */

class CFreeTypeLibrary
Expand Down Expand Up @@ -386,6 +416,13 @@ void CGUIFontTTF::DrawTextInternal(CGraphicContext& context,

if (dirtyCache)
{
// Try to validate any conflicting alignments
//! @todo: This validate is the last resort and can result in a bad rendered text
//! because the alignment it is used also by caller components for other operations
//! this inform the problem on the log, potentially can be improved
//! by add validating alignments from each parent caller component
ValidateAlignments(alignment);

const std::vector<Glyph> glyphs = GetHarfBuzzShapedGlyphs(text);
// save the origin, which is scaled separately
m_originX = x;
Expand All @@ -409,6 +446,11 @@ void CGUIFontTTF::DrawTextInternal(CGraphicContext& context,
if (maxPixelWidth <= 0.0f || GetTextWidthInternal(text, glyphs) <= maxPixelWidth)
alignment &= ~XBFONT_TRUNCATED;
}
else if (alignment & XBFONT_TRUNCATED_LEFT)
{
if (maxPixelWidth <= 0.0f || GetTextWidthInternal(text, glyphs) <= maxPixelWidth)
alignment &= ~XBFONT_TRUNCATED_LEFT;
}
else if (alignment & XBFONT_JUSTIFIED)
{
if (maxPixelWidth <= 0.0f)
Expand All @@ -419,53 +461,76 @@ void CGUIFontTTF::DrawTextInternal(CGraphicContext& context,
float startX = 0;
float startY = (alignment & XBFONT_CENTER_Y) ? -0.5f * m_cellHeight : 0; // vertical centering

// Defines whether ellipses are to be added at the beginning for right-aligned text
bool isBeginWithEllipses{false};
// Defines the index position where start rendering glyphs
size_t startPosGlyph{0};
size_t startPosGlyph{0}; // Defines the index position where start rendering glyphs
float textWidth{0}; // The text width, by taking in account truncate (and ellipses)

if (alignment & XBFONT_CENTER_X)
if (alignment & XBFONT_TRUNCATED_LEFT)
{
// Get the extent of this line
float w = GetTextWidthInternal(text, glyphs);
// To truncate to the left, we skip all characters that exceed the maximum width,
// so the rendering starts from the first character that falls within the maximum width,
// taking into account also the ellipses
textWidth = ellipsesWidth;

if (alignment & XBFONT_TRUNCATED && w > maxPixelWidth + 0.5f) // + 0.5f due to rounding issues
w = maxPixelWidth;

if (alignment & XBFONT_CENTER_X)
w *= 0.5f;
// Offset this line's starting position
startX -= w;
}
else if (alignment & XBFONT_RIGHT)
{
// We need to determine the point at which to stop rendering glyphs starting from the left,
// if "truncated" flag is set then we need to take in account ellipses before the text
float textWidth{0};
if (alignment & XBFONT_TRUNCATED)
{
textWidth = ellipsesWidth;
isBeginWithEllipses = true;
}
// We need to iterate from the end to the beginning
for (auto itRGlyph = glyphs.crbegin(); itRGlyph != glyphs.crend(); ++itRGlyph)
{
Character* ch =
GetCharacter(text[itRGlyph->m_glyphInfo.cluster], itRGlyph->m_glyphInfo.codepoint);
if (!ch)
const character_t ch = text[itRGlyph->m_glyphInfo.cluster];
Character* c = GetCharacter(ch, itRGlyph->m_glyphInfo.codepoint);
if (!c)
continue;

textWidth += ch->m_advance;
float nextWidth;
if ((ch & 0xffff) == static_cast<character_t>('\t'))
nextWidth = GetTabSpaceLength();
else
nextWidth = textWidth + c->m_advance;

if (textWidth > maxPixelWidth)
if (nextWidth > maxPixelWidth)
{
// Start rendering from the glyph that does not exceed the maximum width
startPosGlyph = std::distance(itRGlyph, glyphs.crend());
break;
}
textWidth = nextWidth;
}
}
else
{
// Calculates the text width based on the characters that can be contained within the maximum width
if (alignment & XBFONT_TRUNCATED)
textWidth = ellipsesWidth;

for (const Glyph& glyph : glyphs)
{
const character_t ch = text[glyph.m_glyphInfo.cluster];
Character* c = GetCharacter(ch, glyph.m_glyphInfo.codepoint);
if (!c)
continue;

float nextWidth;
if ((ch & 0xffff) == static_cast<character_t>('\t'))
nextWidth = GetTabSpaceLength();
else
nextWidth = textWidth + c->m_advance;

if (nextWidth > maxPixelWidth)
break;

textWidth = nextWidth;
}
}

if (alignment & XBFONT_RIGHT)
{
// Moves the x pos with the purpose of having the text effect aligned to the right
startX += maxPixelWidth - textWidth;
}
else if (alignment & XBFONT_CENTER_X)
{
textWidth *= 0.5f;
startX -= textWidth;
}

float spacePerSpaceCharacter = 0; // for justification effects
if (alignment & XBFONT_JUSTIFIED)
{
Expand Down Expand Up @@ -494,13 +559,13 @@ void CGUIFontTTF::DrawTextInternal(CGraphicContext& context,
// are not currently cached and cause the texture to be enlarged, which
// would invalidate the texture coordinates.
std::queue<Character> characters;
if (alignment & XBFONT_TRUNCATED)
GetCharacter(L'.', 0);

if (isBeginWithEllipses) // for right aligned text only
if (alignment & XBFONT_TRUNCATED_LEFT)
cursorX += ellipsesWidth;

for (auto itGlyph = glyphs.cbegin() + startPosGlyph; itGlyph != glyphs.cend(); ++itGlyph)
auto glyphBegin = glyphs.cbegin() + startPosGlyph;

for (auto itGlyph = glyphBegin; itGlyph != glyphs.cend(); ++itGlyph)
{
Character* ch =
GetCharacter(text[itGlyph->m_glyphInfo.cluster], itGlyph->m_glyphInfo.codepoint);
Expand All @@ -517,11 +582,8 @@ void CGUIFontTTF::DrawTextInternal(CGraphicContext& context,
float nextCursorX = cursorX;

if (alignment & XBFONT_TRUNCATED)
{
nextCursorX += ch->m_advance;
if (!isBeginWithEllipses) // for left aligned text only
nextCursorX += ellipsesWidth;
}
nextCursorX += ch->m_advance + ellipsesWidth;

if (nextCursorX > maxPixelWidth)
break;
}
Expand All @@ -533,7 +595,7 @@ void CGUIFontTTF::DrawTextInternal(CGraphicContext& context,
tempVertices->reserve(VERTEX_PER_GLYPH * glyphs.size());
cursorX = 0;

for (auto itGlyph = glyphs.cbegin() + startPosGlyph; itGlyph != glyphs.cend(); ++itGlyph)
for (auto itGlyph = glyphBegin; itGlyph != glyphs.cend(); ++itGlyph)
{
// If starting text on a new line, determine justification effects
// Get the current letter in the CStdString
Expand All @@ -556,44 +618,36 @@ void CGUIFontTTF::DrawTextInternal(CGraphicContext& context,

if (alignment & XBFONT_TRUNCATED)
{
if (alignment & XBFONT_RIGHT)
// Check if we will be exceeded the max allowed width
if (cursorX + ch->m_advance + ellipsesWidth > maxPixelWidth)
{
if (isBeginWithEllipses)
// Yup. Let's draw the ellipses, then bail
// Perhaps we should really bail to the next line in this case??
Character* period = GetCharacter(L'.', 0);
if (!period)
break;

for (int i = 0; i < 3; i++)
{
isBeginWithEllipses = false;
Character* period = GetCharacter(L'.', 0);
if (!period)
break;

for (int i = 0; i < 3; i++)
{
RenderCharacter(context, startX + cursorX, startY, period, color, !scrolling,
*tempVertices);
cursorX += period->m_advance;
}
RenderCharacter(context, startX + cursorX, startY, period, color, !scrolling,
*tempVertices);
cursorX += period->m_advance;
}
if (maxPixelWidth > 0 && cursorX > maxPixelWidth)
break; // exceeded max allowed width - stop rendering
break;
}
else
}
else if (alignment & XBFONT_TRUNCATED_LEFT && itGlyph == glyphBegin)
{
// Add ellipsis only at the beginning of the text
Character* period = GetCharacter(L'.', 0);
if (!period)
break;

for (int i = 0; i < 3; i++)
{
// Check if we will be exceeded the max allowed width
if (cursorX + ch->m_advance + ellipsesWidth > maxPixelWidth)
{
// Yup. Let's draw the ellipses, then bail
// Perhaps we should really bail to the next line in this case??
Character* period = GetCharacter(L'.', 0);
if (!period)
break;

for (int i = 0; i < 3; i++)
{
RenderCharacter(context, startX + cursorX, startY, period, color, !scrolling,
*tempVertices);
cursorX += period->m_advance;
}
break;
}
RenderCharacter(context, startX + cursorX, startY, period, color, !scrolling,
*tempVertices);
cursorX += period->m_advance;
}
}
else if (maxPixelWidth > 0 && cursorX > maxPixelWidth)
Expand Down
15 changes: 12 additions & 3 deletions xbmc/guilib/GUILabel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,19 @@ void CGUILabel::Render()
}
else
{
align |= XBFONT_TRUNCATED;
// Allow to text truncate (and relative ellipsis "...") on the left for use cases like edit controls (CGUIEditControl)
if (m_isReversedTruncate && (m_label.align & XBFONT_RIGHT))
if (m_overflowType == OVER_FLOW_TRUNCATE_LEFT)
align |= XBFONT_TRUNCATED_LEFT;
else
align |= XBFONT_TRUNCATED;

if (m_label.align & XBFONT_RIGHT)
align |= XBFONT_RIGHT;

if (m_label.align & XBFONT_CENTER_X)
{
posX += m_renderRect.Width() * 0.5f; // hack for centered multiline text, same of above
align |= XBFONT_CENTER_X;
}
}
m_textLayout.Render(posX, posY, m_label.angle, color, m_label.shadowColor, align, m_overflowType == OVER_FLOW_CLIP ? m_textLayout.GetTextWidth() : m_renderRect.Width(), renderSolid);
}
Expand Down
21 changes: 9 additions & 12 deletions xbmc/guilib/GUILabel.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,14 @@ class CGUILabel

/*! \brief allowed overflow handling techniques for labels, as defined by the skin
*/
enum OVER_FLOW { OVER_FLOW_TRUNCATE = 0,
OVER_FLOW_SCROLL,
OVER_FLOW_WRAP,
OVER_FLOW_CLIP };
enum OVER_FLOW
{
OVER_FLOW_TRUNCATE = 0, // Truncated text from right (text end with ellipses)
OVER_FLOW_SCROLL,
OVER_FLOW_WRAP,
OVER_FLOW_CLIP,
OVER_FLOW_TRUNCATE_LEFT // Truncated text from left (text start with ellipses)
};

CGUILabel(float posX, float posY, float width, float height, const CLabelInfo& labelInfo, OVER_FLOW overflow = OVER_FLOW_TRUNCATE);
CGUILabel(const CGUILabel& label);
Expand Down Expand Up @@ -156,12 +160,6 @@ class CGUILabel
*/
bool SetOverflow(OVER_FLOW overflow);

/*!
* \brief Set if the text truncate (and ellipsis "...") must be done in reverse (on LTR text by default on the left).
* \param isReversed Set true to reversed behaviour
*/
void SetReversedTruncate(bool isReversed) { m_isReversedTruncate = isReversed; }

/*! \brief Set this label invalid. Forces an update of the control
*/
void SetInvalid();
Expand Down Expand Up @@ -236,8 +234,7 @@ class CGUILabel
CGUITextLayout m_textLayout;

bool m_scrolling;
OVER_FLOW m_overflowType;
bool m_isReversedTruncate{false};
OVER_FLOW m_overflowType;
CScrollInfo m_scrollInfo;
CRect m_renderRect; ///< actual sizing of text
CRect m_maxRect; ///< maximum sizing of text
Expand Down
Loading

0 comments on commit 9901571

Please sign in to comment.