Generalize the PDF qa test
This commit is contained in:
@@ -37,6 +37,7 @@
|
||||
#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
|
||||
@@ -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<const unsigned char*>(buffer.data()+ds); z_stream zs{}; zs.next_in=const_cast<Bytef*>(data); zs.avail_in=(uInt)dl; if(inflateInit(&zs)==Z_OK){ std::string out; out.resize(dl*4+64); zs.next_out=reinterpret_cast<Bytef*>(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<const unsigned char*>( buffer.data() + dataStart );
|
||||
size_t dataLen = endPos - dataStart;
|
||||
// Try zlib decompression
|
||||
z_stream zs{};
|
||||
zs.next_in = const_cast<Bytef*>( data );
|
||||
zs.avail_in = static_cast<uInt>( dataLen );
|
||||
if( inflateInit( &zs ) == Z_OK )
|
||||
{
|
||||
std::string out;
|
||||
out.resize( dataLen * 4 + 64 );
|
||||
zs.next_out = reinterpret_cast<Bytef*>( out.data() );
|
||||
zs.avail_out = static_cast<uInt>( 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<const unsigned char*>( buffer.data() + dataStart );
|
||||
z_stream zs{}; zs.next_in = const_cast<Bytef*>( data ); zs.avail_in = (uInt) dataLen;
|
||||
if( inflateInit( &zs ) == Z_OK )
|
||||
{
|
||||
std::string out; out.resize( dataLen * 4 + 64 );
|
||||
zs.next_out = reinterpret_cast<Bytef*>( 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<const unsigned char*>(buffer.data()+ds); z_stream zs{}; zs.next_in=const_cast<Bytef*>(data); zs.avail_in=(uInt)dl; if(inflateInit(&zs)==Z_OK){ std::string out; out.resize(dl*4+64); zs.next_out=reinterpret_cast<Bytef*>(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<const unsigned char*>( buffer.data() + ds );
|
||||
z_stream zs{};
|
||||
zs.next_in = const_cast<Bytef*>( data );
|
||||
zs.avail_in = (uInt) dl;
|
||||
|
||||
if( inflateInit( &zs ) == Z_OK )
|
||||
{
|
||||
std::string out;
|
||||
out.resize( dl * 4 + 64 );
|
||||
zs.next_out = reinterpret_cast<Bytef*>( 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<const unsigned char*>(buffer.data()+ds); z_stream zs{}; zs.next_in=const_cast<Bytef*>(data); zs.avail_in=(uInt)dl; if(inflateInit(&zs)==Z_OK){ std::string out; out.resize(dl*4+64); zs.next_out=reinterpret_cast<Bytef*>(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<wxString> 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<const unsigned char*>(buffer.data()+ds); z_stream zs{}; zs.next_in=const_cast<Bytef*>(data); zs.avail_in=(uInt)dl; if(inflateInit(&zs)==Z_OK){ std::string out; out.resize(dl*4+64); zs.next_out=reinterpret_cast<Bytef*>(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()
|
||||
|
||||
Reference in New Issue
Block a user