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 // Detect if this is a stacked pin number (contains notation like [1-5] or comma-separated values)
138 bool hasStackingNotation = number.Contains( '[' ) || number.Contains( ',' );
139
140 if( verticalOrient )
141 {
142 // Vertical pins: text is rotated vertical so that it reads bottom->top.
143
144 // Check if both name and number are displayed
145 bool showBothNameAndNumber = !m_pin.GetShownName().IsEmpty()
146 && parentSym
147 && parentSym->GetShowPinNames()
148 && parentSym->GetPinNameOffset() == 0; // name is outside
149
150 // Calculate perpendicular offset based on text structure
151 int perpendicularOffset;
152
153 if( hasStackingNotation || formatted.Contains( '\n' ) )
154 {
155 // Stacked/multi-line text: use width-based offset for proper spacing
156 // of stacked pin numbers. Use currentHalfWidth to match original behavior.
157 perpendicularOffset = clearance + currentHalfWidth + m_numberThickness;
158 }
159 else
160 {
161 // True single-line text (no stacking): use text height for consistent
162 // spacing across rotations. This fixes issue 21980 where single-line pin
163 // names/numbers would have different perpendicular offsets at different rotations.
164 perpendicularOffset = clearance + info->m_TextSize / 2 + m_numberThickness;
165 }
166
167 int centerX;
168
169 if( showBothNameAndNumber )
170 {
171 // When both are shown: name goes to the left, number goes to the right
172 centerX = pinPos.x + perpendicularOffset;
173 }
174 else
175 {
176 // When only number is shown: place it to the left of the pin
177 centerX = pinPos.x - perpendicularOffset;
178 }
179
180 info->m_TextPosition.x = centerX;
181
182 if( orient == PIN_ORIENTATION::PIN_DOWN )
183 info->m_TextPosition.y = pinPos.y + halfLength;
184 else
185 info->m_TextPosition.y = pinPos.y - halfLength;
186
187 info->m_Angle = ANGLE_VERTICAL;
188 }
189 else
190 {
191 // Horizontal pins: "above" means negative Y direction.
192
193 // Check if both name and number are displayed
194 bool showBothNameAndNumber = !m_pin.GetShownName().IsEmpty()
195 && parentSym
196 && parentSym->GetShowPinNames()
197 && parentSym->GetPinNameOffset() == 0; // name is outside
198
199 int centerY;
200 if( showBothNameAndNumber )
201 {
202 // When both are shown: name goes above, number goes below (top-aligned)
203 // Position the number below the pin with top edge at: pinPos.y + clearance
204 // Center at: pinPos.y + clearance + currentHalfHeight
205 centerY = pinPos.y + clearance + currentHalfHeight + m_numberThickness;
206 }
207 else
208 {
209 // When only number is shown: place it above the pin
210 centerY = pinPos.y - ( currentHalfHeight + clearance + m_numberThickness );
211 }
212
213 if( orient == PIN_ORIENTATION::PIN_LEFT )
214 info->m_TextPosition.x = pinPos.x - halfLength; // centered horizontally along pin
215 else
216 info->m_TextPosition.x = pinPos.x + halfLength; // centered horizontally along pin
217
218 info->m_TextPosition.y = centerY;
219 info->m_Angle = ANGLE_HORIZONTAL;
220 }
221
222 return info;
223}
224
225
226static int externalPinDecoSize( const SCHEMATIC_SETTINGS* aSettings, const SCH_PIN& aPin )
227{
228 if( aSettings && aSettings->m_PinSymbolSize )
229 return aSettings->m_PinSymbolSize;
230 return aPin.GetNumberTextSize() / 2;
231}
232
233
234static int internalPinDecoSize( const SCHEMATIC_SETTINGS* aSettings, const SCH_PIN& aPin )
235{
236 if( aSettings && aSettings->m_PinSymbolSize > 0 )
237 return aSettings->m_PinSymbolSize;
238 return aPin.GetNameTextSize() != 0 ? aPin.GetNameTextSize() / 2 : aPin.GetNumberTextSize() / 2;
239}
240
241
243 m_pin( aPin ), m_schSettings( nullptr ), m_dirtyFlags( DIRTY_FLAGS::ALL )
244{
245 // Resolve the schematic (can be null, e.g. in previews)
246 const SCHEMATIC* schematic = aPin.Schematic();
247
248 if( schematic )
249 m_schSettings = &schematic->Settings();
250}
251
252
253void PIN_LAYOUT_CACHE::MarkDirty( int aDirtyFlags )
254{
255 m_dirtyFlags |= aDirtyFlags;
256}
257
258
259void PIN_LAYOUT_CACHE::SetRenderParameters( int aNameThickness, int aNumberThickness,
260 bool aShowElectricalType, bool aShowAltIcons )
261{
262 if( aNameThickness != m_nameThickness )
263 {
265 m_nameThickness = aNameThickness;
266 }
267
268 if( aNumberThickness != m_numberThickness )
269 {
271 m_numberThickness = aNumberThickness;
272 }
273
274 if( aShowElectricalType != m_showElectricalType )
275 {
277 m_showElectricalType = aShowElectricalType;
278 }
279
280 // Not (yet?) cached
281 m_showAltIcons = aShowAltIcons;
282}
283
284
285void PIN_LAYOUT_CACHE::recomputeExtentsCache( bool aDefinitelyDirty, KIFONT::FONT* aFont, int aSize,
286 const wxString& aText,
287 const KIFONT::METRICS& aFontMetrics,
288 TEXT_EXTENTS_CACHE& aCache )
289{
290 // Even if not definitely dirty, verify no font changes
291 if( !aDefinitelyDirty && aCache.m_Font == aFont && aCache.m_FontSize == aSize )
292 {
293 return;
294 }
295
296 aCache.m_Font = aFont;
297 aCache.m_FontSize = aSize;
298
299 VECTOR2D fontSize( aSize, aSize );
300 int penWidth = GetPenSizeForNormal( aSize );
301
302 // Handle multi-line text bounds properly
303 if( aText.StartsWith( "[" ) && aText.EndsWith( "]" ) && aText.Contains( "\n" ) )
304 {
305 // Extract content between braces and split into lines
306 wxString content = aText.Mid( 1, aText.Length() - 2 );
307 wxArrayString lines;
308 wxStringSplit( content, lines, '\n' );
309
310 if( lines.size() > 1 )
311 {
312 int lineSpacing = KiROUND( aSize * 1.3 ); // Same as drawMultiLineText
313 int maxWidth = 0;
314
315 // Find the widest line
316 for( const wxString& line : lines )
317 {
318 wxString trimmedLine = line;
319 trimmedLine.Trim( true ).Trim( false );
320 VECTOR2I lineExtents = aFont->StringBoundaryLimits( trimmedLine, fontSize, penWidth, false, false, aFontMetrics );
321 maxWidth = std::max( maxWidth, lineExtents.x );
322 }
323
324 // Calculate total dimensions - width is max line width, height accounts for all lines
325 int totalHeight = aSize + ( lines.size() - 1 ) * lineSpacing;
326
327 // Add space for braces
328 int braceWidth = aSize / 3;
329 maxWidth += braceWidth * 2; // Space for braces on both sides
330 totalHeight += aSize / 3; // Extra height for brace extensions
331
332 aCache.m_Extents = VECTOR2I( maxWidth, totalHeight );
333 return;
334 }
335 }
336
337 // Single line text (normal case)
338 aCache.m_Extents = aFont->StringBoundaryLimits( aText, fontSize, penWidth, false, false, aFontMetrics );
339}
340
341
343{
345 KIFONT::FONT* font = KIFONT::FONT::GetFont( cfg ? cfg->m_Appearance.default_font : wxString( "" ) );
346 const KIFONT::METRICS& metrics = m_pin.GetFontMetrics();
347
348 // Due to the fact a shadow text in position INSIDE or OUTSIDE is drawn left or right aligned,
349 // it needs an offset = shadowWidth/2 to be drawn at the same place as normal text
350 // texts drawn as GR_TEXT_H_ALIGN_CENTER do not need a specific offset.
351 // this offset is shadowWidth/2 but for some reason we need to slightly modify this offset
352 // for a better look (better alignment of shadow shape), for KiCad font only
353 if( !font->IsOutline() )
354 m_shadowOffsetAdjust = 1.2f; // Value chosen after tests
355 else
357
358 {
359 const bool dirty = isDirty( DIRTY_FLAGS::NUMBER );
360 const wxString number = m_pin.GetShownNumber();
361 recomputeExtentsCache( dirty, font, m_pin.GetNumberTextSize(), number, metrics, m_numExtentsCache );
362 }
363
364 {
365 const bool dirty = isDirty( DIRTY_FLAGS::NAME );
366 const wxString name = m_pin.GetShownName();
367 recomputeExtentsCache( dirty, font, m_pin.GetNameTextSize(), name, metrics, m_nameExtentsCache );
368 }
369
370 {
371 double fontSize = std::max( m_pin.GetNameTextSize() * 3 / 4, schIUScale.mmToIU( 0.7 ) );
373 m_pin.GetElectricalTypeName(), metrics, m_typeExtentsCache );
374 }
375
377}
378
379
381{
382 // Now, calculate boundary box corners position for the actual pin orientation
383 switch( m_pin.PinDrawOrient( DefaultTransform ) )
384 {
386 {
387 // Pin is rotated and texts positions are mirrored
388 VECTOR2I c1{ aBox.GetLeft(), aBox.GetTop() };
389 VECTOR2I c2{ aBox.GetRight(), aBox.GetBottom() };
390
391 RotatePoint( c1, VECTOR2I( 0, 0 ), ANGLE_90 );
392 RotatePoint( c2, VECTOR2I( 0, 0 ), ANGLE_90 );
393
394 aBox = BOX2I::ByCorners( c1, c2 );
395 break;
396 }
398 {
399 VECTOR2I c1{ aBox.GetLeft(), aBox.GetTop() };
400 VECTOR2I c2{ aBox.GetRight(), aBox.GetBottom() };
401
402 RotatePoint( c1, VECTOR2I( 0, 0 ), -ANGLE_90 );
403 RotatePoint( c2, VECTOR2I( 0, 0 ), -ANGLE_90 );
404
405 c1.x = -c1.x;
406 c2.x = -c2.x;
407
408 aBox = BOX2I::ByCorners( c1, c2 );
409 break;
410 }
412 // Flip it around
413 aBox.Move( { -aBox.GetCenter().x * 2, 0 } );
414 break;
415
416 default:
418 // Already in this form
419 break;
420 }
421
422 aBox.Move( m_pin.GetPosition() );
423}
424
425
427{
428 // Local nominal position for a PIN_RIGHT orientation.
429 const VECTOR2I baseLocal = aInfo.m_TextPosition;
430
431 // We apply a rotation/mirroring depending on the pin orientation so that the text anchor
432 // maintains a constant perpendicular offset from the pin origin regardless of rotation.
433 VECTOR2I rotated = baseLocal;
434 EDA_ANGLE finalAngle = aInfo.m_Angle;
435
436 switch( m_pin.PinDrawOrient( DefaultTransform ) )
437 {
438 case PIN_ORIENTATION::PIN_RIGHT: // identity
439 break;
441 rotated.x = -rotated.x;
442 rotated.y = -rotated.y;
443 aInfo.m_HAlign = GetFlippedAlignment( aInfo.m_HAlign );
444 break;
445 case PIN_ORIENTATION::PIN_UP: // rotate +90 (x,y)->(y,-x) and vertical text
446 rotated = { baseLocal.y, -baseLocal.x };
447 finalAngle = ANGLE_VERTICAL;
448 break;
449 case PIN_ORIENTATION::PIN_DOWN: // rotate -90 (x,y)->(-y,x) and vertical text, flip h-align
450 rotated = { -baseLocal.y, baseLocal.x };
451 finalAngle = ANGLE_VERTICAL;
452 aInfo.m_HAlign = GetFlippedAlignment( aInfo.m_HAlign );
453 break;
454 default:
455 break;
456 }
457
458 aInfo.m_TextPosition = rotated + m_pin.GetPosition();
459 aInfo.m_Angle = finalAngle;
460}
461
462
463BOX2I PIN_LAYOUT_CACHE::GetPinBoundingBox( bool aIncludeLabelsOnInvisiblePins,
464 bool aIncludeNameAndNumber, bool aIncludeElectricalType )
465{
466 if( const SCH_SYMBOL* symbol = dynamic_cast<const SCH_SYMBOL*>( m_pin.GetParentSymbol() ) )
467 {
468 SCH_PIN* const libPin = m_pin.GetLibPin();
469 wxCHECK( libPin, BOX2I() );
470
471 BOX2I r = libPin->GetBoundingBox( aIncludeLabelsOnInvisiblePins, aIncludeNameAndNumber,
472 aIncludeElectricalType );
473
474 r = symbol->GetTransform().TransformCoordinate( r );
475 r.Offset( symbol->GetPosition() );
476 r.Normalize();
477
478 return r;
479 }
480
481 bool includeName = aIncludeNameAndNumber && !m_pin.GetShownName().IsEmpty();
482 bool includeNumber = aIncludeNameAndNumber && !m_pin.GetShownNumber().IsEmpty();
483 bool includeType = aIncludeElectricalType;
484
485 if( !aIncludeLabelsOnInvisiblePins && !m_pin.IsVisible() )
486 {
487 includeName = false;
488 includeNumber = false;
489 includeType = false;
490 }
491
492 if( const SYMBOL* parentSymbol = m_pin.GetParentSymbol() )
493 {
494 if( !parentSymbol->GetShowPinNames() )
495 includeName = false;
496
497 if( !parentSymbol->GetShowPinNumbers() )
498 includeNumber = false;
499 }
500
502
503 const int pinLength = m_pin.GetLength();
504
505 // Creating and merging all the boxes is pretty quick, if cached we'd have
506 // to track many variables here, which is possible, but unlikely to be worth it.
507 BOX2I bbox;
508
509 // Untransformed pin box
510 {
511 BOX2I pinBox = BOX2I::ByCorners( { 0, 0 }, { pinLength, 0 } );
512 pinBox.Inflate( m_pin.GetPenWidth() / 2 );
513 bbox.Merge( pinBox );
514 }
515
517 {
518 bbox.Merge( *decoBox );
519 }
520
521 if( includeName )
522 {
523 if( OPT_BOX2I nameBox = getUntransformedPinNameBox() )
524 {
525 bbox.Merge( *nameBox );
526 }
527
528 if( OPT_BOX2I altIconBox = getUntransformedAltIconBox() )
529 {
530 bbox.Merge( *altIconBox );
531 }
532 }
533
534 if( includeNumber )
535 {
537 {
538 bbox.Merge( *numBox );
539 }
540 }
541
542 if( includeType )
543 {
544 if( OPT_BOX2I typeBox = getUntransformedPinTypeBox() )
545 {
546 bbox.Merge( *typeBox );
547 }
548 }
549
550 transformBoxForPin( bbox );
551
552 if( m_pin.IsDangling() )
553 {
554 // Not much point caching this, but we could
555 const CIRCLE c = GetDanglingIndicator();
556
557 BOX2I cBox = BOX2I::ByCenter( c.Center, { c.Radius * 2, c.Radius * 2 } );
558 // TODO: need some way to find the thickness...?
559 // cBox.Inflate( ??? );
560
561 bbox.Merge( cBox );
562 }
563
564 bbox.Normalize();
565 bbox.Inflate( ( m_pin.GetPenWidth() / 2 ) + 1 );
566
567 return bbox;
568}
569
570
572{
573 return CIRCLE{
574 m_pin.GetPosition(),
576 };
577}
578
579
581{
582 const float offsetRatio =
584 return schIUScale.MilsToIU( KiROUND( 24 * offsetRatio ) );
585}
586
587
589{
590 int pinNameOffset = 0;
591 if( const SYMBOL* parentSymbol = m_pin.GetParentSymbol() )
592 {
593 if( parentSymbol->GetShowPinNames() )
594 pinNameOffset = parentSymbol->GetPinNameOffset();
595 }
596
597 // We're considering the PIN_RIGHT scenario
598 // TEXT
599 // X-------| TEXT
600 // TEXT
601 //
602 // We'll rotate it later.
603
604 OPT_BOX2I box;
605 const int pinLength = m_pin.GetLength();
606
607 if( pinNameOffset > 0 )
608 {
609 // This means name inside the pin
610 box = BOX2I::ByCenter( { pinLength, 0 }, m_nameExtentsCache.m_Extents );
611
612 // Bump over to be left aligned just inside the pin
613 box->Move( { m_nameExtentsCache.m_Extents.x / 2 + pinNameOffset, 0 } );
614 }
615 else
616 {
617 // The pin name is always over the pin
618 box = BOX2I::ByCenter( { pinLength / 2, 0 }, m_nameExtentsCache.m_Extents );
619
620 // Bump it up
621 box->Move( { 0, -m_nameExtentsCache.m_Extents.y / 2 - getPinTextOffset() } );
622 }
623
624 return box;
625}
626
627
629{
630 int pinNameOffset = 0;
631
632 if( const SYMBOL* parentSymbol = m_pin.GetParentSymbol() )
633 {
634 if( parentSymbol->GetShowPinNames() )
635 pinNameOffset = parentSymbol->GetPinNameOffset();
636 }
637
638 const int pinLength = m_pin.GetLength();
639
640 // The pin number is always over the pin (centered along its length)
641 OPT_BOX2I box = BOX2I::ByCenter( { pinLength / 2, 0 }, m_numExtentsCache.m_Extents );
642
643 // Check if both name and number are displayed (name outside the pin)
644 bool showBothNameAndNumber = ( pinNameOffset == 0
645 && !m_pin.GetShownName().empty()
646 && m_pin.GetParentSymbol()->GetShowPinNames() );
647
648 int textPos;
649 if( showBothNameAndNumber )
650 {
651 // When both are shown: name goes above, number goes below (top-aligned to bottom)
652 // Position the number below the pin, with its top edge at the clearance distance
653 textPos = m_numExtentsCache.m_Extents.y / 2 + getPinTextOffset();
654 }
655 else
656 {
657 // When only number is shown: place it above the pin
658 textPos = -m_numExtentsCache.m_Extents.y / 2 - getPinTextOffset();
659 }
660
661 // Bump it up (or down)
662 box->Move( { 0, textPos } );
663
664 return box;
665}
666
667
669{
671 return std::nullopt;
672
673 BOX2I box{
674 { -m_typeExtentsCache.m_Extents.x, -m_typeExtentsCache.m_Extents.y / 2 },
675 m_typeExtentsCache.m_Extents,
676 };
677
678 // Jog left
679 box.Move( { -schIUScale.MilsToIU( PIN_TEXT_MARGIN ) - TARGET_PIN_RADIUS, 0 } );
680
681 return box;
682}
683
684
686{
687 const OPT_BOX2I nameBox = getUntransformedPinNameBox();
688
689 if( !nameBox || m_pin.GetAlternates().empty() || !m_showAltIcons )
690 return std::nullopt;
691
692 const int iconSize = std::min( m_pin.GetNameTextSize(), schIUScale.mmToIU( 1.5 ) );
693
694 VECTOR2I c{ 0, ( nameBox->GetTop() + nameBox->GetBottom() ) / 2 };
695 if( m_pin.GetParentSymbol()->GetPinNameOffset() > 0 )
696 {
697 // name inside, so icon more inside
698 c.x = nameBox->GetRight() + iconSize * 0.75;
699 }
700 else
701 {
702 c.x = nameBox->GetLeft() - iconSize * 0.75;
703 }
704
705 return BOX2I::ByCenter( c, { iconSize, iconSize } );
706}
707
708
710{
711 const GRAPHIC_PINSHAPE shape = m_pin.GetShape();
712 const int decoSize = externalPinDecoSize( m_schSettings, m_pin );
713 const int intDecoSize = internalPinDecoSize( m_schSettings, m_pin );
714
715 const auto makeInvertBox =
716 [&]()
717 {
718 return BOX2I::ByCenter( { -decoSize, 0 }, { decoSize * 2, decoSize * 2 } );
719 };
720
721 const auto makeLowBox =
722 [&]()
723 {
724 return BOX2I::ByCorners( { -decoSize * 2, -decoSize * 2 }, { 0, 0 } );
725 };
726
727 const auto makeClockBox =
728 [&]()
729 {
730 return BOX2I::ByCorners( { 0, -intDecoSize }, { intDecoSize, intDecoSize } );
731 };
732
733 OPT_BOX2I box;
734
735 switch( shape )
736 {
738 box = makeInvertBox();
739 break;
740
742 box = makeClockBox();
743 break;
744
746 box = makeInvertBox();
747 box->Merge( makeClockBox() );
748 break;
749
751 box = makeLowBox();
752 break;
753
756 box = makeLowBox();
757 box->Merge( makeClockBox() );
758 break;
759
761 box = BOX2I::ByCenter( { 0, 0 }, { decoSize * 2, decoSize * 2 } );
762 break;
763
765 default:
766 // No decoration
767 break;
768 }
769
770 if( box )
771 {
772 // Put the box at the root of the pin
773 box->Move( { m_pin.GetLength(), 0 } );
774 box->Inflate( m_pin.GetPenWidth() / 2 );
775 }
776
777 return box;
778}
779
780
782{
785
786 if( box )
787 transformBoxForPin( *box );
788
789 return box;
790}
791
792
794{
797
798 if( box )
799 transformBoxForPin( *box );
800
801 return box;
802}
803
804
806{
808
809 if( box )
810 transformBoxForPin( *box );
811
812 return box;
813}
814
815
816std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> PIN_LAYOUT_CACHE::GetPinNameInfo( int aShadowWidth )
817{
819 wxString name = m_pin.GetShownName();
820
821 // TODO - work out exactly what we need to do to cache this
822 // (or if it's worth the memory/complexity)
823 // But it's not hugely expensive to recompute, and that's what's always been
824 // done to now
825 //
826 // Because pins are very likely to share a lot of characteristics, a global
827 // cache might make more sense than a per-pin cache.
828
829 if( name.IsEmpty() || !m_pin.GetParentSymbol()->GetShowPinNames() )
830 return std::nullopt;
831
832 std::optional<TEXT_INFO> info = TEXT_INFO();
833 info->m_Text = std::move( name );
834 info->m_TextSize = m_pin.GetNameTextSize();
835 info->m_Thickness = m_nameThickness;
836 info->m_Angle = ANGLE_HORIZONTAL;
837
838 bool nameInside = m_pin.GetParentSymbol()->GetPinNameOffset() > 0;
839
840 if( nameInside )
841 {
842 // This means name inside the pin
843 VECTOR2I pos = { m_pin.GetLength() + m_pin.GetParentSymbol()->GetPinNameOffset(), 0 };
844 const int shadowOffset = KiROUND( aShadowWidth * m_shadowOffsetAdjust ) / 2;
845
846 info->m_TextPosition = pos + VECTOR2I{ -shadowOffset, 0 };
847 info->m_HAlign = GR_TEXT_H_ALIGN_LEFT;
848 info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
850 }
851 else
852 {
853 // The pin name is always over the pin
854 VECTOR2I pos = { m_pin.GetLength() / 2, -getPinTextOffset() - info->m_Thickness / 2 };
855
856 info->m_TextPosition = pos;
857 info->m_HAlign = GR_TEXT_H_ALIGN_CENTER;
858 info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
859
860 // New policy: names follow same positioning semantics as numbers except when
861 // specified as inside. When names are inside, they should not overlap with the
862 // number position.
863 const SYMBOL* parentSym = m_pin.GetParentSymbol();
864 if( parentSym )
865 {
866 int maxHalfHeight = 0;
867 for( const SCH_PIN* p : parentSym->GetPins() )
868 {
869 wxString n = p->GetShownName();
870
871 if( n.IsEmpty() )
872 continue;
873
874 maxHalfHeight = std::max( maxHalfHeight, p->GetNameTextSize() / 2 );
875 }
876
878 VECTOR2I pinPos = m_pin.GetPosition();
879 const int halfLength = m_pin.GetLength() / 2;
880 PIN_ORIENTATION orient = m_pin.PinDrawOrient( DefaultTransform );
881 bool verticalOrient = ( orient == PIN_ORIENTATION::PIN_UP || orient == PIN_ORIENTATION::PIN_DOWN );
882
883 if( verticalOrient )
884 {
885 // Vertical pins: name mirrors number placement (left + rotated) for visual consistency.
886 // Use text HEIGHT (not width) for perpendicular offset - same as horizontal pins.
887 // This ensures consistent spacing between horizontal and vertical pin orientations.
888 int perpendicularOffset = clearance + info->m_TextSize / 2 + info->m_Thickness;
889 int centerX = pinPos.x - perpendicularOffset;
890 info->m_TextPosition = { centerX, pinPos.y };
891
892 if( orient == PIN_ORIENTATION::PIN_DOWN )
893 info->m_TextPosition.y += halfLength;
894 else
895 info->m_TextPosition.y -= halfLength;
896
897 info->m_Angle = ANGLE_VERTICAL;
898 info->m_HAlign = GR_TEXT_H_ALIGN_CENTER;
899 info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
900 }
901 else
902 {
903 // Horizontal pins: name above (negative Y) aligned to same Y offset logic as numbers.
904 info->m_TextPosition = { pinPos.x, pinPos.y - ( maxHalfHeight + clearance + info->m_Thickness ) };
905
906 if( orient == PIN_ORIENTATION::PIN_LEFT )
907 info->m_TextPosition.x -= halfLength;
908 else
909 info->m_TextPosition.x += halfLength;
910
911 info->m_Angle = ANGLE_HORIZONTAL;
912 info->m_HAlign = GR_TEXT_H_ALIGN_CENTER;
913 info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
914 }
915 }
916 }
917 return info;
918}
919
920
921// (Removed duplicate later GetPinNumberInfo – earlier definition retained at top of file.)
922
923
924std::optional<PIN_LAYOUT_CACHE::TEXT_INFO>
926{
928
930 return std::nullopt;
931
932 std::optional<TEXT_INFO> info = TEXT_INFO();
933 info->m_Text = m_pin.GetElectricalTypeName();
934 info->m_TextSize = std::max( m_pin.GetNameTextSize() * 3 / 4, schIUScale.mmToIU( 0.7 ) );
935 info->m_Angle = ANGLE_HORIZONTAL;
936 info->m_Thickness = info->m_TextSize / 8;
937 info->m_TextPosition = { -getPinTextOffset() - info->m_Thickness / 2
938 + KiROUND( aShadowWidth * m_shadowOffsetAdjust ) / 2,
939 0 };
940 info->m_HAlign = GR_TEXT_H_ALIGN_RIGHT;
941 info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
942
943 info->m_TextPosition.x -= TARGET_PIN_RADIUS;
944
945 if( m_pin.IsDangling() )
946 {
947 info->m_TextPosition.x -= TARGET_PIN_RADIUS / 2;
948 }
949
951 return info;
952}
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:451
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:163
virtual std::vector< SCH_PIN * > GetPins() const =0
virtual bool GetShowPinNames() const
Definition symbol.h:169
#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