Files
kicad-source-mirror/qa/qa_utils/pdf_test_utils.cpp
T
2025-09-20 11:34:18 -07:00

219 lines
6.1 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <qa_utils/pdf_test_utils.h>
#include <gal/color4d.h>
#include <wx/filename.h>
#include <wx/filefn.h>
#include <wx/ffile.h>
#include <wx/utils.h>
#include <wx/image.h>
#include <wx/imagpng.h>
#include <zlib.h>
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<KIFONT::STROKE_FONT> LoadStrokeFontUnique()
{
return std::unique_ptr<KIFONT::STROKE_FONT>( 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<const unsigned char*>( aBuffer.data() + dataStart );
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;
}
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<size_t>( 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 );
}