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
33std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> PIN_LAYOUT_CACHE::GetPinNumberInfo( int aShadowWidth )
34{
36
37 if( m_pin.GetShownNumber().IsEmpty() || !m_pin.GetParentSymbol()->GetShowPinNumbers() )
38 return std::nullopt;
39
40 // Draw the string recomputeCaches() measured, so the box always matches the text.
41 const wxString& formatted = m_numExtentsCache.m_Text;
42 const int num_size = m_pin.GetNumberTextSize();
43
44 std::optional<TEXT_INFO> info = TEXT_INFO();
45 info->m_Text = formatted;
46 info->m_TextSize = num_size;
47 info->m_Thickness = m_numberThickness;
48 info->m_HAlign = GR_TEXT_H_ALIGN_CENTER;
49 info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
50
51 PIN_ORIENTATION orient = m_pin.PinDrawOrient( DefaultTransform );
52
53 const SYMBOL* parentSym = m_pin.GetParentSymbol();
54
55 VECTOR2I pinPos = m_pin.GetPosition();
56 const int halfLength = m_pin.GetLength() / 2;
57 bool verticalOrient = ( orient == PIN_ORIENTATION::PIN_UP || orient == PIN_ORIENTATION::PIN_DOWN );
58
59 const int perpendicularOffset = getNumberBlockOffset();
60
61 // Keep a multi-line stacked block on the authored side of a mirrored symbol (issue 24894)
62 const bool flipStacked = formatted.Contains( '\n' )
63 && ( m_pin.GetFlipStackedTextSide() != m_pin.StackedTextSideFlipped( DefaultTransform ) );
64
65 if( verticalOrient )
66 {
67 // Vertical pins: text is rotated vertical so that it reads bottom->top.
68
69 // Check if both name and number are displayed
70 bool showBothNameAndNumber = !m_pin.GetShownName().IsEmpty()
71 && parentSym
72 && parentSym->GetShowPinNames()
73 && parentSym->GetPinNameOffset() == 0; // name is outside
74
75 int centerX;
76
77 if( showBothNameAndNumber )
78 {
79 // When both are shown: name goes to the left, number goes to the right
80 centerX = pinPos.x + perpendicularOffset;
81 }
82 else
83 {
84 // When only number is shown: place it to the left of the pin (right when mirrored)
85 centerX = flipStacked ? pinPos.x + perpendicularOffset : pinPos.x - perpendicularOffset;
86 }
87
88 info->m_TextPosition.x = centerX;
89
90 if( orient == PIN_ORIENTATION::PIN_DOWN )
91 info->m_TextPosition.y = pinPos.y + halfLength;
92 else
93 info->m_TextPosition.y = pinPos.y - halfLength;
94
95 info->m_Angle = ANGLE_VERTICAL;
96 }
97 else
98 {
99 // Horizontal pins: "above" means negative Y direction.
100
101 // Check if both name and number are displayed
102 bool showBothNameAndNumber = !m_pin.GetShownName().IsEmpty()
103 && parentSym
104 && parentSym->GetShowPinNames()
105 && parentSym->GetPinNameOffset() == 0; // name is outside
106
107 int centerY;
108
109 if( showBothNameAndNumber )
110 {
111 // When both are shown: name goes above, number goes below (top-aligned)
112 centerY = pinPos.y + perpendicularOffset;
113 }
114 else
115 {
116 // When only number is shown: place it above the pin (below when mirrored)
117 centerY = flipStacked ? pinPos.y + perpendicularOffset : pinPos.y - perpendicularOffset;
118 }
119
120 if( orient == PIN_ORIENTATION::PIN_LEFT )
121 info->m_TextPosition.x = pinPos.x - halfLength; // centered horizontally along pin
122 else
123 info->m_TextPosition.x = pinPos.x + halfLength; // centered horizontally along pin
124
125 info->m_TextPosition.y = centerY;
126 info->m_Angle = ANGLE_HORIZONTAL;
127 }
128
129 return info;
130}
131
132
133static int externalPinDecoSize( const SCHEMATIC_SETTINGS* aSettings, const SCH_PIN& aPin )
134{
135 if( aSettings && aSettings->m_PinSymbolSize )
136 return aSettings->m_PinSymbolSize;
137 return aPin.GetNumberTextSize() / 2;
138}
139
140
141static int internalPinDecoSize( const SCHEMATIC_SETTINGS* aSettings, const SCH_PIN& aPin )
142{
143 if( aSettings && aSettings->m_PinSymbolSize > 0 )
144 return aSettings->m_PinSymbolSize;
145 return aPin.GetNameTextSize() != 0 ? aPin.GetNameTextSize() / 2 : aPin.GetNumberTextSize() / 2;
146}
147
148
150 m_pin( aPin ), m_schSettings( nullptr ), m_dirtyFlags( DIRTY_FLAGS::ALL )
151{
152 // Resolve the schematic (can be null, e.g. in previews)
153 const SCHEMATIC* schematic = aPin.Schematic();
154
155 if( schematic )
156 m_schSettings = &schematic->Settings();
157}
158
159
160void PIN_LAYOUT_CACHE::MarkDirty( int aDirtyFlags )
161{
162 m_dirtyFlags |= aDirtyFlags;
163}
164
165
166void PIN_LAYOUT_CACHE::SetRenderParameters( int aNameThickness, int aNumberThickness,
167 bool aShowElectricalType, bool aShowAltIcons )
168{
169 if( aNameThickness != m_nameThickness )
170 {
172 m_nameThickness = aNameThickness;
173 }
174
175 if( aNumberThickness != m_numberThickness )
176 {
178 m_numberThickness = aNumberThickness;
179 }
180
181 if( aShowElectricalType != m_showElectricalType )
182 {
184 m_showElectricalType = aShowElectricalType;
185 }
186
187 // Not (yet?) cached
188 m_showAltIcons = aShowAltIcons;
189}
190
191
192void PIN_LAYOUT_CACHE::recomputeExtentsCache( bool aDefinitelyDirty, KIFONT::FONT* aFont, int aSize,
193 const wxString& aText,
194 const KIFONT::METRICS& aFontMetrics,
195 TEXT_EXTENTS_CACHE& aCache )
196{
197 // Even if not definitely dirty, verify no font changes. The text is part of the key because
198 // the stacked-pin layout also depends on the pin length, which has no dirty flag of its own.
199 if( !aDefinitelyDirty && aCache.m_Font == aFont && aCache.m_FontSize == aSize
200 && aCache.m_Text == aText )
201 {
202 return;
203 }
204
205 aCache.m_Font = aFont;
206 aCache.m_FontSize = aSize;
207 aCache.m_Text = aText;
208
209 VECTOR2D fontSize( aSize, aSize );
210 int penWidth = GetPenSizeForNormal( aSize );
211
212 // Handle multi-line text bounds properly
213 if( aText.StartsWith( "[" ) && aText.EndsWith( "]" ) && aText.Contains( "\n" ) )
214 {
215 // Extract content between braces and split into lines
216 wxString content = aText.Mid( 1, aText.Length() - 2 );
217 wxArrayString lines;
218 wxStringSplit( content, lines, '\n' );
219
220 if( lines.size() > 1 )
221 {
222 int lineSpacing = KiROUND( aSize * 1.3 ); // Same as drawMultiLineText
223 int maxWidth = 0;
224
225 // Find the widest line
226 for( const wxString& line : lines )
227 {
228 wxString trimmedLine = line;
229 trimmedLine.Trim( true ).Trim( false );
230 VECTOR2I lineExtents = aFont->StringBoundaryLimits( trimmedLine, fontSize, penWidth, false, false, aFontMetrics );
231 maxWidth = std::max( maxWidth, lineExtents.x );
232 }
233
234 // Calculate total dimensions - width is max line width, height accounts for all lines
235 int totalHeight = aSize + ( lines.size() - 1 ) * lineSpacing;
236
237 // Add space for braces. drawBracesAroundText() anchors each brace a braceWidth
238 // clear of the text and drawBrace() curls a further braceWidth outwards.
239 int braceWidth = aSize / 3;
240 maxWidth += braceWidth * 4;
241 totalHeight += aSize / 3; // Extra height for brace extensions
242
243 aCache.m_Extents = VECTOR2I( maxWidth, totalHeight );
244 return;
245 }
246 }
247
248 // Single line text (normal case)
249 aCache.m_Extents = aFont->StringBoundaryLimits( aText, fontSize, penWidth, false, false, aFontMetrics );
250}
251
252
254{
256 KIFONT::FONT* font = KIFONT::FONT::GetFont( cfg ? cfg->m_Appearance.default_font : wxString( "" ) );
257 const KIFONT::METRICS& metrics = m_pin.GetFontMetrics();
258
259 // Due to the fact a shadow text in position INSIDE or OUTSIDE is drawn left or right aligned,
260 // it needs an offset = shadowWidth/2 to be drawn at the same place as normal text
261 // texts drawn as GR_TEXT_H_ALIGN_CENTER do not need a specific offset.
262 // this offset is shadowWidth/2 but for some reason we need to slightly modify this offset
263 // for a better look (better alignment of shadow shape), for KiCad font only
264 if( !font->IsOutline() )
265 m_shadowOffsetAdjust = 1.2f; // Value chosen after tests
266 else
268
269 {
270 const int numSize = m_pin.GetNumberTextSize();
271 const int length = m_pin.GetLength();
272 const wxString& number = m_pin.GetShownNumber();
273
274 // Formatting a stacked number measures it, so only redo it when one of its inputs moves.
275 // The pin length is one of them and has no dirty flag of its own.
276 if( isDirty( DIRTY_FLAGS::NUMBER ) || m_numSource != number || m_numSourceLength != length
277 || m_numExtentsCache.m_Font != font || m_numExtentsCache.m_FontSize != numSize )
278 {
279 m_numSource = number;
280 m_numSourceLength = length;
281
282 recomputeExtentsCache( true, font, numSize,
283 FormatStackedPinForDisplay( number, length, numSize, font, metrics ),
284 metrics, m_numExtentsCache );
285 }
286 }
287
288 {
289 const bool dirty = isDirty( DIRTY_FLAGS::NAME );
290 const wxString name = m_pin.GetShownName();
291 recomputeExtentsCache( dirty, font, m_pin.GetNameTextSize(), name, metrics, m_nameExtentsCache );
292 }
293
294 {
295 double fontSize = std::max( m_pin.GetNameTextSize() * 3 / 4, schIUScale.mmToIU( 0.7 ) );
297 m_pin.GetElectricalTypeName(), metrics, m_typeExtentsCache );
298 }
299
301}
302
303
305{
306 // Now, calculate boundary box corners position for the actual pin orientation
307 switch( m_pin.PinDrawOrient( DefaultTransform ) )
308 {
310 {
311 // Pin is rotated and texts positions are mirrored
312 VECTOR2I c1{ aBox.GetLeft(), aBox.GetTop() };
313 VECTOR2I c2{ aBox.GetRight(), aBox.GetBottom() };
314
315 RotatePoint( c1, VECTOR2I( 0, 0 ), ANGLE_90 );
316 RotatePoint( c2, VECTOR2I( 0, 0 ), ANGLE_90 );
317
318 aBox = BOX2I::ByCorners( c1, c2 );
319 break;
320 }
322 {
323 VECTOR2I c1{ aBox.GetLeft(), aBox.GetTop() };
324 VECTOR2I c2{ aBox.GetRight(), aBox.GetBottom() };
325
326 RotatePoint( c1, VECTOR2I( 0, 0 ), -ANGLE_90 );
327 RotatePoint( c2, VECTOR2I( 0, 0 ), -ANGLE_90 );
328
329 c1.x = -c1.x;
330 c2.x = -c2.x;
331
332 aBox = BOX2I::ByCorners( c1, c2 );
333 break;
334 }
336 // Flip it around
337 aBox.Move( { -aBox.GetCenter().x * 2, 0 } );
338 break;
339
340 default:
342 // Already in this form
343 break;
344 }
345
346 aBox.Move( m_pin.GetPosition() );
347}
348
349
351{
352 // Local nominal position for a PIN_RIGHT orientation.
353 const VECTOR2I baseLocal = aInfo.m_TextPosition;
354
355 // We apply a rotation/mirroring depending on the pin orientation so that the text anchor
356 // maintains a constant perpendicular offset from the pin origin regardless of rotation.
357 VECTOR2I rotated = baseLocal;
358 EDA_ANGLE finalAngle = aInfo.m_Angle;
359
360 switch( m_pin.PinDrawOrient( DefaultTransform ) )
361 {
362 case PIN_ORIENTATION::PIN_RIGHT: // identity
363 break;
365 rotated.x = -rotated.x;
366 rotated.y = -rotated.y;
367 aInfo.m_HAlign = GetFlippedAlignment( aInfo.m_HAlign );
368 break;
369 case PIN_ORIENTATION::PIN_UP: // rotate +90 (x,y)->(y,-x) and vertical text
370 rotated = { baseLocal.y, -baseLocal.x };
371 finalAngle = ANGLE_VERTICAL;
372 break;
373 case PIN_ORIENTATION::PIN_DOWN: // rotate -90 (x,y)->(-y,x) and vertical text, flip h-align
374 rotated = { -baseLocal.y, baseLocal.x };
375 finalAngle = ANGLE_VERTICAL;
376 aInfo.m_HAlign = GetFlippedAlignment( aInfo.m_HAlign );
377 break;
378 default:
379 break;
380 }
381
382 aInfo.m_TextPosition = rotated + m_pin.GetPosition();
383 aInfo.m_Angle = finalAngle;
384}
385
386
387BOX2I PIN_LAYOUT_CACHE::GetPinBoundingBox( bool aIncludeLabelsOnInvisiblePins,
388 bool aIncludeNameAndNumber, bool aIncludeElectricalType )
389{
390 if( const SCH_SYMBOL* symbol = dynamic_cast<const SCH_SYMBOL*>( m_pin.GetParentSymbol() ) )
391 {
392 SCH_PIN* const libPin = m_pin.GetLibPin();
393 wxCHECK( libPin, BOX2I() );
394
395 BOX2I r = libPin->GetBoundingBox( aIncludeLabelsOnInvisiblePins, aIncludeNameAndNumber,
396 aIncludeElectricalType );
397
398 r = symbol->GetTransform().TransformCoordinate( r );
399 r.Offset( symbol->GetPosition() );
400 r.Normalize();
401
402 return r;
403 }
404
405 bool includeName = aIncludeNameAndNumber && !m_pin.GetShownName().IsEmpty();
406 bool includeNumber = aIncludeNameAndNumber && !m_pin.GetShownNumber().IsEmpty();
407 bool includeType = aIncludeElectricalType;
408
409 if( !aIncludeLabelsOnInvisiblePins && !m_pin.IsVisible() )
410 {
411 includeName = false;
412 includeNumber = false;
413 includeType = false;
414 }
415
416 if( const SYMBOL* parentSymbol = m_pin.GetParentSymbol() )
417 {
418 if( !parentSymbol->GetShowPinNames() )
419 includeName = false;
420
421 if( !parentSymbol->GetShowPinNumbers() )
422 includeNumber = false;
423 }
424
426
427 const int pinLength = m_pin.GetLength();
428
429 // Creating and merging all the boxes is pretty quick, if cached we'd have
430 // to track many variables here, which is possible, but unlikely to be worth it.
431 BOX2I bbox;
432
433 // Untransformed pin box
434 {
435 BOX2I pinBox = BOX2I::ByCorners( { 0, 0 }, { pinLength, 0 } );
436 pinBox.Inflate( m_pin.GetPenWidth() / 2 );
437 bbox.Merge( pinBox );
438 }
439
441 {
442 bbox.Merge( *decoBox );
443 }
444
445 if( includeName )
446 {
447 if( OPT_BOX2I nameBox = getUntransformedPinNameBox() )
448 {
449 bbox.Merge( *nameBox );
450 }
451
452 if( OPT_BOX2I altIconBox = getUntransformedAltIconBox() )
453 {
454 bbox.Merge( *altIconBox );
455 }
456 }
457
458 if( includeNumber )
459 {
461 {
462 bbox.Merge( *numBox );
463 }
464 }
465
466 if( includeType )
467 {
468 if( OPT_BOX2I typeBox = getUntransformedPinTypeBox() )
469 {
470 bbox.Merge( *typeBox );
471 }
472 }
473
474 transformBoxForPin( bbox );
475
476 if( m_pin.IsDangling() )
477 {
478 // Not much point caching this, but we could
479 const CIRCLE c = GetDanglingIndicator();
480
481 BOX2I cBox = BOX2I::ByCenter( c.Center, { c.Radius * 2, c.Radius * 2 } );
482 // TODO: need some way to find the thickness...?
483 // cBox.Inflate( ??? );
484
485 bbox.Merge( cBox );
486 }
487
488 bbox.Normalize();
489 bbox.Inflate( ( m_pin.GetPenWidth() / 2 ) + 1 );
490
491 return bbox;
492}
493
494
496{
497 return CIRCLE{
498 m_pin.GetPosition(),
500 };
501}
502
503
505{
506 const int numSize = m_pin.GetNumberTextSize();
507 int perpHeight = numSize;
508
509 if( m_numExtentsCache.m_Text.Contains( '\n' ) )
510 {
511 wxArrayString lines;
512 wxStringSplit( m_numExtentsCache.m_Text, lines, '\n' );
513 perpHeight = (int) lines.size() * KiROUND( numSize * 1.3 );
514 }
515
516 return perpHeight / 2 + getPinTextOffset() + schIUScale.MilsToIU( PIN_TEXT_MARGIN ) + m_numberThickness;
517}
518
519
521{
522 const float offsetRatio =
524 return schIUScale.MilsToIU( KiROUND( 24 * offsetRatio ) );
525}
526
527
529{
530 int pinNameOffset = 0;
531 if( const SYMBOL* parentSymbol = m_pin.GetParentSymbol() )
532 {
533 if( parentSymbol->GetShowPinNames() )
534 pinNameOffset = parentSymbol->GetPinNameOffset();
535 }
536
537 // We're considering the PIN_RIGHT scenario
538 // TEXT
539 // X-------| TEXT
540 // TEXT
541 //
542 // We'll rotate it later.
543
544 OPT_BOX2I box;
545 const int pinLength = m_pin.GetLength();
546
547 if( pinNameOffset > 0 )
548 {
549 // This means name inside the pin
550 box = BOX2I::ByCenter( { pinLength, 0 }, m_nameExtentsCache.m_Extents );
551
552 // Bump over to be left aligned just inside the pin
553 box->Move( { m_nameExtentsCache.m_Extents.x / 2 + pinNameOffset, 0 } );
554 }
555 else
556 {
557 // The pin name is always over the pin
558 box = BOX2I::ByCenter( { pinLength / 2, 0 }, m_nameExtentsCache.m_Extents );
559
560 // Bump it up
561 box->Move( { 0, -m_nameExtentsCache.m_Extents.y / 2 - getPinTextOffset() } );
562 }
563
564 return box;
565}
566
567
569{
570 int pinNameOffset = 0;
571
572 if( const SYMBOL* parentSymbol = m_pin.GetParentSymbol() )
573 {
574 if( parentSymbol->GetShowPinNames() )
575 pinNameOffset = parentSymbol->GetPinNameOffset();
576 }
577
578 const int pinLength = m_pin.GetLength();
579
580 // The pin number is always over the pin (centered along its length)
581 OPT_BOX2I box = BOX2I::ByCenter( { pinLength / 2, 0 }, m_numExtentsCache.m_Extents );
582
583 // Check if both name and number are displayed (name outside the pin)
584 bool showBothNameAndNumber = ( pinNameOffset == 0
585 && !m_pin.GetShownName().empty()
586 && m_pin.GetParentSymbol()->GetShowPinNames() );
587
588 // Same side decision as GetPinNumberInfo(), so the box follows a mirrored block (issue 24894)
589 const bool flipStacked = m_numExtentsCache.m_Text.Contains( '\n' )
590 && ( m_pin.GetFlipStackedTextSide() != m_pin.StackedTextSideFlipped( DefaultTransform ) );
591
592 const int offset = m_numExtentsCache.m_Extents.y / 2 + getPinTextOffset();
593
594 int textPos;
595
596 if( showBothNameAndNumber )
597 {
598 // When both are shown: name goes above, number goes below (top-aligned to bottom)
599 // Position the number below the pin, with its top edge at the clearance distance
600 textPos = offset;
601 }
602 else
603 {
604 // When only number is shown: place it above the pin (below when mirrored)
605 textPos = flipStacked ? offset : -offset;
606 }
607
608 // Bump it up (or down)
609 box->Move( { 0, textPos } );
610
611 return box;
612}
613
614
616{
618 return std::nullopt;
619
620 BOX2I box{
621 { -m_typeExtentsCache.m_Extents.x, -m_typeExtentsCache.m_Extents.y / 2 },
622 m_typeExtentsCache.m_Extents,
623 };
624
625 // Jog left
626 box.Move( { -schIUScale.MilsToIU( PIN_TEXT_MARGIN ) - TARGET_PIN_RADIUS, 0 } );
627
628 return box;
629}
630
631
633{
634 const OPT_BOX2I nameBox = getUntransformedPinNameBox();
635
636 if( !nameBox || m_pin.GetAlternates().empty() || !m_showAltIcons )
637 return std::nullopt;
638
639 const int iconSize = std::min( m_pin.GetNameTextSize(), schIUScale.mmToIU( 1.5 ) );
640
641 VECTOR2I c{ 0, ( nameBox->GetTop() + nameBox->GetBottom() ) / 2 };
642 if( m_pin.GetParentSymbol()->GetPinNameOffset() > 0 )
643 {
644 // name inside, so icon more inside
645 c.x = nameBox->GetRight() + iconSize * 0.75;
646 }
647 else
648 {
649 c.x = nameBox->GetLeft() - iconSize * 0.75;
650 }
651
652 return BOX2I::ByCenter( c, { iconSize, iconSize } );
653}
654
655
657{
658 const GRAPHIC_PINSHAPE shape = m_pin.GetShape();
659 const int decoSize = externalPinDecoSize( m_schSettings, m_pin );
660 const int intDecoSize = internalPinDecoSize( m_schSettings, m_pin );
661
662 const auto makeInvertBox =
663 [&]()
664 {
665 return BOX2I::ByCenter( { -decoSize, 0 }, { decoSize * 2, decoSize * 2 } );
666 };
667
668 const auto makeLowBox =
669 [&]()
670 {
671 return BOX2I::ByCorners( { -decoSize * 2, -decoSize * 2 }, { 0, 0 } );
672 };
673
674 const auto makeClockBox =
675 [&]()
676 {
677 return BOX2I::ByCorners( { 0, -intDecoSize }, { intDecoSize, intDecoSize } );
678 };
679
680 OPT_BOX2I box;
681
682 switch( shape )
683 {
685 box = makeInvertBox();
686 break;
687
689 box = makeClockBox();
690 break;
691
693 box = makeInvertBox();
694 box->Merge( makeClockBox() );
695 break;
696
698 box = makeLowBox();
699 break;
700
703 box = makeLowBox();
704 box->Merge( makeClockBox() );
705 break;
706
708 box = BOX2I::ByCenter( { 0, 0 }, { decoSize * 2, decoSize * 2 } );
709 break;
710
712 default:
713 // No decoration
714 break;
715 }
716
717 if( box )
718 {
719 // Put the box at the root of the pin
720 box->Move( { m_pin.GetLength(), 0 } );
721 box->Inflate( m_pin.GetPenWidth() / 2 );
722 }
723
724 return box;
725}
726
727
729{
732
733 if( box )
734 transformBoxForPin( *box );
735
736 return box;
737}
738
739
741{
744
745 if( box )
746 transformBoxForPin( *box );
747
748 return box;
749}
750
751
753{
755
756 if( box )
757 transformBoxForPin( *box );
758
759 return box;
760}
761
762
763std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> PIN_LAYOUT_CACHE::GetPinNameInfo( int aShadowWidth )
764{
766 wxString name = m_pin.GetShownName();
767
768 // TODO - work out exactly what we need to do to cache this
769 // (or if it's worth the memory/complexity)
770 // But it's not hugely expensive to recompute, and that's what's always been
771 // done to now
772 //
773 // Because pins are very likely to share a lot of characteristics, a global
774 // cache might make more sense than a per-pin cache.
775
776 if( name.IsEmpty() || !m_pin.GetParentSymbol()->GetShowPinNames() )
777 return std::nullopt;
778
779 std::optional<TEXT_INFO> info = TEXT_INFO();
780 info->m_Text = std::move( name );
781 info->m_TextSize = m_pin.GetNameTextSize();
782 info->m_Thickness = m_nameThickness;
783 info->m_Angle = ANGLE_HORIZONTAL;
784
785 bool nameInside = m_pin.GetParentSymbol()->GetPinNameOffset() > 0;
786
787 if( nameInside )
788 {
789 // This means name inside the pin
790 VECTOR2I pos = { m_pin.GetLength() + m_pin.GetParentSymbol()->GetPinNameOffset(), 0 };
791 const int shadowOffset = KiROUND( aShadowWidth * m_shadowOffsetAdjust ) / 2;
792
793 info->m_TextPosition = pos + VECTOR2I{ -shadowOffset, 0 };
794 info->m_HAlign = GR_TEXT_H_ALIGN_LEFT;
795 info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
797 }
798 else
799 {
800 // The pin name is always over the pin
801 VECTOR2I pos = { m_pin.GetLength() / 2, -getPinTextOffset() - info->m_Thickness / 2 };
802
803 info->m_TextPosition = pos;
804 info->m_HAlign = GR_TEXT_H_ALIGN_CENTER;
805 info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
806
807 // New policy: names follow same positioning semantics as numbers except when
808 // specified as inside. When names are inside, they should not overlap with the
809 // number position.
810 const SYMBOL* parentSym = m_pin.GetParentSymbol();
811 if( parentSym )
812 {
813 int maxHalfHeight = 0;
814 for( const SCH_PIN* p : parentSym->GetPins() )
815 {
816 wxString n = p->GetShownName();
817
818 if( n.IsEmpty() )
819 continue;
820
821 maxHalfHeight = std::max( maxHalfHeight, p->GetNameTextSize() / 2 );
822 }
823
825 VECTOR2I pinPos = m_pin.GetPosition();
826 const int halfLength = m_pin.GetLength() / 2;
827 PIN_ORIENTATION orient = m_pin.PinDrawOrient( DefaultTransform );
828 bool verticalOrient = ( orient == PIN_ORIENTATION::PIN_UP || orient == PIN_ORIENTATION::PIN_DOWN );
829
830 if( verticalOrient )
831 {
832 // Vertical pins: name mirrors number placement (left + rotated) for visual consistency.
833 // Use text HEIGHT (not width) for perpendicular offset - same as horizontal pins.
834 // This ensures consistent spacing between horizontal and vertical pin orientations.
835 int perpendicularOffset = clearance + info->m_TextSize / 2 + info->m_Thickness;
836 int centerX = pinPos.x - perpendicularOffset;
837 info->m_TextPosition = { centerX, pinPos.y };
838
839 if( orient == PIN_ORIENTATION::PIN_DOWN )
840 info->m_TextPosition.y += halfLength;
841 else
842 info->m_TextPosition.y -= halfLength;
843
844 info->m_Angle = ANGLE_VERTICAL;
845 info->m_HAlign = GR_TEXT_H_ALIGN_CENTER;
846 info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
847 }
848 else
849 {
850 // Horizontal pins: name above (negative Y) aligned to same Y offset logic as numbers.
851 info->m_TextPosition = { pinPos.x, pinPos.y - ( maxHalfHeight + clearance + info->m_Thickness ) };
852
853 if( orient == PIN_ORIENTATION::PIN_LEFT )
854 info->m_TextPosition.x -= halfLength;
855 else
856 info->m_TextPosition.x += halfLength;
857
858 info->m_Angle = ANGLE_HORIZONTAL;
859 info->m_HAlign = GR_TEXT_H_ALIGN_CENTER;
860 info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
861 }
862 }
863 }
864 return info;
865}
866
867
868// (Removed duplicate later GetPinNumberInfo – earlier definition retained at top of file.)
869
870
871std::optional<PIN_LAYOUT_CACHE::TEXT_INFO>
873{
875
877 return std::nullopt;
878
879 std::optional<TEXT_INFO> info = TEXT_INFO();
880 info->m_Text = m_pin.GetElectricalTypeName();
881 info->m_TextSize = std::max( m_pin.GetNameTextSize() * 3 / 4, schIUScale.mmToIU( 0.7 ) );
882 info->m_Angle = ANGLE_HORIZONTAL;
883 info->m_Thickness = info->m_TextSize / 8;
884 info->m_TextPosition = { -getPinTextOffset() - info->m_Thickness / 2
885 + KiROUND( aShadowWidth * m_shadowOffsetAdjust ) / 2,
886 0 };
887 info->m_HAlign = GR_TEXT_H_ALIGN_RIGHT;
888 info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
889
890 info->m_TextPosition.x -= TARGET_PIN_RADIUS;
891
892 if( m_pin.IsDangling() )
893 {
894 info->m_TextPosition.x -= TARGET_PIN_RADIUS / 2;
895 }
896
898 return info;
899}
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:123
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
std::optional< BOX2I > OPT_BOX2I
Definition box2.h:931
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:553
static constexpr BOX2< VECTOR2I > ByCorners(const VECTOR2I &aCorner1, const VECTOR2I &aCorner2)
Definition box2.h:67
constexpr BOX2< Vec > & Normalize()
Ensure that the height and width are positive.
Definition box2.h:143
static constexpr BOX2< VECTOR2I > ByCenter(const VECTOR2I &aCenter, const SizeVec &aSize)
Definition box2.h:72
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:653
constexpr const Vec GetCenter() const
Definition box2.h:227
constexpr coord_type GetLeft() const
Definition box2.h:225
constexpr void Move(const Vec &aMoveVector)
Move the rectangle by the aMoveVector.
Definition box2.h:135
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr coord_type GetTop() const
Definition box2.h:226
constexpr void Offset(coord_type dx, coord_type dy)
Definition box2.h:256
constexpr coord_type GetBottom() const
Definition box2.h:219
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:439
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.
int getNumberBlockOffset() const
Get the distance from the pin to the centre of the drawn number, measured perpendicular to the pin.
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:148
SCHEMATIC_SETTINGS & Settings() const
SCHEMATIC * Schematic() const
Search the item hierarchy to find a SCHEMATIC.
Definition sch_item.cpp:281
int GetNumberTextSize() const
Definition sch_pin.cpp:865
int GetNameTextSize() const
Definition sch_pin.cpp:839
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
Definition sch_pin.h:278
Schematic symbol object.
Definition sch_symbol.h:75
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:424
static constexpr EDA_ANGLE ANGLE_VERTICAL
Definition eda_angle.h:419
static constexpr EDA_ANGLE ANGLE_HORIZONTAL
Definition eda_angle.h:418
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)
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
wxString FormatStackedPinForDisplay(const wxString &aPinNumber, int aPinLength, int aTextSize, KIFONT::FONT *aFont, const KIFONT::METRICS &aFontMetrics)
Break a stacked pin number of the form "[A,B,C]" into one number per line when it is too wide to sit ...
Definition sch_pin.cpp:50
#define PIN_TEXT_MARGIN
Definition sch_pin.cpp:108
#define TARGET_PIN_RADIUS
Definition sch_pin.h:46
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:225
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682