/*
* 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;
}