Give up on trying to calculate text bounding boxes.

It results in too many hacks strewn through the code.  Just draw the
text and measure it.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/14803
This commit is contained in:
Jeff Young 2023-05-26 15:39:48 +01:00
parent 6e127829f8
commit 768fbf5af2
7 changed files with 34 additions and 113 deletions

View File

@ -525,100 +525,35 @@ BOX2I EDA_TEXT::GetTextBox( int aLine, bool aInvertY ) const
return m_bounding_box_cache;
}
// We've tried a gazillion different ways of calculating bounding boxes for text; all of them
// fail in one case or another and we end up with compensation hacks strewed throughout the
// code. So I'm pulling the plug on it; we're just going to draw the damn text and see how
// big it is.
BOX2I bbox;
wxArrayString strings;
wxString text = GetShownText( true );
int thickness = GetEffectiveTextPenWidth();
if( IsMultilineAllowed() )
{
wxStringSplit( text, strings, '\n' );
if( strings.GetCount() ) // GetCount() == 0 for void strings with multilines allowed
{
if( aLine >= 0 && ( aLine < static_cast<int>( strings.GetCount() ) ) )
text = strings.Item( aLine );
else
text = strings.Item( 0 );
}
}
// calculate the H and V size
BOX2I strokeBBox;
KIGFX::GAL_DISPLAY_OPTIONS empty_opts;
KIFONT::FONT* font = getDrawFont();
VECTOR2D fontSize( GetTextSize() );
bool bold = IsBold();
bool italic = IsItalic();
VECTOR2I extents = font->StringBoundaryLimits( text, fontSize, thickness, bold, italic );
int overbarOffset = 0;
// Creates bounding box (rectangle) for horizontal, left and top justified text. The
// bounding box will be moved later according to the actual text options
VECTOR2I textsize = VECTOR2I( extents.x, extents.y );
VECTOR2I pos = drawPos;
if( IsMultilineAllowed() && aLine > 0 && aLine < (int) strings.GetCount() )
pos.y -= KiROUND( aLine * font->GetInterline( fontSize.y ) );
if( text.Contains( wxT( "~{" ) ) )
overbarOffset = extents.y / 14;
if( aInvertY )
pos.y = -pos.y;
bbox.SetOrigin( pos );
// for multiline texts and aLine < 0, merge all rectangles (aLine == -1 signals all lines)
if( IsMultilineAllowed() && aLine < 0 && strings.GetCount() )
CALLBACK_GAL callback_gal(
empty_opts,
// Stroke callback
[&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2 )
{
for( unsigned ii = 1; ii < strings.GetCount(); ii++ )
strokeBBox.Merge( aPt1 );
strokeBBox.Merge( aPt2 );
},
// Outline callback
[&]( const SHAPE_LINE_CHAIN& aPoly )
{
text = strings.Item( ii );
extents = font->StringBoundaryLimits( text, fontSize, thickness, bold, italic );
textsize.x = std::max( textsize.x, extents.x );
}
bbox.Merge( aPoly.BBox() );
} );
// interline spacing is only *between* lines, so total height is the height of the first
// line plus the interline distance (with interline spacing) for all subsequent lines
textsize.y += KiROUND( ( strings.GetCount() - 1 ) * font->GetInterline( fontSize.y ) );
}
font->Draw( &callback_gal, GetShownText( true ), drawPos, GetAttributes() );
bbox.SetSize( textsize );
/*
* At this point the rectangle origin is the text origin (m_Pos). This is correct only for
* left and top justified, non-mirrored, non-overbarred texts. Recalculate for all others.
*/
int italicOffset = IsItalic() ? KiROUND( fontSize.y * ITALIC_TILT ) : 0;
switch( GetHorizJustify() )
if( strokeBBox.GetSizeMax() > 0 )
{
case GR_TEXT_H_ALIGN_LEFT:
if( IsMirrored() )
bbox.SetX( bbox.GetX() - ( bbox.GetWidth() - italicOffset ) );
break;
case GR_TEXT_H_ALIGN_CENTER:
bbox.SetX( bbox.GetX() - ( bbox.GetWidth() - italicOffset ) / 2 );
break;
case GR_TEXT_H_ALIGN_RIGHT:
if( !IsMirrored() )
bbox.SetX( bbox.GetX() - ( bbox.GetWidth() - italicOffset ) );
break;
}
switch( GetVertJustify() )
{
case GR_TEXT_V_ALIGN_TOP:
break;
case GR_TEXT_V_ALIGN_CENTER:
bbox.SetY( bbox.GetY() - ( bbox.GetHeight() + overbarOffset ) / 2 );
break;
case GR_TEXT_V_ALIGN_BOTTOM:
bbox.SetY( bbox.GetY() - ( bbox.GetHeight() + overbarOffset ) );
break;
strokeBBox.Inflate( GetTextThickness() / 2 );
bbox.Merge( strokeBBox );
}
bbox.Normalize(); // Make h and v sizes always >= 0
@ -954,11 +889,6 @@ void EDA_TEXT::TransformBoundingBoxToPolygon( SHAPE_POLY_SET* aBuffer, int aClea
VECTOR2I corners[4]; // Buffer of polygon corners
BOX2I rect = GetTextBox();
// TrueType bounding boxes aren't guaranteed to include all descenders, diacriticals, etc.
// Since we use this for zone knockouts and DRC, we need something more accurate.
if( getDrawFont()->IsOutline() )
rect = GetEffectiveTextShape( false, false )->BBox();
rect.Inflate( aClearance );
corners[0].x = rect.GetOrigin().x;

View File

@ -97,13 +97,10 @@ inline void InferBold( TEXT_ATTRIBUTES* aAttrs )
/**
* Returns the margin for knocking out text.
*
* Note that this is not a perfect calculation as fonts (especially outline fonts) vary greatly
* in how well ascender and descender heights are enforced.
*/
inline int GetKnockoutTextMargin( const VECTOR2I& aSize, int aThickness )
{
return std::max( KiROUND( aThickness / 2 ), KiROUND( aSize.y / 7.0 ) );
return std::max( KiROUND( aThickness / 2 ), KiROUND( aSize.y / 9.0 ) );
}

View File

@ -1991,8 +1991,7 @@ void PCB_PAINTER::draw( const PCB_TEXT* aText, int aLayer )
font->Draw( &callback_gal, resolvedText, aText->GetDrawPos(), attrs );
SHAPE_POLY_SET finalPoly;
int margin = attrs.m_StrokeWidth * 1.5
+ GetKnockoutTextMargin( attrs.m_Size, attrs.m_StrokeWidth );
int margin = GetKnockoutTextMargin( attrs.m_Size, attrs.m_StrokeWidth );
aText->TransformBoundingBoxToPolygon( &finalPoly, margin );
finalPoly.BooleanSubtract( knockouts, SHAPE_POLY_SET::PM_FAST );

View File

@ -261,10 +261,7 @@ void PCB_TEXT::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_IT
int PCB_TEXT::getKnockoutMargin() const
{
VECTOR2I textSize( GetTextWidth(), GetTextHeight() );
int thickness = GetTextThickness();
return thickness * 1.5 + GetKnockoutTextMargin( textSize, thickness );
return GetKnockoutTextMargin( VECTOR2I( GetTextWidth(), GetTextHeight() ), GetTextThickness() );
}
@ -449,7 +446,7 @@ std::shared_ptr<SHAPE> PCB_TEXT::GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHIN
SHAPE_POLY_SET finalPoly;
int strokeWidth = GetEffectiveTextPenWidth();
VECTOR2I fontSize = GetTextSize();
int margin = strokeWidth * 1.5 + GetKnockoutTextMargin( fontSize, strokeWidth );
int margin = GetKnockoutTextMargin( fontSize, strokeWidth );
TransformBoundingBoxToPolygon( &finalPoly, margin );
finalPoly.BooleanSubtract( knockouts, SHAPE_POLY_SET::PM_FAST );

View File

@ -500,6 +500,7 @@ void PCB_PLUGIN::formatRenderCache( const EDA_TEXT* aText, int aNestLevel ) cons
m_out->Print( aNestLevel + 1, ")\n" );
} );
callback_gal.SetLineWidth( aText->GetTextThickness() );
callback_gal.DrawGlyphs( *cache );
m_out->Print( aNestLevel, ")\n" );

View File

@ -1289,11 +1289,11 @@ bool PNS_KICAD_IFACE_BASE::syncTextItem( PNS::NODE* aWorld, PCB_TEXT* aText, PCB
solid->SetRoutable( false );
TEXT_ATTRIBUTES attrs = aText->GetAttributes();
int margin = KiROUND( attrs.m_StrokeWidth / 2 );
int margin = 0;
SHAPE_POLY_SET cornerBuffer;
if( aText->IsKnockout() )
margin += attrs.m_StrokeWidth + GetKnockoutTextMargin( attrs.m_Size, attrs.m_StrokeWidth );
margin = GetKnockoutTextMargin( attrs.m_Size, attrs.m_StrokeWidth );
aText->TransformBoundingBoxToPolygon( &cornerBuffer, margin );

View File

@ -822,9 +822,6 @@ void ZONE_FILLER::addKnockout( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, int aGap,
default: break;
}
if( text )
aGap += GetKnockoutTextMargin( text->GetTextSize(), text->GetTextThickness() );
switch( aItem->Type() )
{
case PCB_SHAPE_T: