Files
kicad-source-mirror/qa/tests/common/test_markup_parser.cpp
T
Seth Hillbrand 77a212a72c Preserve multiple spaces inside markup during line-breaking
wordbreakMarkup() uses wxStringTokenizer to split text into words for
line-wrapping. When processing content inside markup nodes (overbar,
subscript, superscript), the tokenizer collapsed consecutive spaces
into a single space because wxTOKEN_RET_DELIMS skips empty tokens
between consecutive delimiters.

This caused textboxes to render "~{     }" as "~{ }" since textboxes
use LinebreakText() while regular text items do not.

Add an aInsideMarkup flag to skip tokenization for content nodes that
are children of markup nodes. Their content is concatenated verbatim
into the parent markup word, preserving all spaces.

Also fix width calculation for space-only tokens outside markup that
were getting zero width after Trim().

Fixes https://gitlab.com/kicad/code/kicad/-/issues/22913
2026-02-02 10:42:55 -08:00

187 lines
5.1 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file
* Test suite for MARKUP_PARSER
*/
#include <qa_utils/wx_utils/unit_test_utils.h>
// Code under test
#include <markup_parser.h>
#include <font/font.h>
#include <font/stroke_font.h>
/**
* Declare the test suite
*/
BOOST_AUTO_TEST_SUITE( MarkupParser )
void nodeToString( std::unique_ptr<MARKUP::NODE>& aNode, std::string& aStringToPopulate )
{
aStringToPopulate += " {";
if( aNode->isOverbar() )
aStringToPopulate += "OVER";
if( aNode->isSubscript() )
aStringToPopulate += "SUB";
if( aNode->isSuperscript() )
aStringToPopulate += "SUP";
if( aNode->has_content() )
aStringToPopulate += "'" + aNode->string() + "'";
for( auto& c : aNode->children )
nodeToString( c, aStringToPopulate );
aStringToPopulate += "} ";
}
struct PARSE_CASE
{
std::string Input;
std::string ExpectedResult;
};
/**
* Test the #Parse method.
*/
BOOST_AUTO_TEST_CASE( Parse )
{
std::vector<PARSE_CASE> cases =
{
{
"A normal string",
" { {'A normal string'} } "
},
{
"_{A subscript String}",
" { {SUB {'A subscript String'} } } "
},
{
"^{A superscript String}",
" { {SUP {'A superscript String'} } } "
},
{
"~{An overbar String}",
" { {OVER {'An overbar String'} } } "
},
{
"~{An incomplete markup",
" { {'~{An incomplete markup'} } "
},
{
"A string ~{overbar}",
" { {'A string '} {OVER {'overbar'} } } "
},
{
"A string ~{incomplete markup",
" { {'A string ~{incomplete markup'} } "
},
{
"A string ~{overbar} ~{incomplete markup",
" { {'A string '} {OVER {'overbar'} } {' ~{incomplete markup'} } "
},
{ "A string ~{incomplete markup ~{overbar}",
" { {'A string ~{incomplete markup '} {OVER {'overbar'} } } "
}
};
for( auto& c : cases )
{
BOOST_TEST_INFO_SCOPE( c.Input );
MARKUP::MARKUP_PARSER parser( c.Input );
std::unique_ptr<MARKUP::NODE> rootNode = parser.Parse();
BOOST_REQUIRE( rootNode );
std::string result;
nodeToString( rootNode, result );
BOOST_CHECK_EQUAL( result, c.ExpectedResult );
// Uncomment for testing / generating test cases:
// printf( "%s\n", result.c_str() );
}
}
/**
* Verify that LinebreakText preserves multiple spaces inside overbar markup and that
* the overbar word receives a nonzero width so line-wrapping accounts for it.
*
* Regression test for https://gitlab.com/kicad/code/kicad/-/issues/22913
*/
BOOST_AUTO_TEST_CASE( OverbarMultipleSpacesWidth )
{
KIFONT::FONT* font = KIFONT::STROKE_FONT::LoadFont( wxEmptyString );
BOOST_REQUIRE( font );
VECTOR2I glyphSize( 1000, 1000 );
// Measure the width of a single space for reference
int spaceWidth = font->GetTextAsGlyphs( nullptr, nullptr, wxS( " " ), glyphSize,
VECTOR2I(), ANGLE_0, false, VECTOR2I(), 0 ).x;
BOOST_REQUIRE( spaceWidth > 0 );
// Measure the width of 5 spaces
int fiveSpaceWidth = font->GetTextAsGlyphs( nullptr, nullptr, wxS( " " ), glyphSize,
VECTOR2I(), ANGLE_0, false, VECTOR2I(), 0 ).x;
BOOST_CHECK_GT( fiveSpaceWidth, spaceWidth );
// Verify that LinebreakText preserves overbar with multiple spaces
struct LINEBREAK_CASE
{
wxString Input;
wxString Expected;
};
int wideColumn = 100000;
std::vector<LINEBREAK_CASE> cases =
{
{ wxS( "~{ }" ), wxS( "~{ }" ) },
{ wxS( "A ~{ }" ), wxS( "A ~{ }" ) },
{ wxS( "A ~{ B }" ), wxS( "A ~{ B }" ) },
{ wxS( "~{ } end" ), wxS( "~{ } end" ) },
{ wxS( "/~{ }" ), wxS( "/~{ }" ) },
{ wxS( "_{ }" ), wxS( "_{ }" ) },
{ wxS( "^{ }" ), wxS( "^{ }" ) },
};
for( auto& c : cases )
{
BOOST_TEST_INFO_SCOPE( c.Input );
wxString text = c.Input;
font->LinebreakText( text, wideColumn, glyphSize, 0, false, false );
BOOST_CHECK_EQUAL( text, c.Expected );
}
}
BOOST_AUTO_TEST_SUITE_END()