24#include <boost/test/unit_test.hpp> 
   26#include <wx/filename.h> 
   56    const std::string sampleUtf8 = 
"ABCDEF Привет 日本語 漢字";
 
   57    wxString sample = wxString::FromUTF8( sampleUtf8.c_str() );
 
   58    wxString pdfPath = 
getTempPdfPath( 
"kicad_pdf_unicode_allstyles" );
 
   68    auto emitStyle = [&]( 
bool bold, 
bool italic, 
int yoff )
 
   75                          strokeFont.get(), metrics );
 
   78    emitStyle( 
false, 
false, 0 );      
 
   79    emitStyle( 
true,  
false, 8000 );   
 
   80    emitStyle( 
false, 
true,  16000 );  
 
   81    emitStyle( 
true,  
true,  24000 );  
 
   88    BOOST_CHECK( buffer.rfind( 
"%PDF", 0 ) == 0 );
 
   93    BOOST_CHECK_MESSAGE( cmapCount >= 4, 
"Expected at least 4 CMaps (got " << cmapCount << 
")" );
 
   95    auto requireAll = [&]( 
const char* codeHex, 
const char* label ) {
 
   97        BOOST_CHECK_MESSAGE( occurrences >= 4, 
"Codepoint " << label << 
" (" << codeHex
 
   98                                                            << 
") expected in all 4 styles; found " << occurrences );
 
  101    requireAll( 
"041F", 
"Cyrillic PE" );
 
  102    requireAll( 
"65E5", 
"Kanji 日" );
 
  103    requireAll( 
"672C", 
"Kanji 本" );
 
 
  111    const std::string sampleUtf8 = 
"ABCDEF Привет 日本語 漢字";
 
  112    wxString sample = wxString::FromUTF8( sampleUtf8.c_str() );
 
  140                      strokeFont.get(), metrics );
 
  147    auto contains = [&]( 
const char* needle ) { 
return PdfContains( buffer2, needle ); };
 
  149    BOOST_CHECK_MESSAGE( contains( 
"041F" ), 
"Missing Cyrillic glyph mapping (041F)" );
 
  150    BOOST_CHECK_MESSAGE( contains( 
"0420" ) || contains( 
"0440" ), 
"Missing Cyrillic glyph mapping (0420/0440)" );
 
  151    BOOST_CHECK_MESSAGE( contains( 
"65E5" ), 
"Missing Japanese Kanji glyph mapping (65E5)" );
 
  152    BOOST_CHECK_MESSAGE( contains( 
"672C" ), 
"Missing Japanese Kanji glyph mapping (672C)" );
 
  153    BOOST_CHECK_MESSAGE( contains( 
"6F22" ) || contains( 
"6漢" ), 
"Expect Chinese Han character mapping (6F22 / 漢)" );
 
  162            BOOST_CHECK_MESSAGE( darkPixels > 200,
 
  163                                 "Rasterized PDF appears blank or too sparse (" << darkPixels
 
  164                                 << 
" dark pixels)" );
 
 
  177    const std::string sampleUtf8 = 
"ABCDEF Привет 日本語 漢字";
 
  178    wxString sample = wxString::FromUTF8( sampleUtf8.c_str() );
 
  193                      strokeFont.get(), metrics );
 
  196    auto contains = [&]( 
const char* needle ) { 
return PdfContains( buffer3, needle ); };
 
  197    BOOST_CHECK_MESSAGE( contains( 
"041F" ), 
"Missing Cyrillic glyph mapping (bold 041F)" );
 
  198    BOOST_CHECK_MESSAGE( contains( 
"65E5" ), 
"Missing Japanese glyph mapping (bold 65E5)" );
 
 
  205    const std::string sampleUtf8 = 
"ABCDEF Привет 日本語 漢字";
 
  206    wxString sample = wxString::FromUTF8( sampleUtf8.c_str() );
 
  222    auto contains = [&]( 
const char* n ) { 
return PdfContains( buffer4, n ); };
 
  223    BOOST_CHECK_MESSAGE( contains( 
"041F" ), 
"Missing Cyrillic glyph mapping (italic 041F)" );
 
  224    BOOST_CHECK_MESSAGE( contains( 
"65E5" ), 
"Missing Japanese glyph mapping (italic 65E5)" );
 
 
  230    const std::string sampleUtf8 = 
