KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pin_layout_cache.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one at
18 * http://www.gnu.org/licenses/
19 */
20
21#include "pin_layout_cache.h"
23#include <pgm_base.h>
25#include <sch_symbol.h>
26#include <eeschema_settings.h>
27#include <schematic_settings.h>
28#include <string_utils.h>
30
31// Small margin in internal units between the pin text and the pin line
32static const int PIN_TEXT_MARGIN = 4;
33
34// Forward declaration for helper implemented in sch_pin.cpp
35wxString FormatStackedPinForDisplay( const wxString& aPinNumber, int aPinLength, int aTextSize,
36 KIFONT::FONT* aFont, const KIFONT::METRICS& aFontMetrics );
37
38std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> PIN_LAYOUT_CACHE::GetPinNumberInfo( int aShadowWidth )
39{
41
42 wxString number = m_pin.GetShownNumber();
43
44 if( number.IsEmpty() || !m_pin.GetParentSymbol()->GetShowPinNumbers() )
45 return std::nullopt;
46
47 // Format stacked representation if necessary
49 KIFONT::FONT* font = KIFONT::FONT::GetFont( cfg ? cfg->m_Appearance.default_font : wxString( "" ) );
50 const KIFONT::METRICS& metrics = m_pin.GetFontMetrics();
51 int length = m_pin.GetLength();
52 int num_size = m_pin.GetNumberTextSize();
53 wxString formatted = FormatStackedPinForDisplay( number, length, num_size, font, metrics );
54
55 std::optional<TEXT_INFO> info = TEXT_INFO();
56 info->m_Text = formatted;
57 info->m_TextSize = num_size;
58 info->m_Thickness = m_numberThickness;
59 info->m_HAlign = GR_TEXT_H_ALIGN_CENTER;
60 info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
61
62 PIN_ORIENTATION orient = m_pin.PinDrawOrient( DefaultTransform );
63
64 auto estimateQABox = [&]( const wxString& txt, int size, bool isVertical ) -> VECTOR2I
65 {
66 int h = size;
67 int w = (int) ( txt.Length() * size * 0.6 );
68
69 if( txt.Contains( '\n' ) )
70 {
71 wxArrayString lines;
72 wxStringSplit( txt, lines, '\n' );
73
74 if( isVertical )
75 {
76 int lineSpacing = KiROUND( size * 1.3 );
77 w = (int) lines.size() * lineSpacing;
78 size_t maxLen = 0;
79 for( const wxString& l : lines )
80 maxLen = std::max( maxLen, l.Length() );
81 h = (int) ( maxLen * size * 0.6 );
82 }
83 else
84 {
85 int lineSpacing = KiROUND( size * 1.3 );
86 h = (int) lines.size() * lineSpacing;
87 size_t maxLen = 0;
88 for( const wxString& l : lines )
89 maxLen = std::max( maxLen, l.Length() );
90 w = (int) ( maxLen * size * 0.6 );
91 }
92 }
93
94 return VECTOR2I( w, h );
95 };
96
97 // Pass 1: determine maximum perpendicular half span among all pin numbers to ensure
98 // a single distance from the pin center that avoids overlap for every pin.
99 const SYMBOL* parentSym = m_pin.GetParentSymbol();
100 int maxHalfHeight = 0; // vertical half span across all numbers (for horizontal pins)
101 int maxHalfWidth = 0; // horizontal half span across all numbers (for vertical pins when rotated)
102 int maxFullHeight = 0; // full height (for dynamic clearance)
103
104 if( parentSym )
105 {
106 for( const SCH_PIN* p : parentSym->GetPins() )
107 {
108 wxString raw = p->GetShownNumber();
109
110 if( raw.IsEmpty() )
111 continue;
112
113 wxString fmt = FormatStackedPinForDisplay( raw, p->GetLength(), p->GetNumberTextSize(), font,
114 p->GetFontMetrics() );
115 // For horizontal pins: compute vertical extent (height when text is horizontal)
116 VECTOR2I boxHoriz = estimateQABox( fmt, p->GetNumberTextSize(), false );
117 maxHalfHeight = std::max( maxHalfHeight, boxHoriz.y / 2 );
118 maxFullHeight = std::max( maxFullHeight, boxHoriz.y );
119
120 // For vertical pins: compute horizontal extent when text is rotated vertical
121 // When text is vertical, the perpendicular span is the original height
122 VECTOR2I boxVert = estimateQABox( fmt, p->GetNumberTextSize(), true );
123 maxHalfWidth = std::max( maxHalfWidth, boxVert.x / 2 );
124 }
125 }
126
128 VECTOR2I pinPos = m_pin.GetPosition();
129 const int halfLength = m_pin.GetLength() / 2;
130 bool verticalOrient = ( orient == PIN_ORIENTATION::PIN_UP || orient == PIN_ORIENTATION::PIN_DOWN );
131
132 // Calculate the current pin's text dimensions for positioning
133 VECTOR2I currentBox = estimateQABox( formatted, info->m_TextSize, verticalOrient );
134 int currentHalfHeight = currentBox.y / 2;
135 int currentHalfWidth = currentBox.x / 2;
136
137 if( verticalOrient )
138 {
139 // Vertical pins: text is rotated vertical so that it reads bottom->top.
140 // When rotated, the perpendicular distance from pin is determined by the text width
141 // when in vertical orientation.
142
143 // Check if both name and number are displayed
144 bool showBothNameAndNumber = !m_pin.GetShownName().IsEmpty()
145 && parentSym
146 && parentSym->GetShowPinNames()
147 && parentSym->GetPinNameOffset() == 0; // name is outside
148
149 int centerX;
150
151 if( showBothNameAndNumber )
152 {
153 // When both are shown: name goes to the left, number goes to the right
154 // Position the number to the right of the pin, left-aligned (so all numbers start at same x)
155 // Left edge at: pinPos.x + clearance
156 // Center at: pinPos.x + clearance + currentHalfWidth
157 centerX = pinPos.x + clearance + currentHalfWidth + m_numberThickness;
158 }
159 else
160 {
161 // When only number is shown: place it to the left of the pin
162 // Use maxHalfWidth for consistent alignment when there's no name
163 centerX = pinPos.x - clearance - currentHalfWidth - m_numberThickness;
164 }
165
166 info->m_TextPosition.x = centerX;
167
168 if( orient == PIN_ORIENTATION::PIN_DOWN )
169 info->m_TextPosition.y = pinPos.y + halfLength;
170 else
171 info->m_TextPosition.y = pinPos.y - halfLength;
172
173 info->m_Angle = ANGLE_VERTICAL;
174 }
175 else
176 {
177 // Horizontal pins: "above" means negative Y direction.
178
179 // Check if both name and number are displayed
180 bool showBothNameAndNumber = !m_pin.GetShownName().IsEmpty()
181 && parentSym
182 && parentSym->GetShowPinNames()
183 && parentSym->GetPinNameOffset() == 0; // name is outside
184
185 int centerY;
186 if( showBothNameAndNumber )
187 {
188 // When both are shown: name goes above, number goes below (top-aligned)
189 // Position the number below the pin with top edge at: pinPos.y + clearance
190 // Center at: pinPos.y + clearance + currentHalfHeight
191 centerY = pinPos.y + clearance + currentHalfHeight + m_numberThickness;
192 }
193 else
194 {
195 // When only number is shown: place it above the pin
196 centerY = pinPos.y - ( currentHalfHeight + clearance + m_numberThickness );
197 }
198
199 if( orient == PIN_ORIENTATION::PIN_LEFT )
200 info->m_TextPosition.x = pinPos.x - halfLength; // centered horizontally along pin
201 else
202 info->m_TextPosition.x = pinPos.x + halfLength; // centered horizontally along pin
203
204 info->m_TextPosition.y = centerY;
205 info->m_Angle = ANGLE_HORIZONTAL;
206 }
207
208 return info;
209}
210
211
212static int externalPinDecoSize( const SCHEMATIC_SETTINGS* aSettings, const SCH_PIN& aPin )
213{
214 if( aSettings && aSettings->m_PinSymbolSize )
215 return aSettings->m_PinSymbolSize;
216 return aPin.GetNumberTextSize() / 2;
217}
218
219
220static int internalPinDecoSize( const SCHEMATIC_SETTINGS* aSettings, const SCH_PIN& aPin )
221{
222 if( aSettings && aSettings->m_PinSymbolSize > 0 )
223 return aSettings->m_PinSymbolSize;
224 return aPin.GetNameTextSize() != 0 ? aPin.GetNameTextSize() / 2 : aPin.GetNumberTextSize() / 2;
225}
226
227
229 m_pin( aPin ), m_schSettings( nullptr ), m_dirtyFlags( DIRTY_FLAGS::ALL )
230{
231 // Resolve the schematic (can be null, e.g. in previews)
232 const SCHEMATIC* schematic = aPin.Schematic();
233
234 if( schematic )
235 m_schSettings = &schematic->Settings();
236}
237
238
239void PIN_LAYOUT_CACHE::MarkDirty( int aDirtyFlags )
240{
241 m_dirtyFlags |= aDirtyFlags;
242}
243
244
245void PIN_LAYOUT_CACHE::SetRenderParameters( int aNameThickness, int aNumberThickness,
246 bool aShowElectricalType, bool aShowAltIcons )
247{
248 if( aNameThickness != m_nameThickness )
249 {
251 m_nameThickness = aNameThickness;
252 }
253
254 if( aNumberThickness != m_numberThickness )
255 {
257 m_numberThickness = aNumberThickness;
258 }
259
260 if( aShowElectricalType != m_showElectricalType )
261 {
263 m_showElectricalType = aShowElectricalType;
264 }
265
266 // Not (yet?) cached
267 m_showAltIcons = aShowAltIcons;
268}
269
270
271void PIN_LAYOUT_CACHE::recomputeExtentsCache( bool aDefinitelyDirty, KIFONT::FONT* aFont, int aSize,
272 const wxString& aText,
273 const KIFONT::METRICS& aFontMetrics,
274 TEXT_EXTENTS_CACHE& aCache )
275{
276 // Even if not definitely dirty, verify no font changes
277 if( !aDefinitelyDirty && aCache.m_Font == aFont && aCache.m_FontSize == aSize )
278 {
279 return;
280 }
281
282 aCache.m_Font = aFont;
283 aCache.m_FontSize = aSize;
284
285 VECTOR2D fontSize( aSize, aSize );
286 int penWidth = GetPenSizeForNormal( aSize );
287
288 // Handle multi-line text bounds properly
289 if( aText.StartsWith( "[" ) && aText.EndsWith( "]" ) && aText.Contains( "\n" ) )
290 {
291 // Extract content between braces and split into lines
292 wxString content = aText.Mid( 1, aText.Length() - 2 );
293 wxArrayString lines;
294 wxStringSplit( content, lines, '\n' );
295
296 if( lines.size() > 1 )
297 {
298 int lineSpacing = KiROUND( aSize * 1.3 ); // Same as drawMultiLineText
299 int maxWidth = 0;
300
301 // Find the widest line
302 for( const wxString& line : lines )
303 {
304 wxString trimmedLine = line;
305 trimmedLine.Trim( true ).Trim( false );
306 VECTOR2I lineExtents = aFont->StringBoundaryLimits( trimmedLine, fontSize, penWidth, false, false, aFontMetrics );
307 maxWidth = std::max( maxWidth, lineExtents.x );
308 }
309
310 // Calculate total dimensions - width is max line width, height accounts for all lines
311 int totalHeight = aSize + ( lines.size() - 1 ) * lineSpacing;
312
313 // Add space for braces
314 int braceWidth = aSize / 3;
315 maxWidth += braceWidth * 2; // Space for braces on both sides
316 totalHeight += aSize / 3; // Extra height for brace extensions
317
318 aCache.m_Extents = VECTOR2I( maxWidth, totalHeight );
319 return;
320 }
321 }
322
323 // Single line text (normal case)
324 aCache.m_Extents = aFont->StringBoundaryLimits( aText, fontSize, penWidth, false, false, aFontMetrics );
325}
326
327
329{
331 KIFONT::FONT* font = KIFONT::FONT::GetFont( cfg ? cfg->m_Appearance.default_font : wxString( "" ) );
332 const KIFONT::METRICS& metrics = m_pin.GetFontMetrics();
333
334 // Due to the fact a shadow text in position INSIDE or OUTSIDE is drawn left or right aligned,
335 // it needs an offset = shadowWidth/2 to be drawn at the same place as normal text
336 // texts drawn as GR_TEXT_H_ALIGN_CENTER do not need a specific offset.
337 // this offset is shadowWidth/2 but for some reason we need to slightly modify this offset
338 // for a better look (better alignment of shadow shape), for KiCad font only
339 if( !font->IsOutline() )
340 m_shadowOffsetAdjust = 1.2f; // Value chosen after tests
341 else
343
344 {
345 const bool dirty = isDirty( DIRTY_FLAGS::NUMBER );
346 const wxString number = m_pin.GetShownNumber();
347 recomputeExtentsCache( dirty, font, m_pin.GetNumberTextSize(), number, metrics, m_numExtentsCache );
348 }
349
350 {
351 const bool dirty = isDirty( DIRTY_FLAGS::NAME );
352 const wxString name = m_pin.GetShownName();
353 recomputeExtentsCache( dirty, font, m_pin.GetNameTextSize(), name, metrics, m_nameExtentsCache );
354 }
355
356 {
357 double fontSize = std::max( m_pin.GetNameTextSize() * 3 / 4, schIUScale.mmToIU( 0.7 ) );
359 m_pin.GetElectricalTypeName(), metrics, m_typeExtentsCache );
360 }
361
363}
364
365
367{
368 // Now, calculate boundary box corners position for the actual pin orientation
369 switch( m_pin.PinDrawOrient( DefaultTransform ) )
370 {
372 {
373 // Pin is rotated and texts positions are mirrored
374 VECTOR2I c1{ aBox.GetLeft(), aBox.GetTop() };
375 VECTOR2I c2{ aBox.GetRight(), aBox.GetBottom() };
376
377 RotatePoint( c1, VECTOR2I( 0, 0 ), ANGLE_90 );
378 RotatePoint( c2, VECTOR2I( 0, 0 ), ANGLE_90 );
379
380 aBox = BOX2I::ByCorners( c1, c2 );
381 break;
382 }
384 {
385 VECTOR2I c1{ aBox.GetLeft(), aBox.GetTop() };
386 VECTOR2I c2{ aBox.GetRight(), aBox.GetBottom() };
387
388 RotatePoint( c1, VECTOR2I( 0, 0 ), -ANGLE_90 );
389 RotatePoint( c2, VECTOR2I( 0, 0 ), -ANGLE_90 );
390
391 c1.x = -c1.x;
392 c2.x = -c2.x;
393
394 aBox = BOX2I::ByCorners( c1, c2 );
395 break;
396 }
398 // Flip it around
399 aBox.Move( { -aBox.GetCenter().x * 2, 0 } );
400 break;
401
402 default:
404 // Already in this form
405 break;
406 }
407
408 aBox.Move( m_pin.GetPosition() );
409}
410
411
413{
414 // Local nominal position for a PIN_RIGHT orientation.
415 const VECTOR2I baseLocal = aInfo.m_TextPosition;
416
417 // We apply a rotation/mirroring depending on the pin orientation so that the text anchor
418 // maintains a constant perpendicular offset from the pin origin regardless of rotation.
419 VECTOR2I rotated = baseLocal;
420 EDA_ANGLE finalAngle = aInfo.m_Angle;
421
422 switch( m_pin.PinDrawOrient( DefaultTransform ) )
423 {
424 case PIN_ORIENTATION::PIN_RIGHT: // identity
425 break;
427 rotated.x = -rotated.x;
428 rotated.y = -rotated.y;
429 aInfo.m_HAlign = GetFlippedAlignment( aInfo.m_HAlign );
430 break;
431 case PIN_ORIENTATION::PIN_UP: // rotate +90 (x,y)->(y,-x) and vertical text
432 rotated = { baseLocal.y, -baseLocal.x };
433 finalAngle = ANGLE_VERTICAL;
434 break;
435 case PIN_ORIENTATION::PIN_DOWN: // rotate -90 (x,y)->(-y,x) and vertical text, flip h-align
436 rotated = { -baseLocal.y, baseLocal.x };
437 finalAngle = ANGLE_VERTICAL;
438 aInfo.m_HAlign = GetFlippedAlignment( aInfo.m_HAlign );
439 break;
440 default:
441 break;
442 }
443
444 aInfo.m_TextPosition = rotated + m_pin.GetPosition();
445 aInfo.m_Angle = finalAngle;
446}
447
448
449BOX2I PIN_LAYOUT_CACHE::GetPinBoundingBox( bool aIncludeLabelsOnInvisiblePins,
450 bool aIncludeNameAndNumber, bool aIncludeElectricalType )
451{
452 if( const SCH_SYMBOL* symbol = dynamic_cast<const SCH_SYMBOL*>( m_pin.GetParentSymbol() ) )
453 {
454 SCH_PIN* const libPin = m_pin.GetLibPin();
455 wxCHECK( libPin, BOX2I() );
456
457 BOX2I r = libPin->GetBoundingBox( aIncludeLabelsOnInvisiblePins, aIncludeNameAndNumber,
458 aIncludeElectricalType );
459
460 r = symbol->GetTransform().TransformCoordinate( r );
461 r.Offset( symbol->GetPosition() );
462 r.Normalize();
463
464 return r;
465 }
466
467 bool includeName = aIncludeNameAndNumber && !m_pin.GetShownName().IsEmpty();
468 bool includeNumber = aIncludeNameAndNumber && !m_pin.GetShownNumber().IsEmpty();
469 bool includeType = aIncludeElectricalType;
470
471 if( !aIncludeLabelsOnInvisiblePins && !m_pin.IsVisible() )
472 {
473 includeName = false;
474 includeNumber = false;
475 includeType = false;
476 }
477
478 if( const SYMBOL* parentSymbol = m_pin.GetParentSymbol() )
479 {
480 if( !parentSymbol->GetShowPinNames() )
481 includeName = false;
482
483 if( !parentSymbol->GetShowPinNumbers() )
484 includeNumber = false;
485 }
486
488
489 const int pinLength = m_pin.GetLength();
490
491 // Creating and merging all the boxes is pretty quick, if cached we'd have
492 // to track many variables here, which is possible, but unlikely to be worth it.
493 BOX2I bbox;
494
495 // Untransformed pin box
496 {
497 BOX2I pinBox = BOX2I::ByCorners( { 0, 0 }, { pinLength, 0 } );
498 pinBox.Inflate( m_pin.GetPenWidth() / 2 );
499 bbox.Merge( pinBox );
500 }
501
503 {
504 bbox.Merge( *decoBox );
505 }
506
507 if( includeName )
508 {
509 if( OPT_BOX2I nameBox = getUntransformedPinNameBox() )
510 {
511 bbox.Merge( *nameBox );
512 }
513
514 if( OPT_BOX2I altIconBox = getUntransformedAltIconBox() )
515 {
516 bbox.Merge( *altIconBox );
517 }
518 }
519
520 if( includeNumber )
521 {
523 {
524 bbox.Merge( *numBox );
525 }
526 }
527
528 if( includeType )
529 {
530 if( OPT_BOX2I typeBox = getUntransformedPinTypeBox() )
531 {
532 bbox.Merge( *typeBox );
533 }
534 }
535
536 transformBoxForPin( bbox );
537
538 if( m_pin.IsDangling() )
539 {
540 // Not much point caching this, but we could
541 const CIRCLE c = GetDanglingIndicator();
542
543 BOX2I cBox = BOX2I::ByCenter( c.Center, { c.Radius * 2, c.Radius * 2 } );
544 // TODO: need some way to find the thickness...?
545 // cBox.Inflate( ??? );
546
547 bbox.Merge( cBox );
548 }
549
550 bbox.Normalize();
551 bbox.Inflate( ( m_pin.GetPenWidth() / 2 ) + 1 );
552
553 return bbox;
554}
555
556
558{
559 return CIRCLE{
560 m_pin.GetPosition(),
562 };
563}
564
565
567{
568 const float offsetRatio =
570 return schIUScale.MilsToIU( KiROUND( 24 * offsetRatio ) );
571}
572
573
575{
576 int pinNameOffset = 0;
577 if( const SYMBOL* parentSymbol = m_pin.GetParentSymbol() )
578 {
579 if( parentSymbol->GetShowPinNames() )
580 pinNameOffset = parentSymbol->GetPinNameOffset();
581 }
582
583 // We're considering the PIN_RIGHT scenario
584 // TEXT
585 // X-------| TEXT
586 // TEXT
587 //
588 // We'll rotate it later.
589
590 OPT_BOX2I box;
591 const int pinLength = m_pin.GetLength();
592
593 if( pinNameOffset > 0 )
594 {
595 // This means name inside the pin
596 box = BOX2I::ByCenter( { pinLength, 0 }, m_nameExtentsCache.m_Extents );
597
598 // Bump over to be left aligned just inside the pin
599 box->Move( { m_nameExtentsCache.m_Extents.x / 2 + pinNameOffset, 0 } );
600 }
601 else
602 {
603 // The pin name is always over the pin
604 box = BOX2I::ByCenter( { pinLength / 2, 0 }, m_nameExtentsCache.m_Extents );
605
606 // Bump it up
607 box->Move( { 0, -m_nameExtentsCache.m_Extents.y / 2 - getPinTextOffset() } );
608 }
609
610 return box;
611}
612
613
615{
616 int pinNameOffset = 0;
617
618 if( const SYMBOL* parentSymbol = m_pin.GetParentSymbol() )
619 {
620 if( parentSymbol->GetShowPinNames() )
621 pinNameOffset = parentSymbol->GetPinNameOffset();
622 }
623
624 const int pinLength = m_pin.GetLength();
625
626 // The pin number is always over the pin (centered along its length)
627 OPT_BOX2I box = BOX2I::ByCenter( { pinLength / 2, 0 }, m_numExtentsCache.m_Extents );
628
629 // Check if both name and number are displayed (name outside the pin)
630 bool showBothNameAndNumber = ( pinNameOffset == 0
631 && !m_pin.GetShownName().empty()
632 && m_pin.GetParentSymbol()->GetShowPinNames() );
633
634 int textPos;
635 if( showBothNameAndNumber )
636 {
637 // When both are shown: name goes above, number goes below (top-aligned to bottom)
638 // Position the number below the pin, with its top edge at the clearance distance
639 textPos = m_numExtentsCache.m_Extents.y / 2 + getPinTextOffset();
640 }
641 else
642 {
643 // When only number is shown: place it above the pin
644 textPos = -m_numExtentsCache.m_Extents.y / 2 - getPinTextOffset();
645 }
646
647 // Bump it up (or down)
648 box->Move( { 0, textPos } );
649
650 return box;
651}
652
653
655{
657 return std::nullopt;
658
659 BOX2I box{
660 { -m_typeExtentsCache.m_Extents.x, -m_typeExtentsCache.m_Extents.y / 2 },
661 m_typeExtentsCache.m_Extents,
662 };
663
664 // Jog left
665 box.Move( { -schIUScale.MilsToIU( PIN_TEXT_MARGIN ) - TARGET_PIN_RADIUS, 0 } );
666
667 return box;
668}
669
670
672{
673 const OPT_BOX2I nameBox = getUntransformedPinNameBox();
674
675 if( !nameBox || m_pin.GetAlternates().empty() || !m_showAltIcons )
676 return std::nullopt;
677
678 const int iconSize = std::min( m_pin.GetNameTextSize(), schIUScale.mmToIU( 1.5 ) );
679
680 VECTOR2I c{ 0, ( nameBox->GetTop() + nameBox->GetBottom() ) / 2 };
681 if( m_pin.GetParentSymbol()->GetPinNameOffset() > 0 )
682 {
683 // name inside, so icon more inside
684 c.x = nameBox->GetRight() + iconSize * 0.75;
685 }
686 else
687 {
688 c.x = nameBox->GetLeft() - iconSize * 0.75;
689 }
690
691 return BOX2I::ByCenter( c, { iconSize, iconSize } );
692}
693
694
696{
697 const GRAPHIC_PINSHAPE shape = m_pin.GetShape();
698 const int decoSize = externalPinDecoSize( m_schSettings, m_pin );
699 const int intDecoSize = internalPinDecoSize( m_schSettings, m_pin );
700
701 const auto makeInvertBox =
702 [&]()
703 {
704 return BOX2I::ByCenter( { -decoSize, 0 }, { decoSize * 2, decoSize * 2 } );
705 };
706
707 const auto makeLowBox =
708 [&]()
709 {
710 return BOX2I::ByCorners( { -decoSize * 2, -decoSize * 2 }, { 0, 0 } );
711 };
712
713 const auto makeClockBox =
714 [&]()
715 {
716 return BOX2I::ByCorners( { 0, -intDecoSize }, { intDecoSize, intDecoSize } );
717 };
718
719 OPT_BOX2I box;
720
721 switch( shape )
722 {
724 box = makeInvertBox();
725 break;
726
728 box = makeClockBox();
729 break;
730
732 box = makeInvertBox();
733 box->Merge( makeClockBox() );
734 break;
735
737 box = makeLowBox();
738 break;
739
742 box = makeLowBox();
743 box->Merge( makeClockBox() );
744 break;
745
747 box = BOX2I::ByCenter( { 0, 0 }, { decoSize * 2, decoSize * 2 } );
748 break;
749
751 default:
752 // No decoration
753 break;
754 }
755
756 if( box )
757 {
758 // Put the box at the root of the pin
759 box->Move( { m_pin.GetLength(), 0 } );
760 box->Inflate( m_pin.GetPenWidth() / 2 );
761 }
762
763 return box;
764}
765
766
768{
771
772 if( box )
773 transformBoxForPin( *box );
774
775 return box;
776}
777
778
780{
783
784 if( box )
785 transformBoxForPin( *box );
786
787 return box;
788}
789
790
792{
794
795 if( box )
796 transformBoxForPin( *box );
797
798 return box;
799}
800
801
802std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> PIN_LAYOUT_CACHE::GetPinNameInfo( int aShadowWidth )
803{
805 wxString name = m_pin.GetShownName();
806
807 // TODO - work out exactly what we need to do to cache this
808 // (or if it's worth the memory/complexity)
809 // But it's not hugely expensive to recompute, and that's what's always been
810 // done to now
811 //
812 // Because pins are very likely to share a lot of characteristics, a global
813 // cache might make more sense than a per-pin cache.
814
815 if( name.IsEmpty() || !m_pin.GetParentSymbol()->GetShowPinNames() )
816 return std::nullopt;
817
818 std::optional<TEXT_INFO> info = TEXT_INFO();
819 info->m_Text = std::move( name );
820 info->m_TextSize = m_pin.GetNameTextSize();
821 info->m_Thickness = m_nameThickness;
822 info->m_Angle = ANGLE_HORIZONTAL;
823
824 bool nameInside = m_pin.GetParentSymbol()->GetPinNameOffset() > 0;
825
826 if( nameInside )
827 {
828 // This means name inside the pin
829 VECTOR2I pos = { m_pin.GetLength() + m_pin.GetParentSymbol()->GetPinNameOffset(), 0 };
830 const int shadowOffset = KiROUND( aShadowWidth * m_shadowOffsetAdjust ) / 2;
831
832 info->m_TextPosition = pos + VECTOR2I{ -shadowOffset, 0 };
833 info->m_HAlign = GR_TEXT_H_ALIGN_LEFT;
834 info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
836 }
837 else
838 {
839 // The pin name is always over the pin
840 VECTOR2I pos = { m_pin.GetLength() / 2, -getPinTextOffset() - info->m_Thickness / 2 };
841
842 info->m_TextPosition = pos;
843 info->m_HAlign = GR_TEXT_H_ALIGN_CENTER;
844 info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
845
846 // New policy: names follow same positioning semantics as numbers except when
847 // specified as inside. When names are inside, they should not overlap with the
848 // number position.
849 const SYMBOL* parentSym = m_pin.GetParentSymbol();
850 if( parentSym )
851 {
852 int maxHalfHeight = 0;
853 for( const SCH_PIN* p : parentSym->GetPins() )
854 {
855 wxString n = p->GetShownName();
856
857 if( n.IsEmpty() )
858 continue;
859
860 maxHalfHeight = std::max( maxHalfHeight, p->GetNameTextSize() / 2 );
861 }
862
864 VECTOR2I pinPos = m_pin.GetPosition();
865 const int halfLength = m_pin.GetLength() / 2;
866 PIN_ORIENTATION orient = m_pin.PinDrawOrient( DefaultTransform );
867 bool verticalOrient = ( orient == PIN_ORIENTATION::PIN_UP || orient == PIN_ORIENTATION::PIN_DOWN );
868
869 if( verticalOrient )
870 {
871 // Vertical pins: name mirrors number placement (left + rotated) for visual consistency.
872 int boxWidth = info->m_TextSize * (int) info->m_Text.Length() * 0.6; // heuristic width
873 int centerX = pinPos.x - clearance - boxWidth / 2 - info->m_Thickness;
874 info->m_TextPosition = { centerX, pinPos.y };
875
876 if( orient == PIN_ORIENTATION::PIN_DOWN )
877 info->m_TextPosition.y += halfLength;
878 else
879 info->m_TextPosition.y -= halfLength;
880
881 info->m_Angle = ANGLE_VERTICAL;
882 info->m_HAlign = GR_TEXT_H_ALIGN_CENTER;
883 info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
884 }
885 else
886 {
887 // Horizontal pins: name above (negative Y) aligned to same Y offset logic as numbers.
888 info->m_TextPosition = { pinPos.x, pinPos.y - ( maxHalfHeight + clearance + info->m_Thickness ) };
889
890 if( orient == PIN_ORIENTATION::PIN_LEFT )
891 info->m_TextPosition.x -= halfLength;
892 else
893 info->m_TextPosition.x += halfLength;
894
895 info->m_Angle = ANGLE_HORIZONTAL;
896 info->m_HAlign = GR_TEXT_H_ALIGN_CENTER;
897 info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
898 }
899 }
900 }
901 return info;
902}
903
904
905// (Removed duplicate later GetPinNumberInfo – earlier definition retained at top of file.)
906
907
908std::optional<PIN_LAYOUT_CACHE::TEXT_INFO>
910{
912
914 return std::nullopt;
915
916 std::optional<TEXT_INFO> info = TEXT_INFO();
917 info->m_Text = m_pin.GetElectricalTypeName();
918 info->m_TextSize = std::max( m_pin.GetNameTextSize() * 3 / 4, schIUScale.mmToIU( 0.7 ) );
919 info->m_Angle = ANGLE_HORIZONTAL;
920 info->m_Thickness = info->m_TextSize / 8;
921 info->m_TextPosition = { -getPinTextOffset() - info->m_Thickness / 2
922 + KiROUND( aShadowWidth * m_shadowOffsetAdjust ) / 2,
923 0 };
924 info->m_HAlign = GR_TEXT_H_ALIGN_RIGHT;
925 info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
926
927 info->m_TextPosition.x -= TARGET_PIN_RADIUS;
928
929 if( m_pin.IsDangling() )
930 {
931 info->m_TextPosition.x -= TARGET_PIN_RADIUS / 2;
932 }
933
935 return info;
936}
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:114
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
std::optional< BOX2I > OPT_BOX2I
Definition box2.h:926
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:558
static constexpr BOX2< VECTOR2I > ByCorners(const VECTOR2I &aCorner1, const VECTOR2I &aCorner2)
Definition box2.h:70
constexpr BOX2< Vec > & Normalize()
Ensure that the height and width are positive.
Definition box2.h:146
static constexpr BOX2< VECTOR2I > ByCenter(const VECTOR2I &aCenter, const SizeVec &aSize)
Definition box2.h:75
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:658
constexpr const Vec GetCenter() const
Definition box2.h:230
constexpr coord_type GetLeft() const
Definition box2.h:228
constexpr void Move(const Vec &aMoveVector)
Move the rectangle by the aMoveVector.
Definition box2.h:138
constexpr coord_type GetRight() const
Definition box2.h:217
constexpr coord_type GetTop() const
Definition box2.h:229
constexpr void Offset(coord_type dx, coord_type dy)
Definition box2.h:259
constexpr coord_type GetBottom() const
Definition box2.h:222
Represent basic circle geometry with utility geometry functions.
Definition circle.h:33
VECTOR2I Center
Public to make access simpler.
Definition circle.h:130
FONT is an abstract base class for both outline and stroke fonts.
Definition font.h:131
static FONT * GetFont(const wxString &aFontName=wxEmptyString, bool aBold=false, bool aItalic=false, const std::vector< wxString > *aEmbeddedFiles=nullptr, bool aForDrawingSheet=false)
Definition font.cpp:147
virtual bool IsOutline() const
Definition font.h:139
VECTOR2I StringBoundaryLimits(const wxString &aText, const VECTOR2I &aSize, int aThickness, bool aBold, bool aItalic, const METRICS &aFontMetrics) const
Compute the boundary limits of aText (the bounding box of all shapes).
Definition font.cpp:427
OPT_BOX2I getUntransformedPinNameBox() const
Get the untransformd text box in the default orientation.
TEXT_EXTENTS_CACHE m_nameExtentsCache
void transformTextForPin(TEXT_INFO &aTextInfo) const
Transform text info to suit a pin's.
OPT_BOX2I GetAltIconBBox()
Get the box of the alt mode icon, if there is one.
void transformBoxForPin(BOX2I &aBox) const
Transform a box (in-place) to the pin's orientation.
void recomputeCaches()
Recompute all the caches that have become dirty.
const SCHEMATIC_SETTINGS * m_schSettings
OPT_BOX2I GetPinNumberBBox()
Get the bounding box of the pin number, if there is one.
PIN_LAYOUT_CACHE(const SCH_PIN &aPin)
TEXT_EXTENTS_CACHE m_typeExtentsCache
BOX2I GetPinBoundingBox(bool aIncludeLabelsOnInvisiblePins, bool aIncludeNameAndNumber, bool aIncludeElectricalType)
Get the bounding box of the pin itself.
TEXT_EXTENTS_CACHE m_numExtentsCache
void setClean(int aMask)
OPT_BOX2I getUntransformedPinTypeBox() const
std::optional< TEXT_INFO > GetPinNameInfo(int aShadowWidth)
Get the text info for the pin name.
static void recomputeExtentsCache(bool aDefinitelyDirty, KIFONT::FONT *aFont, int aSize, const wxString &aText, const KIFONT::METRICS &aFontMetrics, TEXT_EXTENTS_CACHE &aCache)
std::optional< TEXT_INFO > GetPinElectricalTypeInfo(int aShadowWidth)
bool isDirty(int aMask) const
CIRCLE GetDanglingIndicator() const
Gets the dangling indicator geometry for this pin, if the pin were to be dangling.
std::optional< TEXT_INFO > GetPinNumberInfo(int aShadowWidth)
OPT_BOX2I GetPinNameBBox()
Get the bounding box of the pin name, if there is one.
void MarkDirty(int aFlags)
Recompute all the layout information.
void SetRenderParameters(int aNameThickness, int aNumberThickness, bool aShowElectricalType, bool aShowAltIcons)
OPT_BOX2I getUntransformedAltIconBox() const
int getPinTextOffset() const
Get the current pin text offset.
OPT_BOX2I getUntransformedDecorationBox() const
Pin type decoration if any.
const SCH_PIN & m_pin
The pin in question.
OPT_BOX2I getUntransformedPinNumberBox() const
These are loaded from Eeschema settings but then overwritten by the project settings.
Holds all the data relating to one schematic.
Definition schematic.h:88
SCHEMATIC_SETTINGS & Settings() const
SCHEMATIC * Schematic() const
Search the item hierarchy to find a SCHEMATIC.
Definition sch_item.cpp:247
int GetNumberTextSize() const
Definition sch_pin.cpp:679
int GetNameTextSize() const
Definition sch_pin.cpp:655
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
Definition sch_pin.h:215
Schematic symbol object.
Definition sch_symbol.h:76
A base class for LIB_SYMBOL and SCH_SYMBOL.
Definition symbol.h:63
int GetPinNameOffset() const
Definition symbol.h:160
virtual std::vector< SCH_PIN * > GetPins() const =0
virtual bool GetShowPinNames() const
Definition symbol.h:166
#define DEFAULT_TEXT_OFFSET_RATIO
Ratio of the font height to space around global labels.
static bool empty(const wxTextEntryBase *aCtrl)
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
static constexpr EDA_ANGLE ANGLE_VERTICAL
Definition eda_angle.h:408
static constexpr EDA_ANGLE ANGLE_HORIZONTAL
Definition eda_angle.h:407
TRANSFORM DefaultTransform
Definition transform.cpp:32
int GetPenSizeForNormal(int aTextSize)
Definition gr_text.cpp:61
see class PGM_BASE
static int externalPinDecoSize(const SCHEMATIC_SETTINGS *aSettings, const SCH_PIN &aPin)
static int internalPinDecoSize(const SCHEMATIC_SETTINGS *aSettings, const SCH_PIN &aPin)
wxString FormatStackedPinForDisplay(const wxString &aPinNumber, int aPinLength, int aTextSize, KIFONT::FONT *aFont, const KIFONT::METRICS &aFontMetrics)
Definition sch_pin.cpp:43
PIN_ORIENTATION
The symbol library pin object orientations.
Definition pin_type.h:105
@ PIN_UP
The pin extends upwards from the connection point: Probably on the bottom side of the symbol.
Definition pin_type.h:127
@ PIN_RIGHT
The pin extends rightwards from the connection point.
Definition pin_type.h:111
@ PIN_LEFT
The pin extends leftwards from the connection point: Probably on the right side of the symbol.
Definition pin_type.h:118
@ PIN_DOWN
The pin extends downwards from the connection: Probably on the top side of the symbol.
Definition pin_type.h:135
GRAPHIC_PINSHAPE
Definition pin_type.h:84
#define PIN_TEXT_MARGIN
Definition sch_pin.cpp:88
#define TARGET_PIN_RADIUS
Definition sch_pin.h:37
T * GetAppSettings(const char *aFilename)
Utility functions for working with shapes.
void wxStringSplit(const wxString &aText, wxArrayString &aStrings, wxChar aSplitter)
Split aString to a string list separated at aSplitter.
Cached extent of a text item.
int clearance
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_CENTER
constexpr GR_TEXT_H_ALIGN_T GetFlippedAlignment(GR_TEXT_H_ALIGN_T aAlign)
Get the reverse alignment: left-right are swapped, others are unchanged.
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:229
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
VECTOR2< double > VECTOR2D
Definition vector2d.h:694