20#include <boost/test/unit_test.hpp>
22#include <wx/filename.h>
52 const std::string sampleUtf8 =
"ABCDEF Привет 日本語 漢字";
53 wxString sample = wxString::FromUTF8( sampleUtf8.c_str() );
54 wxString pdfPath =
getTempPdfPath(
"kicad_pdf_unicode_allstyles" );
64 auto emitStyle = [&](
bool bold,
bool italic,
int yoff )
71 strokeFont.get(), metrics );
74 emitStyle(
false,
false, 0 );
75 emitStyle(
true,
false, 8000 );
76 emitStyle(
false,
true, 16000 );
77 emitStyle(
true,
true, 24000 );
84 BOOST_CHECK( buffer.rfind(
"%PDF", 0 ) == 0 );
89 BOOST_CHECK_MESSAGE( cmapCount >= 4,
"Expected at least 4 CMaps (got " << cmapCount <<
")" );
91 auto requireAll = [&](
const char* codeHex,
const char* label ) {
94 <<
") expected in all 4 styles; found " << occurrences );
97 requireAll(
"041F",
"Cyrillic PE" );
98 requireAll(
"65E5",
"Kanji 日" );
99 requireAll(
"672C",
"Kanji 本" );
107 const std::string sampleUtf8 =
"ABCDEF Привет 日本語 漢字";
108 wxString sample = wxString::FromUTF8( sampleUtf8.c_str() );
136 strokeFont.get(), metrics );
143 auto contains = [&](
const char* needle ) {
return PdfContains( buffer2, needle ); };
146 BOOST_CHECK_MESSAGE( contains(
"0420" ) || contains(
"0440" ),
"Missing Cyrillic glyph mapping (0420/0440)" );
149 BOOST_CHECK_MESSAGE( contains(
"6F22" ) || contains(
"6漢" ),
"Expect Chinese Han character mapping (6F22 / 漢)" );
159 "Rasterized PDF appears blank or too sparse (" << darkPixels
160 <<
" dark pixels)" );
173 const std::string sampleUtf8 =
"ABCDEF Привет 日本語 漢字";
174 wxString sample = wxString::FromUTF8( sampleUtf8.c_str() );
189 strokeFont.get(), metrics );
192 auto contains = [&](
const char* needle ) {
return PdfContains( buffer3, needle ); };
201 const std::string sampleUtf8 =
"ABCDEF Привет 日本語 漢字";
202 wxString sample = wxString::FromUTF8( sampleUtf8.c_str() );
218 auto contains = [&](
const char* n ) {
return PdfContains( buffer4, n ); };
226 const std::string sampleUtf8 =
"ABCDEF Привет 日本語 漢字";
227 wxString sample = wxString::FromUTF8( sampleUtf8.c_str() );
228 wxString pdfPath =
getTempPdfPath(
"kicad_pdf_unicode_bolditalic" );
246 auto contains = [&](
const char* n ) {
return PdfContains( buffer5, n ); };
247 BOOST_CHECK_MESSAGE( contains(
"041F" ),
"Missing Cyrillic glyph mapping (bold-italic 041F)" );
248 BOOST_CHECK_MESSAGE( contains(
"65E5" ),
"Missing Japanese glyph mapping (bold-italic 65E5)" );
256 const std::string sampleUtf8 =
"MW";
257 wxString sample = wxString::FromUTF8( sampleUtf8.c_str() );
273 strokeFont.get(), metrics );
284 double unitsPerEm = 1000.0;
289 std::string::size_type pos = 0;
290 int checkedGlyphs = 0;
292 while( ( pos = buffer.find(
" d1 ", pos ) ) != std::string::npos )
295 std::string::size_type lineStart = buffer.rfind(
'\n', pos );
297 if( lineStart == std::string::npos )
302 std::string d1Line = buffer.substr( lineStart, pos + 3 - lineStart );
304 double width, wy, minX, minY, maxX, maxY;
306 if( sscanf( d1Line.c_str(),
"%lf %lf %lf %lf %lf %lf", &width, &wy, &minX, &minY,
307 &maxX, &maxY ) == 6 )
310 if( width == 0.0 && maxX == 0.0 )
320 "Glyph bbox minX (" << minX
321 <<
") suggests X offset or stroke padding is missing" );
325 "Glyph bbox maxX (" << maxX <<
") must exceed advance width ("
326 << width <<
") to prevent clipping" );
335 "Expected at least 2 non-notdef glyphs, found " << checkedGlyphs );
348 const std::string sampleUtf8 =
"Yg Test ñ";
349 wxString sample = wxString::FromUTF8( sampleUtf8.c_str() );
372 BOOST_CHECK( buffer6.rfind(
"%PDF", 0 ) == 0 );
377 "PDF should contain d1 operators for glyph bounding boxes" );
388 fontFile.RemoveLastDir();
389 fontFile.AppendDir( wxT(
"resources" ) );
390 fontFile.AppendDir( wxT(
"fonts" ) );
391 fontFile.SetFullName( wxT(
"NotoSans-Regular.ttf" ) );
392 wxString fontPath = fontFile.GetFullPath();
406 std::vector<wxString> embeddedFonts;
407 embeddedFonts.push_back( fontPath );
412 wxString sample = wxString::FromUTF8(
"Outline café" );
415 outlineFont, metrics );
419 wxFFile file( pdfPath,
"rb" );
421 wxFileOffset len = file.Length();
422 std::string buffer; buffer.resize( (
size_t) len ); file.Read( buffer.data(), len );
423 BOOST_CHECK( buffer.rfind(
"%PDF", 0 ) == 0 );
426 appendDecompressed();
429 "Expected CIDFontType2 descendant font" );
431 "Embedded outline font should include FontFile2 stream" );
433 "BaseFont should reference Noto Sans subset" );
435 "ToUnicode map should include Latin character with accent" );
437 "Outline font resource entry missing" );
442 wxString rasterBase = wxFileName::CreateTempFileName( wxT(
"kicad_pdf_raster") );
443 wxString cmd = wxString::Format( wxT(
"pdftoppm -r 72 -singlefile -png \"%s\" \"%s\""),
444 pdfPath, rasterBase );
446 int ret = wxExecute( cmd, wxEXEC_SYNC );
450 wxString pngPath = rasterBase + wxT(
".png");
452 if( wxFileExists( pngPath ) )
455 if( !wxImage::FindHandler( wxBITMAP_TYPE_PNG ) )
456 wxImage::AddHandler(
new wxPNGHandler );
458 wxImage img( pngPath );
459 BOOST_REQUIRE_MESSAGE( img.IsOk(),
"Failed to load rasterized PDF image" );
462 int w = img.GetWidth();
463 int h = img.GetHeight();
465 for(
int y = 0; y < h; ++y )
467 for(
int x = 0; x < w; ++x )
469 unsigned char r = img.GetRed( x, y );
470 unsigned char g = img.GetGreen( x, y );
471 unsigned char b = img.GetBlue( x, y );
474 if( r < 240 || g < 240 || b < 240 )
482 "Rasterized PDF appears blank or too sparse (" << darkPixels
483 <<
" dark pixels). Outline font may not be rendering correctly." );
486 wxRemoveFile( pngPath );
490 BOOST_TEST_MESSAGE(
"pdftoppm succeeded but PNG output missing; skipping raster validation" );
521 wxString textWithTab = wxT(
"Before\tAfter" );
522 wxString textWithoutTab = wxT(
"BeforeAfter" );
525 strokeFont.get(), metrics );
527 strokeFont.get(), metrics );
533 BOOST_CHECK( buffer.rfind(
"%PDF", 0 ) == 0 );
538 "PDF should contain 'A' from 'After'" );
565 const int anchorY = 60000;
566 const int sizeIU = 3000;
567 const int strokeW = 300;
576 strokeFont.get(), metrics );
590 struct MATRIX_SAMPLE {
double a, b, c, d, e, f; };
591 std::vector<MATRIX_SAMPLE> matrices;
592 std::string::size_type pos = 0;
594 while( ( pos = buffer.find(
"cm BT", pos ) ) != std::string::npos )
596 std::string::size_type lineStart = buffer.rfind(
'\n', pos );
597 lineStart = ( lineStart == std::string::npos ) ? 0 : lineStart + 1;
599 std::string cmLine = buffer.substr( lineStart, pos - lineStart );
602 if( sscanf( cmLine.c_str(),
"q %lf %lf %lf %lf %lf %lf", &m.a, &m.b, &m.c, &m.d, &m.e,
605 matrices.push_back( m );
611 BOOST_REQUIRE_EQUAL( matrices.size(), 5u );
613 const double fontSize = (double) sizeIU;
614 const double thickness = (double) strokeW;
623 const double expected_delta_top_center = ( 1.000 - 0.415 ) * fontSize;
624 const double expected_delta_center_bot = ( 0.415 - ( -0.17 ) ) * fontSize;
626 const double observed_delta_top_center = matrices[1].f - matrices[0].f;
627 const double observed_delta_center_bot = matrices[2].f - matrices[1].f;
629 BOOST_CHECK_CLOSE( observed_delta_top_center, expected_delta_top_center, 1.0 );
630 BOOST_CHECK_CLOSE( observed_delta_center_bot, expected_delta_center_bot, 1.0 );
633 "Expected ctm_f to decrease from V_BOTTOM to V_CENTER to V_TOP, got "
634 << matrices[0].f <<
", " << matrices[1].f <<
", " << matrices[2].f );
639 const double uncorrectedDelta = 0.5 * ( fontSize + 3.0 * thickness );
641 "ctm_f delta for V_TOP vs V_CENTER matches the uncorrected formula; "
642 "the stroke-font alignment fix does not appear to be in effect." );
649 const double italic_delta_e = matrices[4].e - matrices[3].e;
650 const double nonitalic_delta_e = matrices[1].e - matrices[0].e;
653 "Italic text matrix does not show the expected shear (adj_c == "
654 << matrices[3].c <<
")" );
657 BOOST_CHECK_SMALL( nonitalic_delta_e, 1.0 );
664 "Italic baseline correction did not shear along the text-matrix Y "
665 "axis; italic_delta_e = "
667 <<
" (should be non-zero when adj_c is non-zero)" );
669 "italic_delta_e should have the opposite sign of adj_c; got "
670 << italic_delta_e <<
" vs adj_c=" << matrices[3].c );
700 const int sizeIU = 1900;
701 const int strokeW = 380;
710 wxT(
"TEST text Silkscreen" ), attrs, strokeFont.get(), metrics );
718 std::vector<double> wordOriginX;
719 std::string::size_type pos = 0;
721 while( ( pos = buffer.find(
"cm BT", pos ) ) != std::string::npos )
723 std::string::size_type lineStart = buffer.rfind(
'\n', pos );
724 lineStart = ( lineStart == std::string::npos ) ? 0 : lineStart + 1;
726 std::string cmLine = buffer.substr( lineStart, pos - lineStart );
727 double a, b, c, d, e, f;
729 if( sscanf( cmLine.c_str(),
"q %lf %lf %lf %lf %lf %lf", &a, &b, &c, &d, &e, &f ) == 6 )
730 wordOriginX.push_back( e );
736 BOOST_REQUIRE_EQUAL( wordOriginX.size(), 3u );
742 auto strokeAdvanceIU = [&](
const wxString& aWord )
745 ->GetTextAsGlyphs(
nullptr,
nullptr, aWord,
VECTOR2I( sizeIU, sizeIU ),
750 const double expectedTestAdvanceIU = (double) strokeAdvanceIU( wxT(
"TEST " ) );
751 const double expectedTextAdvanceIU = (double) strokeAdvanceIU( wxT(
"text " ) );
753 const double observedTestAdvanceDev = wordOriginX[1] - wordOriginX[0];
754 const double observedTextAdvanceDev = wordOriginX[2] - wordOriginX[1];
757 const double scale = observedTestAdvanceDev / expectedTestAdvanceIU;
762 const double expectedTextAdvanceDev =
scale * expectedTextAdvanceIU;
763 BOOST_CHECK_CLOSE( observedTextAdvanceDev, expectedTextAdvanceDev, 0.5 );
768 const double inflatedTextAdvanceDev =
scale * ( expectedTextAdvanceIU + 3.0 * strokeW );
770 > 0.5 * ( inflatedTextAdvanceDev - expectedTextAdvanceDev ),
771 "Word spacing matches the buggy inflated-bbox formula ("
772 << observedTextAdvanceDev <<
" ~= " << inflatedTextAdvanceDev
773 <<
"); the renderWord cursor fix appears inactive." );
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.
GR_TEXT_H_ALIGN_T m_Halign
GR_TEXT_V_ALIGN_T m_Valign
static constexpr EDA_ANGLE ANGLE_0
double m_PDFStrokeFontXOffset
Horizontal offset factor applied to stroke font glyph coordinates (in EM units) after to compensate m...
double m_PDFStrokeFontYOffset
Vertical offset factor applied to stroke font glyph coordinates (in EM units) after Y inversion to co...
double m_PDFStrokeFontWidthFactor
Stroke font line width factor relative to EM size for PDF stroke fonts.
std::string GetTestDataRootDir()
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
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_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
GR_TEXT_V_ALIGN_T
This is API surface mapped to common.types.VertialAlignment.
VECTOR2< int32_t > VECTOR2I