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.

1032 lines
30 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2016 Jean-Pierre Charras, jp.charras at wanadoo.fr
  5. * Copyright (C) 2004-2023 KiCad Developers, see change_log.txt for contributors.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. /**
  25. * @file eda_text.cpp
  26. * @brief Implementation of base KiCad text object.
  27. */
  28. #include <algorithm> // for max
  29. #include <stddef.h> // for NULL
  30. #include <type_traits> // for swap
  31. #include <vector>
  32. #include <eda_item.h>
  33. #include <base_units.h>
  34. #include <callback_gal.h>
  35. #include <eda_text.h> // for EDA_TEXT, TEXT_EFFECTS, GR_TEXT_VJUSTIF...
  36. #include <gal/color4d.h> // for COLOR4D, COLOR4D::BLACK
  37. #include <font/glyph.h>
  38. #include <gr_text.h>
  39. #include <string_utils.h> // for UnescapeString
  40. #include <math/util.h> // for KiROUND
  41. #include <math/vector2d.h>
  42. #include <richio.h>
  43. #include <render_settings.h>
  44. #include <trigo.h> // for RotatePoint
  45. #include <i18n_utility.h>
  46. #include <geometry/shape_segment.h>
  47. #include <geometry/shape_compound.h>
  48. #include <geometry/shape_simple.h>
  49. #include <font/outline_font.h>
  50. #include <geometry/shape_poly_set.h>
  51. #include <properties/property_validators.h>
  52. #include <pcbnew.h> // Text limits are in here for some reason
  53. #include <wx/debug.h> // for wxASSERT
  54. #include <wx/string.h>
  55. #include <wx/url.h> // for wxURL
  56. class OUTPUTFORMATTER;
  57. class wxFindReplaceData;
  58. GR_TEXT_H_ALIGN_T EDA_TEXT::MapHorizJustify( int aHorizJustify )
  59. {
  60. wxASSERT( aHorizJustify >= GR_TEXT_H_ALIGN_LEFT && aHorizJustify <= GR_TEXT_H_ALIGN_RIGHT );
  61. if( aHorizJustify > GR_TEXT_H_ALIGN_RIGHT )
  62. return GR_TEXT_H_ALIGN_RIGHT;
  63. if( aHorizJustify < GR_TEXT_H_ALIGN_LEFT )
  64. return GR_TEXT_H_ALIGN_LEFT;
  65. return static_cast<GR_TEXT_H_ALIGN_T>( aHorizJustify );
  66. }
  67. GR_TEXT_V_ALIGN_T EDA_TEXT::MapVertJustify( int aVertJustify )
  68. {
  69. wxASSERT( aVertJustify >= GR_TEXT_V_ALIGN_TOP && aVertJustify <= GR_TEXT_V_ALIGN_BOTTOM );
  70. if( aVertJustify > GR_TEXT_V_ALIGN_BOTTOM )
  71. return GR_TEXT_V_ALIGN_BOTTOM;
  72. if( aVertJustify < GR_TEXT_V_ALIGN_TOP )
  73. return GR_TEXT_V_ALIGN_TOP;
  74. return static_cast<GR_TEXT_V_ALIGN_T>( aVertJustify );
  75. }
  76. EDA_TEXT::EDA_TEXT( const EDA_IU_SCALE& aIuScale, const wxString& aText ) :
  77. m_text( aText ),
  78. m_IuScale( aIuScale ),
  79. m_bounding_box_cache_valid( false ),
  80. m_bounding_box_cache_line( -1 ),
  81. m_bounding_box_cache_inverted( false )
  82. {
  83. SetTextSize( VECTOR2I( EDA_UNIT_UTILS::Mils2IU( m_IuScale, DEFAULT_SIZE_TEXT ),
  84. EDA_UNIT_UTILS::Mils2IU( m_IuScale, DEFAULT_SIZE_TEXT ) ) );
  85. cacheShownText();
  86. }
  87. EDA_TEXT::EDA_TEXT( const EDA_TEXT& aText ) :
  88. m_IuScale( aText.m_IuScale )
  89. {
  90. m_text = aText.m_text;
  91. m_shown_text = aText.m_shown_text;
  92. m_shown_text_has_text_var_refs = aText.m_shown_text_has_text_var_refs;
  93. m_attributes = aText.m_attributes;
  94. m_pos = aText.m_pos;
  95. m_render_cache_text = aText.m_render_cache_text;
  96. m_render_cache_angle = aText.m_render_cache_angle;
  97. m_render_cache.clear();
  98. for( const std::unique_ptr<KIFONT::GLYPH>& glyph : aText.m_render_cache )
  99. {
  100. if( KIFONT::OUTLINE_GLYPH* outline = dynamic_cast<KIFONT::OUTLINE_GLYPH*>( glyph.get() ) )
  101. m_render_cache.emplace_back( std::make_unique<KIFONT::OUTLINE_GLYPH>( *outline ) );
  102. else if( KIFONT::STROKE_GLYPH* stroke = dynamic_cast<KIFONT::STROKE_GLYPH*>( glyph.get() ) )
  103. m_render_cache.emplace_back( std::make_unique<KIFONT::STROKE_GLYPH>( *stroke ) );
  104. }
  105. m_bounding_box_cache_valid = aText.m_bounding_box_cache_valid;
  106. m_bounding_box_cache = aText.m_bounding_box_cache;
  107. m_bounding_box_cache_line = aText.m_bounding_box_cache_line;
  108. m_bounding_box_cache_inverted = aText.m_bounding_box_cache_inverted;
  109. }
  110. EDA_TEXT::~EDA_TEXT()
  111. {
  112. }
  113. EDA_TEXT& EDA_TEXT::operator=( const EDA_TEXT& aText )
  114. {
  115. m_text = aText.m_text;
  116. m_shown_text = aText.m_shown_text;
  117. m_shown_text_has_text_var_refs = aText.m_shown_text_has_text_var_refs;
  118. m_attributes = aText.m_attributes;
  119. m_pos = aText.m_pos;
  120. m_render_cache_text = aText.m_render_cache_text;
  121. m_render_cache_angle = aText.m_render_cache_angle;
  122. m_render_cache.clear();
  123. for( const std::unique_ptr<KIFONT::GLYPH>& glyph : aText.m_render_cache )
  124. {
  125. if( KIFONT::OUTLINE_GLYPH* outline = dynamic_cast<KIFONT::OUTLINE_GLYPH*>( glyph.get() ) )
  126. m_render_cache.emplace_back( std::make_unique<KIFONT::OUTLINE_GLYPH>( *outline ) );
  127. else if( KIFONT::STROKE_GLYPH* stroke = dynamic_cast<KIFONT::STROKE_GLYPH*>( glyph.get() ) )
  128. m_render_cache.emplace_back( std::make_unique<KIFONT::STROKE_GLYPH>( *stroke ) );
  129. }
  130. m_bounding_box_cache_valid = aText.m_bounding_box_cache_valid;
  131. m_bounding_box_cache = aText.m_bounding_box_cache;
  132. return *this;
  133. }
  134. void EDA_TEXT::SetText( const wxString& aText )
  135. {
  136. m_text = aText;
  137. cacheShownText();
  138. ClearRenderCache();
  139. m_bounding_box_cache_valid = false;
  140. }
  141. void EDA_TEXT::CopyText( const EDA_TEXT& aSrc )
  142. {
  143. m_text = aSrc.m_text;
  144. m_shown_text = aSrc.m_shown_text;
  145. m_shown_text_has_text_var_refs = aSrc.m_shown_text_has_text_var_refs;
  146. ClearRenderCache();
  147. m_bounding_box_cache_valid = false;
  148. }
  149. void EDA_TEXT::SetTextThickness( int aWidth )
  150. {
  151. m_attributes.m_StrokeWidth = aWidth;
  152. ClearRenderCache();
  153. m_bounding_box_cache_valid = false;
  154. }
  155. void EDA_TEXT::SetTextAngle( const EDA_ANGLE& aAngle )
  156. {
  157. m_attributes.m_Angle = aAngle;
  158. ClearRenderCache();
  159. m_bounding_box_cache_valid = false;
  160. }
  161. void EDA_TEXT::SetItalic( bool aItalic )
  162. {
  163. m_attributes.m_Italic = aItalic;
  164. ClearRenderCache();
  165. m_bounding_box_cache_valid = false;
  166. }
  167. void EDA_TEXT::SetBold( bool aBold )
  168. {
  169. m_attributes.m_Bold = aBold;
  170. ClearRenderCache();
  171. m_bounding_box_cache_valid = false;
  172. }
  173. void EDA_TEXT::SetVisible( bool aVisible )
  174. {
  175. m_attributes.m_Visible = aVisible;
  176. ClearRenderCache();
  177. }
  178. void EDA_TEXT::SetMirrored( bool isMirrored )
  179. {
  180. m_attributes.m_Mirrored = isMirrored;
  181. ClearRenderCache();
  182. m_bounding_box_cache_valid = false;
  183. }
  184. void EDA_TEXT::SetMultilineAllowed( bool aAllow )
  185. {
  186. m_attributes.m_Multiline = aAllow;
  187. ClearRenderCache();
  188. m_bounding_box_cache_valid = false;
  189. }
  190. void EDA_TEXT::SetHorizJustify( GR_TEXT_H_ALIGN_T aType )
  191. {
  192. m_attributes.m_Halign = aType;
  193. ClearRenderCache();
  194. m_bounding_box_cache_valid = false;
  195. }
  196. void EDA_TEXT::SetVertJustify( GR_TEXT_V_ALIGN_T aType )
  197. {
  198. m_attributes.m_Valign = aType;
  199. ClearRenderCache();
  200. m_bounding_box_cache_valid = false;
  201. }
  202. void EDA_TEXT::SetKeepUpright( bool aKeepUpright )
  203. {
  204. m_attributes.m_KeepUpright = aKeepUpright;
  205. ClearRenderCache();
  206. m_bounding_box_cache_valid = false;
  207. }
  208. void EDA_TEXT::SetAttributes( const EDA_TEXT& aSrc )
  209. {
  210. m_attributes = aSrc.m_attributes;
  211. m_pos = aSrc.m_pos;
  212. ClearRenderCache();
  213. m_bounding_box_cache_valid = false;
  214. }
  215. void EDA_TEXT::SwapText( EDA_TEXT& aTradingPartner )
  216. {
  217. std::swap( m_text, aTradingPartner.m_text );
  218. std::swap( m_shown_text, aTradingPartner.m_shown_text );
  219. std::swap( m_shown_text_has_text_var_refs, aTradingPartner.m_shown_text_has_text_var_refs );
  220. ClearRenderCache();
  221. m_bounding_box_cache_valid = false;
  222. }
  223. void EDA_TEXT::SwapAttributes( EDA_TEXT& aTradingPartner )
  224. {
  225. std::swap( m_attributes, aTradingPartner.m_attributes );
  226. std::swap( m_pos, aTradingPartner.m_pos );
  227. ClearRenderCache();
  228. aTradingPartner.ClearRenderCache();
  229. m_bounding_box_cache_valid = false;
  230. aTradingPartner.m_bounding_box_cache_valid = false;
  231. }
  232. int EDA_TEXT::GetEffectiveTextPenWidth( int aDefaultPenWidth ) const
  233. {
  234. int penWidth = GetTextThickness();
  235. if( penWidth <= 1 )
  236. {
  237. penWidth = aDefaultPenWidth;
  238. if( IsBold() )
  239. penWidth = GetPenSizeForBold( GetTextWidth() );
  240. else if( penWidth <= 1 )
  241. penWidth = GetPenSizeForNormal( GetTextWidth() );
  242. }
  243. // Clip pen size for small texts:
  244. penWidth = Clamp_Text_PenSize( penWidth, GetTextSize() );
  245. return penWidth;
  246. }
  247. bool EDA_TEXT::Replace( const EDA_SEARCH_DATA& aSearchData )
  248. {
  249. bool retval = EDA_ITEM::Replace( aSearchData, m_text );
  250. cacheShownText();
  251. ClearRenderCache();
  252. m_bounding_box_cache_valid = false;
  253. return retval;
  254. }
  255. void EDA_TEXT::SetFont( KIFONT::FONT* aFont )
  256. {
  257. m_attributes.m_Font = aFont;
  258. ClearRenderCache();
  259. m_bounding_box_cache_valid = false;
  260. }
  261. void EDA_TEXT::SetLineSpacing( double aLineSpacing )
  262. {
  263. m_attributes.m_LineSpacing = aLineSpacing;
  264. ClearRenderCache();
  265. m_bounding_box_cache_valid = false;
  266. }
  267. void EDA_TEXT::SetTextSize( const VECTOR2I& aNewSize )
  268. {
  269. m_attributes.m_Size = aNewSize;
  270. ClearRenderCache();
  271. m_bounding_box_cache_valid = false;
  272. }
  273. void EDA_TEXT::SetTextWidth( int aWidth )
  274. {
  275. m_attributes.m_Size.x = aWidth;
  276. ClearRenderCache();
  277. m_bounding_box_cache_valid = false;
  278. }
  279. void EDA_TEXT::SetTextHeight( int aHeight )
  280. {
  281. m_attributes.m_Size.y = aHeight;
  282. ClearRenderCache();
  283. m_bounding_box_cache_valid = false;
  284. }
  285. void EDA_TEXT::SetTextPos( const VECTOR2I& aPoint )
  286. {
  287. Offset( VECTOR2I( aPoint.x - m_pos.x, aPoint.y - m_pos.y ) );
  288. }
  289. void EDA_TEXT::SetTextX( int aX )
  290. {
  291. Offset( VECTOR2I( aX - m_pos.x, 0 ) );
  292. }
  293. void EDA_TEXT::SetTextY( int aY )
  294. {
  295. Offset( VECTOR2I( 0, aY - m_pos.y ) );
  296. }
  297. void EDA_TEXT::Offset( const VECTOR2I& aOffset )
  298. {
  299. m_pos += aOffset;
  300. for( std::unique_ptr<KIFONT::GLYPH>& glyph : m_render_cache )
  301. {
  302. if( KIFONT::OUTLINE_GLYPH* outline = dynamic_cast<KIFONT::OUTLINE_GLYPH*>( glyph.get() ) )
  303. outline->Move( aOffset );
  304. else if( KIFONT::STROKE_GLYPH* stroke = dynamic_cast<KIFONT::STROKE_GLYPH*>( glyph.get() ) )
  305. glyph = stroke->Transform( { 1.0, 1.0 }, aOffset, 0, ANGLE_0, false, { 0, 0 } );
  306. }
  307. m_bounding_box_cache_valid = false;
  308. }
  309. void EDA_TEXT::Empty()
  310. {
  311. m_text.Empty();
  312. ClearRenderCache();
  313. m_bounding_box_cache_valid = false;
  314. }
  315. void EDA_TEXT::cacheShownText()
  316. {
  317. if( m_text.IsEmpty() )
  318. {
  319. m_shown_text = wxEmptyString;
  320. m_shown_text_has_text_var_refs = false;
  321. }
  322. else
  323. {
  324. m_shown_text = UnescapeString( m_text );
  325. m_shown_text_has_text_var_refs = m_shown_text.Contains( wxT( "${" ) );
  326. }
  327. ClearRenderCache();
  328. m_bounding_box_cache_valid = false;
  329. }
  330. KIFONT::FONT* EDA_TEXT::getDrawFont() const
  331. {
  332. KIFONT::FONT* font = GetFont();
  333. if( !font )
  334. font = KIFONT::FONT::GetFont( wxEmptyString, IsBold(), IsItalic() );
  335. return font;
  336. }
  337. void EDA_TEXT::ClearRenderCache()
  338. {
  339. m_render_cache.clear();
  340. }
  341. void EDA_TEXT::ClearBoundingBoxCache()
  342. {
  343. m_bounding_box_cache_valid = false;
  344. }
  345. std::vector<std::unique_ptr<KIFONT::GLYPH>>*
  346. EDA_TEXT::GetRenderCache( const KIFONT::FONT* aFont, const wxString& forResolvedText,
  347. const VECTOR2I& aOffset ) const
  348. {
  349. if( getDrawFont()->IsOutline() )
  350. {
  351. EDA_ANGLE resolvedAngle = GetDrawRotation();
  352. if( m_render_cache.empty()
  353. || m_render_cache_text != forResolvedText
  354. || m_render_cache_angle != resolvedAngle
  355. || m_render_cache_offset != aOffset )
  356. {
  357. m_render_cache.clear();
  358. KIFONT::OUTLINE_FONT* font = static_cast<KIFONT::OUTLINE_FONT*>( getDrawFont() );
  359. TEXT_ATTRIBUTES attrs = GetAttributes();
  360. attrs.m_Angle = resolvedAngle;
  361. font->GetLinesAsGlyphs( &m_render_cache, forResolvedText, GetDrawPos() + aOffset,
  362. attrs );
  363. m_render_cache_angle = resolvedAngle;
  364. m_render_cache_text = forResolvedText;
  365. m_render_cache_offset = aOffset;
  366. }
  367. return &m_render_cache;
  368. }
  369. return nullptr;
  370. }
  371. void EDA_TEXT::SetupRenderCache( const wxString& aResolvedText, const EDA_ANGLE& aAngle )
  372. {
  373. m_render_cache_text = aResolvedText;
  374. m_render_cache_angle = aAngle;
  375. m_render_cache.clear();
  376. }
  377. void EDA_TEXT::AddRenderCacheGlyph( const SHAPE_POLY_SET& aPoly )
  378. {
  379. m_render_cache.emplace_back( std::make_unique<KIFONT::OUTLINE_GLYPH>( aPoly ) );
  380. }
  381. int EDA_TEXT::GetInterline() const
  382. {
  383. return KiROUND( getDrawFont()->GetInterline( GetTextHeight() ) );
  384. }
  385. BOX2I EDA_TEXT::GetTextBox( int aLine, bool aInvertY ) const
  386. {
  387. VECTOR2I drawPos = GetDrawPos();
  388. if( m_bounding_box_cache_valid
  389. && m_bounding_box_cache_pos == drawPos
  390. && m_bounding_box_cache_line == aLine
  391. && m_bounding_box_cache_inverted == aInvertY )
  392. {
  393. return m_bounding_box_cache;
  394. }
  395. // We've tried a gazillion different ways of calculating bounding boxes for text; all of them
  396. // fail in one case or another and we end up with compensation hacks strewed throughout the
  397. // code. So I'm pulling the plug on it; we're just going to draw the damn text and see how
  398. // big it is.
  399. BOX2I bbox;
  400. BOX2I strokeBBox;
  401. KIGFX::GAL_DISPLAY_OPTIONS empty_opts;
  402. KIFONT::FONT* font = getDrawFont();
  403. CALLBACK_GAL callback_gal(
  404. empty_opts,
  405. // Stroke callback
  406. [&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2 )
  407. {
  408. strokeBBox.Merge( aPt1 );
  409. strokeBBox.Merge( aPt2 );
  410. },
  411. // Outline callback
  412. [&]( const SHAPE_LINE_CHAIN& aPoly )
  413. {
  414. bbox.Merge( aPoly.BBox() );
  415. } );
  416. font->Draw( &callback_gal, GetShownText( true ), drawPos, GetAttributes() );
  417. if( strokeBBox.GetSizeMax() > 0 )
  418. {
  419. strokeBBox.Inflate( GetTextThickness() / 2 );
  420. bbox.Merge( strokeBBox );
  421. }
  422. bbox.Normalize(); // Make h and v sizes always >= 0
  423. m_bounding_box_cache_valid = true;
  424. m_bounding_box_cache_pos = drawPos;
  425. m_bounding_box_cache_line = aLine;
  426. m_bounding_box_cache_inverted = aInvertY;
  427. m_bounding_box_cache = bbox;
  428. return bbox;
  429. }
  430. bool EDA_TEXT::TextHitTest( const VECTOR2I& aPoint, int aAccuracy ) const
  431. {
  432. BOX2I rect = GetTextBox();
  433. VECTOR2I location = aPoint;
  434. rect.Inflate( aAccuracy );
  435. RotatePoint( location, GetDrawPos(), -GetDrawRotation() );
  436. return rect.Contains( location );
  437. }
  438. bool EDA_TEXT::TextHitTest( const BOX2I& aRect, bool aContains, int aAccuracy ) const
  439. {
  440. BOX2I rect = aRect;
  441. rect.Inflate( aAccuracy );
  442. if( aContains )
  443. return rect.Contains( GetTextBox() );
  444. return rect.Intersects( GetTextBox(), GetDrawRotation() );
  445. }
  446. void EDA_TEXT::Print( const RENDER_SETTINGS* aSettings, const VECTOR2I& aOffset,
  447. const COLOR4D& aColor, OUTLINE_MODE aFillMode )
  448. {
  449. if( IsMultilineAllowed() )
  450. {
  451. std::vector<VECTOR2I> positions;
  452. wxArrayString strings;
  453. wxStringSplit( GetShownText( true ), strings, '\n' );
  454. positions.reserve( strings.Count() );
  455. GetLinePositions( positions, (int) strings.Count() );
  456. for( unsigned ii = 0; ii < strings.Count(); ii++ )
  457. printOneLineOfText( aSettings, aOffset, aColor, aFillMode, strings[ii], positions[ii] );
  458. }
  459. else
  460. {
  461. printOneLineOfText( aSettings, aOffset, aColor, aFillMode, GetShownText( true ),
  462. GetDrawPos() );
  463. }
  464. }
  465. void EDA_TEXT::GetLinePositions( std::vector<VECTOR2I>& aPositions, int aLineCount ) const
  466. {
  467. VECTOR2I pos = GetDrawPos(); // Position of first line of the multiline text according
  468. // to the center of the multiline text block
  469. VECTOR2I offset; // Offset to next line.
  470. offset.y = GetInterline();
  471. if( aLineCount > 1 )
  472. {
  473. switch( GetVertJustify() )
  474. {
  475. case GR_TEXT_V_ALIGN_TOP:
  476. break;
  477. case GR_TEXT_V_ALIGN_CENTER:
  478. pos.y -= ( aLineCount - 1 ) * offset.y / 2;
  479. break;
  480. case GR_TEXT_V_ALIGN_BOTTOM:
  481. pos.y -= ( aLineCount - 1 ) * offset.y;
  482. break;
  483. }
  484. }
  485. // Rotate the position of the first line around the center of the multiline text block
  486. RotatePoint( pos, GetDrawPos(), GetDrawRotation() );
  487. // Rotate the offset lines to increase happened in the right direction
  488. RotatePoint( offset, GetDrawRotation() );
  489. for( int ii = 0; ii < aLineCount; ii++ )
  490. {
  491. aPositions.push_back( (VECTOR2I) pos );
  492. pos += offset;
  493. }
  494. }
  495. void EDA_TEXT::printOneLineOfText( const RENDER_SETTINGS* aSettings, const VECTOR2I& aOffset,
  496. const COLOR4D& aColor, OUTLINE_MODE aFillMode,
  497. const wxString& aText, const VECTOR2I& aPos )
  498. {
  499. wxDC* DC = aSettings->GetPrintDC();
  500. int penWidth = GetEffectiveTextPenWidth( aSettings->GetDefaultPenWidth() );
  501. if( aFillMode == SKETCH )
  502. penWidth = -penWidth;
  503. VECTOR2I size = GetTextSize();
  504. if( IsMirrored() )
  505. size.x = -size.x;
  506. KIFONT::FONT* font = GetFont();
  507. if( !font )
  508. font = KIFONT::FONT::GetFont( aSettings->GetDefaultFont(), IsBold(), IsItalic() );
  509. GRPrintText( DC, aOffset + aPos, aColor, aText, GetDrawRotation(), size, GetHorizJustify(),
  510. GetVertJustify(), penWidth, IsItalic(), IsBold(), font );
  511. }
  512. wxString EDA_TEXT::GetTextStyleName() const
  513. {
  514. int style = 0;
  515. if( IsItalic() )
  516. style = 1;
  517. if( IsBold() )
  518. style += 2;
  519. wxString stylemsg[4] = {
  520. _("Normal"),
  521. _("Italic"),
  522. _("Bold"),
  523. _("Bold+Italic")
  524. };
  525. return stylemsg[style];
  526. }
  527. wxString EDA_TEXT::GetFontName() const
  528. {
  529. if( GetFont() )
  530. return GetFont()->GetName();
  531. else
  532. return wxEmptyString;
  533. }
  534. bool EDA_TEXT::IsDefaultFormatting() const
  535. {
  536. return ( IsVisible()
  537. && !IsMirrored()
  538. && GetHorizJustify() == GR_TEXT_H_ALIGN_CENTER
  539. && GetVertJustify() == GR_TEXT_V_ALIGN_CENTER
  540. && GetTextThickness() == 0
  541. && !IsItalic()
  542. && !IsBold()
  543. && !IsMultilineAllowed()
  544. && GetFontName().IsEmpty()
  545. );
  546. }
  547. void EDA_TEXT::Format( OUTPUTFORMATTER* aFormatter, int aNestLevel, int aControlBits ) const
  548. {
  549. aFormatter->Print( aNestLevel + 1, "(effects" );
  550. aFormatter->Print( 0, " (font" );
  551. if( GetFont() && !GetFont()->GetName().IsEmpty() )
  552. aFormatter->Print( 0, " (face \"%s\")", GetFont()->NameAsToken() );
  553. // Text size
  554. aFormatter->Print( 0, " (size %s %s)",
  555. EDA_UNIT_UTILS::FormatInternalUnits( m_IuScale, GetTextHeight() ).c_str(),
  556. EDA_UNIT_UTILS::FormatInternalUnits( m_IuScale, GetTextWidth() ).c_str() );
  557. if( GetLineSpacing() != 1.0 )
  558. {
  559. aFormatter->Print( 0, " (line_spacing %s)",
  560. FormatDouble2Str( GetLineSpacing() ).c_str() );
  561. }
  562. if( GetTextThickness() )
  563. {
  564. aFormatter->Print( 0, " (thickness %s)",
  565. EDA_UNIT_UTILS::FormatInternalUnits( m_IuScale, GetTextThickness() ).c_str() );
  566. }
  567. if( IsBold() )
  568. aFormatter->Print( 0, " bold" );
  569. if( IsItalic() )
  570. aFormatter->Print( 0, " italic" );
  571. if( GetTextColor() != COLOR4D::UNSPECIFIED )
  572. {
  573. aFormatter->Print( 0, " (color %d %d %d %s)",
  574. KiROUND( GetTextColor().r * 255.0 ),
  575. KiROUND( GetTextColor().g * 255.0 ),
  576. KiROUND( GetTextColor().b * 255.0 ),
  577. FormatDouble2Str( GetTextColor().a ).c_str() );
  578. }
  579. aFormatter->Print( 0, ")"); // (font
  580. if( IsMirrored() || GetHorizJustify() != GR_TEXT_H_ALIGN_CENTER
  581. || GetVertJustify() != GR_TEXT_V_ALIGN_CENTER )
  582. {
  583. aFormatter->Print( 0, " (justify");
  584. if( GetHorizJustify() != GR_TEXT_H_ALIGN_CENTER )
  585. aFormatter->Print( 0, GetHorizJustify() == GR_TEXT_H_ALIGN_LEFT ? " left" : " right" );
  586. if( GetVertJustify() != GR_TEXT_V_ALIGN_CENTER )
  587. aFormatter->Print( 0, GetVertJustify() == GR_TEXT_V_ALIGN_TOP ? " top" : " bottom" );
  588. if( IsMirrored() )
  589. aFormatter->Print( 0, " mirror" );
  590. aFormatter->Print( 0, ")" ); // (justify
  591. }
  592. if( !( aControlBits & CTL_OMIT_HIDE ) && !IsVisible() )
  593. aFormatter->Print( 0, " hide" );
  594. if( HasHyperlink() )
  595. {
  596. aFormatter->Print( 0, " (href %s)", aFormatter->Quotew( GetHyperlink() ).c_str() );
  597. }
  598. aFormatter->Print( 0, ")\n" ); // (effects
  599. }
  600. std::shared_ptr<SHAPE_COMPOUND> EDA_TEXT::GetEffectiveTextShape( bool aTriangulate,
  601. bool aUseTextRotation ) const
  602. {
  603. std::shared_ptr<SHAPE_COMPOUND> shape = std::make_shared<SHAPE_COMPOUND>();
  604. KIGFX::GAL_DISPLAY_OPTIONS empty_opts;
  605. KIFONT::FONT* font = getDrawFont();
  606. int penWidth = GetEffectiveTextPenWidth();
  607. TEXT_ATTRIBUTES attrs = GetAttributes();
  608. if( aUseTextRotation )
  609. attrs.m_Angle = GetDrawRotation();
  610. else
  611. attrs.m_Angle = ANGLE_0;
  612. if( aTriangulate )
  613. {
  614. CALLBACK_GAL callback_gal(
  615. empty_opts,
  616. // Stroke callback
  617. [&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2 )
  618. {
  619. shape->AddShape( new SHAPE_SEGMENT( aPt1, aPt2, penWidth ) );
  620. },
  621. // Triangulation callback
  622. [&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2, const VECTOR2I& aPt3 )
  623. {
  624. SHAPE_SIMPLE* triShape = new SHAPE_SIMPLE;
  625. for( const VECTOR2I& point : { aPt1, aPt2, aPt3 } )
  626. triShape->Append( point.x, point.y );
  627. shape->AddShape( triShape );
  628. } );
  629. font->Draw( &callback_gal, GetShownText( true ), GetDrawPos(), attrs );
  630. }
  631. else
  632. {
  633. CALLBACK_GAL callback_gal(
  634. empty_opts,
  635. // Stroke callback
  636. [&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2 )
  637. {
  638. shape->AddShape( new SHAPE_SEGMENT( aPt1, aPt2, penWidth ) );
  639. },
  640. // Outline callback
  641. [&]( const SHAPE_LINE_CHAIN& aPoly )
  642. {
  643. shape->AddShape( aPoly.Clone() );
  644. } );
  645. font->Draw( &callback_gal, GetShownText( true ), GetDrawPos(), attrs );
  646. }
  647. return shape;
  648. }
  649. int EDA_TEXT::Compare( const EDA_TEXT* aOther ) const
  650. {
  651. wxCHECK( aOther, 1 );
  652. int val = m_attributes.Compare( aOther->m_attributes );
  653. if( val != 0 )
  654. return val;
  655. if( m_pos.x != aOther->m_pos.x )
  656. return m_pos.x - aOther->m_pos.x;
  657. if( m_pos.y != aOther->m_pos.y )
  658. return m_pos.y - aOther->m_pos.y;
  659. val = GetFontName().Cmp( aOther->GetFontName() );
  660. if( val != 0 )
  661. return val;
  662. return m_text.Cmp( aOther->m_text );
  663. }
  664. void EDA_TEXT::TransformBoundingBoxToPolygon( SHAPE_POLY_SET* aBuffer, int aClearance ) const
  665. {
  666. if( GetText().Length() == 0 )
  667. return;
  668. VECTOR2I corners[4]; // Buffer of polygon corners
  669. BOX2I rect = GetTextBox();
  670. rect.Inflate( aClearance );
  671. corners[0].x = rect.GetOrigin().x;
  672. corners[0].y = rect.GetOrigin().y;
  673. corners[1].y = corners[0].y;
  674. corners[1].x = rect.GetRight();
  675. corners[2].x = corners[1].x;
  676. corners[2].y = rect.GetBottom();
  677. corners[3].y = corners[2].y;
  678. corners[3].x = corners[0].x;
  679. aBuffer->NewOutline();
  680. for( VECTOR2I& corner : corners )
  681. {
  682. RotatePoint( corner, GetDrawPos(), GetDrawRotation() );
  683. aBuffer->Append( corner.x, corner.y );
  684. }
  685. }
  686. bool EDA_TEXT::ValidateHyperlink( const wxString& aURL )
  687. {
  688. if( aURL.IsEmpty() || IsGotoPageHref( aURL ) )
  689. return true;
  690. // Limit valid urls to file, http and https for now. Note wxURL doesn't support https
  691. wxURI uri;
  692. if( uri.Create( aURL ) && uri.HasScheme() )
  693. {
  694. const wxString& scheme = uri.GetScheme();
  695. return scheme == wxT( "file" ) || scheme == wxT( "http" ) || scheme == wxT( "https" );
  696. }
  697. return false;
  698. }
  699. bool EDA_TEXT::IsGotoPageHref( const wxString& aHref, wxString* aDestination )
  700. {
  701. return aHref.StartsWith( wxT( "#" ), aDestination );
  702. }
  703. wxString EDA_TEXT::GotoPageHref( const wxString& aDestination )
  704. {
  705. return wxT( "#" ) + aDestination;
  706. }
  707. std::ostream& operator<<( std::ostream& aStream, const EDA_TEXT& aText )
  708. {
  709. aStream << aText.GetText();
  710. return aStream;
  711. }
  712. static struct EDA_TEXT_DESC
  713. {
  714. EDA_TEXT_DESC()
  715. {
  716. ENUM_MAP<GR_TEXT_H_ALIGN_T>::Instance()
  717. .Map( GR_TEXT_H_ALIGN_LEFT, _HKI( "Left" ) )
  718. .Map( GR_TEXT_H_ALIGN_CENTER, _HKI( "Center" ) )
  719. .Map( GR_TEXT_H_ALIGN_RIGHT, _HKI( "Right" ) );
  720. ENUM_MAP<GR_TEXT_V_ALIGN_T>::Instance()
  721. .Map( GR_TEXT_V_ALIGN_TOP, _HKI( "Top" ) )
  722. .Map( GR_TEXT_V_ALIGN_CENTER, _HKI( "Center" ) )
  723. .Map( GR_TEXT_V_ALIGN_BOTTOM, _HKI( "Bottom" ) );
  724. PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
  725. REGISTER_TYPE( EDA_TEXT );
  726. propMgr.AddProperty( new PROPERTY<EDA_TEXT, double>( _HKI( "Orientation" ),
  727. &EDA_TEXT::SetTextAngleDegrees, &EDA_TEXT::GetTextAngleDegrees,
  728. PROPERTY_DISPLAY::PT_DEGREE ) );
  729. const wxString textProps = _( "Text Properties" );
  730. propMgr.AddProperty( new PROPERTY<EDA_TEXT, wxString>( _HKI( "Text" ),
  731. &EDA_TEXT::SetText,
  732. &EDA_TEXT::GetText ),
  733. textProps );
  734. propMgr.AddProperty( new PROPERTY<EDA_TEXT, wxString>( _HKI( "Hyperlink" ),
  735. &EDA_TEXT::SetHyperlink,
  736. &EDA_TEXT::GetHyperlink ),
  737. textProps );
  738. propMgr.AddProperty( new PROPERTY<EDA_TEXT, int>( _HKI( "Thickness" ),
  739. &EDA_TEXT::SetTextThickness,
  740. &EDA_TEXT::GetTextThickness,
  741. PROPERTY_DISPLAY::PT_SIZE ),
  742. textProps );
  743. propMgr.AddProperty( new PROPERTY<EDA_TEXT, bool>( _HKI( "Italic" ),
  744. &EDA_TEXT::SetItalic,
  745. &EDA_TEXT::IsItalic ),
  746. textProps );
  747. propMgr.AddProperty( new PROPERTY<EDA_TEXT, bool>( _HKI( "Bold" ),
  748. &EDA_TEXT::SetBold, &EDA_TEXT::IsBold ),
  749. textProps );
  750. propMgr.AddProperty( new PROPERTY<EDA_TEXT, bool>( _HKI( "Mirrored" ),
  751. &EDA_TEXT::SetMirrored,
  752. &EDA_TEXT::IsMirrored ),
  753. textProps );
  754. propMgr.AddProperty( new PROPERTY<EDA_TEXT, bool>( _HKI( "Visible" ),
  755. &EDA_TEXT::SetVisible,
  756. &EDA_TEXT::IsVisible ),
  757. textProps );
  758. propMgr.AddProperty( new PROPERTY<EDA_TEXT, int>( _HKI( "Width" ),
  759. &EDA_TEXT::SetTextWidth,
  760. &EDA_TEXT::GetTextWidth,
  761. PROPERTY_DISPLAY::PT_SIZE ),
  762. textProps )
  763. .SetValidator( PROPERTY_VALIDATORS::RangeIntValidator<TEXTS_MIN_SIZE,
  764. TEXTS_MAX_SIZE> );
  765. propMgr.AddProperty( new PROPERTY<EDA_TEXT, int>( _HKI( "Height" ),
  766. &EDA_TEXT::SetTextHeight,
  767. &EDA_TEXT::GetTextHeight,
  768. PROPERTY_DISPLAY::PT_SIZE ),
  769. textProps )
  770. .SetValidator( PROPERTY_VALIDATORS::RangeIntValidator<TEXTS_MIN_SIZE,
  771. TEXTS_MAX_SIZE> );
  772. propMgr.AddProperty( new PROPERTY_ENUM<EDA_TEXT,
  773. GR_TEXT_H_ALIGN_T>( _HKI( "Horizontal Justification" ),
  774. &EDA_TEXT::SetHorizJustify,
  775. &EDA_TEXT::GetHorizJustify ),
  776. textProps );
  777. propMgr.AddProperty( new PROPERTY_ENUM<EDA_TEXT,
  778. GR_TEXT_V_ALIGN_T>( _HKI( "Vertical Justification" ),
  779. &EDA_TEXT::SetVertJustify,
  780. &EDA_TEXT::GetVertJustify ),
  781. textProps );
  782. }
  783. } _EDA_TEXT_DESC;
  784. ENUM_TO_WXANY( GR_TEXT_H_ALIGN_T )
  785. ENUM_TO_WXANY( GR_TEXT_V_ALIGN_T )