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