/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include "sch_legacy_lib_plugin_cache.h" #include "sch_legacy_plugin_helpers.h" #define LIB_VERSION_MAJOR 2 ///< Legacy symbol library major version. #define LIB_VERSION_MINOR 4 ///< Legacy symbol library minor version. #define LIB_VERSION( major, minor ) ( major * 100 + minor ) /** Legacy symbol library (.lib) file header. */ #define LIBFILE_IDENT "EESchema-LIBRARY Version" /** Legacy symbol library document (.dcm) file header. */ #define DOCFILE_IDENT "EESchema-DOCLIB Version 2.0" /** * Library versions 2.4 and lower use the old separate library (.lib) and * document (.dcm) files. Symbol libraries after 2.4 merged the library * and document files into a single library file. This macro checks if the * library version supports the old format. */ #define USE_OLD_DOC_FILE_FORMAT( major, minor ) \ ( LIB_VERSION( major, minor ) <= LIB_VERSION( 2, 4 ) ) SCH_LEGACY_PLUGIN_CACHE::SCH_LEGACY_PLUGIN_CACHE( const wxString& aFullPathAndFileName ) : SCH_LIB_PLUGIN_CACHE( aFullPathAndFileName ) { m_versionMajor = -1; m_versionMinor = -1; } void SCH_LEGACY_PLUGIN_CACHE::Load() { if( !m_libFileName.FileExists() ) { THROW_IO_ERROR( wxString::Format( _( "Library file '%s' not found." ), m_libFileName.GetFullPath() ) ); } wxCHECK_RET( m_libFileName.IsAbsolute(), wxString::Format( "Cannot use relative file paths in legacy plugin to " "open library '%s'.", m_libFileName.GetFullPath() ) ); wxLogTrace( traceSchLegacyPlugin, "Loading legacy symbol file '%s'", m_libFileName.GetFullPath() ); FILE_LINE_READER reader( m_libFileName.GetFullPath() ); if( !reader.ReadLine() ) THROW_IO_ERROR( _( "Unexpected end of file." ) ); const char* line = reader.Line(); if( !strCompare( "EESchema-LIBRARY Version", line, &line ) ) { // Old .sym files (which are libraries with only one symbol, used to store and reuse shapes) // EESchema-LIB Version x.x SYMBOL. They are valid files. if( !strCompare( "EESchema-LIB Version", line, &line ) ) SCH_PARSE_ERROR( "file is not a valid symbol or symbol library file", reader, line ); } m_versionMajor = parseInt( reader, line, &line ); if( *line != '.' ) SCH_PARSE_ERROR( "invalid file version formatting in header", reader, line ); line++; m_versionMinor = parseInt( reader, line, &line ); if( m_versionMajor < 1 || m_versionMinor < 0 || m_versionMinor > 99 ) SCH_PARSE_ERROR( "invalid file version in header", reader, line ); // Check if this is a symbol library which is the same as a symbol library but without // any alias, documentation, footprint filters, etc. if( strCompare( "SYMBOL", line, &line ) ) { // Symbol files add date and time stamp info to the header. m_libType = SCH_LIB_TYPE::LT_SYMBOL; /// @todo Probably should check for a valid date and time stamp even though it's not used. } else { m_libType = SCH_LIB_TYPE::LT_EESCHEMA; } while( reader.ReadLine() ) { line = reader.Line(); if( *line == '#' || isspace( *line ) ) // Skip comments and blank lines. continue; // Headers where only supported in older library file formats. if( m_libType == SCH_LIB_TYPE::LT_EESCHEMA && strCompare( "$HEADER", line ) ) loadHeader( reader ); if( strCompare( "DEF", line ) ) { // Read one DEF/ENDDEF symbol entry from library: LIB_SYMBOL* symbol = LoadPart( reader, m_versionMajor, m_versionMinor, &m_symbols ); m_symbols[ symbol->GetName() ] = symbol; } } SCH_LEGACY_PLUGIN_CACHE::IncrementModifyHash(); // Remember the file modification time of library file when the // cache snapshot was made, so that in a networked environment we will // reload the cache as needed. m_fileModTime = GetLibModificationTime(); if( USE_OLD_DOC_FILE_FORMAT( m_versionMajor, m_versionMinor ) ) loadDocs(); } void SCH_LEGACY_PLUGIN_CACHE::loadDocs() { const char* line; wxString text; wxString aliasName; wxFileName fn = m_libFileName; LIB_SYMBOL* symbol = nullptr;; fn.SetExt( LegacySymbolDocumentFileExtension ); // Not all libraries will have a document file. if( !fn.FileExists() ) return; if( !fn.IsFileReadable() ) { THROW_IO_ERROR( wxString::Format( _( "Insufficient permissions to read library '%s'." ), fn.GetFullPath() ) ); } FILE_LINE_READER reader( fn.GetFullPath() ); line = reader.ReadLine(); if( !line ) THROW_IO_ERROR( _( "symbol document library file is empty" ) ); if( !strCompare( DOCFILE_IDENT, line, &line ) ) { SCH_PARSE_ERROR( "invalid document library file version formatting in header", reader, line ); } while( reader.ReadLine() ) { line = reader.Line(); if( *line == '#' ) // Comment line. continue; if( !strCompare( "$CMP", line, &line ) != 0 ) SCH_PARSE_ERROR( "$CMP command expected", reader, line ); aliasName = wxString::FromUTF8( line ); aliasName.Trim(); LIB_SYMBOL_MAP::iterator it = m_symbols.find( aliasName ); if( it == m_symbols.end() ) wxLogWarning( "Symbol '%s' not found in library:\n\n" "'%s'\n\nat line %d offset %d", aliasName, fn.GetFullPath(), reader.LineNumber(), (int) (line - reader.Line() ) ); else symbol = it->second; // Read the current alias associated doc. // if the alias does not exist, just skip the description // (Can happen if a .dcm is not synchronized with the corresponding .lib file) while( reader.ReadLine() ) { line = reader.Line(); if( !line ) SCH_PARSE_ERROR( "unexpected end of file", reader, line ); if( strCompare( "$ENDCMP", line, &line ) ) break; text = FROM_UTF8( line + 2 ); // Remove spaces at eol, and eol chars: text = text.Trim(); switch( line[0] ) { case 'D': if( symbol ) symbol->SetDescription( text ); break; case 'K': if( symbol ) symbol->SetKeyWords( text ); break; case 'F': if( symbol ) symbol->GetFieldById( DATASHEET_FIELD )->SetText( text ); break; case 0: case '\n': case '\r': case '#': // Empty line or commment break; default: SCH_PARSE_ERROR( "expected token in symbol definition", reader, line ); } } } } void SCH_LEGACY_PLUGIN_CACHE::loadHeader( FILE_LINE_READER& aReader ) { const char* line = aReader.Line(); wxASSERT( strCompare( "$HEADER", line, &line ) ); while( aReader.ReadLine() ) { line = (char*) aReader; // The time stamp saved in old library files is not used or saved in the latest // library file version. if( strCompare( "TimeStamp", line, &line ) ) continue; else if( strCompare( "$ENDHEADER", line, &line ) ) return; } SCH_PARSE_ERROR( "$ENDHEADER not found", aReader, line ); } LIB_SYMBOL* SCH_LEGACY_PLUGIN_CACHE::LoadPart( LINE_READER& aReader, int aMajorVersion, int aMinorVersion, LIB_SYMBOL_MAP* aMap ) { const char* line = aReader.Line(); while( *line == '#' ) aReader.ReadLine(); if( !strCompare( "DEF", line, &line ) ) SCH_PARSE_ERROR( "invalid symbol definition", aReader, line ); long num; size_t pos = 4; // "DEF" plus the first space. wxString utf8Line = wxString::FromUTF8( line ); wxStringTokenizer tokens( utf8Line, " \r\n\t" ); if( tokens.CountTokens() < 8 ) SCH_PARSE_ERROR( "invalid symbol definition", aReader, line ); // Read DEF line: std::unique_ptr symbol = std::make_unique( wxEmptyString ); wxString name, prefix, tmp; name = tokens.GetNextToken(); name = EscapeString( name, CTX_LIBID ); pos += name.size() + 1; prefix = tokens.GetNextToken(); pos += prefix.size() + 1; tmp = tokens.GetNextToken(); pos += tmp.size() + 1; // NumOfPins, unused. tmp = tokens.GetNextToken(); // Pin name offset. if( !tmp.ToLong( &num ) ) { THROW_PARSE_ERROR( "invalid pin offset", aReader.GetSource(), aReader.Line(), aReader.LineNumber(), pos ); } pos += tmp.size() + 1; symbol->SetPinNameOffset( Mils2Iu( (int)num ) ); tmp = tokens.GetNextToken(); // Show pin numbers. if( !( tmp == "Y" || tmp == "N") ) THROW_PARSE_ERROR( "expected Y or N", aReader.GetSource(), aReader.Line(), aReader.LineNumber(), pos ); pos += tmp.size() + 1; symbol->SetShowPinNumbers( ( tmp == "N" ) ? false : true ); tmp = tokens.GetNextToken(); // Show pin names. if( !( tmp == "Y" || tmp == "N") ) { THROW_PARSE_ERROR( "expected Y or N", aReader.GetSource(), aReader.Line(), aReader.LineNumber(), pos ); } pos += tmp.size() + 1; symbol->SetShowPinNames( ( tmp == "N" ) ? false : true ); tmp = tokens.GetNextToken(); // Number of units. if( !tmp.ToLong( &num ) ) { THROW_PARSE_ERROR( "invalid unit count", aReader.GetSource(), aReader.Line(), aReader.LineNumber(), pos ); } pos += tmp.size() + 1; symbol->SetUnitCount( (int)num ); // Ensure m_unitCount is >= 1. Could be read as 0 in old libraries. if( symbol->GetUnitCount() < 1 ) symbol->SetUnitCount( 1 ); // Copy symbol name and prefix. // The root alias is added to the alias list by SetName() which is called by SetText(). if( name.IsEmpty() ) { symbol->SetName( "~" ); } else if( name[0] != '~' ) { symbol->SetName( name ); } else { symbol->SetName( name.Right( name.Length() - 1 ) ); symbol->GetValueField().SetVisible( false ); } // Don't set the library alias, this is determined by the symbol library table. symbol->SetLibId( LIB_ID( wxEmptyString, symbol->GetName() ) ); LIB_FIELD& reference = symbol->GetReferenceField(); if( prefix == "~" ) { reference.Empty(); reference.SetVisible( false ); } else { reference.SetText( prefix ); } // In version 2.2 and earlier, this parameter was a '0' which was just a place holder. // The was no concept of interchangeable multiple unit symbols. if( LIB_VERSION( aMajorVersion, aMinorVersion ) > 0 && LIB_VERSION( aMajorVersion, aMinorVersion ) <= LIB_VERSION( 2, 2 ) ) { // Nothing needs to be set since the default setting for symbols with multiple // units were never interchangeable. Just parse the 0 an move on. tmp = tokens.GetNextToken(); pos += tmp.size() + 1; } else { tmp = tokens.GetNextToken(); if( tmp == "L" ) symbol->LockUnits( true ); else if( tmp == "F" || tmp == "0" ) symbol->LockUnits( false ); else THROW_PARSE_ERROR( "expected L, F, or 0", aReader.GetSource(), aReader.Line(), aReader.LineNumber(), pos ); pos += tmp.size() + 1; } // There is the optional power symbol flag. if( tokens.HasMoreTokens() ) { tmp = tokens.GetNextToken(); if( tmp == "P" ) symbol->SetPower(); else if( tmp == "N" ) symbol->SetNormal(); else THROW_PARSE_ERROR( "expected P or N", aReader.GetSource(), aReader.Line(), aReader.LineNumber(), pos ); } line = aReader.ReadLine(); // Read lines until "ENDDEF" is found. while( line ) { if( *line == '#' ) // Comment ; else if( strCompare( "Ti", line, &line ) ) // Modification date is ignored. continue; else if( strCompare( "ALIAS", line, &line ) ) // Aliases loadAliases( symbol, aReader, aMap ); else if( *line == 'F' ) // Fields loadField( symbol, aReader ); else if( strCompare( "DRAW", line, &line ) ) // Drawing objects. loadDrawEntries( symbol, aReader, aMajorVersion, aMinorVersion ); else if( strCompare( "$FPLIST", line, &line ) ) // Footprint filter list loadFootprintFilters( symbol, aReader ); else if( strCompare( "ENDDEF", line, &line ) ) // End of symbol description { return symbol.release(); } line = aReader.ReadLine(); } SCH_PARSE_ERROR( "missing ENDDEF", aReader, line ); } void SCH_LEGACY_PLUGIN_CACHE::loadAliases( std::unique_ptr& aSymbol, LINE_READER& aReader, LIB_SYMBOL_MAP* aMap ) { wxString newAliasName; const char* line = aReader.Line(); wxCHECK_RET( strCompare( "ALIAS", line, &line ), "Invalid ALIAS section" ); wxString utf8Line = wxString::FromUTF8( line ); wxStringTokenizer tokens( utf8Line, " \r\n\t" ); // Parse the ALIAS list. while( tokens.HasMoreTokens() ) { newAliasName = tokens.GetNextToken(); if( aMap ) { LIB_SYMBOL* newSymbol = new LIB_SYMBOL( newAliasName ); // Inherit the parent mandatory field attributes. for( int id = 0; id < MANDATORY_FIELDS; ++id ) { LIB_FIELD* field = newSymbol->GetFieldById( id ); // the MANDATORY_FIELDS are exactly that in RAM. wxASSERT( field ); LIB_FIELD* parentField = aSymbol->GetFieldById( id ); wxASSERT( parentField ); *field = *parentField; if( id == VALUE_FIELD ) field->SetText( newAliasName ); field->SetParent( newSymbol ); } newSymbol->SetParent( aSymbol.get() ); // This will prevent duplicate aliases. (*aMap)[ newSymbol->GetName() ] = newSymbol; } } } void SCH_LEGACY_PLUGIN_CACHE::loadField( std::unique_ptr& aSymbol, LINE_READER& aReader ) { const char* line = aReader.Line(); wxCHECK_RET( *line == 'F', "Invalid field line" ); wxString text; int id; if( sscanf( line + 1, "%d", &id ) != 1 || id < 0 ) SCH_PARSE_ERROR( "invalid field ID", aReader, line + 1 ); LIB_FIELD* field; if( id >= 0 && id < MANDATORY_FIELDS ) { field = aSymbol->GetFieldById( id ); // this will fire only if somebody broke a constructor or editor. // MANDATORY_FIELDS are always present in ram resident symbols, no // exceptions, and they always have their names set, even fixed fields. wxASSERT( field ); } else { field = new LIB_FIELD( aSymbol.get(), id ); aSymbol->AddDrawItem( field, false ); } // Skip to the first double quote. while( *line != '"' && *line != 0 ) line++; if( *line == 0 ) SCH_PARSE_ERROR( _( "unexpected end of line" ), aReader, line ); parseQuotedString( text, aReader, line, &line, true ); // The value field needs to be "special" escaped. The other fields are // escaped normally and don't need special handling if( id == VALUE_FIELD ) text = EscapeString( text, CTX_QUOTED_STR ); // Doctor the *.lib file field which has a "~" in blank fields. New saves will // not save like this. if( text.size() == 1 && text[0] == '~' ) field->SetText( wxEmptyString ); else field->SetText( ConvertToNewOverbarNotation( text ) ); VECTOR2I pos; pos.x = Mils2Iu( parseInt( aReader, line, &line ) ); pos.y = Mils2Iu( parseInt( aReader, line, &line ) ); field->SetPosition( pos ); wxSize textSize; textSize.x = textSize.y = Mils2Iu( parseInt( aReader, line, &line ) ); field->SetTextSize( textSize ); char textOrient = parseChar( aReader, line, &line ); if( textOrient == 'H' ) field->SetTextAngle( ANGLE_HORIZONTAL ); else if( textOrient == 'V' ) field->SetTextAngle( ANGLE_VERTICAL ); else SCH_PARSE_ERROR( "invalid field text orientation parameter", aReader, line ); char textVisible = parseChar( aReader, line, &line ); if( textVisible == 'V' ) field->SetVisible( true ); else if ( textVisible == 'I' ) field->SetVisible( false ); else SCH_PARSE_ERROR( "invalid field text visibility parameter", aReader, line ); // It may be technically correct to use the library version to determine if the field text // attributes are present. If anyone knows if that is valid and what version that would be, // please change this to test the library version rather than an EOL or the quoted string // of the field name. if( *line != 0 && *line != '"' ) { char textHJustify = parseChar( aReader, line, &line ); if( textHJustify == 'C' ) field->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER ); else if( textHJustify == 'L' ) field->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); else if( textHJustify == 'R' ) field->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); else SCH_PARSE_ERROR( "invalid field text horizontal justification", aReader, line ); wxString attributes; parseUnquotedString( attributes, aReader, line, &line ); size_t attrSize = attributes.size(); if( !(attrSize == 3 || attrSize == 1 ) ) SCH_PARSE_ERROR( "invalid field text attributes size", aReader, line ); switch( (wxChar) attributes[0] ) { case 'C': field->SetVertJustify( GR_TEXT_V_ALIGN_CENTER ); break; case 'B': field->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM ); break; case 'T': field->SetVertJustify( GR_TEXT_V_ALIGN_TOP ); break; default: SCH_PARSE_ERROR( "invalid field text vertical justification", aReader, line ); } if( attrSize == 3 ) { wxChar attr_1 = attributes[1]; wxChar attr_2 = attributes[2]; if( attr_1 == 'I' ) // Italic field->SetItalic( true ); else if( attr_1 != 'N' ) // No italics is default, check for error. SCH_PARSE_ERROR( "invalid field text italic parameter", aReader, line ); if ( attr_2 == 'B' ) // Bold field->SetBold( true ); else if( attr_2 != 'N' ) // No bold is default, check for error. SCH_PARSE_ERROR( "invalid field text bold parameter", aReader, line ); } } // Fields in RAM must always have names. if( id >= 0 && id < MANDATORY_FIELDS ) { // Fields in RAM must always have names, because we are trying to get // less dependent on field ids and more dependent on names. // Plus assumptions are made in the field editors. field->m_name = TEMPLATE_FIELDNAME::GetDefaultFieldName( id ); // Ensure the VALUE field = the symbol name (can be not the case // with malformed libraries: edited by hand, or converted from other tools) if( id == VALUE_FIELD ) field->SetText( aSymbol->GetName() ); } else { parseQuotedString( field->m_name, aReader, line, &line, true ); // Optional. } } void SCH_LEGACY_PLUGIN_CACHE::loadDrawEntries( std::unique_ptr& aSymbol, LINE_READER& aReader, int aMajorVersion, int aMinorVersion ) { const char* line = aReader.Line(); wxCHECK_RET( strCompare( "DRAW", line, &line ), "Invalid DRAW section" ); line = aReader.ReadLine(); while( line ) { if( strCompare( "ENDDRAW", line, &line ) ) { aSymbol->GetDrawItems().sort(); return; } switch( line[0] ) { case 'A': // Arc aSymbol->AddDrawItem( loadArc( aSymbol, aReader ), false ); break; case 'C': // Circle aSymbol->AddDrawItem( loadCircle( aSymbol, aReader ), false ); break; case 'T': // Text aSymbol->AddDrawItem( loadText( aSymbol, aReader, aMajorVersion, aMinorVersion ), false ); break; case 'S': // Square aSymbol->AddDrawItem( loadRect( aSymbol, aReader ), false ); break; case 'X': // Pin Description aSymbol->AddDrawItem( loadPin( aSymbol, aReader ), false ); break; case 'P': // Polyline aSymbol->AddDrawItem( loadPolyLine( aSymbol, aReader ), false ); break; case 'B': // Bezier Curves aSymbol->AddDrawItem( loadBezier( aSymbol, aReader ), false ); break; case '#': // Comment case '\n': // Empty line case '\r': case 0: break; default: SCH_PARSE_ERROR( "undefined DRAW entry", aReader, line ); } line = aReader.ReadLine(); } SCH_PARSE_ERROR( "File ended prematurely loading symbol draw element.", aReader, line ); } FILL_T SCH_LEGACY_PLUGIN_CACHE::parseFillMode( LINE_READER& aReader, const char* aLine, const char** aOutput ) { switch ( parseChar( aReader, aLine, aOutput ) ) { case 'F': return FILL_T::FILLED_SHAPE; case 'f': return FILL_T::FILLED_WITH_BG_BODYCOLOR; case 'N': return FILL_T::NO_FILL; default: SCH_PARSE_ERROR( "invalid fill type, expected f, F, or N", aReader, aLine ); } // This will never be reached but quiets the compiler warnings return FILL_T::NO_FILL; } LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadArc( std::unique_ptr& aSymbol, LINE_READER& aReader ) { const char* line = aReader.Line(); wxCHECK_MSG( strCompare( "A", line, &line ), nullptr, "Invalid arc definition" ); LIB_SHAPE* arc = new LIB_SHAPE( aSymbol.get(), SHAPE_T::ARC ); VECTOR2I center; center.x = Mils2Iu( parseInt( aReader, line, &line ) ); center.y = Mils2Iu( parseInt( aReader, line, &line ) ); arc->SetPosition( center ); (void) Mils2Iu( parseInt( aReader, line, &line ) ); EDA_ANGLE angle1( parseInt( aReader, line, &line ), TENTHS_OF_A_DEGREE_T ); EDA_ANGLE angle2( parseInt( aReader, line, &line ), TENTHS_OF_A_DEGREE_T ); arc->SetUnit( parseInt( aReader, line, &line ) ); arc->SetConvert( parseInt( aReader, line, &line ) ); STROKE_PARAMS stroke( Mils2Iu( parseInt( aReader, line, &line ) ), PLOT_DASH_TYPE::SOLID ); arc->SetStroke( stroke ); // Old libraries (version <= 2.2) do not have always this FILL MODE param when fill mode // is no fill (default mode). if( *line != 0 ) arc->SetFillMode( parseFillMode( aReader, line, &line ) ); // Actual Coordinates of arc ends are read from file if( *line != 0 ) { VECTOR2I arcStart, arcEnd; arcStart.x = Mils2Iu( parseInt( aReader, line, &line ) ); arcStart.y = Mils2Iu( parseInt( aReader, line, &line ) ); arcEnd.x = Mils2Iu( parseInt( aReader, line, &line ) ); arcEnd.y = Mils2Iu( parseInt( aReader, line, &line ) ); arc->SetStart( arcStart ); arc->SetEnd( arcEnd ); } // Actual Coordinates of arc ends are not read from file (old library), calculate them else { arc->SetArcAngleAndEnd( angle2 - angle1, true ); } /* * Current file format stores start-mid-end and so doesn't care about winding. We * store start-end with an implied winding internally though. * This issue is only for 180 deg arcs, because 180 deg are a limit to handle arcs in * legacy libs. * * So a workaround is to slightly change the arc angle to * avoid 180 deg arc after correction */ EDA_ANGLE arc_angle = arc->GetArcAngle(); if( arc_angle == ANGLE_180 ) { VECTOR2I new_center = CalcArcCenter( arc->GetStart(), arc->GetEnd(), EDA_ANGLE( 179.5, DEGREES_T ) ); arc->SetCenter( new_center ); } return arc; } LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadCircle( std::unique_ptr& aSymbol, LINE_READER& aReader ) { const char* line = aReader.Line(); wxCHECK_MSG( strCompare( "C", line, &line ), nullptr, "Invalid circle definition" ); LIB_SHAPE* circle = new LIB_SHAPE( aSymbol.get(), SHAPE_T::CIRCLE ); VECTOR2I center; center.x = Mils2Iu( parseInt( aReader, line, &line ) ); center.y = Mils2Iu( parseInt( aReader, line, &line ) ); int radius = Mils2Iu( parseInt( aReader, line, &line ) ); circle->SetStart( center ); circle->SetEnd( VECTOR2I( center.x + radius, center.y ) ); circle->SetUnit( parseInt( aReader, line, &line ) ); circle->SetConvert( parseInt( aReader, line, &line ) ); STROKE_PARAMS stroke( Mils2Iu( parseInt( aReader, line, &line ) ), PLOT_DASH_TYPE::SOLID ); circle->SetStroke( stroke ); if( *line != 0 ) circle->SetFillMode( parseFillMode( aReader, line, &line ) ); return circle; } LIB_TEXT* SCH_LEGACY_PLUGIN_CACHE::loadText( std::unique_ptr& aSymbol, LINE_READER& aReader, int aMajorVersion, int aMinorVersion ) { const char* line = aReader.Line(); wxCHECK_MSG( strCompare( "T", line, &line ), nullptr, "Invalid LIB_TEXT definition" ); LIB_TEXT* text = new LIB_TEXT( aSymbol.get() ); double angleInTenths = parseInt( aReader, line, &line ); text->SetTextAngle( EDA_ANGLE( angleInTenths, TENTHS_OF_A_DEGREE_T ) ); VECTOR2I center; center.x = Mils2Iu( parseInt( aReader, line, &line ) ); center.y = Mils2Iu( parseInt( aReader, line, &line ) ); text->SetPosition( center ); wxSize size; size.x = size.y = Mils2Iu( parseInt( aReader, line, &line ) ); text->SetTextSize( size ); text->SetVisible( !parseInt( aReader, line, &line ) ); text->SetUnit( parseInt( aReader, line, &line ) ); text->SetConvert( parseInt( aReader, line, &line ) ); wxString str; // If quoted string loading fails, load as not quoted string. if( *line == '"' ) { parseQuotedString( str, aReader, line, &line ); str = ConvertToNewOverbarNotation( str ); } else { parseUnquotedString( str, aReader, line, &line ); // In old libs, "spaces" are replaced by '~' in unquoted strings: str.Replace( "~", " " ); } if( !str.IsEmpty() ) { // convert two apostrophes back to double quote str.Replace( "''", "\"" ); } text->SetText( str ); // Here things are murky and not well defined. At some point it appears the format // was changed to add text properties. However rather than add the token to the end of // the text definition, it was added after the string and no mention if the file // verion was bumped or not so this code make break on very old symbol libraries. // // Update: apparently even in the latest version this can be different so added a test // for end of line before checking for the text properties. if( LIB_VERSION( aMajorVersion, aMinorVersion ) > 0 && LIB_VERSION( aMajorVersion, aMinorVersion ) > LIB_VERSION( 2, 0 ) && !is_eol( *line ) ) { if( strCompare( "Italic", line, &line ) ) text->SetItalic( true ); else if( !strCompare( "Normal", line, &line ) ) SCH_PARSE_ERROR( "invalid text stype, expected 'Normal' or 'Italic'", aReader, line ); if( parseInt( aReader, line, &line ) > 0 ) text->SetBold( true ); // Some old libaries version > 2.0 do not have these options for text justification: if( !is_eol( *line ) ) { switch( parseChar( aReader, line, &line ) ) { case 'L': text->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); break; case 'C': text->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER ); break; case 'R': text->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); break; default: SCH_PARSE_ERROR( "invalid horizontal text justication; expected L, C, or R", aReader, line ); } switch( parseChar( aReader, line, &line ) ) { case 'T': text->SetVertJustify( GR_TEXT_V_ALIGN_TOP ); break; case 'C': text->SetVertJustify( GR_TEXT_V_ALIGN_CENTER ); break; case 'B': text->SetVertJustify( GR_TEXT_V_ALIGN_BOTTOM ); break; default: SCH_PARSE_ERROR( "invalid vertical text justication; expected T, C, or B", aReader, line ); } } } return text; } LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadRect( std::unique_ptr& aSymbol, LINE_READER& aReader ) { const char* line = aReader.Line(); wxCHECK_MSG( strCompare( "S", line, &line ), nullptr, "Invalid rectangle definition" ); LIB_SHAPE* rectangle = new LIB_SHAPE( aSymbol.get(), SHAPE_T::RECT ); VECTOR2I pos; pos.x = Mils2Iu( parseInt( aReader, line, &line ) ); pos.y = Mils2Iu( parseInt( aReader, line, &line ) ); rectangle->SetPosition( pos ); VECTOR2I end; end.x = Mils2Iu( parseInt( aReader, line, &line ) ); end.y = Mils2Iu( parseInt( aReader, line, &line ) ); rectangle->SetEnd( end ); rectangle->SetUnit( parseInt( aReader, line, &line ) ); rectangle->SetConvert( parseInt( aReader, line, &line ) ); STROKE_PARAMS stroke( Mils2Iu( parseInt( aReader, line, &line ) ), PLOT_DASH_TYPE::SOLID ); rectangle->SetStroke( stroke ); if( *line != 0 ) rectangle->SetFillMode( parseFillMode( aReader, line, &line ) ); return rectangle; } LIB_PIN* SCH_LEGACY_PLUGIN_CACHE::loadPin( std::unique_ptr& aSymbol, LINE_READER& aReader ) { const char* line = aReader.Line(); wxCHECK_MSG( strCompare( "X", line, &line ), nullptr, "Invalid LIB_PIN definition" ); wxString name; wxString number; size_t pos = 2; // "X" plus ' ' space character. wxString tmp; wxString utf8Line = wxString::FromUTF8( line ); wxStringTokenizer tokens( utf8Line, " \r\n\t" ); if( tokens.CountTokens() < 11 ) SCH_PARSE_ERROR( "invalid pin definition", aReader, line ); tmp = tokens.GetNextToken(); name = tmp; pos += tmp.size() + 1; tmp = tokens.GetNextToken(); number = tmp ; pos += tmp.size() + 1; long num; VECTOR2I position; tmp = tokens.GetNextToken(); if( !tmp.ToLong( &num ) ) { THROW_PARSE_ERROR( "invalid pin X coordinate", aReader.GetSource(), aReader.Line(), aReader.LineNumber(), pos ); } pos += tmp.size() + 1; position.x = Mils2Iu( (int) num ); tmp = tokens.GetNextToken(); if( !tmp.ToLong( &num ) ) { THROW_PARSE_ERROR( "invalid pin Y coordinate", aReader.GetSource(), aReader.Line(), aReader.LineNumber(), pos ); } pos += tmp.size() + 1; position.y = Mils2Iu( (int) num ); tmp = tokens.GetNextToken(); if( !tmp.ToLong( &num ) ) { THROW_PARSE_ERROR( "invalid pin length", aReader.GetSource(), aReader.Line(), aReader.LineNumber(), pos ); } pos += tmp.size() + 1; int length = Mils2Iu( (int) num ); tmp = tokens.GetNextToken(); if( tmp.size() > 1 ) { THROW_PARSE_ERROR( "invalid pin orientation", aReader.GetSource(), aReader.Line(), aReader.LineNumber(), pos ); } pos += tmp.size() + 1; int orientation = tmp[0]; tmp = tokens.GetNextToken(); if( !tmp.ToLong( &num ) ) { THROW_PARSE_ERROR( "invalid pin number text size", aReader.GetSource(), aReader.Line(), aReader.LineNumber(), pos ); } pos += tmp.size() + 1; int numberTextSize = Mils2Iu( (int) num ); tmp = tokens.GetNextToken(); if( !tmp.ToLong( &num ) ) { THROW_PARSE_ERROR( "invalid pin name text size", aReader.GetSource(), aReader.Line(), aReader.LineNumber(), pos ); } pos += tmp.size() + 1; int nameTextSize = Mils2Iu( (int) num ); tmp = tokens.GetNextToken(); if( !tmp.ToLong( &num ) ) { THROW_PARSE_ERROR( "invalid pin unit", aReader.GetSource(), aReader.Line(), aReader.LineNumber(), pos ); } pos += tmp.size() + 1; int unit = (int) num; tmp = tokens.GetNextToken(); if( !tmp.ToLong( &num ) ) { THROW_PARSE_ERROR( "invalid pin alternate body type", aReader.GetSource(), aReader.Line(), aReader.LineNumber(), pos ); } pos += tmp.size() + 1; int convert = (int) num; tmp = tokens.GetNextToken(); if( tmp.size() != 1 ) { THROW_PARSE_ERROR( "invalid pin type", aReader.GetSource(), aReader.Line(), aReader.LineNumber(), pos ); } pos += tmp.size() + 1; char type = tmp[0]; ELECTRICAL_PINTYPE pinType; switch( type ) { case 'I': pinType = ELECTRICAL_PINTYPE::PT_INPUT; break; case 'O': pinType = ELECTRICAL_PINTYPE::PT_OUTPUT; break; case 'B': pinType = ELECTRICAL_PINTYPE::PT_BIDI; break; case 'T': pinType = ELECTRICAL_PINTYPE::PT_TRISTATE; break; case 'P': pinType = ELECTRICAL_PINTYPE::PT_PASSIVE; break; case 'U': pinType = ELECTRICAL_PINTYPE::PT_UNSPECIFIED; break; case 'W': pinType = ELECTRICAL_PINTYPE::PT_POWER_IN; break; case 'w': pinType = ELECTRICAL_PINTYPE::PT_POWER_OUT; break; case 'C': pinType = ELECTRICAL_PINTYPE::PT_OPENCOLLECTOR; break; case 'E': pinType = ELECTRICAL_PINTYPE::PT_OPENEMITTER; break; case 'N': pinType = ELECTRICAL_PINTYPE::PT_NC; break; default: THROW_PARSE_ERROR( "unknown pin type", aReader.GetSource(), aReader.Line(), aReader.LineNumber(), pos ); } LIB_PIN* pin = new LIB_PIN( aSymbol.get(), ConvertToNewOverbarNotation( name ), ConvertToNewOverbarNotation( number ), orientation, pinType, length, nameTextSize, numberTextSize, convert, position, unit ); // Optional if( tokens.HasMoreTokens() ) /* Special Symbol defined */ { tmp = tokens.GetNextToken(); enum { INVERTED = 1 << 0, CLOCK = 1 << 1, LOWLEVEL_IN = 1 << 2, LOWLEVEL_OUT = 1 << 3, FALLING_EDGE = 1 << 4, NONLOGIC = 1 << 5 }; int flags = 0; for( int j = tmp.size(); j > 0; ) { switch( tmp[--j].GetValue() ) { case '~': break; case 'N': pin->SetVisible( false ); break; case 'I': flags |= INVERTED; break; case 'C': flags |= CLOCK; break; case 'L': flags |= LOWLEVEL_IN; break; case 'V': flags |= LOWLEVEL_OUT; break; case 'F': flags |= FALLING_EDGE; break; case 'X': flags |= NONLOGIC; break; default: THROW_PARSE_ERROR( "invalid pin attribut", aReader.GetSource(), aReader.Line(), aReader.LineNumber(), pos ); } pos += 1; } switch( flags ) { case 0: pin->SetShape( GRAPHIC_PINSHAPE::LINE ); break; case INVERTED: pin->SetShape( GRAPHIC_PINSHAPE::INVERTED ); break; case CLOCK: pin->SetShape( GRAPHIC_PINSHAPE::CLOCK ); break; case INVERTED | CLOCK: pin->SetShape( GRAPHIC_PINSHAPE::INVERTED_CLOCK ); break; case LOWLEVEL_IN: pin->SetShape( GRAPHIC_PINSHAPE::INPUT_LOW ); break; case LOWLEVEL_IN | CLOCK: pin->SetShape( GRAPHIC_PINSHAPE::CLOCK_LOW ); break; case LOWLEVEL_OUT: pin->SetShape( GRAPHIC_PINSHAPE::OUTPUT_LOW ); break; case FALLING_EDGE: pin->SetShape( GRAPHIC_PINSHAPE::FALLING_EDGE_CLOCK ); break; case NONLOGIC: pin->SetShape( GRAPHIC_PINSHAPE::NONLOGIC ); break; default: SCH_PARSE_ERROR( "pin attributes do not define a valid pin shape", aReader, line ); } } return pin; } LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadPolyLine( std::unique_ptr& aSymbol, LINE_READER& aReader ) { const char* line = aReader.Line(); wxCHECK_MSG( strCompare( "P", line, &line ), nullptr, "Invalid poly definition" ); LIB_SHAPE* polyLine = new LIB_SHAPE( aSymbol.get(), SHAPE_T::POLY ); int points = parseInt( aReader, line, &line ); polyLine->SetUnit( parseInt( aReader, line, &line ) ); polyLine->SetConvert( parseInt( aReader, line, &line ) ); STROKE_PARAMS stroke( Mils2Iu( parseInt( aReader, line, &line ) ), PLOT_DASH_TYPE::SOLID ); polyLine->SetStroke( stroke ); VECTOR2I pt; for( int i = 0; i < points; i++ ) { pt.x = Mils2Iu( parseInt( aReader, line, &line ) ); pt.y = Mils2Iu( parseInt( aReader, line, &line ) ); polyLine->AddPoint( pt ); } if( *line != 0 ) polyLine->SetFillMode( parseFillMode( aReader, line, &line ) ); return polyLine; } LIB_SHAPE* SCH_LEGACY_PLUGIN_CACHE::loadBezier( std::unique_ptr& aSymbol, LINE_READER& aReader ) { const char* line = aReader.Line(); wxCHECK_MSG( strCompare( "B", line, &line ), nullptr, "Invalid Bezier definition" ); int points = parseInt( aReader, line, &line ); wxCHECK_MSG( points == 4, NULL, "Invalid Bezier curve definition" ); LIB_SHAPE* bezier = new LIB_SHAPE( aSymbol.get(), SHAPE_T::BEZIER ); bezier->SetUnit( parseInt( aReader, line, &line ) ); bezier->SetConvert( parseInt( aReader, line, &line ) ); STROKE_PARAMS stroke ( Mils2Iu( parseInt( aReader, line, &line ) ), PLOT_DASH_TYPE::SOLID ); bezier->SetStroke( stroke ); VECTOR2I pt; pt.x = Mils2Iu( parseInt( aReader, line, &line ) ); pt.y = Mils2Iu( parseInt( aReader, line, &line ) ); bezier->SetStart( pt ); pt.x = Mils2Iu( parseInt( aReader, line, &line ) ); pt.y = Mils2Iu( parseInt( aReader, line, &line ) ); bezier->SetBezierC1( pt ); pt.x = Mils2Iu( parseInt( aReader, line, &line ) ); pt.y = Mils2Iu( parseInt( aReader, line, &line ) ); bezier->SetBezierC2( pt ); pt.x = Mils2Iu( parseInt( aReader, line, &line ) ); pt.y = Mils2Iu( parseInt( aReader, line, &line ) ); bezier->SetEnd( pt ); bezier->RebuildBezierToSegmentsPointsList( bezier->GetWidth() ); if( *line != 0 ) bezier->SetFillMode( parseFillMode( aReader, line, &line ) ); return bezier; } void SCH_LEGACY_PLUGIN_CACHE::loadFootprintFilters( std::unique_ptr& aSymbol, LINE_READER& aReader ) { const char* line = aReader.Line(); wxCHECK_RET( strCompare( "$FPLIST", line, &line ), "Invalid footprint filter list" ); line = aReader.ReadLine(); wxArrayString footprintFilters; while( line ) { if( strCompare( "$ENDFPLIST", line, &line ) ) { aSymbol->SetFPFilters( footprintFilters ); return; } wxString footprint; parseUnquotedString( footprint, aReader, line, &line ); footprintFilters.Add( footprint ); line = aReader.ReadLine(); } SCH_PARSE_ERROR( "File ended prematurely while loading footprint filters.", aReader, line ); } void SCH_LEGACY_PLUGIN_CACHE::Save( const std::optional& aOpt ) { wxCHECK( aOpt, /* void */ ); bool doSaveDocFile = *aOpt; if( !m_isModified ) return; // Write through symlinks, don't replace them wxFileName fn = GetRealFile(); auto formatter = std::make_unique( fn.GetFullPath() ); formatter->Print( 0, "%s %d.%d\n", LIBFILE_IDENT, LIB_VERSION_MAJOR, LIB_VERSION_MINOR ); formatter->Print( 0, "#encoding utf-8\n"); for( LIB_SYMBOL_MAP::iterator it = m_symbols.begin(); it != m_symbols.end(); it++ ) { if( !it->second->IsRoot() ) continue; SaveSymbol( it->second, *formatter.get(), &m_symbols ); } formatter->Print( 0, "#\n#End Library\n" ); formatter.reset(); m_fileModTime = fn.GetModificationTime(); m_isModified = false; if( doSaveDocFile ) saveDocFile(); } void SCH_LEGACY_PLUGIN_CACHE::SaveSymbol( LIB_SYMBOL* aSymbol, OUTPUTFORMATTER& aFormatter, LIB_SYMBOL_MAP* aMap ) { /* * NB: * Some of the rescue code still uses the legacy format as an intermediary, so we have * to keep this code. */ wxCHECK_RET( aSymbol && aSymbol->IsRoot(), "Invalid LIB_SYMBOL pointer." ); // LIB_ALIAS objects are deprecated but we still need to gather up the derived symbols // and save their names for the old file format. wxArrayString aliasNames; if( aMap ) { for( auto entry : *aMap ) { LIB_SYMBOL* symbol = entry.second; if( symbol->IsAlias() && symbol->GetParent().lock() == aSymbol->SharedPtr() ) aliasNames.Add( symbol->GetName() ); } } LIB_FIELD& value = aSymbol->GetValueField(); // First line: it s a comment (symbol name for readers) aFormatter.Print( 0, "#\n# %s\n#\n", TO_UTF8( value.GetText() ) ); // Save data aFormatter.Print( 0, "DEF" ); aFormatter.Print( 0, " %s", TO_UTF8( value.GetText() ) ); LIB_FIELD& reference = aSymbol->GetReferenceField(); if( !reference.GetText().IsEmpty() ) aFormatter.Print( 0, " %s", TO_UTF8( reference.GetText() ) ); else aFormatter.Print( 0, " ~" ); aFormatter.Print( 0, " %d %d %c %c %d %c %c\n", 0, Iu2Mils( aSymbol->GetPinNameOffset() ), aSymbol->ShowPinNumbers() ? 'Y' : 'N', aSymbol->ShowPinNames() ? 'Y' : 'N', aSymbol->GetUnitCount(), aSymbol->UnitsLocked() ? 'L' : 'F', aSymbol->IsPower() ? 'P' : 'N' ); timestamp_t dateModified = aSymbol->GetLastModDate(); if( dateModified != 0 ) { int sec = dateModified & 63; int min = ( dateModified >> 6 ) & 63; int hour = ( dateModified >> 12 ) & 31; int day = ( dateModified >> 17 ) & 31; int mon = ( dateModified >> 22 ) & 15; int year = ( dateModified >> 26 ) + 1990; aFormatter.Print( 0, "Ti %d/%d/%d %d:%d:%d\n", year, mon, day, hour, min, sec ); } std::vector fields; aSymbol->GetFields( fields ); // Mandatory fields: // may have their own save policy so there is a separate loop for them. // Empty fields are saved, because the user may have set visibility, // size and orientation for( int i = 0; i < MANDATORY_FIELDS; ++i ) saveField( fields[i], aFormatter ); // User defined fields: // may have their own save policy so there is a separate loop for them. int fieldId = MANDATORY_FIELDS; // really wish this would go away. for( unsigned i = MANDATORY_FIELDS; i < fields.size(); ++i ) { // There is no need to save empty fields, i.e. no reason to preserve field // names now that fields names come in dynamically through the template // fieldnames. if( !fields[i]->GetText().IsEmpty() ) { fields[i]->SetId( fieldId++ ); saveField( fields[i], aFormatter ); } } // Save the alias list: a line starting by "ALIAS". if( !aliasNames.IsEmpty() ) { aFormatter.Print( 0, "ALIAS" ); for( unsigned i = 0; i < aliasNames.GetCount(); i++ ) aFormatter.Print( 0, " %s", TO_UTF8( aliasNames[i] ) ); aFormatter.Print( 0, "\n" ); } wxArrayString footprints = aSymbol->GetFPFilters(); // Write the footprint filter list if( footprints.GetCount() != 0 ) { aFormatter.Print( 0, "$FPLIST\n" ); for( unsigned i = 0; i < footprints.GetCount(); i++ ) aFormatter.Print( 0, " %s\n", TO_UTF8( footprints[i] ) ); aFormatter.Print( 0, "$ENDFPLIST\n" ); } // Save graphics items (including pins) if( !aSymbol->GetDrawItems().empty() ) { // Sort the draw items in order to editing a file editing by hand. aSymbol->GetDrawItems().sort(); aFormatter.Print( 0, "DRAW\n" ); for( LIB_ITEM& item : aSymbol->GetDrawItems() ) { switch( item.Type() ) { default: case LIB_FIELD_T: /* Fields have already been saved above. */ break; case LIB_PIN_T: savePin( (LIB_PIN* ) &item, aFormatter ); break; case LIB_TEXT_T: saveText( ( LIB_TEXT* ) &item, aFormatter ); break; case LIB_SHAPE_T: { LIB_SHAPE& shape = static_cast( item ); switch( shape.GetShape() ) { case SHAPE_T::ARC: saveArc( &shape, aFormatter ); break; case SHAPE_T::BEZIER: saveBezier( &shape, aFormatter ); break; case SHAPE_T::CIRCLE: saveCircle( &shape, aFormatter ); break; case SHAPE_T::POLY: savePolyLine( &shape, aFormatter ); break; case SHAPE_T::RECT: saveRectangle( &shape, aFormatter ); break; default: break; } } } } aFormatter.Print( 0, "ENDDRAW\n" ); } aFormatter.Print( 0, "ENDDEF\n" ); } void SCH_LEGACY_PLUGIN_CACHE::saveArc( LIB_SHAPE* aArc, OUTPUTFORMATTER& aFormatter ) { wxCHECK_RET( aArc && aArc->GetShape() == SHAPE_T::ARC, "Invalid ARC object." ); EDA_ANGLE startAngle, endAngle; aArc->CalcArcAngles( startAngle, endAngle ); startAngle.Normalize180(); endAngle.Normalize180(); aFormatter.Print( 0, "A %d %d %d %d %d %d %d %d %c %d %d %d %d\n", Iu2Mils( aArc->GetPosition().x ), Iu2Mils( aArc->GetPosition().y ), Iu2Mils( aArc->GetRadius() ), startAngle.AsTenthsOfADegree(), endAngle.AsTenthsOfADegree(), aArc->GetUnit(), aArc->GetConvert(), Iu2Mils( aArc->GetWidth() ), fill_tab[ static_cast( aArc->GetFillMode() ) - 1 ], Iu2Mils( aArc->GetStart().x ), Iu2Mils( aArc->GetStart().y ), Iu2Mils( aArc->GetEnd().x ), Iu2Mils( aArc->GetEnd().y ) ); } void SCH_LEGACY_PLUGIN_CACHE::saveBezier( LIB_SHAPE* aBezier, OUTPUTFORMATTER& aFormatter ) { wxCHECK_RET( aBezier && aBezier->GetShape() == SHAPE_T::BEZIER, "Invalid BEZIER object." ); aFormatter.Print( 0, "B %u %d %d %d", (unsigned)aBezier->GetBezierPoints().size(), aBezier->GetUnit(), aBezier->GetConvert(), Iu2Mils( aBezier->GetWidth() ) ); for( const VECTOR2I& pt : aBezier->GetBezierPoints() ) aFormatter.Print( 0, " %d %d", Iu2Mils( pt.x ), Iu2Mils( pt.y ) ); aFormatter.Print( 0, " %c\n", fill_tab[ static_cast( aBezier->GetFillMode() ) - 1 ] ); } void SCH_LEGACY_PLUGIN_CACHE::saveCircle( LIB_SHAPE* aCircle, OUTPUTFORMATTER& aFormatter ) { wxCHECK_RET( aCircle && aCircle->GetShape() == SHAPE_T::CIRCLE, "Invalid CIRCLE object." ); aFormatter.Print( 0, "C %d %d %d %d %d %d %c\n", Iu2Mils( aCircle->GetPosition().x ), Iu2Mils( aCircle->GetPosition().y ), Iu2Mils( aCircle->GetRadius() ), aCircle->GetUnit(), aCircle->GetConvert(), Iu2Mils( aCircle->GetWidth() ), fill_tab[ static_cast( aCircle->GetFillMode() ) - 1 ] ); } void SCH_LEGACY_PLUGIN_CACHE::saveField( const LIB_FIELD* aField, OUTPUTFORMATTER& aFormatter ) { wxCHECK_RET( aField && aField->Type() == LIB_FIELD_T, "Invalid LIB_FIELD object." ); int hjustify, vjustify; int id = aField->GetId(); wxString text = aField->GetText(); hjustify = 'C'; if( aField->GetHorizJustify() == GR_TEXT_H_ALIGN_LEFT ) hjustify = 'L'; else if( aField->GetHorizJustify() == GR_TEXT_H_ALIGN_RIGHT ) hjustify = 'R'; vjustify = 'C'; if( aField->GetVertJustify() == GR_TEXT_V_ALIGN_BOTTOM ) vjustify = 'B'; else if( aField->GetVertJustify() == GR_TEXT_V_ALIGN_TOP ) vjustify = 'T'; aFormatter.Print( 0, "F%d %s %d %d %d %c %c %c %c%c%c", id, EscapedUTF8( text ).c_str(), // wraps in quotes Iu2Mils( aField->GetTextPos().x ), Iu2Mils( aField->GetTextPos().y ), Iu2Mils( aField->GetTextWidth() ), aField->GetTextAngle().IsHorizontal() ? 'H' : 'V', aField->IsVisible() ? 'V' : 'I', hjustify, vjustify, aField->IsItalic() ? 'I' : 'N', aField->IsBold() ? 'B' : 'N' ); /* Save field name, if necessary * Field name is saved only if it is not the default name. * Just because default name depends on the language and can change from * a country to another */ wxString defName = TEMPLATE_FIELDNAME::GetDefaultFieldName( id ); if( id >= MANDATORY_FIELDS && !aField->m_name.IsEmpty() && aField->m_name != defName ) aFormatter.Print( 0, " %s", EscapedUTF8( aField->m_name ).c_str() ); aFormatter.Print( 0, "\n" ); } void SCH_LEGACY_PLUGIN_CACHE::savePin( const LIB_PIN* aPin, OUTPUTFORMATTER& aFormatter ) { wxCHECK_RET( aPin && aPin->Type() == LIB_PIN_T, "Invalid LIB_PIN object." ); int Etype; switch( aPin->GetType() ) { default: case ELECTRICAL_PINTYPE::PT_INPUT: Etype = 'I'; break; case ELECTRICAL_PINTYPE::PT_OUTPUT: Etype = 'O'; break; case ELECTRICAL_PINTYPE::PT_BIDI: Etype = 'B'; break; case ELECTRICAL_PINTYPE::PT_TRISTATE: Etype = 'T'; break; case ELECTRICAL_PINTYPE::PT_PASSIVE: Etype = 'P'; break; case ELECTRICAL_PINTYPE::PT_UNSPECIFIED: Etype = 'U'; break; case ELECTRICAL_PINTYPE::PT_POWER_IN: Etype = 'W'; break; case ELECTRICAL_PINTYPE::PT_POWER_OUT: Etype = 'w'; break; case ELECTRICAL_PINTYPE::PT_OPENCOLLECTOR: Etype = 'C'; break; case ELECTRICAL_PINTYPE::PT_OPENEMITTER: Etype = 'E'; break; case ELECTRICAL_PINTYPE::PT_NC: Etype = 'N'; break; } if( !aPin->GetName().IsEmpty() ) aFormatter.Print( 0, "X %s", TO_UTF8( aPin->GetName() ) ); else aFormatter.Print( 0, "X ~" ); aFormatter.Print( 0, " %s %d %d %d %c %d %d %d %d %c", aPin->GetNumber().IsEmpty() ? "~" : TO_UTF8( aPin->GetNumber() ), Iu2Mils( aPin->GetPosition().x ), Iu2Mils( aPin->GetPosition().y ), Iu2Mils( (int) aPin->GetLength() ), (int) aPin->GetOrientation(), Iu2Mils( aPin->GetNumberTextSize() ), Iu2Mils( aPin->GetNameTextSize() ), aPin->GetUnit(), aPin->GetConvert(), Etype ); if( aPin->GetShape() != GRAPHIC_PINSHAPE::LINE || !aPin->IsVisible() ) aFormatter.Print( 0, " " ); if( !aPin->IsVisible() ) aFormatter.Print( 0, "N" ); switch( aPin->GetShape() ) { case GRAPHIC_PINSHAPE::LINE: break; case GRAPHIC_PINSHAPE::INVERTED: aFormatter.Print( 0, "I" ); break; case GRAPHIC_PINSHAPE::CLOCK: aFormatter.Print( 0, "C" ); break; case GRAPHIC_PINSHAPE::INVERTED_CLOCK: aFormatter.Print( 0, "IC" ); break; case GRAPHIC_PINSHAPE::INPUT_LOW: aFormatter.Print( 0, "L" ); break; case GRAPHIC_PINSHAPE::CLOCK_LOW: aFormatter.Print( 0, "CL" ); break; case GRAPHIC_PINSHAPE::OUTPUT_LOW: aFormatter.Print( 0, "V" ); break; case GRAPHIC_PINSHAPE::FALLING_EDGE_CLOCK: aFormatter.Print( 0, "F" ); break; case GRAPHIC_PINSHAPE::NONLOGIC: aFormatter.Print( 0, "X" ); break; default: wxFAIL_MSG( "Invalid pin shape" ); } aFormatter.Print( 0, "\n" ); const_cast( aPin )->ClearFlags( IS_CHANGED ); } void SCH_LEGACY_PLUGIN_CACHE::savePolyLine( LIB_SHAPE* aPolyLine, OUTPUTFORMATTER& aFormatter ) { wxCHECK_RET( aPolyLine && aPolyLine->GetShape() == SHAPE_T::POLY, "Invalid POLY object." ); aFormatter.Print( 0, "P %d %d %d %d", (int) aPolyLine->GetPolyShape().Outline( 0 ).GetPointCount(), aPolyLine->GetUnit(), aPolyLine->GetConvert(), Iu2Mils( aPolyLine->GetWidth() ) ); for( const VECTOR2I& pt : aPolyLine->GetPolyShape().Outline( 0 ).CPoints() ) aFormatter.Print( 0, " %d %d", Iu2Mils( pt.x ), Iu2Mils( pt.y ) ); aFormatter.Print( 0, " %c\n", fill_tab[ static_cast( aPolyLine->GetFillMode() ) - 1 ] ); } void SCH_LEGACY_PLUGIN_CACHE::saveRectangle( LIB_SHAPE* aRectangle, OUTPUTFORMATTER& aFormatter ) { wxCHECK_RET( aRectangle && aRectangle->GetShape() == SHAPE_T::RECT, "Invalid RECT object." ); aFormatter.Print( 0, "S %d %d %d %d %d %d %d %c\n", Iu2Mils( aRectangle->GetPosition().x ), Iu2Mils( aRectangle->GetPosition().y ), Iu2Mils( aRectangle->GetEnd().x ), Iu2Mils( aRectangle->GetEnd().y ), aRectangle->GetUnit(), aRectangle->GetConvert(), Iu2Mils( aRectangle->GetWidth() ), fill_tab[ static_cast( aRectangle->GetFillMode() ) - 1 ] ); } void SCH_LEGACY_PLUGIN_CACHE::saveText( const LIB_TEXT* aText, OUTPUTFORMATTER& aFormatter ) { wxCHECK_RET( aText && aText->Type() == LIB_TEXT_T, "Invalid LIB_TEXT object." ); wxString text = aText->GetText(); if( text.Contains( wxT( " " ) ) || text.Contains( wxT( "~" ) ) || text.Contains( wxT( "\"" ) ) ) { // convert double quote to similar-looking two apostrophes text.Replace( wxT( "\"" ), wxT( "''" ) ); text = wxT( "\"" ) + text + wxT( "\"" ); } aFormatter.Print( 0, "T %g %d %d %d %d %d %d %s", (double) aText->GetTextAngle().AsTenthsOfADegree(), Iu2Mils( aText->GetTextPos().x ), Iu2Mils( aText->GetTextPos().y ), Iu2Mils( aText->GetTextWidth() ), !aText->IsVisible(), aText->GetUnit(), aText->GetConvert(), TO_UTF8( text ) ); aFormatter.Print( 0, " %s %d", aText->IsItalic() ? "Italic" : "Normal", aText->IsBold() ); char hjustify = 'C'; if( aText->GetHorizJustify() == GR_TEXT_H_ALIGN_LEFT ) hjustify = 'L'; else if( aText->GetHorizJustify() == GR_TEXT_H_ALIGN_RIGHT ) hjustify = 'R'; char vjustify = 'C'; if( aText->GetVertJustify() == GR_TEXT_V_ALIGN_BOTTOM ) vjustify = 'B'; else if( aText->GetVertJustify() == GR_TEXT_V_ALIGN_TOP ) vjustify = 'T'; aFormatter.Print( 0, " %c %c\n", hjustify, vjustify ); } void SCH_LEGACY_PLUGIN_CACHE::saveDocFile() { /* * NB: * Some of the rescue code still uses the legacy format as an intermediary, so we have * to keep this code. */ wxFileName fileName = m_libFileName; fileName.SetExt( LegacySymbolDocumentFileExtension ); FILE_OUTPUTFORMATTER formatter( fileName.GetFullPath() ); formatter.Print( 0, "%s\n", DOCFILE_IDENT ); for( LIB_SYMBOL_MAP::iterator it = m_symbols.begin(); it != m_symbols.end(); ++it ) { wxString description = it->second->GetDescription(); wxString keyWords = it->second->GetKeyWords(); wxString docFileName = it->second->GetDatasheetField().GetText(); if( description.IsEmpty() && keyWords.IsEmpty() && docFileName.IsEmpty() ) continue; formatter.Print( 0, "#\n$CMP %s\n", TO_UTF8( it->second->GetName() ) ); if( !description.IsEmpty() ) formatter.Print( 0, "D %s\n", TO_UTF8( description ) ); if( !keyWords.IsEmpty() ) formatter.Print( 0, "K %s\n", TO_UTF8( keyWords ) ); if( !docFileName.IsEmpty() ) formatter.Print( 0, "F %s\n", TO_UTF8( docFileName ) ); formatter.Print( 0, "$ENDCMP\n" ); } formatter.Print( 0, "#\n#End Doc Library\n" ); } void SCH_LEGACY_PLUGIN_CACHE::DeleteSymbol( const wxString& aSymbolName ) { LIB_SYMBOL_MAP::iterator it = m_symbols.find( aSymbolName ); if( it == m_symbols.end() ) THROW_IO_ERROR( wxString::Format( _( "library %s does not contain a symbol named %s" ), m_libFileName.GetFullName(), aSymbolName ) ); LIB_SYMBOL* symbol = it->second; if( symbol->IsRoot() ) { LIB_SYMBOL* rootSymbol = symbol; // Remove the root symbol and all its children. m_symbols.erase( it ); LIB_SYMBOL_MAP::iterator it1 = m_symbols.begin(); while( it1 != m_symbols.end() ) { if( it1->second->IsAlias() && it1->second->GetParent().lock() == rootSymbol->SharedPtr() ) { delete it1->second; it1 = m_symbols.erase( it1 ); } else { it1++; } } delete rootSymbol; } else { // Just remove the alias. m_symbols.erase( it ); delete symbol; } SCH_LEGACY_PLUGIN_CACHE::IncrementModifyHash(); m_isModified = true; }