"ABCDEF Привет 日本語 漢字";
 
  231    wxString          sample = wxString::FromUTF8( sampleUtf8.c_str() );
 
  232    wxString          pdfPath = 
getTempPdfPath( 
"kicad_pdf_unicode_bolditalic" );
 
  250    auto contains = [&]( 
const char* n ) { 
return PdfContains( buffer5, n ); };
 
  251    BOOST_CHECK_MESSAGE( contains( 
"041F" ), 
"Missing Cyrillic glyph mapping (bold-italic 041F)" );
 
  252    BOOST_CHECK_MESSAGE( contains( 
"65E5" ), 
"Missing Japanese glyph mapping (bold-italic 65E5)" );
 
 
  264    const std::string sampleUtf8 = 
"Yg Test ñ";  
 
  265    wxString sample = wxString::FromUTF8( sampleUtf8.c_str() );
 
  288    BOOST_CHECK( buffer6.rfind( 
"%PDF", 0 ) == 0 );
 
  292    BOOST_CHECK_MESSAGE( buffer6.find( 
"d1" ) != std::string::npos,
 
  293                         "PDF should contain d1 operators for glyph bounding boxes" );
 
 
  304    fontFile.RemoveLastDir();
 
  305    fontFile.AppendDir( wxT( 
"resources" ) );
 
  306    fontFile.AppendDir( wxT( 
"fonts" ) );
 
  307    fontFile.SetFullName( wxT( 
"NotoSans-Regular.ttf" ) );
 
  308    wxString fontPath = fontFile.GetFullPath();
 
  322    std::vector<wxString> embeddedFonts;
 
  323    embeddedFonts.push_back( fontPath );
 
  328    wxString sample = wxString::FromUTF8( 
"Outline café" );
 
  331                      outlineFont, metrics );
 
  335    wxFFile file( pdfPath, 
"rb" );
 
  337    wxFileOffset len = file.Length();
 
  338    std::string buffer; buffer.resize( (
size_t) len ); file.Read( buffer.data(), len );
 
  339    BOOST_CHECK( buffer.rfind( 
"%PDF", 0 ) == 0 );
 
  342    appendDecompressed();
 
  344    BOOST_CHECK_MESSAGE( buffer.find( 
"/CIDFontType2" ) != std::string::npos,
 
  345                         "Expected CIDFontType2 descendant font" );
 
  346    BOOST_CHECK_MESSAGE( buffer.find( 
"/FontFile2" ) != std::string::npos,
 
  347                         "Embedded outline font should include FontFile2 stream" );
 
  348    BOOST_CHECK_MESSAGE( buffer.find( 
"AAAAAA+Noto-Sans" ) != std::string::npos,
 
  349                         "BaseFont should reference Noto Sans subset" );
 
  350    BOOST_CHECK_MESSAGE( buffer.find( 
"00E9" ) != std::string::npos,
 
  351                         "ToUnicode map should include Latin character with accent" );
 
  352    BOOST_CHECK_MESSAGE( buffer.find( 
"/KiCadOutline" ) != std::string::npos,
 
  353                         "Outline font resource entry missing" );
 
  358        wxString rasterBase = wxFileName::CreateTempFileName( wxT(
"kicad_pdf_raster") );
 
  359        wxString cmd = wxString::Format( wxT(
"pdftoppm -r 72 -singlefile -png \"%s\" \"%s\""),
 
  360                                         pdfPath, rasterBase );
 
  362        int ret = wxExecute( cmd, wxEXEC_SYNC );
 
  366            wxString pngPath = rasterBase + wxT(
".png");
 
  368            if( wxFileExists( pngPath ) )
 
  371                if( !wxImage::FindHandler( wxBITMAP_TYPE_PNG ) )
 
  372                    wxImage::AddHandler( 
new wxPNGHandler );
 
  374                wxImage img( pngPath );
 
  375                BOOST_REQUIRE_MESSAGE( img.IsOk(), 
"Failed to load rasterized PDF image" );
 
  378                int  w = img.GetWidth();
 
  379                int  h = img.GetHeight();
 
  381                for( 
int y = 0; y < h; ++y )
 
  383                    for( 
int x = 0; x < w; ++x )
 
  385                        unsigned char r = img.GetRed( x, y );
 
  386                        unsigned char g = img.GetGreen( x, y );
 
  387                        unsigned char b = img.GetBlue( x, y );
 
  390                        if( r < 240 || g < 240 || b < 240 )
 
  397                BOOST_CHECK_MESSAGE( darkPixels > 100,
 
  398                                     "Rasterized PDF appears blank or too sparse (" << darkPixels
 
  399                                     << 
" dark pixels). Outline font may not be rendering correctly." );
 
  402                wxRemoveFile( pngPath );
 
  406                BOOST_TEST_MESSAGE( 
"pdftoppm succeeded but PNG output missing; skipping raster validation" );
 
 
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
 
