Fix PDF plotting of text containing tab characters

The PDF plotter's renderWord function now properly handles tab characters
within text. Previously, tabs were passed to HarfBuzz or the stroke font
encoder which would render them as zero-width or small-width glyphs,
causing text alignment issues.

The fix splits text at tab boundaries and calculates proper tab stop
positions using the same algorithm as the font rendering code (TAB_WIDTH
= 4 * 0.6 font units), advancing the cursor to the next tab stop for
each tab character encountered.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/22606
This commit is contained in:
Seth Hillbrand
2026-01-09 18:09:47 -08:00
parent 0d6074e2b2
commit 5364eeb718
2 changed files with 89 additions and 2 deletions
+48 -2
View File
@@ -2085,10 +2085,11 @@ VECTOR2I PDF_PLOTTER::renderWord( const wxString& aWord, const VECTOR2I& aPositi
return aPosition;
// If the word is just a space character, advance position by space width and continue
if( aWord == wxT(" ") )
if( aWord == wxT( " " ) )
{
// Calculate space width and advance position
VECTOR2I spaceBox( aFont->StringBoundaryLimits( "n", aSize, aWidth, aBold, aItalic, aFontMetrics ).x / 2, 0 );
VECTOR2I spaceBox( aFont->StringBoundaryLimits( "n", aSize, aWidth, aBold, aItalic,
aFontMetrics ).x / 2, 0 );
if( aTextMirrored )
spaceBox.x *= -1;
@@ -2098,6 +2099,51 @@ VECTOR2I PDF_PLOTTER::renderWord( const wxString& aWord, const VECTOR2I& aPositi
return aPosition + rotatedSpaceBox;
}
// If the word contains tab characters, we need to handle them specially.
// Split by tabs and render each segment, advancing to the next tab stop for each tab.
if( aWord.Contains( wxT( '\t' ) ) )
{
constexpr double TAB_WIDTH = 4 * 0.6;
VECTOR2I pos = aPosition;
wxString segment;
for( wxUniChar c : aWord )
{
if( c == '\t' )
{
if( !segment.IsEmpty() )
{
pos = renderWord( segment, pos, aSize, aOrient, aTextMirrored, aWidth, aBold,
aItalic, aFont, aFontMetrics, aV_justify, aTextStyle );
segment.clear();
}
int tabWidth = KiROUND( aSize.x * TAB_WIDTH );
int currentIntrusion = ( pos.x - aPosition.x ) % tabWidth;
VECTOR2I tabAdvance( tabWidth - currentIntrusion, 0 );
if( aTextMirrored )
tabAdvance.x *= -1;
RotatePoint( tabAdvance, aOrient );
pos += tabAdvance;
}
else
{
segment += c;
}
}
if( !segment.IsEmpty() )
{
pos = renderWord( segment, pos, aSize, aOrient, aTextMirrored, aWidth, aBold, aItalic,
aFont, aFontMetrics, aV_justify, aTextStyle );
}
return pos;
}
// Compute transformation parameters for this word
double ctm_a, ctm_b, ctm_c, ctm_d, ctm_e, ctm_f;
double wideningFactor, heightFactor;