5364eeb718
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
460 lines
20 KiB
C++
460 lines
20 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 2
|
|
* 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, you may find one here:
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
* or you may search the http://www.gnu.org website for the version 2 license,
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include <boost/test/unit_test.hpp>
|
|
|
|
#include <wx/filename.h>
|
|
#include <wx/filefn.h>
|
|
#include <wx/ffile.h>
|
|
#include <wx/utils.h>
|
|
#include <wx/image.h>
|
|
#include <zlib.h>
|
|
|
|
#include <plotters/plotters_pslike.h>
|
|
#include <advanced_config.h>
|
|
#include <render_settings.h>
|
|
#include <trigo.h>
|
|
#include <font/font.h>
|
|
#include <font/stroke_font.h>
|
|
#include <qa_utils/wx_utils/unit_test_utils.h>
|
|
#include <qa_utils/pdf_test_utils.h>
|
|
|
|
/* Test objective:
|
|
* Ensure PDF_PLOTTER can emit glyphs for ASCII plus some Cyrillic, Japanese and Chinese
|
|
* characters using stroke font fallback. We verify by checking resulting PDF file contains
|
|
* expected glyph names or UTF-16 hex sequences for those code points.
|
|
*/
|
|
|
|
BOOST_AUTO_TEST_SUITE( PDFUnicodePlot )
|
|
|
|
static wxString getTempPdfPath( const wxString& name ) { return MakeTempPdfPath( name ); }
|
|
|
|
// Comprehensive mapping test: emit all four style variants in a single PDF and verify that
|
|
// every style's ToUnicode CMap contains expected codepoints (Cyrillic 041F, Japanese 65E5, Chinese 672C).
|
|
BOOST_AUTO_TEST_CASE( PlotMultilingualAllStylesMappings )
|
|
{
|
|
const std::string sampleUtf8 = "ABCDEF Привет 日本語 漢字";
|
|
wxString sample = wxString::FromUTF8( sampleUtf8.c_str() );
|
|
wxString pdfPath = getTempPdfPath( "kicad_pdf_unicode_allstyles" );
|
|
|
|
PDF_PLOTTER plotter;
|
|
SIMPLE_RENDER_SETTINGS renderSettings;
|
|
|
|
plotter.SetRenderSettings( &renderSettings );
|
|
BOOST_REQUIRE( plotter.OpenFile( pdfPath ) );
|
|
plotter.SetViewport( VECTOR2I(0,0), 1.0, 1.0, false );
|
|
BOOST_REQUIRE( plotter.StartPlot( wxT("1"), wxT("TestPage") ) );
|
|
|
|
auto emitStyle = [&]( bool bold, bool italic, int yoff )
|
|
{
|
|
TEXT_ATTRIBUTES attrs = BuildTextAttributes( 3000, 300, bold, italic );
|
|
auto strokeFont = LoadStrokeFontUnique();
|
|
KIFONT::METRICS metrics;
|
|
|
|
plotter.PlotText( VECTOR2I( 50000, 60000 - yoff ), COLOR4D( 0, 0, 0, 1 ), sample, attrs,
|
|
strokeFont.get(), metrics );
|
|
};
|
|
|
|
emitStyle( false, false, 0 ); // normal
|
|
emitStyle( true, false, 8000 ); // bold
|
|
emitStyle( false, true, 16000 ); // italic
|
|
emitStyle( true, true, 24000 ); // bold-italic
|
|
|
|
plotter.EndPlot();
|
|
|
|
// Read entire PDF (may have compression). We'll search each Type3 font object's preceding
|
|
// name to separate CMaps logically.
|
|
std::string buffer; BOOST_REQUIRE( ReadPdfWithDecompressedStreams( pdfPath, buffer ) );
|
|
BOOST_CHECK( buffer.rfind( "%PDF", 0 ) == 0 );
|
|
|
|
// Count how many distinct KiCadStrokeCMap names present; expect at least 4 (one per style).
|
|
int cmapCount = CountOccurrences( buffer, "/CMapName /KiCadStrokeCMap" );
|
|
|
|
BOOST_CHECK_MESSAGE( cmapCount >= 4, "Expected at least 4 CMaps (got " << cmapCount << ")" );
|
|
|
|
auto requireAll = [&]( const char* codeHex, const char* label ) {
|
|
int occurrences = CountOccurrences( buffer, codeHex );
|
|
BOOST_CHECK_MESSAGE( occurrences >= 4, "Codepoint " << label << " (" << codeHex
|
|
<< ") expected in all 4 styles; found " << occurrences );
|
|
};
|
|
|
|
requireAll( "041F", "Cyrillic PE" );
|
|
requireAll( "65E5", "Kanji 日" );
|
|
requireAll( "672C", "Kanji 本" );
|
|
|
|
MaybeRemoveFile( pdfPath );
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE( PlotMultilingualText )
|
|
{
|
|
// UTF-8 sample with Latin, Cyrillic, Japanese, Chinese (shared Han) characters.
|
|
const std::string sampleUtf8 = "ABCDEF Привет 日本語 漢字";
|
|
wxString sample = wxString::FromUTF8( sampleUtf8.c_str() );
|
|
|
|
wxString pdfPath = getTempPdfPath( "kicad_pdf_unicode" );
|
|
|
|
PDF_PLOTTER plotter;
|
|
|
|
// Force uncompressed PDF streams via environment so we can directly search for
|
|
// unicode hex strings in the output (otherwise they are Flate compressed).
|
|
// Do not force debug writer; allow normal compression so page content is valid.
|
|
// The plotter expects non-null render settings for default pen width and font queries.
|
|
// Provide a minimal concrete RENDER_SETTINGS implementation for the plotter.
|
|
SIMPLE_RENDER_SETTINGS renderSettings; plotter.SetRenderSettings( &renderSettings );
|
|
|
|
// Minimal viewport and plot setup. Use 1 IU per decimil so internal coordinates are small
|
|
// and resulting translation keeps text inside the page for rasterization.
|
|
BOOST_REQUIRE( plotter.OpenFile( pdfPath ) );
|
|
plotter.SetViewport( VECTOR2I(0,0), 1.0, 1.0, false );
|
|
// StartPlot opens first page stream internally; use simple page number
|
|
BOOST_REQUIRE( plotter.StartPlot( wxT("1"), wxT("TestPage") ) );
|
|
|
|
TEXT_ATTRIBUTES attrs = BuildTextAttributes( 3000, 300, false, false );
|
|
auto strokeFont = LoadStrokeFontUnique();
|
|
KIFONT::METRICS metrics; // not used for stroke fallback
|
|
|
|
// Plot near lower-left inside the page.
|
|
// Place text near the top of the page in internal units so after the 0.0072 scale it
|
|
// appears well within the MediaBox. Empirically m_paperSize.y ~ 116k internal units.
|
|
plotter.PlotText( VECTOR2I( 50000, 60000 ), COLOR4D( 0.0, 0.0, 0.0, 1.0 ), sample, attrs,
|
|
strokeFont.get(), metrics );
|
|
|
|
plotter.EndPlot();
|
|
|
|
// Read file back and check for expected UTF-16 hex encodings for some code points
|
|
// We expect CMap to contain mappings. E.g. '041F' (Cyrillic capital Pe), '65E5'(日), '672C'(本).
|
|
std::string buffer2; BOOST_REQUIRE( ReadPdfWithDecompressedStreams( pdfPath, buffer2 ) );
|
|
auto contains = [&]( const char* needle ) { return PdfContains( buffer2, needle ); };
|
|
|
|
BOOST_CHECK_MESSAGE( contains( "041F" ), "Missing Cyrillic glyph mapping (041F)" );
|
|
BOOST_CHECK_MESSAGE( contains( "0420" ) || contains( "0440" ), "Missing Cyrillic glyph mapping (0420/0440)" );
|
|
BOOST_CHECK_MESSAGE( contains( "65E5" ), "Missing Japanese Kanji glyph mapping (65E5)" );
|
|
BOOST_CHECK_MESSAGE( contains( "672C" ), "Missing Japanese Kanji glyph mapping (672C)" );
|
|
BOOST_CHECK_MESSAGE( contains( "6F22" ) || contains( "6漢" ), "Expect Chinese Han character mapping (6F22 / 漢)" );
|
|
|
|
// Cleanup temp file (unless debugging requested)
|
|
// Optional: rasterize PDF to image (requires poppler 'pdftoppm').
|
|
// We treat absence of the tool as a skipped sub-check rather than a failure.
|
|
{
|
|
long darkPixels = 0;
|
|
if( RasterizePdfCountDark( pdfPath, 72, 240, darkPixels ) )
|
|
{
|
|
BOOST_CHECK_MESSAGE( darkPixels > 200,
|
|
"Rasterized PDF appears blank or too sparse (" << darkPixels
|
|
<< " dark pixels)" );
|
|
}
|
|
else
|
|
{
|
|
BOOST_TEST_MESSAGE( "pdftoppm not available or failed; skipping raster validation" );
|
|
}
|
|
}
|
|
|
|
MaybeRemoveFile( pdfPath );
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE( PlotMultilingualTextBold )
|
|
{
|
|
const std::string sampleUtf8 = "ABCDEF Привет 日本語 漢字";
|
|
wxString sample = wxString::FromUTF8( sampleUtf8.c_str() );
|
|
|
|
wxString pdfPath = getTempPdfPath( "kicad_pdf_unicode_bold" );
|
|
|
|
PDF_PLOTTER plotter;
|
|
SIMPLE_RENDER_SETTINGS renderSettings;
|
|
plotter.SetRenderSettings( &renderSettings );
|
|
BOOST_REQUIRE( plotter.OpenFile( pdfPath ) );
|
|
plotter.SetViewport( VECTOR2I(0,0), 1.0, 1.0, false );
|
|
BOOST_REQUIRE( plotter.StartPlot( wxT("1"), wxT("TestPage") ) );
|
|
|
|
TEXT_ATTRIBUTES attrs = BuildTextAttributes( 3000, 300, true, false );
|
|
auto strokeFont = LoadStrokeFontUnique();
|
|
KIFONT::METRICS metrics;
|
|
plotter.PlotText( VECTOR2I( 50000, 60000 ), COLOR4D( 0.0, 0.0, 0.0, 1.0 ), sample, attrs,
|
|
strokeFont.get(), metrics );
|
|
plotter.EndPlot();
|
|
std::string buffer3; BOOST_REQUIRE( ReadPdfWithDecompressedStreams( pdfPath, buffer3 ) );
|
|
auto contains = [&]( const char* needle ) { return PdfContains( buffer3, needle ); };
|
|
BOOST_CHECK_MESSAGE( contains( "041F" ), "Missing Cyrillic glyph mapping (bold 041F)" );
|
|
BOOST_CHECK_MESSAGE( contains( "65E5" ), "Missing Japanese glyph mapping (bold 65E5)" );
|
|
|
|
MaybeRemoveFile( pdfPath );
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE( PlotMultilingualTextItalic )
|
|
{
|
|
const std::string sampleUtf8 = "ABCDEF Привет 日本語 漢字";
|
|
wxString sample = wxString::FromUTF8( sampleUtf8.c_str() );
|
|
wxString pdfPath = getTempPdfPath( "kicad_pdf_unicode_italic" );
|
|
PDF_PLOTTER plotter;
|
|
SIMPLE_RENDER_SETTINGS renderSettings;
|
|
|
|
plotter.SetRenderSettings( &renderSettings );
|
|
BOOST_REQUIRE( plotter.OpenFile( pdfPath ) );
|
|
plotter.SetViewport( VECTOR2I(0,0), 1.0, 1.0, false );
|
|
BOOST_REQUIRE( plotter.StartPlot( wxT( "1" ), wxT( "TestPage" ) ) );
|
|
TEXT_ATTRIBUTES attrs = BuildTextAttributes( 3000, 300, false, true );
|
|
auto strokeFont = LoadStrokeFontUnique();
|
|
KIFONT::METRICS metrics;
|
|
plotter.PlotText( VECTOR2I( 50000, 60000 ), COLOR4D( 0, 0, 0, 1 ), sample, attrs, strokeFont.get(), metrics );
|
|
plotter.EndPlot();
|
|
|
|
std::string buffer4; BOOST_REQUIRE( ReadPdfWithDecompressedStreams( pdfPath, buffer4 ) );
|
|
auto contains = [&]( const char* n ) { return PdfContains( buffer4, n ); };
|
|
BOOST_CHECK_MESSAGE( contains( "041F" ), "Missing Cyrillic glyph mapping (italic 041F)" );
|
|
BOOST_CHECK_MESSAGE( contains( "65E5" ), "Missing Japanese glyph mapping (italic 65E5)" );
|
|
MaybeRemoveFile( pdfPath );
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE( PlotMultilingualTextBoldItalic )
|
|
{
|
|
const std::string sampleUtf8 = "ABCDEF Привет 日本語 漢字";
|
|
wxString sample = wxString::FromUTF8( sampleUtf8.c_str() );
|
|
wxString pdfPath = getTempPdfPath( "kicad_pdf_unicode_bolditalic" );
|
|
PDF_PLOTTER plotter;
|
|
SIMPLE_RENDER_SETTINGS renderSettings;
|
|
|
|
plotter.SetRenderSettings( &renderSettings );
|
|
BOOST_REQUIRE( plotter.OpenFile( pdfPath ) );
|
|
|
|
plotter.SetViewport( VECTOR2I( 0, 0 ), 1.0, 1.0, false );
|
|
BOOST_REQUIRE( plotter.StartPlot( wxT( "1" ), wxT( "TestPage" ) ) );
|
|
|
|
TEXT_ATTRIBUTES attrs = BuildTextAttributes( 3000, 300, true, true );
|
|
auto strokeFont = LoadStrokeFontUnique();
|
|
KIFONT::METRICS metrics;
|
|
|
|
plotter.PlotText( VECTOR2I( 50000, 60000 ), COLOR4D( 0, 0, 0, 1 ), sample, attrs, strokeFont.get(), metrics );
|
|
plotter.EndPlot();
|
|
|
|
std::string buffer5; BOOST_REQUIRE( ReadPdfWithDecompressedStreams( pdfPath, buffer5 ) );
|
|
auto contains = [&]( const char* n ) { return PdfContains( buffer5, n ); };
|
|
BOOST_CHECK_MESSAGE( contains( "041F" ), "Missing Cyrillic glyph mapping (bold-italic 041F)" );
|
|
BOOST_CHECK_MESSAGE( contains( "65E5" ), "Missing Japanese glyph mapping (bold-italic 65E5)" );
|
|
MaybeRemoveFile( pdfPath );
|
|
}
|
|
|
|
// Test Y offset bounding box fix: ensure characters are not clipped when Y offset is applied
|
|
BOOST_AUTO_TEST_CASE( PlotMultilingualTextWithYOffset )
|
|
{
|
|
// Temporarily modify the Y offset configuration
|
|
ADVANCED_CFG& cfg = const_cast<ADVANCED_CFG&>( ADVANCED_CFG::GetCfg() );
|
|
double originalOffset = cfg.m_PDFStrokeFontYOffset;
|
|
cfg.m_PDFStrokeFontYOffset = 0.2; // 20% of EM unit offset upward
|
|
|
|
const std::string sampleUtf8 = "Yg Test ñ"; // characters with ascenders and descenders
|
|
wxString sample = wxString::FromUTF8( sampleUtf8.c_str() );
|
|
wxString pdfPath = getTempPdfPath( "kicad_pdf_unicode_yoffset" );
|
|
|
|
PDF_PLOTTER plotter;
|
|
SIMPLE_RENDER_SETTINGS renderSettings;
|
|
|
|
plotter.SetRenderSettings( &renderSettings );
|
|
BOOST_REQUIRE( plotter.OpenFile( pdfPath ) );
|
|
plotter.SetViewport( VECTOR2I( 0, 0 ), 1.0, 1.0, false );
|
|
BOOST_REQUIRE( plotter.StartPlot( wxT( "1" ), wxT( "TestPage" ) ) );
|
|
|
|
TEXT_ATTRIBUTES attrs = BuildTextAttributes( 4000, 400, false, false );
|
|
auto strokeFont = LoadStrokeFontUnique();
|
|
KIFONT::METRICS metrics;
|
|
plotter.PlotText( VECTOR2I( 50000, 60000 ), COLOR4D( 0, 0, 0, 1 ), sample, attrs, strokeFont.get(), metrics );
|
|
plotter.EndPlot();
|
|
|
|
|
|
// Restore original Y offset
|
|
cfg.m_PDFStrokeFontYOffset = originalOffset;
|
|
|
|
// Basic PDF validation and decompression
|
|
std::string buffer6; BOOST_REQUIRE( ReadPdfWithDecompressedStreams( pdfPath, buffer6 ) );
|
|
BOOST_CHECK( buffer6.rfind( "%PDF", 0 ) == 0 );
|
|
|
|
// Check that bounding boxes exist and are reasonable (not clipped)
|
|
// Look for d1 operators which specify character bounding boxes
|
|
BOOST_CHECK_MESSAGE( buffer6.find( "d1" ) != std::string::npos,
|
|
"PDF should contain d1 operators for glyph bounding boxes" );
|
|
|
|
MaybeRemoveFile( pdfPath );
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE( PlotOutlineFontEmbedding )
|
|
{
|
|
wxString pdfPath = getTempPdfPath( "kicad_pdf_outline_font" );
|
|
|
|
// Locate test font file (Noto Sans) in test resources
|
|
wxFileName fontFile( KI_TEST::GetTestDataRootDir() );
|
|
fontFile.RemoveLastDir();
|
|
fontFile.AppendDir( wxT( "resources" ) );
|
|
fontFile.AppendDir( wxT( "fonts" ) );
|
|
fontFile.SetFullName( wxT( "NotoSans-Regular.ttf" ) );
|
|
wxString fontPath = fontFile.GetFullPath();
|
|
|
|
BOOST_REQUIRE( wxFileExists( fontPath ) );
|
|
|
|
PDF_PLOTTER plotter;
|
|
SIMPLE_RENDER_SETTINGS renderSettings;
|
|
|
|
plotter.SetRenderSettings( &renderSettings );
|
|
BOOST_REQUIRE( plotter.OpenFile( pdfPath ) );
|
|
plotter.SetViewport( VECTOR2I(0,0), 1.0, 1.0, false );
|
|
BOOST_REQUIRE( plotter.StartPlot( wxT("1"), wxT("OutlineFont") ) );
|
|
|
|
TEXT_ATTRIBUTES attrs = BuildTextAttributes( 4000, 0, false, false );
|
|
|
|
std::vector<wxString> embeddedFonts;
|
|
embeddedFonts.push_back( fontPath );
|
|
|
|
KIFONT::FONT* outlineFont = KIFONT::FONT::GetFont( wxT( "Noto Sans" ), false, false, &embeddedFonts );
|
|
KIFONT::METRICS metrics;
|
|
|
|
wxString sample = wxString::FromUTF8( "Outline café" );
|
|
|
|
plotter.PlotText( VECTOR2I( 42000, 52000 ), COLOR4D( 0, 0, 0, 1 ), sample, attrs,
|
|
outlineFont, metrics );
|
|
|
|
plotter.EndPlot();
|
|
|
|
wxFFile file( pdfPath, "rb" );
|
|
BOOST_REQUIRE( file.IsOpened() );
|
|
wxFileOffset len = file.Length();
|
|
std::string buffer; buffer.resize( (size_t) len ); file.Read( buffer.data(), len );
|
|
BOOST_CHECK( buffer.rfind( "%PDF", 0 ) == 0 );
|
|
|
|
auto appendDecompressed = [&]() { std::string tmp; ReadPdfWithDecompressedStreams( pdfPath, tmp ); buffer.swap( tmp ); };
|
|
appendDecompressed();
|
|
|
|
BOOST_CHECK_MESSAGE( buffer.find( "/CIDFontType2" ) != std::string::npos,
|
|
"Expected CIDFontType2 descendant font" );
|
|
BOOST_CHECK_MESSAGE( buffer.find( "/FontFile2" ) != std::string::npos,
|
|
"Embedded outline font should include FontFile2 stream" );
|
|
BOOST_CHECK_MESSAGE( buffer.find( "AAAAAA+Noto-Sans" ) != std::string::npos,
|
|
"BaseFont should reference Noto Sans subset" );
|
|
BOOST_CHECK_MESSAGE( buffer.find( "00E9" ) != std::string::npos,
|
|
"ToUnicode map should include Latin character with accent" );
|
|
BOOST_CHECK_MESSAGE( buffer.find( "/KiCadOutline" ) != std::string::npos,
|
|
"Outline font resource entry missing" );
|
|
|
|
// Optional: rasterize PDF to image (requires poppler 'pdftoppm').
|
|
// We treat absence of the tool as a skipped sub-check rather than a failure.
|
|
{
|
|
wxString rasterBase = wxFileName::CreateTempFileName( wxT("kicad_pdf_raster") );
|
|
wxString cmd = wxString::Format( wxT("pdftoppm -r 72 -singlefile -png \"%s\" \"%s\""),
|
|
pdfPath, rasterBase );
|
|
|
|
int ret = wxExecute( cmd, wxEXEC_SYNC );
|
|
|
|
if( ret == 0 )
|
|
{
|
|
wxString pngPath = rasterBase + wxT(".png");
|
|
|
|
if( wxFileExists( pngPath ) )
|
|
{
|
|
// Ensure PNG handler is available
|
|
if( !wxImage::FindHandler( wxBITMAP_TYPE_PNG ) )
|
|
wxImage::AddHandler( new wxPNGHandler );
|
|
|
|
wxImage img( pngPath );
|
|
BOOST_REQUIRE_MESSAGE( img.IsOk(), "Failed to load rasterized PDF image" );
|
|
|
|
long darkPixels = 0;
|
|
int w = img.GetWidth();
|
|
int h = img.GetHeight();
|
|
|
|
for( int y = 0; y < h; ++y )
|
|
{
|
|
for( int x = 0; x < w; ++x )
|
|
{
|
|
unsigned char r = img.GetRed( x, y );
|
|
unsigned char g = img.GetGreen( x, y );
|
|
unsigned char b = img.GetBlue( x, y );
|
|
|
|
// Count any non-near-white pixel as drawn content
|
|
if( r < 240 || g < 240 || b < 240 )
|
|
++darkPixels;
|
|
}
|
|
}
|
|
|
|
// Demand at least 100 non-white pixels to consider outline font rendered correctly.
|
|
// This threshold is lower than stroke font since outline fonts may render differently.
|
|
BOOST_CHECK_MESSAGE( darkPixels > 100,
|
|
"Rasterized PDF appears blank or too sparse (" << darkPixels
|
|
<< " dark pixels). Outline font may not be rendering correctly." );
|
|
|
|
// Housekeeping
|
|
wxRemoveFile( pngPath );
|
|
}
|
|
else
|
|
{
|
|
BOOST_TEST_MESSAGE( "pdftoppm succeeded but PNG output missing; skipping raster validation" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BOOST_TEST_MESSAGE( "pdftoppm not available or failed; skipping raster validation" );
|
|
}
|
|
}
|
|
|
|
MaybeRemoveFile( pdfPath );
|
|
}
|
|
|
|
// Test tab handling in PDF text output (issue #22606).
|
|
// When text contains tab characters, each tab should advance to the next tab stop.
|
|
// We verify that text with tabs produces different glyph positions than without tabs.
|
|
BOOST_AUTO_TEST_CASE( PlotTextWithTabs )
|
|
{
|
|
wxString pdfPath = getTempPdfPath( "kicad_pdf_tabs" );
|
|
|
|
PDF_PLOTTER plotter;
|
|
SIMPLE_RENDER_SETTINGS renderSettings;
|
|
|
|
plotter.SetRenderSettings( &renderSettings );
|
|
BOOST_REQUIRE( plotter.OpenFile( pdfPath ) );
|
|
plotter.SetViewport( VECTOR2I( 0, 0 ), 1.0, 1.0, false );
|
|
BOOST_REQUIRE( plotter.StartPlot( wxT( "1" ), wxT( "TabTest" ) ) );
|
|
|
|
TEXT_ATTRIBUTES attrs = BuildTextAttributes( 3000, 300, false, false );
|
|
auto strokeFont = LoadStrokeFontUnique();
|
|
KIFONT::METRICS metrics;
|
|
|
|
wxString textWithTab = wxT( "Before\tAfter" );
|
|
wxString textWithoutTab = wxT( "BeforeAfter" );
|
|
|
|
plotter.PlotText( VECTOR2I( 50000, 60000 ), COLOR4D( 0, 0, 0, 1 ), textWithTab, attrs,
|
|
strokeFont.get(), metrics );
|
|
plotter.PlotText( VECTOR2I( 50000, 50000 ), COLOR4D( 0, 0, 0, 1 ), textWithoutTab, attrs,
|
|
strokeFont.get(), metrics );
|
|
|
|
plotter.EndPlot();
|
|
|
|
std::string buffer;
|
|
BOOST_REQUIRE( ReadPdfWithDecompressedStreams( pdfPath, buffer ) );
|
|
BOOST_CHECK( buffer.rfind( "%PDF", 0 ) == 0 );
|
|
|
|
// The PDF should contain text content. Tabs should not produce visible tab glyphs but should
|
|
// create proper spacing. We verify the PDF is valid and contains our text characters.
|
|
BOOST_CHECK_MESSAGE( PdfContains( buffer, "0041" ) || PdfContains( buffer, "A" ),
|
|
"PDF should contain 'A' from 'After'" );
|
|
|
|
MaybeRemoveFile( pdfPath );
|
|
}
|
|
|
|
BOOST_AUTO_TEST_SUITE_END()
|