FONT is an abstract base class for both outline and stroke fonts.
 
static FONT * GetFont(const wxString &aFontName=wxEmptyString, bool aBold=false, bool aItalic=false, const std::vector< wxString > *aEmbeddedFiles=nullptr, bool aForDrawingSheet=false)
 
A color representation with 4 components: red, green, blue, alpha.
 
virtual bool EndPlot() override
 
virtual bool OpenFile(const wxString &aFullFilename) override
Open or create the plot file aFullFilename.
 
virtual bool StartPlot(const wxString &aPageNumber) override
The PDF engine supports multiple pages; the first one is opened 'for free' the following are to be cl...
 
virtual void SetViewport(const VECTOR2I &aOffset, double aIusPerDecimil, double aScale, bool aMirror) override
PDF can have multiple pages, so SetPageSettings can be called with the outputFile open (but not insid...
 
virtual void PlotText(const VECTOR2I &aPos, const COLOR4D &aColor, const wxString &aText, const TEXT_ATTRIBUTES &aAttributes, KIFONT::FONT *aFont, const KIFONT::METRICS &aFontMetrics, void *aData=nullptr) override
 
void SetRenderSettings(RENDER_SETTINGS *aSettings)
 
Minimal concrete render settings suitable for plotters in tests.
 
double m_PDFStrokeFontYOffset
Vertical offset factor applied to stroke font glyph coordinates (in EM units) after Y inversion to co...
 
std::string GetTestDataRootDir()
 
int CountOccurrences(const std::string &aHaystack, const std::string &aNeedle)
Count occurrences of a substring in a string (overlapping allowed).
 
bool PdfContains(const std::string &aBuffer, const char *aNeedle)
Convenience contains check.
 
bool ReadPdfWithDecompressedStreams(const wxString &aPdfPath, std::string &aOutBuffer)
Read a PDF file and append best-effort decompressed contents of any Flate streams to the returned buf...
 
std::unique_ptr< KIFONT::STROKE_FONT > LoadStrokeFontUnique()
Load the default stroke font and return a unique_ptr for RAII deletion.
 
TEXT_ATTRIBUTES BuildTextAttributes(int aSizeIu=3000, int aStrokeWidth=300, bool aBold=false, bool aItalic=false)
Build a commonly used set of text attributes for plotting text in tests.
 
bool RasterizePdfCountDark(const wxString &aPdfPath, int aDpi, int aNearWhiteThresh, long &aOutDarkPixels)
Rasterize a PDF page to PNG using pdftoppm if available and count non-near-white pixels.
 
wxString MakeTempPdfPath(const wxString &aPrefix)
Make a temporary file path with .pdf extension using a given prefix.
 
void MaybeRemoveFile(const wxString &aPath, const wxString &aEnvVar=wxT("KICAD_KEEP_TEST_PDF"))
Remove a file unless the given environment variable is set (defaults to KICAD_KEEP_TEST_PDF).
 
Plotting engines similar to ps (PostScript, Gerber, svg)
 
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
 
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
 
BOOST_AUTO_TEST_SUITE_END()
 
BOOST_AUTO_TEST_CASE(PlotMultilingualAllStylesMappings)
 
static wxString getTempPdfPath(const wxString &name)
 
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
 
VECTOR2< int32_t > VECTOR2I