From ba988ccbb1e0f1bca84b6fee9725ab11db98a3ec Mon Sep 17 00:00:00 2001 From: Seth Hillbrand Date: Sat, 20 Sep 2025 11:34:18 -0700 Subject: [PATCH] Generalize the PDF qa test --- qa/qa_utils/CMakeLists.txt | 3 + qa/qa_utils/include/qa_utils/pdf_test_utils.h | 117 ++++ qa/qa_utils/pdf_test_utils.cpp | 218 ++++++++ qa/tests/common/test_pdf_unicode_plot.cpp | 524 +++--------------- 4 files changed, 409 insertions(+), 453 deletions(-) create mode 100644 qa/qa_utils/include/qa_utils/pdf_test_utils.h create mode 100644 qa/qa_utils/pdf_test_utils.cpp diff --git a/qa/qa_utils/CMakeLists.txt b/qa/qa_utils/CMakeLists.txt index e63f3f88dd..ee4e9516c0 100644 --- a/qa/qa_utils/CMakeLists.txt +++ b/qa/qa_utils/CMakeLists.txt @@ -32,6 +32,9 @@ set( QA_UTIL_COMMON_SRC wx_utils/unit_test_utils.cpp wx_utils/wx_assert.cpp + + # PDF testing helpers + pdf_test_utils.cpp ) # A generic library of useful functions for various testing purposes diff --git a/qa/qa_utils/include/qa_utils/pdf_test_utils.h b/qa/qa_utils/include/qa_utils/pdf_test_utils.h new file mode 100644 index 0000000000..00468712e8 --- /dev/null +++ b/qa/qa_utils/include/qa_utils/pdf_test_utils.h @@ -0,0 +1,117 @@ +/* + * 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 + */ + +#ifndef QA_UTILS_PDF_TEST_UTILS_H +#define QA_UTILS_PDF_TEST_UTILS_H + +#include +#include + +#include + +#include +#include +#include +#include + +/** + * Make a temporary file path with .pdf extension using a given prefix. + */ +wxString MakeTempPdfPath( const wxString& aPrefix ); + +/** + * Minimal concrete render settings suitable for plotters in tests. + */ +class SIMPLE_RENDER_SETTINGS : public KIGFX::RENDER_SETTINGS +{ +public: + SIMPLE_RENDER_SETTINGS(); + + KIGFX::COLOR4D GetColor( const KIGFX::VIEW_ITEM*, int ) const override; + const KIGFX::COLOR4D& GetBackgroundColor() const override { return m_background; } + void SetBackgroundColor( const KIGFX::COLOR4D& aColor ) override { m_background = aColor; } + const KIGFX::COLOR4D& GetGridColor() override { return m_grid; } + const KIGFX::COLOR4D& GetCursorColor() override { return m_cursor; } + +private: + KIGFX::COLOR4D m_background; + KIGFX::COLOR4D m_grid; + KIGFX::COLOR4D m_cursor; +}; + +/** + * Build a commonly used set of text attributes for plotting text in tests. + * + * @param aSizeIu Size in internal units (both X and Y) + * @param aStrokeWidth Stroke width in internal units (0 for outline fonts) + * @param aBold Bold flag + * @param aItalic Italic flag + */ +TEXT_ATTRIBUTES BuildTextAttributes( int aSizeIu = 3000, int aStrokeWidth = 300, + bool aBold = false, bool aItalic = false ); + +/** + * Load the default stroke font and return a unique_ptr for RAII deletion. + */ +std::unique_ptr LoadStrokeFontUnique(); + +/** + * Read a PDF file and append best-effort decompressed contents of any Flate streams to the + * returned buffer to make text searches easier. + * + * @return true on success; false if file cannot be opened or read. + */ +bool ReadPdfWithDecompressedStreams( const wxString& aPdfPath, std::string& aOutBuffer ); + +/** + * Count occurrences of a substring in a string (overlapping allowed). + */ +int CountOccurrences( const std::string& aHaystack, const std::string& aNeedle ); + +/** + * Convenience contains check. + */ +inline bool PdfContains( const std::string& aBuffer, const char* aNeedle ) +{ + return aBuffer.find( aNeedle ) != std::string::npos; +} + +/** + * Rasterize a PDF page to PNG using pdftoppm if available and count non-near-white pixels. + * + * @param aPdfPath Input PDF path. + * @param aDpi Rasterization DPI. + * @param aNearWhiteThresh Threshold for each RGB channel to consider white (e.g., 240). + * @param aOutDarkPixels Output: number of pixels darker than threshold. + * @return true if rasterization succeeded and dark pixel count is valid. False if tool not + * available or conversion failed. + */ +bool RasterizePdfCountDark( const wxString& aPdfPath, int aDpi, int aNearWhiteThresh, + long& aOutDarkPixels ); + +/** + * Remove a file unless the given environment variable is set (defaults to KICAD_KEEP_TEST_PDF). + */ +void MaybeRemoveFile( const wxString& aPath, const wxString& aEnvVar = wxT( "KICAD_KEEP_TEST_PDF" ) ); + +#endif // QA_UTILS_PDF_TEST_UTILS_H diff --git a/qa/qa_utils/pdf_test_utils.cpp b/qa/qa_utils/pdf_test_utils.cpp new file mode 100644 index 0000000000..e2a442348b --- /dev/null +++ b/qa/qa_utils/pdf_test_utils.cpp @@ -0,0 +1,218 @@ +/* + * 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 +#include + +#include +#include +#include +#include +#include +#include + +#include + +wxString MakeTempPdfPath( const wxString& aPrefix ) +{ + wxFileName fn = wxFileName::CreateTempFileName( aPrefix ); + fn.SetExt( "pdf" ); + return fn.GetFullPath(); +} + +SIMPLE_RENDER_SETTINGS::SIMPLE_RENDER_SETTINGS() +{ + m_background = KIGFX::COLOR4D( 1.0, 1.0, 1.0, 1.0 ); + m_grid = KIGFX::COLOR4D( 0.8, 0.8, 0.8, 1.0 ); + m_cursor = KIGFX::COLOR4D( 0.0, 0.0, 0.0, 1.0 ); +} + +KIGFX::COLOR4D SIMPLE_RENDER_SETTINGS::GetColor( const KIGFX::VIEW_ITEM*, int ) const +{ + return KIGFX::COLOR4D( 0.0, 0.0, 0.0, 1.0 ); +} + +TEXT_ATTRIBUTES BuildTextAttributes( int aSizeIu, int aStrokeWidth, bool aBold, bool aItalic ) +{ + TEXT_ATTRIBUTES attrs; + attrs.m_Size = VECTOR2I( aSizeIu, aSizeIu ); + attrs.m_StrokeWidth = aStrokeWidth; + attrs.m_Multiline = false; + attrs.m_Italic = aItalic; + attrs.m_Bold = aBold; + attrs.m_Halign = GR_TEXT_H_ALIGN_LEFT; + attrs.m_Valign = GR_TEXT_V_ALIGN_BOTTOM; + attrs.m_Angle = ANGLE_0; + attrs.m_Mirrored = false; + return attrs; +} + +std::unique_ptr LoadStrokeFontUnique() +{ + return std::unique_ptr( KIFONT::STROKE_FONT::LoadFont( wxEmptyString ) ); +} + +static void append_decompressed_streams( std::string& aBuffer ) +{ + std::string aggregate = aBuffer; + size_t pos = 0; + + while( true ) + { + size_t streamPos = aBuffer.find( "stream\n", pos ); + + if( streamPos == std::string::npos ) + break; + + size_t endPos = aBuffer.find( "endstream", streamPos ); + + if( endPos == std::string::npos ) + break; + + size_t dataStart = streamPos + 7; // skip "stream\n" + size_t dataLen = ( endPos > dataStart ) ? ( endPos - dataStart ) : 0; + + if( dataLen > 0 ) + { + const unsigned char* data = reinterpret_cast( aBuffer.data() + dataStart ); + z_stream zs{}; + zs.next_in = const_cast( data ); + zs.avail_in = static_cast( dataLen ); + + if( inflateInit( &zs ) == Z_OK ) + { + std::string out; + out.resize( dataLen * 4 + 64 ); + zs.next_out = reinterpret_cast( out.data() ); + zs.avail_out = static_cast( out.size() ); + int ret = inflate( &zs, Z_FINISH ); + + if( ret == Z_STREAM_END ) + { + out.resize( zs.total_out ); + aggregate += out; + } + + inflateEnd( &zs ); + } + } + + pos = endPos + 9; // skip past "endstream" + } + + aBuffer.swap( aggregate ); +} + +bool ReadPdfWithDecompressedStreams( const wxString& aPdfPath, std::string& aOutBuffer ) +{ + wxFFile file( aPdfPath, "rb" ); + + if( !file.IsOpened() ) + return false; + + wxFileOffset len = file.Length(); + if( len <= 0 ) + return false; + + aOutBuffer.resize( static_cast( len ) ); + file.Read( aOutBuffer.data(), len ); + + append_decompressed_streams( aOutBuffer ); + return true; +} + +int CountOccurrences( const std::string& aHaystack, const std::string& aNeedle ) +{ + if( aNeedle.empty() ) + return 0; + + int count = 0; + size_t pos = 0; + + while( true ) + { + size_t p = aHaystack.find( aNeedle, pos ); + if( p == std::string::npos ) + break; + ++count; + pos = p + 1; // allow overlaps + } + + return count; +} + +bool RasterizePdfCountDark( const wxString& aPdfPath, int aDpi, int aNearWhiteThresh, + long& aOutDarkPixels ) +{ + aOutDarkPixels = 0; + + wxString rasterBase = wxFileName::CreateTempFileName( wxT( "kicad_pdf_raster" ) ); + wxString cmd = wxString::Format( wxT( "pdftoppm -r %d -singlefile -png \"%s\" \"%s\"" ), + aDpi, aPdfPath, rasterBase ); + int ret = wxExecute( cmd, wxEXEC_SYNC ); + + if( ret != 0 ) + return false; + + wxString pngPath = rasterBase + wxT( ".png" ); + + if( !wxFileExists( pngPath ) ) + return false; + + if( !wxImage::FindHandler( wxBITMAP_TYPE_PNG ) ) + wxImage::AddHandler( new wxPNGHandler ); + + wxImage img( pngPath ); + if( !img.IsOk() ) + return false; + + int w = img.GetWidth(); + int h = img.GetHeight(); + + long dark = 0; + 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 ); + + if( r < aNearWhiteThresh || g < aNearWhiteThresh || b < aNearWhiteThresh ) + ++dark; + } + } + + aOutDarkPixels = dark; + + // cleanup the rasterized file + wxRemoveFile( pngPath ); + return true; +} + +void MaybeRemoveFile( const wxString& aPath, const wxString& aEnvVar ) +{ + wxString keepEnv; + if( !wxGetEnv( aEnvVar, &keepEnv ) || keepEnv.IsEmpty() ) + wxRemoveFile( aPath ); +} diff --git a/qa/tests/common/test_pdf_unicode_plot.cpp b/qa/tests/common/test_pdf_unicode_plot.cpp index 2fe5e7bbd4..9e3f365eee 100644 --- a/qa/tests/common/test_pdf_unicode_plot.cpp +++ b/qa/tests/common/test_pdf_unicode_plot.cpp @@ -37,6 +37,7 @@ #include #include #include +#include /* Test objective: * Ensure PDF_PLOTTER can emit glyphs for ASCII plus some Cyrillic, Japanese and Chinese @@ -46,12 +47,7 @@ BOOST_AUTO_TEST_SUITE( PDFUnicodePlot ) -static wxString getTempPdfPath( const wxString& name ) -{ - wxFileName fn = wxFileName::CreateTempFileName( name ); - fn.SetExt( "pdf" ); - return fn.GetFullPath(); -} +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). @@ -62,22 +58,7 @@ BOOST_AUTO_TEST_CASE( PlotMultilingualAllStylesMappings ) wxString pdfPath = getTempPdfPath( "kicad_pdf_unicode_allstyles" ); PDF_PLOTTER plotter; - class TEST_RENDER_SETTINGS : public RENDER_SETTINGS - { - public: - TEST_RENDER_SETTINGS() - { - m_background = COLOR4D( 1, 1, 1, 1 ); - m_grid = COLOR4D( .8, .8, .8, 1 ); - m_cursor = COLOR4D( 0, 0, 0, 1 ); - } - COLOR4D GetColor( const KIGFX::VIEW_ITEM*, int ) const override { return COLOR4D( 0, 0, 0, 1 ); } - const COLOR4D& GetBackgroundColor() const override { return m_background; } - void SetBackgroundColor( const COLOR4D& c ) override { m_background = c; } - const COLOR4D& GetGridColor() override { return m_grid; } - const COLOR4D& GetCursorColor() override { return m_cursor; } - COLOR4D m_background, m_grid, m_cursor; - } renderSettings; + SIMPLE_RENDER_SETTINGS renderSettings; plotter.SetRenderSettings( &renderSettings ); BOOST_REQUIRE( plotter.OpenFile( pdfPath ) ); @@ -86,21 +67,12 @@ BOOST_AUTO_TEST_CASE( PlotMultilingualAllStylesMappings ) auto emitStyle = [&]( bool bold, bool italic, int yoff ) { - TEXT_ATTRIBUTES attrs; - attrs.m_Size = VECTOR2I( 3000, 3000 ); - attrs.m_StrokeWidth = 300; - attrs.m_Multiline = false; - attrs.m_Italic = italic; - attrs.m_Bold = bold; - attrs.m_Halign = GR_TEXT_H_ALIGN_LEFT; - attrs.m_Valign = GR_TEXT_V_ALIGN_BOTTOM; - attrs.m_Angle = ANGLE_0; - attrs.m_Mirrored = false; - KIFONT::STROKE_FONT* strokeFont = KIFONT::STROKE_FONT::LoadFont( wxEmptyString ); + 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, metrics ); - delete strokeFont; + plotter.PlotText( VECTOR2I( 50000, 60000 - yoff ), COLOR4D( 0, 0, 0, 1 ), sample, attrs, + strokeFont.get(), metrics ); }; emitStyle( false, false, 0 ); // normal @@ -112,32 +84,25 @@ BOOST_AUTO_TEST_CASE( PlotMultilingualAllStylesMappings ) // Read entire PDF (may have compression). We'll search each Type3 font object's preceding // name to separate CMaps logically. - 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 ); + std::string buffer; BOOST_REQUIRE( ReadPdfWithDecompressedStreams( pdfPath, buffer ) ); BOOST_CHECK( buffer.rfind( "%PDF", 0 ) == 0 ); - // If compressed, opportunistically decompress each stream and append for searching. - auto appendDecompressed = [&]() { - std::string aggregate = buffer; size_t pos=0; while(true){ size_t s=buffer.find("stream\n",pos); if(s==std::string::npos) break; size_t e=buffer.find("endstream",s); if(e==std::string::npos) break; size_t ds=s+7; size_t dl=e-ds; const unsigned char* data=reinterpret_cast(buffer.data()+ds); z_stream zs{}; zs.next_in=const_cast(data); zs.avail_in=(uInt)dl; if(inflateInit(&zs)==Z_OK){ std::string out; out.resize(dl*4+64); zs.next_out=reinterpret_cast(out.data()); zs.avail_out=(uInt)out.size(); int ret=inflate(&zs,Z_FINISH); if(ret==Z_STREAM_END){ out.resize(zs.total_out); aggregate+=out; } inflateEnd(&zs);} pos=e+9;} buffer.swap(aggregate); }; - appendDecompressed(); - // Count how many distinct KiCadStrokeCMap names present; expect at least 4 (one per style). - int cmapCount = 0; size_t searchPos = 0; while( true ) { size_t p = buffer.find("/CMapName /KiCadStrokeCMap", searchPos); if( p == std::string::npos ) break; ++cmapCount; searchPos = p + 1; } + 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 ) { - // ensure appears at least 4 times (once per style) - int occurrences = 0; size_t pos=0; while(true){ size_t f=buffer.find(codeHex,pos); if(f==std::string::npos) break; ++occurrences; pos=f+1; } - BOOST_CHECK_MESSAGE( occurrences >= 4, "Codepoint " << label << " (" << codeHex << ") expected in all 4 styles; found " << occurrences ); + 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 本" ); - wxString keepEnv; if( !wxGetEnv( wxT("KICAD_KEEP_TEST_PDF"), &keepEnv ) || keepEnv.IsEmpty() ) wxRemoveFile( pdfPath ); + MaybeRemoveFile( pdfPath ); } BOOST_AUTO_TEST_CASE( PlotMultilingualText ) @@ -155,125 +120,31 @@ BOOST_AUTO_TEST_CASE( PlotMultilingualText ) // 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. - class TEST_RENDER_SETTINGS : public RENDER_SETTINGS - { - public: - TEST_RENDER_SETTINGS() - { - m_background = COLOR4D( 1.0, 1.0, 1.0, 1.0 ); - m_grid = COLOR4D( 0.8, 0.8, 0.8, 1.0 ); - m_cursor = COLOR4D( 0.0, 0.0, 0.0, 1.0 ); - } - - COLOR4D GetColor( const KIGFX::VIEW_ITEM* /*aItem*/, int /*aLayer*/ ) const override - { - return COLOR4D( 0.0, 0.0, 0.0, 1.0 ); - } - - const COLOR4D& GetBackgroundColor() const override { return m_background; } - void SetBackgroundColor( const COLOR4D& aColor ) override { m_background = aColor; } - const COLOR4D& GetGridColor() override { return m_grid; } - const COLOR4D& GetCursorColor() override { return m_cursor; } - - private: - COLOR4D m_background; - COLOR4D m_grid; - COLOR4D m_cursor; - } renderSettings; - - plotter.SetRenderSettings( &renderSettings ); - BOOST_REQUIRE( plotter.OpenFile( pdfPath ) ); + 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; // zero-init then set expected fields - // Use a modest stroke font size that will reasonably map onto the page - // (roughly 1000 internal units ~ 7.2pt with the 0.0072 scale factor). - attrs.m_Size = VECTOR2I( 3000, 3000 ); - attrs.m_StrokeWidth = 300; - attrs.m_Multiline = false; - attrs.m_Italic = false; - attrs.m_Bold = false; - attrs.m_Halign = GR_TEXT_H_ALIGN_LEFT; - attrs.m_Valign = GR_TEXT_V_ALIGN_BOTTOM; - attrs.m_Angle = ANGLE_0; - attrs.m_Mirrored = false; - - KIFONT::STROKE_FONT* strokeFont = KIFONT::STROKE_FONT::LoadFont( wxEmptyString ); + 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, metrics ); + strokeFont.get(), metrics ); plotter.EndPlot(); - delete strokeFont; - // 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'(本). - 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 ); - - // Basic sanity: file starts with %PDF - BOOST_CHECK( buffer.rfind( "%PDF", 0 ) == 0 ); - - auto contains = [&]( const char* needle ) { return buffer.find( needle ) != std::string::npos; }; - - // If expected hex sequences are not found in the raw file, attempt to locate them inside - // any Flate encoded streams by opportunistic decompression (best-effort; ignores errors). - auto ensureHexSearchable = [&]() { - if( contains( "041F" ) && contains( "65E5" ) ) - return; // already present - - std::string aggregate = buffer; - size_t pos = 0; - while( true ) - { - size_t streamPos = buffer.find( "stream\n", pos ); - if( streamPos == std::string::npos ) - break; - size_t endPos = buffer.find( "endstream", streamPos ); - if( endPos == std::string::npos ) - break; - // Skip keyword and newline - size_t dataStart = streamPos + 7; - const unsigned char* data = reinterpret_cast( buffer.data() + dataStart ); - size_t dataLen = endPos - dataStart; - // Try zlib decompression - z_stream zs{}; - zs.next_in = const_cast( data ); - zs.avail_in = static_cast( dataLen ); - if( inflateInit( &zs ) == Z_OK ) - { - std::string out; - out.resize( dataLen * 4 + 64 ); - zs.next_out = reinterpret_cast( out.data() ); - zs.avail_out = static_cast( out.size() ); - int ret = inflate( &zs, Z_FINISH ); - if( ret == Z_STREAM_END ) - { - out.resize( zs.total_out ); - aggregate += out; // append decompressed for searching - } - inflateEnd( &zs ); - } - pos = endPos + 9; - } - buffer.swap( aggregate ); - }; - - ensureHexSearchable(); + 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)" ); @@ -285,58 +156,12 @@ BOOST_AUTO_TEST_CASE( PlotMultilingualText ) // 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 ) + long darkPixels = 0; + if( RasterizePdfCountDark( pdfPath, 72, 240, darkPixels ) ) { - 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 200 non-white pixels to consider text rendered; this filters - // out tiny artifacts from broken conversions. - // TODO(#unicode-pdf): Once coordinate transform is corrected so the text falls - // within the MediaBox, raise this threshold back to a meaningful value. - BOOST_CHECK_MESSAGE( darkPixels > 200, - "Rasterized PDF appears blank or too sparse (" << darkPixels - << " dark pixels)" ); - - // Housekeeping - wxRemoveFile( pngPath ); - } - else - { - BOOST_TEST_MESSAGE( "pdftoppm succeeded but PNG output missing; skipping raster validation" ); - } + BOOST_CHECK_MESSAGE( darkPixels > 200, + "Rasterized PDF appears blank or too sparse (" << darkPixels + << " dark pixels)" ); } else { @@ -344,11 +169,7 @@ BOOST_AUTO_TEST_CASE( PlotMultilingualText ) } } - wxString keepEnv; - if( !wxGetEnv( wxT("KICAD_KEEP_TEST_PDF"), &keepEnv ) || keepEnv.IsEmpty() ) - wxRemoveFile( pdfPath ); - else - BOOST_TEST_MESSAGE( "Keeping debug PDF: " << pdfPath ); + MaybeRemoveFile( pdfPath ); } BOOST_AUTO_TEST_CASE( PlotMultilingualTextBold ) @@ -359,93 +180,24 @@ BOOST_AUTO_TEST_CASE( PlotMultilingualTextBold ) wxString pdfPath = getTempPdfPath( "kicad_pdf_unicode_bold" ); PDF_PLOTTER plotter; - - class TEST_RENDER_SETTINGS : public RENDER_SETTINGS - { - public: - TEST_RENDER_SETTINGS() - { - m_background = COLOR4D( 1.0, 1.0, 1.0, 1.0 ); - m_grid = COLOR4D( 0.8, 0.8, 0.8, 1.0 ); - m_cursor = COLOR4D( 0.0, 0.0, 0.0, 1.0 ); - } - - COLOR4D GetColor( const KIGFX::VIEW_ITEM*, int ) const override - { - return COLOR4D( 0.0, 0.0, 0.0, 1.0 ); - } - - const COLOR4D& GetBackgroundColor() const override { return m_background; } - void SetBackgroundColor( const COLOR4D& aColor ) override { m_background = aColor; } - const COLOR4D& GetGridColor() override { return m_grid; } - const COLOR4D& GetCursorColor() override { return m_cursor; } - - private: - COLOR4D m_background; - COLOR4D m_grid; - COLOR4D m_cursor; - } renderSettings; - + 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; - attrs.m_Size = VECTOR2I( 3000, 3000 ); - attrs.m_StrokeWidth = 300; - attrs.m_Multiline = false; - attrs.m_Italic = false; - attrs.m_Bold = true; // bold - attrs.m_Halign = GR_TEXT_H_ALIGN_LEFT; - attrs.m_Valign = GR_TEXT_V_ALIGN_BOTTOM; - attrs.m_Angle = ANGLE_0; - attrs.m_Mirrored = false; - - KIFONT::STROKE_FONT* strokeFont = KIFONT::STROKE_FONT::LoadFont( wxEmptyString ); + 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, metrics ); + strokeFont.get(), metrics ); plotter.EndPlot(); - delete strokeFont; - - 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 contains = [&]( const char* needle ) { return buffer.find( needle ) != std::string::npos; }; - if( !contains( "041F" ) || !contains( "65E5" ) ) - { - // attempt decompression pass copied from base test (simplified: just search once) - size_t pos = 0; std::string aggregate = buffer; - while( true ) - { - size_t streamPos = buffer.find( "stream\n", pos ); - if( streamPos == std::string::npos ) break; - size_t endPos = buffer.find( "endstream", streamPos ); - if( endPos == std::string::npos ) break; - size_t dataStart = streamPos + 7; size_t dataLen = endPos - dataStart; - const unsigned char* data = reinterpret_cast( buffer.data() + dataStart ); - z_stream zs{}; zs.next_in = const_cast( data ); zs.avail_in = (uInt) dataLen; - if( inflateInit( &zs ) == Z_OK ) - { - std::string out; out.resize( dataLen * 4 + 64 ); - zs.next_out = reinterpret_cast( out.data() ); zs.avail_out = (uInt) out.size(); - int ret = inflate( &zs, Z_FINISH ); - if( ret == Z_STREAM_END ) { out.resize( zs.total_out ); aggregate += out; } - inflateEnd( &zs ); - } - pos = endPos + 9; - } - buffer.swap( aggregate ); - } + 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)" ); - wxString keepEnv; if( !wxGetEnv( wxT("KICAD_KEEP_TEST_PDF"), &keepEnv ) || keepEnv.IsEmpty() ) wxRemoveFile( pdfPath ); + MaybeRemoveFile( pdfPath ); } BOOST_AUTO_TEST_CASE( PlotMultilingualTextItalic ) @@ -454,12 +206,24 @@ BOOST_AUTO_TEST_CASE( PlotMultilingualTextItalic ) wxString sample = wxString::FromUTF8( sampleUtf8.c_str() ); wxString pdfPath = getTempPdfPath( "kicad_pdf_unicode_italic" ); PDF_PLOTTER plotter; - class TEST_RENDER_SETTINGS : public RENDER_SETTINGS { public: TEST_RENDER_SETTINGS(){ m_background=COLOR4D(1,1,1,1); m_grid=COLOR4D(.8,.8,.8,1); m_cursor=COLOR4D(0,0,0,1);} COLOR4D GetColor(const KIGFX::VIEW_ITEM*,int) const override { return COLOR4D(0,0,0,1);} const COLOR4D& GetBackgroundColor() const override {return m_background;} void SetBackgroundColor(const COLOR4D& c) override {m_background=c;} const COLOR4D& GetGridColor() override {return m_grid;} const COLOR4D& GetCursorColor() override {return m_cursor;} COLOR4D m_background,m_grid,m_cursor; } renderSettings; + 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; attrs.m_Size=VECTOR2I(3000,3000); attrs.m_StrokeWidth=300; attrs.m_Multiline=false; attrs.m_Italic=true; attrs.m_Bold=false; attrs.m_Halign=GR_TEXT_H_ALIGN_LEFT; attrs.m_Valign=GR_TEXT_V_ALIGN_BOTTOM; attrs.m_Angle=ANGLE_0; attrs.m_Mirrored=false; KIFONT::STROKE_FONT* strokeFont=KIFONT::STROKE_FONT::LoadFont(wxEmptyString); KIFONT::METRICS metrics; plotter.PlotText( VECTOR2I(50000,60000), COLOR4D(0,0,0,1), sample, attrs, strokeFont, metrics ); plotter.EndPlot(); delete strokeFont; 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 contains=[&](const char* n){return buffer.find(n)!=std::string::npos;}; if(!contains("041F")||!contains("65E5")){ size_t pos=0; std::string aggregate=buffer; while(true){ size_t s=buffer.find("stream\n",pos); if(s==std::string::npos) break; size_t e=buffer.find("endstream",s); if(e==std::string::npos) break; size_t ds=s+7; size_t dl=e-ds; const unsigned char* data=reinterpret_cast(buffer.data()+ds); z_stream zs{}; zs.next_in=const_cast(data); zs.avail_in=(uInt)dl; if(inflateInit(&zs)==Z_OK){ std::string out; out.resize(dl*4+64); zs.next_out=reinterpret_cast(out.data()); zs.avail_out=(uInt)out.size(); int ret=inflate(&zs,Z_FINISH); if(ret==Z_STREAM_END){ out.resize(zs.total_out); aggregate+=out;} inflateEnd(&zs);} pos=e+9;} buffer.swap(aggregate);} BOOST_CHECK_MESSAGE( contains("041F"), "Missing Cyrillic glyph mapping (italic 041F)" ); BOOST_CHECK_MESSAGE( contains("65E5"), "Missing Japanese glyph mapping (italic 65E5)" ); wxString keepEnv; if( !wxGetEnv( wxT("KICAD_KEEP_TEST_PDF"), &keepEnv ) || keepEnv.IsEmpty() ) wxRemoveFile( pdfPath ); } + 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 ) { @@ -467,22 +231,7 @@ BOOST_AUTO_TEST_CASE( PlotMultilingualTextBoldItalic ) wxString sample = wxString::FromUTF8( sampleUtf8.c_str() ); wxString pdfPath = getTempPdfPath( "kicad_pdf_unicode_bolditalic" ); PDF_PLOTTER plotter; - class TEST_RENDER_SETTINGS : public RENDER_SETTINGS - { - public: - TEST_RENDER_SETTINGS() - { - m_background = COLOR4D( 1, 1, 1, 1 ); - m_grid = COLOR4D( .8, .8, .8, 1 ); - m_cursor = COLOR4D( 0, 0, 0, 1 ); - } - COLOR4D GetColor( const KIGFX::VIEW_ITEM*, int ) const override { return COLOR4D( 0, 0, 0, 1 ); } - const COLOR4D& GetBackgroundColor() const override { return m_background; } - void SetBackgroundColor( const COLOR4D& c ) override { m_background = c; } - const COLOR4D& GetGridColor() override { return m_grid; } - const COLOR4D& GetCursorColor() override { return m_cursor; } - COLOR4D m_background, m_grid, m_cursor; - } renderSettings; + SIMPLE_RENDER_SETTINGS renderSettings; plotter.SetRenderSettings( &renderSettings ); BOOST_REQUIRE( plotter.OpenFile( pdfPath ) ); @@ -490,92 +239,18 @@ BOOST_AUTO_TEST_CASE( PlotMultilingualTextBoldItalic ) plotter.SetViewport( VECTOR2I( 0, 0 ), 1.0, 1.0, false ); BOOST_REQUIRE( plotter.StartPlot( wxT( "1" ), wxT( "TestPage" ) ) ); - TEXT_ATTRIBUTES attrs; - attrs.m_Size = VECTOR2I( 3000, 3000 ); - attrs.m_StrokeWidth = 300; - attrs.m_Multiline = false; - attrs.m_Italic = true; - attrs.m_Bold = true; - attrs.m_Halign = GR_TEXT_H_ALIGN_LEFT; - attrs.m_Valign = GR_TEXT_V_ALIGN_BOTTOM; - attrs.m_Angle = ANGLE_0; - attrs.m_Mirrored = false; - - KIFONT::STROKE_FONT* strokeFont = KIFONT::STROKE_FONT::LoadFont( wxEmptyString ); + 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, metrics ); + plotter.PlotText( VECTOR2I( 50000, 60000 ), COLOR4D( 0, 0, 0, 1 ), sample, attrs, strokeFont.get(), metrics ); plotter.EndPlot(); - delete strokeFont; - - 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 contains = [&]( const char* n ) - { - return buffer.find( n ) != std::string::npos; - }; - - if( !contains( "041F" ) || !contains( "65E5" ) ) - { - size_t pos = 0; - std::string aggregate = buffer; - while( true ) - { - size_t s = buffer.find( "stream\n", pos ); - - if( s == std::string::npos ) - break; - - size_t e = buffer.find( "endstream", s ); - - if( e == std::string::npos ) - break; - - size_t ds = s + 7; - size_t dl = e - ds; - const unsigned char* data = reinterpret_cast( buffer.data() + ds ); - z_stream zs{}; - zs.next_in = const_cast( data ); - zs.avail_in = (uInt) dl; - - if( inflateInit( &zs ) == Z_OK ) - { - std::string out; - out.resize( dl * 4 + 64 ); - zs.next_out = reinterpret_cast( out.data() ); - zs.avail_out = (uInt) out.size(); - int ret = inflate( &zs, Z_FINISH ); - - if( ret == Z_STREAM_END ) - { - out.resize( zs.total_out ); - aggregate += out; - } - - inflateEnd( &zs ); - } - - pos = e + 9; - } - - buffer.swap( aggregate ); - } - + 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)" ); - wxString keepEnv; - - if( !wxGetEnv( wxT( "KICAD_KEEP_TEST_PDF" ), &keepEnv ) || keepEnv.IsEmpty() ) - wxRemoveFile( pdfPath ); + MaybeRemoveFile( pdfPath ); } // Test Y offset bounding box fix: ensure characters are not clipped when Y offset is applied @@ -591,62 +266,33 @@ BOOST_AUTO_TEST_CASE( PlotMultilingualTextWithYOffset ) wxString pdfPath = getTempPdfPath( "kicad_pdf_unicode_yoffset" ); PDF_PLOTTER plotter; - class TEST_RENDER_SETTINGS : public RENDER_SETTINGS - { - public: - TEST_RENDER_SETTINGS() - { - m_background = COLOR4D( 1, 1, 1, 1 ); - m_grid = COLOR4D( .8, .8, .8, 1 ); - m_cursor = COLOR4D( 0, 0, 0, 1 ); - } - COLOR4D GetColor( const KIGFX::VIEW_ITEM*, int ) const override { return COLOR4D( 0, 0, 0, 1 ); } - const COLOR4D& GetBackgroundColor() const override { return m_background; } - void SetBackgroundColor( const COLOR4D& c ) override { m_background = c; } - const COLOR4D& GetGridColor() override { return m_grid; } - const COLOR4D& GetCursorColor() override { return m_cursor; } - COLOR4D m_background, m_grid, m_cursor; - } renderSettings; + 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; - attrs.m_Size = VECTOR2I( 4000, 4000 ); - attrs.m_StrokeWidth = 400; - attrs.m_Multiline = false; - attrs.m_Italic = false; - attrs.m_Bold = false; - attrs.m_Halign = GR_TEXT_H_ALIGN_LEFT; - attrs.m_Valign = GR_TEXT_V_ALIGN_BOTTOM; - attrs.m_Angle = ANGLE_0; - attrs.m_Mirrored = false; - KIFONT::STROKE_FONT* strokeFont = KIFONT::STROKE_FONT::LoadFont( wxEmptyString ); + 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, metrics ); + plotter.PlotText( VECTOR2I( 50000, 60000 ), COLOR4D( 0, 0, 0, 1 ), sample, attrs, strokeFont.get(), metrics ); plotter.EndPlot(); - delete strokeFont; + // Restore original Y offset cfg.m_PDFStrokeFontYOffset = originalOffset; - // Basic PDF validation - 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); - - // Decompress streams to find d1 operators - auto appendDecompressed = [&]() { - std::string aggregate = buffer; size_t pos=0; while(true){ size_t s=buffer.find("stream\n",pos); if(s==std::string::npos) break; size_t e=buffer.find("endstream",s); if(e==std::string::npos) break; size_t ds=s+7; size_t dl=e-ds; const unsigned char* data=reinterpret_cast(buffer.data()+ds); z_stream zs{}; zs.next_in=const_cast(data); zs.avail_in=(uInt)dl; if(inflateInit(&zs)==Z_OK){ std::string out; out.resize(dl*4+64); zs.next_out=reinterpret_cast(out.data()); zs.avail_out=(uInt)out.size(); int ret=inflate(&zs,Z_FINISH); if(ret==Z_STREAM_END){ out.resize(zs.total_out); aggregate+=out; } inflateEnd(&zs);} pos=e+9;} buffer.swap(aggregate); }; - appendDecompressed(); + // 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(buffer.find("d1") != std::string::npos, "PDF should contain d1 operators for glyph bounding boxes"); + BOOST_CHECK_MESSAGE( buffer6.find( "d1" ) != std::string::npos, + "PDF should contain d1 operators for glyph bounding boxes" ); - wxString keepEnv2; if( !wxGetEnv( wxT("KICAD_KEEP_TEST_PDF"), &keepEnv2 ) || keepEnv2.IsEmpty() ) wxRemoveFile( pdfPath ); - else BOOST_TEST_MESSAGE( "Keeping Y-offset debug PDF: " << pdfPath ); + MaybeRemoveFile( pdfPath ); } BOOST_AUTO_TEST_CASE( PlotOutlineFontEmbedding ) @@ -664,38 +310,14 @@ BOOST_AUTO_TEST_CASE( PlotOutlineFontEmbedding ) BOOST_REQUIRE( wxFileExists( fontPath ) ); PDF_PLOTTER plotter; - class TEST_RENDER_SETTINGS : public RENDER_SETTINGS - { - public: - TEST_RENDER_SETTINGS() - { - m_background = COLOR4D( 1, 1, 1, 1 ); - m_grid = COLOR4D( .8, .8, .8, 1 ); - m_cursor = COLOR4D( 0, 0, 0, 1 ); - } - COLOR4D GetColor( const KIGFX::VIEW_ITEM*, int ) const override { return COLOR4D( 0, 0, 0, 1 ); } - const COLOR4D& GetBackgroundColor() const override { return m_background; } - void SetBackgroundColor( const COLOR4D& c ) override { m_background = c; } - const COLOR4D& GetGridColor() override { return m_grid; } - const COLOR4D& GetCursorColor() override { return m_cursor; } - COLOR4D m_background, m_grid, m_cursor; - } renderSettings; + 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; - attrs.m_Size = VECTOR2I( 4000, 4000 ); - attrs.m_StrokeWidth = 0; - attrs.m_Multiline = false; - attrs.m_Italic = false; - attrs.m_Bold = false; - attrs.m_Halign = GR_TEXT_H_ALIGN_LEFT; - attrs.m_Valign = GR_TEXT_V_ALIGN_BOTTOM; - attrs.m_Angle = ANGLE_0; - attrs.m_Mirrored = false; + TEXT_ATTRIBUTES attrs = BuildTextAttributes( 4000, 0, false, false ); std::vector embeddedFonts; embeddedFonts.push_back( fontPath ); @@ -716,8 +338,7 @@ BOOST_AUTO_TEST_CASE( PlotOutlineFontEmbedding ) std::string buffer; buffer.resize( (size_t) len ); file.Read( buffer.data(), len ); BOOST_CHECK( buffer.rfind( "%PDF", 0 ) == 0 ); - auto appendDecompressed = [&]() { - std::string aggregate = buffer; size_t pos=0; while(true){ size_t s=buffer.find("stream\n",pos); if(s==std::string::npos) break; size_t e=buffer.find("endstream",s); if(e==std::string::npos) break; size_t ds=s+7; size_t dl=e-ds; const unsigned char* data=reinterpret_cast(buffer.data()+ds); z_stream zs{}; zs.next_in=const_cast(data); zs.avail_in=(uInt)dl; if(inflateInit(&zs)==Z_OK){ std::string out; out.resize(dl*4+64); zs.next_out=reinterpret_cast(out.data()); zs.avail_out=(uInt)out.size(); int ret=inflate(&zs,Z_FINISH); if(ret==Z_STREAM_END){ out.resize(zs.total_out); aggregate+=out; } inflateEnd(&zs);} pos=e+9;} buffer.swap(aggregate); }; + auto appendDecompressed = [&]() { std::string tmp; ReadPdfWithDecompressedStreams( pdfPath, tmp ); buffer.swap( tmp ); }; appendDecompressed(); BOOST_CHECK_MESSAGE( buffer.find( "/CIDFontType2" ) != std::string::npos, @@ -791,10 +412,7 @@ BOOST_AUTO_TEST_CASE( PlotOutlineFontEmbedding ) } } - wxString keepEnv; - - if( !wxGetEnv( wxT("KICAD_KEEP_TEST_PDF"), &keepEnv ) || keepEnv.IsEmpty() ) - wxRemoveFile( pdfPath ); + MaybeRemoveFile( pdfPath ); } BOOST_AUTO_TEST_SUITE_END()