/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2010-2014 Jean-Pierre Charras jp.charras at wanadoo.fr * Copyright (C) 1992-2023 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, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include <math/util.h> // for KiROUND #include <gerber_file_image.h> #include <base_units.h> /* These routines read the text string point from Text. * On exit, Text points the beginning of the sequence unread */ // conversion scale from gerber file units to Gerbview internal units // depending on the gerber file format // this scale list assumes gerber units are imperial. // for metric gerber units, the imperial to metric conversion is made in read functions #define SCALE_LIST_SIZE 9 static double scale_list[SCALE_LIST_SIZE] = { 1000.0 * GERB_IU_PER_MM * 0.0254, // x.1 format (certainly useless) 100.0 * GERB_IU_PER_MM * 0.0254, // x.2 format (certainly useless) 10.0 * GERB_IU_PER_MM * 0.0254, // x.3 format 1.0 * GERB_IU_PER_MM * 0.0254, // x.4 format 0.1 * GERB_IU_PER_MM * 0.0254, // x.5 format 0.01 * GERB_IU_PER_MM * 0.0254, // x.6 format 0.001 * GERB_IU_PER_MM * 0.0254, // x.7 format (currently the max allowed precision) 0.0001 * GERB_IU_PER_MM * 0.0254, // provided, but not used 0.00001 * GERB_IU_PER_MM * 0.0254, // provided, but not used }; /** * Convert a coordinate given in floating point to GerbView's internal units * (currently = 10 nanometers). */ int scaletoIU( double aCoord, bool isMetric ) { int ret; if( isMetric ) // gerber are units in mm ret = KiROUND( aCoord * GERB_IU_PER_MM ); else // gerber are units in inches ret = KiROUND( aCoord * GERB_IU_PER_MM * 25.4 ); return ret; } // An useful function used when reading gerber files static bool IsNumber( char x ) { return ( ( x >= '0' ) && ( x <='9' ) ) || ( x == '-' ) || ( x == '+' ) || ( x == '.' ); } VECTOR2I GERBER_FILE_IMAGE::ReadXYCoord( char*& aText, bool aExcellonMode ) { VECTOR2I pos( 0, 0 ); bool is_float = false; std::string line; // Reserve the anticipated length plus an optional sign and decimal line.reserve( std::max( m_FmtLen.x, m_FmtLen.y ) + 3 ); // Set up return value for case where aText == nullptr if( !m_Relative ) pos = m_CurrentPos; if( aText == nullptr ) return pos; while( *aText && ( ( *aText == 'X' ) || ( *aText == 'Y' ) || ( *aText == 'A' ) ) ) { double decimal_scale = 1.0; int nbdigits = 0; int current_coord = 0; char type_coord = *aText++; line.clear(); while( IsNumber( *aText ) ) { if( *aText == '.' ) // Force decimal format if reading a floating point number is_float = true; // count digits only (sign and decimal point are not counted) if( (*aText >= '0') && (*aText <='9') ) nbdigits++; line.push_back( *( aText++ ) ); } double val = strtod( line.data(), nullptr ); if( is_float ) { current_coord = scaletoIU( val, m_GerbMetric ); } else { int fmt_scale = (type_coord == 'X') ? m_FmtScale.x : m_FmtScale.y; if( m_NoTrailingZeros ) { // no trailing zero format, we need to add missing zeros. int digit_count = (type_coord == 'X') ? m_FmtLen.x : m_FmtLen.y; // Truncate the extra digits if the len is more than expected // because the conversion to internal units expect exactly // digit_count digits. Alternatively, add some additional digits // to pad out to the missing zeros if( nbdigits < digit_count || ( aExcellonMode && ( nbdigits > digit_count ) ) ) decimal_scale = std::pow<double>( 10, digit_count - nbdigits ); } double real_scale = scale_list[fmt_scale]; if( m_GerbMetric ) real_scale = real_scale / 25.4; current_coord = KiROUND( val * real_scale * decimal_scale ); } if( type_coord == 'X' ) { pos.x = current_coord; } else if( type_coord == 'Y' ) { pos.y = current_coord; } else if( type_coord == 'A' ) { m_ArcRadius = current_coord; m_LastArcDataType = ARC_INFO_TYPE_RADIUS; } } if( m_Relative ) pos += m_CurrentPos; m_CurrentPos = pos; return pos; } VECTOR2I GERBER_FILE_IMAGE::ReadIJCoord( char*& aText ) { VECTOR2I pos( 0, 0 ); bool is_float = false; std::string line; // Reserve the anticipated length plus an optional sign and decimal line.reserve( std::max( m_FmtLen.x, m_FmtLen.y ) + 3 ); if( aText == nullptr ) return pos; while( *aText && ( ( *aText == 'I' ) || ( *aText == 'J' ) ) ) { double decimal_scale = 1.0; int nbdigits = 0; int current_coord = 0; char type_coord = *aText++; line.clear(); while( IsNumber( *aText ) ) { if( *aText == '.' ) // Force decimal format if reading a floating point number is_float = true; // count digits only (sign and decimal point are not counted) if( (*aText >= '0') && (*aText <='9') ) nbdigits++; line.push_back( *( aText++ ) ); } double val = strtod( line.data(), nullptr ); if( is_float ) { current_coord = scaletoIU( val, m_GerbMetric ); } else { int fmt_scale = ( type_coord == 'I' ) ? m_FmtScale.x : m_FmtScale.y; if( m_NoTrailingZeros ) { // no trailing zero format, we need to add missing zeros. int digit_count = ( type_coord == 'I' ) ? m_FmtLen.x : m_FmtLen.y; // Truncate the extra digits if the len is more than expected // because the conversion to internal units expect exactly // digit_count digits. Alternatively, add some additional digits // to pad out to the missing zeros if( nbdigits < digit_count ) decimal_scale = std::pow<double>( 10, digit_count - nbdigits ); } double real_scale = scale_list[fmt_scale]; if( m_GerbMetric ) real_scale = real_scale / 25.4; current_coord = KiROUND( val * real_scale * decimal_scale ); } if( type_coord == 'I' ) { pos.x = current_coord; } else if( type_coord == 'J' ) { pos.y = current_coord; } } m_IJPos = pos; m_LastArcDataType = ARC_INFO_TYPE_CENTER; m_LastCoordIsIJPos = true; return pos; } // Helper functions: /** * Read an integer from an ASCII character buffer. * * If there is a comma after the integer, then skip over that. * * @param text is a reference to a character pointer from which bytes are read * and the pointer is advanced for each byte read. * @param aSkipSeparator set to true (default) to skip comma. * @return The integer read in. */ int ReadInt( char*& text, bool aSkipSeparator = true ) { int ret; // For strtol, a string starting by 0X or 0x is a valid number in hexadecimal or octal. // However, 'X' is a separator in Gerber strings with numbers. // We need to detect that if( strncasecmp( text, "0X", 2 ) == 0 ) { text++; ret = 0; } else { ret = (int) strtol( text, &text, 10 ); } if( *text == ',' || isspace( *text ) ) { if( aSkipSeparator ) ++text; } return ret; } /** * Read a double precision floating point number from an ASCII character buffer. * * If there is a comma after the number, then skip over that. * * @param text is a reference to a character pointer from which the ASCII double * is read from and the pointer advanced for each character read. * @param aSkipSeparator set to true (default) to skip comma. * @return number read. */ double ReadDouble( char*& text, bool aSkipSeparator = true ) { double ret; // For strtod, a string starting by 0X or 0x is a valid number in hexadecimal or octal. // However, 'X' is a separator in Gerber strings with numbers. // We need to detect that if( strncasecmp( text, "0X", 2 ) == 0 ) { text++; ret = 0.0; } else { ret = strtod( text, &text ); } if( *text == ',' || isspace( *text ) ) { if( aSkipSeparator ) ++text; } return ret; }