LibreOffice Module vcl (master)  1
outdev/hatch.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  * Licensed to the Apache Software Foundation (ASF) under one or more
12  * contributor license agreements. See the NOTICE file distributed
13  * with this work for additional information regarding copyright
14  * ownership. The ASF licenses this file to you under the Apache
15  * License, Version 2.0 (the "License"); you may not use this file
16  * except in compliance with the License. You may obtain a copy of
17  * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <cassert>
21 #include <cstdlib>
22 
23 #include <osl/diagnose.h>
24 #include <tools/line.hxx>
25 #include <tools/helpers.hxx>
26 
27 #include <unotools/configmgr.hxx>
28 
29 #include <vcl/hatch.hxx>
30 #include <vcl/metaact.hxx>
31 #include <vcl/settings.hxx>
32 #include <vcl/outdev.hxx>
33 #include <vcl/virdev.hxx>
34 
35 #include <drawmode.hxx>
36 #include <salgdi.hxx>
37 
38 #include <memory>
39 
40 #define HATCH_MAXPOINTS 1024
41 
42 extern "C" {
43 
44 static int HatchCmpFnc( const void* p1, const void* p2 )
45 {
46  const tools::Long nX1 = static_cast<Point const *>(p1)->X();
47  const tools::Long nX2 = static_cast<Point const *>(p2)->X();
48  const tools::Long nY1 = static_cast<Point const *>(p1)->Y();
49  const tools::Long nY2 = static_cast<Point const *>(p2)->Y();
50 
51  return ( nX1 > nX2 ? 1 : nX1 == nX2 ? nY1 > nY2 ? 1: nY1 == nY2 ? 0 : -1 : -1 );
52 }
53 
54 }
55 
56 void OutputDevice::DrawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
57 {
58  assert(!is_double_buffered_window());
59 
60  Hatch aHatch( rHatch );
62 
63  if( mpMetaFile )
64  mpMetaFile->AddAction( new MetaHatchAction( rPolyPoly, aHatch ) );
65 
67  return;
68 
69  if( !mpGraphics && !AcquireGraphics() )
70  return;
71  assert(mpGraphics);
72 
73  if( mbInitClipRegion )
75 
76  if( mbOutputClipped )
77  return;
78 
79  if( rPolyPoly.Count() )
80  {
81  tools::PolyPolygon aPolyPoly( LogicToPixel( rPolyPoly ) );
82  GDIMetaFile* pOldMetaFile = mpMetaFile;
83  bool bOldMap = mbMap;
84 
85  aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME );
87 
88  mpMetaFile = nullptr;
89  EnableMapMode( false );
91  SetLineColor( aHatch.GetColor() );
92  InitLineColor();
93  DrawHatch( aPolyPoly, aHatch, false );
94  Pop();
95  EnableMapMode( bOldMap );
96  mpMetaFile = pOldMetaFile;
97  }
98 
99  if( mpAlphaVDev )
100  mpAlphaVDev->DrawHatch( rPolyPoly, rHatch );
101 }
102 
103 void OutputDevice::AddHatchActions( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch,
104  GDIMetaFile& rMtf )
105 {
106 
107  tools::PolyPolygon aPolyPoly( rPolyPoly );
108  aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME | PolyOptimizeFlags::CLOSE );
109 
110  if( aPolyPoly.Count() )
111  {
112  GDIMetaFile* pOldMtf = mpMetaFile;
113 
114  mpMetaFile = &rMtf;
116  mpMetaFile->AddAction( new MetaLineColorAction( rHatch.GetColor(), true ) );
117  DrawHatch( aPolyPoly, rHatch, true );
119  mpMetaFile = pOldMtf;
120  }
121 }
122 
123 static bool HasSaneNSteps(const Point& rPt1, const Point& rEndPt1, const Size& rInc)
124 {
125  tools::Long nVertSteps = -1;
126  if (rInc.Height())
127  {
128  bool bFail = o3tl::checked_sub(rEndPt1.Y(), rPt1.Y(), nVertSteps);
129  if (bFail)
130  nVertSteps = std::numeric_limits<tools::Long>::max();
131  else
132  nVertSteps = nVertSteps / rInc.Height();
133  }
134  tools::Long nHorzSteps = -1;
135  if (rInc.Width())
136  {
137  bool bFail = o3tl::checked_sub(rEndPt1.X(), rPt1.X(), nHorzSteps);
138  if (bFail)
139  nHorzSteps = std::numeric_limits<tools::Long>::max();
140  else
141  nHorzSteps = nHorzSteps / rInc.Width();
142  }
143  auto nSteps = std::max(nVertSteps, nHorzSteps);
144  if (nSteps > 1024)
145  {
146  SAL_WARN("vcl.gdi", "skipping slow hatch with " << nSteps << " steps");
147  return false;
148  }
149  return true;
150 }
151 
152 void OutputDevice::DrawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch, bool bMtf )
153 {
154  assert(!is_double_buffered_window());
155 
156  if(!rPolyPoly.Count())
157  return;
158 
159  // #i115630# DrawHatch does not work with beziers included in the polypolygon, take care of that
160  bool bIsCurve(false);
161 
162  for(sal_uInt16 a(0); !bIsCurve && a < rPolyPoly.Count(); a++)
163  {
164  if(rPolyPoly[a].HasFlags())
165  {
166  bIsCurve = true;
167  }
168  }
169 
170  if(bIsCurve)
171  {
172  OSL_ENSURE(false, "DrawHatch does *not* support curves, falling back to AdaptiveSubdivide()...");
173  tools::PolyPolygon aPolyPoly;
174 
175  rPolyPoly.AdaptiveSubdivide(aPolyPoly);
176  DrawHatch(aPolyPoly, rHatch, bMtf);
177  }
178  else
179  {
180  tools::Rectangle aRect( rPolyPoly.GetBoundRect() );
181  const tools::Long nLogPixelWidth = ImplDevicePixelToLogicWidth( 1 );
182  const tools::Long nWidth = ImplDevicePixelToLogicWidth( std::max( ImplLogicWidthToDevicePixel( rHatch.GetDistance() ), tools::Long(3) ) );
183  std::unique_ptr<Point[]> pPtBuffer(new Point[ HATCH_MAXPOINTS ]);
184  Point aPt1, aPt2, aEndPt1;
185  Size aInc;
186 
187  // Single hatch
188  aRect.AdjustLeft( -nLogPixelWidth ); aRect.AdjustTop( -nLogPixelWidth ); aRect.AdjustRight(nLogPixelWidth ); aRect.AdjustBottom(nLogPixelWidth );
189  CalcHatchValues( aRect, nWidth, rHatch.GetAngle(), aPt1, aPt2, aInc, aEndPt1 );
190  if (utl::ConfigManager::IsFuzzing() && !HasSaneNSteps(aPt1, aEndPt1, aInc))
191  return;
192 
193  if (aInc.Width() <= 0 && aInc.Height() <= 0)
194  SAL_WARN("vcl.gdi", "invalid increment");
195  else
196  {
197  do
198  {
199  DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf );
200  aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() );
201  aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() );
202  }
203  while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) );
204  }
205 
206  if( ( rHatch.GetStyle() == HatchStyle::Double ) || ( rHatch.GetStyle() == HatchStyle::Triple ) )
207  {
208  // Double hatch
209  CalcHatchValues( aRect, nWidth, rHatch.GetAngle() + 900_deg10, aPt1, aPt2, aInc, aEndPt1 );
210  if (utl::ConfigManager::IsFuzzing() && !HasSaneNSteps(aPt1, aEndPt1, aInc))
211  return;
212 
213  do
214  {
215  DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf );
216  aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() );
217  aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() );
218  }
219  while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) );
220 
221  if( rHatch.GetStyle() == HatchStyle::Triple )
222  {
223  // Triple hatch
224  CalcHatchValues( aRect, nWidth, rHatch.GetAngle() + 450_deg10, aPt1, aPt2, aInc, aEndPt1 );
225  if (utl::ConfigManager::IsFuzzing() && !HasSaneNSteps(aPt1, aEndPt1, aInc))
226  return;
227 
228  do
229  {
230  DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf );
231  aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() );
232  aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() );
233  }
234  while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) );
235  }
236  }
237  }
238 }
239 
241  Point& rPt1, Point& rPt2, Size& rInc, Point& rEndPt1 )
242 {
243  Point aRef;
244  Degree10 nAngle = nAngle10 % 1800_deg10;
245  tools::Long nOffset = 0;
246 
247  if( nAngle > 900_deg10 )
248  nAngle -= 1800_deg10;
249 
250  aRef = ( !IsRefPoint() ? rRect.TopLeft() : GetRefPoint() );
251 
252  if( 0_deg10 == nAngle )
253  {
254  rInc = Size( 0, nDist );
255  rPt1 = rRect.TopLeft();
256  rPt2 = rRect.TopRight();
257  rEndPt1 = rRect.BottomLeft();
258 
259  if( aRef.Y() <= rRect.Top() )
260  nOffset = ( ( rRect.Top() - aRef.Y() ) % nDist );
261  else
262  nOffset = ( nDist - ( ( aRef.Y() - rRect.Top() ) % nDist ) );
263 
264  rPt1.AdjustY( -nOffset );
265  rPt2.AdjustY( -nOffset );
266  }
267  else if( 900_deg10 == nAngle )
268  {
269  rInc = Size( nDist, 0 );
270  rPt1 = rRect.TopLeft();
271  rPt2 = rRect.BottomLeft();
272  rEndPt1 = rRect.TopRight();
273 
274  if( aRef.X() <= rRect.Left() )
275  nOffset = ( rRect.Left() - aRef.X() ) % nDist;
276  else
277  nOffset = nDist - ( ( aRef.X() - rRect.Left() ) % nDist );
278 
279  rPt1.AdjustX( -nOffset );
280  rPt2.AdjustX( -nOffset );
281  }
282  else if( nAngle >= Degree10(-450) && nAngle <= 450_deg10 )
283  {
284  const double fAngle = std::abs( toRadians(nAngle) );
285  const double fTan = tan( fAngle );
286  const tools::Long nYOff = FRound( ( rRect.Right() - rRect.Left() ) * fTan );
287  tools::Long nPY;
288 
289  nDist = FRound( nDist / cos( fAngle ) );
290  rInc = Size( 0, nDist );
291 
292  if( nAngle > 0_deg10 )
293  {
294  rPt1 = rRect.TopLeft();
295  rPt2 = Point( rRect.Right(), rRect.Top() - nYOff );
296  rEndPt1 = Point( rRect.Left(), rRect.Bottom() + nYOff );
297  nPY = FRound( aRef.Y() - ( ( rPt1.X() - aRef.X() ) * fTan ) );
298  }
299  else
300  {
301  rPt1 = rRect.TopRight();
302  rPt2 = Point( rRect.Left(), rRect.Top() - nYOff );
303  rEndPt1 = Point( rRect.Right(), rRect.Bottom() + nYOff );
304  nPY = FRound( aRef.Y() + ( ( rPt1.X() - aRef.X() ) * fTan ) );
305  }
306 
307  if( nPY <= rPt1.Y() )
308  nOffset = ( rPt1.Y() - nPY ) % nDist;
309  else
310  nOffset = nDist - ( ( nPY - rPt1.Y() ) % nDist );
311 
312  rPt1.AdjustY( -nOffset );
313  rPt2.AdjustY( -nOffset );
314  }
315  else
316  {
317  const double fAngle = std::abs( toRadians(nAngle) );
318  const double fTan = tan( fAngle );
319  const tools::Long nXOff = FRound( ( rRect.Bottom() - rRect.Top() ) / fTan );
320  tools::Long nPX;
321 
322  nDist = FRound( nDist / sin( fAngle ) );
323  rInc = Size( nDist, 0 );
324 
325  if( nAngle > 0_deg10 )
326  {
327  rPt1 = rRect.TopLeft();
328  rPt2 = Point( rRect.Left() - nXOff, rRect.Bottom() );
329  rEndPt1 = Point( rRect.Right() + nXOff, rRect.Top() );
330  nPX = FRound( aRef.X() - ( ( rPt1.Y() - aRef.Y() ) / fTan ) );
331  }
332  else
333  {
334  rPt1 = rRect.BottomLeft();
335  rPt2 = Point( rRect.Left() - nXOff, rRect.Top() );
336  rEndPt1 = Point( rRect.Right() + nXOff, rRect.Bottom() );
337  nPX = FRound( aRef.X() + ( ( rPt1.Y() - aRef.Y() ) / fTan ) );
338  }
339 
340  if( nPX <= rPt1.X() )
341  nOffset = ( rPt1.X() - nPX ) % nDist;
342  else
343  nOffset = nDist - ( ( nPX - rPt1.X() ) % nDist );
344 
345  rPt1.AdjustX( -nOffset );
346  rPt2.AdjustX( -nOffset );
347  }
348 }
349 
350 void OutputDevice::DrawHatchLine( const tools::Line& rLine, const tools::PolyPolygon& rPolyPoly,
351  Point* pPtBuffer, bool bMtf )
352 {
353  assert(!is_double_buffered_window());
354 
355  double fX, fY;
356  tools::Long nAdd, nPCounter = 0;
357 
358  for( tools::Long nPoly = 0, nPolyCount = rPolyPoly.Count(); nPoly < nPolyCount; nPoly++ )
359  {
360  const tools::Polygon& rPoly = rPolyPoly[ static_cast<sal_uInt16>(nPoly) ];
361 
362  if( rPoly.GetSize() > 1 )
363  {
364  tools::Line aCurSegment( rPoly[ 0 ], Point() );
365 
366  for( tools::Long i = 1, nCount = rPoly.GetSize(); i <= nCount; i++ )
367  {
368  aCurSegment.SetEnd( rPoly[ static_cast<sal_uInt16>( i % nCount ) ] );
369  nAdd = 0;
370 
371  if( rLine.Intersection( aCurSegment, fX, fY ) )
372  {
373  if( ( fabs( fX - aCurSegment.GetStart().X() ) <= 0.0000001 ) &&
374  ( fabs( fY - aCurSegment.GetStart().Y() ) <= 0.0000001 ) )
375  {
376  const tools::Line aPrevSegment( rPoly[ static_cast<sal_uInt16>( ( i > 1 ) ? ( i - 2 ) : ( nCount - 1 ) ) ], aCurSegment.GetStart() );
377  const double fPrevDistance = rLine.GetDistance( aPrevSegment.GetStart() );
378  const double fCurDistance = rLine.GetDistance( aCurSegment.GetEnd() );
379 
380  if( ( fPrevDistance <= 0.0 && fCurDistance > 0.0 ) ||
381  ( fPrevDistance > 0.0 && fCurDistance < 0.0 ) )
382  {
383  nAdd = 1;
384  }
385  }
386  else if( ( fabs( fX - aCurSegment.GetEnd().X() ) <= 0.0000001 ) &&
387  ( fabs( fY - aCurSegment.GetEnd().Y() ) <= 0.0000001 ) )
388  {
389  const tools::Line aNextSegment( aCurSegment.GetEnd(), rPoly[ static_cast<sal_uInt16>( ( i + 1 ) % nCount ) ] );
390 
391  if( ( fabs( rLine.GetDistance( aNextSegment.GetEnd() ) ) <= 0.0000001 ) &&
392  ( rLine.GetDistance( aCurSegment.GetStart() ) > 0.0 ) )
393  {
394  nAdd = 1;
395  }
396  }
397  else
398  nAdd = 1;
399 
400  if( nAdd )
401  {
402  if (nPCounter == HATCH_MAXPOINTS)
403  {
404  SAL_WARN("vcl.gdi", "too many hatch points");
405  return;
406  }
407  pPtBuffer[ nPCounter++ ] = Point( FRound( fX ), FRound( fY ) );
408  }
409  }
410 
411  aCurSegment.SetStart( aCurSegment.GetEnd() );
412  }
413  }
414  }
415 
416  if( nPCounter <= 1 )
417  return;
418 
419  qsort( pPtBuffer, nPCounter, sizeof( Point ), HatchCmpFnc );
420 
421  if( nPCounter & 1 )
422  nPCounter--;
423 
424  if( bMtf )
425  {
426  for( tools::Long i = 0; i < nPCounter; i += 2 )
427  mpMetaFile->AddAction( new MetaLineAction( pPtBuffer[ i ], pPtBuffer[ i + 1 ] ) );
428  }
429  else
430  {
431  for( tools::Long i = 0; i < nPCounter; i += 2 )
432  DrawHatchLine_DrawLine(pPtBuffer[i], pPtBuffer[i+1]);
433  }
434 }
435 
436 void OutputDevice::DrawHatchLine_DrawLine(const Point& rStartPoint, const Point& rEndPoint)
437 {
438  Point aPt1{ImplLogicToDevicePixel(rStartPoint)}, aPt2{ImplLogicToDevicePixel(rEndPoint)};
439  mpGraphics->DrawLine(aPt1.X(), aPt1.Y(), aPt2.X(), aPt2.Y(), *this);
440 }
441 
442 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
sal_uInt16 Count() const
bool Intersection(const tools::Line &rLine, double &rIntersectionX, double &rIntersectionY) const
std::enable_if< std::is_signed< T >::value, bool >::type checked_sub(T a, T b, T &result)
void SetDistance(tools::Long nDistance)
Definition: gdi/hatch.cxx:69
Definition: hatch.hxx:46
SAL_DLLPRIVATE bool ImplIsRecordLayout() const
Definition: outdev.cxx:724
static bool HasSaneNSteps(const Point &rPt1, const Point &rEndPt1, const Size &rInc)
void DrawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2, const OutputDevice &rOutDev)
const Point & GetRefPoint() const
Definition: outdev.hxx:314
constexpr tools::Long Left() const
SAL_DLLPRIVATE void CalcHatchValues(const tools::Rectangle &rRect, tools::Long nDist, Degree10 nAngle10, Point &rPt1, Point &rPt2, Size &rInc, Point &rEndPt1)
long Long
const StyleSettings & GetStyleSettings() const
void Push(vcl::PushFlags nFlags=vcl::PushFlags::ALL)
Definition: stack.cxx:33
bool mbOutputClipped
Definition: outdev.hxx:252
SAL_DLLPRIVATE bool is_double_buffered_window() const
void EnableMapMode(bool bEnable=true)
Definition: map.cxx:641
bool mbMap
Definition: outdev.hxx:247
constexpr tools::Long Width() const
SAL_DLLPRIVATE void InitLineColor()
Definition: line.cxx:84
constexpr Point BottomLeft() const
static bool IsFuzzing()
int nCount
void Pop()
Definition: stack.cxx:92
const Point & GetEnd() const
HatchStyle GetStyle() const
Definition: hatch.hxx:59
static int HatchCmpFnc(const void *p1, const void *p2)
void AdaptiveSubdivide(tools::PolyPolygon &rResult) const
#define HATCH_MAXPOINTS
SalGraphics * mpGraphics
Graphics context to draw on.
Definition: outdev.hxx:188
void Optimize(PolyOptimizeFlags nOptimizeFlags)
void SetLineColor()
Definition: line.cxx:36
int i
uno_Any a
tools::Long FRound(double fVal)
SAL_DLLPRIVATE void DrawHatchLine(const tools::Line &rLine, const tools::PolyPolygon &rPolyPoly, Point *pPtBuffer, bool bMtf)
virtual bool AcquireGraphics() const =0
Acquire a graphics device that the output device uses to draw on.
const Color & GetColor() const
Definition: hatch.hxx:62
void SetEnd(const Point &rEndPt)
constexpr tools::Long Right() const
SAL_DLLPRIVATE tools::Long ImplLogicWidthToDevicePixel(tools::Long nWidth) const
Convert a logical width to a width in units of device pixels.
Definition: map.cxx:324
constexpr tools::Long Top() const
sal_uInt16 GetSize() const
double toRadians(D x)
const AllSettings & GetSettings() const
Definition: outdev.hxx:295
DrawModeFlags GetDrawMode() const
Definition: outdev.hxx:498
void DrawHatch(const tools::PolyPolygon &rPolyPoly, const Hatch &rHatch)
#define Y
virtual void DrawHatchLine_DrawLine(const Point &rStartPoint, const Point &rEndPoint)
constexpr Point TopLeft() const
constexpr tools::Long Bottom() const
SAL_WARN_UNUSED_RESULT Point LogicToPixel(const Point &rLogicPt) const
Definition: map.cxx:933
VclPtr< VirtualDevice > mpAlphaVDev
Definition: outdev.hxx:202
void SetStart(const Point &rStartPt)
bool IsRefPoint() const
Definition: outdev.hxx:315
constexpr tools::Long Height() const
void AddAction(const rtl::Reference< MetaAction > &pAction)
Definition: gdimtf.cxx:581
bool mbInitClipRegion
Definition: outdev.hxx:259
constexpr Point TopRight() const
virtual void InitClipRegion()
void AddHatchActions(const tools::PolyPolygon &rPolyPoly, const Hatch &rHatch, GDIMetaFile &rMtf)
tools::Long GetDistance() const
Definition: hatch.hxx:65
SAL_DLLPRIVATE tools::Rectangle ImplLogicToDevicePixel(const tools::Rectangle &rLogicRect) const
Convert a logical rectangle to a rectangle in physical device pixel units.
Definition: map.cxx:386
Color GetHatchColor(Color const &rColor, DrawModeFlags nDrawMode, StyleSettings const &rStyleSettings)
Definition: drawmode.cxx:102
tools::Rectangle GetBoundRect() const
#define SAL_WARN(area, stream)
double GetDistance(const double &rPtX, const double &rPtY) const
Degree10 GetAngle() const
Definition: hatch.hxx:68
const Point & GetStart() const
SAL_DLLPRIVATE tools::Long ImplDevicePixelToLogicWidth(tools::Long nWidth) const
Convert device pixels to a width in logical units.
Definition: map.cxx:348
bool IsDeviceOutputNecessary() const
Definition: outdev.hxx:488
void SetColor(const Color &rColor)
Definition: gdi/hatch.cxx:64
GDIMetaFile * mpMetaFile
Definition: outdev.hxx:191