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.

363 lines
11 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. PCB_TEXTBOX::PCB_TEXTBOX( BOARD_ITEM* parent ) :
  38. PCB_SHAPE( parent, PCB_TEXTBOX_T, SHAPE_T::RECT ),
  39. EDA_TEXT()
  40. {
  41. SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
  42. SetVertJustify( GR_TEXT_V_ALIGN_TOP );
  43. SetMultilineAllowed( true );
  44. }
  45. PCB_TEXTBOX::~PCB_TEXTBOX()
  46. {
  47. }
  48. int PCB_TEXTBOX::GetTextMargin() const
  49. {
  50. return KiROUND( GetTextSize().y * 0.8 );
  51. }
  52. std::vector<VECTOR2I> PCB_TEXTBOX::GetAnchorAndOppositeCorner() const
  53. {
  54. EDA_ANGLE epsilon( 1.0, DEGREES_T ); // Yeah, it's a pretty coarse epsilon, but anything
  55. // under 45° will work for our purposes.
  56. std::vector<VECTOR2I> pts;
  57. std::vector<VECTOR2I> corners = GetCorners();
  58. EDA_ANGLE textAngle = GetDrawRotation().Normalize();
  59. EDA_ANGLE toCorner1 = EDA_ANGLE( corners[1] - corners[0] ).Normalize();
  60. EDA_ANGLE fromCorner1 = ( toCorner1 + ANGLE_180 ).Normalize();
  61. pts.emplace_back( corners[0] );
  62. if( std::abs( toCorner1 - textAngle ) < epsilon || std::abs( fromCorner1 - textAngle ) < epsilon )
  63. pts.emplace_back( corners[1] );
  64. else
  65. pts.emplace_back( corners[3] );
  66. return pts;
  67. }
  68. VECTOR2I PCB_TEXTBOX::GetDrawPos() const
  69. {
  70. std::vector<VECTOR2I> corners = GetAnchorAndOppositeCorner();
  71. GR_TEXT_H_ALIGN_T effectiveAlignment = GetHorizJustify();
  72. VECTOR2I textAnchor;
  73. VECTOR2I offset;
  74. if( IsMirrored() )
  75. {
  76. switch( GetHorizJustify() )
  77. {
  78. case GR_TEXT_H_ALIGN_LEFT: effectiveAlignment = GR_TEXT_H_ALIGN_RIGHT; break;
  79. case GR_TEXT_H_ALIGN_CENTER: effectiveAlignment = GR_TEXT_H_ALIGN_CENTER; break;
  80. case GR_TEXT_H_ALIGN_RIGHT: effectiveAlignment = GR_TEXT_H_ALIGN_LEFT; break;
  81. }
  82. }
  83. switch( effectiveAlignment )
  84. {
  85. case GR_TEXT_H_ALIGN_LEFT:
  86. textAnchor = corners[0];
  87. offset = VECTOR2I( GetTextMargin(), GetTextMargin() );
  88. break;
  89. case GR_TEXT_H_ALIGN_CENTER:
  90. textAnchor = ( corners[0] + corners[1] ) / 2;
  91. offset = VECTOR2I( 0, GetTextMargin() );
  92. break;
  93. case GR_TEXT_H_ALIGN_RIGHT:
  94. textAnchor = corners[1];
  95. offset = VECTOR2I( -GetTextMargin(), GetTextMargin() );
  96. break;
  97. }
  98. RotatePoint( offset, GetDrawRotation() );
  99. return textAnchor + offset;
  100. }
  101. double PCB_TEXTBOX::ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const
  102. {
  103. constexpr double HIDE = std::numeric_limits<double>::max();
  104. KIGFX::PCB_PAINTER* painter = static_cast<KIGFX::PCB_PAINTER*>( aView->GetPainter() );
  105. KIGFX::PCB_RENDER_SETTINGS* renderSettings = painter->GetSettings();
  106. if( aLayer == LAYER_LOCKED_ITEM_SHADOW )
  107. {
  108. // Hide shadow if the main layer is not shown
  109. if( !aView->IsLayerVisible( m_layer ) )
  110. return HIDE;
  111. // Hide shadow on dimmed tracks
  112. if( renderSettings->GetHighContrast() )
  113. {
  114. if( m_layer != renderSettings->GetPrimaryHighContrastLayer() )
  115. return HIDE;
  116. }
  117. }
  118. return 0.0;
  119. }
  120. wxString PCB_TEXTBOX::GetShownText( int aDepth ) const
  121. {
  122. BOARD* board = dynamic_cast<BOARD*>( GetParent() );
  123. std::function<bool( wxString* )> pcbTextResolver =
  124. [&]( wxString* token ) -> bool
  125. {
  126. if( token->IsSameAs( wxT( "LAYER" ) ) )
  127. {
  128. *token = GetLayerName();
  129. return true;
  130. }
  131. if( token->Contains( ':' ) )
  132. {
  133. wxString remainder;
  134. wxString ref = token->BeforeFirst( ':', &remainder );
  135. BOARD_ITEM* refItem = board->GetItem( KIID( ref ) );
  136. if( refItem && refItem->Type() == PCB_FOOTPRINT_T )
  137. {
  138. FOOTPRINT* refFP = static_cast<FOOTPRINT*>( refItem );
  139. if( refFP->ResolveTextVar( &remainder, aDepth + 1 ) )
  140. {
  141. *token = remainder;
  142. return true;
  143. }
  144. }
  145. }
  146. return false;
  147. };
  148. std::function<bool( wxString* )> boardTextResolver =
  149. [&]( wxString* token ) -> bool
  150. {
  151. return board->ResolveTextVar( token, aDepth + 1 );
  152. };
  153. wxString text = EDA_TEXT::GetShownText();
  154. if( board && HasTextVars() && aDepth < 10 )
  155. text = ExpandTextVars( text, &pcbTextResolver, &boardTextResolver, board->GetProject() );
  156. KIFONT::FONT* font = GetDrawFont();
  157. std::vector<VECTOR2I> corners = GetAnchorAndOppositeCorner();
  158. int colWidth = ( corners[1] - corners[0] ).EuclideanNorm();
  159. colWidth -= GetTextMargin() * 2;
  160. font->LinebreakText( text, colWidth, GetTextSize(), GetTextThickness(), IsBold(), IsItalic() );
  161. return text;
  162. }
  163. void PCB_TEXTBOX::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
  164. {
  165. EDA_UNITS units = aFrame->GetUserUnits();
  166. // Don't use GetShownText() here; we want to show the user the variable references
  167. aList.emplace_back( _( "Text Box" ), UnescapeString( GetText() ) );
  168. if( aFrame->GetName() == PCB_EDIT_FRAME_NAME && IsLocked() )
  169. aList.emplace_back( _( "Status" ), _( "Locked" ) );
  170. aList.emplace_back( _( "Layer" ), GetLayerName() );
  171. aList.emplace_back( _( "Mirror" ), IsMirrored() ? _( "Yes" ) : _( "No" ) );
  172. aList.emplace_back( _( "Angle" ), wxString::Format( "%g", GetTextAngle().AsDegrees() ) );
  173. aList.emplace_back( _( "Thickness" ), MessageTextFromValue( units, GetTextThickness() ) );
  174. aList.emplace_back( _( "Width" ), MessageTextFromValue( units, GetTextWidth() ) );
  175. aList.emplace_back( _( "Height" ), MessageTextFromValue( units, GetTextHeight() ) );
  176. }
  177. void PCB_TEXTBOX::Rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle )
  178. {
  179. PCB_SHAPE::Rotate( aRotCentre, aAngle );
  180. SetTextAngle( GetTextAngle() + aAngle );
  181. }
  182. void PCB_TEXTBOX::Flip( const VECTOR2I& aCentre, bool aFlipLeftRight )
  183. {
  184. if( aFlipLeftRight )
  185. {
  186. m_start.x = aCentre.x - ( m_start.x - aCentre.x );
  187. m_end.x = aCentre.x - ( m_end.x - aCentre.x );
  188. SetTextAngle( -GetTextAngle() );
  189. }
  190. else
  191. {
  192. m_start.y = aCentre.y - ( m_start.y - aCentre.y );
  193. m_end.y = aCentre.y - ( m_end.y - aCentre.y );
  194. SetTextAngle( ANGLE_180 - GetTextAngle() );
  195. }
  196. SetLayer( FlipLayer( GetLayer(), GetBoard()->GetCopperLayerCount() ) );
  197. SetMirrored( !IsMirrored() );
  198. }
  199. bool PCB_TEXTBOX::HitTest( const VECTOR2I& aPosition, int aAccuracy ) const
  200. {
  201. EDA_RECT rect = GetBoundingBox();
  202. rect.Inflate( aAccuracy );
  203. return rect.Contains( aPosition );
  204. }
  205. bool PCB_TEXTBOX::HitTest( const EDA_RECT& aRect, bool aContained, int aAccuracy ) const
  206. {
  207. EDA_RECT rect = aRect;
  208. rect.Inflate( aAccuracy );
  209. if( aContained )
  210. return rect.Contains( GetBoundingBox() );
  211. return rect.Intersects( GetBoundingBox() );
  212. }
  213. wxString PCB_TEXTBOX::GetSelectMenuText( EDA_UNITS aUnits ) const
  214. {
  215. return wxString::Format( _( "PCB Text Box on %s"), GetLayerName() );
  216. }
  217. BITMAPS PCB_TEXTBOX::GetMenuImage() const
  218. {
  219. return BITMAPS::add_textbox;
  220. }
  221. EDA_ITEM* PCB_TEXTBOX::Clone() const
  222. {
  223. return new PCB_TEXTBOX( *this );
  224. }
  225. void PCB_TEXTBOX::SwapData( BOARD_ITEM* aImage )
  226. {
  227. assert( aImage->Type() == PCB_TEXTBOX_T );
  228. std::swap( *((PCB_TEXTBOX*) this), *((PCB_TEXTBOX*) aImage) );
  229. }
  230. std::shared_ptr<SHAPE> PCB_TEXTBOX::GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHING aFlash ) const
  231. {
  232. if( PCB_SHAPE::GetStroke().GetWidth() >= 0 )
  233. return PCB_SHAPE::GetEffectiveShape( aLayer, aFlash );
  234. else
  235. return GetEffectiveTextShape();
  236. }
  237. void PCB_TEXTBOX::TransformTextShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
  238. PCB_LAYER_ID aLayer, int aClearance,
  239. int aError, ERROR_LOC aErrorLoc ) const
  240. {
  241. KIGFX::GAL_DISPLAY_OPTIONS empty_opts;
  242. KIFONT::FONT* font = GetDrawFont();
  243. int penWidth = GetEffectiveTextPenWidth();
  244. CALLBACK_GAL callback_gal( empty_opts,
  245. // Stroke callback
  246. [&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2 )
  247. {
  248. TransformOvalToPolygon( aCornerBuffer, aPt1, aPt2, penWidth+ ( 2 * aClearance ),
  249. aError, ERROR_INSIDE );
  250. },
  251. // Triangulation callback
  252. [&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2, const VECTOR2I& aPt3 )
  253. {
  254. aCornerBuffer.NewOutline();
  255. for( const VECTOR2I& point : { aPt1, aPt2, aPt3 } )
  256. aCornerBuffer.Append( point.x, point.y );
  257. } );
  258. font->Draw( &callback_gal, GetShownText(), GetTextPos(), GetAttributes() );
  259. }
  260. void PCB_TEXTBOX::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
  261. PCB_LAYER_ID aLayer, int aClearance,
  262. int aError, ERROR_LOC aErrorLoc,
  263. bool aIgnoreLineWidth ) const
  264. {
  265. if( PCB_SHAPE::GetStroke().GetWidth() >= 0 )
  266. {
  267. PCB_SHAPE::TransformShapeWithClearanceToPolygon( aCornerBuffer, aLayer, aClearance,
  268. aError, aErrorLoc );
  269. }
  270. else
  271. {
  272. EDA_TEXT::TransformBoundingBoxWithClearanceToPolygon( &aCornerBuffer, aClearance );
  273. }
  274. }
  275. static struct PCB_TEXTBOX_DESC
  276. {
  277. PCB_TEXTBOX_DESC()
  278. {
  279. PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
  280. REGISTER_TYPE( PCB_TEXTBOX );
  281. propMgr.AddTypeCast( new TYPE_CAST<PCB_TEXTBOX, PCB_SHAPE> );
  282. propMgr.AddTypeCast( new TYPE_CAST<PCB_TEXTBOX, EDA_TEXT> );
  283. propMgr.InheritsAfter( TYPE_HASH( PCB_TEXTBOX ), TYPE_HASH( PCB_SHAPE ) );
  284. propMgr.InheritsAfter( TYPE_HASH( PCB_TEXTBOX ), TYPE_HASH( EDA_TEXT ) );
  285. }
  286. } _PCB_TEXTBOX_DESC;