1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2017-2021 Kicad Developers, see change_log.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 
24 #include <preview_items/ruler_item.h>
25 #include <preview_items/preview_utils.h>
26 #include <gal/graphics_abstraction_layer.h>
27 #include <layer_ids.h>
28 #include <painter.h>
29 #include <view/view.h>
30 #include <trigo.h>
31 
32 using namespace KIGFX::PREVIEW;
33 
34 static const double maxTickDensity = 10.0;       // min pixels between tick marks
35 static const double midTickLengthFactor = 1.5;
36 static const double majorTickLengthFactor = 2.5;
37 
38 
39 /*
40  * It would be nice to know why Cairo seems to have an opposite layer order from GAL, but
41  * only when drawing RULER_ITEMs (the TWO_POINT_ASSISTANT and ARC_ASSISTANT are immune from
42  * this issue).
43  *
44  * Until then, this egregious hack...
45  */
getShadowLayer(KIGFX::GAL * aGal)46 static int getShadowLayer( KIGFX::GAL* aGal )
47 {
48     if( aGal->IsCairoEngine() )
49         return LAYER_SELECT_OVERLAY;
50     else
51         return LAYER_GP_OVERLAY;
52 }
53 
54 
drawCursorStrings(KIGFX::VIEW * aView,const VECTOR2D & aCursor,const VECTOR2D & aRulerVec,EDA_UNITS aUnits,bool aDrawingDropShadows,bool aFlipX,bool aFlipY)55 static void drawCursorStrings( KIGFX::VIEW* aView, const VECTOR2D& aCursor,
56                                const VECTOR2D& aRulerVec, EDA_UNITS aUnits,
57                                bool aDrawingDropShadows, bool aFlipX, bool aFlipY )
58 {
59     // draw the cursor labels
60     std::vector<wxString> cursorStrings;
61 
62     VECTOR2D temp = aRulerVec;
63 
64     if( aFlipX )
65         temp.x = -temp.x;
66 
67     if( aFlipY )
68         temp.y = -temp.y;
69 
70     cursorStrings.push_back( DimensionLabel( "x", temp.x, aUnits ) );
71     cursorStrings.push_back( DimensionLabel( "y", temp.y, aUnits ) );
72 
73     cursorStrings.push_back( DimensionLabel( "r", aRulerVec.EuclideanNorm(), aUnits ) );
74 
75     double degs = RAD2DECIDEG( -aRulerVec.Angle() );
76     cursorStrings.push_back( DimensionLabel( wxString::FromUTF8( "θ" ), degs,
77                                              EDA_UNITS::DEGREES ) );
78 
79     temp = aRulerVec;
80     DrawTextNextToCursor( aView, aCursor, -temp, cursorStrings, aDrawingDropShadows );
81 }
82 
83 
getTickLineWidth(const TEXT_DIMS & textDims,bool aDrawingDropShadows)84 static double getTickLineWidth( const TEXT_DIMS& textDims, bool aDrawingDropShadows )
85 {
86     double width = textDims.StrokeWidth * 0.8;
87 
88     if( aDrawingDropShadows )
89         width += textDims.ShadowWidth;
90 
91     return width;
92 }
93 
94 
95 /**
96  * Description of a "tick format" for a scale factor - how many ticks there are
97  * between medium/major ticks and how each scale relates to the last one
98  */
99 struct TICK_FORMAT
100 {
101     double divisionBase;    ///< multiple from the last scale
102     int majorStep;          ///< ticks between major ticks
103     int midStep;            ///< ticks between medium ticks (0 if no medium ticks)
104 };
105 
106 
getTickFormatForScale(double aScale,double & aTickSpace,EDA_UNITS aUnits)107 static TICK_FORMAT getTickFormatForScale( double aScale, double& aTickSpace, EDA_UNITS aUnits )
108 {
109     // simple 1/2/5 scales per decade
110     static std::vector<TICK_FORMAT> tickFormats =
111     {
112         { 2,    10,     5 },    // |....:....|
113         { 2,     5,     0 },    // |....|
114         { 2.5,   2,     0 },    // |.|.|
115     };
116 
117     // could start at a set number of MM, but that's not available in common
118     aTickSpace = 1;
119 
120     // Convert to a round (mod-10) number of mils for imperial units
121     if( EDA_UNIT_UTILS::IsImperialUnit( aUnits ) )
122         aTickSpace *= 2.54;
123 
124     int tickFormat = 0;
125 
126     while( true )
127     {
128         const auto pixelSpace = aTickSpace * aScale;
129 
130         if( pixelSpace >= maxTickDensity )
131             break;
132 
133         tickFormat = ( tickFormat + 1 ) % tickFormats.size();
134         aTickSpace *= tickFormats[tickFormat].divisionBase;
135     }
136 
137     return tickFormats[tickFormat];
138 }
139 
140 
141 /**
142  * Draw labelled ticks on a line. Ticks are spaced according to a
143  * maximum density. Minor ticks are not labelled.
144  *
145  * @param aGal the GAL to draw on
146  * @param aOrigin start of line to draw ticks on
147  * @param aLine line vector
148  * @param aMinorTickLen length of minor ticks in IU
149  */
drawTicksAlongLine(KIGFX::VIEW * aView,const VECTOR2D & aOrigin,const VECTOR2D & aLine,double aMinorTickLen,EDA_UNITS aUnits,bool aDrawingDropShadows)150 void drawTicksAlongLine( KIGFX::VIEW* aView, const VECTOR2D& aOrigin, const VECTOR2D& aLine,
151                          double aMinorTickLen, EDA_UNITS aUnits, bool aDrawingDropShadows )
152 {
153     KIGFX::GAL* gal = aView->GetGAL();
154     VECTOR2D    tickLine = aLine.Rotate( -M_PI_2 );
155     double      tickSpace;
156     TICK_FORMAT tickF = getTickFormatForScale( gal->GetWorldScale(), tickSpace, aUnits );
157 
158     // number of ticks in whole ruler
159     int         numTicks = (int) std::ceil( aLine.EuclideanNorm() / tickSpace );
160 
161     // work out which way up the tick labels go
162     TEXT_DIMS   textDims = SetConstantGlyphHeight( gal, -1 );
163     double      textThickness = textDims.StrokeWidth;
164     double      labelAngle = -tickLine.Angle();
165     double      textOffset = 0;
166 
167     if( aDrawingDropShadows )
168     {
169         textOffset = textDims.ShadowWidth;
170         textThickness += 2 * textDims.ShadowWidth;
171     }
172 
173     double majorTickLen = aMinorTickLen * ( majorTickLengthFactor + 1 );
174     VECTOR2D labelOffset = tickLine.Resize( majorTickLen - textOffset );
175 
176     if( aView->IsMirroredX() )
177     {
178         textOffset = -textOffset;
179         labelOffset = -labelOffset;
180     }
181 
182     if( aLine.Angle() > 0 )
183     {
184         gal->SetHorizontalJustify( GR_TEXT_HJUSTIFY_LEFT );
185     }
186     else
187     {
188         gal->SetHorizontalJustify( GR_TEXT_HJUSTIFY_RIGHT );
189         labelAngle += M_PI;
190     }
191 
192     BOX2D viewportD = aView->GetViewport();
193     BOX2I viewport( VECTOR2I( viewportD.GetPosition() ), VECTOR2I( viewportD.GetSize() ) );
194 
195     viewport.Inflate( majorTickLen * 2 );   // Doesn't have to be accurate, just big enough not
196                                             // to exclude anything that should be partially drawn
197 
198     for( int i = 0; i < numTicks; ++i )
199     {
200         const VECTOR2D tickPos = aOrigin + aLine.Resize( tickSpace * i );
201 
202         if( !viewport.Contains( tickPos ) )
203             continue;
204 
205         double length = aMinorTickLen;
206         bool drawLabel = false;
207 
208         if( i % tickF.majorStep == 0 )
209         {
210             drawLabel = true;
211             length *= majorTickLengthFactor;
212         }
213         else if( tickF.midStep && i % tickF.midStep == 0 )
214         {
215             drawLabel = true;
216             length *= midTickLengthFactor;
217         }
218 
219         gal->SetLineWidth( textThickness / 2 );
220         gal->DrawLine( tickPos, tickPos + tickLine.Resize( length ) );
221 
222         if( drawLabel )
223         {
224             wxString label = DimensionLabel( "", tickSpace * i, aUnits, false );
225             gal->SetLineWidth( textThickness );
226             gal->StrokeText( label, tickPos + labelOffset, labelAngle );
227         }
228     }
229 }
230 
231 
232 /**
233  * Draw simple ticks on the back of a line such that the line is
234  * divided into n parts.
235  *
236  * @param aGal the GAL to draw on
237  * @param aOrigin start of line to draw ticks on
238  * @param aLine line vector
239  * @param aTickLen length of ticks in IU
240  * @param aNumDivisions number of parts to divide the line into
241  */
drawBacksideTicks(KIGFX::VIEW * aView,const VECTOR2D & aOrigin,const VECTOR2D & aLine,double aTickLen,int aNumDivisions,bool aDrawingDropShadows)242 void drawBacksideTicks( KIGFX::VIEW* aView, const VECTOR2D& aOrigin, const VECTOR2D& aLine,
243                         double aTickLen, int aNumDivisions, bool aDrawingDropShadows )
244 {
245     KIGFX::GAL*    gal = aView->GetGAL();
246     const double   backTickSpace = aLine.EuclideanNorm() / aNumDivisions;
247     const VECTOR2D backTickVec = aLine.Rotate( M_PI_2 ).Resize( aTickLen );
248     TEXT_DIMS      textDims = SetConstantGlyphHeight( gal, -1 );
249 
250     BOX2D viewportD = aView->GetViewport();
251     BOX2I viewport( VECTOR2I( viewportD.GetPosition() ), VECTOR2I( viewportD.GetSize() ) );
252 
253     viewport.Inflate( aTickLen * 4 );   // Doesn't have to be accurate, just big enough not to
254                                         // exclude anything that should be partially drawn
255 
256     for( int i = 0; i < aNumDivisions + 1; ++i )
257     {
258         const VECTOR2D backTickPos = aOrigin + aLine.Resize( backTickSpace * i );
259 
260         if( !viewport.Contains( backTickPos ) )
261             continue;
262 
263         gal->SetLineWidth( getTickLineWidth( textDims, aDrawingDropShadows ) );
264         gal->DrawLine( backTickPos, backTickPos + backTickVec );
265     }
266 }
267 
268 
RULER_ITEM(const TWO_POINT_GEOMETRY_MANAGER & aGeomMgr,EDA_UNITS userUnits,bool aFlipX,bool aFlipY)269 RULER_ITEM::RULER_ITEM( const TWO_POINT_GEOMETRY_MANAGER& aGeomMgr, EDA_UNITS userUnits,
270         bool aFlipX, bool aFlipY )
271         : EDA_ITEM( NOT_USED ), // Never added to anything - just a preview
272           m_geomMgr( aGeomMgr ),
273           m_userUnits( userUnits ),
274           m_flipX( aFlipX ),
275           m_flipY( aFlipY )
276 {
277 }
278 
279 
ViewBBox() const280 const BOX2I RULER_ITEM::ViewBBox() const
281 {
282     BOX2I tmp;
283 
284     if( m_geomMgr.GetOrigin() == m_geomMgr.GetEnd() )
285         return tmp;
286 
287     // this is an edit-time artefact; no reason to try and be smart with the bounding box
288     // (besides, we can't tell the text extents without a view to know what the scale is)
289     tmp.SetMaximum();
290     return tmp;
291 }
292 
293 
ViewGetLayers(int aLayers[],int & aCount) const294 void RULER_ITEM::ViewGetLayers( int aLayers[], int& aCount ) const
295 {
296     aLayers[0] = LAYER_SELECT_OVERLAY;
297     aLayers[1] = LAYER_GP_OVERLAY;
298     aCount = 2;
299 }
300 
301 
ViewDraw(int aLayer,KIGFX::VIEW * aView) const302 void RULER_ITEM::ViewDraw( int aLayer, KIGFX::VIEW* aView ) const
303 {
304     KIGFX::GAL*      gal = aView->GetGAL();
305     RENDER_SETTINGS* rs = aView->GetPainter()->GetSettings();
306     bool             drawingDropShadows = ( aLayer == getShadowLayer( gal ) );
307 
308     gal->PushDepth();
309     gal->SetLayerDepth( gal->GetMinDepth() );
310 
311     VECTOR2D origin = m_geomMgr.GetOrigin();
312     VECTOR2D end = m_geomMgr.GetEnd();
313 
314     gal->SetIsStroke( true );
315     gal->SetIsFill( false );
316 
317     gal->SetTextMirrored( false );
318     gal->SetStrokeColor( rs->GetLayerColor( LAYER_AUX_ITEMS ) );
319 
320     if( drawingDropShadows )
321         gal->SetStrokeColor( GetShadowColor( gal->GetStrokeColor() ) );
322 
323     gal->ResetTextAttributes();
324     TEXT_DIMS textDims = SetConstantGlyphHeight( gal );
325 
326     // draw the main line from the origin to cursor
327     gal->SetLineWidth( getTickLineWidth( textDims, drawingDropShadows ) );
328     gal->DrawLine( origin, end );
329 
330     VECTOR2D rulerVec( end - origin );
331 
332     drawCursorStrings( aView, end, rulerVec, m_userUnits, drawingDropShadows, m_flipX, m_flipY );
333 
334     // basic tick size
335     const double minorTickLen = 5.0 / gal->GetWorldScale();
336     const double majorTickLen = minorTickLen * majorTickLengthFactor;
337 
338     drawTicksAlongLine( aView, origin, rulerVec, minorTickLen, m_userUnits, drawingDropShadows );
339 
340     drawBacksideTicks( aView, origin, rulerVec, majorTickLen, 2, drawingDropShadows );
341 
342     // draw the back of the origin "crosshair"
343     gal->DrawLine( origin, origin + rulerVec.Resize( -minorTickLen * midTickLengthFactor ) );
344     gal->PopDepth();
345 }
346