KiCad PCB EDA Suite
Loading...
Searching...
No Matches
kicad_io_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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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
21
22#include <wx/buffer.h>
23#include <wx/ffile.h>
24
25// For some reason wxWidgets is built with wxUSE_BASE64 unset so expose the wxWidgets
26// base64 code.
27#define wxUSE_BASE64 1
28#include <wx/base64.h>
29
30#include <fmt/format.h>
31
32#include <eda_item.h>
33#include <kiid.h>
34#include <richio.h>
35#include <string_utils.h>
36
37namespace KICAD_FORMAT {
38
39void FormatBool( OUTPUTFORMATTER* aOut, const wxString& aKey, bool aValue )
40{
41 aOut->Print( "(%ls %s)", aKey.wc_str(), aValue ? "yes" : "no" );
42}
43
44void FormatOptBool( OUTPUTFORMATTER* aOut, const wxString& aKey, std::optional<bool> aValue )
45{
46 if( aValue.has_value() )
47 FormatBool( aOut, aKey, aValue.value() );
48 else
49 aOut->Print( "(%ls none)", aKey.wc_str() );
50}
51
52
53void FormatUuid( OUTPUTFORMATTER* aOut, const KIID& aUuid )
54{
55 aOut->Print( "(uuid %s)", aOut->Quotew( aUuid.AsString() ).c_str() );
56}
57
58
60{
61 if( !aItem.HasCustomProperties() )
62 return;
63
64 for( const auto& [key, value] : aItem.GetCustomProperties() )
65 aOut->Print( "(custom_property %s %s)", aOut->Quotew( key ).c_str(), aOut->Quotew( value ).c_str() );
66}
67
68
69void FormatStreamData( OUTPUTFORMATTER& aOut, const wxStreamBuffer& aStream )
70{
71 aOut.Print( "(data" );
72
73 const wxString out = wxBase64Encode( aStream.GetBufferStart(), aStream.GetBufferSize() );
74
75 // Apparently the MIME standard character width for base64 encoding is 76 (unconfirmed)
76 // so use it in a vein attempt to be standard like.
77 static constexpr unsigned MIME_BASE64_LENGTH = 76;
78
79 size_t first = 0;
80
81 while( first < out.Length() )
82 {
83 aOut.Print( "\n\"%s\"", TO_UTF8( out( first, MIME_BASE64_LENGTH ) ) );
84 first += MIME_BASE64_LENGTH;
85 }
86
87 aOut.Print( ")" ); // Closes data token.
88}
89
90
91/*
92 * Formatting rules:
93 * - All extra (non-indentation) whitespace is trimmed
94 * - Indentation is one tab
95 * - Starting a new list (open paren) starts a new line with one deeper indentation
96 * - Lists with no inner lists go on a single line
97 * - End of multi-line lists (close paren) goes on a single line at same indentation as its start
98 *
99 * For example:
100 * (first
101 * (second
102 * (third list)
103 * (another list)
104 * )
105 * (fifth)
106 * (sixth thing with lots of tokens
107 * (and a sub list)
108 * )
109 * )
110 */
111void Prettify( std::string& aSource, FORMAT_MODE aMode )
112{
113 // Configuration
114 const char quoteChar = '"';
115 const char indentChar = '\t';
116 const int indentSize = 1;
117
118 // In order to visually compress PCB files, it is helpful to special-case long lists of (xy ...)
119 // lists, which we allow to exist on a single line until we reach column 99.
120 const int xySpecialCaseColumnLimit = 99;
121
122 // If whitespace occurs inside a list after this threshold, it will be converted into a newline
123 // and the indentation will be increased. This is mainly used for image and group objects,
124 // which contain potentially long sets of string tokens within a single list.
125 const int consecutiveTokenWrapThreshold = 72;
126
127 const bool textSpecialCase = aMode == FORMAT_MODE::COMPACT_TEXT_PROPERTIES;
128 const bool libSpecialCase = aMode == FORMAT_MODE::LIBRARY_TABLE;
129
130 std::string formatted;
131 formatted.reserve( aSource.length() );
132
133 auto cursor = aSource.begin();
134 auto seek = cursor;
135
136 int listDepth = 0;
137 int libDepth = 0;
138 char lastNonWhitespace = 0;
139 bool inQuote = false;
140 bool hasInsertedSpace = false;
141 bool inMultiLineList = false;
142 bool inXY = false;
143 bool inShortForm = false;
144 bool inLibRow = false;
145 int shortFormDepth = 0;
146 int column = 0;
147 int backslashCount = 0; // Count of successive backslash read since any other char
148
149 auto isWhitespace = []( const char aChar )
150 {
151 return ( aChar == ' ' || aChar == '\t' || aChar == '\n' || aChar == '\r' );
152 };
153
154 auto nextNonWhitespace =
155 [&]( std::string::iterator aIt )
156 {
157 seek = aIt;
158
159 while( seek != aSource.end() && isWhitespace( *seek ) )
160 seek++;
161
162 if( seek == aSource.end() )
163 return (char)0;
164
165 return *seek;
166 };
167
168 auto isXY =
169 [&]( std::string::iterator aIt )
170 {
171 seek = aIt;
172
173 if( ++seek == aSource.end() || *seek != 'x' )
174 return false;
175
176 if( ++seek == aSource.end() || *seek != 'y' )
177 return false;
178
179 if( ++seek == aSource.end() || *seek != ' ' )
180 return false;
181
182 return true;
183 };
184
185 auto isShortForm =
186 [&]( std::string::iterator aIt )
187 {
188 seek = aIt;
189 std::string token;
190
191 while( ++seek != aSource.end() && isalpha( *seek ) )
192 token += *seek;
193
194 return token == "font" || token == "stroke" || token == "fill" || token == "teardrop"
195 || token == "offset" || token == "rotate" || token == "scale";
196 };
197
198 auto isLib =
199 [&]( std::string::iterator aIt )
200 {
201 seek = aIt;
202 std::string token;
203
204 while( ++seek != aSource.end() && isalpha( *seek ) )
205 token += *seek;
206
207 return token == "lib";
208 };
209
210 while( cursor != aSource.end() )
211 {
212 char next = nextNonWhitespace( cursor );
213
214 if( isWhitespace( *cursor ) && !inQuote )
215 {
216 if( !hasInsertedSpace // Only permit one space between chars
217 && listDepth > 0 // Do not permit spaces in outer list
218 && lastNonWhitespace != '(' // Remove extra space after start of list
219 && next != ')' // Remove extra space before end of list
220 && next != '(' ) // Remove extra space before newline
221 {
222 if( inXY || column < consecutiveTokenWrapThreshold )
223 {
224 // Note that we only insert spaces here, no matter what kind of whitespace is
225 // in the input. Newlines will be inserted as needed by the logic below.
226 formatted.push_back( ' ' );
227 column++;
228 }
229 else if( inShortForm || inLibRow )
230 {
231 formatted.push_back( ' ' );
232 }
233 else
234 {
235 formatted += fmt::format( "\n{}",
236 std::string( listDepth * indentSize, indentChar ) );
237 column = listDepth * indentSize;
238 inMultiLineList = true;
239 }
240
241 hasInsertedSpace = true;
242 }
243 }
244 else
245 {
246 hasInsertedSpace = false;
247
248 if( *cursor == '(' && !inQuote )
249 {
250 bool currentIsXY = isXY( cursor );
251 bool currentIsShortForm = textSpecialCase && isShortForm( cursor );
252 bool currentIsLib = libSpecialCase && isLib( cursor );
253
254 if( formatted.empty() )
255 {
256 formatted.push_back( '(' );
257 column++;
258 }
259 else if( inXY && currentIsXY && column < xySpecialCaseColumnLimit )
260 {
261 // List-of-points special case
262 formatted += " (";
263 column += 2;
264 }
265 else if( inShortForm || inLibRow )
266 {
267 formatted += " (";
268 column += 2;
269 }
270 else
271 {
272 formatted += fmt::format( "\n{}(",
273 std::string( listDepth * indentSize, indentChar ) );
274 column = listDepth * indentSize + 1;
275 }
276
277 inXY = currentIsXY;
278
279 if( currentIsShortForm )
280 {
281 inShortForm = true;
282 shortFormDepth = listDepth;
283 }
284 else if( currentIsLib )
285 {
286 inLibRow = true;
287 libDepth = listDepth;
288 }
289
290 listDepth++;
291 }
292 else if( *cursor == ')' && !inQuote )
293 {
294 if( listDepth > 0 )
295 listDepth--;
296
297 if( inShortForm )
298 {
299 formatted.push_back( ')' );
300 column++;
301 }
302 else if( inLibRow && listDepth == libDepth )
303 {
304 formatted.push_back( ')' );
305 inLibRow = false;
306 }
307 else if( lastNonWhitespace == ')' || inMultiLineList )
308 {
309 formatted += fmt::format( "\n{})",
310 std::string( listDepth * indentSize, indentChar ) );
311 column = listDepth * indentSize + 1;
312 inMultiLineList = false;
313 }
314 else
315 {
316 formatted.push_back( ')' );
317 column++;
318 }
319
320 if( shortFormDepth == listDepth )
321 {
322 inShortForm = false;
323 shortFormDepth = 0;
324 }
325 }
326 else
327 {
328 // The output formatter escapes double-quotes (like \")
329 // But a corner case is a sequence like \\"
330 // therefore a '\' is attached to a '"' if a odd number of '\' is detected
331 if( *cursor == '\\' )
332 backslashCount++;
333 else if( *cursor == quoteChar && ( backslashCount & 1 ) == 0 )
334 inQuote = !inQuote;
335
336 if( *cursor != '\\' )
337 backslashCount = 0;
338
339 formatted.push_back( *cursor );
340 column++;
341 }
342
343 lastNonWhitespace = *cursor;
344 }
345
346 ++cursor;
347 }
348
349 // newline required at end of line / file for POSIX compliance. Keeps git diffs clean.
350 formatted += '\n';
351
352 aSource = std::move( formatted );
353}
354
355} // namespace KICAD_FORMAT
356
357
358bool LoadFileToMemory( const wxString& aFileName, wxMemoryBuffer& aBuffer )
359{
360 wxFFile file( aFileName, wxS( "rb" ) );
361
362 if( !file.IsOpened() )
363 return false;
364
365 wxFileOffset size = file.Length();
366
367 if( size <= 0 )
368 return false;
369
370 void* data = aBuffer.GetWriteBuf( size );
371
372 if( file.Read( data, size ) != static_cast<size_t>( size ) )
373 {
374 aBuffer.UngetWriteBuf( 0 );
375 return false;
376 }
377
378 aBuffer.UngetWriteBuf( size );
379 return true;
380}
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
bool HasCustomProperties() const
Definition eda_item.h:269
const std::map< wxString, wxString > & GetCustomProperties() const
Custom user-defined string key/value properties attached to this item.
Definition eda_item.h:242
Definition kiid.h:46
wxString AsString() const
Definition kiid.cpp:264
An interface used to output 8 bit text in a convenient way.
Definition richio.h:294
std::string Quotew(const wxString &aWrapee) const
Definition richio.cpp:505
int PRINTF_FUNC_N Print(int nestLevel, const char *fmt,...)
Format and write text to the output stream.
Definition richio.cpp:432
bool LoadFileToMemory(const wxString &aFileName, wxMemoryBuffer &aBuffer)
Load the contents of a file into a memory buffer.
void FormatCustomProperties(OUTPUTFORMATTER *aOut, const EDA_ITEM &aItem)
Writes the item's custom properties as a series of (custom_property "key" "value")
void FormatOptBool(OUTPUTFORMATTER *aOut, const wxString &aKey, std::optional< bool > aValue)
Writes an optional boolean to the formatter.
void Prettify(std::string &aSource, FORMAT_MODE aMode)
Pretty-prints s-expression text according to KiCad format rules.
void FormatUuid(OUTPUTFORMATTER *aOut, const KIID &aUuid)
void FormatStreamData(OUTPUTFORMATTER &aOut, const wxStreamBuffer &aStream)
Write binary data to the formatter as base 64 encoded string.
void FormatBool(OUTPUTFORMATTER *aOut, const wxString &aKey, bool aValue)
Writes a boolean to the formatter, in the style (aKey [yes|no])
CITER next(CITER it)
Definition ptree.cpp:120
#define MIME_BASE64_LENGTH
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.