KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pdf_test_utils.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <algorithm>
21
23#include <gal/color4d.h>
24
25#include <wx/filename.h>
26#include <wx/filefn.h>
27#include <wx/ffile.h>
28#include <wx/utils.h>
29#include <wx/image.h>
30#include <wx/imagpng.h>
31
32#include <zlib.h>
33
34wxString MakeTempPdfPath( const wxString& aPrefix )
35{
36 wxFileName fn = wxFileName::CreateTempFileName( aPrefix );
37 fn.SetExt( "pdf" );
38 return fn.GetFullPath();
39}
40
42{
43 m_background = KIGFX::COLOR4D( 1.0, 1.0, 1.0, 1.0 );
44 m_grid = KIGFX::COLOR4D( 0.8, 0.8, 0.8, 1.0 );
45 m_cursor = KIGFX::COLOR4D( 0.0, 0.0, 0.0, 1.0 );
46}
47
49{
50 return KIGFX::COLOR4D( 0.0, 0.0, 0.0, 1.0 );
51}
52
53TEXT_ATTRIBUTES BuildTextAttributes( int aSizeIu, int aStrokeWidth, bool aBold, bool aItalic )
54{
55 TEXT_ATTRIBUTES attrs;
56 attrs.m_Size = VECTOR2I( aSizeIu, aSizeIu );
57 attrs.m_StrokeWidth = aStrokeWidth;
58 attrs.m_Multiline = false;
59 attrs.m_Italic = aItalic;
60 attrs.m_Bold = aBold;
63 attrs.m_Angle = ANGLE_0;
64 attrs.m_Mirrored = false;
65 return attrs;
66}
67
68std::unique_ptr<KIFONT::STROKE_FONT> LoadStrokeFontUnique()
69{
70 return std::unique_ptr<KIFONT::STROKE_FONT>( KIFONT::STROKE_FONT::LoadFont( wxEmptyString ) );
71}
72
73static void append_decompressed_streams( std::string& aBuffer )
74{
75 std::string aggregate = aBuffer;
76 size_t pos = 0;
77
78 while( true )
79 {
80 size_t streamPos = aBuffer.find( "stream\n", pos );
81
82 if( streamPos == std::string::npos )
83 break;
84
85 size_t endPos = aBuffer.find( "endstream", streamPos );
86
87 if( endPos == std::string::npos )
88 break;
89
90 size_t dataStart = streamPos + 7; // skip "stream\n"
91 size_t dataLen = ( endPos > dataStart ) ? ( endPos - dataStart ) : 0;
92
93 if( dataLen > 0 )
94 {
95 // Trailing newline after the zlib payload is common in PDF writers; strip it so
96 // inflate does not see leftover input.
97 while( dataLen > 0
98 && ( aBuffer[dataStart + dataLen - 1] == '\n' || aBuffer[dataStart + dataLen - 1] == '\r' ) )
99 {
100 --dataLen;
101 }
102
103 const unsigned char* data = reinterpret_cast<const unsigned char*>( aBuffer.data() + dataStart );
104 z_stream zs{};
105 zs.next_in = const_cast<Bytef*>( data );
106 zs.avail_in = static_cast<uInt>( dataLen );
107
108 if( inflateInit( &zs ) == Z_OK )
109 {
110 // Grow until inflate finishes.
111 std::string out;
112 out.resize( std::max( dataLen * 8 + 256, size_t( 4096 ) ) );
113 int ret = Z_OK;
114
115 for( ;; )
116 {
117 zs.next_out = reinterpret_cast<Bytef*>( out.data() ) + zs.total_out;
118 zs.avail_out = static_cast<uInt>( out.size() - zs.total_out );
119 ret = inflate( &zs, Z_FINISH );
120
121 if( ret == Z_STREAM_END || ret != Z_BUF_ERROR )
122 break;
123
124 out.resize( out.size() * 2 );
125 }
126
127 if( ret == Z_STREAM_END )
128 {
129 out.resize( zs.total_out );
130 aggregate += out;
131 }
132
133 inflateEnd( &zs );
134 }
135 }
136
137 pos = endPos + 9; // skip past "endstream"
138 }
139
140 aBuffer.swap( aggregate );
141}
142
143bool ReadPdfWithDecompressedStreams( const wxString& aPdfPath, std::string& aOutBuffer )
144{
145 wxFFile file( aPdfPath, "rb" );
146
147 if( !file.IsOpened() )
148 return false;
149
150 wxFileOffset len = file.Length();
151 if( len <= 0 )
152 return false;
153
154 aOutBuffer.resize( static_cast<size_t>( len ) );
155 file.Read( aOutBuffer.data(), len );
156
157 append_decompressed_streams( aOutBuffer );
158 return true;
159}
160
161int CountOccurrences( const std::string& aHaystack, const std::string& aNeedle )
162{
163 if( aNeedle.empty() )
164 return 0;
165
166 int count = 0;
167 size_t pos = 0;
168
169 while( true )
170 {
171 size_t p = aHaystack.find( aNeedle, pos );
172 if( p == std::string::npos )
173 break;
174 ++count;
175 pos = p + 1; // allow overlaps
176 }
177
178 return count;
179}
180
181bool RasterizePdfCountDark( const wxString& aPdfPath, int aDpi, int aNearWhiteThresh,
182 long& aOutDarkPixels )
183{
184 aOutDarkPixels = 0;
185
186 wxString rasterBase = wxFileName::CreateTempFileName( wxT( "kicad_pdf_raster" ) );
187 wxString cmd = wxString::Format( wxT( "pdftoppm -r %d -singlefile -png \"%s\" \"%s\"" ),
188 aDpi, aPdfPath, rasterBase );
189 int ret = wxExecute( cmd, wxEXEC_SYNC );
190
191 if( ret != 0 )
192 return false;
193
194 wxString pngPath = rasterBase + wxT( ".png" );
195
196 if( !wxFileExists( pngPath ) )
197 return false;
198
199 if( !wxImage::FindHandler( wxBITMAP_TYPE_PNG ) )
200 wxImage::AddHandler( new wxPNGHandler );
201
202 wxImage img( pngPath );
203 if( !img.IsOk() )
204 return false;
205
206 int w = img.GetWidth();
207 int h = img.GetHeight();
208
209 long dark = 0;
210 for( int y = 0; y < h; ++y )
211 {
212 for( int x = 0; x < w; ++x )
213 {
214 unsigned char r = img.GetRed( x, y );
215 unsigned char g = img.GetGreen( x, y );
216 unsigned char b = img.GetBlue( x, y );
217
218 if( r < aNearWhiteThresh || g < aNearWhiteThresh || b < aNearWhiteThresh )
219 ++dark;
220 }
221 }
222
223 aOutDarkPixels = dark;
224
225 // cleanup the rasterized file
226 wxRemoveFile( pngPath );
227 return true;
228}
229
230void MaybeRemoveFile( const wxString& aPath, const wxString& aEnvVar )
231{
232 wxString keepEnv;
233 if( !wxGetEnv( aEnvVar, &keepEnv ) || keepEnv.IsEmpty() )
234 wxRemoveFile( aPath );
235}
static STROKE_FONT * LoadFont(const wxString &aFontName)
Load a stroke font.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
An abstract base class for deriving all objects that can be added to a VIEW.
Definition view_item.h:82
KIGFX::COLOR4D m_cursor
KIGFX::COLOR4D m_background
KIGFX::COLOR4D GetColor(const KIGFX::VIEW_ITEM *, int) const override
Returns the color that should be used to draw the specific VIEW_ITEM on the specific layer using curr...
GR_TEXT_H_ALIGN_T m_Halign
GR_TEXT_V_ALIGN_T m_Valign
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:411
int CountOccurrences(const std::string &aHaystack, const std::string &aNeedle)
Count occurrences of a substring in a string (overlapping allowed).
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.
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.
static void append_decompressed_streams(std::string &aBuffer)
wxString MakeTempPdfPath(const wxString &aPrefix)
Make a temporary file path with .pdf extension using a given prefix.
TEXT_ATTRIBUTES BuildTextAttributes(int aSizeIu, int aStrokeWidth, bool aBold, bool aItalic)
Build a commonly used set of text attributes for plotting text in tests.
void MaybeRemoveFile(const wxString &aPath, const wxString &aEnvVar)
Remove a file unless the given environment variable is set (defaults to KICAD_KEEP_TEST_PDF).
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_BOTTOM
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683