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