From 55b3ef2ff2a36acbb9f2ddf4fd5c2ccc4d0cd195 Mon Sep 17 00:00:00 2001 From: Alex Shvartzkop Date: Sat, 10 Aug 2024 01:21:53 +0300 Subject: [PATCH] Improve DXF MText content import. Support more than 250 characters, improve control codes conversion. Fixes https://gitlab.com/kicad/code/kicad/-/issues/18524 --- common/import_gfx/dxf_import_plugin.cpp | 201 +++++++++++++++++------- common/import_gfx/dxf_import_plugin.h | 6 +- 2 files changed, 150 insertions(+), 57 deletions(-) diff --git a/common/import_gfx/dxf_import_plugin.cpp b/common/import_gfx/dxf_import_plugin.cpp index 5429305bef..39f6f0c635 100644 --- a/common/import_gfx/dxf_import_plugin.cpp +++ b/common/import_gfx/dxf_import_plugin.cpp @@ -1,4 +1,4 @@ -/* +/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2019 Jean-Pierre Charras, jp.charras at wanadoo.fr @@ -840,10 +840,23 @@ void DXF_IMPORT_PLUGIN::addText( const DL_TextData& aData ) } +void DXF_IMPORT_PLUGIN::addMTextChunk( const std::string& text ) +{ + // If the text string is greater than 250 characters, the string is divided into 250-character + // chunks, which appear in one or more group 3 codes. If group 3 codes are used, the last group + // is a group 1 and has fewer than 250 characters + + m_mtextContent.append( text ); +} + + void DXF_IMPORT_PLUGIN::addMText( const DL_MTextData& aData ) { - wxString text = toNativeString( wxString::FromUTF8( aData.text.c_str() ) ); - wxString attrib; + m_mtextContent.append( aData.text ); + + // TODO: determine control codes applied to the whole text? + wxString text = toNativeString( wxString::FromUTF8( m_mtextContent.c_str() ) ); + DXF_IMPORT_STYLE* style = getImportStyle( aData.style.c_str() ); double textHeight = mapDim( aData.height ); @@ -861,30 +874,6 @@ void DXF_IMPORT_PLUGIN::addMText( const DL_MTextData& aData ) VECTOR2D topLeft(0.0, 0.0); VECTOR2D topRight(0.0, 0.0); - /* Some texts start by '\' and have formatting chars (font name, font option...) - * ending with ';' - * Here are some mtext formatting codes: - * Format code Purpose - * \0...\o Turns overline on and off - * \L...\l Turns underline on and off - * \~ Inserts a nonbreaking space - \\ Inserts a backslash - \\\{...\} Inserts an opening and closing brace - \\ \File name; Changes to the specified font file - \\ \Hvalue; Changes to the text height specified in drawing units - \\ \Hvaluex; Changes the text height to a multiple of the current text height - \\ \S...^...; Stacks the subsequent text at the \, #, or ^ symbol - \\ \Tvalue; Adjusts the space between characters, from.75 to 4 times - \\ \Qangle; Changes oblique angle - \\ \Wvalue; Changes width factor to produce wide text - \\ \A Sets the alignment value; valid values: 0, 1, 2 (bottom, center, top) while( text.StartsWith( wxT("\\") ) ) - */ - while( text.StartsWith( wxT( "\\" ) ) ) - { - attrib << text.BeforeFirst( ';' ); - text = text.AfterFirst( ';' ); - } - MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() ); VECTOR3D textposCoords = ocsToWcs( arbAxis, VECTOR3D( aData.ipx, aData.ipy, aData.ipz ) ); VECTOR2D textpos( mapX( textposCoords.x ), mapY( textposCoords.y ) ); @@ -1001,6 +990,8 @@ void DXF_IMPORT_PLUGIN::addMText( const DL_MTextData& aData ) updateImageLimits( bottomRight ); updateImageLimits( topLeft ); updateImageLimits( topRight ); + + m_mtextContent.clear(); } @@ -1165,55 +1156,153 @@ wxString DXF_IMPORT_PLUGIN::toDxfString( const wxString& aStr ) wxString DXF_IMPORT_PLUGIN::toNativeString( const wxString& aData ) { - wxString res; + wxString res; + size_t i = 0; + int braces = 0; + int overbarLevel = -1; - // Ignore font tags: - int j = 0; + // For description, see: + // https://ezdxf.readthedocs.io/en/stable/dxfinternals/entities/mtext.html + // https://www.cadforum.cz/en/text-formatting-codes-in-mtext-objects-tip8640 - for( unsigned i = 0; i < aData.length(); ++i ) + for( i = 0; i < aData.length(); i++ ) { - if( aData[ i ] == 0x7B ) // is '{' ? + switch( (wchar_t) aData[i] ) + { + case '{': // Text area influenced by the code + braces++; + break; + + case '}': + if( overbarLevel == braces ) + { + res << '}'; + overbarLevel = -1; + } + braces--; + break; + + case '^': // C0 control code + if( ++i >= aData.length() ) + break; + + switch( (wchar_t) aData[i] ) + { + case 'I': res << '\t'; break; + case 'J': res << '\b'; break; + case ' ': res << '^'; break; + default: break; + } + break; + + case '\\': { - if( aData[ i + 1 ] == 0x5c && aData[ i + 2 ] == 0x66 ) // is "\f" ? + if( ++i >= aData.length() ) + break; + + switch( (wchar_t) aData[i] ) + { + case 'P': // New paragraph (new line) + case 'X': // Paragraph wrap on the dimension line (only in dimensions) + res << '\n'; + break; + + case '~': // Non-wrapping space, hard space + res << '\u00A0'; + break; + + case 'S': // Stacking { - // found font tag, append parsed part - res.append( aData.Mid( j, i - j ) ); + i++; + wxString stacked; - // skip to ';' - for( unsigned k = i + 3; k < aData.length(); ++k ) + for( ; i < aData.length(); i++ ) { - if( aData[ k ] == 0x3B ) - { - i = j = ++k; + if( aData[i] == ';' ) break; - } + else + stacked << aData[i]; + } + + if( stacked.Contains( wxS( "#" ) ) ) + { + res << '^' << '{'; + res << stacked.BeforeFirst( '#' ); + res << '}' << '/' << '_' << '{'; + res << stacked.AfterFirst( '#' ); + res << '}'; + } + else + { + stacked.Replace( wxS( "^ " ), wxS( "/" ) ); + res << stacked; + } + } + break; + + case 'O': // Start overstrike + if( overbarLevel == -1 ) + { + res << '~' << '{'; + overbarLevel = braces; + } + break; + case 'o': // Stop overstrike + if( overbarLevel == braces ) + { + res << '}'; + overbarLevel = -1; } + break; + + case 'L': // Start underline + case 'l': // Stop underline + case 'K': // Start strike-through + case 'k': // Stop strike-through + case 'N': // New column + // Ignore + break; - // add to '}' - for( unsigned k = i; k < aData.length(); ++k ) + case 'p': // Control codes for bullets, numbered paragraphs, tab stops and columns + case 'Q': // Slanting (obliquing) text by angle + case 'H': // Text height + case 'W': // Text width + case 'F': // Font selection + case 'f': // Font selection (alternative) + case 'A': // Alignment + case 'C': // Color change (ACI colors) + case 'c': // Color change (truecolor) + case 'T': // Tracking, char.spacing + // Skip to ; + for( ; i < aData.length(); i++ ) { - if( aData[ k ] == 0x7D ) - { - res.append( aData.Mid( i, k - i ) ); - i = j = ++k; + if( aData[i] == ';' ) break; - } } + break; + + default: // Escaped character + if( ++i >= aData.length() ) + break; + + res << aData[i]; + break; } } + break; + + default: res << aData[i]; + } } - res.append( aData.Mid( j ) ); + if( overbarLevel != -1 ) + { + res << '}'; + overbarLevel = -1; + } #if 1 wxRegEx regexp; - // Line feed: - regexp.Compile( wxT( "\\\\P" ) ); - regexp.Replace( &res, wxT( "\n" ) ); - - // Space: - regexp.Compile( wxT( "\\\\~" ) ); - regexp.Replace( &res, wxT( " " ) ); // diameter: regexp.Compile( wxT( "%%[cC]" ) ); diff --git a/common/import_gfx/dxf_import_plugin.h b/common/import_gfx/dxf_import_plugin.h index 387f79e08b..a4316a8c05 100644 --- a/common/import_gfx/dxf_import_plugin.h +++ b/common/import_gfx/dxf_import_plugin.h @@ -428,7 +428,9 @@ private: * Called for every polyline vertex. */ virtual void addVertex( const DL_VertexData& aData ) override; - virtual void addMText( const DL_MTextData& aData) override; + + virtual void addMTextChunk( const std::string& text ) override; + virtual void addMText( const DL_MTextData& aData ) override; virtual void endEntity() override; @@ -572,6 +574,8 @@ private: // Each message ends by '\n' DXF2BRD_ENTITY_DATA m_curr_entity; // the current entity parameters when parsing a DXF entity + std::string m_mtextContent; // Contents of MText. + double m_minX, m_maxX; // handles image size in mm double m_minY, m_maxY; // handles image size in mm