]> git.saurik.com Git - wxWidgets.git/blame_incremental - src/msw/dc.cpp
Updated to new PyCrust
[wxWidgets.git] / src / msw / dc.cpp
... / ...
CommitLineData
1/////////////////////////////////////////////////////////////////////////////
2// Name: dc.cpp
3// Purpose: wxDC class
4// Author: Julian Smart
5// Modified by:
6// Created: 01/02/97
7// RCS-ID: $Id$
8// Copyright: (c) Julian Smart and Markus Holzem
9// Licence: wxWindows licence
10/////////////////////////////////////////////////////////////////////////////
11
12// ===========================================================================
13// declarations
14// ===========================================================================
15
16// ---------------------------------------------------------------------------
17// headers
18// ---------------------------------------------------------------------------
19
20#ifdef __GNUG__
21 #pragma implementation "dc.h"
22#endif
23
24// For compilers that support precompilation, includes "wx.h".
25#include "wx/wxprec.h"
26
27#ifdef __BORLANDC__
28 #pragma hdrstop
29#endif
30
31#ifndef WX_PRECOMP
32 #include "wx/window.h"
33 #include "wx/dc.h"
34 #include "wx/utils.h"
35 #include "wx/dialog.h"
36 #include "wx/app.h"
37 #include "wx/bitmap.h"
38 #include "wx/dcmemory.h"
39 #include "wx/log.h"
40 #include "wx/icon.h"
41#endif
42
43#include "wx/sysopt.h"
44#include "wx/dcprint.h"
45#include "wx/module.h"
46
47#include <string.h>
48#include <math.h>
49
50#include "wx/msw/private.h" // needs to be before #include <commdlg.h>
51
52#if wxUSE_COMMON_DIALOGS
53 #include <commdlg.h>
54#endif
55
56#ifndef __WIN32__
57 #include <print.h>
58#endif
59
60IMPLEMENT_ABSTRACT_CLASS(wxDC, wxDCBase)
61
62// ---------------------------------------------------------------------------
63// constants
64// ---------------------------------------------------------------------------
65
66static const int VIEWPORT_EXTENT = 1000;
67
68static const int MM_POINTS = 9;
69static const int MM_METRIC = 10;
70
71// usually this is defined in math.h
72#ifndef M_PI
73 static const double M_PI = 3.14159265358979323846;
74#endif // M_PI
75
76// ROPs which don't have standard names (see "Ternary Raster Operations" in the
77// MSDN docs for how this and other numbers in wxDC::Blit() are obtained)
78#define DSTCOPY 0x00AA0029 // a.k.a. NOP operation
79
80// ---------------------------------------------------------------------------
81// private functions
82// ---------------------------------------------------------------------------
83
84// convert degrees to radians
85static inline double DegToRad(double deg) { return (deg * M_PI) / 180.0; }
86
87// ----------------------------------------------------------------------------
88// private classes
89// ----------------------------------------------------------------------------
90
91// instead of duplicating the same code which sets and then restores text
92// colours in each wxDC method working with wxSTIPPLE_MASK_OPAQUE brushes,
93// encapsulate this in a small helper class
94
95// wxColourChanger: changes the text colours in the ctor if required and
96// restores them in the dtor
97class wxColourChanger
98{
99public:
100 wxColourChanger(wxDC& dc);
101 ~wxColourChanger();
102
103private:
104 wxDC& m_dc;
105
106 COLORREF m_colFgOld, m_colBgOld;
107
108 bool m_changed;
109};
110
111// ===========================================================================
112// implementation
113// ===========================================================================
114
115// ----------------------------------------------------------------------------
116// wxColourChanger
117// ----------------------------------------------------------------------------
118
119wxColourChanger::wxColourChanger(wxDC& dc) : m_dc(dc)
120{
121 if ( dc.GetBrush().GetStyle() == wxSTIPPLE_MASK_OPAQUE )
122 {
123 HDC hdc = GetHdcOf(dc);
124 m_colFgOld = ::GetTextColor(hdc);
125 m_colBgOld = ::GetBkColor(hdc);
126
127 // note that Windows convention is opposite to wxWindows one, this is
128 // why text colour becomes the background one and vice versa
129 const wxColour& colFg = dc.GetTextForeground();
130 if ( colFg.Ok() )
131 {
132 ::SetBkColor(hdc, colFg.GetPixel());
133 }
134
135 const wxColour& colBg = dc.GetTextBackground();
136 if ( colBg.Ok() )
137 {
138 ::SetTextColor(hdc, colBg.GetPixel());
139 }
140
141 SetBkMode(hdc,
142 dc.GetBackgroundMode() == wxTRANSPARENT ? TRANSPARENT
143 : OPAQUE);
144
145 // flag which telsl us to undo changes in the dtor
146 m_changed = TRUE;
147 }
148 else
149 {
150 // nothing done, nothing to undo
151 m_changed = FALSE;
152 }
153}
154
155wxColourChanger::~wxColourChanger()
156{
157 if ( m_changed )
158 {
159 // restore the colours we changed
160 HDC hdc = GetHdcOf(m_dc);
161
162 ::SetBkMode(hdc, TRANSPARENT);
163 ::SetTextColor(hdc, m_colFgOld);
164 ::SetBkColor(hdc, m_colBgOld);
165 }
166}
167
168// ---------------------------------------------------------------------------
169// wxDC
170// ---------------------------------------------------------------------------
171
172// Default constructor
173wxDC::wxDC()
174{
175 m_canvas = NULL;
176
177 m_oldBitmap = 0;
178 m_oldPen = 0;
179 m_oldBrush = 0;
180 m_oldFont = 0;
181#if wxUSE_PALETTE
182 m_oldPalette = 0;
183#endif // wxUSE_PALETTE
184
185 m_bOwnsDC = FALSE;
186 m_hDC = 0;
187
188 m_windowExtX = VIEWPORT_EXTENT;
189 m_windowExtY = VIEWPORT_EXTENT;
190}
191
192
193wxDC::~wxDC()
194{
195 if ( m_hDC != 0 )
196 {
197 SelectOldObjects(m_hDC);
198
199 // if we own the HDC, we delete it, otherwise we just release it
200
201 if ( m_bOwnsDC )
202 {
203 ::DeleteDC(GetHdc());
204 }
205 else // we don't own our HDC
206 {
207 if (m_canvas)
208 {
209 ::ReleaseDC(GetHwndOf(m_canvas), GetHdc());
210 }
211 else
212 {
213 // Must have been a wxScreenDC
214 ::ReleaseDC((HWND) NULL, GetHdc());
215 }
216 }
217 }
218}
219
220// This will select current objects out of the DC,
221// which is what you have to do before deleting the
222// DC.
223void wxDC::SelectOldObjects(WXHDC dc)
224{
225 if (dc)
226 {
227 if (m_oldBitmap)
228 {
229 ::SelectObject((HDC) dc, (HBITMAP) m_oldBitmap);
230 if (m_selectedBitmap.Ok())
231 {
232 m_selectedBitmap.SetSelectedInto(NULL);
233 }
234 }
235 m_oldBitmap = 0;
236 if (m_oldPen)
237 {
238 ::SelectObject((HDC) dc, (HPEN) m_oldPen);
239 }
240 m_oldPen = 0;
241 if (m_oldBrush)
242 {
243 ::SelectObject((HDC) dc, (HBRUSH) m_oldBrush);
244 }
245 m_oldBrush = 0;
246 if (m_oldFont)
247 {
248 ::SelectObject((HDC) dc, (HFONT) m_oldFont);
249 }
250 m_oldFont = 0;
251
252#if wxUSE_PALETTE
253 if (m_oldPalette)
254 {
255 ::SelectPalette((HDC) dc, (HPALETTE) m_oldPalette, FALSE);
256 }
257 m_oldPalette = 0;
258#endif // wxUSE_PALETTE
259 }
260
261 m_brush = wxNullBrush;
262 m_pen = wxNullPen;
263#if wxUSE_PALETTE
264 m_palette = wxNullPalette;
265#endif // wxUSE_PALETTE
266 m_font = wxNullFont;
267 m_backgroundBrush = wxNullBrush;
268 m_selectedBitmap = wxNullBitmap;
269}
270
271// ---------------------------------------------------------------------------
272// clipping
273// ---------------------------------------------------------------------------
274
275void wxDC::UpdateClipBox()
276{
277#ifdef __WXMICROWIN__
278 if (!GetHDC()) return;
279#endif
280
281 RECT rect;
282 GetClipBox(GetHdc(), &rect);
283
284 m_clipX1 = (wxCoord) XDEV2LOG(rect.left);
285 m_clipY1 = (wxCoord) YDEV2LOG(rect.top);
286 m_clipX2 = (wxCoord) XDEV2LOG(rect.right);
287 m_clipY2 = (wxCoord) YDEV2LOG(rect.bottom);
288}
289
290void wxDC::DoSetClippingRegion(wxCoord x, wxCoord y, wxCoord w, wxCoord h)
291{
292#ifdef __WXMICROWIN__
293 if (!GetHDC()) return;
294#endif
295
296 m_clipping = TRUE;
297
298 // the region coords are always the device ones, so do the translation
299 // manually
300 //
301 // FIXME: possible +/-1 error here, to check!
302 HRGN hrgn = ::CreateRectRgn(LogicalToDeviceX(x),
303 LogicalToDeviceY(y),
304 LogicalToDeviceX(x + w),
305 LogicalToDeviceY(y + h));
306 if ( !hrgn )
307 {
308 wxLogLastError(_T("CreateRectRgn"));
309 }
310 else
311 {
312 if ( ::SelectClipRgn(GetHdc(), hrgn) == ERROR )
313 {
314 wxLogLastError(_T("SelectClipRgn"));
315 }
316 DeleteObject(hrgn);
317
318 UpdateClipBox();
319 }
320}
321
322void wxDC::DoSetClippingRegionAsRegion(const wxRegion& region)
323{
324#ifdef __WXMICROWIN__
325 if (!GetHDC()) return;
326#endif
327
328 wxCHECK_RET( GetHrgnOf(region), wxT("invalid clipping region") );
329
330 m_clipping = TRUE;
331
332#ifdef __WIN16__
333 SelectClipRgn(GetHdc(), GetHrgnOf(region));
334#else // Win32
335 ExtSelectClipRgn(GetHdc(), GetHrgnOf(region), RGN_AND);
336#endif // Win16/32
337
338 UpdateClipBox();
339}
340
341void wxDC::DestroyClippingRegion()
342{
343#ifdef __WXMICROWIN__
344 if (!GetHDC()) return;
345#endif
346
347 if (m_clipping && m_hDC)
348 {
349 // TODO: this should restore the previous clipping region,
350 // so that OnPaint processing works correctly, and the update clipping region
351 // doesn't get destroyed after the first DestroyClippingRegion.
352 HRGN rgn = CreateRectRgn(0, 0, 32000, 32000);
353 SelectClipRgn(GetHdc(), rgn);
354 DeleteObject(rgn);
355 }
356
357 m_clipping = FALSE;
358}
359
360// ---------------------------------------------------------------------------
361// query capabilities
362// ---------------------------------------------------------------------------
363
364bool wxDC::CanDrawBitmap() const
365{
366 return TRUE;
367}
368
369bool wxDC::CanGetTextExtent() const
370{
371#ifdef __WXMICROWIN__
372 // TODO Extend MicroWindows' GetDeviceCaps function
373 return TRUE;
374#else
375 // What sort of display is it?
376 int technology = ::GetDeviceCaps(GetHdc(), TECHNOLOGY);
377
378 return (technology == DT_RASDISPLAY) || (technology == DT_RASPRINTER);
379#endif
380}
381
382int wxDC::GetDepth() const
383{
384#ifdef __WXMICROWIN__
385 if (!GetHDC()) return 16;
386#endif
387
388 return (int)::GetDeviceCaps(GetHdc(), BITSPIXEL);
389}
390
391// ---------------------------------------------------------------------------
392// drawing
393// ---------------------------------------------------------------------------
394
395void wxDC::Clear()
396{
397#ifdef __WXMICROWIN__
398 if (!GetHDC()) return;
399#endif
400
401 RECT rect;
402 if ( m_canvas )
403 {
404 GetClientRect((HWND) m_canvas->GetHWND(), &rect);
405 }
406 else
407 {
408 // No, I think we should simply ignore this if printing on e.g.
409 // a printer DC.
410 // wxCHECK_RET( m_selectedBitmap.Ok(), wxT("this DC can't be cleared") );
411 if (!m_selectedBitmap.Ok())
412 return;
413
414 rect.left = 0; rect.top = 0;
415 rect.right = m_selectedBitmap.GetWidth();
416 rect.bottom = m_selectedBitmap.GetHeight();
417 }
418
419 (void) ::SetMapMode(GetHdc(), MM_TEXT);
420
421 DWORD colour = GetBkColor(GetHdc());
422 HBRUSH brush = CreateSolidBrush(colour);
423 FillRect(GetHdc(), &rect, brush);
424 DeleteObject(brush);
425
426 ::SetMapMode(GetHdc(), MM_ANISOTROPIC);
427 ::SetViewportExtEx(GetHdc(), VIEWPORT_EXTENT, VIEWPORT_EXTENT, NULL);
428 ::SetWindowExtEx(GetHdc(), m_windowExtX, m_windowExtY, NULL);
429 ::SetViewportOrgEx(GetHdc(), (int)m_deviceOriginX, (int)m_deviceOriginY, NULL);
430 ::SetWindowOrgEx(GetHdc(), (int)m_logicalOriginX, (int)m_logicalOriginY, NULL);
431}
432
433void wxDC::DoFloodFill(wxCoord x, wxCoord y, const wxColour& col, int style)
434{
435#ifdef __WXMICROWIN__
436 if (!GetHDC()) return;
437#endif
438
439 if ( !::ExtFloodFill(GetHdc(), XLOG2DEV(x), YLOG2DEV(y),
440 col.GetPixel(),
441 style == wxFLOOD_SURFACE ? FLOODFILLSURFACE
442 : FLOODFILLBORDER) )
443 {
444 // quoting from the MSDN docs:
445 //
446 // Following are some of the reasons this function might fail:
447 //
448 // * The filling could not be completed.
449 // * The specified point has the boundary color specified by the
450 // crColor parameter (if FLOODFILLBORDER was requested).
451 // * The specified point does not have the color specified by
452 // crColor (if FLOODFILLSURFACE was requested)
453 // * The point is outside the clipping region that is, it is not
454 // visible on the device.
455 //
456 wxLogLastError(wxT("ExtFloodFill"));
457 }
458
459 CalcBoundingBox(x, y);
460}
461
462bool wxDC::DoGetPixel(wxCoord x, wxCoord y, wxColour *col) const
463{
464#ifdef __WXMICROWIN__
465 if (!GetHDC()) return FALSE;
466#endif
467
468 wxCHECK_MSG( col, FALSE, _T("NULL colour parameter in wxDC::GetPixel") );
469
470 // get the color of the pixel
471 COLORREF pixelcolor = ::GetPixel(GetHdc(), XLOG2DEV(x), YLOG2DEV(y));
472
473 wxRGBToColour(*col, pixelcolor);
474
475 return TRUE;
476}
477
478void wxDC::DoCrossHair(wxCoord x, wxCoord y)
479{
480#ifdef __WXMICROWIN__
481 if (!GetHDC()) return;
482#endif
483
484 wxCoord x1 = x-VIEWPORT_EXTENT;
485 wxCoord y1 = y-VIEWPORT_EXTENT;
486 wxCoord x2 = x+VIEWPORT_EXTENT;
487 wxCoord y2 = y+VIEWPORT_EXTENT;
488
489 (void)MoveToEx(GetHdc(), XLOG2DEV(x1), YLOG2DEV(y), NULL);
490 (void)LineTo(GetHdc(), XLOG2DEV(x2), YLOG2DEV(y));
491
492 (void)MoveToEx(GetHdc(), XLOG2DEV(x), YLOG2DEV(y1), NULL);
493 (void)LineTo(GetHdc(), XLOG2DEV(x), YLOG2DEV(y2));
494
495 CalcBoundingBox(x1, y1);
496 CalcBoundingBox(x2, y2);
497}
498
499void wxDC::DoDrawLine(wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
500{
501#ifdef __WXMICROWIN__
502 if (!GetHDC()) return;
503#endif
504
505 (void)MoveToEx(GetHdc(), XLOG2DEV(x1), YLOG2DEV(y1), NULL);
506 (void)LineTo(GetHdc(), XLOG2DEV(x2), YLOG2DEV(y2));
507
508 // Normalization: Windows doesn't draw the last point of the line.
509 // But apparently neither does GTK+, so we take it out again.
510// (void)LineTo(GetHdc(), XLOG2DEV(x2) + 1, YLOG2DEV(y2));
511
512 CalcBoundingBox(x1, y1);
513 CalcBoundingBox(x2, y2);
514}
515
516// Draws an arc of a circle, centred on (xc, yc), with starting point (x1, y1)
517// and ending at (x2, y2)
518void wxDC::DoDrawArc(wxCoord x1, wxCoord y1,
519 wxCoord x2, wxCoord y2,
520 wxCoord xc, wxCoord yc)
521{
522#ifdef __WXMICROWIN__
523 if (!GetHDC()) return;
524#endif
525
526 wxColourChanger cc(*this); // needed for wxSTIPPLE_MASK_OPAQUE handling
527
528 double dx = xc - x1;
529 double dy = yc - y1;
530 double radius = (double)sqrt(dx*dx+dy*dy);
531 wxCoord r = (wxCoord)radius;
532
533 // treat the special case of full circle separately
534 if ( x1 == x2 && y1 == y2 )
535 {
536 DrawEllipse(xc - r, yc - r, 2*r, 2*r);
537 return;
538 }
539
540 wxCoord xx1 = XLOG2DEV(x1);
541 wxCoord yy1 = YLOG2DEV(y1);
542 wxCoord xx2 = XLOG2DEV(x2);
543 wxCoord yy2 = YLOG2DEV(y2);
544 wxCoord xxc = XLOG2DEV(xc);
545 wxCoord yyc = YLOG2DEV(yc);
546 wxCoord ray = (wxCoord) sqrt(double((xxc-xx1)*(xxc-xx1)+(yyc-yy1)*(yyc-yy1)));
547
548 wxCoord xxx1 = (wxCoord) (xxc-ray);
549 wxCoord yyy1 = (wxCoord) (yyc-ray);
550 wxCoord xxx2 = (wxCoord) (xxc+ray);
551 wxCoord yyy2 = (wxCoord) (yyc+ray);
552
553 if ( m_brush.Ok() && m_brush.GetStyle() != wxTRANSPARENT )
554 {
555 // Have to add 1 to bottom-right corner of rectangle
556 // to make semi-circles look right (crooked line otherwise).
557 // Unfortunately this is not a reliable method, depends
558 // on the size of shape.
559 // TODO: figure out why this happens!
560 Pie(GetHdc(),xxx1,yyy1,xxx2+1,yyy2+1, xx1,yy1,xx2,yy2);
561 }
562 else
563 {
564 Arc(GetHdc(),xxx1,yyy1,xxx2,yyy2, xx1,yy1,xx2,yy2);
565 }
566
567 CalcBoundingBox(xc - r, yc - r);
568 CalcBoundingBox(xc + r, yc + r);
569}
570
571void wxDC::DoDrawCheckMark(wxCoord x1, wxCoord y1,
572 wxCoord width, wxCoord height)
573{
574#ifdef __WXMICROWIN__
575 if (!GetHDC()) return;
576#endif
577
578 wxCoord x2 = x1 + width,
579 y2 = y1 + height;
580
581#if defined(__WIN32__) && !defined(__SC__) && !defined(__WXMICROWIN__)
582 RECT rect;
583 rect.left = x1;
584 rect.top = y1;
585 rect.right = x2;
586 rect.bottom = y2;
587
588 DrawFrameControl(GetHdc(), &rect, DFC_MENU, DFCS_MENUCHECK);
589#else // Win16
590 // In WIN16, draw a cross
591 HPEN blackPen = ::CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
592 HPEN whiteBrush = (HPEN)::GetStockObject(WHITE_BRUSH);
593 HPEN hPenOld = (HPEN)::SelectObject(GetHdc(), blackPen);
594 HPEN hBrushOld = (HPEN)::SelectObject(GetHdc(), whiteBrush);
595 ::SetROP2(GetHdc(), R2_COPYPEN);
596 Rectangle(GetHdc(), x1, y1, x2, y2);
597 MoveToEx(GetHdc(), x1, y1, NULL);
598 LineTo(GetHdc(), x2, y2);
599 MoveToEx(GetHdc(), x2, y1, NULL);
600 LineTo(GetHdc(), x1, y2);
601 ::SelectObject(GetHdc(), hPenOld);
602 ::SelectObject(GetHdc(), hBrushOld);
603 ::DeleteObject(blackPen);
604#endif // Win32/16
605
606 CalcBoundingBox(x1, y1);
607 CalcBoundingBox(x2, y2);
608}
609
610void wxDC::DoDrawPoint(wxCoord x, wxCoord y)
611{
612#ifdef __WXMICROWIN__
613 if (!GetHDC()) return;
614#endif
615
616 COLORREF color = 0x00ffffff;
617 if (m_pen.Ok())
618 {
619 color = m_pen.GetColour().GetPixel();
620 }
621
622 SetPixel(GetHdc(), XLOG2DEV(x), YLOG2DEV(y), color);
623
624 CalcBoundingBox(x, y);
625}
626
627void wxDC::DoDrawPolygon(int n, wxPoint points[], wxCoord xoffset, wxCoord yoffset,int fillStyle)
628{
629#ifdef __WXMICROWIN__
630 if (!GetHDC()) return;
631#endif
632
633 wxColourChanger cc(*this); // needed for wxSTIPPLE_MASK_OPAQUE handling
634
635 // Do things less efficiently if we have offsets
636 if (xoffset != 0 || yoffset != 0)
637 {
638 POINT *cpoints = new POINT[n];
639 int i;
640 for (i = 0; i < n; i++)
641 {
642 cpoints[i].x = (int)(points[i].x + xoffset);
643 cpoints[i].y = (int)(points[i].y + yoffset);
644
645 CalcBoundingBox(cpoints[i].x, cpoints[i].y);
646 }
647 int prev = SetPolyFillMode(GetHdc(),fillStyle==wxODDEVEN_RULE?ALTERNATE:WINDING);
648 (void)Polygon(GetHdc(), cpoints, n);
649 SetPolyFillMode(GetHdc(),prev);
650 delete[] cpoints;
651 }
652 else
653 {
654 int i;
655 for (i = 0; i < n; i++)
656 CalcBoundingBox(points[i].x, points[i].y);
657
658 int prev = SetPolyFillMode(GetHdc(),fillStyle==wxODDEVEN_RULE?ALTERNATE:WINDING);
659 (void)Polygon(GetHdc(), (POINT*) points, n);
660 SetPolyFillMode(GetHdc(),prev);
661 }
662}
663
664void wxDC::DoDrawLines(int n, wxPoint points[], wxCoord xoffset, wxCoord yoffset)
665{
666#ifdef __WXMICROWIN__
667 if (!GetHDC()) return;
668#endif
669
670 // Do things less efficiently if we have offsets
671 if (xoffset != 0 || yoffset != 0)
672 {
673 POINT *cpoints = new POINT[n];
674 int i;
675 for (i = 0; i < n; i++)
676 {
677 cpoints[i].x = (int)(points[i].x + xoffset);
678 cpoints[i].y = (int)(points[i].y + yoffset);
679
680 CalcBoundingBox(cpoints[i].x, cpoints[i].y);
681 }
682 (void)Polyline(GetHdc(), cpoints, n);
683 delete[] cpoints;
684 }
685 else
686 {
687 int i;
688 for (i = 0; i < n; i++)
689 CalcBoundingBox(points[i].x, points[i].y);
690
691 (void)Polyline(GetHdc(), (POINT*) points, n);
692 }
693}
694
695void wxDC::DoDrawRectangle(wxCoord x, wxCoord y, wxCoord width, wxCoord height)
696{
697#ifdef __WXMICROWIN__
698 if (!GetHDC()) return;
699#endif
700
701 wxColourChanger cc(*this); // needed for wxSTIPPLE_MASK_OPAQUE handling
702
703 wxCoord x2 = x + width;
704 wxCoord y2 = y + height;
705
706 if ((m_logicalFunction == wxCOPY) && (m_pen.GetStyle() == wxTRANSPARENT))
707 {
708 RECT rect;
709 rect.left = XLOG2DEV(x);
710 rect.top = YLOG2DEV(y);
711 rect.right = XLOG2DEV(x2);
712 rect.bottom = YLOG2DEV(y2);
713 (void)FillRect(GetHdc(), &rect, (HBRUSH)m_brush.GetResourceHandle() );
714 }
715 else
716 {
717 // Windows draws the filled rectangles without outline (i.e. drawn with a
718 // transparent pen) one pixel smaller in both directions and we want them
719 // to have the same size regardless of which pen is used - adjust
720
721