kicad-source/include/eda_text.h
Jeff Young 40b4052ad4 Improve text hittesting in schematics.
Text has a fairly big bounding box to account for descenders, overbars,
etc., but it makes it feel too sloppy for hittesting.  This change
allows selection disambiguation to look at the actual strokes of the
text when deciding what's "closest".

Fixes https://gitlab.com/kicad/code/kicad/issues/9506
2021-11-01 21:13:12 +00:00

428 lines
15 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2013 Jean-Pierre Charras, jp.charras at wanadoo.fr
* Copyright (C) 2004-2021 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#ifndef EDA_TEXT_H_
#define EDA_TEXT_H_
#include <memory>
#include <vector>
#include <outline_mode.h>
#include <eda_rect.h>
class OUTPUTFORMATTER;
class SHAPE_COMPOUND;
class SHAPE_POLY_SET;
class wxFindReplaceData;
/**
* A helper for the text to polygon callback function.
*
* These variables are parameters used in #addTextSegmToPoly but #addTextSegmToPoly is a
* callback function so the cannot be sent as arguments.
*/
struct TSEGM_2_POLY_PRMS
{
int m_textWidth;
int m_error;
SHAPE_POLY_SET* m_cornerBuffer;
};
/**
* Callback function used to convert text segments to polygons.
*/
extern void addTextSegmToPoly( int x0, int y0, int xf, int yf, void* aData );
namespace KIGFX
{
class RENDER_SETTINGS;
class COLOR4D;
}
using KIGFX::RENDER_SETTINGS;
using KIGFX::COLOR4D;
/// Frequent text rotations, used with {Set,Get}TextAngle(),
/// in 0.1 degrees for now, hoping to migrate to degrees eventually.
#define TEXT_ANGLE_HORIZ 0
#define TEXT_ANGLE_VERT 900
// part of the kicad_plugin.h family of defines.
// See kicad_plugin.h for the choice of the value
// When set when calling EDA_TEXT::Format, disable writing the "hide" keyword in save file
#define CTL_OMIT_HIDE (1 << 6)
// Graphic Text justify:
// Values -1,0,1 are used in computations, do not change them
enum EDA_TEXT_HJUSTIFY_T {
GR_TEXT_HJUSTIFY_LEFT = -1,
GR_TEXT_HJUSTIFY_CENTER = 0,
GR_TEXT_HJUSTIFY_RIGHT = 1
};
enum EDA_TEXT_VJUSTIFY_T {
GR_TEXT_VJUSTIFY_TOP = -1,
GR_TEXT_VJUSTIFY_CENTER = 0,
GR_TEXT_VJUSTIFY_BOTTOM = 1
};
/**
* This is the "default-of-the-default" hardcoded text size; individual
* application define their own default policy starting with this
* (usually with a user option or project).
*/
#define DEFAULT_SIZE_TEXT 50 // default text height (in mils, i.e. 1/1000")
#define DIM_ANCRE_TEXTE 2 // Anchor size for text
/**
* A container for text effects.
*
* These fields are bundled so they can be easily copied together as a lot. The privacy
* policy is established by client (incorporating) code.
*/
struct TEXT_EFFECTS
{
TEXT_EFFECTS( int aSetOfBits = 0 ) :
bits( aSetOfBits ),
hjustify( GR_TEXT_HJUSTIFY_CENTER ),
vjustify( GR_TEXT_VJUSTIFY_CENTER ),
penwidth( 0 ),
angle( 0.0 )
{}
int bits; ///< any set of booleans a client uses.
signed char hjustify; ///< horizontal justification
signed char vjustify; ///< vertical justification
wxSize size;
int penwidth;
double angle; ///< now: 0.1 degrees; future: degrees
wxPoint pos;
void Bit( int aBit, bool aValue ) { aValue ? bits |= (1<<aBit) : bits &= ~(1<<aBit); }
bool Bit( int aBit ) const { return bits & (1<<aBit); }
};
/**
* A mix-in class (via multiple inheritance) that handles texts such as labels, parts,
* components, or footprints. Because it's a mix-in class, care is used to provide
* function names (accessors) that to not collide with function names likely to be seen
* in the combined derived classes.
*/
class EDA_TEXT
{
public:
EDA_TEXT( const wxString& text = wxEmptyString );
EDA_TEXT( const EDA_TEXT& aText );
virtual ~EDA_TEXT();
/**
* Return the string associated with the text object.
*
* @return a const wxString reference containing the string of the item.
*/
virtual const wxString& GetText() const { return m_text; }
/**
* Return the string actually shown after processing of the base text.
*
* @param aDepth is used to prevent infinite recursions and loops when expanding
* text variables.
*/
virtual wxString GetShownText( int aDepth = 0 ) const { return m_shown_text; }
/**
* Returns a shortened version (max 15 characters) of the shown text
*/
wxString ShortenedShownText() const;
/**
* Indicates the ShownText has text var references which need to be processed.
*/
bool HasTextVars() const { return m_shown_text_has_text_var_refs; }
virtual void SetText( const wxString& aText );
/**
* The TextThickness is that set by the user. The EffectiveTextPenWidth also factors
* in bold text and thickness clamping.
*/
void SetTextThickness( int aWidth ) { m_e.penwidth = aWidth; };
int GetTextThickness() const { return m_e.penwidth; };
/**
* The EffectiveTextPenWidth uses the text thickness if > 1 or aDefaultWidth.
*/
int GetEffectiveTextPenWidth( int aDefaultWidth = 0 ) const;
virtual void SetTextAngle( double aAngle )
{
// Higher level classes may be more restrictive than this by overloading
// SetTextAngle() or merely calling EDA_TEXT::SetTextAngle() after clamping
// aAngle before calling this lowest inline accessor.
m_e.angle = aAngle;
}
double GetTextAngle() const { return m_e.angle; }
double GetTextAngleDegrees() const { return GetTextAngle() / 10.0; }
double GetTextAngleRadians() const { return GetTextAngle() * M_PI/1800; }
void SetItalic( bool isItalic ) { m_e.Bit( TE_ITALIC, isItalic ); }
bool IsItalic() const { return m_e.Bit( TE_ITALIC ); }
void SetBold( bool aBold ) { m_e.Bit( TE_BOLD, aBold); }
bool IsBold() const { return m_e.Bit( TE_BOLD ); }
virtual void SetVisible( bool aVisible ) { m_e.Bit( TE_VISIBLE, aVisible ); }
virtual bool IsVisible() const { return m_e.Bit( TE_VISIBLE ); }
void SetMirrored( bool isMirrored ) { m_e.Bit( TE_MIRROR, isMirrored ); }
bool IsMirrored() const { return m_e.Bit( TE_MIRROR ); }
/**
* @param aAllow true if ok to use multiline option, false if ok to use only single line
* text. (Single line is faster in calculations than multiline.)
*/
void SetMultilineAllowed( bool aAllow ) { m_e.Bit( TE_MULTILINE, aAllow ); }
bool IsMultilineAllowed() const { return m_e.Bit( TE_MULTILINE ); }
EDA_TEXT_HJUSTIFY_T GetHorizJustify() const { return EDA_TEXT_HJUSTIFY_T( m_e.hjustify ); };
EDA_TEXT_VJUSTIFY_T GetVertJustify() const { return EDA_TEXT_VJUSTIFY_T( m_e.vjustify ); };
void SetHorizJustify( EDA_TEXT_HJUSTIFY_T aType ) { m_e.hjustify = aType; };
void SetVertJustify( EDA_TEXT_VJUSTIFY_T aType ) { m_e.vjustify = aType; };
/**
* Set the text effects from another instance.
*
* #TEXT_EFFECTS is not exposed in the public API, but includes everything except the actual
* text string itself.
*/
void SetEffects( const EDA_TEXT& aSrc );
/**
* Swap the text effects of the two involved instances.
*
* #TEXT_EFFECTS is not exposed in the public API, but includes everything except the actual
* text string itself.
*/
void SwapEffects( EDA_TEXT& aTradingPartner );
void SwapText( EDA_TEXT& aTradingPartner );
void CopyText( const EDA_TEXT& aSrc );
/**
* Helper function used in search and replace dialog.
*
* Perform a text replace using the find and replace criteria in \a aSearchData.
*
* @param aSearchData A reference to a wxFindReplaceData object containing the
* search and replace criteria.
* @return True if the text item was modified, otherwise false.
*/
bool Replace( const wxFindReplaceData& aSearchData );
bool IsDefaultFormatting() const;
void SetTextSize( const wxSize& aNewSize ) { m_e.size = aNewSize; }
const wxSize& GetTextSize() const { return m_e.size; }
void SetTextWidth( int aWidth ) { m_e.size.x = aWidth; }
int GetTextWidth() const { return m_e.size.x; }
void SetTextHeight( int aHeight ) { m_e.size.y = aHeight; }
int GetTextHeight() const { return m_e.size.y; }
void SetTextPos( const wxPoint& aPoint ) { m_e.pos = aPoint; }
const wxPoint& GetTextPos() const { return m_e.pos; }
void SetTextX( int aX ) { m_e.pos.x = aX; }
void SetTextY( int aY ) { m_e.pos.y = aY; }
void Offset( const wxPoint& aOffset ) { m_e.pos += aOffset; }
void Empty() { m_text.Empty(); }
static EDA_TEXT_HJUSTIFY_T MapHorizJustify( int aHorizJustify );
static EDA_TEXT_VJUSTIFY_T MapVertJustify( int aVertJustify );
/**
* Print this text object to the device context \a aDC.
*
* @param aDC the current Device Context.
* @param aOffset draw offset (usually (0,0)).
* @param aColor text color.
* @param aDisplay_mode #FILLED or #SKETCH.
*/
void Print( const RENDER_SETTINGS* aSettings, const wxPoint& aOffset,
const COLOR4D& aColor, OUTLINE_MODE aDisplay_mode = FILLED );
/**
* Convert the text shape to a list of segment.
*
* Each segment is stored as 2 wxPoints: the starting point and the ending point
* there are therefore 2*n points.
*/
std::vector<wxPoint> TransformToSegmentList() const;
/**
* Convert the text bounding box to a rectangular polygon depending on the text
* orientation, the bounding box is not always horizontal or vertical
*
* Used in filling zones calculations
* Circles and arcs are approximated by segments
*
* @param aCornerBuffer a buffer to store the polygon.
* @param aClearanceValue the clearance around the text bounding box
* to the real clearance value (usually near from 1.0).
*/
void TransformBoundingBoxWithClearanceToPolygon( SHAPE_POLY_SET* aCornerBuffer,
int aClearanceValue ) const;
std::shared_ptr<SHAPE_COMPOUND> GetEffectiveTextShape() const;
/**
* Test if \a aPoint is within the bounds of this object.
*
* @param aPoint A wxPoint to test.
* @param aAccuracy Amount to inflate the bounding box.
* @return true if a hit, else false.
*/
virtual bool TextHitTest( const wxPoint& aPoint, int aAccuracy = 0 ) const;
/**
* Test if object bounding box is contained within or intersects \a aRect.
*
* @param aRect Rect to test against.
* @param aContains Test for containment instead of intersection if true.
* @param aAccuracy Amount to inflate the bounding box.
* @return true if a hit, else false.
*/
virtual bool TextHitTest( const EDA_RECT& aRect, bool aContains, int aAccuracy = 0 ) const;
/**
* @return the text length in internal units.
* @param aLine the line of text to consider. For single line text, this parameter
* is always m_Text.
* @param aThickness the stroke width of the text.
*/
int LenSize( const wxString& aLine, int aThickness ) const;
/**
* Useful in multiline texts to calculate the full text or a line area (for zones filling,
* locate functions....)
*
* @param aLine The line of text to consider. Pass -1 for all lines.
* @param aInvertY Invert the Y axis when calculating bounding box.
* @return the rect containing the line of text (i.e. the position and the size of one line)
* this rectangle is calculated for 0 orient text.
* If orientation is not 0 the rect must be rotated to match the physical area
*/
EDA_RECT GetTextBox( int aLine = -1, bool aInvertY = false ) const;
/**
* Return the distance between two lines of text.
*
* Calculates the distance (pitch) between two lines of text. This distance includes the
* interline distance plus room for characters like j, {, and [. It also used for single
* line text, to calculate the text bounding box.
*/
int GetInterline() const;
/**
* @return a wxString with the style name( Normal, Italic, Bold, Bold+Italic).
*/
wxString GetTextStyleName() const;
/**
* Populate \a aPositions with the position of each line of a multiline text, according
* to the vertical justification and the rotation of the whole text.
*
* @param aPositions is the list to populate by the wxPoint positions.
* @param aLineCount is the number of lines (not recalculated here for efficiency reasons.
*/
void GetLinePositions( std::vector<wxPoint>& aPositions, int aLineCount ) const;
/**
* Output the object to \a aFormatter in s-expression form.
*
* @param aFormatter The #OUTPUTFORMATTER object to write to.
* @param aNestLevel The indentation next level.
* @param aControlBits The control bit definition for object specific formatting.
* @throw IO_ERROR on write error.
*/
virtual void Format( OUTPUTFORMATTER* aFormatter, int aNestLevel, int aControlBits ) const;
virtual double GetDrawRotation() const { return GetTextAngle(); }
virtual wxPoint GetDrawPos() const { return GetTextPos(); }
virtual EDA_TEXT_HJUSTIFY_T GetDrawHorizJustify() const { return GetHorizJustify(); };
virtual EDA_TEXT_VJUSTIFY_T GetDrawVertJustify() const { return GetVertJustify(); };
int Compare( const EDA_TEXT* aOther ) const;
private:
void cacheShownText();
/**
* Print each line of this EDA_TEXT..
*
* @param aOffset draw offset (usually (0,0)).
* @param aColor text color.
* @param aFillMode FILLED or SKETCH
* @param aText the single line of text to draw.
* @param aPos the position of this line ).
*/
void printOneLineOfText( const RENDER_SETTINGS* aSettings, const wxPoint& aOffset,
const COLOR4D& aColor, OUTLINE_MODE aFillMode, const wxString& aText,
const wxPoint& aPos );
wxString m_text;
wxString m_shown_text; // Cache of unescaped text for efficient access
bool m_shown_text_has_text_var_refs;
TEXT_EFFECTS m_e; // Private bitflags for text styling. API above
// provides accessor funcs.
enum TE_FLAGS {
TE_MIRROR,
TE_ITALIC,
TE_BOLD,
TE_MULTILINE,
TE_VISIBLE,
};
};
#endif // EDA_TEXT_H_