From 995388fb711bd11022549def5a575f56ad3dd1b2 Mon Sep 17 00:00:00 2001 From: Jeff Young Date: Fri, 5 May 2023 22:31:29 +0100 Subject: [PATCH] Rewrite linebreaking algorithm. Previous version was overly simplistic to deal correctly with multiple spaces or textruns not separated by spaces. Fixes https://gitlab.com/kicad/code/kicad/issues/14683 --- common/font/font.cpp | 94 +++++++++++++++++++++++++++++++------------- 1 file changed, 66 insertions(+), 28 deletions(-) diff --git a/common/font/font.cpp b/common/font/font.cpp index 77346033da..b1880036ab 100644 --- a/common/font/font.cpp +++ b/common/font/font.cpp @@ -39,6 +39,7 @@ // The "official" name of the building Kicad stroke font (always existing) #include #include "macros.h" +#include "wx/tokenzr.h" // markup_parser.h includes pegtl.hpp which includes windows.h... which leaks #define DrawText @@ -512,20 +513,22 @@ void wordbreakMarkup( std::vector>* aWords, } else { - wxString textRun = aNode->asWxString(); - wxArrayString words; + wxString textRun = aNode->asWxString(); + wxStringTokenizer tokenizer( textRun, " ", wxTOKEN_RET_DELIMS ); + std::vector words; - wxStringSplit( textRun, words, ' ' ); + while( tokenizer.HasMoreTokens() ) + words.emplace_back( tokenizer.GetNextToken() ); - if( textRun.EndsWith( wxS( " " ) ) ) - words.Add( wxS( " " ) ); - - for( size_t ii = 0; ii < words.size(); ++ii ) + for( const wxString& word : words ) { - int w = aFont->GetTextAsGlyphs( nullptr, nullptr, words[ii], aSize, { 0, 0 }, + wxString chars = word; + chars.Trim(); + + int w = aFont->GetTextAsGlyphs( nullptr, nullptr, chars, aSize, { 0, 0 }, ANGLE_0, false, { 0, 0 }, textStyle ).x; - aWords->emplace_back( std::make_pair( words[ii], w ) ); + aWords->emplace_back( std::make_pair( word, w ) ); } } } @@ -577,35 +580,70 @@ void FONT::LinebreakText( wxString& aText, int aColumnWidth, const VECTOR2I& aSi for( size_t ii = 0; ii < textLines.Count(); ++ii ) { - bool virginLine = true; - int lineWidth = 0; + std::vector> markup; std::vector> words; - wordbreakMarkup( &words, textLines[ii], aSize, textStyle ); + wordbreakMarkup( &markup, textLines[ii], aSize, textStyle ); - for( size_t jj = 0; jj < words.size(); /* advance in loop */ ) + for( const auto& [ run, runWidth ] : markup ) { - if( virginLine ) + if( !words.empty() && !words.back().first.EndsWith( ' ' ) ) { - // First word is always placed, even when wider than columnWidth. - aText += words[jj].first; - lineWidth += words[jj].second; - jj++; - - virginLine = false; - } - else if( lineWidth + spaceWidth + words[jj].second < aColumnWidth - aThickness ) - { - aText += " " + words[jj].first; - lineWidth += spaceWidth + words[jj].second; - jj++; + words.back().first += run; + words.back().second += runWidth; } else { - aText += '\n'; + words.emplace_back( std::make_pair( run, runWidth ) ); + } + } + bool buryMode = false; + int lineWidth = 0; + wxString pendingSpaces; + + for( const auto& [ word, wordWidth ] : words ) + { + int pendingSpaceWidth = (int) pendingSpaces.Length() * spaceWidth; + bool overflow = lineWidth + pendingSpaceWidth + wordWidth > aColumnWidth - aThickness; + + if( overflow && pendingSpaces.Length() > 0 ) + { + aText += '\n'; lineWidth = 0; - virginLine = true; + pendingSpaces = wxEmptyString; + pendingSpaceWidth = 0; + buryMode = true; + } + + if( word == wxS( " " ) ) + { + pendingSpaces += word; + } + else + { + if( buryMode ) + { + buryMode = false; + } + else + { + aText += pendingSpaces; + lineWidth += pendingSpaceWidth; + } + + if( word.EndsWith( ' ' ) ) + { + aText += word.Left( word.Length() - 1 ); + pendingSpaces = wxS( " " ); + } + else + { + aText += word; + pendingSpaces = wxEmptyString; + } + + lineWidth += wordWidth; } }