1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2016 CERN
5  * Copyright (C) 2016-2021 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * @author Wayne Stambaugh <stambaughw@gmail.com>
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include <algorithm>
24 #include <boost/algorithm/string/join.hpp>
25 #include <cctype>
26 #include <mutex>
27 #include <set>
28 
29 #include <wx/mstream.h>
30 #include <wx/filename.h>
31 #include <wx/log.h>
32 #include <wx/textfile.h>
33 #include <wx/tokenzr.h>
34 #include <wx_filename.h>       // For ::ResolvePossibleSymlinks()
35 
36 #include <kiway.h>
37 #include <string_utils.h>
38 #include <locale_io.h>
39 #include <richio.h>
40 #include <trace_helpers.h>
41 #include <trigo.h>
42 #include <progress_reporter.h>
43 #include <general.h>
44 #include <sch_bitmap.h>
45 #include <sch_bus_entry.h>
46 #include <sch_symbol.h>
47 #include <sch_junction.h>
48 #include <sch_line.h>
49 #include <sch_marker.h>
50 #include <sch_no_connect.h>
51 #include <sch_text.h>
52 #include <sch_sheet.h>
53 #include <sch_sheet_pin.h>
54 #include <bus_alias.h>
55 #include <sch_plugins/legacy/sch_legacy_plugin.h>
56 #include <sch_screen.h>
57 #include <schematic.h>
58 #include <symbol_library.h>
59 #include <lib_shape.h>
60 #include <lib_field.h>
61 #include <lib_pin.h>
62 #include <lib_text.h>
63 #include <eeschema_id.h>       // for MAX_UNIT_COUNT_PER_PACKAGE definition
64 #include <tool/selection.h>
65 
66 
67 #define Mils2Iu( x ) Mils2iu( x )
68 
69 
70 // Must be the first line of symbol library document (.dcm) files.
71 #define DOCFILE_IDENT     "EESchema-DOCLIB  Version 2.0"
72 
73 #define SCH_PARSE_ERROR( text, reader, pos )                         \
74     THROW_PARSE_ERROR( text, reader.GetSource(), reader.Line(),      \
75                        reader.LineNumber(), pos - reader.Line() )
76 
77 
78 // Token delimiters.
79 const char* delims = " \t\r\n";
80 
81 // Tokens to read/save graphic lines style
82 #define T_STYLE "style"
83 #define T_COLOR "rgb"          // cannot be modified (used by wxWidgets)
84 #define T_COLORA "rgba"        // cannot be modified (used by wxWidgets)
85 #define T_WIDTH "width"
86 
87 
is_eol(char c)88 static bool is_eol( char c )
89 {
90     //        The default file eol character used internally by KiCad.
91     //        |
92     //        |            Possible eol if someone edited the file by hand on certain platforms.
93     //        |            |
94     //        |            |           May have gone past eol with strtok().
95     //        |            |           |
96     if( c == '\n' || c == '\r' || c == 0 )
97         return true;
98 
99     return false;
100 }
101 
102 
103 /**
104  * Compare \a aString to the string starting at \a aLine and advances the character point to
105  * the end of \a String and returns the new pointer position in \a aOutput if it is not NULL.
106  *
107  * @param aString - A pointer to the string to compare.
108  * @param aLine - A pointer to string to begin the comparison.
109  * @param aOutput - A pointer to a string pointer to the end of the comparison if not NULL.
110  * @return true if \a aString was found starting at \a aLine.  Otherwise false.
111  */
strCompare(const char * aString,const char * aLine,const char ** aOutput=nullptr)112 static bool strCompare( const char* aString, const char* aLine, const char** aOutput = nullptr )
113 {
114     size_t len = strlen( aString );
115     bool retv = ( strncasecmp( aLine, aString, len ) == 0 ) &&
116                 ( isspace( aLine[ len ] ) || aLine[ len ] == 0 );
117 
118     if( retv && aOutput )
119     {
120         const char* tmp = aLine;
121 
122         // Move past the end of the token.
123         tmp += len;
124 
125         // Move to the beginning of the next token.
126         while( *tmp && isspace( *tmp ) )
127             tmp++;
128 
129         *aOutput = tmp;
130     }
131 
132     return retv;
133 }
134 
135 
136 /**
137  * Parse an ASCII integer string with possible leading whitespace into
138  * an integer and updates the pointer at \a aOutput if it is not NULL, just
139  * like "man strtol()".
140  *
141  * @param aReader - The line reader used to generate exception throw information.
142  * @param aLine - A pointer the current position in a string.
143  * @param aOutput - The pointer to a string pointer to copy the string pointer position when
144  *                  the parsing is complete.
145  * @return A valid integer value.
146  * @throw An #IO_ERROR on an unexpected end of line.
147  * @throw A #PARSE_ERROR if the parsed token is not a valid integer.
148  */
parseInt(LINE_READER & aReader,const char * aLine,const char ** aOutput=nullptr)149 static int parseInt( LINE_READER& aReader, const char* aLine, const char** aOutput = nullptr )
150 {
151     if( !*aLine )
152         SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, aLine );
153 
154     // Clear errno before calling strtol() in case some other crt call set it.
155     errno = 0;
156 
157     long retv = strtol( aLine, (char**) aOutput, 10 );
158 
159     // Make sure no error occurred when calling strtol().
160     if( errno == ERANGE )
161         SCH_PARSE_ERROR( "invalid integer value", aReader, aLine );
162 
163     // strtol does not strip off whitespace before the next token.
164     if( aOutput )
165     {
166         const char* next = *aOutput;
167 
168         while( *next && isspace( *next ) )
169             next++;
170 
171         *aOutput = next;
172     }
173 
174     return (int) retv;
175 }
176 
177 
178 /**
179  * Parse an ASCII hex integer string with possible leading whitespace into
180  * a long integer and updates the pointer at \a aOutput if it is not NULL, just
181  * like "man strtoll".
182  *
183  * @param aReader - The line reader used to generate exception throw information.
184  * @param aLine - A pointer the current position in a string.
185  * @param aOutput - The pointer to a string pointer to copy the string pointer position when
186  *                  the parsing is complete.
187  * @return A valid uint32_t value.
188  * @throw IO_ERROR on an unexpected end of line.
189  * @throw PARSE_ERROR if the parsed token is not a valid integer.
190  */
parseHex(LINE_READER & aReader,const char * aLine,const char ** aOutput=nullptr)191 static uint32_t parseHex( LINE_READER& aReader, const char* aLine, const char** aOutput = nullptr )
192 {
193     if( !*aLine )
194         SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, aLine );
195 
196     // Due to some issues between some files created by a 64 bits version and those
197     // created by a 32 bits version, we use here a temporary at least 64 bits storage:
198     unsigned long long retv;
199 
200     // Clear errno before calling strtoull() in case some other crt call set it.
201     errno = 0;
202     retv = strtoull( aLine, (char**) aOutput, 16 );
203 
204     // Make sure no error occurred when calling strtoull().
205     if( errno == ERANGE )
206         SCH_PARSE_ERROR( "invalid hexadecimal number", aReader, aLine );
207 
208     // Strip off whitespace before the next token.
209     if( aOutput )
210     {
211         const char* next = *aOutput;
212 
213         while( *next && isspace( *next ) )
214             next++;
215 
216         *aOutput = next;
217     }
218 
219     return (uint32_t)retv;
220 }
221 
222 
223 /**
224  * Parses an ASCII point string with possible leading whitespace into a double precision
225  * floating point number and  updates the pointer at \a aOutput if it is not NULL, just
226  * like "man strtod".
227  *
228  * @param aReader - The line reader used to generate exception throw information.
229  * @param aLine - A pointer the current position in a string.
230  * @param aOutput - The pointer to a string pointer to copy the string pointer position when
231  *                  the parsing is complete.
232  * @return A valid double value.
233  * @throw IO_ERROR on an unexpected end of line.
234  * @throw PARSE_ERROR if the parsed token is not a valid integer.
235  */
parseDouble(LINE_READER & aReader,const char * aLine,const char ** aOutput=nullptr)236 static double parseDouble( LINE_READER& aReader, const char* aLine,
237                            const char** aOutput = nullptr )
238 {
239     if( !*aLine )
240         SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, aLine );
241 
242     // Clear errno before calling strtod() in case some other crt call set it.
243     errno = 0;
244 
245     double retv = strtod( aLine, (char**) aOutput );
246 
247     // Make sure no error occurred when calling strtod().
248     if( errno == ERANGE )
249         SCH_PARSE_ERROR( "invalid floating point number", aReader, aLine );
250 
251     // strtod does not strip off whitespace before the next token.
252     if( aOutput )
253     {
254         const char* next = *aOutput;
255 
256         while( *next && isspace( *next ) )
257             next++;
258 
259         *aOutput = next;
260     }
261 
262     return retv;
263 }
264 
265 
266 /**
267  * Parse a single ASCII character and updates the pointer at \a aOutput if it is not NULL.
268  *
269  * @param aReader - The line reader used to generate exception throw information.
270  * @param aCurrentToken - A pointer the current position in a string.
271  * @param aNextToken - The pointer to a string pointer to copy the string pointer position when
272  *                     the parsing is complete.
273  * @return A valid ASCII character.
274  * @throw IO_ERROR on an unexpected end of line.
275  * @throw PARSE_ERROR if the parsed token is not a single character token.
276  */
parseChar(LINE_READER & aReader,const char * aCurrentToken,const char ** aNextToken=nullptr)277 static char parseChar( LINE_READER& aReader, const char* aCurrentToken,
278                        const char** aNextToken = nullptr )
279 {
280     while( *aCurrentToken && isspace( *aCurrentToken ) )
281         aCurrentToken++;
282 
283     if( !*aCurrentToken )
284         SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, aCurrentToken );
285 
286     if( !isspace( *( aCurrentToken + 1 ) ) )
287         SCH_PARSE_ERROR( "expected single character token", aReader, aCurrentToken );
288 
289     if( aNextToken )
290     {
291         const char* next = aCurrentToken + 2;
292 
293         while( *next && isspace( *next ) )
294             next++;
295 
296         *aNextToken = next;
297     }
298 
299     return *aCurrentToken;
300 }
301 
302 
303 /**
304  * Parse an unquoted utf8 string and updates the pointer at \a aOutput if it is not NULL.
305  *
306  * The parsed string must be a continuous string with no white space.
307  *
308  * @param aString - A reference to the parsed string.
309  * @param aReader - The line reader used to generate exception throw information.
310  * @param aCurrentToken - A pointer the current position in a string.
311  * @param aNextToken - The pointer to a string pointer to copy the string pointer position when
312  *                     the parsing is complete.
313  * @param aCanBeEmpty - True if the parsed string is optional.  False if it is mandatory.
314  * @throw IO_ERROR on an unexpected end of line.
315  * @throw PARSE_ERROR if the \a aCanBeEmpty is false and no string was parsed.
316  */
parseUnquotedString(wxString & aString,LINE_READER & aReader,const char * aCurrentToken,const char ** aNextToken=nullptr,bool aCanBeEmpty=false)317 static void parseUnquotedString( wxString& aString, LINE_READER& aReader,
318                                  const char* aCurrentToken, const char** aNextToken = nullptr,
319                                  bool aCanBeEmpty = false )
320 {
321     if( !*aCurrentToken )
322     {
323         if( aCanBeEmpty )
324             return;
325         else
326             SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, aCurrentToken );
327     }
328 
329     const char* tmp = aCurrentToken;
330 
331     while( *tmp && isspace( *tmp ) )
332         tmp++;
333 
334     if( !*tmp )
335     {
336         if( aCanBeEmpty )
337             return;
338         else
339             SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, aCurrentToken );
340     }
341 
342     std::string utf8;
343 
344     while( *tmp && !isspace( *tmp ) )
345         utf8 += *tmp++;
346 
347     aString = FROM_UTF8( utf8.c_str() );
348 
349     if( aString.IsEmpty() && !aCanBeEmpty )
350         SCH_PARSE_ERROR( _( "expected unquoted string" ), aReader, aCurrentToken );
351 
352     if( aNextToken )
353     {
354         const char* next = tmp;
355 
356         while( *next && isspace( *next ) )
357             next++;
358 
359         *aNextToken = next;
360     }
361 }
362 
363 
364 /**
365  * Parse an quoted ASCII utf8 and updates the pointer at \a aOutput if it is not NULL.
366  *
367  * The parsed string must be contained within a single line.  There are no multi-line
368  * quoted strings in the legacy schematic file format.
369  *
370  * @param aString - A reference to the parsed string.
371  * @param aReader - The line reader used to generate exception throw information.
372  * @param aCurrentToken - A pointer the current position in a string.
373  * @param aNextToken - The pointer to a string pointer to copy the string pointer position when
374  *                     the parsing is complete.
375  * @param aCanBeEmpty - True if the parsed string is optional.  False if it is mandatory.
376  * @throw IO_ERROR on an unexpected end of line.
377  * @throw PARSE_ERROR if the \a aCanBeEmpty is false and no string was parsed.
378  */
parseQuotedString(wxString & aString,LINE_READER & aReader,const char * aCurrentToken,const char ** aNextToken=nullptr,bool aCanBeEmpty=false)379 static void parseQuotedString( wxString& aString, LINE_READER& aReader,
380                                const char* aCurrentToken, const char** aNextToken = nullptr,
381                                bool aCanBeEmpty = false )
382 {
383     if( !*aCurrentToken )
384     {
385         if( aCanBeEmpty )
386             return;
387         else
388             SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, aCurrentToken );
389     }
390 
391     const char* tmp = aCurrentToken;
392 
393     while( *tmp && isspace( *tmp ) )
394         tmp++;
395 
396     if( !*tmp )
397     {
398         if( aCanBeEmpty )
399             return;
400         else
401             SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, aCurrentToken );
402     }
403 
404     // Verify opening quote.
405     if( *tmp != '"' )
406         SCH_PARSE_ERROR( "expecting opening quote", aReader, aCurrentToken );
407 
408     tmp++;
409 
410     std::string utf8;     // utf8 without escapes and quotes.
411 
412     // Fetch everything up to closing quote.
413     while( *tmp )
414     {
415         if( *tmp == '\\' )
416         {
417             tmp++;
418 
419             if( !*tmp )
420                 SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, aCurrentToken );
421 
422             // Do not copy the escape byte if it is followed by \ or "
423             if( *tmp != '"' && *tmp != '\\' )
424                 utf8 += '\\';
425 
426             utf8 += *tmp;
427         }
428         else if( *tmp == '"' )  // Closing double quote.
429         {
430             break;
431         }
432         else
433         {
434             utf8 += *tmp;
435         }
436 
437         tmp++;
438     }
439 
440     aString = FROM_UTF8( utf8.c_str() );
441 
442     if( aString.IsEmpty() && !aCanBeEmpty )
443         SCH_PARSE_ERROR( "expected quoted string", aReader, aCurrentToken );
444 
445     if( *tmp && *tmp != '"' )
446         SCH_PARSE_ERROR( "no closing quote for string found", aReader, tmp );
447 
448     // Move past the closing quote.
449     tmp++;
450 
451     if( aNextToken )
452     {
453         const char* next = tmp;
454 
455         while( *next == ' ' )
456             next++;
457 
458         *aNextToken = next;
459     }
460 }
461 
462 
463 /**
464  * A cache assistant for the symbol library portion of the #SCH_PLUGIN API, and only for the
465  * #SCH_LEGACY_PLUGIN, so therefore is private to this implementation file, i.e. not placed
466  * into a header.
467  */
468 class SCH_LEGACY_PLUGIN_CACHE
469 {
470     static int      s_modHash;      // Keep track of the modification status of the library.
471     wxString        m_fileName;     // Absolute path and file name.
472     wxFileName      m_libFileName;  // Absolute path and file name is required here.
473     wxDateTime      m_fileModTime;
474     LIB_SYMBOL_MAP  m_symbols;      // Map of names of #LIB_SYMBOL pointers.
475     bool            m_isWritable;
476     bool            m_isModified;
477     int             m_versionMajor;
478     int             m_versionMinor;
479     SCH_LIB_TYPE    m_libType;      // Is this cache a symbol or symbol library.
480 
481     void              loadHeader( FILE_LINE_READER& aReader );
482     static void       loadAliases( std::unique_ptr<LIB_SYMBOL>& aSymbol, LINE_READER& aReader,
483                                    LIB_SYMBOL_MAP* aMap = nullptr );
484     static void       loadField( std::unique_ptr<LIB_SYMBOL>& aSymbol, LINE_READER& aReader );
485     static void       loadDrawEntries( std::unique_ptr<LIB_SYMBOL>& aSymbol, LINE_READER& aReader,
486                                        int aMajorVersion, int aMinorVersion );
487     static void       loadFootprintFilters( std::unique_ptr<LIB_SYMBOL>& aSymbol,
488                                             LINE_READER& aReader );
489     void              loadDocs();
490     static LIB_SHAPE* loadArc( std::unique_ptr<LIB_SYMBOL>& aSymbol, LINE_READER& aReader );
491     static LIB_SHAPE* loadCircle( std::unique_ptr<LIB_SYMBOL>& aSymbol, LINE_READER& aReader );
492     static LIB_TEXT*  loadText( std::unique_ptr<LIB_SYMBOL>& aSymbol, LINE_READER& aReader,
493                                 int aMajorVersion, int aMinorVersion );
494     static LIB_SHAPE* loadRect( std::unique_ptr<LIB_SYMBOL>& aSymbol, LINE_READER& aReader );
495     static LIB_PIN*   loadPin( std::unique_ptr<LIB_SYMBOL>& aSymbol, LINE_READER& aReader );
496     static LIB_SHAPE* loadPolyLine( std::unique_ptr<LIB_SYMBOL>& aSymbol, LINE_READER& aReader );
497     static LIB_SHAPE* loadBezier( std::unique_ptr<LIB_SYMBOL>& aSymbol, LINE_READER& aReader );
498 
499     static FILL_T   parseFillMode( LINE_READER& aReader, const char* aLine, const char** aOutput );
500     LIB_SYMBOL*     removeSymbol( LIB_SYMBOL* aAlias );
501 
502     void            saveDocFile();
503     static void     saveArc( LIB_SHAPE* aArc, OUTPUTFORMATTER& aFormatter );
504     static void     saveBezier( LIB_SHAPE* aBezier, OUTPUTFORMATTER& aFormatter );
505     static void     saveCircle( LIB_SHAPE* aCircle, OUTPUTFORMATTER& aFormatter );
506     static void     saveField( const LIB_FIELD* aField, OUTPUTFORMATTER& aFormatter );
507     static void     savePin( const LIB_PIN* aPin, OUTPUTFORMATTER& aFormatter );
508     static void     savePolyLine( LIB_SHAPE* aPolyLine, OUTPUTFORMATTER& aFormatter );
509     static void     saveRectangle( LIB_SHAPE* aRectangle, OUTPUTFORMATTER& aFormatter );
510     static void     saveText( const LIB_TEXT* aText, OUTPUTFORMATTER& aFormatter );
511 
512     friend SCH_LEGACY_PLUGIN;
513 
514     static std::mutex s_modHashMutex;
515 
516 public:
517     SCH_LEGACY_PLUGIN_CACHE( const wxString& aLibraryPath );
518     ~SCH_LEGACY_PLUGIN_CACHE();
519 
IncrementModifyHash()520     static void IncrementModifyHash()
521     {
522         std::lock_guard<std::mutex> mut( SCH_LEGACY_PLUGIN_CACHE::s_modHashMutex );
523         SCH_LEGACY_PLUGIN_CACHE::s_modHash++;
524     }
525 
GetModifyHash()526     static int GetModifyHash()
527     {
528         std::lock_guard<std::mutex> mut( SCH_LEGACY_PLUGIN_CACHE::s_modHashMutex );
529         return SCH_LEGACY_PLUGIN_CACHE::s_modHash;
530     }
531 
532     // Most all functions in this class throw IO_ERROR exceptions.  There are no
533     // error codes nor user interface calls from here, nor in any SCH_PLUGIN objects.
534     // Catch these exceptions higher up please.
535 
536     /// Save the entire library to file m_libFileName;
537     void Save( bool aSaveDocFile = true );
538 
539     void Load();
540 
541     void AddSymbol( const LIB_SYMBOL* aSymbol );
542 
543     void DeleteSymbol( const wxString& aName );
544 
545     // If m_libFileName is a symlink follow it to the real source file
546     wxFileName GetRealFile() const;
547 
548     wxDateTime GetLibModificationTime();
549 
550     bool IsFile( const wxString& aFullPathAndFileName ) const;
551 
552     bool IsFileChanged() const;
553 
SetModified(bool aModified=true)554     void SetModified( bool aModified = true ) { m_isModified = aModified; }
555 
GetLogicalName() const556     wxString GetLogicalName() const { return m_libFileName.GetName(); }
557 
SetFileName(const wxString & aFileName)558     void SetFileName( const wxString& aFileName ) { m_libFileName = aFileName; }
559 
GetFileName() const560     wxString GetFileName() const { return m_libFileName.GetFullPath(); }
561 
562     static LIB_SYMBOL* LoadPart( LINE_READER& aReader, int aMajorVersion, int aMinorVersion,
563                                  LIB_SYMBOL_MAP* aMap = nullptr );
564     static void      SaveSymbol( LIB_SYMBOL* aSymbol, OUTPUTFORMATTER& aFormatter,
565                                  LIB_SYMBOL_MAP* aMap = nullptr );
566 };
567 
568 
SCH_LEGACY_PLUGIN()569 SCH_LEGACY_PLUGIN::SCH_LEGACY_PLUGIN() :
570     m_progressReporter( nullptr ),
571     m_lineReader( nullptr ),
572     m_lastProgressLine( 0 ),
573     m_lineCount( 0 )
574 {
575     init( nullptr );
576 }
577 
578 
~SCH_LEGACY_PLUGIN()579 SCH_LEGACY_PLUGIN::~SCH_LEGACY_PLUGIN()
580 {
581     delete m_cache;
582 }
583 
584 
init(SCHEMATIC * aSchematic,const PROPERTIES * aProperties)585 void SCH_LEGACY_PLUGIN::init( SCHEMATIC* aSchematic, const PROPERTIES* aProperties )
586 {
587     m_version   = 0;
588     m_rootSheet = nullptr;
589     m_schematic = aSchematic;
590     m_cache     = nullptr;
591     m_out       = nullptr;
592 }
593 
594 
checkpoint()595 void SCH_LEGACY_PLUGIN::checkpoint()
596 {
597     const unsigned PROGRESS_DELTA = 250;
598 
599     if( m_progressReporter )
600     {
601         unsigned curLine = m_lineReader->LineNumber();
602 
603         if( curLine > m_lastProgressLine + PROGRESS_DELTA )
604         {
605             m_progressReporter->SetCurrentProgress( ( (double) curLine )
606                                                             / std::max( 1U, m_lineCount ) );
607 
608             if( !m_progressReporter->KeepRefreshing() )
609                 THROW_IO_ERROR( ( "Open cancelled by user." ) );
610 
611             m_lastProgressLine = curLine;
612         }
613     }
614 }
615 
616 
Load(const wxString & aFileName,SCHEMATIC * aSchematic,SCH_SHEET * aAppendToMe,const PROPERTIES * aProperties)617 SCH_SHEET* SCH_LEGACY_PLUGIN::Load( const wxString& aFileName, SCHEMATIC* aSchematic,
618                                     SCH_SHEET* aAppendToMe, const PROPERTIES* aProperties )
619 {
620     wxASSERT( !aFileName || aSchematic != nullptr );
621 
622     LOCALE_IO   toggle;     // toggles on, then off, the C locale.
623     SCH_SHEET*  sheet;
624 
625     wxFileName fn = aFileName;
626 
627     // Unfortunately child sheet file names the legacy schematic file format are not fully
628     // qualified and are always appended to the project path.  The aFileName attribute must
629     // always be an absolute path so the project path can be used for load child sheet files.
630     wxASSERT( fn.IsAbsolute() );
631 
632     if( aAppendToMe )
633     {
634         wxLogTrace( traceSchLegacyPlugin, "Append \"%s\" to sheet \"%s\".",
635                     aFileName, aAppendToMe->GetFileName() );
636 
637         wxFileName normedFn = aAppendToMe->GetFileName();
638 
639         if( !normedFn.IsAbsolute() )
640         {
641             if( aFileName.Right( normedFn.GetFullPath().Length() ) == normedFn.GetFullPath() )
642                 m_path = aFileName.Left( aFileName.Length() - normedFn.GetFullPath().Length() );
643         }
644 
645         if( m_path.IsEmpty() )
646             m_path = aSchematic->Prj().GetProjectPath();
647 
648         wxLogTrace( traceSchLegacyPlugin, "m_Normalized append path \"%s\".", m_path );
649     }
650     else
651     {
652         m_path = aSchematic->Prj().GetProjectPath();
653     }
654 
655     m_currentPath.push( m_path );
656     init( aSchematic, aProperties );
657 
658     if( aAppendToMe == nullptr )
659     {
660         // Clean up any allocated memory if an exception occurs loading the schematic.
661         std::unique_ptr<SCH_SHEET> newSheet = std::make_unique<SCH_SHEET>( aSchematic );
662         newSheet->SetFileName( aFileName );
663         m_rootSheet = newSheet.get();
664         loadHierarchy( newSheet.get() );
665 
666         // If we got here, the schematic loaded successfully.
667         sheet = newSheet.release();
668         m_rootSheet = nullptr;         // Quiet Coverity warning.
669     }
670     else
671     {
672         wxCHECK_MSG( aSchematic->IsValid(), nullptr, "Can't append to a schematic with no root!" );
673         m_rootSheet = &aSchematic->Root();
674         sheet = aAppendToMe;
675         loadHierarchy( sheet );
676     }
677 
678     wxASSERT( m_currentPath.size() == 1 );  // only the project path should remain
679 
680     return sheet;
681 }
682 
683 
684 // Everything below this comment is recursive.  Modify with care.
685 
loadHierarchy(SCH_SHEET * aSheet)686 void SCH_LEGACY_PLUGIN::loadHierarchy( SCH_SHEET* aSheet )
687 {
688     SCH_SCREEN* screen = nullptr;
689 
690     if( !aSheet->GetScreen() )
691     {
692         // SCH_SCREEN objects store the full path and file name where the SCH_SHEET object only
693         // stores the file name and extension.  Add the project path to the file name and
694         // extension to compare when calling SCH_SHEET::SearchHierarchy().
695         wxFileName fileName = aSheet->GetFileName();
696         fileName.SetExt( "sch" );
697 
698         if( !fileName.IsAbsolute() )
699             fileName.MakeAbsolute( m_currentPath.top() );
700 
701         // Save the current path so that it gets restored when descending and ascending the
702         // sheet hierarchy which allows for sheet schematic files to be nested in folders
703         // relative to the last path a schematic was loaded from.
704         wxLogTrace( traceSchLegacyPlugin, "Saving path    '%s'", m_currentPath.top() );
705         m_currentPath.push( fileName.GetPath() );
706         wxLogTrace( traceSchLegacyPlugin, "Current path   '%s'", m_currentPath.top() );
707         wxLogTrace( traceSchLegacyPlugin, "Loading        '%s'", fileName.GetFullPath() );
708 
709         m_rootSheet->SearchHierarchy( fileName.GetFullPath(), &screen );
710 
711         if( screen )
712         {
713             aSheet->SetScreen( screen );
714             screen->SetParent( m_schematic );
715             // Do not need to load the sub-sheets - this has already been done.
716         }
717         else
718         {
719             aSheet->SetScreen( new SCH_SCREEN( m_schematic ) );
720             aSheet->GetScreen()->SetFileName( fileName.GetFullPath() );
721 
722             try
723             {
724                 loadFile( fileName.GetFullPath(), aSheet->GetScreen() );
725             }
726             catch( const IO_ERROR& ioe )
727             {
728                 // If there is a problem loading the root sheet, there is no recovery.
729                 if( aSheet == m_rootSheet )
730                     throw( ioe );
731 
732                 // For all subsheets, queue up the error message for the caller.
733                 if( !m_error.IsEmpty() )
734                     m_error += "\n";
735 
736                 m_error += ioe.What();
737             }
738 
739             aSheet->GetScreen()->SetFileReadOnly( !fileName.IsFileWritable() );
740             aSheet->GetScreen()->SetFileExists( true );
741 
742             for( auto aItem : aSheet->GetScreen()->Items().OfType( SCH_SHEET_T ) )
743             {
744                 wxCHECK2( aItem->Type() == SCH_SHEET_T, continue );
745                 auto sheet = static_cast<SCH_SHEET*>( aItem );
746 
747                 // Set the parent to aSheet.  This effectively creates a method to find
748                 // the root sheet from any sheet so a pointer to the root sheet does not
749                 // need to be stored globally.  Note: this is not the same as a hierarchy.
750                 // Complex hierarchies can have multiple copies of a sheet.  This only
751                 // provides a simple tree to find the root sheet.
752                 sheet->SetParent( aSheet );
753 
754                 // Recursion starts here.
755                 loadHierarchy( sheet );
756             }
757         }
758 
759         m_currentPath.pop();
760         wxLogTrace( traceSchLegacyPlugin, "Restoring path \"%s\"", m_currentPath.top() );
761     }
762 }
763 
764 
loadFile(const wxString & aFileName,SCH_SCREEN * aScreen)765 void SCH_LEGACY_PLUGIN::loadFile( const wxString& aFileName, SCH_SCREEN* aScreen )
766 {
767     FILE_LINE_READER reader( aFileName );
768 
769     if( m_progressReporter )
770     {
771         m_progressReporter->Report( wxString::Format( _( "Loading %s..." ), aFileName ) );
772 
773         if( !m_progressReporter->KeepRefreshing() )
774             THROW_IO_ERROR( ( "Open cancelled by user." ) );
775 
776         m_lineReader = &reader;
777         m_lineCount = 0;
778 
779         while( reader.ReadLine() )
780             m_lineCount++;
781 
782         reader.Rewind();
783     }
784 
785     loadHeader( reader, aScreen );
786 
787     LoadContent( reader, aScreen, m_version );
788 
789     // Unfortunately schematic files prior to version 2 are not terminated with $EndSCHEMATC
790     // so checking for its existance will fail so just exit here and take our chances. :(
791     if( m_version > 1 )
792     {
793         char* line = reader.Line();
794 
795         while( *line == ' ' )
796             line++;
797 
798         if( !strCompare( "$EndSCHEMATC", line ) )
799             THROW_IO_ERROR( "'$EndSCHEMATC' not found" );
800     }
801 }
802 
803 
LoadContent(LINE_READER & aReader,SCH_SCREEN * aScreen,int version)804 void SCH_LEGACY_PLUGIN::LoadContent( LINE_READER& aReader, SCH_SCREEN* aScreen, int version )
805 {
806     m_version = version;
807 
808     // We cannot safely load content without a set root level.
809     wxCHECK_RET( m_rootSheet,
810             "Cannot call SCH_LEGACY_PLUGIN::LoadContent() without setting root sheet." );
811 
812     while( aReader.ReadLine() )
813     {
814         checkpoint();
815 
816         char* line = aReader.Line();
817 
818         while( *line == ' ' )
819             line++;
820 
821         // Either an object will be loaded properly or the file load will fail and raise
822         // an exception.
823         if( strCompare( "$Descr", line ) )
824             loadPageSettings( aReader, aScreen );
825         else if( strCompare( "$Comp", line ) )
826             aScreen->Append( loadSymbol( aReader ) );
827         else if( strCompare( "$Sheet", line ) )
828             aScreen->Append( loadSheet( aReader ) );
829         else if( strCompare( "$Bitmap", line ) )
830             aScreen->Append( loadBitmap( aReader ) );
831         else if( strCompare( "Connection", line ) )
832             aScreen->Append( loadJunction( aReader ) );
833         else if( strCompare( "NoConn", line ) )
834             aScreen->Append( loadNoConnect( aReader ) );
835         else if( strCompare( "Wire", line ) )
836             aScreen->Append( loadWire( aReader ) );
837         else if( strCompare( "Entry", line ) )
838             aScreen->Append( loadBusEntry( aReader ) );
839         else if( strCompare( "Text", line ) )
840             aScreen->Append( loadText( aReader ) );
841         else if( strCompare( "BusAlias", line ) )
842             aScreen->AddBusAlias( loadBusAlias( aReader, aScreen ) );
843         else if( strCompare( "$EndSCHEMATC", line ) )
844             return;
845         else
846             SCH_PARSE_ERROR( "unrecognized token", aReader, line );
847     }
848 }
849 
850 
loadHeader(LINE_READER & aReader,SCH_SCREEN * aScreen)851 void SCH_LEGACY_PLUGIN::loadHeader( LINE_READER& aReader, SCH_SCREEN* aScreen )
852 {
853     const char* line = aReader.ReadLine();
854 
855     if( !line || !strCompare( "Eeschema Schematic File Version", line, &line ) )
856     {
857         m_error.Printf( _( "'%s' does not appear to be an Eeschema file." ),
858                         aScreen->GetFileName() );
859         THROW_IO_ERROR( m_error );
860     }
861 
862     // get the file version here.
863     m_version = parseInt( aReader, line, &line );
864 
865     // The next lines are the lib list section, and are mainly comments, like:
866     // LIBS:power
867     // the lib list is not used, but is in schematic file just in case.
868     // It is usually not empty, but we accept empty list.
869     // If empty, there is a legacy section, not used
870     // EELAYER i j
871     // and the last line is
872     // EELAYER END
873     // Skip all lines until the end of header "EELAYER END" is found
874     while( aReader.ReadLine() )
875     {
876         checkpoint();
877 
878         line = aReader.Line();
879 
880         while( *line == ' ' )
881             line++;
882 
883         if( strCompare( "EELAYER END", line ) )
884             return;
885     }
886 
887     THROW_IO_ERROR( _( "Missing 'EELAYER END'" ) );
888 }
889 
890 
loadPageSettings(LINE_READER & aReader,SCH_SCREEN * aScreen)891 void SCH_LEGACY_PLUGIN::loadPageSettings( LINE_READER& aReader, SCH_SCREEN* aScreen )
892 {
893     wxASSERT( aScreen != nullptr );
894 
895     wxString    buf;
896     const char* line = aReader.Line();
897 
898     PAGE_INFO   pageInfo;
899     TITLE_BLOCK tb;
900 
901     wxCHECK_RET( strCompare( "$Descr", line, &line ), "Invalid sheet description" );
902 
903     parseUnquotedString( buf, aReader, line, &line );
904 
905     if( !pageInfo.SetType( buf ) )
906         SCH_PARSE_ERROR( "invalid page size", aReader, line );
907 
908     int pagew = parseInt( aReader, line, &line );
909     int pageh = parseInt( aReader, line, &line );
910 
911     if( buf == PAGE_INFO::Custom )
912     {
913         pageInfo.SetWidthMils( pagew );
914         pageInfo.SetHeightMils( pageh );
915     }
916     else
917     {
918         wxString orientation;
919 
920         // Non custom size, set portrait if its present.  Can be empty string which defaults
921         // to landscape.
922         parseUnquotedString( orientation, aReader, line, &line, true );
923 
924         if( orientation == "portrait" )
925             pageInfo.SetPortrait( true );
926     }
927 
928     aScreen->SetPageSettings( pageInfo );
929 
930     while( line != nullptr )
931     {
932         buf.clear();
933 
934         if( !aReader.ReadLine() )
935             SCH_PARSE_ERROR( _( "unexpected end of file" ), aReader, line );
936 
937         line = aReader.Line();
938 
939         if( strCompare( "Sheet", line, &line ) )
940         {
941             aScreen->SetVirtualPageNumber( parseInt( aReader, line, &line ) );
942             aScreen->SetPageCount( parseInt( aReader, line, &line ) );
943         }
944         else if( strCompare( "Title", line, &line ) )
945         {
946             parseQuotedString( buf, aReader, line, &line, true );
947             tb.SetTitle( buf );
948         }
949         else if( strCompare( "Date", line, &line ) )
950         {
951             parseQuotedString( buf, aReader, line, &line, true );
952             tb.SetDate( buf );
953         }
954         else if( strCompare( "Rev", line, &line ) )
955         {
956             parseQuotedString( buf, aReader, line, &line, true );
957             tb.SetRevision( buf );
958         }
959         else if( strCompare( "Comp", line, &line ) )
960         {
961             parseQuotedString( buf, aReader, line, &line, true );
962             tb.SetCompany( buf );
963         }
964         else if( strCompare( "Comment1", line, &line ) )
965         {
966             parseQuotedString( buf, aReader, line, &line, true );
967             tb.SetComment( 0, buf );
968         }
969         else if( strCompare( "Comment2", line, &line ) )
970         {
971             parseQuotedString( buf, aReader, line, &line, true );
972             tb.SetComment( 1, buf );
973         }
974         else if( strCompare( "Comment3", line, &line ) )
975         {
976             parseQuotedString( buf, aReader, line, &line, true );
977             tb.SetComment( 2, buf );
978         }
979         else if( strCompare( "Comment4", line, &line ) )
980         {
981             parseQuotedString( buf, aReader, line, &line, true );
982             tb.SetComment( 3, buf );
983         }
984         else if( strCompare( "Comment5", line, &line ) )
985         {
986             parseQuotedString( buf, aReader, line, &line, true );
987             tb.SetComment( 4, buf );
988         }
989         else if( strCompare( "Comment6", line, &line ) )
990         {
991             parseQuotedString( buf, aReader, line, &line, true );
992             tb.SetComment( 5, buf );
993         }
994         else if( strCompare( "Comment7", line, &line ) )
995         {
996             parseQuotedString( buf, aReader, line, &line, true );
997             tb.SetComment( 6, buf );
998         }
999         else if( strCompare( "Comment8", line, &line ) )
1000         {
1001             parseQuotedString( buf, aReader, line, &line, true );
1002             tb.SetComment( 7, buf );
1003         }
1004         else if( strCompare( "Comment9", line, &line ) )
1005         {
1006             parseQuotedString( buf, aReader, line, &line, true );
1007             tb.SetComment( 8, buf );
1008         }
1009         else if( strCompare( "$EndDescr", line ) )
1010         {
1011             aScreen->SetTitleBlock( tb );
1012             return;
1013         }
1014     }
1015 
1016     SCH_PARSE_ERROR( "missing 'EndDescr'", aReader, line );
1017 }
1018 
1019 
loadSheet(LINE_READER & aReader)1020 SCH_SHEET* SCH_LEGACY_PLUGIN::loadSheet( LINE_READER& aReader )
1021 {
1022     std::unique_ptr<SCH_SHEET> sheet = std::make_unique<SCH_SHEET>();
1023 
1024     const char* line = aReader.ReadLine();
1025 
1026     while( line != nullptr )
1027     {
1028         if( strCompare( "S", line, &line ) )        // Sheet dimensions.
1029         {
1030             wxPoint position;
1031 
1032             position.x = Mils2Iu( parseInt( aReader, line, &line ) );
1033             position.y = Mils2Iu( parseInt( aReader, line, &line ) );
1034             sheet->SetPosition( position );
1035 
1036             wxSize  size;
1037 
1038             size.SetWidth( Mils2Iu( parseInt( aReader, line, &line ) ) );
1039             size.SetHeight( Mils2Iu( parseInt( aReader, line, &line ) ) );
1040             sheet->SetSize( size );
1041         }
1042         else if( strCompare( "U", line, &line ) )   // Sheet UUID.
1043         {
1044             wxString text;
1045             parseUnquotedString( text, aReader, line );
1046 
1047             if( text != "00000000" )
1048                 const_cast<KIID&>( sheet->m_Uuid ) = KIID( text );
1049         }
1050         else if( *line == 'F' )                     // Sheet field.
1051         {
1052             line++;
1053 
1054             wxString text;
1055             int size;
1056             int fieldId = parseInt( aReader, line, &line );
1057 
1058             if( fieldId == 0 || fieldId == 1 )      // Sheet name and file name.
1059             {
1060                 parseQuotedString( text, aReader, line, &line );
1061                 size = Mils2Iu( parseInt( aReader, line, &line ) );
1062 
1063                 SCH_FIELD& field = sheet->GetFields()[ fieldId ];
1064                 field.SetText( text );
1065                 field.SetTextSize( wxSize( size, size ) );
1066             }
1067             else                                   // Sheet pin.
1068             {
1069                 // Use a unique_ptr so that we clean up in the case of a throw
1070                 std::unique_ptr<SCH_SHEET_PIN> sheetPin = std::make_unique<SCH_SHEET_PIN>( sheet.get() );
1071 
1072                 sheetPin->SetNumber( fieldId );
1073 
1074                 // Can be empty fields.
1075                 parseQuotedString( text, aReader, line, &line, true );
1076 
1077                 sheetPin->SetText( ConvertToNewOverbarNotation( text ) );
1078 
1079                 if( line == nullptr )
1080                     THROW_IO_ERROR( _( "unexpected end of line" ) );
1081 
1082                 switch( parseChar( aReader, line, &line ) )
1083                 {
1084                 case 'I': sheetPin->SetShape( PINSHEETLABEL_SHAPE::PS_INPUT );       break;
1085                 case 'O': sheetPin->SetShape( PINSHEETLABEL_SHAPE::PS_OUTPUT );      break;
1086                 case 'B': sheetPin->SetShape( PINSHEETLABEL_SHAPE::PS_BIDI );        break;
1087                 case 'T': sheetPin->SetShape( PINSHEETLABEL_SHAPE::PS_TRISTATE );    break;
1088                 case 'U': sheetPin->SetShape( PINSHEETLABEL_SHAPE::PS_UNSPECIFIED ); break;
1089                 default:  SCH_PARSE_ERROR( "invalid sheet pin type", aReader, line );
1090                 }
1091 
1092                 switch( parseChar( aReader, line, &line ) )
1093                 {
1094                 case 'R': sheetPin->SetSide( SHEET_SIDE::RIGHT ); break;
1095                 case 'T': sheetPin->SetSide( SHEET_SIDE::TOP ); break;
1096                 case 'B': sheetPin->SetSide( SHEET_SIDE::BOTTOM ); break;
1097                 case 'L': sheetPin->SetSide( SHEET_SIDE::LEFT ); break;
1098                 default:
1099                     SCH_PARSE_ERROR( "invalid sheet pin side", aReader, line );
1100                 }
1101 
1102                 wxPoint position;
1103 
1104                 position.x = Mils2Iu( parseInt( aReader, line, &line ) );
1105                 position.y = Mils2Iu( parseInt( aReader, line, &line ) );
1106                 sheetPin->SetPosition( position );
1107 
1108                 size = Mils2Iu( parseInt( aReader, line, &line ) );
1109 
1110                 sheetPin->SetTextSize( wxSize( size, size ) );
1111 
1112                 sheet->AddPin( sheetPin.release() );
1113             }
1114         }
1115         else if( strCompare( "$EndSheet", line ) )
1116         {
1117             sheet->AutoplaceFields( /* aScreen */ nullptr, /* aManual */ false );
1118             return sheet.release();
1119         }
1120 
1121         line = aReader.ReadLine();
1122     }
1123 
1124     SCH_PARSE_ERROR( "missing '$EndSheet`", aReader, line );
1125 
1126     return nullptr;  // Prevents compiler warning.  Should never get here.
1127 }
1128 
1129 
loadBitmap(LINE_READER & aReader)1130 SCH_BITMAP* SCH_LEGACY_PLUGIN::loadBitmap( LINE_READER& aReader )
1131 {
1132     std::unique_ptr<SCH_BITMAP> bitmap = std::make_unique<SCH_BITMAP>();
1133 
1134     const char* line = aReader.Line();
1135 
1136     wxCHECK( strCompare( "$Bitmap", line, &line ), nullptr );
1137 
1138     line = aReader.ReadLine();
1139 
1140     while( line != nullptr )
1141     {
1142         if( strCompare( "Pos", line, &line ) )
1143         {
1144             wxPoint position;
1145 
1146             position.x = Mils2Iu( parseInt( aReader, line, &line ) );
1147             position.y = Mils2Iu( parseInt( aReader, line, &line ) );
1148             bitmap->SetPosition( position );
1149         }
1150         else if( strCompare( "Scale", line, &line ) )
1151         {
1152             auto scalefactor = parseDouble( aReader, line, &line );
1153 
1154             // Prevent scalefactor values that cannot be displayed.
1155             // In the case of a bad value, we accept that the image might be mis-scaled
1156             // rather than removing the full image.  Users can then edit the scale factor in
1157             // Eeschema to the appropriate value
1158             if( !std::isnormal( scalefactor ) )
1159                 scalefactor = 1.0;
1160 
1161             bitmap->GetImage()->SetScale( scalefactor );
1162         }
1163         else if( strCompare( "Data", line, &line ) )
1164         {
1165             wxMemoryOutputStream stream;
1166 
1167             while( line )
1168             {
1169                 if( !aReader.ReadLine() )
1170                     SCH_PARSE_ERROR( _( "Unexpected end of file" ), aReader, line );
1171 
1172                 line = aReader.Line();
1173 
1174                 if( strCompare( "EndData", line ) )
1175                 {
1176                     // all the PNG date is read.
1177                     // We expect here m_image and m_bitmap are void
1178                     wxImage* image = new wxImage();
1179                     wxMemoryInputStream istream( stream );
1180                     image->LoadFile( istream, wxBITMAP_TYPE_PNG );
1181                     bitmap->GetImage()->SetImage( image );
1182                     bitmap->GetImage()->SetBitmap( new wxBitmap( *image ) );
1183                     break;
1184                 }
1185 
1186                 // Read PNG data, stored in hexadecimal,
1187                 // each byte = 2 hexadecimal digits and a space between 2 bytes
1188                 // and put it in memory stream buffer
1189                 // Note:
1190                 // Some old files created bu the V4 schematic versions have a extra
1191                 // "$EndBitmap" at the end of the hexadecimal data. (Probably due to
1192                 // a bug). So discard it
1193                 int len = strlen( line );
1194 
1195                 for( ; len > 0 && !isspace( *line ) && '$' != *line; len -= 3, line += 3 )
1196                 {
1197                     int value = 0;
1198 
1199                     if( sscanf( line, "%X", &value ) == 1 )
1200                         stream.PutC( (char) value );
1201                     else
1202                         THROW_IO_ERROR( "invalid PNG data" );
1203                 }
1204             }
1205 
1206             if( line == nullptr )
1207                 THROW_IO_ERROR( _( "unexpected end of file" ) );
1208         }
1209         else if( strCompare( "$EndBitmap", line ) )
1210             return bitmap.release();
1211 
1212         line = aReader.ReadLine();
1213     }
1214 
1215     THROW_IO_ERROR( _( "unexpected end of file" ) );
1216 }
1217 
1218 
loadJunction(LINE_READER & aReader)1219 SCH_JUNCTION* SCH_LEGACY_PLUGIN::loadJunction( LINE_READER& aReader )
1220 {
1221     std::unique_ptr<SCH_JUNCTION> junction = std::make_unique<SCH_JUNCTION>();
1222 
1223     const char* line = aReader.Line();
1224 
1225     wxCHECK( strCompare( "Connection", line, &line ), nullptr );
1226 
1227     wxString name;
1228 
1229     parseUnquotedString( name, aReader, line, &line );
1230 
1231     wxPoint position;
1232 
1233     position.x = Mils2Iu( parseInt( aReader, line, &line ) );
1234     position.y = Mils2Iu( parseInt( aReader, line, &line ) );
1235     junction->SetPosition( position );
1236 
1237     return junction.release();
1238 }
1239 
1240 
loadNoConnect(LINE_READER & aReader)1241 SCH_NO_CONNECT* SCH_LEGACY_PLUGIN::loadNoConnect( LINE_READER& aReader )
1242 {
1243     std::unique_ptr<SCH_NO_CONNECT> no_connect = std::make_unique<SCH_NO_CONNECT>();
1244 
1245     const char* line = aReader.Line();
1246 
1247     wxCHECK( strCompare( "NoConn", line, &line ), nullptr );
1248 
1249     wxString name;
1250 
1251     parseUnquotedString( name, aReader, line, &line );
1252 
1253     wxPoint position;
1254 
1255     position.x = Mils2Iu( parseInt( aReader, line, &line ) );
1256     position.y = Mils2Iu( parseInt( aReader, line, &line ) );
1257     no_connect->SetPosition( position );
1258 
1259     return no_connect.release();
1260 }
1261 
1262 
loadWire(LINE_READER & aReader)1263 SCH_LINE* SCH_LEGACY_PLUGIN::loadWire( LINE_READER& aReader )
1264 {
1265     std::unique_ptr<SCH_LINE> wire = std::make_unique<SCH_LINE>();
1266 
1267     const char* line = aReader.Line();
1268 
1269     wxCHECK( strCompare( "Wire", line, &line ), nullptr );
1270 
1271     if( strCompare( "Wire", line, &line ) )
1272         wire->SetLayer( LAYER_WIRE );
1273     else if( strCompare( "Bus", line, &line ) )
1274         wire->SetLayer( LAYER_BUS );
1275     else if( strCompare( "Notes", line, &line ) )
1276         wire->SetLayer( LAYER_NOTES );
1277     else
1278         SCH_PARSE_ERROR( "invalid line type", aReader, line );
1279 
1280     if( !strCompare( "Line", line, &line ) )
1281         SCH_PARSE_ERROR( "invalid wire definition", aReader, line );
1282 
1283     // Since Sept 15, 2017, a line style is alloved (width, style, color)
1284     // Only non default values are stored
1285     while( !is_eol( *line ) )
1286     {
1287         wxString buf;
1288 
1289         parseUnquotedString( buf, aReader, line, &line );
1290 
1291         if( buf == ")" )
1292             continue;
1293 
1294         else if( buf == T_WIDTH )
1295         {
1296             int size = Mils2Iu( parseInt( aReader, line, &line ) );
1297             wire->SetLineWidth( size );
1298         }
1299         else if( buf == T_STYLE )
1300         {
1301             parseUnquotedString( buf, aReader, line, &line );
1302             PLOT_DASH_TYPE style = SCH_LINE::GetLineStyleByName( buf );
1303             wire->SetLineStyle( style );
1304         }
1305         else    // should be the color parameter.
1306         {
1307             // The color param is something like rgb(150, 40, 191)
1308             // and because there is no space between ( and 150
1309             // the first param is inside buf.
1310             // So break keyword and the first param into 2 separate strings.
1311             wxString prm, keyword;
1312             keyword = buf.BeforeLast( '(', &prm );
1313 
1314             if( ( keyword == T_COLOR ) || ( keyword == T_COLORA ) )
1315             {
1316                 long color[4] = { 0 };
1317 
1318                 int ii = 0;
1319 
1320                 if( !prm.IsEmpty() )
1321                 {
1322                     prm.ToLong( &color[ii] );
1323                     ii++;
1324                 }
1325 
1326                 int prm_count = ( keyword == T_COLORA ) ? 4 : 3;
1327                 // fix opacity to 1.0 or 255, when not exists in file
1328                 color[3] = 255;
1329 
1330                 for(; ii < prm_count && !is_eol( *line ); ii++ )
1331                 {
1332                     color[ii] = parseInt( aReader, line, &line );
1333 
1334                     // Skip the separator between values
1335                     if( *line == ',' || *line == ' ')
1336                         line++;
1337                 }
1338 
1339                 wire->SetLineColor( color[0]/255.0, color[1]/255.0, color[2]/255.0,color[3]/255.0 );
1340             }
1341         }
1342     }
1343 
1344     // Read the segment en points coordinates:
1345     line = aReader.ReadLine();
1346 
1347     wxPoint begin, end;
1348 
1349     begin.x = Mils2Iu( parseInt( aReader, line, &line ) );
1350     begin.y = Mils2Iu( parseInt( aReader, line, &line ) );
1351     end.x = Mils2Iu( parseInt( aReader, line, &line ) );
1352     end.y = Mils2Iu( parseInt( aReader, line, &line ) );
1353 
1354     wire->SetStartPoint( begin );
1355     wire->SetEndPoint( end );
1356 
1357     return wire.release();
1358 }
1359 
1360 
loadBusEntry(LINE_READER & aReader)1361 SCH_BUS_ENTRY_BASE* SCH_LEGACY_PLUGIN::loadBusEntry( LINE_READER& aReader )
1362 {
1363     const char* line = aReader.Line();
1364 
1365     wxCHECK( strCompare( "Entry", line, &line ), nullptr );
1366 
1367     std::unique_ptr<SCH_BUS_ENTRY_BASE> busEntry;
1368 
1369     if( strCompare( "Wire", line, &line ) )
1370     {
1371         busEntry = std::make_unique<SCH_BUS_WIRE_ENTRY>();
1372 
1373         if( !strCompare( "Line", line, &line ) )
1374             SCH_PARSE_ERROR( "invalid bus entry definition expected 'Line'", aReader, line );
1375     }
1376     else if( strCompare( "Bus", line, &line ) )
1377     {
1378         busEntry = std::make_unique<SCH_BUS_BUS_ENTRY>();
1379 
1380         if( !strCompare( "Bus", line, &line ) )
1381             SCH_PARSE_ERROR( "invalid bus entry definition expected 'Bus'", aReader, line );
1382     }
1383     else
1384         SCH_PARSE_ERROR( "invalid bus entry type", aReader, line );
1385 
1386     line = aReader.ReadLine();
1387 
1388     wxPoint pos;
1389     wxSize size;
1390 
1391     pos.x = Mils2Iu( parseInt( aReader, line, &line ) );
1392     pos.y = Mils2Iu( parseInt( aReader, line, &line ) );
1393     size.x = Mils2Iu( parseInt( aReader, line, &line ) );
1394     size.y = Mils2Iu( parseInt( aReader, line, &line ) );
1395 
1396     size.x -= pos.x;
1397     size.y -= pos.y;
1398 
1399     busEntry->SetPosition( pos );
1400     busEntry->SetSize( size );
1401 
1402     return busEntry.release();
1403 }
1404 
1405 // clang-format off
1406 const std::map<PINSHEETLABEL_SHAPE, const char*> sheetLabelNames
1407 {
1408     { PINSHEETLABEL_SHAPE::PS_INPUT,       "Input" },
1409     { PINSHEETLABEL_SHAPE::PS_OUTPUT,      "Output" },
1410     { PINSHEETLABEL_SHAPE::PS_BIDI,        "BiDi" },
1411     { PINSHEETLABEL_SHAPE::PS_TRISTATE,    "3State" },
1412     { PINSHEETLABEL_SHAPE::PS_UNSPECIFIED, "UnSpc" },
1413 };
1414 // clang-format on
1415 
1416 
loadText(LINE_READER & aReader)1417 SCH_TEXT* SCH_LEGACY_PLUGIN::loadText( LINE_READER& aReader )
1418 {
1419     const char*   line = aReader.Line();
1420 
1421     wxCHECK( strCompare( "Text", line, &line ), nullptr );
1422 
1423     std::unique_ptr<SCH_TEXT> text;
1424 
1425     if( strCompare( "Notes", line, &line ) )
1426         text.reset( new SCH_TEXT );
1427     else if( strCompare( "Label", line, &line ) )
1428         text.reset( new SCH_LABEL );
1429     else if( strCompare( "HLabel", line, &line ) )
1430         text.reset( new SCH_HIERLABEL );
1431     else if( strCompare( "GLabel", line, &line ) )
1432     {
1433         // Prior to version 2, the SCH_GLOBALLABEL object did not exist.
1434         if( m_version == 1 )
1435             text = std::make_unique<SCH_HIERLABEL>();
1436         else
1437             text = std::make_unique<SCH_GLOBALLABEL>();
1438     }
1439     else
1440         SCH_PARSE_ERROR( "unknown Text type", aReader, line );
1441 
1442     // Parse the parameters common to all text objects.
1443     wxPoint position;
1444 
1445     position.x = Mils2Iu( parseInt( aReader, line, &line ) );
1446     position.y = Mils2Iu( parseInt( aReader, line, &line ) );
1447     text->SetPosition( position );
1448 
1449     int spinStyle = parseInt( aReader, line, &line );
1450 
1451     // Sadly we store the orientation of hierarchical and global labels using a different
1452     // int encoding than that for local labels:
1453     //                   Global      Local
1454     // Left justified      0           2
1455     // Up                  1           3
1456     // Right justified     2           0
1457     // Down                3           1
1458     // So we must flip it as the enum is setup with the "global" numbering
1459     if( text->Type() != SCH_GLOBAL_LABEL_T && text->Type() != SCH_HIER_LABEL_T )
1460     {
1461         if( spinStyle == 0 )
1462             spinStyle = 2;
1463         else if( spinStyle == 2 )
1464             spinStyle = 0;
1465     }
1466 
1467     text->SetLabelSpinStyle( (LABEL_SPIN_STYLE::SPIN) spinStyle );
1468 
1469     int size = Mils2Iu( parseInt( aReader, line, &line ) );
1470 
1471     text->SetTextSize( wxSize( size, size ) );
1472 
1473     // Parse the global and hierarchical label type.
1474     if( text->Type() == SCH_HIER_LABEL_T || text->Type() == SCH_GLOBAL_LABEL_T )
1475     {
1476         auto resultIt = std::find_if( sheetLabelNames.begin(), sheetLabelNames.end(),
1477                 [ &line ]( const auto& it )
1478                 {
1479                     return strCompare( it.second, line, &line );
1480                 } );
1481 
1482         if( resultIt != sheetLabelNames.end() )
1483             text->SetShape( resultIt->first );
1484         else
1485             SCH_PARSE_ERROR( "invalid label type", aReader, line );
1486     }
1487 
1488     int penWidth = 0;
1489 
1490     // The following tokens do not exist in version 1 schematic files,
1491     // and not always in version 2 for HLabels and GLabels
1492     if( m_version > 1 )
1493     {
1494         if( m_version > 2 || *line >= ' ' )
1495         {
1496             if( strCompare( "Italic", line, &line ) )
1497                 text->SetItalic( true );
1498             else if( !strCompare( "~", line, &line ) )
1499                 SCH_PARSE_ERROR( _( "expected 'Italics' or '~'" ), aReader, line );
1500         }
1501 
1502         // The penWidth token does not exist in older versions of the schematic file format
1503         // so calling parseInt will be made only if the EOL is not reached.
1504         if( *line >= ' ' )
1505             penWidth = parseInt( aReader, line, &line );
1506     }
1507 
1508     text->SetBold( penWidth != 0 );
1509     text->SetTextThickness( penWidth != 0 ? GetPenSizeForBold( size ) : 0 );
1510 
1511     // Read the text string for the text.
1512     char* tmp = aReader.ReadLine();
1513 
1514     tmp = strtok( tmp, "\r\n" );
1515     wxString val = FROM_UTF8( tmp );
1516 
1517     for( ; ; )
1518     {
1519         int i = val.find( wxT( "\\n" ) );
1520 
1521         if( i == wxNOT_FOUND )
1522             break;
1523 
1524         val.erase( i, 2 );
1525         val.insert( i, wxT( "\n" ) );
1526     }
1527 
1528     text->SetText( ConvertToNewOverbarNotation( val ) );
1529 
1530     return text.release();
1531 }
1532 
1533 
loadSymbol(LINE_READER & aReader)1534 SCH_SYMBOL* SCH_LEGACY_PLUGIN::loadSymbol( LINE_READER& aReader )
1535 {
1536     const char* line = aReader.Line();
1537 
1538     wxCHECK( strCompare( "$Comp", line, &line ), nullptr );
1539 
1540     std::unique_ptr<SCH_SYMBOL> symbol = std::make_unique<SCH_SYMBOL>();
1541 
1542     line = aReader.ReadLine();
1543 
1544     while( line != nullptr )
1545     {
1546         if( strCompare( "L", line, &line ) )
1547         {
1548             wxString libName;
1549             size_t pos = 2;                               // "X" plus ' ' space character.
1550             wxString utf8Line = wxString::FromUTF8( line );
1551             wxStringTokenizer tokens( utf8Line, " \r\n\t" );
1552 
1553             if( tokens.CountTokens() < 2 )
1554                 THROW_PARSE_ERROR( "invalid symbol library definition", aReader.GetSource(),
1555                                    aReader.Line(), aReader.LineNumber(), pos );
1556 
1557             libName = tokens.GetNextToken();
1558             libName.Replace( "~", " " );
1559 
1560             LIB_ID libId;
1561 
1562             // Prior to schematic version 4, library IDs did not have a library nickname so
1563             // parsing the symbol name with LIB_ID::Parse() would break symbol library links
1564             // that contained '/' and ':' characters.
1565             if( m_version > 3 )
1566                 libId.Parse( libName, true );
1567             else
1568                 libId.SetLibItemName( libName );
1569 
1570             symbol->SetLibId( libId );
1571 
1572             wxString refDesignator = tokens.GetNextToken();
1573 
1574             refDesignator.Replace( "~", " " );
1575 
1576             wxString prefix = refDesignator;
1577 
1578             while( prefix.Length() )
1579             {
1580                 if( ( prefix.Last() < '0' || prefix.Last() > '9') && prefix.Last() != '?' )
1581                     break;
1582 
1583                 prefix.RemoveLast();
1584             }
1585 
1586             // Avoid a prefix containing trailing/leading spaces
1587             prefix.Trim( true );
1588             prefix.Trim( false );
1589 
1590             if( prefix.IsEmpty() )
1591                 symbol->SetPrefix( wxString( "U" ) );
1592             else
1593                 symbol->SetPrefix( prefix );
1594         }
1595         else if( strCompare( "U", line, &line ) )
1596         {
1597             // This fixes a potentially buggy files caused by unit being set to zero which
1598             // causes netlist issues.  See https://bugs.launchpad.net/kicad/+bug/1677282.
1599             int unit = parseInt( aReader, line, &line );
1600 
1601             if( unit == 0 )
1602             {
1603                 unit = 1;
1604 
1605                 // Set the file as modified so the user can be warned.
1606                 if( m_rootSheet->GetScreen() )
1607                     m_rootSheet->GetScreen()->SetContentModified();
1608             }
1609 
1610             symbol->SetUnit( unit );
1611 
1612             // Same can also happen with the convert parameter
1613             int convert = parseInt( aReader, line, &line );
1614 
1615             if( convert == 0 )
1616             {
1617                 convert = 1;
1618 
1619                 // Set the file as modified so the user can be warned.
1620                 if( m_rootSheet->GetScreen() )
1621                     m_rootSheet->GetScreen()->SetContentModified();
1622             }
1623 
1624             symbol->SetConvert( convert );
1625 
1626             wxString text;
1627             parseUnquotedString( text, aReader, line, &line );
1628 
1629             if( text != "00000000" )
1630                 const_cast<KIID&>( symbol->m_Uuid ) = KIID( text );
1631         }
1632         else if( strCompare( "P", line, &line ) )
1633         {
1634             wxPoint pos;
1635 
1636             pos.x = Mils2Iu( parseInt( aReader, line, &line ) );
1637             pos.y = Mils2Iu( parseInt( aReader, line, &line ) );
1638             symbol->SetPosition( pos );
1639         }
1640         else if( strCompare( "AR", line, &line ) )
1641         {
1642             const char* strCompare = "Path=";
1643             int         len = strlen( strCompare );
1644 
1645             if( strncasecmp( strCompare, line, len ) != 0 )
1646                 SCH_PARSE_ERROR( "missing 'Path=' token", aReader, line );
1647 
1648             line += len;
1649             wxString pathStr, reference, unit;
1650 
1651             parseQuotedString( pathStr, aReader, line, &line );
1652 
1653             // Note: AR path excludes root sheet, but includes symbol.  Normalize to
1654             // internal format by shifting everything down one and adding the root sheet.
1655             KIID_PATH path( pathStr );
1656 
1657             if( path.size() > 0 )
1658             {
1659                 for( size_t i = path.size() - 1; i > 0; --i )
1660                     path[i] = path[i-1];
1661 
1662                 path[0] = m_rootSheet->m_Uuid;
1663             }
1664             else
1665                 path.push_back( m_rootSheet->m_Uuid );
1666 
1667             strCompare = "Ref=";
1668             len = strlen( strCompare );
1669 
1670             if( strncasecmp( strCompare, line, len ) != 0 )
1671                 SCH_PARSE_ERROR( "missing 'Ref=' token", aReader, line );
1672 
1673             line+= len;
1674             parseQuotedString( reference, aReader, line, &line );
1675 
1676             strCompare = "Part=";
1677             len = strlen( strCompare );
1678 
1679             if( strncasecmp( strCompare, line, len ) != 0 )
1680                 SCH_PARSE_ERROR( "missing 'Part=' token", aReader, line );
1681 
1682             line+= len;
1683             parseQuotedString( unit, aReader, line, &line );
1684 
1685             long tmp;
1686 
1687             if( !unit.ToLong( &tmp, 10 ) )
1688                 SCH_PARSE_ERROR( "expected integer value", aReader, line );
1689 
1690             if( tmp < 0 || tmp > MAX_UNIT_COUNT_PER_PACKAGE )
1691                 SCH_PARSE_ERROR( "unit value out of range", aReader, line );
1692 
1693             symbol->AddHierarchicalReference( path, reference, (int)tmp );
1694             symbol->GetField( REFERENCE_FIELD )->SetText( reference );
1695 
1696         }
1697         else if( strCompare( "F", line, &line ) )
1698         {
1699             int index = parseInt( aReader, line, &line );
1700 
1701             wxString text, name;
1702 
1703             parseQuotedString( text, aReader, line, &line, true );
1704 
1705             char orientation = parseChar( aReader, line, &line );
1706             wxPoint pos;
1707             pos.x = Mils2Iu( parseInt( aReader, line, &line ) );
1708             pos.y = Mils2Iu( parseInt( aReader, line, &line ) );
1709             int size = Mils2Iu( parseInt( aReader, line, &line ) );
1710             int attributes = parseHex( aReader, line, &line );
1711 
1712             if( index >= symbol->GetFieldCount() )
1713             {
1714                 // The first MANDATOR_FIELDS _must_ be constructed within the SCH_SYMBOL
1715                 // constructor.  This assert is simply here to guard against a change in that
1716                 // constructor.
1717                 wxASSERT( symbol->GetFieldCount() >= MANDATORY_FIELDS );
1718 
1719                 // Ignore the _supplied_ fieldNdx.  It is not important anymore if within the
1720                 // user defined fields region (i.e. >= MANDATORY_FIELDS).
1721                 // We freely renumber the index to fit the next available field slot.
1722                 index = symbol->GetFieldCount();  // new has this index after insertion
1723 
1724                 SCH_FIELD field( wxPoint( 0, 0 ), index, symbol.get(), name );
1725                 symbol->AddField( field );
1726             }
1727 
1728             SCH_FIELD& field = symbol->GetFields()[index];
1729 
1730             // Prior to version 2 of the schematic file format, none of the following existed.
1731             if( m_version > 1 )
1732             {
1733                 wxString textAttrs;
1734                 char hjustify = parseChar( aReader, line, &line );
1735 
1736                 parseUnquotedString( textAttrs, aReader, line, &line );
1737 
1738                 // The name of the field is optional.
1739                 parseQuotedString( name, aReader, line, &line, true );
1740 
1741                 if( hjustify == 'L' )
1742                     field.SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT );
1743                 else if( hjustify == 'R' )
1744                     field.SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT );
1745                 else if( hjustify != 'C' )
1746                     SCH_PARSE_ERROR( "symbol field text horizontal justification must be "
1747                                      "L, R, or C", aReader, line );
1748 
1749                 // We are guaranteed to have a least one character here for older file formats
1750                 // otherwise an exception would have been raised..
1751                 if( textAttrs[0] == 'T' )
1752                     field.SetVertJustify( GR_TEXT_VJUSTIFY_TOP );
1753                 else if( textAttrs[0] == 'B' )
1754                     field.SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM );
1755                 else if( textAttrs[0] != 'C' )
1756                     SCH_PARSE_ERROR( "symbol field text vertical justification must be "
1757                                      "B, T, or C", aReader, line );
1758 
1759                 // Newer file formats include the bold and italics text attribute.
1760                 if( textAttrs.Length() > 1 )
1761                 {
1762                     if( textAttrs.Length() != 3 )
1763                         SCH_PARSE_ERROR( _( "symbol field text attributes must be 3 characters wide" ),
1764                                          aReader, line );
1765 
1766                     if( textAttrs[1] == 'I' )
1767                         field.SetItalic( true );
1768                     else if( textAttrs[1] != 'N' )
1769                         SCH_PARSE_ERROR( "symbol field text italics indicator must be I or N",
1770                                          aReader, line );
1771 
1772                     if( textAttrs[2] == 'B' )
1773                         field.SetBold( true );
1774                     else if( textAttrs[2] != 'N' )
1775                         SCH_PARSE_ERROR( "symbol field text bold indicator must be B or N",
1776                                          aReader, line );
1777                 }
1778             }
1779 
1780             field.SetText( text );
1781             field.SetTextPos( pos );
1782             field.SetVisible( !attributes );
1783             field.SetTextSize( wxSize( size, size ) );
1784 
1785             if( orientation == 'H' )
1786                 field.SetTextAngle( TEXT_ANGLE_HORIZ );
1787             else if( orientation == 'V' )
1788                 field.SetTextAngle( TEXT_ANGLE_VERT );
1789             else
1790                 SCH_PARSE_ERROR( "symbol field orientation must be H or V", aReader, line );
1791 
1792             if( name.IsEmpty() )
1793                 name = TEMPLATE_FIELDNAME::GetDefaultFieldName( index );
1794 
1795             field.SetName( name );
1796         }
1797         else if( strCompare( "$EndComp", line ) )
1798         {
1799             // Ensure all flags (some are set by previous initializations) are reset:
1800             symbol->ClearFlags();
1801             return symbol.release();
1802         }
1803         else
1804         {
1805             // There are two lines that begin with a tab or spaces that includes a line with the
1806             // redundant position information and the transform matrix settings.
1807 
1808             // Parse the redundant position information just the same to check for formatting
1809             // errors.
1810             parseInt( aReader, line, &line );    // Always 1.
1811             parseInt( aReader, line, &line );    // The X coordinate.
1812             parseInt( aReader, line, &line );    // The Y coordinate.
1813 
1814             line = aReader.ReadLine();
1815 
1816             TRANSFORM transform;
1817 
1818             transform.x1 = parseInt( aReader, line, &line );
1819 
1820             if( transform.x1 < -1 || transform.x1 > 1 )
1821                 SCH_PARSE_ERROR( "invalid symbol X1 transform value", aReader, line );
1822 
1823             transform.y1 = parseInt( aReader, line, &line );
1824 
1825             if( transform.y1 < -1 || transform.y1 > 1 )
1826                 SCH_PARSE_ERROR( "invalid symbol Y1 transform value", aReader, line );
1827 
1828             transform.x2 = parseInt( aReader, line, &line );
1829 
1830             if( transform.x2 < -1 || transform.x2 > 1 )
1831                 SCH_PARSE_ERROR( "invalid symbol X2 transform value", aReader, line );
1832 
1833             transform.y2 = parseInt( aReader, line, &line );
1834 
1835             if( transform.y2 < -1 || transform.y2 > 1 )
1836                 SCH_PARSE_ERROR( "invalid symbol Y2 transform value", aReader, line );
1837 
1838             symbol->SetTransform( transform );
1839         }
1840 
1841         line = aReader.ReadLine();
1842     }
1843 
1844     SCH_PARSE_ERROR( "invalid symbol line", aReader, line );
1845 
1846     return nullptr;  // Prevents compiler warning.  Should never get here.
1847 }
1848 
1849 
loadBusAlias(LINE_READER & aReader,SCH_SCREEN * aScreen)1850 std::shared_ptr<BUS_ALIAS> SCH_LEGACY_PLUGIN::loadBusAlias( LINE_READER& aReader,
1851                                                             SCH_SCREEN* aScreen )
1852 {
1853     auto busAlias = std::make_shared<BUS_ALIAS>( aScreen );
1854     const char* line = aReader.Line();
1855 
1856     wxCHECK( strCompare( "BusAlias", line, &line ), nullptr );
1857 
1858     wxString buf;
1859     parseUnquotedString( buf, aReader, line, &line );
1860     busAlias->SetName( buf );
1861 
1862     while( *line != '\0' )
1863     {
1864         buf.clear();
1865         parseUnquotedString( buf, aReader, line, &line, true );
1866 
1867         if( buf.Len() > 0 )
1868         {
1869             busAlias->AddMember( buf );
1870         }
1871     }
1872 
1873     return busAlias;
1874 }
1875 
1876 
Save(const wxString & aFileName,SCH_SHEET * aSheet,SCHEMATIC * aSchematic,const PROPERTIES * aProperties)1877 void SCH_LEGACY_PLUGIN::Save( const wxString& aFileName, SCH_SHEET* aSheet, SCHEMATIC* aSchematic,
1878                               const PROPERTIES* aProperties )
1879 {
1880     wxCHECK_RET( aSheet != nullptr, "NULL SCH_SHEET object." );
1881     wxCHECK_RET( !aFileName.IsEmpty(), "No schematic file name defined." );
1882 
1883     LOCALE_IO   toggle;     // toggles on, then off, the C locale, to write floating point values.
1884 
1885     init( aSchematic, aProperties );
1886 
1887     wxFileName fn = aFileName;
1888 
1889     // File names should be absolute.  Don't assume everything relative to the project path
1890     // works properly.
1891     wxASSERT( fn.IsAbsolute() );
1892 
1893     FILE_OUTPUTFORMATTER formatter( fn.GetFullPath() );
1894 
1895     m_out = &formatter;     // no ownership
1896 
1897     Format( aSheet );
1898 
1899     aSheet->GetScreen()->SetFileExists( true );
1900 }
1901 
1902 
Format(SCH_SHEET * aSheet)1903 void SCH_LEGACY_PLUGIN::Format( SCH_SHEET* aSheet )
1904 {
1905     wxCHECK_RET( aSheet != nullptr, "NULL SCH_SHEET* object." );
1906     wxCHECK_RET( m_schematic != nullptr, "NULL SCHEMATIC* object." );
1907 
1908     SCH_SCREEN* screen = aSheet->GetScreen();
1909 
1910     wxCHECK( screen, /* void */ );
1911 
1912     // Write the header
1913     m_out->Print( 0, "%s %s %d\n", "EESchema", SCHEMATIC_HEAD_STRING, EESCHEMA_VERSION );
1914 
1915     // This section is not used, but written for file compatibility
1916     m_out->Print( 0, "EELAYER %d %d\n", SCH_LAYER_ID_COUNT, 0 );
1917     m_out->Print( 0, "EELAYER END\n" );
1918 
1919     /* Write page info, ScreenNumber and NumberOfScreen; not very meaningful for
1920      * SheetNumber and Sheet Count in a complex hierarchy, but useful in
1921      * simple hierarchy and flat hierarchy.  Used also to search the root
1922      * sheet ( ScreenNumber = 1 ) within the files
1923      */
1924     const TITLE_BLOCK& tb = screen->GetTitleBlock();
1925     const PAGE_INFO& page = screen->GetPageSettings();
1926 
1927     m_out->Print( 0, "$Descr %s %d %d%s\n", TO_UTF8( page.GetType() ),
1928                   page.GetWidthMils(),
1929                   page.GetHeightMils(),
1930                   !page.IsCustom() && page.IsPortrait() ? " portrait" : "" );
1931     m_out->Print( 0, "encoding utf-8\n" );
1932     m_out->Print( 0, "Sheet %d %d\n", screen->GetVirtualPageNumber(), screen->GetPageCount() );
1933     m_out->Print( 0, "Title %s\n",    EscapedUTF8( tb.GetTitle() ).c_str() );
1934     m_out->Print( 0, "Date %s\n",     EscapedUTF8( tb.GetDate() ).c_str() );
1935     m_out->Print( 0, "Rev %s\n",      EscapedUTF8( tb.GetRevision() ).c_str() );
1936     m_out->Print( 0, "Comp %s\n",     EscapedUTF8( tb.GetCompany() ).c_str() );
1937     m_out->Print( 0, "Comment1 %s\n", EscapedUTF8( tb.GetComment( 0 ) ).c_str() );
1938     m_out->Print( 0, "Comment2 %s\n", EscapedUTF8( tb.GetComment( 1 ) ).c_str() );
1939     m_out->Print( 0, "Comment3 %s\n", EscapedUTF8( tb.GetComment( 2 ) ).c_str() );
1940     m_out->Print( 0, "Comment4 %s\n", EscapedUTF8( tb.GetComment( 3 ) ).c_str() );
1941     m_out->Print( 0, "Comment5 %s\n", EscapedUTF8( tb.GetComment( 4 ) ).c_str() );
1942     m_out->Print( 0, "Comment6 %s\n", EscapedUTF8( tb.GetComment( 5 ) ).c_str() );
1943     m_out->Print( 0, "Comment7 %s\n", EscapedUTF8( tb.GetComment( 6 ) ).c_str() );
1944     m_out->Print( 0, "Comment8 %s\n", EscapedUTF8( tb.GetComment( 7 ) ).c_str() );
1945     m_out->Print( 0, "Comment9 %s\n", EscapedUTF8( tb.GetComment( 8 ) ).c_str() );
1946     m_out->Print( 0, "$EndDescr\n" );
1947 
1948     for( const auto& alias : screen->GetBusAliases() )
1949     {
1950         saveBusAlias( alias );
1951     }
1952 
1953     // Enforce item ordering
1954     auto cmp = []( const SCH_ITEM* a, const SCH_ITEM* b ) { return *a < *b; };
1955     std::multiset<SCH_ITEM*, decltype( cmp )> save_map( cmp );
1956 
1957     for( auto item : screen->Items() )
1958         save_map.insert( item );
1959 
1960 
1961     for( auto& item : save_map )
1962     {
1963         switch( item->Type() )
1964         {
1965         case SCH_SYMBOL_T:
1966             saveSymbol( static_cast<SCH_SYMBOL*>( item ) );
1967             break;
1968         case SCH_BITMAP_T:
1969             saveBitmap( static_cast<SCH_BITMAP*>( item ) );
1970             break;
1971         case SCH_SHEET_T:
1972             saveSheet( static_cast<SCH_SHEET*>( item ) );
1973             break;
1974         case SCH_JUNCTION_T:
1975             saveJunction( static_cast<SCH_JUNCTION*>( item ) );
1976             break;
1977         case SCH_NO_CONNECT_T:
1978             saveNoConnect( static_cast<SCH_NO_CONNECT*>( item ) );
1979             break;
1980         case SCH_BUS_WIRE_ENTRY_T:
1981         case SCH_BUS_BUS_ENTRY_T:
1982             saveBusEntry( static_cast<SCH_BUS_ENTRY_BASE*>( item ) );
1983             break;
1984         case SCH_LINE_T:
1985             saveLine( static_cast<SCH_LINE*>( item ) );
1986             break;
1987         case SCH_TEXT_T:
1988         case SCH_LABEL_T:
1989         case SCH_GLOBAL_LABEL_T:
1990         case SCH_HIER_LABEL_T:
1991             saveText( static_cast<SCH_TEXT*>( item ) );
1992             break;
1993         default:
1994             wxASSERT( "Unexpected schematic object type in SCH_LEGACY_PLUGIN::Format()" );
1995         }
1996     }
1997 
1998     m_out->Print( 0, "$EndSCHEMATC\n" );
1999 }
2000 
2001 
Format(SELECTION * aSelection,OUTPUTFORMATTER * aFormatter)2002 void SCH_LEGACY_PLUGIN::Format( SELECTION* aSelection, OUTPUTFORMATTER* aFormatter )
2003 {
2004     m_out = aFormatter;
2005 
2006     for( unsigned i = 0; i < aSelection->GetSize(); ++i )
2007     {
2008         SCH_ITEM* item = (SCH_ITEM*) aSelection->GetItem( i );
2009 
2010         switch( item->Type() )
2011         {
2012         case SCH_SYMBOL_T:
2013             saveSymbol( static_cast< SCH_SYMBOL* >( item ) );
2014             break;
2015         case SCH_BITMAP_T:
2016             saveBitmap( static_cast< SCH_BITMAP* >( item ) );
2017             break;
2018         case SCH_SHEET_T:
2019             saveSheet( static_cast< SCH_SHEET* >( item ) );
2020             break;
2021         case SCH_JUNCTION_T:
2022             saveJunction( static_cast< SCH_JUNCTION* >( item ) );
2023             break;
2024         case SCH_NO_CONNECT_T:
2025             saveNoConnect( static_cast< SCH_NO_CONNECT* >( item ) );
2026             break;
2027         case SCH_BUS_WIRE_ENTRY_T:
2028         case SCH_BUS_BUS_ENTRY_T:
2029             saveBusEntry( static_cast< SCH_BUS_ENTRY_BASE* >( item ) );
2030             break;
2031         case SCH_LINE_T:
2032             saveLine( static_cast< SCH_LINE* >( item ) );
2033             break;
2034         case SCH_TEXT_T:
2035         case SCH_LABEL_T:
2036         case SCH_GLOBAL_LABEL_T:
2037         case SCH_HIER_LABEL_T:
2038             saveText( static_cast< SCH_TEXT* >( item ) );
2039             break;
2040         default:
2041             wxASSERT( "Unexpected schematic object type in SCH_LEGACY_PLUGIN::Format()" );
2042         }
2043     }
2044 }
2045 
2046 
saveSymbol(SCH_SYMBOL * aSymbol)2047 void SCH_LEGACY_PLUGIN::saveSymbol( SCH_SYMBOL* aSymbol )
2048 {
2049     std::string     name1;
2050     std::string     name2;
2051 
2052     static wxString delimiters( wxT( " " ) );
2053 
2054     // This is redundant with the AR entries below, but it makes the files backwards-compatible.
2055     if( aSymbol->GetInstanceReferences().size() > 0 )
2056     {
2057         const SYMBOL_INSTANCE_REFERENCE& instance = aSymbol->GetInstanceReferences()[0];
2058         name1 = toUTFTildaText( instance.m_Reference );
2059     }
2060     else
2061     {
2062         if( aSymbol->GetField( REFERENCE_FIELD )->GetText().IsEmpty() )
2063             name1 = toUTFTildaText( aSymbol->GetPrefix() );
2064         else
2065             name1 = toUTFTildaText( aSymbol->GetField( REFERENCE_FIELD )->GetText() );
2066     }
2067 
2068     wxString symbol_name = aSymbol->GetLibId().Format();
2069 
2070     if( symbol_name.size() )
2071     {
2072         name2 = toUTFTildaText( symbol_name );
2073     }
2074     else
2075     {
2076         name2 = "_NONAME_";
2077     }
2078 
2079     m_out->Print( 0, "$Comp\n" );
2080     m_out->Print( 0, "L %s %s\n", name2.c_str(), name1.c_str() );
2081 
2082     // Generate unit number, conversion and timestamp
2083     m_out->Print( 0, "U %d %d %8.8X\n",
2084                   aSymbol->GetUnit(),
2085                   aSymbol->GetConvert(),
2086                   aSymbol->m_Uuid.AsLegacyTimestamp() );
2087 
2088     // Save the position
2089     m_out->Print( 0, "P %d %d\n",
2090                   Iu2Mils( aSymbol->GetPosition().x ),
2091                   Iu2Mils( aSymbol->GetPosition().y ) );
2092 
2093     /* If this is a complex hierarchy; save hierarchical references.
2094      * but for simple hierarchies it is not necessary.
2095      * the reference inf is already saved
2096      * this is useful for old Eeschema version compatibility
2097      */
2098     if( aSymbol->GetInstanceReferences().size() > 1 )
2099     {
2100         for( const SYMBOL_INSTANCE_REFERENCE& instance : aSymbol->GetInstanceReferences() )
2101         {
2102             /*format:
2103              * AR Path="/140/2" Ref="C99"   Part="1"
2104              * where 140 is the uid of the containing sheet and 2 is the timestamp of this symbol.
2105              * (timestamps are actually 8 hex chars)
2106              * Ref is the conventional symbol reference designator for this 'path'
2107              * Part is the conventional symbol unit selection for this 'path'
2108              */
2109             wxString path = "/";
2110 
2111             // Skip root sheet
2112             for( int i = 1; i < (int) instance.m_Path.size(); ++i )
2113                 path += instance.m_Path[i].AsLegacyTimestampString() + "/";
2114 
2115             m_out->Print( 0, "AR Path=\"%s\" Ref=\"%s\"  Part=\"%d\" \n",
2116                           TO_UTF8( path + aSymbol->m_Uuid.AsLegacyTimestampString() ),
2117                           TO_UTF8( instance.m_Reference ),
2118                           instance.m_Unit );
2119         }
2120     }
2121 
2122     // update the ugly field id, which I would like to see go away someday soon.
2123     for( int i = 0;  i < aSymbol->GetFieldCount();  ++i )
2124         aSymbol->GetFields()[i].SetId( i );
2125 
2126     // Fixed fields:
2127     // Save mandatory fields even if they are blank,
2128     // because the visibility, size and orientation are set from library editor.
2129     for( unsigned i = 0; i < MANDATORY_FIELDS; ++i )
2130         saveField( &aSymbol->GetFields()[i] );
2131 
2132     // User defined fields:
2133     // The *policy* about which user defined fields are symbol of a symbol is now
2134     // only in the dialog editors.  No policy should be enforced here, simply
2135     // save all the user defined fields, they are present because a dialog editor
2136     // thought they should be.  If you disagree, go fix the dialog editors.
2137     for( int i = MANDATORY_FIELDS;  i < aSymbol->GetFieldCount();  ++i )
2138         saveField( &aSymbol->GetFields()[i] );
2139 
2140     // Unit number, position, box ( old standard )
2141     m_out->Print( 0, "\t%-4d %-4d %-4d\n", aSymbol->GetUnit(),
2142                   Iu2Mils( aSymbol->GetPosition().x ),
2143                   Iu2Mils( aSymbol->GetPosition().y ) );
2144 
2145     TRANSFORM transform = aSymbol->GetTransform();
2146 
2147     m_out->Print( 0, "\t%-4d %-4d %-4d %-4d\n",
2148                   transform.x1, transform.y1, transform.x2, transform.y2 );
2149     m_out->Print( 0, "$EndComp\n" );
2150 }
2151 
2152 
saveField(SCH_FIELD * aField)2153 void SCH_LEGACY_PLUGIN::saveField( SCH_FIELD* aField )
2154 {
2155     char hjustify = 'C';
2156 
2157     if( aField->GetHorizJustify() == GR_TEXT_HJUSTIFY_LEFT )
2158         hjustify = 'L';
2159     else if( aField->GetHorizJustify() == GR_TEXT_HJUSTIFY_RIGHT )
2160         hjustify = 'R';
2161 
2162     char vjustify = 'C';
2163 
2164     if( aField->GetVertJustify() == GR_TEXT_VJUSTIFY_BOTTOM )
2165         vjustify = 'B';
2166     else if( aField->GetVertJustify() == GR_TEXT_VJUSTIFY_TOP )
2167         vjustify = 'T';
2168 
2169     m_out->Print( 0, "F %d %s %c %-3d %-3d %-3d %4.4X %c %c%c%c",
2170                   aField->GetId(),
2171                   EscapedUTF8( aField->GetText() ).c_str(),     // wraps in quotes too
2172                   aField->GetTextAngle() == TEXT_ANGLE_HORIZ ? 'H' : 'V',
2173                   Iu2Mils( aField->GetLibPosition().x ),
2174                   Iu2Mils( aField->GetLibPosition().y ),
2175                   Iu2Mils( aField->GetTextWidth() ),
2176                   !aField->IsVisible(),
2177                   hjustify, vjustify,
2178                   aField->IsItalic() ? 'I' : 'N',
2179                   aField->IsBold() ? 'B' : 'N' );
2180 
2181     // Save field name, if the name is user definable
2182     if( aField->GetId() >= MANDATORY_FIELDS )
2183         m_out->Print( 0, " %s", EscapedUTF8( aField->GetName() ).c_str() );
2184 
2185     m_out->Print( 0, "\n" );
2186 }
2187 
2188 
saveBitmap(SCH_BITMAP * aBitmap)2189 void SCH_LEGACY_PLUGIN::saveBitmap( SCH_BITMAP* aBitmap )
2190 {
2191     wxCHECK_RET( aBitmap != nullptr, "SCH_BITMAP* is NULL" );
2192 
2193     const wxImage* image = aBitmap->GetImage()->GetImageData();
2194 
2195     wxCHECK_RET( image != nullptr, "wxImage* is NULL" );
2196 
2197     m_out->Print( 0, "$Bitmap\n" );
2198     m_out->Print( 0, "Pos %-4d %-4d\n",
2199                   Iu2Mils( aBitmap->GetPosition().x ),
2200                   Iu2Mils( aBitmap->GetPosition().y ) );
2201     m_out->Print( 0, "Scale %f\n", aBitmap->GetImage()->GetScale() );
2202     m_out->Print( 0, "Data\n" );
2203 
2204     wxMemoryOutputStream stream;
2205 
2206     image->SaveFile( stream, wxBITMAP_TYPE_PNG );
2207 
2208     // Write binary data in hexadecimal form (ASCII)
2209     wxStreamBuffer* buffer = stream.GetOutputStreamBuffer();
2210     char*           begin  = (char*) buffer->GetBufferStart();
2211 
2212     for( int ii = 0; begin < buffer->GetBufferEnd(); begin++, ii++ )
2213     {
2214         if( ii >= 32 )
2215         {
2216             ii = 0;
2217 
2218             m_out->Print( 0, "\n" );
2219         }
2220 
2221         m_out->Print( 0, "%2.2X ", *begin & 0xFF );
2222     }
2223 
2224     m_out->Print( 0, "\nEndData\n" );
2225     m_out->Print( 0, "$EndBitmap\n" );
2226 }
2227 
2228 
saveSheet(SCH_SHEET * aSheet)2229 void SCH_LEGACY_PLUGIN::saveSheet( SCH_SHEET* aSheet )
2230 {
2231     wxCHECK_RET( aSheet != nullptr, "SCH_SHEET* is NULL" );
2232 
2233     m_out->Print( 0, "$Sheet\n" );
2234     m_out->Print( 0, "S %-4d %-4d %-4d %-4d\n",
2235                   Iu2Mils( aSheet->GetPosition().x ),
2236                   Iu2Mils( aSheet->GetPosition().y ),
2237                   Iu2Mils( aSheet->GetSize().x ),
2238                   Iu2Mils( aSheet->GetSize().y ) );
2239 
2240     m_out->Print( 0, "U %8.8X\n", aSheet->m_Uuid.AsLegacyTimestamp() );
2241 
2242     SCH_FIELD& sheetName = aSheet->GetFields()[SHEETNAME];
2243     SCH_FIELD& fileName = aSheet->GetFields()[SHEETFILENAME];
2244 
2245     if( !sheetName.GetText().IsEmpty() )
2246         m_out->Print( 0, "F0 %s %d\n",
2247                       EscapedUTF8( sheetName.GetText() ).c_str(),
2248                       Iu2Mils( sheetName.GetTextSize().x ) );
2249 
2250     if( !fileName.GetText().IsEmpty() )
2251         m_out->Print( 0, "F1 %s %d\n",
2252                       EscapedUTF8( fileName.GetText() ).c_str(),
2253                       Iu2Mils( fileName.GetTextSize().x ) );
2254 
2255     for( const SCH_SHEET_PIN* pin : aSheet->GetPins() )
2256     {
2257         int type, side;
2258 
2259         if( pin->GetText().IsEmpty() )
2260             break;
2261 
2262         switch( pin->GetSide() )
2263         {
2264         default:
2265         case SHEET_SIDE::LEFT: side = 'L'; break;
2266         case SHEET_SIDE::RIGHT: side = 'R'; break;
2267         case SHEET_SIDE::TOP: side = 'T'; break;
2268         case SHEET_SIDE::BOTTOM: side = 'B'; break;
2269         }
2270 
2271         switch( pin->GetShape() )
2272         {
2273         default:
2274         case PINSHEETLABEL_SHAPE::PS_UNSPECIFIED: type = 'U'; break;
2275         case PINSHEETLABEL_SHAPE::PS_INPUT:       type = 'I'; break;
2276         case PINSHEETLABEL_SHAPE::PS_OUTPUT:      type = 'O'; break;
2277         case PINSHEETLABEL_SHAPE::PS_BIDI:        type = 'B'; break;
2278         case PINSHEETLABEL_SHAPE::PS_TRISTATE:    type = 'T'; break;
2279         }
2280 
2281         m_out->Print( 0, "F%d %s %c %c %-3d %-3d %-3d\n",
2282                       pin->GetNumber(),
2283                       EscapedUTF8( pin->GetText() ).c_str(),     // supplies wrapping quotes
2284                       type, side, Iu2Mils( pin->GetPosition().x ),
2285                       Iu2Mils( pin->GetPosition().y ),
2286                       Iu2Mils( pin->GetTextWidth() ) );
2287     }
2288 
2289     m_out->Print( 0, "$EndSheet\n" );
2290 }
2291 
2292 
saveJunction(SCH_JUNCTION * aJunction)2293 void SCH_LEGACY_PLUGIN::saveJunction( SCH_JUNCTION* aJunction )
2294 {
2295     wxCHECK_RET( aJunction != nullptr, "SCH_JUNCTION* is NULL" );
2296 
2297     m_out->Print( 0, "Connection ~ %-4d %-4d\n",
2298                   Iu2Mils( aJunction->GetPosition().x ),
2299                   Iu2Mils( aJunction->GetPosition().y ) );
2300 }
2301 
2302 
saveNoConnect(SCH_NO_CONNECT * aNoConnect)2303 void SCH_LEGACY_PLUGIN::saveNoConnect( SCH_NO_CONNECT* aNoConnect )
2304 {
2305     wxCHECK_RET( aNoConnect != nullptr, "SCH_NOCONNECT* is NULL" );
2306 
2307     m_out->Print( 0, "NoConn ~ %-4d %-4d\n",
2308                   Iu2Mils( aNoConnect->GetPosition().x ),
2309                   Iu2Mils( aNoConnect->GetPosition().y ) );
2310 }
2311 
2312 
saveBusEntry(SCH_BUS_ENTRY_BASE * aBusEntry)2313 void SCH_LEGACY_PLUGIN::saveBusEntry( SCH_BUS_ENTRY_BASE* aBusEntry )
2314 {
2315     wxCHECK_RET( aBusEntry != nullptr, "SCH_BUS_ENTRY_BASE* is NULL" );
2316 
2317     if( aBusEntry->GetLayer() == LAYER_WIRE )
2318         m_out->Print( 0, "Entry Wire Line\n\t%-4d %-4d %-4d %-4d\n",
2319                       Iu2Mils( aBusEntry->GetPosition().x ),
2320                       Iu2Mils( aBusEntry->GetPosition().y ),
2321                       Iu2Mils( aBusEntry->GetEnd().x ), Iu2Mils( aBusEntry->GetEnd().y ) );
2322     else
2323         m_out->Print( 0, "Entry Bus Bus\n\t%-4d %-4d %-4d %-4d\n",
2324                       Iu2Mils( aBusEntry->GetPosition().x ),
2325                       Iu2Mils( aBusEntry->GetPosition().y ),
2326                       Iu2Mils( aBusEntry->GetEnd().x ), Iu2Mils( aBusEntry->GetEnd().y ) );
2327 }
2328 
2329 
saveLine(SCH_LINE * aLine)2330 void SCH_LEGACY_PLUGIN::saveLine( SCH_LINE* aLine )
2331 {
2332     wxCHECK_RET( aLine != nullptr, "SCH_LINE* is NULL" );
2333 
2334     const char* layer = "Notes";
2335     const char* width = "Line";
2336 
2337     if( aLine->GetLayer() == LAYER_WIRE )
2338         layer = "Wire";
2339     else if( aLine->GetLayer() == LAYER_BUS )
2340         layer = "Bus";
2341 
2342     m_out->Print( 0, "Wire %s %s", layer, width );
2343 
2344     // Write line style (width, type, color) only for non default values
2345     if( aLine->IsGraphicLine() )
2346     {
2347         if( aLine->GetLineSize() != 0 )
2348             m_out->Print( 0, " %s %d", T_WIDTH, Iu2Mils( aLine->GetLineSize() ) );
2349 
2350         if( aLine->GetLineStyle() != aLine->GetDefaultStyle() )
2351             m_out->Print( 0, " %s %s", T_STYLE,
2352                           SCH_LINE::GetLineStyleName( aLine->GetLineStyle() ) );
2353 
2354         if( aLine->GetLineColor() != COLOR4D::UNSPECIFIED )
2355             m_out->Print( 0, " %s",
2356                 TO_UTF8( aLine->GetLineColor().ToColour().GetAsString( wxC2S_CSS_SYNTAX ) ) );
2357     }
2358 
2359     m_out->Print( 0, "\n" );
2360 
2361     m_out->Print( 0, "\t%-4d %-4d %-4d %-4d",
2362                   Iu2Mils( aLine->GetStartPoint().x ), Iu2Mils( aLine->GetStartPoint().y ),
2363                   Iu2Mils( aLine->GetEndPoint().x ), Iu2Mils( aLine->GetEndPoint().y ) );
2364 
2365     m_out->Print( 0, "\n");
2366 }
2367 
2368 
saveText(SCH_TEXT * aText)2369 void SCH_LEGACY_PLUGIN::saveText( SCH_TEXT* aText )
2370 {
2371     wxCHECK_RET( aText != nullptr, "SCH_TEXT* is NULL" );
2372 
2373     const char* italics  = "~";
2374     const char* textType = "Notes";
2375 
2376     if( aText->IsItalic() )
2377         italics = "Italic";
2378 
2379     wxString text = aText->GetText();
2380 
2381     SCH_LAYER_ID layer = aText->GetLayer();
2382 
2383     if( layer == LAYER_NOTES || layer == LAYER_LOCLABEL )
2384     {
2385         if( layer == LAYER_NOTES )
2386         {
2387             // For compatibility reasons, the text must be saved in only one text line
2388             // so replace all EOLs with \\n
2389             text.Replace( wxT( "\n" ), wxT( "\\n" ) );
2390 
2391             // Here we should have no CR or LF character in line
2392             // This is not always the case if a multiline text was copied (using a copy/paste
2393             // function) from a text that uses E.O.L characters that differs from the current
2394             // EOL format.  This is mainly the case under Linux using LF symbol when copying
2395             // a text from Windows (using CRLF symbol) so we must just remove the extra CR left
2396             // (or LF left under MacOSX)
2397             for( unsigned ii = 0; ii < text.Len();  )
2398             {
2399                 if( text[ii] == 0x0A || text[ii] == 0x0D )
2400                     text.erase( ii, 1 );
2401                 else
2402                     ii++;
2403             }
2404         }
2405         else
2406         {
2407             textType = "Label";
2408         }
2409 
2410         // Local labels must have their spin style inverted for left and right
2411         int spinStyle = static_cast<int>( aText->GetLabelSpinStyle() );
2412 
2413         if( spinStyle == 0 )
2414             spinStyle = 2;
2415         else if( spinStyle == 2 )
2416             spinStyle = 0;
2417 
2418         m_out->Print( 0, "Text %s %-4d %-4d %-4d %-4d %s %d\n%s\n", textType,
2419                       Iu2Mils( aText->GetPosition().x ), Iu2Mils( aText->GetPosition().y ),
2420                       spinStyle,
2421                       Iu2Mils( aText->GetTextWidth() ),
2422                       italics, Iu2Mils( aText->GetTextThickness() ), TO_UTF8( text ) );
2423     }
2424     else if( layer == LAYER_GLOBLABEL || layer == LAYER_HIERLABEL )
2425     {
2426         textType = ( layer == LAYER_GLOBLABEL ) ? "GLabel" : "HLabel";
2427 
2428         auto shapeLabelIt = sheetLabelNames.find( aText->GetShape() );
2429         wxCHECK_RET( shapeLabelIt != sheetLabelNames.end(), "Shape not found in names list" );
2430 
2431         m_out->Print( 0, "Text %s %-4d %-4d %-4d %-4d %s %s %d\n%s\n", textType,
2432                       Iu2Mils( aText->GetPosition().x ), Iu2Mils( aText->GetPosition().y ),
2433                       static_cast<int>( aText->GetLabelSpinStyle() ),
2434                       Iu2Mils( aText->GetTextWidth() ),
2435                       shapeLabelIt->second,
2436                       italics,
2437                       Iu2Mils( aText->GetTextThickness() ), TO_UTF8( text ) );
2438     }
2439 }
2440 
2441 
saveBusAlias(std::shared_ptr<BUS_ALIAS> aAlias)2442 void SCH_LEGACY_PLUGIN::saveBusAlias( std::shared_ptr<BUS_ALIAS> aAlias )
2443 {
2444     wxCHECK_RET( aAlias != nullptr, "BUS_ALIAS* is NULL" );
2445 
2446     wxString members = boost::algorithm::join( aAlias->Members(), " " );
2447 
2448     m_out->Print( 0, "BusAlias %s %s\n",
2449                   TO_UTF8( aAlias->GetName() ), TO_UTF8( members ) );
2450 }
2451 
2452 
2453 int SCH_LEGACY_PLUGIN_CACHE::s_modHash = 1;     // starts at 1 and goes up
2454 std::mutex SCH_LEGACY_PLUGIN_CACHE::s_modHashMutex;
2455 
2456 
SCH_LEGACY_PLUGIN_CACHE(const wxString & aFullPathAndFileName)2457 SCH_LEGACY_PLUGIN_CACHE::SCH_LEGACY_PLUGIN_CACHE( const wxString& aFullPathAndFileName ) :
2458     m_fileName( aFullPathAndFileName ),
2459     m_libFileName( aFullPathAndFileName ),
2460     m_isWritable( true ),
2461     m_isModified( false )
2462 {
2463     m_versionMajor = -1;
2464     m_versionMinor = -1;
2465     m_libType = SCH_LIB_TYPE::LT_EESCHEMA;
2466 }
2467 
2468 
~SCH_LEGACY_PLUGIN_CACHE()2469 SCH_LEGACY_PLUGIN_CACHE::~SCH_LEGACY_PLUGIN_CACHE()
2470 {
2471     // When the cache is destroyed, all of the alias objects on the heap should be deleted.
2472     for( auto& symbol : m_symbols )
2473         delete symbol.second;
2474 
2475     m_symbols.clear();
2476 }
2477 
2478 
2479 // If m_libFileName is a symlink follow it to the real source file
GetRealFile() const2480 wxFileName SCH_LEGACY_PLUGIN_CACHE::GetRealFile() const
2481 {
2482     wxFileName fn( m_libFileName );
2483     WX_FILENAME::ResolvePossibleSymlinks( fn );
2484     return fn;
2485 }
2486 
2487 
GetLibModificationTime()2488 wxDateTime SCH_LEGACY_PLUGIN_CACHE::GetLibModificationTime()
2489 {
2490     wxFileName fn = GetRealFile();
2491 
2492     // update the writable flag while we have a wxFileName, in a network this
2493     // is possibly quite dynamic anyway.
2494     m_isWritable = fn.IsFileWritable();
2495 
2496     return fn.GetModificationTime();
2497 }
2498 
2499 
IsFile(const wxString & aFullPathAndFileName) const2500 bool SCH_LEGACY_PLUGIN_CACHE::IsFile( const wxString& aFullPathAndFileName ) const
2501 {
2502     return m_fileName == aFullPathAndFileName;
2503 }
2504 
2505 
IsFileChanged() const2506 bool SCH_LEGACY_PLUGIN_CACHE::IsFileChanged() const
2507 {
2508     wxFileName fn = GetRealFile();
2509 
2510     if( m_fileModTime.IsValid() && fn.IsOk() && fn.FileExists() )
2511         return fn.GetModificationTime() != m_fileModTime;
2512 
2513     return false;
2514 }
2515 
2516 
removeSymbol(LIB_SYMBOL * aSymbol)2517 LIB_SYMBOL* SCH_LEGACY_PLUGIN_CACHE::removeSymbol( LIB_SYMBOL* aSymbol )
2518 {
2519     wxCHECK_MSG( aSymbol != nullptr, nullptr, "NULL pointer cannot be removed from library." );
2520 
2521     LIB_SYMBOL* firstChild = nullptr;
2522     LIB_SYMBOL_MAP::iterator it = m_symbols.find( aSymbol->GetName() );
2523 
2524     if( it == m_symbols.end() )
2525         return nullptr;
2526 
2527     // If the entry pointer doesn't match the name it is mapped to in the library, we
2528     // have done something terribly wrong.
2529     wxCHECK_MSG( *it->second == aSymbol, nullptr,
2530                  "Pointer mismatch while attempting to remove alias entry <" + aSymbol->GetName() +
2531                  "> from library cache <" + m_libFileName.GetName() + ">." );
2532 
2533     // If the symbol is a root symbol used by other symbols find the first alias that uses
2534     // the root symbol and make it the new root.
2535     if( aSymbol->IsRoot() )
2536     {
2537         for( auto entry : m_symbols )
2538         {
2539             if( entry.second->IsAlias()
2540               && entry.second->GetParent().lock() == aSymbol->SharedPtr() )
2541             {
2542                 firstChild = entry.second;
2543                 break;
2544             }
2545         }
2546 
2547         if( firstChild )
2548         {
2549             for( LIB_ITEM& drawItem : aSymbol->GetDrawItems() )
2550             {
2551                 if( drawItem.Type() == LIB_FIELD_T )
2552                 {
2553                     LIB_FIELD& field = static_cast<LIB_FIELD&>( drawItem );
2554 
2555                     if( firstChild->FindField( field.GetCanonicalName() ) )
2556                         continue;
2557                 }
2558 
2559                 LIB_ITEM* newItem = (LIB_ITEM*) drawItem.Clone();
2560                 drawItem.SetParent( firstChild );
2561                 firstChild->AddDrawItem( newItem );
2562             }
2563 
2564             // Reparent the remaining aliases.
2565             for( auto entry : m_symbols )
2566             {
2567                 if( entry.second->IsAlias()
2568                   && entry.second->GetParent().lock() == aSymbol->SharedPtr() )
2569                     entry.second->SetParent( firstChild );
2570             }
2571         }
2572     }
2573 
2574     m_symbols.erase( it );
2575     delete aSymbol;
2576     m_isModified = true;
2577     SCH_LEGACY_PLUGIN_CACHE::IncrementModifyHash();
2578     return firstChild;
2579 }
2580 
2581 
AddSymbol(const LIB_SYMBOL * aSymbol)2582 void SCH_LEGACY_PLUGIN_CACHE::AddSymbol( const LIB_SYMBOL* aSymbol )
2583 {
2584     // aSymbol is cloned in SYMBOL_LIB::AddSymbol().  The cache takes ownership of aSymbol.
2585     wxString name = aSymbol->GetName();
2586     LIB_SYMBOL_MAP::iterator it = m_symbols.find( name );
2587 
2588     if( it != m_symbols.end() )
2589     {
2590         removeSymbol( it->second );
2591     }
2592 
2593     m_symbols[ name ] = const_cast< LIB_SYMBOL* >( aSymbol );
2594     m_isModified = true;
2595     SCH_LEGACY_PLUGIN_CACHE::IncrementModifyHash();
2596 }
2597 
2598 
Load()2599 void SCH_LEGACY_PLUGIN_CACHE::Load()
2600 {
2601     if( !m_libFileName.FileExists() )
2602     {
2603         THROW_IO_ERROR( wxString::Format( _( "Library file '%s' not found." ),
2604                                           m_libFileName.GetFullPath() ) );
2605     }
2606 
2607     wxCHECK_RET( m_libFileName.IsAbsolute(),
2608                  wxString::Format( "Cannot use relative file paths in legacy plugin to "
2609                                    "open library '%s'.", m_libFileName.GetFullPath() ) );
2610 
2611     wxLogTrace( traceSchLegacyPlugin, "Loading legacy symbol file '%s'",
2612                 m_libFileName.GetFullPath() );
2613 
2614     FILE_LINE_READER reader( m_libFileName.GetFullPath() );
2615 
2616     if( !reader.ReadLine() )
2617         THROW_IO_ERROR( _( "Unexpected end of file." ) );
2618 
2619     const char* line = reader.Line();
2620 
2621     if( !strCompare( "EESchema-LIBRARY Version", line, &line ) )
2622     {
2623         // Old .sym files (which are libraries with only one symbol, used to store and reuse shapes)
2624         // EESchema-LIB Version x.x SYMBOL. They are valid files.
2625         if( !strCompare( "EESchema-LIB Version", line, &line ) )
2626             SCH_PARSE_ERROR( "file is not a valid symbol or symbol library file", reader, line );
2627     }
2628 
2629     m_versionMajor = parseInt( reader, line, &line );
2630 
2631     if( *line != '.' )
2632         SCH_PARSE_ERROR( "invalid file version formatting in header", reader, line );
2633 
2634     line++;
2635 
2636     m_versionMinor = parseInt( reader, line, &line );
2637 
2638     if( m_versionMajor < 1 || m_versionMinor < 0 || m_versionMinor > 99 )
2639         SCH_PARSE_ERROR( "invalid file version in header", reader, line );
2640 
2641     // Check if this is a symbol library which is the same as a symbol library but without
2642     // any alias, documentation, footprint filters, etc.
2643     if( strCompare( "SYMBOL", line, &line ) )
2644     {
2645         // Symbol files add date and time stamp info to the header.
2646         m_libType = SCH_LIB_TYPE::LT_SYMBOL;
2647 
2648         /// @todo Probably should check for a valid date and time stamp even though it's not used.
2649     }
2650     else
2651     {
2652         m_libType = SCH_LIB_TYPE::LT_EESCHEMA;
2653     }
2654 
2655     while( reader.ReadLine() )
2656     {
2657         line = reader.Line();
2658 
2659         if( *line == '#' || isspace( *line ) )  // Skip comments and blank lines.
2660             continue;
2661 
2662         // Headers where only supported in older library file formats.
2663         if( m_libType == SCH_LIB_TYPE::LT_EESCHEMA && strCompare( "$HEADER", line ) )
2664             loadHeader( reader );
2665 
2666         if( strCompare( "DEF", line ) )
2667         {
2668             // Read one DEF/ENDDEF symbol entry from library:
2669             LIB_SYMBOL* symbol = LoadPart( reader, m_versionMajor, m_versionMinor, &m_symbols );
2670 
2671             m_symbols[ symbol->GetName() ] = symbol;
2672         }
2673     }
2674 
2675     SCH_LEGACY_PLUGIN_CACHE::IncrementModifyHash();
2676 
2677     // Remember the file modification time of library file when the
2678     // cache snapshot was made, so that in a networked environment we will
2679     // reload the cache as needed.
2680     m_fileModTime = GetLibModificationTime();
2681 
2682     if( USE_OLD_DOC_FILE_FORMAT( m_versionMajor, m_versionMinor ) )
2683         loadDocs();
2684 }
2685 
2686 
loadDocs()2687 void SCH_LEGACY_PLUGIN_CACHE::loadDocs()
2688 {
2689     const char* line;
2690     wxString    text;
2691     wxString    aliasName;
2692     wxFileName  fn = m_libFileName;
2693     LIB_SYMBOL* symbol = nullptr;;
2694 
2695     fn.SetExt( DOC_EXT );
2696 
2697     // Not all libraries will have a document file.
2698     if( !fn.FileExists() )
2699         return;
2700 
2701     if( !fn.IsFileReadable() )
2702     {
2703         THROW_IO_ERROR( wxString::Format( _( "Insufficient permissions to read library '%s'." ),
2704                                           fn.GetFullPath() ) );
2705     }
2706 
2707     FILE_LINE_READER reader( fn.GetFullPath() );
2708 
2709     line = reader.ReadLine();
2710 
2711     if( !line )
2712         THROW_IO_ERROR( _( "symbol document library file is empty" ) );
2713 
2714     if( !strCompare( DOCFILE_IDENT, line, &line ) )
2715         SCH_PARSE_ERROR( "invalid document library file version formatting in header",
2716                          reader, line );
2717 
2718     while( reader.ReadLine() )
2719     {
2720         line = reader.Line();
2721 
2722         if( *line == '#' )    // Comment line.
2723             continue;
2724 
2725         if( !strCompare( "$CMP", line, &line ) != 0 )
2726             SCH_PARSE_ERROR( "$CMP command expected", reader, line );
2727 
2728         aliasName = wxString::FromUTF8( line );
2729         aliasName.Trim();
2730         // aliasName = EscapeString( aliasName, CTX_LIBID );
2731 
2732         LIB_SYMBOL_MAP::iterator it = m_symbols.find( aliasName );
2733 
2734         if( it == m_symbols.end() )
2735             wxLogWarning( "Symbol '%s' not found in library:\n\n"
2736                           "'%s'\n\nat line %d offset %d", aliasName, fn.GetFullPath(),
2737                           reader.LineNumber(), (int) (line - reader.Line() ) );
2738         else
2739             symbol = it->second;
2740 
2741         // Read the current alias associated doc.
2742         // if the alias does not exist, just skip the description
2743         // (Can happen if a .dcm is not synchronized with the corresponding .lib file)
2744         while( reader.ReadLine() )
2745         {
2746             line = reader.Line();
2747 
2748             if( !line )
2749                 SCH_PARSE_ERROR( "unexpected end of file", reader, line );
2750 
2751             if( strCompare( "$ENDCMP", line, &line ) )
2752                 break;
2753 
2754             text = FROM_UTF8( line + 2 );
2755             // Remove spaces at eol, and eol chars:
2756             text = text.Trim();
2757 
2758             switch( line[0] )
2759             {
2760             case 'D':
2761                 if( symbol )
2762                     symbol->SetDescription( text );
2763                 break;
2764 
2765             case 'K':
2766                 if( symbol )
2767                     symbol->SetKeyWords( text );
2768                 break;
2769 
2770             case 'F':
2771                 if( symbol )
2772                     symbol->GetFieldById( DATASHEET_FIELD )->SetText( text );
2773                 break;
2774 
2775             case 0:
2776             case '\n':
2777             case '\r':
2778             case '#':
2779                 // Empty line or commment
2780                 break;
2781 
2782             default:
2783                 SCH_PARSE_ERROR( "expected token in symbol definition", reader, line );
2784             }
2785         }
2786     }
2787 }
2788 
2789 
loadHeader(FILE_LINE_READER & aReader)2790 void SCH_LEGACY_PLUGIN_CACHE::loadHeader( FILE_LINE_READER& aReader )
2791 {
2792     const char* line = aReader.Line();
2793 
2794     wxASSERT( strCompare( "$HEADER", line, &line ) );
2795 
2796     while( aReader.ReadLine() )
2797     {
2798         line = (char*) aReader;
2799 
2800         // The time stamp saved in old library files is not used or saved in the latest
2801         // library file version.
2802         if( strCompare( "TimeStamp", line, &line ) )
2803             continue;
2804         else if( strCompare( "$ENDHEADER", line, &line ) )
2805             return;
2806     }
2807 
2808     SCH_PARSE_ERROR( "$ENDHEADER not found", aReader, line );
2809 }
2810 
2811 
LoadPart(LINE_READER & aReader,int aMajorVersion,int aMinorVersion,LIB_SYMBOL_MAP * aMap)2812 LIB_SYMBOL* SCH_LEGACY_PLUGIN_CACHE::LoadPart( LINE_READER& aReader, int aMajorVersion,
2813                                                int aMinorVersion, LIB_SYMBOL_MAP* aMap )
2814 {
2815     const char* line = aReader.Line();
2816 
2817     while( *line == '#' )
2818         aReader.ReadLine();
2819 
2820     if( !strCompare( "DEF", line, &line ) )
2821         SCH_PARSE_ERROR( "invalid symbol definition", aReader, line );
2822 
2823     long num;
2824     size_t pos = 4;                               // "DEF" plus the first space.
2825     wxString utf8Line = wxString::FromUTF8( line );
2826     wxStringTokenizer tokens( utf8Line, " \r\n\t" );
2827 
2828     if( tokens.CountTokens() < 8 )
2829         SCH_PARSE_ERROR( "invalid symbol definition", aReader, line );
2830 
2831     // Read DEF line:
2832     std::unique_ptr<LIB_SYMBOL> symbol = std::make_unique<LIB_SYMBOL>( wxEmptyString );
2833 
2834     wxString name, prefix, tmp;
2835 
2836     name = tokens.GetNextToken();
2837     pos += name.size() + 1;
2838 
2839     prefix = tokens.GetNextToken();
2840     pos += prefix.size() + 1;
2841 
2842     tmp = tokens.GetNextToken();
2843     pos += tmp.size() + 1;                        // NumOfPins, unused.
2844 
2845     tmp = tokens.GetNextToken();                  // Pin name offset.
2846 
2847     if( !tmp.ToLong( &num ) )
2848         THROW_PARSE_ERROR( "invalid pin offset", aReader.GetSource(), aReader.Line(),
2849                            aReader.LineNumber(), pos );
2850 
2851     pos += tmp.size() + 1;
2852     symbol->SetPinNameOffset( Mils2Iu( (int)num ) );
2853 
2854     tmp = tokens.GetNextToken();                  // Show pin numbers.
2855 
2856     if( !( tmp == "Y" || tmp == "N") )
2857         THROW_PARSE_ERROR( "expected Y or N", aReader.GetSource(), aReader.Line(),
2858                            aReader.LineNumber(), pos );
2859 
2860     pos += tmp.size() + 1;
2861     symbol->SetShowPinNumbers( ( tmp == "N" ) ? false : true );
2862 
2863     tmp = tokens.GetNextToken();                  // Show pin names.
2864 
2865     if( !( tmp == "Y" || tmp == "N") )
2866         THROW_PARSE_ERROR( "expected Y or N", aReader.GetSource(), aReader.Line(),
2867                            aReader.LineNumber(), pos );
2868 
2869     pos += tmp.size() + 1;
2870     symbol->SetShowPinNames( ( tmp == "N" ) ? false : true );
2871 
2872     tmp = tokens.GetNextToken();                  // Number of units.
2873 
2874     if( !tmp.ToLong( &num ) )
2875         THROW_PARSE_ERROR( "invalid unit count", aReader.GetSource(), aReader.Line(),
2876                            aReader.LineNumber(), pos );
2877 
2878     pos += tmp.size() + 1;
2879     symbol->SetUnitCount( (int)num );
2880 
2881     // Ensure m_unitCount is >= 1.  Could be read as 0 in old libraries.
2882     if( symbol->GetUnitCount() < 1 )
2883         symbol->SetUnitCount( 1 );
2884 
2885     // Copy symbol name and prefix.
2886 
2887     // The root alias is added to the alias list by SetName() which is called by SetText().
2888     if( name.IsEmpty() )
2889     {
2890         symbol->SetName( "~" );
2891     }
2892     else if( name[0] != '~' )
2893     {
2894         symbol->SetName( name );
2895     }
2896     else
2897     {
2898         symbol->SetName( name.Right( name.Length() - 1 ) );
2899         symbol->GetValueField().SetVisible( false );
2900     }
2901 
2902     // Don't set the library alias, this is determined by the symbol library table.
2903     symbol->SetLibId( LIB_ID( wxEmptyString, symbol->GetName() ) );
2904 
2905     LIB_FIELD& reference = symbol->GetReferenceField();
2906 
2907     if( prefix == "~" )
2908     {
2909         reference.Empty();
2910         reference.SetVisible( false );
2911     }
2912     else
2913     {
2914         reference.SetText( prefix );
2915     }
2916 
2917     // In version 2.2 and earlier, this parameter was a '0' which was just a place holder.
2918     // The was no concept of interchangeable multiple unit symbols.
2919     if( LIB_VERSION( aMajorVersion, aMinorVersion ) > 0
2920      && LIB_VERSION( aMajorVersion, aMinorVersion ) <= LIB_VERSION( 2, 2 ) )
2921     {
2922         // Nothing needs to be set since the default setting for symbols with multiple
2923         // units were never interchangeable.  Just parse the 0 an move on.
2924         tmp = tokens.GetNextToken();
2925         pos += tmp.size() + 1;
2926     }
2927     else
2928     {
2929         tmp = tokens.GetNextToken();
2930 
2931         if( tmp == "L" )
2932             symbol->LockUnits( true );
2933         else if( tmp == "F" || tmp == "0" )
2934             symbol->LockUnits( false );
2935         else
2936             THROW_PARSE_ERROR( "expected L, F, or 0", aReader.GetSource(), aReader.Line(),
2937                                aReader.LineNumber(), pos );
2938 
2939         pos += tmp.size() + 1;
2940     }
2941 
2942     // There is the optional power symbol flag.
2943     if( tokens.HasMoreTokens() )
2944     {
2945         tmp = tokens.GetNextToken();
2946 
2947         if( tmp == "P" )
2948             symbol->SetPower();
2949         else if( tmp == "N" )
2950             symbol->SetNormal();
2951         else
2952             THROW_PARSE_ERROR( "expected P or N", aReader.GetSource(), aReader.Line(),
2953                                aReader.LineNumber(), pos );
2954     }
2955 
2956     line = aReader.ReadLine();
2957 
2958     // Read lines until "ENDDEF" is found.
2959     while( line )
2960     {
2961         if( *line == '#' )                                  // Comment
2962             ;
2963         else if( strCompare( "Ti", line, &line ) )          // Modification date is ignored.
2964             continue;
2965         else if( strCompare( "ALIAS", line, &line ) )       // Aliases
2966             loadAliases( symbol, aReader, aMap );
2967         else if( *line == 'F' )                             // Fields
2968             loadField( symbol, aReader );
2969         else if( strCompare( "DRAW", line, &line ) )        // Drawing objects.
2970             loadDrawEntries( symbol, aReader, aMajorVersion, aMinorVersion );
2971         else if( strCompare( "$FPLIST", line, &line ) )     // Footprint filter list
2972             loadFootprintFilters( symbol, aReader );
2973         else if( strCompare( "ENDDEF", line, &line ) )      // End of symbol description
2974         {
2975             return symbol.release();
2976         }
2977 
2978         line = aReader.ReadLine();
2979     }
2980 
2981     SCH_PARSE_ERROR( "missing ENDDEF", aReader, line );
2982 }
2983 
2984 
loadAliases(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader,LIB_SYMBOL_MAP * aMap)2985 void SCH_LEGACY_PLUGIN_CACHE::loadAliases( std::unique_ptr<LIB_SYMBOL>& aSymbol,
2986                                            LINE_READER&                 aReader,
2987                                            LIB_SYMBOL_MAP*              aMap )
2988 {
2989     wxString newAliasName;
2990     const char* line = aReader.Line();
2991 
2992     wxCHECK_RET( strCompare( "ALIAS", line, &line ), "Invalid ALIAS section" );
2993 
2994     wxString utf8Line = wxString::FromUTF8( line );
2995     wxStringTokenizer tokens( utf8Line, " \r\n\t" );
2996 
2997     // Parse the ALIAS list.
2998     while( tokens.HasMoreTokens() )
2999     {
3000         newAliasName = tokens.GetNextToken();
3001 
3002         if( aMap )
3003         {
3004             LIB_SYMBOL* newSymbol = new LIB_SYMBOL( newAliasName );
3005 
3006             // Inherit the parent mandatory field attributes.
3007             for( int id = 0; id < MANDATORY_FIELDS; ++id )
3008             {
3009                 LIB_FIELD* field = newSymbol->GetFieldById( id );
3010 
3011                 // the MANDATORY_FIELDS are exactly that in RAM.
3012                 wxASSERT( field );
3013 
3014                 LIB_FIELD* parentField = aSymbol->GetFieldById( id );
3015 
3016                 wxASSERT( parentField );
3017 
3018                 *field = *parentField;
3019 
3020                 if( id == VALUE_FIELD )
3021                     field->SetText( newAliasName );
3022 
3023                 field->SetParent( newSymbol );
3024             }
3025 
3026             newSymbol->SetParent( aSymbol.get() );
3027 
3028             // This will prevent duplicate aliases.
3029             (*aMap)[ newSymbol->GetName() ] = newSymbol;
3030         }
3031     }
3032 }
3033 
3034 
loadField(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader)3035 void SCH_LEGACY_PLUGIN_CACHE::loadField( std::unique_ptr<LIB_SYMBOL>& aSymbol,
3036                                          LINE_READER&                 aReader )
3037 {
3038     const char* line = aReader.Line();
3039 
3040     wxCHECK_RET( *line == 'F', "Invalid field line" );
3041 
3042     wxString    text;
3043     int         id;
3044 
3045     if( sscanf( line + 1, "%d", &id ) != 1 || id < 0 )
3046         SCH_PARSE_ERROR( "invalid field ID", aReader, line + 1 );
3047 
3048     LIB_FIELD* field;
3049 
3050     if( id >= 0 && id < MANDATORY_FIELDS )
3051     {
3052         field = aSymbol->GetFieldById( id );
3053 
3054         // this will fire only if somebody broke a constructor or editor.
3055         // MANDATORY_FIELDS are always present in ram resident symbols, no
3056         // exceptions, and they always have their names set, even fixed fields.
3057         wxASSERT( field );
3058     }
3059     else
3060     {
3061         field = new LIB_FIELD( aSymbol.get(), id );
3062         aSymbol->AddDrawItem( field, false );
3063     }
3064 
3065     // Skip to the first double quote.
3066     while( *line != '"' && *line != 0 )
3067         line++;
3068 
3069     if( *line == 0 )
3070         SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, line );
3071 
3072     parseQuotedString( text, aReader, line, &line, true );
3073 
3074     // Doctor the *.lib file field which has a "~" in blank fields.  New saves will
3075     // not save like this.
3076     if( text.size() == 1 && text[0] == '~' )
3077         field->SetText( wxEmptyString );
3078     else
3079         field->SetText( ConvertToNewOverbarNotation( text ) );
3080 
3081     wxPoint pos;
3082 
3083     pos.x = Mils2Iu( parseInt( aReader, line, &line ) );
3084     pos.y = Mils2Iu( parseInt( aReader, line, &line ) );
3085     field->SetPosition( pos );
3086 
3087     wxSize textSize;
3088 
3089     textSize.x = textSize.y = Mils2Iu( parseInt( aReader, line, &line ) );
3090     field->SetTextSize( textSize );
3091 
3092     char textOrient = parseChar( aReader, line, &line );
3093 
3094     if( textOrient == 'H' )
3095         field->SetTextAngle( TEXT_ANGLE_HORIZ );
3096     else if( textOrient == 'V' )
3097         field->SetTextAngle( TEXT_ANGLE_VERT );
3098     else
3099         SCH_PARSE_ERROR( "invalid field text orientation parameter", aReader, line );
3100 
3101     char textVisible = parseChar( aReader, line, &line );
3102 
3103     if( textVisible == 'V' )
3104         field->SetVisible( true );
3105     else if ( textVisible == 'I' )
3106         field->SetVisible( false );
3107     else
3108         SCH_PARSE_ERROR( "invalid field text visibility parameter", aReader, line );
3109 
3110     // It may be technically correct to use the library version to determine if the field text
3111     // attributes are present.  If anyone knows if that is valid and what version that would be,
3112     // please change this to test the library version rather than an EOL or the quoted string
3113     // of the field name.
3114     if( *line != 0 && *line != '"' )
3115     {
3116         char textHJustify = parseChar( aReader, line, &line );
3117 
3118         if( textHJustify == 'C' )
3119             field->SetHorizJustify( GR_TEXT_HJUSTIFY_CENTER );
3120         else if( textHJustify == 'L' )
3121             field->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT );
3122         else if( textHJustify == 'R' )
3123             field->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT );
3124         else
3125             SCH_PARSE_ERROR( "invalid field text horizontal justification", aReader, line );
3126 
3127         wxString attributes;
3128 
3129         parseUnquotedString( attributes, aReader, line, &line );
3130 
3131         size_t attrSize = attributes.size();
3132 
3133         if( !(attrSize == 3 || attrSize == 1 ) )
3134             SCH_PARSE_ERROR( "invalid field text attributes size", aReader, line );
3135 
3136         switch( (wxChar) attributes[0] )
3137         {
3138         case 'C': field->SetVertJustify( GR_TEXT_VJUSTIFY_CENTER ); break;
3139         case 'B': field->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM ); break;
3140         case 'T': field->SetVertJustify( GR_TEXT_VJUSTIFY_TOP );    break;
3141         default:  SCH_PARSE_ERROR( "invalid field text vertical justification", aReader, line );
3142         }
3143 
3144         if( attrSize == 3 )
3145         {
3146             wxChar attr_1 = attributes[1];
3147             wxChar attr_2 = attributes[2];
3148 
3149             if( attr_1 == 'I' )        // Italic
3150                 field->SetItalic( true );
3151             else if( attr_1 != 'N' )   // No italics is default, check for error.
3152                 SCH_PARSE_ERROR( "invalid field text italic parameter", aReader, line );
3153 
3154             if ( attr_2 == 'B' )       // Bold
3155                 field->SetBold( true );
3156             else if( attr_2 != 'N' )   // No bold is default, check for error.
3157                 SCH_PARSE_ERROR( "invalid field text bold parameter", aReader, line );
3158         }
3159     }
3160 
3161     // Fields in RAM must always have names.
3162     if( id >= 0 && id < MANDATORY_FIELDS )
3163     {
3164         // Fields in RAM must always have names, because we are trying to get
3165         // less dependent on field ids and more dependent on names.
3166         // Plus assumptions are made in the field editors.
3167         field->m_name = TEMPLATE_FIELDNAME::GetDefaultFieldName( id );
3168 
3169         // Ensure the VALUE field = the symbol name (can be not the case
3170         // with malformed libraries: edited by hand, or converted from other tools)
3171         if( id == VALUE_FIELD )
3172             field->SetText( aSymbol->GetName() );
3173     }
3174     else
3175     {
3176         parseQuotedString( field->m_name, aReader, line, &line, true );  // Optional.
3177     }
3178 }
3179 
3180 
loadDrawEntries(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader,int aMajorVersion,int aMinorVersion)3181 void SCH_LEGACY_PLUGIN_CACHE::loadDrawEntries( std::unique_ptr<LIB_SYMBOL>& aSymbol,
3182                                                LINE_READER&                 aReader,
3183                                                int                          aMajorVersion,
3184                                                int                          aMinorVersion )
3185 {
3186     const char* line = aReader.Line();
3187 
3188     wxCHECK_RET( strCompare( "DRAW", line, &line ), "Invalid DRAW section" );
3189 
3190     line = aReader.ReadLine();
3191 
3192     while( line )
3193     {
3194         if( strCompare( "ENDDRAW", line, &line ) )
3195         {
3196             aSymbol->GetDrawItems().sort();
3197             return;
3198         }
3199 
3200         switch( line[0] )
3201         {
3202         case 'A':    // Arc
3203             aSymbol->AddDrawItem( loadArc( aSymbol, aReader ), false );
3204             break;
3205 
3206         case 'C':    // Circle
3207             aSymbol->AddDrawItem( loadCircle( aSymbol, aReader ), false );
3208             break;
3209 
3210         case 'T':    // Text
3211             aSymbol->AddDrawItem( loadText( aSymbol, aReader, aMajorVersion,
3212                                             aMinorVersion ), false );
3213             break;
3214 
3215         case 'S':    // Square
3216             aSymbol->AddDrawItem( loadRect( aSymbol, aReader ), false );
3217             break;
3218 
3219         case 'X':    // Pin Description
3220             aSymbol->AddDrawItem( loadPin( aSymbol, aReader ), false );
3221             break;
3222 
3223         case 'P':    // Polyline
3224             aSymbol->AddDrawItem( loadPolyLine( aSymbol, aReader ), false );
3225             break;
3226 
3227         case 'B':    // Bezier Curves
3228             aSymbol->AddDrawItem( loadBezier( aSymbol, aReader ), false );
3229             break;
3230 
3231         case '#':    // Comment
3232         case '\n':   // Empty line
3233         case '\r':
3234         case 0:
3235             break;
3236 
3237         default:
3238             SCH_PARSE_ERROR( "undefined DRAW entry", aReader, line );
3239         }
3240 
3241         line = aReader.ReadLine();
3242     }
3243 
3244     SCH_PARSE_ERROR( "File ended prematurely loading symbol draw element.", aReader, line );
3245 }
3246 
3247 
parseFillMode(LINE_READER & aReader,const char * aLine,const char ** aOutput)3248 FILL_T SCH_LEGACY_PLUGIN_CACHE::parseFillMode( LINE_READER& aReader, const char* aLine,
3249                                                const char** aOutput )
3250 {
3251     switch ( parseChar( aReader, aLine, aOutput ) )
3252     {
3253     case 'F': return FILL_T::FILLED_SHAPE;
3254     case 'f': return FILL_T::FILLED_WITH_BG_BODYCOLOR;
3255     case 'N': return FILL_T::NO_FILL;
3256     default:  SCH_PARSE_ERROR( "invalid fill type, expected f, F, or N", aReader, aLine );
3257     }
3258 
3259     // This will never be reached but quiets the compiler warnings
3260     return FILL_T::NO_FILL;
3261 }
3262 
3263 
loadArc(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader)3264 LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadArc( std::unique_ptr<LIB_SYMBOL>& aSymbol,
3265                                              LINE_READER&                 aReader )
3266 {
3267     const char* line = aReader.Line();
3268 
3269     wxCHECK_MSG( strCompare( "A", line, &line ), nullptr, "Invalid arc definition" );
3270 
3271     LIB_SHAPE* arc = new LIB_SHAPE( aSymbol.get(), SHAPE_T::ARC );
3272 
3273     wxPoint center;
3274 
3275     center.x = Mils2Iu( parseInt( aReader, line, &line ) );
3276     center.y = Mils2Iu( parseInt( aReader, line, &line ) );
3277 
3278     arc->SetPosition( center );
3279 
3280     int radius = Mils2Iu( parseInt( aReader, line, &line ) );
3281     int angle1 = parseInt( aReader, line, &line );
3282     int angle2 = parseInt( aReader, line, &line );
3283 
3284     NORMALIZE_ANGLE_POS( angle1 );
3285     NORMALIZE_ANGLE_POS( angle2 );
3286 
3287     arc->SetUnit( parseInt( aReader, line, &line ) );
3288     arc->SetConvert( parseInt( aReader, line, &line ) );
3289     arc->SetWidth( Mils2Iu( parseInt( aReader, line, &line ) ) );
3290 
3291     // Old libraries (version <= 2.2) do not have always this FILL MODE param
3292     // when fill mode is no fill (default mode).
3293     if( *line != 0 )
3294         arc->SetFillMode( parseFillMode( aReader, line, &line ) );
3295 
3296     // Actual Coordinates of arc ends are read from file
3297     if( *line != 0 )
3298     {
3299         wxPoint arcStart, arcEnd;
3300 
3301         arcStart.x = Mils2Iu( parseInt( aReader, line, &line ) );
3302         arcStart.y = Mils2Iu( parseInt( aReader, line, &line ) );
3303         arcEnd.x = Mils2Iu( parseInt( aReader, line, &line ) );
3304         arcEnd.y = Mils2Iu( parseInt( aReader, line, &line ) );
3305 
3306         arc->SetStart( arcStart );
3307         arc->SetEnd( arcEnd );
3308     }
3309     else
3310     {
3311         // Actual Coordinates of arc ends are not read from file
3312         // (old library), calculate them
3313         wxPoint arcStart( radius, 0 );
3314         wxPoint arcEnd( radius, 0 );
3315 
3316         RotatePoint( &arcStart.x, &arcStart.y, -angle1 );
3317         arcStart += arc->GetCenter();
3318         arc->SetStart( arcStart );
3319         RotatePoint( &arcEnd.x, &arcEnd.y, -angle2 );
3320         arcEnd += arc->GetCenter();
3321         arc->SetEnd( arcEnd );
3322     }
3323 
3324     /**
3325      * This accounts for an oddity in the old library format, where the symbol is overdefined.
3326      * The previous draw (based on wxwidgets) used start point and end point and always drew
3327      * counter-clockwise.  The new GAL draw takes center, radius and start/end angles.  All of
3328      * these points were stored in the file, so we need to mimic the swapping of start/end
3329      * points rather than using the stored angles in order to properly map edge cases.
3330      */
3331     if( !TRANSFORM().MapAngles( &angle1, &angle2 ) )
3332     {
3333         wxPoint temp = arc->GetStart();
3334         arc->SetStart( arc->GetEnd() );
3335         arc->SetEnd( temp );
3336     }
3337 
3338     return arc;
3339 }
3340 
3341 
loadCircle(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader)3342 LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadCircle( std::unique_ptr<LIB_SYMBOL>& aSymbol,
3343                                                 LINE_READER&                 aReader )
3344 {
3345     const char* line = aReader.Line();
3346 
3347     wxCHECK_MSG( strCompare( "C", line, &line ), nullptr, "Invalid circle definition" );
3348 
3349     LIB_SHAPE* circle = new LIB_SHAPE( aSymbol.get(), SHAPE_T::CIRCLE );
3350 
3351     wxPoint center;
3352 
3353     center.x = Mils2Iu( parseInt( aReader, line, &line ) );
3354     center.y = Mils2Iu( parseInt( aReader, line, &line ) );
3355 
3356     int radius = Mils2Iu( parseInt( aReader, line, &line ) );
3357 
3358     circle->SetStart( center );
3359     circle->SetEnd( wxPoint( center.x + radius, center.y ) );
3360     circle->SetUnit( parseInt( aReader, line, &line ) );
3361     circle->SetConvert( parseInt( aReader, line, &line ) );
3362     circle->SetWidth( Mils2Iu( parseInt( aReader, line, &line ) ) );
3363 
3364     if( *line != 0 )
3365         circle->SetFillMode( parseFillMode( aReader, line, &line ) );
3366 
3367     return circle;
3368 }
3369 
3370 
loadText(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader,int aMajorVersion,int aMinorVersion)3371 LIB_TEXT* SCH_LEGACY_PLUGIN_CACHE::loadText( std::unique_ptr<LIB_SYMBOL>& aSymbol,
3372                                              LINE_READER&                 aReader,
3373                                              int                          aMajorVersion,
3374                                              int                          aMinorVersion )
3375 {
3376     const char* line = aReader.Line();
3377 
3378     wxCHECK_MSG( strCompare( "T", line, &line ), nullptr, "Invalid LIB_TEXT definition" );
3379 
3380     LIB_TEXT* text = new LIB_TEXT( aSymbol.get() );
3381 
3382     text->SetTextAngle( (double) parseInt( aReader, line, &line ) );
3383 
3384     wxPoint center;
3385 
3386     center.x = Mils2Iu( parseInt( aReader, line, &line ) );
3387     center.y = Mils2Iu( parseInt( aReader, line, &line ) );
3388     text->SetPosition( center );
3389 
3390     wxSize size;
3391 
3392     size.x = size.y = Mils2Iu( parseInt( aReader, line, &line ) );
3393     text->SetTextSize( size );
3394     text->SetVisible( !parseInt( aReader, line, &line ) );
3395     text->SetUnit( parseInt( aReader, line, &line ) );
3396     text->SetConvert( parseInt( aReader, line, &line ) );
3397 
3398     wxString str;
3399 
3400     // If quoted string loading fails, load as not quoted string.
3401     if( *line == '"' )
3402     {
3403         parseQuotedString( str, aReader, line, &line );
3404 
3405         str = ConvertToNewOverbarNotation( str );
3406     }
3407     else
3408     {
3409         parseUnquotedString( str, aReader, line, &line );
3410 
3411         // In old libs, "spaces" are replaced by '~' in unquoted strings:
3412         str.Replace( "~", " " );
3413     }
3414 
3415     if( !str.IsEmpty() )
3416     {
3417         // convert two apostrophes back to double quote
3418         str.Replace( "''", "\"" );
3419     }
3420 
3421     text->SetText( str );
3422 
3423     // Here things are murky and not well defined.  At some point it appears the format
3424     // was changed to add text properties.  However rather than add the token to the end of
3425     // the text definition, it was added after the string and no mention if the file
3426     // verion was bumped or not so this code make break on very old symbol libraries.
3427     //
3428     // Update: apparently even in the latest version this can be different so added a test
3429     //         for end of line before checking for the text properties.
3430     if( LIB_VERSION( aMajorVersion, aMinorVersion ) > 0
3431      && LIB_VERSION( aMajorVersion, aMinorVersion ) > LIB_VERSION( 2, 0 ) && !is_eol( *line ) )
3432     {
3433         if( strCompare( "Italic", line, &line ) )
3434             text->SetItalic( true );
3435         else if( !strCompare( "Normal", line, &line ) )
3436             SCH_PARSE_ERROR( "invalid text stype, expected 'Normal' or 'Italic'", aReader, line );
3437 
3438         if( parseInt( aReader, line, &line ) > 0 )
3439             text->SetBold( true );
3440 
3441         // Some old libaries version > 2.0 do not have these options for text justification:
3442         if( !is_eol( *line ) )
3443         {
3444             switch( parseChar( aReader, line, &line ) )
3445             {
3446             case 'L': text->SetHorizJustify( GR_TEXT_HJUSTIFY_LEFT );   break;
3447             case 'C': text->SetHorizJustify( GR_TEXT_HJUSTIFY_CENTER ); break;
3448             case 'R': text->SetHorizJustify( GR_TEXT_HJUSTIFY_RIGHT );  break;
3449             default: SCH_PARSE_ERROR( "invalid horizontal text justication; expected L, C, or R",
3450                                       aReader, line );
3451             }
3452 
3453             switch( parseChar( aReader, line, &line ) )
3454             {
3455             case 'T': text->SetVertJustify( GR_TEXT_VJUSTIFY_TOP );    break;
3456             case 'C': text->SetVertJustify( GR_TEXT_VJUSTIFY_CENTER ); break;
3457             case 'B': text->SetVertJustify( GR_TEXT_VJUSTIFY_BOTTOM ); break;
3458             default: SCH_PARSE_ERROR( "invalid vertical text justication; expected T, C, or B",
3459                                       aReader, line );
3460             }
3461         }
3462     }
3463 
3464     return text;
3465 }
3466 
3467 
loadRect(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader)3468 LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadRect( std::unique_ptr<LIB_SYMBOL>& aSymbol,
3469                                               LINE_READER&                 aReader )
3470 {
3471     const char* line = aReader.Line();
3472 
3473     wxCHECK_MSG( strCompare( "S", line, &line ), nullptr, "Invalid rectangle definition" );
3474 
3475     LIB_SHAPE* rectangle = new LIB_SHAPE( aSymbol.get(), SHAPE_T::RECT );
3476 
3477     wxPoint pos;
3478 
3479     pos.x = Mils2Iu( parseInt( aReader, line, &line ) );
3480     pos.y = Mils2Iu( parseInt( aReader, line, &line ) );
3481     rectangle->SetPosition( pos );
3482 
3483     wxPoint end;
3484 
3485     end.x = Mils2Iu( parseInt( aReader, line, &line ) );
3486     end.y = Mils2Iu( parseInt( aReader, line, &line ) );
3487     rectangle->SetEnd( end );
3488 
3489     rectangle->SetUnit( parseInt( aReader, line, &line ) );
3490     rectangle->SetConvert( parseInt( aReader, line, &line ) );
3491     rectangle->SetWidth( Mils2Iu( parseInt( aReader, line, &line ) ) );
3492 
3493     if( *line != 0 )
3494         rectangle->SetFillMode( parseFillMode( aReader, line, &line ) );
3495 
3496     return rectangle;
3497 }
3498 
3499 
loadPin(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader)3500 LIB_PIN* SCH_LEGACY_PLUGIN_CACHE::loadPin( std::unique_ptr<LIB_SYMBOL>& aSymbol,
3501                                            LINE_READER&                 aReader )
3502 {
3503     const char* line = aReader.Line();
3504 
3505     wxCHECK_MSG( strCompare( "X", line, &line ), nullptr, "Invalid LIB_PIN definition" );
3506 
3507     wxString name;
3508     wxString number;
3509 
3510     size_t pos = 2;                               // "X" plus ' ' space character.
3511     wxString tmp;
3512     wxString utf8Line = wxString::FromUTF8( line );
3513     wxStringTokenizer tokens( utf8Line, " \r\n\t" );
3514 
3515     if( tokens.CountTokens() < 11 )
3516         SCH_PARSE_ERROR( "invalid pin definition", aReader, line );
3517 
3518     tmp = tokens.GetNextToken();
3519     name = tmp;
3520     pos += tmp.size() + 1;
3521 
3522     tmp = tokens.GetNextToken();
3523     number = tmp ;
3524     pos += tmp.size() + 1;
3525 
3526     long num;
3527     wxPoint position;
3528 
3529     tmp = tokens.GetNextToken();
3530 
3531     if( !tmp.ToLong( &num ) )
3532         THROW_PARSE_ERROR( "invalid pin X coordinate", aReader.GetSource(), aReader.Line(),
3533                            aReader.LineNumber(), pos );
3534 
3535     pos += tmp.size() + 1;
3536     position.x = Mils2Iu( (int) num );
3537 
3538     tmp = tokens.GetNextToken();
3539 
3540     if( !tmp.ToLong( &num ) )
3541         THROW_PARSE_ERROR( "invalid pin Y coordinate", aReader.GetSource(), aReader.Line(),
3542                            aReader.LineNumber(), pos );
3543 
3544     pos += tmp.size() + 1;
3545     position.y = Mils2Iu( (int) num );
3546 
3547     tmp = tokens.GetNextToken();
3548 
3549     if( !tmp.ToLong( &num ) )
3550         THROW_PARSE_ERROR( "invalid pin length", aReader.GetSource(), aReader.Line(),
3551                            aReader.LineNumber(), pos );
3552 
3553     pos += tmp.size() + 1;
3554     int length = Mils2Iu( (int) num );
3555 
3556 
3557     tmp = tokens.GetNextToken();
3558 
3559     if( tmp.size() > 1 )
3560         THROW_PARSE_ERROR( "invalid pin orientation", aReader.GetSource(), aReader.Line(),
3561                            aReader.LineNumber(), pos );
3562 
3563     pos += tmp.size() + 1;
3564     int orientation = tmp[0];
3565 
3566     tmp = tokens.GetNextToken();
3567 
3568     if( !tmp.ToLong( &num ) )
3569         THROW_PARSE_ERROR( "invalid pin number text size", aReader.GetSource(), aReader.Line(),
3570                            aReader.LineNumber(), pos );
3571 
3572     pos += tmp.size() + 1;
3573     int numberTextSize = Mils2Iu( (int) num );
3574 
3575     tmp = tokens.GetNextToken();
3576 
3577     if( !tmp.ToLong( &num ) )
3578         THROW_PARSE_ERROR( "invalid pin name text size", aReader.GetSource(), aReader.Line(),
3579                            aReader.LineNumber(), pos );
3580 
3581     pos += tmp.size() + 1;
3582     int nameTextSize = Mils2Iu( (int) num );
3583 
3584     tmp = tokens.GetNextToken();
3585 
3586     if( !tmp.ToLong( &num ) )
3587         THROW_PARSE_ERROR( "invalid pin unit", aReader.GetSource(), aReader.Line(),
3588                            aReader.LineNumber(), pos );
3589 
3590     pos += tmp.size() + 1;
3591     int unit = (int) num;
3592 
3593     tmp = tokens.GetNextToken();
3594 
3595     if( !tmp.ToLong( &num ) )
3596         THROW_PARSE_ERROR( "invalid pin alternate body type", aReader.GetSource(), aReader.Line(),
3597                            aReader.LineNumber(), pos );
3598 
3599     pos += tmp.size() + 1;
3600     int convert = (int) num;
3601 
3602     tmp = tokens.GetNextToken();
3603 
3604     if( tmp.size() != 1 )
3605         THROW_PARSE_ERROR( "invalid pin type", aReader.GetSource(), aReader.Line(),
3606                            aReader.LineNumber(), pos );
3607 
3608     pos += tmp.size() + 1;
3609     char type = tmp[0];
3610     ELECTRICAL_PINTYPE pinType;
3611 
3612     switch( type )
3613     {
3614     case 'I': pinType = ELECTRICAL_PINTYPE::PT_INPUT;         break;
3615     case 'O': pinType = ELECTRICAL_PINTYPE::PT_OUTPUT;        break;
3616     case 'B': pinType = ELECTRICAL_PINTYPE::PT_BIDI;          break;
3617     case 'T': pinType = ELECTRICAL_PINTYPE::PT_TRISTATE;      break;
3618     case 'P': pinType = ELECTRICAL_PINTYPE::PT_PASSIVE;       break;
3619     case 'U': pinType = ELECTRICAL_PINTYPE::PT_UNSPECIFIED;   break;
3620     case 'W': pinType = ELECTRICAL_PINTYPE::PT_POWER_IN;      break;
3621     case 'w': pinType = ELECTRICAL_PINTYPE::PT_POWER_OUT;     break;
3622     case 'C': pinType = ELECTRICAL_PINTYPE::PT_OPENCOLLECTOR; break;
3623     case 'E': pinType = ELECTRICAL_PINTYPE::PT_OPENEMITTER;   break;
3624     case 'N': pinType = ELECTRICAL_PINTYPE::PT_NC;            break;
3625     default:
3626         THROW_PARSE_ERROR( "unknown pin type", aReader.GetSource(), aReader.Line(),
3627                 aReader.LineNumber(), pos );
3628     }
3629 
3630 
3631     LIB_PIN* pin = new LIB_PIN( aSymbol.get(),
3632                                 ConvertToNewOverbarNotation( name ),
3633                                 ConvertToNewOverbarNotation( number ),
3634                                 orientation,
3635                                 pinType,
3636                                 length,
3637                                 nameTextSize,
3638                                 numberTextSize,
3639                                 convert,
3640                                 position,
3641                                 unit );
3642 
3643     // Optional
3644     if( tokens.HasMoreTokens() )       /* Special Symbol defined */
3645     {
3646         tmp = tokens.GetNextToken();
3647 
3648         enum
3649         {
3650             INVERTED        = 1 << 0,
3651             CLOCK           = 1 << 1,
3652             LOWLEVEL_IN     = 1 << 2,
3653             LOWLEVEL_OUT    = 1 << 3,
3654             FALLING_EDGE    = 1 << 4,
3655             NONLOGIC        = 1 << 5
3656         };
3657 
3658         int flags = 0;
3659 
3660         for( int j = tmp.size(); j > 0; )
3661         {
3662             switch( tmp[--j].GetValue() )
3663             {
3664             case '~': break;
3665             case 'N': pin->SetVisible( false ); break;
3666             case 'I': flags |= INVERTED;        break;
3667             case 'C': flags |= CLOCK;           break;
3668             case 'L': flags |= LOWLEVEL_IN;     break;
3669             case 'V': flags |= LOWLEVEL_OUT;    break;
3670             case 'F': flags |= FALLING_EDGE;    break;
3671             case 'X': flags |= NONLOGIC;        break;
3672             default: THROW_PARSE_ERROR( "invalid pin attribut", aReader.GetSource(),
3673                                         aReader.Line(), aReader.LineNumber(), pos );
3674             }
3675 
3676             pos += 1;
3677         }
3678 
3679         switch( flags )
3680         {
3681         case 0:                   pin->SetShape( GRAPHIC_PINSHAPE::LINE );               break;
3682         case INVERTED:            pin->SetShape( GRAPHIC_PINSHAPE::INVERTED );           break;
3683         case CLOCK:               pin->SetShape( GRAPHIC_PINSHAPE::CLOCK );              break;
3684         case INVERTED | CLOCK:    pin->SetShape( GRAPHIC_PINSHAPE::INVERTED_CLOCK );     break;
3685         case LOWLEVEL_IN:         pin->SetShape( GRAPHIC_PINSHAPE::INPUT_LOW );          break;
3686         case LOWLEVEL_IN | CLOCK: pin->SetShape( GRAPHIC_PINSHAPE::CLOCK_LOW );          break;
3687         case LOWLEVEL_OUT:        pin->SetShape( GRAPHIC_PINSHAPE::OUTPUT_LOW );         break;
3688         case FALLING_EDGE:        pin->SetShape( GRAPHIC_PINSHAPE::FALLING_EDGE_CLOCK ); break;
3689         case NONLOGIC:            pin->SetShape( GRAPHIC_PINSHAPE::NONLOGIC );           break;
3690         default:
3691             SCH_PARSE_ERROR( "pin attributes do not define a valid pin shape", aReader, line );
3692         }
3693     }
3694 
3695     return pin;
3696 }
3697 
3698 
loadPolyLine(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader)3699 LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadPolyLine( std::unique_ptr<LIB_SYMBOL>& aSymbol,
3700                                                    LINE_READER&                 aReader )
3701 {
3702     const char* line = aReader.Line();
3703 
3704     wxCHECK_MSG( strCompare( "P", line, &line ), nullptr, "Invalid poly definition" );
3705 
3706     LIB_SHAPE* polyLine = new LIB_SHAPE( aSymbol.get(), SHAPE_T::POLY );
3707 
3708     int points = parseInt( aReader, line, &line );
3709     polyLine->SetUnit( parseInt( aReader, line, &line ) );
3710     polyLine->SetConvert( parseInt( aReader, line, &line ) );
3711     polyLine->SetWidth( Mils2Iu( parseInt( aReader, line, &line ) ) );
3712 
3713     wxPoint pt;
3714 
3715     for( int i = 0; i < points; i++ )
3716     {
3717         pt.x = Mils2Iu( parseInt( aReader, line, &line ) );
3718         pt.y = Mils2Iu( parseInt( aReader, line, &line ) );
3719         polyLine->AddPoint( pt );
3720     }
3721 
3722     if( *line != 0 )
3723         polyLine->SetFillMode( parseFillMode( aReader, line, &line ) );
3724 
3725     return polyLine;
3726 }
3727 
3728 
loadBezier(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader)3729 LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadBezier( std::unique_ptr<LIB_SYMBOL>& aSymbol,
3730                                                 LINE_READER&                 aReader )
3731 {
3732     const char* line = aReader.Line();
3733 
3734     wxCHECK_MSG( strCompare( "B", line, &line ), nullptr, "Invalid Bezier definition" );
3735 
3736     int points = parseInt( aReader, line, &line );
3737 
3738     wxCHECK_MSG( points == 4, NULL, "Invalid Bezier curve definition" );
3739 
3740     LIB_SHAPE* bezier = new LIB_SHAPE( aSymbol.get(), SHAPE_T::BEZIER );
3741 
3742     bezier->SetUnit( parseInt( aReader, line, &line ) );
3743     bezier->SetConvert( parseInt( aReader, line, &line ) );
3744     bezier->SetWidth( Mils2Iu( parseInt( aReader, line, &line ) ) );
3745 
3746     bezier->SetStart( wxPoint( Mils2Iu( parseInt( aReader, line, &line ) ),
3747                                Mils2Iu( parseInt( aReader, line, &line ) ) ) );
3748 
3749     bezier->SetBezierC1( wxPoint( Mils2Iu( parseInt( aReader, line, &line ) ),
3750                                   Mils2Iu( parseInt( aReader, line, &line ) ) ) );
3751 
3752     bezier->SetBezierC2( wxPoint( Mils2Iu( parseInt( aReader, line, &line ) ),
3753                                   Mils2Iu( parseInt( aReader, line, &line ) ) ) );
3754 
3755     bezier->SetEnd( wxPoint( Mils2Iu( parseInt( aReader, line, &line ) ),
3756                              Mils2Iu( parseInt( aReader, line, &line ) ) ) );
3757 
3758     bezier->RebuildBezierToSegmentsPointsList( bezier->GetWidth() );
3759 
3760     if( *line != 0 )
3761         bezier->SetFillMode( parseFillMode( aReader, line, &line ) );
3762 
3763     return bezier;
3764 }
3765 
3766 
loadFootprintFilters(std::unique_ptr<LIB_SYMBOL> & aSymbol,LINE_READER & aReader)3767 void SCH_LEGACY_PLUGIN_CACHE::loadFootprintFilters( std::unique_ptr<LIB_SYMBOL>& aSymbol,
3768                                                     LINE_READER&                 aReader )
3769 {
3770     const char* line = aReader.Line();
3771 
3772     wxCHECK_RET( strCompare( "$FPLIST", line, &line ), "Invalid footprint filter list" );
3773 
3774     line = aReader.ReadLine();
3775 
3776     wxArrayString footprintFilters;
3777 
3778     while( line )
3779     {
3780         if( strCompare( "$ENDFPLIST", line, &line ) )
3781         {
3782             aSymbol->SetFPFilters( footprintFilters );
3783             return;
3784         }
3785 
3786         wxString footprint;
3787 
3788         parseUnquotedString( footprint, aReader, line, &line );
3789         footprintFilters.Add( footprint );
3790         line = aReader.ReadLine();
3791     }
3792 
3793     SCH_PARSE_ERROR( "File ended prematurely while loading footprint filters.", aReader, line );
3794 }
3795 
3796 
Save(bool aSaveDocFile)3797 void SCH_LEGACY_PLUGIN_CACHE::Save( bool aSaveDocFile )
3798 {
3799     if( !m_isModified )
3800         return;
3801 
3802     // Write through symlinks, don't replace them
3803     wxFileName fn = GetRealFile();
3804 
3805     auto formatter = std::make_unique<FILE_OUTPUTFORMATTER>( fn.GetFullPath() );
3806     formatter->Print( 0, "%s %d.%d\n", LIBFILE_IDENT, LIB_VERSION_MAJOR, LIB_VERSION_MINOR );
3807     formatter->Print( 0, "#encoding utf-8\n");
3808 
3809     for( LIB_SYMBOL_MAP::iterator it = m_symbols.begin();  it != m_symbols.end();  it++ )
3810     {
3811         if( !it->second->IsRoot() )
3812             continue;
3813 
3814         SaveSymbol( it->second, *formatter.get(), &m_symbols );
3815     }
3816 
3817     formatter->Print( 0, "#\n#End Library\n" );
3818     formatter.reset();
3819 
3820     m_fileModTime = fn.GetModificationTime();
3821     m_isModified = false;
3822 
3823     if( aSaveDocFile )
3824         saveDocFile();
3825 }
3826 
3827 
SaveSymbol(LIB_SYMBOL * aSymbol,OUTPUTFORMATTER & aFormatter,LIB_SYMBOL_MAP * aMap)3828 void SCH_LEGACY_PLUGIN_CACHE::SaveSymbol( LIB_SYMBOL* aSymbol, OUTPUTFORMATTER& aFormatter,
3829                                           LIB_SYMBOL_MAP* aMap )
3830 {
3831     /*
3832      * NB:
3833      * Some of the rescue code still uses the legacy format as an intermediary, so we have
3834      * to keep this code.
3835      */
3836 
3837     wxCHECK_RET( aSymbol && aSymbol->IsRoot(), "Invalid LIB_SYMBOL pointer." );
3838 
3839     // LIB_ALIAS objects are deprecated but we still need to gather up the derived symbols
3840     // and save their names for the old file format.
3841     wxArrayString aliasNames;
3842 
3843     if( aMap )
3844     {
3845         for( auto entry : *aMap )
3846         {
3847             LIB_SYMBOL* symbol = entry.second;
3848 
3849             if( symbol->IsAlias() && symbol->GetParent().lock() == aSymbol->SharedPtr() )
3850                 aliasNames.Add( symbol->GetName() );
3851         }
3852     }
3853 
3854     LIB_FIELD&  value = aSymbol->GetValueField();
3855 
3856     // First line: it s a comment (symbol name for readers)
3857     aFormatter.Print( 0, "#\n# %s\n#\n", TO_UTF8( value.GetText() ) );
3858 
3859     // Save data
3860     aFormatter.Print( 0, "DEF" );
3861     aFormatter.Print( 0, " %s", TO_UTF8( value.GetText() ) );
3862 
3863     LIB_FIELD& reference = aSymbol->GetReferenceField();
3864 
3865     if( !reference.GetText().IsEmpty() )
3866         aFormatter.Print( 0, " %s", TO_UTF8( reference.GetText() ) );
3867     else
3868         aFormatter.Print( 0, " ~" );
3869 
3870     aFormatter.Print( 0, " %d %d %c %c %d %c %c\n",
3871                       0, Iu2Mils( aSymbol->GetPinNameOffset() ),
3872                       aSymbol->ShowPinNumbers() ? 'Y' : 'N',
3873                       aSymbol->ShowPinNames() ? 'Y' : 'N',
3874                       aSymbol->GetUnitCount(), aSymbol->UnitsLocked() ? 'L' : 'F',
3875                       aSymbol->IsPower() ? 'P' : 'N' );
3876 
3877     timestamp_t dateModified = aSymbol->GetLastModDate();
3878 
3879     if( dateModified != 0 )
3880     {
3881         int sec  = dateModified & 63;
3882         int min  = ( dateModified >> 6 ) & 63;
3883         int hour = ( dateModified >> 12 ) & 31;
3884         int day  = ( dateModified >> 17 ) & 31;
3885         int mon  = ( dateModified >> 22 ) & 15;
3886         int year = ( dateModified >> 26 ) + 1990;
3887 
3888         aFormatter.Print( 0, "Ti %d/%d/%d %d:%d:%d\n", year, mon, day, hour, min, sec );
3889     }
3890 
3891     std::vector<LIB_FIELD*> fields;
3892     aSymbol->GetFields( fields );
3893 
3894     // Mandatory fields:
3895     // may have their own save policy so there is a separate loop for them.
3896     // Empty fields are saved, because the user may have set visibility,
3897     // size and orientation
3898     for( int i = 0; i < MANDATORY_FIELDS; ++i )
3899         saveField( fields[i], aFormatter );
3900 
3901     // User defined fields:
3902     // may have their own save policy so there is a separate loop for them.
3903     int fieldId = MANDATORY_FIELDS;     // really wish this would go away.
3904 
3905     for( unsigned i = MANDATORY_FIELDS; i < fields.size(); ++i )
3906     {
3907         // There is no need to save empty fields, i.e. no reason to preserve field
3908         // names now that fields names come in dynamically through the template
3909         // fieldnames.
3910         if( !fields[i]->GetText().IsEmpty() )
3911         {
3912             fields[i]->SetId( fieldId++ );
3913             saveField( fields[i], aFormatter );
3914         }
3915     }
3916 
3917     // Save the alias list: a line starting by "ALIAS".
3918     if( !aliasNames.IsEmpty() )
3919     {
3920         aFormatter.Print( 0, "ALIAS" );
3921 
3922         for( unsigned i = 0; i < aliasNames.GetCount(); i++ )
3923             aFormatter.Print( 0, " %s", TO_UTF8( aliasNames[i] ) );
3924 
3925         aFormatter.Print( 0, "\n" );
3926     }
3927 
3928     wxArrayString footprints = aSymbol->GetFPFilters();
3929 
3930     // Write the footprint filter list
3931     if( footprints.GetCount() != 0 )
3932     {
3933         aFormatter.Print( 0, "$FPLIST\n" );
3934 
3935         for( unsigned i = 0; i < footprints.GetCount(); i++ )
3936             aFormatter.Print( 0, " %s\n", TO_UTF8( footprints[i] ) );
3937 
3938         aFormatter.Print( 0, "$ENDFPLIST\n" );
3939     }
3940 
3941     // Save graphics items (including pins)
3942     if( !aSymbol->GetDrawItems().empty() )
3943     {
3944         // Sort the draw items in order to editing a file editing by hand.
3945         aSymbol->GetDrawItems().sort();
3946 
3947         aFormatter.Print( 0, "DRAW\n" );
3948 
3949         for( LIB_ITEM& item : aSymbol->GetDrawItems() )
3950         {
3951             switch( item.Type() )
3952             {
3953             default:
3954             case LIB_FIELD_T:     /* Fields have already been saved above. */  break;
3955             case LIB_PIN_T:       savePin( (LIB_PIN* ) &item, aFormatter );    break;
3956             case LIB_TEXT_T:      saveText( ( LIB_TEXT* ) &item, aFormatter ); break;
3957             case LIB_SHAPE_T:
3958             {
3959                 LIB_SHAPE& shape = static_cast<LIB_SHAPE&>( item );
3960 
3961                 switch( shape.GetShape() )
3962                 {
3963                 case SHAPE_T::ARC:    saveArc( &shape, aFormatter );           break;
3964                 case SHAPE_T::BEZIER: saveBezier( &shape, aFormatter );        break;
3965                 case SHAPE_T::CIRCLE: saveCircle( &shape, aFormatter );        break;
3966                 case SHAPE_T::POLY:   savePolyLine( &shape, aFormatter );      break;
3967                 case SHAPE_T::RECT:   saveRectangle( &shape, aFormatter );     break;
3968                 default:                                                       break;
3969                 }
3970             }
3971             }
3972         }
3973 
3974         aFormatter.Print( 0, "ENDDRAW\n" );
3975     }
3976 
3977     aFormatter.Print( 0, "ENDDEF\n" );
3978 }
3979 
3980 
saveArc(LIB_SHAPE * aArc,OUTPUTFORMATTER & aFormatter)3981 void SCH_LEGACY_PLUGIN_CACHE::saveArc( LIB_SHAPE* aArc, OUTPUTFORMATTER& aFormatter )
3982 {
3983     wxCHECK_RET( aArc && aArc->GetShape() == SHAPE_T::ARC, "Invalid ARC object." );
3984 
3985     int x1;
3986     int x2;
3987 
3988     aArc->CalcArcAngles( x1, x2 );
3989 
3990     if( x1 > 1800 )
3991         x1 -= 3600;
3992 
3993     if( x2 > 1800 )
3994         x2 -= 3600;
3995 
3996     aFormatter.Print( 0, "A %d %d %d %d %d %d %d %d %c %d %d %d %d\n",
3997                       Iu2Mils( aArc->GetPosition().x ),
3998                       Iu2Mils( aArc->GetPosition().y ),
3999                       Iu2Mils( aArc->GetRadius() ),
4000                       x1,
4001                       x2,
4002                       aArc->GetUnit(),
4003                       aArc->GetConvert(),
4004                       Iu2Mils( aArc->GetWidth() ),
4005                       fill_tab[ static_cast<int>( aArc->GetFillType() ) - 1 ],
4006                       Iu2Mils( aArc->GetStart().x ),
4007                       Iu2Mils( aArc->GetStart().y ),
4008                       Iu2Mils( aArc->GetEnd().x ),
4009                       Iu2Mils( aArc->GetEnd().y ) );
4010 }
4011 
4012 
saveBezier(LIB_SHAPE * aBezier,OUTPUTFORMATTER & aFormatter)4013 void SCH_LEGACY_PLUGIN_CACHE::saveBezier( LIB_SHAPE* aBezier, OUTPUTFORMATTER& aFormatter )
4014 {
4015     wxCHECK_RET( aBezier && aBezier->GetShape() == SHAPE_T::BEZIER, "Invalid BEZIER object." );
4016 
4017     aFormatter.Print( 0, "B %u %d %d %d",
4018                       (unsigned)aBezier->GetBezierPoints().size(),
4019                       aBezier->GetUnit(),
4020                       aBezier->GetConvert(),
4021                       Iu2Mils( aBezier->GetWidth() ) );
4022 
4023     for( const wxPoint& pt : aBezier->GetBezierPoints() )
4024         aFormatter.Print( 0, " %d %d", Iu2Mils( pt.x ), Iu2Mils( pt.y ) );
4025 
4026     aFormatter.Print( 0, " %c\n", fill_tab[ static_cast<int>( aBezier->GetFillType() ) - 1 ] );
4027 }
4028 
4029 
saveCircle(LIB_SHAPE * aCircle,OUTPUTFORMATTER & aFormatter)4030 void SCH_LEGACY_PLUGIN_CACHE::saveCircle( LIB_SHAPE* aCircle, OUTPUTFORMATTER& aFormatter )
4031 {
4032     wxCHECK_RET( aCircle && aCircle->GetShape() == SHAPE_T::CIRCLE, "Invalid CIRCLE object." );
4033 
4034     aFormatter.Print( 0, "C %d %d %d %d %d %d %c\n",
4035                       Iu2Mils( aCircle->GetPosition().x ),
4036                       Iu2Mils( aCircle->GetPosition().y ),
4037                       Iu2Mils( aCircle->GetRadius() ),
4038                       aCircle->GetUnit(),
4039                       aCircle->GetConvert(),
4040                       Iu2Mils( aCircle->GetWidth() ),
4041                       fill_tab[ static_cast<int>( aCircle->GetFillType() ) - 1 ] );
4042 }
4043 
4044 
saveField(const LIB_FIELD * aField,OUTPUTFORMATTER & aFormatter)4045 void SCH_LEGACY_PLUGIN_CACHE::saveField( const LIB_FIELD* aField, OUTPUTFORMATTER& aFormatter )
4046 {
4047     wxCHECK_RET( aField && aField->Type() == LIB_FIELD_T, "Invalid LIB_FIELD object." );
4048 
4049     int      hjustify, vjustify;
4050     int      id = aField->GetId();
4051     wxString text = aField->GetText();
4052 
4053     hjustify = 'C';
4054 
4055     if( aField->GetHorizJustify() == GR_TEXT_HJUSTIFY_LEFT )
4056         hjustify = 'L';
4057     else if( aField->GetHorizJustify() == GR_TEXT_HJUSTIFY_RIGHT )
4058         hjustify = 'R';
4059 
4060     vjustify = 'C';
4061 
4062     if( aField->GetVertJustify() == GR_TEXT_VJUSTIFY_BOTTOM )
4063         vjustify = 'B';
4064     else if( aField->GetVertJustify() == GR_TEXT_VJUSTIFY_TOP )
4065         vjustify = 'T';
4066 
4067     aFormatter.Print( 0, "F%d %s %d %d %d %c %c %c %c%c%c",
4068                       id,
4069                       EscapedUTF8( text ).c_str(),       // wraps in quotes
4070                       Iu2Mils( aField->GetTextPos().x ),
4071                       Iu2Mils( aField->GetTextPos().y ),
4072                       Iu2Mils( aField->GetTextWidth() ),
4073                       aField->GetTextAngle() == 0 ? 'H' : 'V',
4074                       aField->IsVisible() ? 'V' : 'I',
4075                       hjustify, vjustify,
4076                       aField->IsItalic() ? 'I' : 'N',
4077                       aField->IsBold() ? 'B' : 'N' );
4078 
4079     /* Save field name, if necessary
4080      * Field name is saved only if it is not the default name.
4081      * Just because default name depends on the language and can change from
4082      * a country to another
4083      */
4084     wxString defName = TEMPLATE_FIELDNAME::GetDefaultFieldName( id );
4085 
4086     if( id >= MANDATORY_FIELDS && !aField->m_name.IsEmpty() && aField->m_name != defName )
4087         aFormatter.Print( 0, " %s", EscapedUTF8( aField->m_name ).c_str() );
4088 
4089     aFormatter.Print( 0, "\n" );
4090 }
4091 
4092 
savePin(const LIB_PIN * aPin,OUTPUTFORMATTER & aFormatter)4093 void SCH_LEGACY_PLUGIN_CACHE::savePin( const LIB_PIN* aPin, OUTPUTFORMATTER& aFormatter )
4094 {
4095     wxCHECK_RET( aPin && aPin->Type() == LIB_PIN_T, "Invalid LIB_PIN object." );
4096 
4097     int      Etype;
4098 
4099     switch( aPin->GetType() )
4100     {
4101     default:
4102     case ELECTRICAL_PINTYPE::PT_INPUT:         Etype = 'I'; break;
4103     case ELECTRICAL_PINTYPE::PT_OUTPUT:        Etype = 'O'; break;
4104     case ELECTRICAL_PINTYPE::PT_BIDI:          Etype = 'B'; break;
4105     case ELECTRICAL_PINTYPE::PT_TRISTATE:      Etype = 'T'; break;
4106     case ELECTRICAL_PINTYPE::PT_PASSIVE:       Etype = 'P'; break;
4107     case ELECTRICAL_PINTYPE::PT_UNSPECIFIED:   Etype = 'U'; break;
4108     case ELECTRICAL_PINTYPE::PT_POWER_IN:      Etype = 'W'; break;
4109     case ELECTRICAL_PINTYPE::PT_POWER_OUT:     Etype = 'w'; break;
4110     case ELECTRICAL_PINTYPE::PT_OPENCOLLECTOR: Etype = 'C'; break;
4111     case ELECTRICAL_PINTYPE::PT_OPENEMITTER:   Etype = 'E'; break;
4112     case ELECTRICAL_PINTYPE::PT_NC:            Etype = 'N'; break;
4113     }
4114 
4115     if( !aPin->GetName().IsEmpty() )
4116         aFormatter.Print( 0, "X %s", TO_UTF8( aPin->GetName() ) );
4117     else
4118         aFormatter.Print( 0, "X ~" );
4119 
4120     aFormatter.Print( 0, " %s %d %d %d %c %d %d %d %d %c",
4121                       aPin->GetNumber().IsEmpty() ? "~" : TO_UTF8( aPin->GetNumber() ),
4122                       Iu2Mils( aPin->GetPosition().x ),
4123                       Iu2Mils( aPin->GetPosition().y ),
4124                       Iu2Mils( (int) aPin->GetLength() ),
4125                       (int) aPin->GetOrientation(),
4126                       Iu2Mils( aPin->GetNumberTextSize() ),
4127                       Iu2Mils( aPin->GetNameTextSize() ),
4128                       aPin->GetUnit(),
4129                       aPin->GetConvert(),
4130                       Etype );
4131 
4132     if( aPin->GetShape() != GRAPHIC_PINSHAPE::LINE || !aPin->IsVisible() )
4133         aFormatter.Print( 0, " " );
4134 
4135     if( !aPin->IsVisible() )
4136         aFormatter.Print( 0, "N" );
4137 
4138     switch( aPin->GetShape() )
4139     {
4140     case GRAPHIC_PINSHAPE::LINE:                                            break;
4141     case GRAPHIC_PINSHAPE::INVERTED:           aFormatter.Print( 0, "I" );  break;
4142     case GRAPHIC_PINSHAPE::CLOCK:              aFormatter.Print( 0, "C" );  break;
4143     case GRAPHIC_PINSHAPE::INVERTED_CLOCK:     aFormatter.Print( 0, "IC" ); break;
4144     case GRAPHIC_PINSHAPE::INPUT_LOW:          aFormatter.Print( 0, "L" );  break;
4145     case GRAPHIC_PINSHAPE::CLOCK_LOW:          aFormatter.Print( 0, "CL" ); break;
4146     case GRAPHIC_PINSHAPE::OUTPUT_LOW:         aFormatter.Print( 0, "V" );  break;
4147     case GRAPHIC_PINSHAPE::FALLING_EDGE_CLOCK: aFormatter.Print( 0, "F" );  break;
4148     case GRAPHIC_PINSHAPE::NONLOGIC:           aFormatter.Print( 0, "X" );  break;
4149     default:                                   wxFAIL_MSG( "Invalid pin shape" );
4150     }
4151 
4152     aFormatter.Print( 0, "\n" );
4153 
4154     const_cast<LIB_PIN*>( aPin )->ClearFlags( IS_CHANGED );
4155 }
4156 
4157 
savePolyLine(LIB_SHAPE * aPolyLine,OUTPUTFORMATTER & aFormatter)4158 void SCH_LEGACY_PLUGIN_CACHE::savePolyLine( LIB_SHAPE* aPolyLine, OUTPUTFORMATTER& aFormatter )
4159 {
4160     wxCHECK_RET( aPolyLine && aPolyLine->GetShape() == SHAPE_T::POLY, "Invalid POLY object." );
4161 
4162     aFormatter.Print( 0, "P %d %d %d %d",
4163                       (int) aPolyLine->GetPolyShape().Outline( 0 ).GetPointCount(),
4164                       aPolyLine->GetUnit(),
4165                       aPolyLine->GetConvert(),
4166                       Iu2Mils( aPolyLine->GetWidth() ) );
4167 
4168     for( const VECTOR2I& pt : aPolyLine->GetPolyShape().Outline( 0 ).CPoints() )
4169         aFormatter.Print( 0, " %d %d", Iu2Mils( pt.x ), Iu2Mils( pt.y ) );
4170 
4171     aFormatter.Print( 0, " %c\n", fill_tab[ static_cast<int>( aPolyLine->GetFillType() ) - 1 ] );
4172 }
4173 
4174 
saveRectangle(LIB_SHAPE * aRectangle,OUTPUTFORMATTER & aFormatter)4175 void SCH_LEGACY_PLUGIN_CACHE::saveRectangle( LIB_SHAPE* aRectangle, OUTPUTFORMATTER& aFormatter )
4176 {
4177     wxCHECK_RET( aRectangle && aRectangle->GetShape() == SHAPE_T::RECT, "Invalid RECT object." );
4178 
4179     aFormatter.Print( 0, "S %d %d %d %d %d %d %d %c\n",
4180                       Iu2Mils( aRectangle->GetPosition().x ),
4181                       Iu2Mils( aRectangle->GetPosition().y ),
4182                       Iu2Mils( aRectangle->GetEnd().x ),
4183                       Iu2Mils( aRectangle->GetEnd().y ),
4184                       aRectangle->GetUnit(),
4185                       aRectangle->GetConvert(),
4186                       Iu2Mils( aRectangle->GetWidth() ),
4187                       fill_tab[ static_cast<int>( aRectangle->GetFillType() ) - 1 ] );
4188 }
4189 
4190 
saveText(const LIB_TEXT * aText,OUTPUTFORMATTER & aFormatter)4191 void SCH_LEGACY_PLUGIN_CACHE::saveText( const LIB_TEXT* aText, OUTPUTFORMATTER& aFormatter )
4192 {
4193     wxCHECK_RET( aText && aText->Type() == LIB_TEXT_T, "Invalid LIB_TEXT object." );
4194 
4195     wxString text = aText->GetText();
4196 
4197     if( text.Contains( wxT( " " ) ) || text.Contains( wxT( "~" ) ) || text.Contains( wxT( "\"" ) ) )
4198     {
4199         // convert double quote to similar-looking two apostrophes
4200         text.Replace( wxT( "\"" ), wxT( "''" ) );
4201         text = wxT( "\"" ) + text + wxT( "\"" );
4202     }
4203 
4204     aFormatter.Print( 0, "T %g %d %d %d %d %d %d %s", aText->GetTextAngle(),
4205                       Iu2Mils( aText->GetTextPos().x ), Iu2Mils( aText->GetTextPos().y ),
4206                       Iu2Mils( aText->GetTextWidth() ), !aText->IsVisible(),
4207                       aText->GetUnit(), aText->GetConvert(), TO_UTF8( text ) );
4208 
4209     aFormatter.Print( 0, " %s %d", aText->IsItalic() ? "Italic" : "Normal", aText->IsBold() );
4210 
4211     char hjustify = 'C';
4212 
4213     if( aText->GetHorizJustify() == GR_TEXT_HJUSTIFY_LEFT )
4214         hjustify = 'L';
4215     else if( aText->GetHorizJustify() == GR_TEXT_HJUSTIFY_RIGHT )
4216         hjustify = 'R';
4217 
4218     char vjustify = 'C';
4219 
4220     if( aText->GetVertJustify() == GR_TEXT_VJUSTIFY_BOTTOM )
4221         vjustify = 'B';
4222     else if( aText->GetVertJustify() == GR_TEXT_VJUSTIFY_TOP )
4223         vjustify = 'T';
4224 
4225     aFormatter.Print( 0, " %c %c\n", hjustify, vjustify );
4226 }
4227 
4228 
saveDocFile()4229 void SCH_LEGACY_PLUGIN_CACHE::saveDocFile()
4230 {
4231     /*
4232      * NB:
4233      * Some of the rescue code still uses the legacy format as an intermediary, so we have
4234      * to keep this code.
4235      */
4236 
4237     wxFileName fileName = m_libFileName;
4238 
4239     fileName.SetExt( DOC_EXT );
4240     FILE_OUTPUTFORMATTER formatter( fileName.GetFullPath() );
4241 
4242     formatter.Print( 0, "%s\n", DOCFILE_IDENT );
4243 
4244     for( LIB_SYMBOL_MAP::iterator it = m_symbols.begin();  it != m_symbols.end();  ++it )
4245     {
4246         wxString description =  it->second->GetDescription();
4247         wxString keyWords = it->second->GetKeyWords();
4248         wxString docFileName = it->second->GetDatasheetField().GetText();
4249 
4250         if( description.IsEmpty() && keyWords.IsEmpty() && docFileName.IsEmpty() )
4251             continue;
4252 
4253         formatter.Print( 0, "#\n$CMP %s\n", TO_UTF8( it->second->GetName() ) );
4254 
4255         if( !description.IsEmpty() )
4256             formatter.Print( 0, "D %s\n", TO_UTF8( description ) );
4257 
4258         if( !keyWords.IsEmpty() )
4259             formatter.Print( 0, "K %s\n", TO_UTF8( keyWords ) );
4260 
4261         if( !docFileName.IsEmpty() )
4262             formatter.Print( 0, "F %s\n", TO_UTF8( docFileName ) );
4263 
4264         formatter.Print( 0, "$ENDCMP\n" );
4265     }
4266 
4267     formatter.Print( 0, "#\n#End Doc Library\n" );
4268 }
4269 
4270 
DeleteSymbol(const wxString & aSymbolName)4271 void SCH_LEGACY_PLUGIN_CACHE::DeleteSymbol( const wxString& aSymbolName )
4272 {
4273     LIB_SYMBOL_MAP::iterator it = m_symbols.find( aSymbolName );
4274 
4275     if( it == m_symbols.end() )
4276         THROW_IO_ERROR( wxString::Format( _( "library %s does not contain a symbol named %s" ),
4277                                           m_libFileName.GetFullName(), aSymbolName ) );
4278 
4279     LIB_SYMBOL* symbol = it->second;
4280 
4281     if( symbol->IsRoot() )
4282     {
4283         LIB_SYMBOL* rootSymbol = symbol;
4284 
4285         // Remove the root symbol and all its children.
4286         m_symbols.erase( it );
4287 
4288         LIB_SYMBOL_MAP::iterator it1 = m_symbols.begin();
4289 
4290         while( it1 != m_symbols.end() )
4291         {
4292             if( it1->second->IsAlias()
4293               && it1->second->GetParent().lock() == rootSymbol->SharedPtr() )
4294             {
4295                 delete it1->second;
4296                 it1 = m_symbols.erase( it1 );
4297             }
4298             else
4299             {
4300                 it1++;
4301             }
4302         }
4303 
4304         delete rootSymbol;
4305     }
4306     else
4307     {
4308         // Just remove the alias.
4309         m_symbols.erase( it );
4310         delete symbol;
4311     }
4312 
4313     SCH_LEGACY_PLUGIN_CACHE::IncrementModifyHash();
4314     m_isModified = true;
4315 }
4316 
4317 
cacheLib(const wxString & aLibraryFileName,const PROPERTIES * aProperties)4318 void SCH_LEGACY_PLUGIN::cacheLib( const wxString& aLibraryFileName, const PROPERTIES* aProperties )
4319 {
4320     if( !m_cache || !m_cache->IsFile( aLibraryFileName ) || m_cache->IsFileChanged() )
4321     {
4322         // a spectacular episode in memory management:
4323         delete m_cache;
4324         m_cache = new SCH_LEGACY_PLUGIN_CACHE( aLibraryFileName );
4325 
4326         // Because m_cache is rebuilt, increment SYMBOL_LIBS::s_modify_generation
4327         // to modify the hash value that indicate symbol to symbol links
4328         // must be updated.
4329         SYMBOL_LIBS::IncrementModifyGeneration();
4330 
4331         if( !isBuffering( aProperties ) )
4332             m_cache->Load();
4333     }
4334 }
4335 
4336 
writeDocFile(const PROPERTIES * aProperties)4337 bool SCH_LEGACY_PLUGIN::writeDocFile( const PROPERTIES* aProperties )
4338 {
4339     std::string propName( SCH_LEGACY_PLUGIN::PropNoDocFile );
4340 
4341     if( aProperties && aProperties->find( propName ) != aProperties->end() )
4342         return false;
4343 
4344     return true;
4345 }
4346 
4347 
isBuffering(const PROPERTIES * aProperties)4348 bool SCH_LEGACY_PLUGIN::isBuffering( const PROPERTIES* aProperties )
4349 {
4350     return ( aProperties && aProperties->Exists( SCH_LEGACY_PLUGIN::PropBuffering ) );
4351 }
4352 
4353 
GetModifyHash() const4354 int SCH_LEGACY_PLUGIN::GetModifyHash() const
4355 {
4356     if( m_cache )
4357         return SCH_LEGACY_PLUGIN_CACHE::GetModifyHash();
4358 
4359     // If the cache hasn't been loaded, it hasn't been modified.
4360     return 0;
4361 }
4362 
4363 
EnumerateSymbolLib(wxArrayString & aSymbolNameList,const wxString & aLibraryPath,const PROPERTIES * aProperties)4364 void SCH_LEGACY_PLUGIN::EnumerateSymbolLib( wxArrayString&    aSymbolNameList,
4365                                             const wxString&   aLibraryPath,
4366                                             const PROPERTIES* aProperties )
4367 {
4368     LOCALE_IO   toggle;     // toggles on, then off, the C locale.
4369 
4370     bool powerSymbolsOnly = ( aProperties &&
4371                               aProperties->find( SYMBOL_LIB_TABLE::PropPowerSymsOnly ) != aProperties->end() );
4372 
4373     cacheLib( aLibraryPath, aProperties  );
4374 
4375     const LIB_SYMBOL_MAP& symbols = m_cache->m_symbols;
4376 
4377     for( LIB_SYMBOL_MAP::const_iterator it = symbols.begin();  it != symbols.end();  ++it )
4378     {
4379         if( !powerSymbolsOnly || it->second->IsPower() )
4380             aSymbolNameList.Add( it->first );
4381     }
4382 }
4383 
4384 
EnumerateSymbolLib(std::vector<LIB_SYMBOL * > & aSymbolList,const wxString & aLibraryPath,const PROPERTIES * aProperties)4385 void SCH_LEGACY_PLUGIN::EnumerateSymbolLib( std::vector<LIB_SYMBOL*>& aSymbolList,
4386                                             const wxString&   aLibraryPath,
4387                                             const PROPERTIES* aProperties )
4388 {
4389     LOCALE_IO   toggle;     // toggles on, then off, the C locale.
4390 
4391     bool powerSymbolsOnly = ( aProperties &&
4392                               aProperties->find( SYMBOL_LIB_TABLE::PropPowerSymsOnly ) != aProperties->end() );
4393 
4394     cacheLib( aLibraryPath, aProperties );
4395 
4396     const LIB_SYMBOL_MAP& symbols = m_cache->m_symbols;
4397 
4398     for( LIB_SYMBOL_MAP::const_iterator it = symbols.begin();  it != symbols.end();  ++it )
4399     {
4400         if( !powerSymbolsOnly || it->second->IsPower() )
4401             aSymbolList.push_back( it->second );
4402     }
4403 }
4404 
4405 
LoadSymbol(const wxString & aLibraryPath,const wxString & aSymbolName,const PROPERTIES * aProperties)4406 LIB_SYMBOL* SCH_LEGACY_PLUGIN::LoadSymbol( const wxString& aLibraryPath,
4407                                            const wxString& aSymbolName,
4408                                            const PROPERTIES* aProperties )
4409 {
4410     LOCALE_IO toggle;     // toggles on, then off, the C locale.
4411 
4412     cacheLib( aLibraryPath, aProperties );
4413 
4414     LIB_SYMBOL_MAP::const_iterator it = m_cache->m_symbols.find( aSymbolName );
4415 
4416     if( it == m_cache->m_symbols.end() )
4417         return nullptr;
4418 
4419     return it->second;
4420 }
4421 
4422 
SaveSymbol(const wxString & aLibraryPath,const LIB_SYMBOL * aSymbol,const PROPERTIES * aProperties)4423 void SCH_LEGACY_PLUGIN::SaveSymbol( const wxString& aLibraryPath, const LIB_SYMBOL* aSymbol,
4424                                     const PROPERTIES* aProperties )
4425 {
4426     LOCALE_IO toggle;     // toggles on, then off, the C locale.
4427 
4428     cacheLib( aLibraryPath, aProperties );
4429 
4430     m_cache->AddSymbol( aSymbol );
4431 
4432     if( !isBuffering( aProperties ) )
4433         m_cache->Save( writeDocFile( aProperties ) );
4434 }
4435 
4436 
DeleteSymbol(const wxString & aLibraryPath,const wxString & aSymbolName,const PROPERTIES * aProperties)4437 void SCH_LEGACY_PLUGIN::DeleteSymbol( const wxString& aLibraryPath, const wxString& aSymbolName,
4438                                       const PROPERTIES* aProperties )
4439 {
4440     LOCALE_IO toggle;     // toggles on, then off, the C locale.
4441 
4442     cacheLib( aLibraryPath, aProperties );
4443 
4444     m_cache->DeleteSymbol( aSymbolName );
4445 
4446     if( !isBuffering( aProperties ) )
4447         m_cache->Save( writeDocFile( aProperties ) );
4448 }
4449 
4450 
CreateSymbolLib(const wxString & aLibraryPath,const PROPERTIES * aProperties)4451 void SCH_LEGACY_PLUGIN::CreateSymbolLib( const wxString& aLibraryPath,
4452                                          const PROPERTIES* aProperties )
4453 {
4454     if( wxFileExists( aLibraryPath ) )
4455     {
4456         THROW_IO_ERROR( wxString::Format( _( "Symbol library '%s' already exists." ),
4457                                           aLibraryPath.GetData() ) );
4458     }
4459 
4460     LOCALE_IO toggle;
4461 
4462     delete m_cache;
4463     m_cache = new SCH_LEGACY_PLUGIN_CACHE( aLibraryPath );
4464     m_cache->SetModified();
4465     m_cache->Save( writeDocFile( aProperties ) );
4466     m_cache->Load();    // update m_writable and m_mod_time
4467 }
4468 
4469 
DeleteSymbolLib(const wxString & aLibraryPath,const PROPERTIES * aProperties)4470 bool SCH_LEGACY_PLUGIN::DeleteSymbolLib( const wxString& aLibraryPath,
4471                                          const PROPERTIES* aProperties )
4472 {
4473     wxFileName fn = aLibraryPath;
4474 
4475     if( !fn.FileExists() )
4476         return false;
4477 
4478     // Some of the more elaborate wxRemoveFile() crap puts up its own wxLog dialog
4479     // we don't want that.  we want bare metal portability with no UI here.
4480     if( wxRemove( aLibraryPath ) )
4481     {
4482         THROW_IO_ERROR( wxString::Format( _( "Symbol library '%s' cannot be deleted." ),
4483                                           aLibraryPath.GetData() ) );
4484     }
4485 
4486     if( m_cache && m_cache->IsFile( aLibraryPath ) )
4487     {
4488         delete m_cache;
4489         m_cache = nullptr;
4490     }
4491 
4492     return true;
4493 }
4494 
4495 
SaveLibrary(const wxString & aLibraryPath,const PROPERTIES * aProperties)4496 void SCH_LEGACY_PLUGIN::SaveLibrary( const wxString& aLibraryPath, const PROPERTIES* aProperties )
4497 {
4498     if( !m_cache )
4499         m_cache = new SCH_LEGACY_PLUGIN_CACHE( aLibraryPath );
4500 
4501     wxString oldFileName = m_cache->GetFileName();
4502 
4503     if( !m_cache->IsFile( aLibraryPath ) )
4504     {
4505         m_cache->SetFileName( aLibraryPath );
4506     }
4507 
4508     // This is a forced save.
4509     m_cache->SetModified();
4510     m_cache->Save( writeDocFile( aProperties ) );
4511     m_cache->SetFileName( oldFileName );
4512 }
4513 
4514 
CheckHeader(const wxString & aFileName)4515 bool SCH_LEGACY_PLUGIN::CheckHeader( const wxString& aFileName )
4516 {
4517     // Open file and check first line
4518     wxTextFile tempFile;
4519 
4520     tempFile.Open( aFileName );
4521     wxString firstline;
4522     // read the first line
4523     firstline = tempFile.GetFirstLine();
4524     tempFile.Close();
4525 
4526     return firstline.StartsWith( "EESchema" );
4527 }
4528 
4529 
IsSymbolLibWritable(const wxString & aLibraryPath)4530 bool SCH_LEGACY_PLUGIN::IsSymbolLibWritable( const wxString& aLibraryPath )
4531 {
4532     // Writing legacy symbol libraries is deprecated.
4533     return false;
4534 }
4535 
4536 
ParsePart(LINE_READER & reader,int aMajorVersion,int aMinorVersion)4537 LIB_SYMBOL* SCH_LEGACY_PLUGIN::ParsePart( LINE_READER& reader, int aMajorVersion,
4538                                           int aMinorVersion )
4539 {
4540     return SCH_LEGACY_PLUGIN_CACHE::LoadPart( reader, aMajorVersion, aMinorVersion );
4541 }
4542 
4543 
FormatPart(LIB_SYMBOL * symbol,OUTPUTFORMATTER & formatter)4544 void SCH_LEGACY_PLUGIN::FormatPart( LIB_SYMBOL* symbol, OUTPUTFORMATTER & formatter )
4545 {
4546     SCH_LEGACY_PLUGIN_CACHE::SaveSymbol( symbol, formatter );
4547 }
4548 
4549 
4550 
4551 const char* SCH_LEGACY_PLUGIN::PropBuffering = "buffering";
4552 const char* SCH_LEGACY_PLUGIN::PropNoDocFile = "no_doc_file";
4553