You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

535 lines
16 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors.
  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 2
  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 here:
  18. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  19. * or you may search the http://www.gnu.org website for the version 2 license,
  20. * or you may write to the Free Software Foundation, Inc.,
  21. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  22. */
  23. #include <pcb_edit_frame.h>
  24. #include <base_units.h>
  25. #include <bitmaps.h>
  26. #include <board.h>
  27. #include <board_design_settings.h>
  28. #include <footprint.h>
  29. #include <pcb_textbox.h>
  30. #include <pcb_painter.h>
  31. #include <trigo.h>
  32. #include <string_utils.h>
  33. #include <geometry/shape_compound.h>
  34. #include <callback_gal.h>
  35. #include <convert_basic_shapes_to_polygon.h>
  36. #include <macros.h>
  37. #include <ignore.h>
  38. PCB_TEXTBOX::PCB_TEXTBOX( BOARD_ITEM* parent ) :
  39. PCB_SHAPE( parent, PCB_TEXTBOX_T, SHAPE_T::RECT ),
  40. EDA_TEXT( pcbIUScale )
  41. {
  42. SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
  43. SetVertJustify( GR_TEXT_V_ALIGN_TOP );
  44. SetMultilineAllowed( true );
  45. }
  46. PCB_TEXTBOX::~PCB_TEXTBOX()
  47. {
  48. }
  49. int PCB_TEXTBOX::GetTextMargin() const
  50. {
  51. return KiROUND( GetTextSize().y * 0.8 );
  52. }
  53. VECTOR2I PCB_TEXTBOX::GetTopLeft() const
  54. {
  55. EDA_ANGLE rotation = GetDrawRotation();
  56. if( rotation == ANGLE_90 )
  57. return VECTOR2I( GetStartX(), GetEndY() );
  58. else if( rotation == ANGLE_180 )
  59. return GetEnd();
  60. else if( rotation == ANGLE_270 )
  61. return VECTOR2I( GetEndX(), GetStartY() );
  62. else
  63. return GetStart();
  64. }
  65. VECTOR2I PCB_TEXTBOX::GetBotRight() const
  66. {
  67. EDA_ANGLE rotation = GetDrawRotation();
  68. if( rotation == ANGLE_90 )
  69. return VECTOR2I( GetEndX(), GetStartY() );
  70. else if( rotation == ANGLE_180 )
  71. return GetStart();
  72. else if( rotation == ANGLE_270 )
  73. return VECTOR2I( GetStartX(), GetEndY() );
  74. else
  75. return GetEnd();
  76. }
  77. void PCB_TEXTBOX::SetTop( int aVal )
  78. {
  79. EDA_ANGLE rotation = GetDrawRotation();
  80. if( rotation == ANGLE_90 || rotation == ANGLE_180 )
  81. SetEndY( aVal );
  82. else
  83. SetStartY( aVal );
  84. }
  85. void PCB_TEXTBOX::SetBottom( int aVal )
  86. {
  87. EDA_ANGLE rotation = GetDrawRotation();
  88. if( rotation == ANGLE_90 || rotation == ANGLE_180 )
  89. SetStartY( aVal );
  90. else
  91. SetEndY( aVal );
  92. }
  93. void PCB_TEXTBOX::SetLeft( int aVal )
  94. {
  95. EDA_ANGLE rotation = GetDrawRotation();
  96. if( rotation == ANGLE_180 || rotation == ANGLE_270 )
  97. SetEndX( aVal );
  98. else
  99. SetStartX( aVal );
  100. }
  101. void PCB_TEXTBOX::SetRight( int aVal )
  102. {
  103. EDA_ANGLE rotation = GetDrawRotation();
  104. if( rotation == ANGLE_180 || rotation == ANGLE_270 )
  105. SetStartX( aVal );
  106. else
  107. SetEndX( aVal );
  108. }
  109. std::vector<VECTOR2I> PCB_TEXTBOX::GetAnchorAndOppositeCorner() const
  110. {
  111. std::vector<VECTOR2I> pts;
  112. std::vector<VECTOR2I> corners = GetCorners();
  113. EDA_ANGLE textAngle( GetDrawRotation() );
  114. textAngle.Normalize();
  115. pts.emplace_back( corners[0] );
  116. if( textAngle < ANGLE_90 )
  117. {
  118. if( corners[1].y <= corners[0].y )
  119. pts.emplace_back( corners[1] );
  120. else
  121. pts.emplace_back( corners[3] );
  122. }
  123. else if( textAngle < ANGLE_180 )
  124. {
  125. if( corners[1].x <= corners[0].x )
  126. pts.emplace_back( corners[1] );
  127. else
  128. pts.emplace_back( corners[3] );
  129. }
  130. else if( textAngle < ANGLE_270 )
  131. {
  132. if( corners[1].y >= corners[0].y )
  133. pts.emplace_back( corners[1] );
  134. else
  135. pts.emplace_back( corners[3] );
  136. }
  137. else
  138. {
  139. if( corners[1].x >= corners[0].x )
  140. pts.emplace_back( corners[1] );
  141. else
  142. pts.emplace_back( corners[3] );
  143. }
  144. return pts;
  145. }
  146. VECTOR2I PCB_TEXTBOX::GetDrawPos() const
  147. {
  148. std::vector<VECTOR2I> corners = GetAnchorAndOppositeCorner();
  149. GR_TEXT_H_ALIGN_T effectiveAlignment = GetHorizJustify();
  150. VECTOR2I textAnchor;
  151. VECTOR2I offset;
  152. if( IsMirrored() )
  153. {
  154. switch( GetHorizJustify() )
  155. {
  156. case GR_TEXT_H_ALIGN_LEFT: effectiveAlignment = GR_TEXT_H_ALIGN_RIGHT; break;
  157. case GR_TEXT_H_ALIGN_CENTER: effectiveAlignment = GR_TEXT_H_ALIGN_CENTER; break;
  158. case GR_TEXT_H_ALIGN_RIGHT: effectiveAlignment = GR_TEXT_H_ALIGN_LEFT; break;
  159. }
  160. }
  161. switch( effectiveAlignment )
  162. {
  163. case GR_TEXT_H_ALIGN_LEFT:
  164. textAnchor = corners[0];
  165. offset = VECTOR2I( GetTextMargin(), GetTextMargin() );
  166. break;
  167. case GR_TEXT_H_ALIGN_CENTER:
  168. textAnchor = ( corners[0] + corners[1] ) / 2;
  169. offset = VECTOR2I( 0, GetTextMargin() );
  170. break;
  171. case GR_TEXT_H_ALIGN_RIGHT:
  172. textAnchor = corners[1];
  173. offset = VECTOR2I( -GetTextMargin(), GetTextMargin() );
  174. break;
  175. }
  176. RotatePoint( offset, GetDrawRotation() );
  177. return textAnchor + offset;
  178. }
  179. double PCB_TEXTBOX::ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const
  180. {
  181. constexpr double HIDE = std::numeric_limits<double>::max();
  182. KIGFX::PCB_PAINTER* painter = static_cast<KIGFX::PCB_PAINTER*>( aView->GetPainter() );
  183. KIGFX::PCB_RENDER_SETTINGS* renderSettings = painter->GetSettings();
  184. if( aLayer == LAYER_LOCKED_ITEM_SHADOW )
  185. {
  186. // Hide shadow if the main layer is not shown
  187. if( !aView->IsLayerVisible( m_layer ) )
  188. return HIDE;
  189. // Hide shadow on dimmed tracks
  190. if( renderSettings->GetHighContrast() )
  191. {
  192. if( m_layer != renderSettings->GetPrimaryHighContrastLayer() )
  193. return HIDE;
  194. }
  195. }
  196. return 0.0;
  197. }
  198. wxString PCB_TEXTBOX::GetShownText( int aDepth, bool aAllowExtraText ) const
  199. {
  200. BOARD* board = dynamic_cast<BOARD*>( GetParent() );
  201. std::function<bool( wxString* )> pcbTextResolver =
  202. [&]( wxString* token ) -> bool
  203. {
  204. if( token->IsSameAs( wxT( "LAYER" ) ) )
  205. {
  206. *token = GetLayerName();
  207. return true;
  208. }
  209. if( token->Contains( ':' ) )
  210. {
  211. wxString remainder;
  212. wxString ref = token->BeforeFirst( ':', &remainder );
  213. BOARD_ITEM* refItem = board->GetItem( KIID( ref ) );
  214. if( refItem && refItem->Type() == PCB_FOOTPRINT_T )
  215. {
  216. FOOTPRINT* refFP = static_cast<FOOTPRINT*>( refItem );
  217. if( refFP->ResolveTextVar( &remainder, aDepth + 1 ) )
  218. {
  219. *token = remainder;
  220. return true;
  221. }
  222. }
  223. }
  224. return false;
  225. };
  226. std::function<bool( wxString* )> boardTextResolver =
  227. [&]( wxString* token ) -> bool
  228. {
  229. return board->ResolveTextVar( token, aDepth + 1 );
  230. };
  231. wxString text = EDA_TEXT::GetShownText();
  232. if( board && HasTextVars() && aDepth < 10 )
  233. text = ExpandTextVars( text, &pcbTextResolver, &boardTextResolver, board->GetProject() );
  234. KIFONT::FONT* font = getDrawFont();
  235. std::vector<VECTOR2I> corners = GetAnchorAndOppositeCorner();
  236. int colWidth = ( corners[1] - corners[0] ).EuclideanNorm();
  237. colWidth -= GetTextMargin() * 2;
  238. font->LinebreakText( text, colWidth, GetTextSize(), GetTextThickness(), IsBold(), IsItalic() );
  239. return text;
  240. }
  241. void PCB_TEXTBOX::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
  242. {
  243. // Don't use GetShownText() here; we want to show the user the variable references
  244. aList.emplace_back( _( "Text Box" ), KIUI::EllipsizeStatusText( aFrame, GetText() ) );
  245. if( aFrame->GetName() == PCB_EDIT_FRAME_NAME && IsLocked() )
  246. aList.emplace_back( _( "Status" ), _( "Locked" ) );
  247. aList.emplace_back( _( "Layer" ), GetLayerName() );
  248. aList.emplace_back( _( "Mirror" ), IsMirrored() ? _( "Yes" ) : _( "No" ) );
  249. aList.emplace_back( _( "Angle" ), wxString::Format( "%g", GetTextAngle().AsDegrees() ) );
  250. aList.emplace_back( _( "Font" ), GetFont() ? GetFont()->GetName() : _( "Default" ) );
  251. aList.emplace_back( _( "Text Thickness" ), aFrame->MessageTextFromValue( GetTextThickness() ) );
  252. aList.emplace_back( _( "Text Width" ), aFrame->MessageTextFromValue( GetTextWidth() ) );
  253. aList.emplace_back( _( "Text Height" ), aFrame->MessageTextFromValue( GetTextHeight() ) );
  254. aList.emplace_back( _( "Box Width" ),
  255. aFrame->MessageTextFromValue( std::abs( GetEnd().x - GetStart().x ) ) );
  256. aList.emplace_back( _( "Box Height" ),
  257. aFrame->MessageTextFromValue( std::abs( GetEnd().y - GetStart().y ) ));
  258. m_stroke.GetMsgPanelInfo( aFrame, aList );
  259. }
  260. void PCB_TEXTBOX::Move( const VECTOR2I& aMoveVector )
  261. {
  262. PCB_SHAPE::Move( aMoveVector );
  263. EDA_TEXT::Offset( aMoveVector );
  264. }
  265. void PCB_TEXTBOX::Rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle )
  266. {
  267. PCB_SHAPE::Rotate( aRotCentre, aAngle );
  268. SetTextAngle( GetTextAngle() + aAngle );
  269. if( GetTextAngle().IsCardinal() && GetShape() != SHAPE_T::RECT )
  270. {
  271. std::vector<VECTOR2I> corners = GetCorners();
  272. VECTOR2I diag = corners[2] - corners[0];
  273. EDA_ANGLE angle = GetTextAngle();
  274. SetShape( SHAPE_T::RECT );
  275. SetStart( corners[0] );
  276. angle.Normalize();
  277. if( angle == ANGLE_90 )
  278. SetEnd( VECTOR2I( corners[0].x + abs( diag.x ), corners[0].y - abs( diag.y ) ) );
  279. else if( angle == ANGLE_180 )
  280. SetEnd( VECTOR2I( corners[0].x - abs( diag.x ), corners[0].y - abs( diag.y ) ) );
  281. else if( angle == ANGLE_270 )
  282. SetEnd( VECTOR2I( corners[0].x - abs( diag.x ), corners[0].y + abs( diag.y ) ) );
  283. else
  284. SetEnd( VECTOR2I( corners[0].x + abs( diag.x ), corners[0].y + abs( diag.y ) ) );
  285. }
  286. }
  287. void PCB_TEXTBOX::Mirror( const VECTOR2I& aCentre, bool aMirrorAroundXAxis )
  288. {
  289. // the position is mirrored, but not the text (or its justification)
  290. PCB_SHAPE::Mirror( aCentre, aMirrorAroundXAxis );
  291. BOX2I rect( m_start, m_end - m_start );
  292. rect.Normalize();
  293. m_start = VECTOR2I( rect.GetLeft(), rect.GetTop() );
  294. m_end = VECTOR2I( rect.GetRight(), rect.GetBottom() );
  295. }
  296. void PCB_TEXTBOX::Flip( const VECTOR2I& aCentre, bool aFlipLeftRight )
  297. {
  298. if( aFlipLeftRight )
  299. {
  300. m_start.x = aCentre.x - ( m_start.x - aCentre.x );
  301. m_end.x = aCentre.x - ( m_end.x - aCentre.x );
  302. SetTextAngle( -GetTextAngle() );
  303. }
  304. else
  305. {
  306. m_start.y = aCentre.y - ( m_start.y - aCentre.y );
  307. m_end.y = aCentre.y - ( m_end.y - aCentre.y );
  308. SetTextAngle( ANGLE_180 - GetTextAngle() );
  309. }
  310. SetLayer( FlipLayer( GetLayer(), GetBoard()->GetCopperLayerCount() ) );
  311. if( ( GetLayerSet() & LSET::SideSpecificMask() ).any() )
  312. SetMirrored( !IsMirrored() );
  313. }
  314. bool PCB_TEXTBOX::HitTest( const VECTOR2I& aPosition, int aAccuracy ) const
  315. {
  316. BOX2I rect = GetBoundingBox();
  317. rect.Inflate( aAccuracy );
  318. return rect.Contains( aPosition );
  319. }
  320. bool PCB_TEXTBOX::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
  321. {
  322. BOX2I rect = aRect;
  323. rect.Inflate( aAccuracy );
  324. if( aContained )
  325. return rect.Contains( GetBoundingBox() );
  326. return rect.Intersects( GetBoundingBox() );
  327. }
  328. wxString PCB_TEXTBOX::GetSelectMenuText( UNITS_PROVIDER* aUnitsProvider ) const
  329. {
  330. return wxString::Format( _( "PCB Text Box on %s"), GetLayerName() );
  331. }
  332. BITMAPS PCB_TEXTBOX::GetMenuImage() const
  333. {
  334. return BITMAPS::add_textbox;
  335. }
  336. EDA_ITEM* PCB_TEXTBOX::Clone() const
  337. {
  338. return new PCB_TEXTBOX( *this );
  339. }
  340. void PCB_TEXTBOX::swapData( BOARD_ITEM* aImage )
  341. {
  342. assert( aImage->Type() == PCB_TEXTBOX_T );
  343. std::swap( *((PCB_TEXTBOX*) this), *((PCB_TEXTBOX*) aImage) );
  344. }
  345. std::shared_ptr<SHAPE> PCB_TEXTBOX::GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHING aFlash ) const
  346. {
  347. std::shared_ptr<SHAPE_COMPOUND> shape = GetEffectiveTextShape();
  348. if( PCB_SHAPE::GetStroke().GetWidth() >= 0 )
  349. shape->AddShape( PCB_SHAPE::GetEffectiveShape( aLayer, aFlash ) );
  350. return shape;
  351. }
  352. void PCB_TEXTBOX::TransformTextToPolySet( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer,
  353. int aClearance, int aError, ERROR_LOC aErrorLoc ) const
  354. {
  355. KIGFX::GAL_DISPLAY_OPTIONS empty_opts;
  356. KIFONT::FONT* font = getDrawFont();
  357. int penWidth = GetEffectiveTextPenWidth();
  358. // Note: this function is mainly used in 3D viewer.
  359. // the polygonal shape of a text can have many basic shapes,
  360. // so combining these shapes can be very useful to create a final shape
  361. // swith a lot less vertices to speedup calculations using this final shape
  362. // Simplify shapes is not usually always efficient, but in this case it is.
  363. SHAPE_POLY_SET buffer;
  364. CALLBACK_GAL callback_gal( empty_opts,
  365. // Stroke callback
  366. [&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2 )
  367. {
  368. TransformOvalToPolygon( buffer, aPt1, aPt2, penWidth + ( 2 * aClearance ), aError,
  369. ERROR_INSIDE );
  370. },
  371. // Triangulation callback
  372. [&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2, const VECTOR2I& aPt3 )
  373. {
  374. buffer.NewOutline();
  375. for( const VECTOR2I& point : { aPt1, aPt2, aPt3 } )
  376. buffer.Append( point.x, point.y );
  377. } );
  378. font->Draw( &callback_gal, GetShownText(), GetDrawPos(), GetAttributes() );
  379. buffer.Simplify( SHAPE_POLY_SET::PM_FAST );
  380. aBuffer.Append( buffer );
  381. }
  382. void PCB_TEXTBOX::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer,
  383. int aClearance, int aError, ERROR_LOC aErrorLoc,
  384. bool aIgnoreLineWidth ) const
  385. {
  386. // Don't use PCB_SHAPE::TransformShapeToPolygon. We want to treat the textbox as filled even
  387. // if there's no background colour.
  388. std::vector<VECTOR2I> pts = GetRectCorners();
  389. aBuffer.NewOutline();
  390. for( const VECTOR2I& pt : pts )
  391. aBuffer.Append( pt );
  392. int width = GetWidth() + ( 2 * aClearance );
  393. if( width > 0 )
  394. {
  395. // Add in segments
  396. TransformOvalToPolygon( aBuffer, pts[0], pts[1], width, aError, aErrorLoc );
  397. TransformOvalToPolygon( aBuffer, pts[1], pts[2], width, aError, aErrorLoc );
  398. TransformOvalToPolygon( aBuffer, pts[2], pts[3], width, aError, aErrorLoc );
  399. TransformOvalToPolygon( aBuffer, pts[3], pts[0], width, aError, aErrorLoc );
  400. }
  401. }
  402. static struct PCB_TEXTBOX_DESC
  403. {
  404. PCB_TEXTBOX_DESC()
  405. {
  406. PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
  407. REGISTER_TYPE( PCB_TEXTBOX );
  408. propMgr.AddTypeCast( new TYPE_CAST<PCB_TEXTBOX, PCB_SHAPE> );
  409. propMgr.AddTypeCast( new TYPE_CAST<PCB_TEXTBOX, EDA_TEXT> );
  410. propMgr.InheritsAfter( TYPE_HASH( PCB_TEXTBOX ), TYPE_HASH( PCB_SHAPE ) );
  411. propMgr.InheritsAfter( TYPE_HASH( PCB_TEXTBOX ), TYPE_HASH( EDA_TEXT ) );
  412. propMgr.Mask( TYPE_HASH( PCB_TEXTBOX ), TYPE_HASH( EDA_SHAPE ), _HKI( "Shape" ) );
  413. propMgr.Mask( TYPE_HASH( PCB_TEXTBOX ), TYPE_HASH( EDA_SHAPE ), _HKI( "Start X" ) );
  414. propMgr.Mask( TYPE_HASH( PCB_TEXTBOX ), TYPE_HASH( EDA_SHAPE ), _HKI( "Start Y" ) );
  415. propMgr.Mask( TYPE_HASH( PCB_TEXTBOX ), TYPE_HASH( EDA_SHAPE ), _HKI( "End X" ) );
  416. propMgr.Mask( TYPE_HASH( PCB_TEXTBOX ), TYPE_HASH( EDA_SHAPE ), _HKI( "End Y" ) );
  417. propMgr.Mask( TYPE_HASH( PCB_TEXTBOX ), TYPE_HASH( EDA_SHAPE ), _HKI( "Line Width" ) );
  418. }
  419. } _PCB_TEXTBOX_DESC;