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, bool aCompactSave )
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 std::string formatted;
114 formatted.reserve( aSource.length() );
115
116 auto cursor = aSource.begin();
117 auto seek = cursor;
118
119 int listDepth = 0;
120 char lastNonWhitespace = 0;
121 bool inQuote = false;
122 bool hasInsertedSpace = false;
123 bool inMultiLineList = false;
124 bool inXY = false;
125 bool inShortForm = false;
126 int shortFormDepth = 0;
127 int column = 0;
128 int backslashCount = 0; // Count of successive backslash read since any other char
129
130 auto isWhitespace = []( const char aChar )
131 {
132 return ( aChar == ' ' || aChar == '\t' || aChar == '\n' || aChar == '\r' );
133 };
134
135 auto nextNonWhitespace =
136 [&]( std::string::iterator aIt )
137 {
138 seek = aIt;
139
140 while( seek != aSource.end() && isWhitespace( *seek ) )
141 seek++;
142
143 if( seek == aSource.end() )
144 return (char)0;
145
146 return *seek;
147 };
148
149 auto isXY =
150 [&]( std::string::iterator aIt )
151 {
152 seek = aIt;
153
154 if( ++seek == aSource.end() || *seek != 'x' )
155 return false;
156
157 if( ++seek == aSource.end() || *seek != 'y' )
158 return false;
159
160 if( ++seek == aSource.end() || *seek != ' ' )
161 return false;
162
163 return true;
164 };
165
166 auto isShortForm =
167 [&]( std::string::iterator aIt )
168 {
169 seek = aIt;
170 std::string token;
171
172 while( ++seek != aSource.end() && isalpha( *seek ) )
173 token += *seek;
174
175 return token == "font" || token == "stroke" || token == "fill" || token == "teardrop"
176 || token == "offset" || token == "rotate" || token == "scale";
177 };
178
179 while( cursor != aSource.end() )
180 {
181 char next = nextNonWhitespace( cursor );
182
183 if( isWhitespace( *cursor ) && !inQuote )
184 {
185 if( !hasInsertedSpace // Only permit one space between chars
186 && listDepth > 0 // Do not permit spaces in outer list
187 && lastNonWhitespace != '(' // Remove extra space after start of list
188 && next != ')' // Remove extra space before end of list
189 && next != '(' ) // Remove extra space before newline
190 {
191 if( inXY || column < consecutiveTokenWrapThreshold )
192 {
193 // Note that we only insert spaces here, no matter what kind of whitespace is
194 // in the input. Newlines will be inserted as needed by the logic below.
195 formatted.push_back( ' ' );
196 column++;
197 }
198 else if( inShortForm )
199 {
200 formatted.push_back( ' ' );
201 }
202 else
203 {
204 formatted += fmt::format( "\n{}",
205 std::string( listDepth * indentSize, indentChar ) );
206 column = listDepth * indentSize;
207 inMultiLineList = true;
208 }
209
210 hasInsertedSpace = true;
211 }
212 }
213 else
214 {
215 hasInsertedSpace = false;
216
217 if( *cursor == '(' && !inQuote )
218 {
219 bool currentIsXY = isXY( cursor );
220 bool currentIsShortForm = aCompactSave && isShortForm( cursor );
221
222 if( formatted.empty() )
223 {
224 formatted.push_back( '(' );
225 column++;
226 }
227 else if( inXY && currentIsXY && column < xySpecialCaseColumnLimit )
228 {
229 // List-of-points special case
230 formatted += " (";
231 column += 2;
232 }
233 else if( inShortForm )
234 {
235 formatted += " (";
236 column += 2;
237 }
238 else
239 {
240 formatted += fmt::format( "\n{}(",
241 std::string( listDepth * indentSize, indentChar ) );
242 column = listDepth * indentSize + 1;
243 }
244
245 inXY = currentIsXY;
246
247 if( currentIsShortForm )
248 {
249 inShortForm = true;
250 shortFormDepth = listDepth;
251 }
252
253 listDepth++;
254 }
255 else if( *cursor == ')' && !inQuote )
256 {
257 if( listDepth > 0 )
258 listDepth--;
259
260 if( inShortForm )
261 {
262 formatted.push_back( ')' );
263 column++;
264 }
265 else if( lastNonWhitespace == ')' || inMultiLineList )
266 {
267 formatted += fmt::format( "\n{})",
268 std::string( listDepth * indentSize, indentChar ) );
269 column = listDepth * indentSize + 1;
270 inMultiLineList = false;
271 }
272 else
273 {
274 formatted.push_back( ')' );
275 column++;
276 }
277
278 if( shortFormDepth == listDepth )
279 {
280 inShortForm = false;
281 shortFormDepth = 0;
282 }
283 }
284 else
285 {
286 // The output formatter escapes double-quotes (like \")
287 // But a corner case is a sequence like \\"
288 // therefore a '\' is attached to a '"' if a odd number of '\' is detected
289 if( *cursor == '\\' )
290 backslashCount++;
291 else if( *cursor == quoteChar && ( backslashCount & 1 ) == 0 )
292 inQuote = !inQuote;
293
294 if( *cursor != '\\' )
295 backslashCount = 0;
296
297 formatted.push_back( *cursor );
298 column++;
299 }
300
301 lastNonWhitespace = *cursor;
302 }
303
304 ++cursor;
305 }
306
307 // newline required at end of line / file for POSIX compliance. Keeps git diffs clean.
308 formatted += '\n';
309
310 aSource = std::move( formatted );
311}
312
313} // 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:322
std::string Quotew(const wxString &aWrapee) const
Definition: richio.cpp:542
int PRINTF_FUNC_N Print(int nestLevel, const char *fmt,...)
Format and write text to the output stream.
Definition: richio.cpp:457
void FormatOptBool(OUTPUTFORMATTER *aOut, const wxString &aKey, std::optional< bool > aValue)
Writes an optional boolean to the formatter.
void Prettify(std::string &aSource, bool aCompactSave)
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.
Definition: string_utils.h:408