diff --git a/common/eda_text.cpp b/common/eda_text.cpp index 3d47d8a88c..c329746f55 100644 --- a/common/eda_text.cpp +++ b/common/eda_text.cpp @@ -70,6 +70,14 @@ void addTextSegmToPoly( int x0, int y0, int xf, int yf, void* aData ) } +void addTextSegmToShape( int x0, int y0, int xf, int yf, void* aData ) +{ + TSEGM_2_SHAPE_PRMS* prm = static_cast( aData ); + prm->m_shape->AddShape( new SHAPE_SEGMENT( VECTOR2I( x0, y0 ), VECTOR2I( xf, yf ), + prm->m_penWidth ) ); +} + + GR_TEXT_H_ALIGN_T EDA_TEXT::MapHorizJustify( int aHorizJustify ) { wxASSERT( aHorizJustify >= GR_TEXT_H_ALIGN_LEFT && aHorizJustify <= GR_TEXT_H_ALIGN_RIGHT ); @@ -107,12 +115,25 @@ EDA_TEXT::EDA_TEXT( const wxString& text ) : } -EDA_TEXT::EDA_TEXT( const EDA_TEXT& aText ) : - m_text( aText.m_text ), - m_attributes( aText.m_attributes ), - m_pos( aText.m_pos ) +EDA_TEXT::EDA_TEXT( const EDA_TEXT& aText ) { - cacheShownText(); + m_text = aText.m_text; + m_shown_text = aText.m_shown_text; + m_shown_text_has_text_var_refs = aText.m_shown_text_has_text_var_refs; + + m_attributes = aText.m_attributes; + m_pos = aText.m_pos; + + m_render_cache_text = aText.m_render_cache_text; + m_render_cache_angle = aText.m_render_cache_angle; + + m_render_cache.clear(); + + for( const std::unique_ptr& glyph : aText.m_render_cache ) + { + KIFONT::OUTLINE_GLYPH* outline_glyph = static_cast( glyph.get() ); + m_render_cache.emplace_back( std::make_unique( *outline_glyph ) ); + } } @@ -121,6 +142,30 @@ EDA_TEXT::~EDA_TEXT() } +EDA_TEXT& EDA_TEXT::operator=( const EDA_TEXT& aText ) +{ + m_text = aText.m_text; + m_shown_text = aText.m_shown_text; + m_shown_text_has_text_var_refs = aText.m_shown_text_has_text_var_refs; + + m_attributes = aText.m_attributes; + m_pos = aText.m_pos; + + m_render_cache_text = aText.m_render_cache_text; + m_render_cache_angle = aText.m_render_cache_angle; + + m_render_cache.clear(); + + for( const std::unique_ptr& glyph : aText.m_render_cache ) + { + KIFONT::OUTLINE_GLYPH* outline_glyph = static_cast( glyph.get() ); + m_render_cache.emplace_back( std::make_unique( *outline_glyph ) ); + } + + return *this; +} + + void EDA_TEXT::SetText( const wxString& aText ) { m_text = aText; @@ -133,6 +178,78 @@ void EDA_TEXT::CopyText( const EDA_TEXT& aSrc ) m_text = aSrc.m_text; m_shown_text = aSrc.m_shown_text; m_shown_text_has_text_var_refs = aSrc.m_shown_text_has_text_var_refs; + + m_render_cache.clear(); +} + + +void EDA_TEXT::SetTextThickness( int aWidth ) +{ + m_attributes.m_StrokeWidth = aWidth; + m_render_cache.clear(); +} + + +void EDA_TEXT::SetTextAngle( const EDA_ANGLE& aAngle ) +{ + m_attributes.m_Angle = aAngle; + m_render_cache.clear(); +} + + +void EDA_TEXT::SetItalic( bool aItalic ) +{ + m_attributes.m_Italic = aItalic; + m_render_cache.clear(); +} + + +void EDA_TEXT::SetBold( bool aBold ) +{ + m_attributes.m_Bold = aBold; + m_render_cache.clear(); +} + + +void EDA_TEXT::SetVisible( bool aVisible ) +{ + m_attributes.m_Visible = aVisible; + m_render_cache.clear(); +} + + +void EDA_TEXT::SetMirrored( bool isMirrored ) +{ + m_attributes.m_Mirrored = isMirrored; + m_render_cache.clear(); +} + + +void EDA_TEXT::SetMultilineAllowed( bool aAllow ) +{ + m_attributes.m_Multiline = aAllow; + m_render_cache.clear(); +} + + +void EDA_TEXT::SetHorizJustify( GR_TEXT_H_ALIGN_T aType ) +{ + m_attributes.m_Halign = aType; + m_render_cache.clear(); +} + + +void EDA_TEXT::SetVertJustify( GR_TEXT_V_ALIGN_T aType ) +{ + m_attributes.m_Valign = aType; + m_render_cache.clear(); +} + + +void EDA_TEXT::SetKeepUpright( bool aKeepUpright ) +{ + m_attributes.m_KeepUpright = aKeepUpright; + m_render_cache.clear(); } @@ -140,6 +257,7 @@ void EDA_TEXT::SetAttributes( const EDA_TEXT& aSrc ) { m_attributes = aSrc.m_attributes; m_pos = aSrc.m_pos; + m_render_cache.clear(); } @@ -148,6 +266,8 @@ void EDA_TEXT::SwapText( EDA_TEXT& aTradingPartner ) std::swap( m_text, aTradingPartner.m_text ); std::swap( m_shown_text, aTradingPartner.m_shown_text ); std::swap( m_shown_text_has_text_var_refs, aTradingPartner.m_shown_text_has_text_var_refs ); + + m_render_cache.clear(); } @@ -155,6 +275,8 @@ void EDA_TEXT::SwapAttributes( EDA_TEXT& aTradingPartner ) { std::swap( m_attributes, aTradingPartner.m_attributes ); std::swap( m_pos, aTradingPartner.m_pos ); + + m_render_cache.clear(); } @@ -188,6 +310,78 @@ bool EDA_TEXT::Replace( const wxFindReplaceData& aSearchData ) } +void EDA_TEXT::SetFont( KIFONT::FONT* aFont ) +{ + m_attributes.m_Font = aFont; + m_render_cache.clear(); +} + + +void EDA_TEXT::SetLineSpacing( double aLineSpacing ) +{ + m_attributes.m_LineSpacing = aLineSpacing; + m_render_cache.clear(); +} + + +void EDA_TEXT::SetTextSize( const wxSize& aNewSize ) +{ + m_attributes.m_Size = aNewSize; + m_render_cache.clear(); +} + + +void EDA_TEXT::SetTextWidth( int aWidth ) +{ + m_attributes.m_Size.x = aWidth; + m_render_cache.clear(); +} + + +void EDA_TEXT::SetTextHeight( int aHeight ) +{ + m_attributes.m_Size.y = aHeight; + m_render_cache.clear(); +} + + +void EDA_TEXT::SetTextPos( const VECTOR2I& aPoint ) +{ + Offset( VECTOR2I( aPoint.x - m_pos.x, aPoint.y - m_pos.y ) ); +} + + +void EDA_TEXT::SetTextX( int aX ) +{ + Offset( VECTOR2I( aX - m_pos.x, 0 ) ); +} + + +void EDA_TEXT::SetTextY( int aY ) +{ + Offset( VECTOR2I( 0, aY - m_pos.y ) ); +} + + +void EDA_TEXT::Offset( const VECTOR2I& aOffset ) +{ + m_pos += aOffset; + + for( std::unique_ptr& glyph : m_render_cache ) + { + KIFONT::OUTLINE_GLYPH* outline_glyph = static_cast( glyph.get() ); + outline_glyph->Move( aOffset ); + } +} + + +void EDA_TEXT::Empty() +{ + m_text.Empty(); + m_render_cache.clear(); +} + + void EDA_TEXT::cacheShownText() { if( m_text.IsEmpty() || m_text == wxT( "~" ) ) // ~ is legacy empty-string token @@ -200,6 +394,49 @@ void EDA_TEXT::cacheShownText() m_shown_text = UnescapeString( m_text ); m_shown_text_has_text_var_refs = m_shown_text.Contains( wxT( "${" ) ); } + + m_render_cache.clear(); +} + + +std::vector>* +EDA_TEXT::GetRenderCache( const wxString& forResolvedText ) const +{ + if( GetFont() && GetFont()->IsOutline() ) + { + EDA_ANGLE resolvedAngle = GetDrawRotation(); + + if( m_render_cache.empty() + || m_render_cache_text != forResolvedText + || m_render_cache_angle != resolvedAngle ) + { + m_render_cache.clear(); + + KIFONT::OUTLINE_FONT* font = static_cast( GetFont() ); + font->GetLinesAsGlyphs( m_render_cache, this ); + + m_render_cache_angle = resolvedAngle; + m_render_cache_text = forResolvedText; + } + + return &m_render_cache; + } + + return nullptr; +} + + +void EDA_TEXT::SetupRenderCache( const wxString& aResolvedText, const EDA_ANGLE& aAngle ) +{ + m_render_cache_text = aResolvedText; + m_render_cache_angle = aAngle; + m_render_cache.clear(); +} + + +void EDA_TEXT::AddRenderCacheGlyph( const SHAPE_POLY_SET& aPoly ) +{ + m_render_cache.emplace_back( std::make_unique( aPoly ) ); } @@ -567,58 +804,6 @@ void EDA_TEXT::Format( OUTPUTFORMATTER* aFormatter, int aNestLevel, int aControl #endif } -// Convert the text shape to a list of segment -// each segment is stored as 2 VECTOR2Is: its starting point and its ending point -// we are using GRText to create the segments and therefore a call-back function is needed - -// This is a call back function, used by GRText to put each segment in buffer -static void addTextSegmToBuffer( int x0, int y0, int xf, int yf, void* aData ) -{ - std::vector* cornerBuffer = static_cast*>( aData ); - cornerBuffer->push_back( VECTOR2I( x0, y0 ) ); - cornerBuffer->push_back( VECTOR2I( xf, yf ) ); -} - - -std::vector EDA_TEXT::TransformToSegmentList() const -{ - std::vector cornerBuffer; - wxSize size = GetTextSize(); - - if( IsMirrored() ) - size.x = -size.x; - - bool forceBold = true; - int penWidth = 0; // use max-width for bold text - - COLOR4D color = COLOR4D::BLACK; // not actually used, but needed by GRText - - if( IsMultilineAllowed() ) - { - wxArrayString strings_list; - wxStringSplit( GetShownText(), strings_list, wxChar('\n') ); - std::vector positions; - positions.reserve( strings_list.Count() ); - GetLinePositions( positions, strings_list.Count() ); - - for( unsigned ii = 0; ii < strings_list.Count(); ii++ ) - { - wxString txt = strings_list.Item( ii ); - GRText( nullptr, positions[ii], color, txt, GetDrawRotation(), size, - GetDrawHorizJustify(), GetDrawVertJustify(), penWidth, IsItalic(), forceBold, - GetFont(), addTextSegmToBuffer, &cornerBuffer ); - } - } - else - { - GRText( nullptr, GetDrawPos(), color, GetShownText(), GetDrawRotation(), size, - GetDrawHorizJustify(), GetDrawVertJustify(), penWidth, IsItalic(), forceBold, - GetFont(), addTextSegmToBuffer, &cornerBuffer ); - } - - return cornerBuffer; -} - std::shared_ptr EDA_TEXT::GetEffectiveTextShape( ) const { @@ -626,20 +811,13 @@ std::shared_ptr EDA_TEXT::GetEffectiveTextShape( ) const if( GetFont() && GetFont()->IsOutline() ) { - // FONT TODO: Use the cached glyphs rather than rendering them + // Make sure the cache is up-to-date before using it + (void) GetRenderCache( m_render_cache_text ); - KIFONT::OUTLINE_FONT* font = static_cast( GetFont() ); - std::vector> glyphs; - - font->GetLinesAsGlyphs( glyphs, this ); - - for( std::unique_ptr& baseGlyph : glyphs ) + for( std::unique_ptr& baseGlyph : m_render_cache ) { KIFONT::OUTLINE_GLYPH* glyph = static_cast( baseGlyph.get() ); - if( IsMirrored() ) - glyph->Mirror( GetTextPos() ); - glyph->CacheTriangulation(); for( unsigned int ii = 0; ii < glyph->TriangulatedPolyCount(); ++ii ) @@ -663,11 +841,38 @@ std::shared_ptr EDA_TEXT::GetEffectiveTextShape( ) const } else { - int penWidth = GetEffectiveTextPenWidth(); - std::vector pts = TransformToSegmentList(); + wxSize size = GetTextSize(); + int penWidth = GetEffectiveTextPenWidth(); + bool forceBold = true; - for( unsigned jj = 0; jj < pts.size(); jj += 2 ) - shape->AddShape( new SHAPE_SEGMENT( pts[jj], pts[jj+1], penWidth ) ); + TSEGM_2_SHAPE_PRMS prms; + prms.m_penWidth = penWidth; + prms.m_shape = shape.get(); + + if( IsMirrored() ) + size.x = -size.x; + + if( IsMultilineAllowed() ) + { + wxArrayString strings_list; + wxStringSplit( GetShownText(), strings_list, wxChar('\n') ); + std::vector positions; + positions.reserve( strings_list.Count() ); + GetLinePositions( positions, strings_list.Count() ); + + for( unsigned ii = 0; ii < strings_list.Count(); ii++ ) + { + GRText( nullptr, positions[ii], COLOR4D::BLACK, strings_list.Item( ii ), + GetDrawRotation(), size, GetDrawHorizJustify(), GetDrawVertJustify(), + penWidth, IsItalic(), forceBold, GetFont(), addTextSegmToShape, &prms ); + } + } + else + { + GRText( nullptr, GetDrawPos(), COLOR4D::BLACK, GetShownText(), + GetDrawRotation(), size, GetDrawHorizJustify(), GetDrawVertJustify(), + penWidth, IsItalic(), forceBold, GetFont(), addTextSegmToShape, &prms ); + } } return shape; diff --git a/common/font/font.cpp b/common/font/font.cpp index 0a251b9a77..0fbd3348bf 100644 --- a/common/font/font.cpp +++ b/common/font/font.cpp @@ -112,28 +112,28 @@ bool FONT::IsStroke( const wxString& aFontName ) * @param aPosition is the text position * @return bounding box width/height */ -VECTOR2D FONT::doDrawString( KIGFX::GAL* aGal, const UTF8& aText, const VECTOR2D& aPosition, +VECTOR2D FONT::doDrawString( KIGFX::GAL* aGal, const UTF8& aText, const VECTOR2I& aPosition, bool aParse, const TEXT_ATTRIBUTES& aAttrs ) const { if( aText.empty() ) return VECTOR2D( 0.0, 0.0 ); wxArrayString strings; - std::vector positions; - int n_lines; + std::vector positions; VECTOR2D boundingBox; std::vector lineBoundingBoxes; - getLinePositions( aText, aPosition, strings, positions, n_lines, lineBoundingBoxes, aAttrs ); + getLinePositions( aText, aPosition, strings, positions, lineBoundingBoxes, aAttrs ); - for( int i = 0; i < n_lines; i++ ) + for( size_t i = 0; i < strings.GetCount(); i++ ) { VECTOR2D lineBoundingBox; + if( aParse ) { MARKUP::MARKUP_PARSER markupParser( std::string( strings.Item( i ) ) ); //auto parse_result = markupParser.Parse(); - VECTOR2D cursor = positions[i]; + VECTOR2I cursor = positions[i]; std::function& )> nodeHandler = [&]( const std::unique_ptr& aNode ) @@ -160,30 +160,29 @@ VECTOR2D FONT::doDrawString( KIGFX::GAL* aGal, const UTF8& aText, const VECTOR2D boundingBox.x = fmax( boundingBox.x, lineBoundingBox.x ); } - boundingBox.y = ( n_lines + 1 ) * GetInterline( aAttrs.m_Size.y ); + boundingBox.y = ( strings.GetCount() + 1 ) * GetInterline( aAttrs.m_Size.y ); return boundingBox; } -void FONT::getLinePositions( const UTF8& aText, const VECTOR2D& aPosition, - wxArrayString& aStringList, std::vector& aPositions, - int& aLineCount, std::vector& aBoundingBoxes, +void FONT::getLinePositions( const UTF8& aText, const VECTOR2I& aPosition, + wxArrayString& aTextLines, std::vector& aPositions, + std::vector& aBoundingBoxes, const TEXT_ATTRIBUTES& aAttrs ) const { - wxStringSplit( aText, aStringList, '\n' ); - aLineCount = aStringList.Count(); - aPositions.reserve( aLineCount ); + wxStringSplit( aText, aTextLines, '\n' ); + int lineCount = aTextLines.Count(); + aPositions.reserve( lineCount ); - wxPoint origin( aPosition.x, aPosition.y ); - int interline = GetInterline( aAttrs.m_Size.y, aAttrs.m_LineSpacing ); - int height = 0; + int interline = GetInterline( aAttrs.m_Size.y, aAttrs.m_LineSpacing ); + int height = 0; - for( int i = 0; i < aLineCount; i++ ) + for( int i = 0; i < lineCount; i++ ) { - VECTOR2D pos( origin.x, origin.y + i * interline ); - VECTOR2D end = boundingBoxSingleLine( nullptr, aStringList[i], pos, aAttrs.m_Size, - aAttrs.m_Angle, aAttrs.m_Italic ); + VECTOR2D pos( aPosition.x, aPosition.y + i * interline ); + VECTOR2D end = boundingBoxSingleLine( nullptr, aTextLines[i], pos, aAttrs.m_Size, + aAttrs.m_Italic ); VECTOR2D bBox( end - pos ); aBoundingBoxes.push_back( bBox ); @@ -204,66 +203,26 @@ void FONT::getLinePositions( const UTF8& aText, const VECTOR2D& aPosition, case GR_TEXT_V_ALIGN_BOTTOM: offset.y -= height; break; } - int mirrorX = aAttrs.m_Mirrored ? -1 : 1; - - for( int i = 0; i < aLineCount; i++ ) + for( int i = 0; i < lineCount; i++ ) { - VECTOR2D lineSize = aBoundingBoxes.at( i ); + VECTOR2I lineSize = aBoundingBoxes.at( i ); wxPoint lineOffset( offset ); + lineOffset.y += i * interline; switch( aAttrs.m_Halign ) { - case GR_TEXT_H_ALIGN_LEFT: break; - case GR_TEXT_H_ALIGN_CENTER: lineOffset.x = mirrorX * -lineSize.x / 2; break; - case GR_TEXT_H_ALIGN_RIGHT: lineOffset.x = mirrorX * -lineSize.x; break; + case GR_TEXT_H_ALIGN_LEFT: break; + case GR_TEXT_H_ALIGN_CENTER: lineOffset.x = -lineSize.x / 2; break; + case GR_TEXT_H_ALIGN_RIGHT: lineOffset.x = -lineSize.x; break; } - VECTOR2I pos( aPosition.x + lineOffset.x, aPosition.y + lineOffset.y ); - RotatePoint( pos, origin, aAttrs.m_Angle ); - - aPositions.push_back( (wxPoint) pos ); + aPositions.push_back( aPosition + lineOffset ); } } -VECTOR2D FONT::getBoundingBox( const UTF8& aText, TEXT_STYLE_FLAGS aTextStyle, - const TEXT_ATTRIBUTES& aAttributes ) const -{ - if( aText.empty() ) - return VECTOR2D( 0.0, 0.0 ); - - if( false ) // aParse ) // FONT TODO: parse markup! - { - MARKUP::MARKUP_PARSER markupParser( aText ); - auto parse_result = markupParser.Parse(); - - /* ... */ - } - - wxArrayString strings; - std::vector positions; - int n_lines; - VECTOR2D boundingBox; - std::vector boundingBoxes; - - getLinePositions( aText, VECTOR2D( 0.0, 0.0 ), strings, positions, n_lines, boundingBoxes, - aAttributes ); - - int i = 1; - - for( VECTOR2D lineBoundingBox : boundingBoxes ) - { - boundingBox.x = fmax( boundingBox.x, lineBoundingBox.x ); - boundingBox.y += lineBoundingBox.y; - i++; - } - - return boundingBox; -} - - -void FONT::DrawText( KIGFX::GAL* aGal, const UTF8& aText, const VECTOR2D& aPosition, +void FONT::DrawText( KIGFX::GAL* aGal, const UTF8& aText, const VECTOR2I& aPosition, const TEXT_ATTRIBUTES& aAttributes ) const { // FONT TODO: do we need to set the attributes to the gal at all? @@ -283,45 +242,36 @@ void FONT::DrawText( KIGFX::GAL* aGal, const UTF8& aText, const VECTOR2D& aPosit * * @param aGal * @param aText is the text to be drawn. - * @param aPosition is the text position in world coordinates. - * @param aAngle is the text rotation angle + * @param aPosition is the text object position in world coordinates. + * @param aCursor is the current text position (for multiple text blocks within a single text + * object, such as a run of superscript characters) + * @param aAttrs are the styling attributes of the text, including its rotation */ -VECTOR2D FONT::Draw( KIGFX::GAL* aGal, const UTF8& aText, const VECTOR2D& aPosition, - const VECTOR2D& aOrigin, const TEXT_ATTRIBUTES& aAttrs ) const +VECTOR2D FONT::Draw( KIGFX::GAL* aGal, const UTF8& aText, const VECTOR2I& aPosition, + const VECTOR2I& aCursor, const TEXT_ATTRIBUTES& aAttrs ) const { if( !aGal || aText.empty() ) return VECTOR2D( 0, 0 ); - VECTOR2D position( aPosition - aOrigin ); - - // Context needs to be saved before any transformations - //aGal->Save(); + VECTOR2D position( aPosition - aCursor ); // Split multiline strings into separate ones and draw them line by line wxArrayString strings_list; - std::vector positions; + std::vector positions; std::vector boundingBoxes; - int n; - getLinePositions( aText, position, strings_list, positions, n, boundingBoxes, aAttrs ); + getLinePositions( aText, position, strings_list, positions, boundingBoxes, aAttrs ); VECTOR2D boundingBox( 0, 0 ); BOX2I lineBoundingBox; - for( int i = 0; i < n; i++ ) + aGal->SetLineWidth( aAttrs.m_StrokeWidth ); + + for( size_t i = 0; i < strings_list.GetCount(); i++ ) { - aGal->Save(); - aGal->Translate( positions[i] ); - aGal->SetLineWidth( aAttrs.m_StrokeWidth ); - - if( !aAttrs.m_Angle.IsZero() ) - aGal->Rotate( aAttrs.m_Angle.Invert().AsRadians() ); - - (void) drawSingleLineText( aGal, &lineBoundingBox, strings_list[i], VECTOR2D( 0, 0 ), - aAttrs.m_Size, aAttrs.m_Angle, aAttrs.m_Italic, - aAttrs.m_Mirrored ); - aGal->Restore(); - + (void) drawSingleLineText( aGal, &lineBoundingBox, strings_list[i], positions[i], + aAttrs.m_Size, aAttrs.m_Angle, aAttrs.m_Mirrored, aPosition, + aAttrs.m_Italic ); // expand bounding box of whole text boundingBox.x = std::max( boundingBox.x, (double) lineBoundingBox.GetWidth() ); @@ -329,9 +279,6 @@ VECTOR2D FONT::Draw( KIGFX::GAL* aGal, const UTF8& aText, const VECTOR2D& aPosit boundingBox.y += lineHeight; } - // undo rotation - //aGal->Restore(); - return boundingBox; } @@ -340,11 +287,11 @@ VECTOR2D FONT::Draw( KIGFX::GAL* aGal, const UTF8& aText, const VECTOR2D& aPosit * @return position of cursor for drawing next substring */ VECTOR2D drawMarkup( BOX2I* aBoundingBox, std::vector>& aGlyphs, - const std::unique_ptr& aNode, const VECTOR2D& aPosition, - const KIFONT::FONT* aFont, const VECTOR2D& aGlyphSize, const EDA_ANGLE& aAngle, - TEXT_STYLE_FLAGS aTextStyle ) + const std::unique_ptr& aNode, const VECTOR2I& aPosition, + const KIFONT::FONT* aFont, const VECTOR2D& aSize, const EDA_ANGLE& aAngle, + bool aMirror, const VECTOR2I& aOrigin, TEXT_STYLE_FLAGS aTextStyle ) { - VECTOR2D nextPosition = aPosition; + VECTOR2I nextPosition = aPosition; TEXT_STYLE_FLAGS textStyle = aTextStyle; @@ -361,27 +308,20 @@ VECTOR2D drawMarkup( BOX2I* aBoundingBox, std::vector>& a if( aNode->has_content() ) { std::string txt = aNode->string(); - //std::vector glyphs; - wxPoint pt( aPosition.x, aPosition.y ); + BOX2I bbox; - BOX2I bbox; - nextPosition = aFont->GetTextAsGlyphs( &bbox, aGlyphs, txt, aGlyphSize, pt, aAngle, - textStyle ); + nextPosition = aFont->GetTextAsGlyphs( &bbox, aGlyphs, txt, aSize, aPosition, aAngle, + aMirror, aOrigin, textStyle ); if( aBoundingBox ) - { - BOX2I boundingBox; - boundingBox = aBoundingBox->Merge( bbox ); - aBoundingBox->SetOrigin( boundingBox.GetOrigin() ); - aBoundingBox->SetSize( boundingBox.GetSize() ); - } + aBoundingBox->Merge( bbox ); } } - for( const auto& child : aNode->children ) + for( const std::unique_ptr& child : aNode->children ) { - nextPosition = drawMarkup( aBoundingBox, aGlyphs, child, nextPosition, aFont, aGlyphSize, - aAngle, textStyle ); + nextPosition = drawMarkup( aBoundingBox, aGlyphs, child, nextPosition, aFont, aSize, + aAngle, aMirror, aOrigin, textStyle ); } return nextPosition; @@ -389,20 +329,22 @@ VECTOR2D drawMarkup( BOX2I* aBoundingBox, std::vector>& a VECTOR2D FONT::drawMarkup( BOX2I* aBoundingBox, std::vector>& aGlyphs, - const UTF8& aText, const VECTOR2D& aPosition, const VECTOR2D& aGlyphSize, - const EDA_ANGLE& aAngle, TEXT_STYLE_FLAGS aTextStyle ) const + const UTF8& aText, const VECTOR2I& aPosition, const VECTOR2D& aSize, + const EDA_ANGLE& aAngle, bool aMirror, const VECTOR2I& aOrigin, + TEXT_STYLE_FLAGS aTextStyle ) const { MARKUP::MARKUP_PARSER markupParser( aText ); std::unique_ptr root = markupParser.Parse(); - return ::drawMarkup( aBoundingBox, aGlyphs, root, aPosition, this, aGlyphSize, aAngle, - aTextStyle ); + return ::drawMarkup( aBoundingBox, aGlyphs, root, aPosition, this, aSize, aAngle, aMirror, + aOrigin, aTextStyle ); } VECTOR2D FONT::drawSingleLineText( KIGFX::GAL* aGal, BOX2I* aBoundingBox, const UTF8& aText, - const VECTOR2D& aPosition, const VECTOR2D& aGlyphSize, - const EDA_ANGLE& aAngle, bool aIsItalic, bool aIsMirrored ) const + const VECTOR2I& aPosition, const VECTOR2D& aSize, + const EDA_ANGLE& aAngle, bool aMirror, const VECTOR2I& aOrigin, + bool aItalic ) const { if( !aGal ) { @@ -412,37 +354,32 @@ VECTOR2D FONT::drawSingleLineText( KIGFX::GAL* aGal, BOX2I* aBoundingBox, const TEXT_STYLE_FLAGS textStyle = 0; - if( aIsItalic ) + if( aItalic ) textStyle |= TEXT_STYLE::ITALIC; std::vector> glyphs; - VECTOR2D nextPosition = drawMarkup( aBoundingBox, glyphs, aText, aPosition, aGlyphSize, - aAngle, textStyle ); + VECTOR2D nextPosition = drawMarkup( aBoundingBox, glyphs, aText, aPosition, aSize, aAngle, + aMirror, aOrigin, textStyle ); for( const std::unique_ptr& glyph : glyphs ) - { - if( aIsMirrored ) - glyph->Mirror( aPosition ); - aGal->DrawGlyph( *glyph.get() ); - } return nextPosition; } VECTOR2D FONT::boundingBoxSingleLine( BOX2I* aBoundingBox, const UTF8& aText, - const VECTOR2D& aPosition, const VECTOR2D& aGlyphSize, - const EDA_ANGLE& aAngle, bool aIsItalic ) const + const VECTOR2I& aPosition, const VECTOR2D& aSize, + bool aItalic ) const { TEXT_STYLE_FLAGS textStyle = 0; - if( aIsItalic ) + if( aItalic ) textStyle |= TEXT_STYLE::ITALIC; std::vector> glyphs; // ignored - VECTOR2D nextPosition = drawMarkup( aBoundingBox, glyphs, aText, aPosition, aGlyphSize, - aAngle, textStyle ); + VECTOR2D nextPosition = drawMarkup( aBoundingBox, glyphs, aText, aPosition, aSize, + EDA_ANGLE::ANGLE_0, false, VECTOR2I(), textStyle ); return nextPosition; } diff --git a/common/font/glyph.cpp b/common/font/glyph.cpp index 51c699c01a..59f7a37a59 100644 --- a/common/font/glyph.cpp +++ b/common/font/glyph.cpp @@ -20,6 +20,7 @@ #include #include +#include using namespace KIFONT; @@ -62,8 +63,9 @@ void STROKE_GLYPH::Finalize() } -std::unique_ptr STROKE_GLYPH::Transform( const VECTOR2D& aGlyphSize, const VECTOR2D& aOffset, - double aTilt ) +std::unique_ptr STROKE_GLYPH::Transform( const VECTOR2D& aGlyphSize, const VECTOR2I& aOffset, + double aTilt, const EDA_ANGLE& aAngle, bool aMirror, + const VECTOR2I& aOrigin ) { std::unique_ptr glyph = std::make_unique( *this ); @@ -82,14 +84,18 @@ std::unique_ptr STROKE_GLYPH::Transform( const VECTOR2D& aGlyphSize, cons { for( VECTOR2D& point : pointList ) { - point.x *= aGlyphSize.x; - point.y *= aGlyphSize.y; + point *= aGlyphSize; if( aTilt ) point.x -= point.y * aTilt; - point.x += aOffset.x; - point.y += aOffset.y; + point += aOffset; + + if( aMirror ) + point.x = aOrigin.x - ( point.x - aOrigin.x ); + + if( !aAngle.IsZero() ) + RotatePoint( point, aOrigin, aAngle ); } } @@ -97,35 +103,8 @@ std::unique_ptr STROKE_GLYPH::Transform( const VECTOR2D& aGlyphSize, cons } -void STROKE_GLYPH::Mirror( const VECTOR2D& aMirrorOrigin ) -{ - double originX = aMirrorOrigin.x; - - VECTOR2D pos = m_boundingBox.GetPosition(); - VECTOR2D end = m_boundingBox.GetEnd(); - - pos.x = originX - ( pos.x - originX ); - end.x = originX - ( end.x - originX ); - - m_boundingBox.SetOrigin( pos ); - m_boundingBox.SetEnd( end ); - - for( std::vector& pointList : *this ) - { - for( VECTOR2D& point : pointList ) - point.x = originX - ( point.x - originX ); - } -} - - BOX2D OUTLINE_GLYPH::BoundingBox() { BOX2I bbox = BBox(); return BOX2D( bbox.GetOrigin(), bbox.GetSize() ); } - - -void OUTLINE_GLYPH::Mirror( const VECTOR2D& aMirrorOrigin ) -{ - SHAPE_POLY_SET::Mirror( true, false, aMirrorOrigin ); -} diff --git a/common/font/outline_font.cpp b/common/font/outline_font.cpp index ac586959c1..cb0e664ada 100644 --- a/common/font/outline_font.cpp +++ b/common/font/outline_font.cpp @@ -36,14 +36,30 @@ #include FT_BBOX_H #include #include +#include using namespace KIFONT; + +// The height of the KiCad stroke font is the distance between stroke endpoints for a vertical +// line of cap-height. So the cap-height of the font is actually stroke-width taller than its +// height. +// Outline fonts are normally scaled on full-height (including ascenders and descenders), so we +// need to compensate to keep them from being much smaller than their stroked counterparts. +constexpr double OUTLINE_FONT_SIZE_COMPENSATION = 1.4; + +// The KiCad stroke font uses a subscript/superscript size ratio of 0.7. This ratio is also +// commonly used in LaTeX, but fonts with designed-in subscript and superscript glyphs are more +// likely to use 0.58. +// For auto-generated subscript and superscript glyphs in outline fonts we split the difference +// with 0.64. +static constexpr double SUBSCRIPT_SUPERSCRIPT_SIZE = 0.64; + + FT_Library OUTLINE_FONT::m_freeType = nullptr; OUTLINE_FONT::OUTLINE_FONT() : - m_faceSize( 16 ), - m_subscriptSize( 13 ) + m_faceSize( 16 ) { if( !m_freeType ) { @@ -83,27 +99,6 @@ bool OUTLINE_FONT::loadFontSimple( const wxString& aFontFileName ) // TODO: handle ft_error properly (now we just return false if load does not succeed) FT_Error ft_error = loadFace( fileName ); - if( ft_error ) - { - // Try user dir - fontFile.SetExt( "otf" ); - fontFile.SetPath( Pgm().GetSettingsManager().GetUserSettingsPath() + wxT( "/fonts" ) ); - fileName = fontFile.GetFullPath(); - - if( wxFile::Exists( fileName ) ) - { - ft_error = loadFace( fileName ); - } - else - { - fontFile.SetExt( "ttf" ); - fileName = fontFile.GetFullPath(); - - if( wxFile::Exists( fileName ) ) - ft_error = loadFace( fileName ); - } - } - if( ft_error == FT_Err_Unknown_File_Format ) { wxLogWarning( _( "The font file %s could be opened and read, " @@ -128,7 +123,7 @@ bool OUTLINE_FONT::loadFontSimple( const wxString& aFontFileName ) FT_Error OUTLINE_FONT::loadFace( const wxString& aFontFileName ) { m_faceScaler = m_faceSize * 64; - m_subscriptFaceScaler = m_subscriptSize * 64; + m_subscriptFaceScaler = KiROUND( m_faceSize * 64 * SUBSCRIPT_SUPERSCRIPT_SIZE ); // TODO: check that going from wxString to char* with UTF-8 // conversion for filename makes sense on any/all platforms @@ -223,10 +218,12 @@ double OUTLINE_FONT::ComputeOverbarVerticalPosition( double aGlyphHeight ) const */ double OUTLINE_FONT::GetInterline( double aGlyphHeight, double aLineSpacing ) const { + double pitch = INTERLINE_PITCH_RATIO; + if( GetFace()->units_per_EM ) - return ( aLineSpacing * aGlyphHeight * ( GetFace()->height / GetFace()->units_per_EM ) ); - else - return ( aLineSpacing * aGlyphHeight * INTERLINE_PITCH_RATIO ); + pitch = GetFace()->height / GetFace()->units_per_EM; + + return ( aLineSpacing * aGlyphHeight * pitch * OUTLINE_FONT_SIZE_COMPENSATION ); } @@ -292,32 +289,34 @@ VECTOR2I OUTLINE_FONT::GetLinesAsGlyphs( std::vector>& aG const EDA_TEXT* aText ) const { wxArrayString strings; - std::vector positions; - int n; + std::vector positions; VECTOR2I ret; std::vector boundingBoxes; + TEXT_ATTRIBUTES attrs = aText->GetAttributes(); TEXT_STYLE_FLAGS textStyle = 0; + attrs.m_Angle = aText->GetDrawRotation(); + if( aText->IsItalic() ) textStyle |= TEXT_STYLE::ITALIC; - getLinePositions( aText->GetShownText(), aText->GetTextPos(), strings, positions, n, - boundingBoxes, aText->GetAttributes() ); + getLinePositions( aText->GetShownText(), aText->GetTextPos(), strings, positions, boundingBoxes, + attrs ); - for( int i = 0; i < n; i++ ) + for( size_t i = 0; i < strings.GetCount(); i++ ) { - ret = drawMarkup( nullptr, aGlyphs, UTF8( strings.Item( i ) ), positions[i], - aText->GetTextSize(), aText->GetTextAngle(), textStyle ); + ret = drawMarkup( nullptr, aGlyphs, UTF8( strings.Item( i ) ), positions[i], attrs.m_Size, + attrs.m_Angle, attrs.m_Mirrored, aText->GetTextPos(), textStyle ); } return ret; } -VECTOR2I OUTLINE_FONT::GetTextAsGlyphs( BOX2I* aBoundingBox, - std::vector>& aGlyphs, - const UTF8& aText, const VECTOR2D& aGlyphSize, - const wxPoint& aPosition, const EDA_ANGLE& aOrientation, +VECTOR2I OUTLINE_FONT::GetTextAsGlyphs( BOX2I* aBBox, std::vector>& aGlyphs, + const UTF8& aText, const VECTOR2D& aSize, + const VECTOR2I& aPosition, const EDA_ANGLE& aAngle, + bool aMirror, const VECTOR2I& aOrigin, TEXT_STYLE_FLAGS aTextStyle ) const { hb_buffer_t* buf = hb_buffer_create(); @@ -331,28 +330,22 @@ VECTOR2I OUTLINE_FONT::GetTextAsGlyphs( BOX2I* aBoundingBox, hb_glyph_position_t* glyphPos = hb_buffer_get_glyph_positions( buf, &glyphCount ); hb_font_t* referencedFont; - //const double subscriptAndSuperscriptScaler = 0.5; - VECTOR2D glyphSize = aGlyphSize; + VECTOR2D glyphSize = aSize; FT_Face face = m_face; - int scaler = m_faceScaler; + double scaler = m_faceScaler / OUTLINE_FONT_SIZE_COMPENSATION; if( IsSubscript( aTextStyle ) || IsSuperscript( aTextStyle ) ) - { face = m_subscriptFace; - //scaler = m_subscriptFaceScaler; - } referencedFont = hb_ft_font_create_referenced( face ); hb_ft_font_set_funcs( referencedFont ); hb_shape( referencedFont, buf, nullptr, 0 ); - const VECTOR2D scaleFactor( -glyphSize.x / scaler, glyphSize.y / scaler ); + const VECTOR2D scaleFactor( glyphSize.x / scaler, -glyphSize.y / scaler ); VECTOR2I cursor( 0, 0 ); - VECTOR2I extentBottomLeft( INT_MAX, INT_MAX ); - VECTOR2I extentTopRight( INT_MIN, INT_MIN ); - VECTOR2I vBottomLeft( INT_MAX, INT_MAX ); - VECTOR2I vTopRight( INT_MIN, INT_MIN ); + VECTOR2D topLeft( INT_MAX * 1.0, -INT_MAX * 1.0 ); + VECTOR2D topRight( -INT_MAX * 1.0, -INT_MAX * 1.0 ); for( unsigned int i = 0; i < glyphCount; i++ ) { @@ -380,46 +373,34 @@ VECTOR2I OUTLINE_FONT::GetTextAsGlyphs( BOX2I* aBoundingBox, GLYPH_POINTS points = c.points; SHAPE_LINE_CHAIN shape; - VECTOR2D offset( aPosition ); - - if( IsSubscript( aTextStyle ) ) - offset.y += glyphSize.y * 0.1; - else if( IsSuperscript( aTextStyle ) ) - offset.y -= glyphSize.y * 0.2; - for( const VECTOR2D& v : points ) { - // Save text extents - if( vBottomLeft.x > v.x ) - vBottomLeft.x = v.x; - if( vBottomLeft.y > v.y ) - vBottomLeft.y = v.y; - if( vTopRight.x < v.x ) - vTopRight.x = v.x; - if( vTopRight.y < v.y ) - vTopRight.y = v.y; + VECTOR2D pt( v + cursor ); - VECTOR2D pt( v.x, v.y ); - VECTOR2D ptC( pt.x + cursor.x, pt.y + cursor.y ); - wxPoint scaledPtOrig( -ptC.x * scaleFactor.x, -ptC.y * scaleFactor.y ); - wxPoint scaledPt( scaledPtOrig ); - RotatePoint( &scaledPt.x, &scaledPt.y, aOrientation.AsTenthsOfADegree() ); - scaledPt.x += offset.x; - scaledPt.y += offset.y; + topLeft.x = std::min( topLeft.x, pt.x ); + topLeft.y = std::max( topLeft.y, pt.y ); + topRight.x = std::max( topRight.x, pt.x ); + topRight.y = std::max( topRight.y, pt.y ); - if( extentBottomLeft.x > scaledPt.x ) - extentBottomLeft.x = scaledPt.x; - if( extentBottomLeft.y > scaledPt.y ) - extentBottomLeft.y = scaledPt.y; - if( extentTopRight.x < scaledPt.x ) - extentTopRight.x = scaledPt.x; - if( extentTopRight.y < scaledPt.y ) - extentTopRight.y = scaledPt.y; + if( IsSubscript( aTextStyle ) ) + pt.y -= 0.25 * scaler; + else if( IsSuperscript( aTextStyle ) ) + pt.y += 0.45 * scaler; - shape.Append( scaledPt.x, scaledPt.y ); - //ptListScaled.push_back( scaledPt ); + pt *= scaleFactor; + pt += aPosition; + + if( aMirror ) + pt.x = aOrigin.x - ( pt.x - aOrigin.x ); + + if( !aAngle.IsZero() ) + RotatePoint( pt, aOrigin, aAngle ); + + shape.Append( pt.x, pt.y ); } + shape.SetClosed( true ); + if( contourIsHole( c ) ) holes.push_back( std::move( shape ) ); else @@ -429,10 +410,7 @@ VECTOR2I OUTLINE_FONT::GetTextAsGlyphs( BOX2I* aBoundingBox, for( SHAPE_LINE_CHAIN& outline : outlines ) { if( outline.PointCount() ) - { - outline.SetClosed( true ); glyph->AddOutline( outline ); - } } int nthHole = 0; @@ -441,17 +419,14 @@ VECTOR2I OUTLINE_FONT::GetTextAsGlyphs( BOX2I* aBoundingBox, { if( hole.PointCount() ) { - hole.SetClosed( true ); VECTOR2I firstPoint = hole.GetPoint( 0 ); - //SHAPE_SIMPLE *outlineForHole = nullptr; - int nthOutline = -1; - int n = 0; + int nthOutline = -1; + int n = 0; for( SHAPE_LINE_CHAIN& outline : outlines ) { if( outline.PointInside( firstPoint ) ) { - //outlineForHole = outline; nthOutline = n; break; } @@ -476,106 +451,52 @@ VECTOR2I OUTLINE_FONT::GetTextAsGlyphs( BOX2I* aBoundingBox, if( IsOverbar( aTextStyle ) ) { - std::unique_ptr overbarGlyph = std::make_unique(); - SHAPE_LINE_CHAIN overbar; + topLeft *= scaleFactor; + topRight *= scaleFactor; - int left = extentBottomLeft.x; - int right = extentTopRight.x; - int top = extentBottomLeft.y - 800; - int barHeight = -3200; + topLeft.y -= aSize.y * 0.16; + topRight.y -= aSize.y * 0.16; - overbar.Append( VECTOR2D( left, top ) ); - overbar.Append( VECTOR2D( right, top ) ); - overbar.Append( VECTOR2D( right, top + barHeight ) ); - overbar.Append( VECTOR2D( left, top + barHeight ) ); - overbar.SetClosed( true ); + topLeft += aPosition; + topRight += aPosition; - overbarGlyph->AddOutline( overbar ); + if( !aAngle.IsZero() ) + { + RotatePoint( topLeft, aOrigin, aAngle ); + RotatePoint( topRight, aOrigin, aAngle ); + } + double overbarHeight = aSize.y * 0.07; + SHAPE_POLY_SET overbar; + + TransformOvalToPolygon( overbar, topLeft, topRight, overbarHeight, overbarHeight / 8, + ERROR_INSIDE ); + + std::unique_ptr overbarGlyph = std::make_unique( overbar ); aGlyphs.push_back( std::move( overbarGlyph ) ); } hb_buffer_destroy( buf ); - VECTOR2I cursorDisplacement( -cursorEnd.x * scaleFactor.x, cursorEnd.y * scaleFactor.y ); + VECTOR2I cursorDisplacement( cursorEnd.x * scaleFactor.x, -cursorEnd.y * scaleFactor.y ); - if( aBoundingBox ) + if( aBBox ) { - aBoundingBox->SetOrigin( aPosition.x, aPosition.y ); - aBoundingBox->SetEnd( cursorDisplacement ); + aBBox->SetOrigin( aPosition.x, aPosition.y ); + aBBox->SetEnd( cursorDisplacement ); } return VECTOR2I( aPosition.x + cursorDisplacement.x, aPosition.y + cursorDisplacement.y ); } -VECTOR2D OUTLINE_FONT::getBoundingBox( const UTF8& aString, const VECTOR2D& aGlyphSize, - TEXT_STYLE_FLAGS aTextStyle ) const -{ - hb_buffer_t* buf = hb_buffer_create(); - hb_buffer_add_utf8( buf, aString.c_str(), -1, 0, -1 ); - - // guess direction, script, and language based on contents - hb_buffer_guess_segment_properties( buf ); - - FT_Face face = m_face; - int scaler = m_faceScaler; - - if( IsSubscript( aTextStyle ) || IsSuperscript( aTextStyle ) ) - face = m_subscriptFace; - - hb_font_t* referencedFont = hb_ft_font_create_referenced( face ); - hb_ft_font_set_funcs( referencedFont ); - hb_shape( referencedFont, buf, nullptr, 0 ); - - unsigned int glyphCount; - hb_glyph_info_t* glyphInfo = hb_buffer_get_glyph_infos( buf, &glyphCount ); - //hb_glyph_position_t* glyphPos = hb_buffer_get_glyph_positions( buf, &glyphCount ); - - VECTOR2D boundingBox( 0, 0 ); - - int xScaler = aGlyphSize.x / scaler; - int yScaler = aGlyphSize.y / scaler; - double maxHeight = 0.0; - - for( unsigned int i = 0; i < glyphCount; i++ ) - { - //hb_glyph_position_t& pos = glyphPos[i]; - int codepoint = glyphInfo[i].codepoint; - - FT_Load_Glyph( face, codepoint, FT_LOAD_NO_BITMAP ); - - FT_GlyphSlot glyphSlot = face->glyph; - FT_Glyph glyph; - FT_BBox controlBox; - - FT_Get_Glyph( glyphSlot, &glyph ); - FT_Glyph_Get_CBox( glyph, FT_Glyph_BBox_Mode::FT_GLYPH_BBOX_UNSCALED, &controlBox ); - - double width = controlBox.xMax * xScaler; - boundingBox.x += width; - - double height = controlBox.yMax * yScaler; - if( height > maxHeight ) - maxHeight = height; - - FT_Done_Glyph( glyph ); - } - boundingBox.y = aGlyphSize.y; //maxHeight; - - hb_buffer_destroy( buf ); - - return boundingBox; -} - - #undef OUTLINEFONT_RENDER_AS_PIXELS #ifdef OUTLINEFONT_RENDER_AS_PIXELS /* * WIP: eeschema (and PDF output?) should use pixel rendering instead of linear segmentation */ void OUTLINE_FONT::RenderToOpenGLCanvas( KIGFX::OPENGL_GAL& aGal, const UTF8& aString, - const VECTOR2D& aGlyphSize, const wxPoint& aPosition, + const VECTOR2D& aGlyphSize, const VECTOR2I& aPosition, const EDA_ANGLE& aOrientation, bool aIsMirrored ) const { hb_buffer_t* buf = hb_buffer_create(); diff --git a/common/font/stroke_font.cpp b/common/font/stroke_font.cpp index cfac3f1cc7..5eb8118094 100644 --- a/common/font/stroke_font.cpp +++ b/common/font/stroke_font.cpp @@ -211,28 +211,20 @@ VECTOR2D STROKE_FONT::StringBoundaryLimits( const KIGFX::GAL* aGal, const UTF8& BOX2I boundingBox; (void) drawMarkup( &boundingBox, glyphs, aText, VECTOR2D(), aGlyphSize, EDA_ANGLE::ANGLE_0, - 0 /* TODO: this should really include TEXT_STYLE::ITALIC if set */ ); + false, VECTOR2D(), 0 /* TODO: should include TEXT_STYLE::ITALIC if set */ ); return boundingBox.GetSize(); } -VECTOR2D STROKE_FONT::getBoundingBox( const UTF8& aString, const VECTOR2D& aGlyphSize, - TEXT_STYLE_FLAGS aTextStyle ) const -{ - // TODO: take glyph thickness into account! - return StringBoundaryLimits( nullptr, aString, aGlyphSize, 0 ); -} - - -VECTOR2I STROKE_FONT::GetTextAsGlyphs( BOX2I* aBoundingBox, - std::vector>& aGlyphs, - const UTF8& aText, const VECTOR2D& aGlyphSize, - const wxPoint& aPosition, const EDA_ANGLE& aAngle, +VECTOR2I STROKE_FONT::GetTextAsGlyphs( BOX2I* aBBox, std::vector>& aGlyphs, + const UTF8& aText, const VECTOR2D& aSize, + const VECTOR2I& aPosition, const EDA_ANGLE& aAngle, + bool aMirror, const VECTOR2I& aOrigin, TEXT_STYLE_FLAGS aTextStyle ) const { wxPoint cursor( aPosition ); - VECTOR2D glyphSize( aGlyphSize ); + VECTOR2D glyphSize( aSize ); double tilt = ( aTextStyle & TEXT_STYLE::ITALIC ) ? ITALIC_TILT : 0.0; if( aTextStyle & TEXT_STYLE::SUBSCRIPT || aTextStyle & TEXT_STYLE::SUPERSCRIPT ) @@ -282,7 +274,8 @@ VECTOR2I STROKE_FONT::GetTextAsGlyphs( BOX2I* aBoundingBox, { STROKE_GLYPH* source = static_cast( m_glyphs->at( dd ).get() ); - aGlyphs.push_back( source->Transform( glyphSize, cursor, tilt ) ); + aGlyphs.push_back( source->Transform( glyphSize, cursor, tilt, aAngle, aMirror, + aOrigin ) ); cursor.x = aGlyphs.back()->BoundingBox().GetEnd().x; } @@ -299,18 +292,27 @@ VECTOR2I STROKE_FONT::GetTextAsGlyphs( BOX2I* aBoundingBox, if( aTextStyle & TEXT_STYLE::ITALIC ) barOffset.x = barOffset.y * ITALIC_TILT; - overbarGlyph->AddPoint( VECTOR2D( aPosition.x + barOffset.x, cursor.y - barOffset.y ) ); - overbarGlyph->AddPoint( VECTOR2D( cursor.x + barOffset.x, cursor.y - barOffset.y ) ); + VECTOR2D barStart( aPosition.x + barOffset.x, cursor.y - barOffset.y ); + VECTOR2D barEnd( cursor.x + barOffset.x, cursor.y - barOffset.y ); + + if( !aAngle.IsZero() ) + { + RotatePoint( barStart, aOrigin, aAngle ); + RotatePoint( barEnd, aOrigin, aAngle ); + } + + overbarGlyph->AddPoint( barStart ); + overbarGlyph->AddPoint( barEnd ); overbarGlyph->Finalize(); aGlyphs.push_back( std::move( overbarGlyph ) ); } - if( aBoundingBox ) + if( aBBox ) { - aBoundingBox->SetOrigin( aPosition.x, aPosition.y ); - aBoundingBox->SetEnd( cursor.x + barOffset.x, cursor.y + std::max( glyphSize.y, barOffset.y ) ); - aBoundingBox->Normalize(); + aBBox->SetOrigin( aPosition ); + aBBox->SetEnd( cursor.x + barOffset.x, cursor.y + std::max( glyphSize.y, barOffset.y ) ); + aBBox->Normalize(); } return VECTOR2I( cursor.x, aPosition.y ); diff --git a/common/pcb.keywords b/common/pcb.keywords index 849d081728..8ab096de41 100644 --- a/common/pcb.keywords +++ b/common/pcb.keywords @@ -238,6 +238,7 @@ rect rect_delta reference remove_unused_layers +render_cache right rotate roundrect diff --git a/common/widgets/font_choice.cpp b/common/widgets/font_choice.cpp index 26d926d0d0..7a3dc3b463 100644 --- a/common/widgets/font_choice.cpp +++ b/common/widgets/font_choice.cpp @@ -53,16 +53,19 @@ void FONT_CHOICE::SetFontSelection( KIFONT::FONT* aFont ) if( !aFont ) { SetSelection( 0 ); - return; } - - SetStringSelection( aFont->Name() ); - - if( GetSelection() == wxNOT_FOUND ) + else { - Append( aFont->Name() + m_notFound ); - SetSelection( GetCount() ); + SetStringSelection( aFont->Name() ); + + if( GetSelection() == wxNOT_FOUND ) + { + Append( aFont->Name() + m_notFound ); + SetSelection( GetCount() ); + } } + + SendSelectionChangedEvent( wxEVT_CHOICE ); } diff --git a/include/eda_text.h b/include/eda_text.h index 50d6dcbc6d..b11f8b625f 100644 --- a/include/eda_text.h +++ b/include/eda_text.h @@ -30,6 +30,7 @@ #include #include +#include #include class OUTPUTFORMATTER; @@ -46,12 +47,19 @@ class wxFindReplaceData; */ struct TSEGM_2_POLY_PRMS { - int m_textWidth; - int m_error; + int m_textWidth; + int m_error; SHAPE_POLY_SET* m_cornerBuffer; }; +struct TSEGM_2_SHAPE_PRMS +{ + int m_penWidth; + SHAPE_COMPOUND* m_shape; +}; + + /** * Callback function used to convert text segments to polygons. */ @@ -97,6 +105,8 @@ public: virtual ~EDA_TEXT(); + EDA_TEXT& operator=( const EDA_TEXT& aItem ); + /** * Return the string associated with the text object. * @@ -128,7 +138,7 @@ public: * The TextThickness is that set by the user. The EffectiveTextPenWidth also factors * in bold text and thickness clamping. */ - void SetTextThickness( int aWidth ) { m_attributes.m_StrokeWidth = aWidth; }; + void SetTextThickness( int aWidth ); int GetTextThickness() const { return m_attributes.m_StrokeWidth; }; /** @@ -141,42 +151,38 @@ public: // Higher level classes may be more restrictive than this by overloading // SetTextAngle() or merely calling EDA_TEXT::SetTextAngle() after clamping // aAngle before calling this lowest inline accessor. - m_attributes.m_Angle = EDA_ANGLE( aAngleInTenthsOfADegree, EDA_ANGLE::TENTHS_OF_A_DEGREE ); - } - - void SetTextAngle( const EDA_ANGLE& aAngle ) - { - m_attributes.m_Angle = aAngle; + SetTextAngle( EDA_ANGLE( aAngleInTenthsOfADegree, EDA_ANGLE::TENTHS_OF_A_DEGREE ) ); } + void SetTextAngle( const EDA_ANGLE& aAngle ); const EDA_ANGLE& GetTextAngle() const { return m_attributes.m_Angle; } - void SetItalic( bool aItalic ) { m_attributes.m_Italic = aItalic; } + void SetItalic( bool aItalic ); bool IsItalic() const { return m_attributes.m_Italic; } - void SetBold( bool aBold ) { m_attributes.m_Bold = aBold; } + void SetBold( bool aBold ); bool IsBold() const { return m_attributes.m_Bold; } - virtual void SetVisible( bool aVisible ) { m_attributes.m_Visible = aVisible; } + virtual void SetVisible( bool aVisible ); virtual bool IsVisible() const { return m_attributes.m_Visible; } - void SetMirrored( bool isMirrored ) { m_attributes.m_Mirrored = isMirrored; } + void SetMirrored( bool isMirrored ); bool IsMirrored() const { return m_attributes.m_Mirrored; } /** * @param aAllow true if ok to use multiline option, false if ok to use only single line * text. (Single line is faster in calculations than multiline.) */ - void SetMultilineAllowed( bool aAllow ) { m_attributes.m_Multiline = aAllow; } + void SetMultilineAllowed( bool aAllow ); bool IsMultilineAllowed() const { return m_attributes.m_Multiline; } + void SetHorizJustify( GR_TEXT_H_ALIGN_T aType ); GR_TEXT_H_ALIGN_T GetHorizJustify() const { return m_attributes.m_Halign; }; + + void SetVertJustify( GR_TEXT_V_ALIGN_T aType ); GR_TEXT_V_ALIGN_T GetVertJustify() const { return m_attributes.m_Valign; }; - void SetHorizJustify( GR_TEXT_H_ALIGN_T aType ) { m_attributes.m_Halign = aType; }; - void SetVertJustify( GR_TEXT_V_ALIGN_T aType ) { m_attributes.m_Valign = aType; }; - - void SetKeepUpright( bool aKeepUpright ) { m_attributes.m_KeepUpright = aKeepUpright; } + void SetKeepUpright( bool aKeepUpright ); bool IsKeepUpright() const { return m_attributes.m_KeepUpright; } /** @@ -208,36 +214,35 @@ public: bool IsDefaultFormatting() const; - void SetFont( KIFONT::FONT* aFont ) { m_attributes.m_Font = aFont; } + void SetFont( KIFONT::FONT* aFont ); KIFONT::FONT* GetFont() const { return m_attributes.m_Font; } wxString GetFontName() const; - void SetLineSpacing( double aLineSpacing ) { m_attributes.m_LineSpacing = aLineSpacing; } + void SetLineSpacing( double aLineSpacing ); double GetLineSpacing() const { return m_attributes.m_LineSpacing; } - void SetTextSize( const wxSize& aNewSize ) { m_attributes.m_Size = aNewSize; } + void SetTextSize( const wxSize& aNewSize ); wxSize GetTextSize() const { return wxSize( m_attributes.m_Size.x, m_attributes.m_Size.y ); } - void SetTextWidth( int aWidth ) { m_attributes.m_Size.x = aWidth; } + void SetTextWidth( int aWidth ); int GetTextWidth() const { return m_attributes.m_Size.x; } - void SetTextHeight( int aHeight ) { m_attributes.m_Size.y = aHeight; } + void SetTextHeight( int aHeight ); int GetTextHeight() const { return m_attributes.m_Size.y; } - void SetTextPos( const VECTOR2I& aPoint ) { m_pos = aPoint; } + void SetTextPos( const VECTOR2I& aPoint ); const VECTOR2I& GetTextPos() const { return m_pos; } - void SetTextX( int aX ) { m_pos.x = aX; } - void SetTextY( int aY ) { m_pos.y = aY; } + void SetTextX( int aX ); + void SetTextY( int aY ); - void Offset( const VECTOR2I& aOffset ) { m_pos += aOffset; } + void Offset( const VECTOR2I& aOffset ); - void Empty() { m_text.Empty(); } + void Empty(); static GR_TEXT_H_ALIGN_T MapHorizJustify( int aHorizJustify ); - static GR_TEXT_V_ALIGN_T MapVertJustify( int aVertJustify ); /** @@ -251,14 +256,6 @@ public: void Print( const RENDER_SETTINGS* aSettings, const VECTOR2I& aOffset, const COLOR4D& aColor, OUTLINE_MODE aDisplay_mode = FILLED ); - /** - * Convert the text shape to a list of segment. - * - * Each segment is stored as 2 VECTOR2Is: the starting point and the ending point - * there are therefore 2*n points. - */ - std::vector TransformToSegmentList() const; - /** * Convert the text bounding box to a rectangular polygon depending on the text * orientation, the bounding box is not always horizontal or vertical @@ -340,10 +337,19 @@ public: virtual void Format( OUTPUTFORMATTER* aFormatter, int aNestLevel, int aControlBits ) const; virtual EDA_ANGLE GetDrawRotation() const { return GetTextAngle(); } - virtual VECTOR2I GetDrawPos() const { return GetTextPos(); } + virtual VECTOR2I GetDrawPos() const { return GetTextPos(); } virtual GR_TEXT_H_ALIGN_T GetDrawHorizJustify() const { return GetHorizJustify(); }; virtual GR_TEXT_V_ALIGN_T GetDrawVertJustify() const { return GetVertJustify(); }; + void ClearRenderCache() { m_render_cache.clear(); } + + std::vector>* + GetRenderCache( const wxString& forResolvedText ) const; + + // Support for reading the cache from disk. + void SetupRenderCache( const wxString& aResolvedText, const EDA_ANGLE& aAngle ); + void AddRenderCacheGlyph( const SHAPE_POLY_SET& aPoly ); + int Compare( const EDA_TEXT* aOther ) const; private: @@ -366,6 +372,10 @@ private: wxString m_shown_text; // Cache of unescaped text for efficient access bool m_shown_text_has_text_var_refs; + mutable wxString m_render_cache_text; + mutable EDA_ANGLE m_render_cache_angle; + mutable std::vector> m_render_cache; + TEXT_ATTRIBUTES m_attributes; VECTOR2I m_pos; }; diff --git a/include/font/font.h b/include/font/font.h index 8179104612..fe688f796c 100644 --- a/include/font/font.h +++ b/include/font/font.h @@ -119,19 +119,21 @@ public: * @param aGal is the graphics context. * @param aText is the text to be drawn. * @param aPosition is the text position in world coordinates. - * @param aRotationAngle is the text rotation angle + * @param aCursor is the current text position (for multiple text blocks within a single text + * object, such as a run of superscript characters) + * @param aAttrs are the styling attributes of the text, including its rotation * @return bounding box */ - VECTOR2D Draw( KIGFX::GAL* aGal, const UTF8& aText, const VECTOR2D& aPosition, - const VECTOR2D& aOrigin, const TEXT_ATTRIBUTES& aAttrs ) const; + VECTOR2D Draw( KIGFX::GAL* aGal, const UTF8& aText, const VECTOR2I& aPosition, + const VECTOR2I& aCursor, const TEXT_ATTRIBUTES& aAttrs ) const; - VECTOR2D Draw( KIGFX::GAL* aGal, const UTF8& aText, const VECTOR2D& aPosition, + VECTOR2D Draw( KIGFX::GAL* aGal, const UTF8& aText, const VECTOR2I& aPosition, const TEXT_ATTRIBUTES& aAttributes ) const { - return Draw( aGal, aText, aPosition, VECTOR2D( 0, 0 ), aAttributes ); + return Draw( aGal, aText, aPosition, VECTOR2I( 0, 0 ), aAttributes ); } - virtual void DrawText( KIGFX::GAL* aGal, const UTF8& aText, const VECTOR2D& aPosition, + virtual void DrawText( KIGFX::GAL* aGal, const UTF8& aText, const VECTOR2I& aPosition, const TEXT_ATTRIBUTES& aAttributes ) const; /** @@ -169,19 +171,21 @@ public: /** * Convert text string to an array of GLYPHs. * - * @param aBoundingBox pointer to a BOX2I that will set to the bounding box, or nullptr + * @param aBBox pointer to a BOX2I that will set to the bounding box, or nullptr * @param aGlyphs storage for the returned GLYPHs * @param aText text to convert to polygon/polyline - * @param aGlyphSize glyph size + * @param aSize is the cap-height and em-width of the text * @param aPosition position of text (cursor position before this text) * @param aAngle text angle + * @param aMirror is true if text should be drawn mirrored, false otherwise. + * @param aOrigin is the point around which the text should be rotated, mirrored, etc. * @param aTextStyle text style flags * @return text cursor position after this text */ - virtual VECTOR2I GetTextAsGlyphs( BOX2I* aBoundingBox, - std::vector>& aGlyphs, - const UTF8& aText, const VECTOR2D& aGlyphSize, - const wxPoint& aPosition, const EDA_ANGLE& aAngle, + virtual VECTOR2I GetTextAsGlyphs( BOX2I* aBBox, std::vector>& aGlyphs, + const UTF8& aText, const VECTOR2D& aSize, + const VECTOR2I& aPosition, const EDA_ANGLE& aAngle, + bool aMirror, const VECTOR2I& aOrigin, TEXT_STYLE_FLAGS aTextStyle ) const = 0; protected: @@ -205,45 +209,42 @@ protected: * function. * * @param aGal is a pointer to the graphics abstraction layer, or nullptr (nothing is drawn) - * @param aBoundingBox is a pointer to a BOX2I variable which will be set to the bounding box, - * or nullptr + * @param aBBox is an optional pointer to be filled with the bounding box. * @param aText is the text to be drawn. * @param aPosition is text position. + * @param aSize is the cap-height and em-width of the text * @param aAngle is text angle. - * @param aIsMirrored is true if text should be drawn mirrored, false otherwise. - * @return new cursor position + * @param aMirror is true if text should be drawn mirrored, false otherwise. + * @param aOrigin is the point around which the text should be rotated, mirrored, etc. + * @return new cursor position in non-rotated, non-mirrored coordinates */ VECTOR2D drawSingleLineText( KIGFX::GAL* aGal, BOX2I* aBoundingBox, const UTF8& aText, - const VECTOR2D& aPosition, const VECTOR2D& aGlyphSize, - const EDA_ANGLE& aAngle, bool aIsItalic, bool aIsMirrored ) const; + const VECTOR2I& aPosition, const VECTOR2D& aSize, + const EDA_ANGLE& aAngle, bool aMirror, const VECTOR2I& aOrigin, + bool aItalic ) const; /** * Computes the bounding box for a single line of text. * Multiline texts should be split before using the function. * - * @param aBoundingBox is a pointer to a BOX2I variable which will be set to the bounding box, - * or nullptr + * @param aBBox is an optional pointer to be filled with the bounding box. * @param aText is the text to be drawn. * @param aPosition is text position. - * @param aGlyphSize is glyph size. - * @param aAngle is text angle. + * @param aSize is the cap-height and em-width of the text. * @return new cursor position */ - VECTOR2D boundingBoxSingleLine( BOX2I* aBoundingBox, const UTF8& aText, - const VECTOR2D& aPosition, const VECTOR2D& aGlyphSize, - const EDA_ANGLE& aAngle, bool aIsItalic ) const; + VECTOR2D boundingBoxSingleLine( BOX2I* aBBox, const UTF8& aText, const VECTOR2I& aPosition, + const VECTOR2D& aSize, bool aItalic ) const; - void getLinePositions( const UTF8& aText, const VECTOR2D& aPosition, wxArrayString& aStringList, - std::vector& aPositions, int& aLineCount, + void getLinePositions( const UTF8& aText, const VECTOR2I& aPosition, + wxArrayString& aTextLines, std::vector& aPositions, std::vector& aBoundingBoxes, const TEXT_ATTRIBUTES& aAttributes ) const; - virtual VECTOR2D getBoundingBox( const UTF8& aString, const VECTOR2D& aGlyphSize, - TEXT_STYLE_FLAGS aTextStyle = 0 ) const = 0; - VECTOR2D drawMarkup( BOX2I* aBoundingBox, std::vector>& aGlyphs, - const UTF8& aText, const VECTOR2D& aPosition, const VECTOR2D& aGlyphSize, - const EDA_ANGLE& aAngle, TEXT_STYLE_FLAGS aTextStyle ) const; + const UTF8& aText, const VECTOR2I& aPosition, const VECTOR2D& aSize, + const EDA_ANGLE& aAngle, bool aMirror, const VECTOR2I& aOrigin, + TEXT_STYLE_FLAGS aTextStyle ) const; ///< Factor that determines the pitch between 2 lines. static constexpr double INTERLINE_PITCH_RATIO = 1.62; // The golden mean @@ -251,10 +252,8 @@ protected: private: static FONT* getDefaultFont(); - VECTOR2D doDrawString( KIGFX::GAL* aGal, const UTF8& aText, const VECTOR2D& aPosition, + VECTOR2D doDrawString( KIGFX::GAL* aGal, const UTF8& aText, const VECTOR2I& aPosition, bool aParse, const TEXT_ATTRIBUTES& aAttrs ) const; - VECTOR2D getBoundingBox( const UTF8& aText, TEXT_STYLE_FLAGS aTextStyle, - const TEXT_ATTRIBUTES& aAttrs ) const; protected: wxString m_fontName; ///< Font name diff --git a/include/font/glyph.h b/include/font/glyph.h index 48ef9fc63e..38d218e622 100644 --- a/include/font/glyph.h +++ b/include/font/glyph.h @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -42,8 +43,6 @@ public: virtual bool IsStroke() const { return false; } virtual BOX2D BoundingBox() = 0; - - virtual void Mirror( const VECTOR2D& aMirrorOrigin = { 0, 0 } ) = 0; }; @@ -54,11 +53,17 @@ public: SHAPE_POLY_SET() {} + OUTLINE_GLYPH( const OUTLINE_GLYPH& aGlyph ) : + SHAPE_POLY_SET( aGlyph ) + {} + + OUTLINE_GLYPH( const SHAPE_POLY_SET& aPoly ) : + SHAPE_POLY_SET( aPoly ) + {} + bool IsOutline() const override { return true; } BOX2D BoundingBox() override; - - void Mirror( const VECTOR2D& aMirrorOrigin = VECTOR2D( 0, 0 ) ) override; }; @@ -79,10 +84,9 @@ public: BOX2D BoundingBox() override { return m_boundingBox; } void SetBoundingBox( const BOX2D& bbox ) { m_boundingBox = bbox; } - void Mirror( const VECTOR2D& aMirrorOrigin = { 0, 0 } ) override; - - std::unique_ptr Transform( const VECTOR2D& aGlyphSize, const VECTOR2D& aOffset, - double aTilt ); + std::unique_ptr Transform( const VECTOR2D& aGlyphSize, const VECTOR2I& aOffset, + double aTilt, const EDA_ANGLE& aAngle, bool aMirror, + const VECTOR2I& aOrigin ); private: bool m_penIsDown = false; diff --git a/include/font/outline_font.h b/include/font/outline_font.h index fd3aa2e1cd..826ad4a086 100644 --- a/include/font/outline_font.h +++ b/include/font/outline_font.h @@ -111,8 +111,8 @@ public: VECTOR2I GetTextAsGlyphs( BOX2I* aBoundingBox, std::vector>& aGlyphs, - const UTF8& aText, const VECTOR2D& aGlyphSize, - const wxPoint& aPosition, const EDA_ANGLE& aAngle, + const UTF8& aText, const VECTOR2D& aSize, const VECTOR2I& aPosition, + const EDA_ANGLE& aAngle, bool aMirror, const VECTOR2I& aOrigin, TEXT_STYLE_FLAGS aTextStyle ) const override; /** @@ -130,15 +130,11 @@ public: #if 0 void RenderToOpenGLCanvas( KIGFX::OPENGL_FREETYPE& aTarget, const UTF8& aString, - const VECTOR2D& aGlyphSize, const wxPoint& aPosition, - double aOrientation, bool aIsMirrored ) const; + const VECTOR2D& aSize, const wxPoint& aPosition, + const EDA_ANGLE& aAngle, bool aMirror ) const; #endif protected: - VECTOR2D getBoundingBox( const UTF8& aString, const VECTOR2D& aGlyphSize, - TEXT_STYLE_FLAGS aTextStyle ) const override; - - FT_Error loadFace( const wxString& aFontFileName ); bool loadFontSimple( const wxString& aFontFileName ); @@ -151,10 +147,9 @@ private: FT_Face m_face; const int m_faceSize; FT_Face m_subscriptFace; - const int m_subscriptSize; - int m_faceScaler; - int m_subscriptFaceScaler; + int m_faceScaler; + int m_subscriptFaceScaler; // cache for glyphs converted to straight segments // key is glyph index (FT_GlyphSlot field glyph_index) diff --git a/include/font/stroke_font.h b/include/font/stroke_font.h index 48a1d56d0b..9d5abca45e 100644 --- a/include/font/stroke_font.h +++ b/include/font/stroke_font.h @@ -91,14 +91,10 @@ public: VECTOR2D ComputeTextLineSize( const KIGFX::GAL* aGal, const UTF8& aText ) const override; VECTOR2I GetTextAsGlyphs( BOX2I* aBoundingBox, std::vector>& aGlyphs, - const UTF8& aText, const VECTOR2D& aGlyphSize, - const wxPoint& aPosition, const EDA_ANGLE& aAngle, + const UTF8& aText, const VECTOR2D& aSize, const VECTOR2I& aPosition, + const EDA_ANGLE& aAngle, bool aMirror, const VECTOR2I& aOrigin, TEXT_STYLE_FLAGS aTextStyle ) const override; -protected: - VECTOR2D getBoundingBox( const UTF8& aString, const VECTOR2D& aGlyphSize, - TEXT_STYLE_FLAGS aTextStyle ) const override; - private: /** * Load the standard KiCad stroke font. diff --git a/libs/kimath/include/math/vector2d.h b/libs/kimath/include/math/vector2d.h index 3675627cbf..3d0cc3cbcb 100644 --- a/libs/kimath/include/math/vector2d.h +++ b/libs/kimath/include/math/vector2d.h @@ -220,6 +220,9 @@ public: /// Compound assignment operator VECTOR2& operator+=( const VECTOR2& aVector ); + /// Compound assignment operator + VECTOR2& operator*=( const VECTOR2& aVector ); + /// Compound assignment operator VECTOR2& operator+=( const T& aScalar ); @@ -346,6 +349,15 @@ VECTOR2& VECTOR2::operator+=( const VECTOR2& aVector ) } +template +VECTOR2& VECTOR2::operator*=( const VECTOR2& aVector ) +{ + x *= aVector.x; + y *= aVector.y; + return *this; +} + + template VECTOR2& VECTOR2::operator+=( const T& aScalar ) { diff --git a/libs/kimath/include/trigo.h b/libs/kimath/include/trigo.h index 215657af4d..29a1bd1bd8 100644 --- a/libs/kimath/include/trigo.h +++ b/libs/kimath/include/trigo.h @@ -99,9 +99,19 @@ inline void RotatePoint( VECTOR2I& point, const VECTOR2I& centre, EDA_ANGLE angl * Calculate the new coord point point for a center rotation center and angle in (1/10 degree). */ -void RotatePoint( double *pX, double *pY, double angle ); +void RotatePoint( double* pX, double* pY, double angle ); -void RotatePoint( double *pX, double *pY, double cx, double cy, double angle ); +inline void RotatePoint( VECTOR2D& point, EDA_ANGLE angle ) +{ + RotatePoint( &point.x, &point.y, angle.AsTenthsOfADegree() ); +} + +void RotatePoint( double* pX, double* pY, double cx, double cy, double angle ); + +inline void RotatePoint( VECTOR2D& point, const VECTOR2D& aCenter, EDA_ANGLE angle ) +{ + RotatePoint( &point.x, &point.y, aCenter.x, aCenter.y, angle.AsTenthsOfADegree() ); +} /** * Determine the center of an arc or circle given three points on its circumference. diff --git a/pcbnew/pcb_painter.cpp b/pcbnew/pcb_painter.cpp index 1eb5748304..7ae95e8b16 100644 --- a/pcbnew/pcb_painter.cpp +++ b/pcbnew/pcb_painter.cpp @@ -1554,7 +1554,7 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer ) } -void PCB_PAINTER::strokeText( const wxString& aText, const VECTOR2D& aPosition, +void PCB_PAINTER::strokeText( const wxString& aText, const VECTOR2I& aPosition, const TEXT_ATTRIBUTES& aAttrs ) { KIFONT::FONT* font = aAttrs.m_Font; @@ -1571,9 +1571,9 @@ void PCB_PAINTER::strokeText( const wxString& aText, const VECTOR2D& aPosition, void PCB_PAINTER::draw( const PCB_TEXT* aText, int aLayer ) { - wxString shownText( aText->GetShownText() ); + wxString resolvedText( aText->GetShownText() ); - if( shownText.Length() == 0 ) + if( resolvedText.Length() == 0 ) return; const COLOR4D& color = m_pcbSettings.GetColor( aText, aText->GetLayer() ); @@ -1589,15 +1589,25 @@ void PCB_PAINTER::draw( const PCB_TEXT* aText, int aLayer ) else attrs.m_StrokeWidth = getLineThickness( aText->GetEffectiveTextPenWidth() ); - strokeText( shownText, aText->GetTextPos(), attrs ); + std::vector>* cache = aText->GetRenderCache( resolvedText ); + + if( cache ) + { + for( const std::unique_ptr& glyph : *cache ) + m_gal->DrawGlyph( *glyph.get() ); + } + else + { + strokeText( resolvedText, aText->GetTextPos(), attrs ); + } } void PCB_PAINTER::draw( const FP_TEXT* aText, int aLayer ) { - wxString shownText( aText->GetShownText() ); + wxString resolvedText( aText->GetShownText() ); - if( shownText.Length() == 0 ) + if( resolvedText.Length() == 0 ) return; const COLOR4D& color = m_pcbSettings.GetColor( aText, aLayer ); @@ -1615,7 +1625,17 @@ void PCB_PAINTER::draw( const FP_TEXT* aText, int aLayer ) else attrs.m_StrokeWidth = getLineThickness( aText->GetEffectiveTextPenWidth() ); - strokeText( shownText, aText->GetTextPos(), attrs ); + std::vector>* cache = aText->GetRenderCache( resolvedText ); + + if( cache ) + { + for( const std::unique_ptr& glyph : *cache ) + m_gal->DrawGlyph( *glyph.get() ); + } + else + { + strokeText( resolvedText, aText->GetTextPos(), attrs ); + } // Draw the umbilical line if( aText->IsSelected() ) @@ -1848,6 +1868,7 @@ void PCB_PAINTER::draw( const PCB_DIMENSION_BASE* aDimension, int aLayer ) // Draw text const PCB_TEXT& text = aDimension->Text(); + wxString resolvedText = text.GetShownText(); VECTOR2D position( text.GetTextPos().x, text.GetTextPos().y ); TEXT_ATTRIBUTES attrs = text.GetAttributes(); @@ -1856,7 +1877,17 @@ void PCB_PAINTER::draw( const PCB_DIMENSION_BASE* aDimension, int aLayer ) else attrs.m_StrokeWidth = getLineThickness( text.GetEffectiveTextPenWidth() ); - strokeText( text.GetShownText(), position, attrs ); + std::vector>* cache = text.GetRenderCache( resolvedText ); + + if( cache ) + { + for( const std::unique_ptr& glyph : *cache ) + m_gal->DrawGlyph( *glyph.get() ); + } + else + { + strokeText( resolvedText, position, attrs ); + } } diff --git a/pcbnew/pcb_painter.h b/pcbnew/pcb_painter.h index 2b4bedf139..0c1e9933de 100644 --- a/pcbnew/pcb_painter.h +++ b/pcbnew/pcb_painter.h @@ -201,7 +201,7 @@ protected: */ virtual int getDrillSize( const PCB_VIA* aVia ) const; - void strokeText( const wxString& aText, const VECTOR2D& aPosition, + void strokeText( const wxString& aText, const VECTOR2I& aPosition, const TEXT_ATTRIBUTES& aAttrs ); protected: diff --git a/pcbnew/plugins/kicad/pcb_parser.cpp b/pcbnew/plugins/kicad/pcb_parser.cpp index 3db7659bd9..4212e146c1 100644 --- a/pcbnew/plugins/kicad/pcb_parser.cpp +++ b/pcbnew/plugins/kicad/pcb_parser.cpp @@ -537,6 +537,57 @@ void PCB_PARSER::parseEDA_TEXT( EDA_TEXT* aText ) } +void PCB_PARSER::parseRenderCache( EDA_TEXT* text ) +{ + T token; + + NeedSYMBOLorNUMBER(); + wxString cacheText = FROM_UTF8( CurText() ); + double cacheAngle = parseAngle( "render cache angle" ); + + text->SetupRenderCache( cacheText, + EDA_ANGLE( cacheAngle, EDA_ANGLE::TENTHS_OF_A_DEGREE ) ); + + for( token = NextTok(); token != T_RIGHT; token = NextTok() ) + { + if( token != T_LEFT ) + Expecting( T_LEFT ); + + token = NextTok(); + + if( token != T_polygon ) + Expecting( T_polygon ); + + SHAPE_POLY_SET poly; + + for( token = NextTok(); token != T_RIGHT; token = NextTok() ) + { + if( token != T_LEFT ) + Expecting( T_LEFT ); + + token = NextTok(); + + if( token != T_pts ) + Expecting( T_pts ); + + SHAPE_LINE_CHAIN lineChain; + + while( (token = NextTok() ) != T_RIGHT ) + parseOutlinePoints( lineChain ); + + lineChain.SetClosed( true ); + + if( poly.OutlineCount() == 0 ) + poly.AddOutline( lineChain ); + else + poly.AddHole( lineChain ); + } + + text->AddRenderCacheGlyph( poly ); + } +} + + FP_3DMODEL* PCB_PARSER::parse3DModel() { wxCHECK_MSG( CurTok() == T_model, nullptr, @@ -2811,7 +2862,11 @@ PCB_TEXT* PCB_PARSER::parsePCB_TEXT() break; case T_effects: - parseEDA_TEXT( (EDA_TEXT*) text.get() ); + parseEDA_TEXT( static_cast( text.get() ) ); + break; + + case T_render_cache: + parseRenderCache( static_cast( text.get() ) ); break; default: @@ -3714,7 +3769,11 @@ FP_TEXT* PCB_PARSER::parseFP_TEXT() break; case T_effects: - parseEDA_TEXT( (EDA_TEXT*) text.get() ); + parseEDA_TEXT( static_cast( text.get() ) ); + break; + + case T_render_cache: + parseRenderCache( static_cast( text.get() ) ); break; case T_tstamp: diff --git a/pcbnew/plugins/kicad/pcb_parser.h b/pcbnew/plugins/kicad/pcb_parser.h index a8d02da8a1..6a05e7b56d 100644 --- a/pcbnew/plugins/kicad/pcb_parser.h +++ b/pcbnew/plugins/kicad/pcb_parser.h @@ -254,11 +254,19 @@ private: /** * Parse the common settings for any object derived from #EDA_TEXT. * - * @param aText A point to the #EDA_TEXT object to save the parsed settings into. + * @param aText A pointer to the #EDA_TEXT object to save the parsed settings into. * @throw PARSE_ERROR if the text syntax is not valid. */ void parseEDA_TEXT( EDA_TEXT* aText ); + /** + * Parse the render cache for any object derived from #EDA_TEXT. + * + * @param aText A pointer to the #EDA_TEXT object to save the parsed settings into. + * @throw PARSE_ERROR if the text syntax is not valid. + */ + void parseRenderCache( EDA_TEXT* text ); + FP_3DMODEL* parse3DModel(); /** diff --git a/pcbnew/plugins/kicad/pcb_plugin.cpp b/pcbnew/plugins/kicad/pcb_plugin.cpp index cfd92bf8b8..d40e576383 100644 --- a/pcbnew/plugins/kicad/pcb_plugin.cpp +++ b/pcbnew/plugins/kicad/pcb_plugin.cpp @@ -470,6 +470,92 @@ void PCB_PLUGIN::formatLayer( const BOARD_ITEM* aItem ) const } +void PCB_PLUGIN::formatPolyPts( const SHAPE_LINE_CHAIN& outline, int aNestLevel, + bool aCompact ) const +{ + m_out->Print( aNestLevel + 1, "(pts\n" ); + + bool needNewline = false; + int nestLevel = aNestLevel + 2; + int shapesAdded = 0; + + for( int ii = 0; ii < outline.PointCount(); ++ii ) + { + int ind = outline.ArcIndex( ii ); + + if( ind < 0 ) + { + m_out->Print( nestLevel, "(xy %s)", + FormatInternalUnits( outline.CPoint( ii ) ).c_str() ); + needNewline = true; + } + else + { + const SHAPE_ARC& arc = outline.Arc( ind ); + m_out->Print( nestLevel, "(arc (start %s) (mid %s) (end %s))", + FormatInternalUnits( arc.GetP0() ).c_str(), + FormatInternalUnits( arc.GetArcMid() ).c_str(), + FormatInternalUnits( arc.GetP1() ).c_str() ); + needNewline = true; + + do + { + ++ii; + } while( ii < outline.PointCount() && outline.ArcIndex( ii ) == ind ); + + --ii; + } + + ++shapesAdded; + + if( !( shapesAdded % 4 ) || !aCompact ) + { + // newline every 4 shapes if compact save + m_out->Print( 0, "\n" ); + needNewline = false; + } + } + + if( needNewline ) + m_out->Print( 0, "\n" ); + + m_out->Print( aNestLevel + 1, ")\n" ); +} + + +void PCB_PLUGIN::formatRenderCache( const EDA_TEXT* aText, int aNestLevel ) const +{ + const wxString& shownText = aText->GetShownText(); + std::vector>* cache = aText->GetRenderCache( shownText ); + + m_out->Print( aNestLevel, "(render_cache %s %s\n", + m_out->Quotew( shownText ).c_str(), + FormatAngle( aText->GetDrawRotation().AsTenthsOfADegree() ).c_str() ); + + for( const std::unique_ptr& baseGlyph : *cache ) + { + KIFONT::OUTLINE_GLYPH* glyph = static_cast( baseGlyph.get() ); + + if( glyph->OutlineCount() > 0 ) + { + for( int ii = 0; ii < glyph->OutlineCount(); ++ii ) + { + m_out->Print( aNestLevel + 1, "(polygon\n" ); + + formatPolyPts( glyph->Outline( ii ), aNestLevel + 1, true ); + + for( int jj = 0; jj < glyph->HoleCount( ii ); ++jj ) + formatPolyPts( glyph->Hole( ii, jj ), aNestLevel + 2, true ); + + m_out->Print( aNestLevel + 1, ")\n" ); + } + } + } + + m_out->Print( aNestLevel, ")\n" ); +} + + void PCB_PLUGIN::formatSetup( const BOARD* aBoard, int aNestLevel ) const { // Setup @@ -854,53 +940,8 @@ void PCB_PLUGIN::format( const PCB_SHAPE* aShape, int aNestLevel ) const const SHAPE_LINE_CHAIN& outline = poly.Outline( 0 ); m_out->Print( aNestLevel, "(gr_poly%s\n", locked.c_str() ); - m_out->Print( aNestLevel + 1, "(pts\n" ); - - bool needNewline = false; - int nestLevel = aNestLevel + 2; - int shapesAdded = 0; - - for( int ii = 0; ii < outline.PointCount(); ++ii ) - { - int ind = outline.ArcIndex( ii ); - - if( ind < 0 ) - { - m_out->Print( nestLevel, "(xy %s)", - FormatInternalUnits( outline.CPoint( ii ) ).c_str() ); - needNewline = true; - } - else - { - const SHAPE_ARC& arc = outline.Arc( ind ); - m_out->Print( nestLevel, "(arc (start %s) (mid %s) (end %s))", - FormatInternalUnits( arc.GetP0() ).c_str(), - FormatInternalUnits( arc.GetArcMid() ).c_str(), - FormatInternalUnits( arc.GetP1() ).c_str() ); - needNewline = true; - - do - { - ++ii; - } while( ii < outline.PointCount() && outline.ArcIndex( ii ) == ind ); - - --ii; - } - - ++shapesAdded; - - if( !( shapesAdded % 4 ) || !ADVANCED_CFG::GetCfg().m_CompactSave ) - { - // newline every 4 shapes if compact save - m_out->Print( 0, "\n" ); - needNewline = false; - } - } - - if( needNewline ) - m_out->Print( 0, "\n" ); - - m_out->Print( aNestLevel + 1, ")" ); + formatPolyPts( outline, aNestLevel, ADVANCED_CFG::GetCfg().m_CompactSave ); + m_out->Print( aNestLevel + 1, ")\n" ); } else { @@ -988,55 +1029,9 @@ void PCB_PLUGIN::format( const FP_SHAPE* aFPShape, int aNestLevel ) const const SHAPE_POLY_SET& poly = aFPShape->GetPolyShape(); const SHAPE_LINE_CHAIN& outline = poly.Outline( 0 ); - m_out->Print( aNestLevel, "(fp_poly%s (pts", - locked.c_str() ); - - bool need_newline = false; - - for( int ii = 0; ii < outline.PointCount(); ++ii ) - { - int nestLevel = 0; - - if( !( ii % 4 ) || !ADVANCED_CFG::GetCfg().m_CompactSave ) - { - // newline every 4 pts. - m_out->Print( 0, "\n" ); - need_newline = false; - nestLevel = aNestLevel + 2; - } - - int ind = outline.ArcIndex( ii ); - - if( ind < 0 ) - { - m_out->Print( nestLevel, "%s(xy %s)", - nestLevel ? "" : " ", - FormatInternalUnits( outline.CPoint( ii ) ).c_str() ); - need_newline = true; - } - else - { - auto& arc = outline.Arc( ind ); - m_out->Print( nestLevel, "%s(arc (start %s) (mid %s) (end %s))", - nestLevel ? "" : " ", - FormatInternalUnits( arc.GetP0() ).c_str(), - FormatInternalUnits( arc.GetArcMid() ).c_str(), - FormatInternalUnits( arc.GetP1() ).c_str() ); - need_newline = true; - - do - { - ++ii; - } while( ii < outline.PointCount() && outline.ArcIndex( ii ) == ind ); - - --ii; - } - } - - if( need_newline ) - m_out->Print( 0, "\n" ); - - m_out->Print( aNestLevel + 1, ")" ); + m_out->Print( aNestLevel, "(fp_poly%s\n", locked.c_str() ); + formatPolyPts( outline, aNestLevel, ADVANCED_CFG::GetCfg().m_CompactSave ); + m_out->Print( aNestLevel + 1, ")\n" ); } else { @@ -1691,54 +1686,9 @@ void PCB_PLUGIN::format( const PAD* aPad, int aNestLevel ) const const SHAPE_POLY_SET& poly = primitive->GetPolyShape(); const SHAPE_LINE_CHAIN& outline = poly.Outline( 0 ); - m_out->Print( nested_level, "(gr_poly (pts" ); - - bool need_newline = false; - - for( int ii = 0; ii < outline.PointCount(); ++ii ) - { - nested_level = 0; - - if( !( ii % 4 ) || !ADVANCED_CFG::GetCfg().m_CompactSave ) - { - // newline every 4 pts. - m_out->Print( 0, "\n" ); - need_newline = false; - nested_level = aNestLevel + 4; - } - - int ind = outline.ArcIndex( ii ); - - if( ind < 0 ) - { - m_out->Print( nested_level, "%s(xy %s)", - nested_level ? "" : " ", - FormatInternalUnits( outline.CPoint( ii ) ).c_str() ); - need_newline = true; - } - else - { - const SHAPE_ARC& arc = outline.Arc( ind ); - m_out->Print( nested_level, "%s(arc (start %s) (mid %s) (end %s))", - nested_level ? "" : " ", - FormatInternalUnits( arc.GetP0() ).c_str(), - FormatInternalUnits( arc.GetArcMid() ).c_str(), - FormatInternalUnits( arc.GetP1() ).c_str() ); - need_newline = true; - - do - { - ++ii; - } while( ii < outline.PointCount() && outline.ArcIndex( ii ) == ind ); - - --ii; - } - } - - if( need_newline ) - m_out->Print( 0, "\n" ); - - m_out->Print( aNestLevel + 3, ")" ); + m_out->Print( nested_level, "(gr_poly\n" ); + formatPolyPts( outline, nested_level, ADVANCED_CFG::GetCfg().m_CompactSave ); + m_out->Print( aNestLevel + 1, ")\n" ); } break; @@ -1785,6 +1735,9 @@ void PCB_PLUGIN::format( const PCB_TEXT* aText, int aNestLevel ) const // PCB_TEXTS are never hidden, so always omit "hide" attribute aText->EDA_TEXT::Format( m_out, aNestLevel, m_ctl | CTL_OMIT_HIDE ); + if( aText->GetFont() && aText->GetFont()->IsOutline() ) + formatRenderCache( aText, aNestLevel + 1 ); + m_out->Print( aNestLevel, ")\n" ); } @@ -1878,6 +1831,9 @@ void PCB_PLUGIN::format( const FP_TEXT* aText, int aNestLevel ) const m_out->Print( aNestLevel + 1, "(tstamp %s)\n", TO_UTF8( aText->m_Uuid.AsString() ) ); + if( aText->GetFont() && aText->GetFont()->IsOutline() ) + formatRenderCache( aText, aNestLevel + 1 ); + m_out->Print( aNestLevel, ")\n" ); } @@ -2149,55 +2105,8 @@ void PCB_PLUGIN::format( const ZONE* aZone, int aNestLevel ) const for( auto& chain : poly ) { m_out->Print( aNestLevel + 1, "(polygon\n" ); - m_out->Print( aNestLevel + 2, "(pts" ); - - bool need_newline = true; - - for( int ii = 0; ii < chain.PointCount(); ++ii ) - { - int nestLevel = 0; - - if( !( ii % 4 ) || !ADVANCED_CFG::GetCfg().m_CompactSave ) // newline every 4 pts - { - m_out->Print( 0, "\n" ); - need_newline = false; - nestLevel = aNestLevel + 3; - } - - int ind = chain.ArcIndex( ii ); - - if( ind < 0 ) - { - m_out->Print( nestLevel, "%s(xy %s)", - nestLevel ? "" : " ", - FormatInternalUnits( chain.CPoint( ii ) ).c_str() ); - need_newline = true; - } - else - { - auto& arc = chain.Arc( ind ); - m_out->Print( nestLevel, "%s(arc (start %s) (mid %s) (end %s))", - nestLevel ? "" : " ", - FormatInternalUnits( arc.GetP0() ).c_str(), - FormatInternalUnits( arc.GetArcMid() ).c_str(), - FormatInternalUnits( arc.GetP1() ).c_str() ); - need_newline = true; - - do - { - ++ii; - } while( ii < chain.PointCount() && chain.ArcIndex( ii ) == ind ); - - --ii; - } - } - - if( need_newline ) - m_out->Print( 0, "\n" ); - - m_out->Print( aNestLevel + 2, ")\n" ); + formatPolyPts( chain, aNestLevel + 1, ADVANCED_CFG::GetCfg().m_CompactSave ); m_out->Print( aNestLevel + 1, ")\n" ); - } } @@ -2215,56 +2124,10 @@ void PCB_PLUGIN::format( const ZONE* aZone, int aNestLevel ) const if( aZone->IsIsland( layer, ii ) ) m_out->Print( aNestLevel + 2, "(island)\n" ); - m_out->Print( aNestLevel + 2, "(pts" ); - const SHAPE_LINE_CHAIN& chain = fv.COutline( ii ); - bool need_newline = true; - - for( int jj = 0; jj < chain.PointCount(); ++jj ) - { - int nestLevel = 0; - - if( !( jj%4 ) || !ADVANCED_CFG::GetCfg().m_CompactSave ) // newline every 4 pts - { - m_out->Print( 0, "\n" ); - need_newline = false; - nestLevel = aNestLevel + 3; - } - - int ind = chain.ArcIndex( jj ); - - if( ind < 0 ) - { - m_out->Print( nestLevel, "%s(xy %s)", - nestLevel ? "" : " ", - FormatInternalUnits( chain.CPoint( jj ) ).c_str() ); - need_newline = true; - } - else - { - auto& arc = chain.Arc( ind ); - m_out->Print( nestLevel, "%s(arc (start %s) (mid %s) (end %s))", - nestLevel ? "" : " ", - FormatInternalUnits( arc.GetP0() ).c_str(), - FormatInternalUnits( arc.GetArcMid() ).c_str(), - FormatInternalUnits( arc.GetP1() ).c_str() ); - need_newline = true; - - do - { - ++jj; - } while( jj < chain.PointCount() && chain.ArcIndex( jj ) == ind ); - - --jj; - } - } - - if( need_newline ) - m_out->Print( 0, "\n" ); - - m_out->Print( aNestLevel+2, ")\n" ); - m_out->Print( aNestLevel+1, ")\n" ); + formatPolyPts( chain, aNestLevel + 1, ADVANCED_CFG::GetCfg().m_CompactSave ); + m_out->Print( aNestLevel + 1, ")\n" ); } // Save the filling segments list diff --git a/pcbnew/plugins/kicad/pcb_plugin.h b/pcbnew/plugins/kicad/pcb_plugin.h index 3f1cfecddc..c4dfa77c8c 100644 --- a/pcbnew/plugins/kicad/pcb_plugin.h +++ b/pcbnew/plugins/kicad/pcb_plugin.h @@ -45,6 +45,8 @@ class PCB_GROUP; class PCB_TRACK; class ZONE; class PCB_TEXT; +class EDA_TEXT; +class SHAPE_LINE_CHAIN; /// Current s-expression file format version. 2 was the last legacy format version. @@ -280,6 +282,10 @@ private: void format( const ZONE* aZone, int aNestLevel = 0 ) const; + void formatPolyPts( const SHAPE_LINE_CHAIN& outline, int aNestLevel, bool aCompact ) const; + + void formatRenderCache( const EDA_TEXT* aText, int aNestLevel ) const; + void formatLayer( const BOARD_ITEM* aItem ) const; void formatLayers( LSET aLayerMask, int aNestLevel = 0 ) const; diff --git a/pcbnew/router/pns_kicad_iface.cpp b/pcbnew/router/pns_kicad_iface.cpp index b81322a5e8..8ec9c2822d 100644 --- a/pcbnew/router/pns_kicad_iface.cpp +++ b/pcbnew/router/pns_kicad_iface.cpp @@ -1110,46 +1110,15 @@ bool PNS_KICAD_IFACE_BASE::syncTextItem( PNS::NODE* aWorld, EDA_TEXT* aText, PCB if( !IsCopperLayer( aLayer ) ) return false; - if( aText->GetFont() && aText->GetFont()->IsOutline() ) - { - for( SHAPE* shape : aText->GetEffectiveTextShape()->Shapes() ) - { - std::unique_ptr solid = std::make_unique(); + std::unique_ptr solid = std::make_unique(); - solid->SetLayer( aLayer ); - solid->SetNet( -1 ); - solid->SetParent( dynamic_cast( aText ) ); - solid->SetShape( shape ); - solid->SetIsCompoundShapePrimitive(); - solid->SetRoutable( false ); + solid->SetLayer( aLayer ); + solid->SetNet( -1 ); + solid->SetParent( dynamic_cast( aText ) ); + solid->SetShape( aText->GetEffectiveTextShape()->Clone() ); + solid->SetRoutable( false ); - aWorld->Add( std::move( solid ) ); - } - } - else - { - int textWidth = aText->GetEffectiveTextPenWidth(); - std::vector textShape = aText->TransformToSegmentList(); - - if( textShape.size() < 2 ) - return false; - - for( size_t jj = 0; jj < textShape.size(); jj += 2 ) - { - VECTOR2I start( textShape[jj] ); - VECTOR2I end( textShape[jj+1] ); - std::unique_ptr solid = std::make_unique(); - - solid->SetLayer( aLayer ); - solid->SetNet( -1 ); - solid->SetParent( dynamic_cast( aText ) ); - solid->SetShape( new SHAPE_SEGMENT( start, end, textWidth ) ); - solid->SetIsCompoundShapePrimitive(); - solid->SetRoutable( false ); - - aWorld->Add( std::move( solid ) ); - } - } + aWorld->Add( std::move( solid ) ); return true;