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