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
22#include <qa_utils/file_utils.h>
24#include <gal/color4d.h>
25
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
35{
36 m_background = KIGFX::COLOR4D( 1.0, 1.0, 1.0, 1.0 );
37 m_grid = KIGFX::COLOR4D( 0.8, 0.8, 0.8, 1.0 );
38 m_cursor = KIGFX::COLOR4D( 0.0, 0.0, 0.0, 1.0 );
39}
40
42{
43 return KIGFX::COLOR4D( 0.0, 0.0, 0.0, 1.0 );
44}
45
46TEXT_ATTRIBUTES BuildTextAttributes( int aSizeIu, int aStrokeWidth, bool aBold, bool aItalic )
47{
48 TEXT_ATTRIBUTES attrs;
49 attrs.m_Size = VECTOR2I( aSizeIu, aSizeIu );
50 attrs.m_StrokeWidth = aStrokeWidth;
51 attrs.m_Multiline = false;
52 attrs.m_Italic = aItalic;
53 attrs.m_Bold = aBold;
56 attrs.m_Angle = ANGLE_0;
57 attrs.m_Mirrored = false;
58 return attrs;
59}
60
61std::unique_ptr<KIFONT::STROKE_FONT> LoadStrokeFontUnique()
62{
63 return std::unique_ptr<KIFONT::STROKE_FONT>( KIFONT::STROKE_FONT::LoadFont( wxEmptyString ) );
64}
65
66static void append_decompressed_streams( std::string& aBuffer )
67{
68 std::string aggregate = aBuffer;
69 size_t pos = 0;
70
71 while( true )
72 {
73 size_t streamPos = aBuffer.find( "stream\n", pos );
74
75 if( streamPos == std::string::npos )
76 break;
77
78 size_t endPos = aBuffer.find( "endstream", streamPos );
79
80 if( endPos == std::string::npos )
81 break;
82
83 size_t dataStart = streamPos + 7; // skip "stream\n"
84 size_t dataLen = ( endPos > dataStart ) ? ( endPos - dataStart ) : 0;
85
86 if( dataLen > 0 )
87 {
88 // Trailing newline after the zlib payload is common in PDF writers; strip it so
89 // inflate does not see leftover input.
90 while( dataLen > 0
91 && ( aBuffer[dataStart + dataLen - 1] == '\n' || aBuffer[dataStart + dataLen - 1] == '\r' ) )
92 {
93 --dataLen;
94 }
95
96 const unsigned char* data = reinterpret_cast<const unsigned char*>( aBuffer.data() + dataStart );
97 z_stream zs{};
98 zs.next_in = const_cast<Bytef*>( data );
99 zs.avail_in = static_cast<uInt>( dataLen );
100
101 if( inflateInit( &zs ) == Z_OK )
102 {
103 // Grow until inflate finishes.
104 std::string out;
105 out.resize( std::max( dataLen * 8 + 256, size_t( 4096 ) ) );
106 int ret = Z_OK;
107
108 for( ;; )
109 {
110 zs.next_out = reinterpret_cast<Bytef*>( out.data() ) + zs.total_out;
111 zs.avail_out = static_cast<uInt>( out.size() - zs.total_out );
112 ret = inflate( &zs, Z_FINISH );
113
114 if( ret != Z_BUF_ERROR )
115 break;
116
117 // Z_BUF_ERROR also reports exhausted input, which a bigger output buffer
118 // cannot cure. Doubling on that alone allocates until it throws
119 if( zs.avail_out > 0 )
120 break;
121
122 out.resize( out.size() * 2 );
123 }
124
125 if( ret == Z_STREAM_END )
126 {
127 out.resize( zs.total_out );
128 aggregate += out;
129 }
130
131 inflateEnd( &zs );
132 }
133 }
134
135 pos = endPos + 9; // skip past "endstream"
136 }
137
138 aBuffer.swap( aggregate );
139}
140
141bool ReadPdfWithDecompressedStreams( const wxString& aPdfPath, std::string& aOutBuffer )
142{
143 wxFFile file( aPdfPath, "rb" );
144
145 if( !file.IsOpened() )
146 return false;
147
148 wxFileOffset len = file.Length();
149 if( len <= 0 )
150 return false;
151
152 aOutBuffer.resize( static_cast<size_t>( len ) );
153 file.Read( aOutBuffer.data(), len );
154
155 append_decompressed_streams( aOutBuffer );
156 return true;
157}
158
159int CountOccurrences( const std::string& aHaystack, const std::string& aNeedle )
160{
161 if( aNeedle.empty() )
162 return 0;
163
164 int count = 0;
165 size_t pos = 0;
166
167 while( true )
168 {
169 size_t p = aHaystack.find( aNeedle, pos );
170 if( p == std::string::npos )
171 break;
172 ++count;
173 pos = p + 1; // allow overlaps
174 }
175
176 return count;
177}
178
179bool RasterizePdfCountDark( const wxString& aPdfPath, int aDpi, int aNearWhiteThresh,
180 long& aOutDarkPixels )
181{
182 aOutDarkPixels = 0;
183
184 KI_TEST::SCOPED_TEMP_DIR rasterDir( "kicad_pdf_raster" );
185 wxString rasterBase = rasterDir.ChildPathStr( "raster" );
186 wxString cmd = wxString::Format( wxT( "pdftoppm -r %d -singlefile -png \"%s\" \"%s\"" ),
187 aDpi, aPdfPath, rasterBase );
188 int ret = wxExecute( cmd, wxEXEC_SYNC );
189
190 if( ret != 0 )
191 return false;
192
193 wxString pngPath = rasterBase + wxT( ".png" );
194
195 if( !wxFileExists( pngPath ) )
196 return false;
197
198 if( !wxImage::FindHandler( wxBITMAP_TYPE_PNG ) )
199 wxImage::AddHandler( new wxPNGHandler );
200
201 wxImage img( pngPath );
202 if( !img.IsOk() )
203 return false;
204
205 int w = img.GetWidth();
206 int h = img.GetHeight();
207
208 long dark = 0;
209 for( int y = 0; y < h; ++y )
210 {
211 for( int x = 0; x < w; ++x )
212 {
213 unsigned char r = img.GetRed( x, y );
214 unsigned char g = img.GetGreen( x, y );
215 unsigned char b = img.GetBlue( x, y );
216
217 if( r < aNearWhiteThresh || g < aNearWhiteThresh || b < aNearWhiteThresh )
218 ++dark;
219 }
220 }
221
222 aOutDarkPixels = dark;
223
224 return true;
225}
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
wxString ChildPathStr(const wxString &aName) const
Get the path to a direct child of the temporary directory as a wxString, without creating the child.
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:422
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)
TEXT_ATTRIBUTES BuildTextAttributes(int aSizeIu, int aStrokeWidth, bool aBold, bool aItalic)
Build a commonly used set of text attributes for plotting text in tests.
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_BOTTOM
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683