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 
22 #include <sal/types.h>
23 
24 #include <basegfx/matrix/b2dhommatrix.hxx>
25 #include <basegfx/polygon/b2dlinegeometry.hxx>
26 #include <vcl/gdimtf.hxx>
27 #include <vcl/metaact.hxx>
28 #include <vcl/outdev.hxx>
29 #include <vcl/virdev.hxx>
30 
31 #include <salgdi.hxx>
32 
DrawPolyLine(const tools::Polygon & rPoly)33 void OutputDevice::DrawPolyLine( const tools::Polygon& rPoly )
34 {
35     assert(!is_double_buffered_window());
36 
37     if( mpMetaFile )
38         mpMetaFile->AddAction( new MetaPolyLineAction( rPoly ) );
39 
40     sal_uInt16 nPoints = rPoly.GetSize();
41 
42     if ( !IsDeviceOutputNecessary() || !mbLineColor || (nPoints < 2) || ImplIsRecordLayout() )
43         return;
44 
45     // we need a graphics
46     if ( !mpGraphics && !AcquireGraphics() )
47         return;
48     assert(mpGraphics);
49 
50     if ( mbInitClipRegion )
51         InitClipRegion();
52 
53     if ( mbOutputClipped )
54         return;
55 
56     if ( mbInitLineColor )
57         InitLineColor();
58 
59     // use b2dpolygon drawing if possible
60     if(DrawPolyLineDirectInternal(
61         basegfx::B2DHomMatrix(),
62         rPoly.getB2DPolygon()))
63     {
64         return;
65     }
66 
67     const basegfx::B2DPolygon aB2DPolyLine(rPoly.getB2DPolygon());
68     const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
69     const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
70 
71     bool bDrawn = mpGraphics->DrawPolyLine(
72         aTransform,
73         aB2DPolyLine,
74         0.0,
75         0.0, // tdf#124848 hairline
76         nullptr, // MM01
77         basegfx::B2DLineJoin::NONE,
78         css::drawing::LineCap_BUTT,
79         basegfx::deg2rad(15.0) /*default fMiterMinimumAngle, not used*/,
80         bPixelSnapHairline,
81         *this);
82 
83     if(!bDrawn)
84     {
85         tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly );
86         Point* pPtAry = aPoly.GetPointAry();
87 
88         // #100127# Forward beziers to sal, if any
89         if( aPoly.HasFlags() )
90         {
91             const PolyFlags* pFlgAry = aPoly.GetConstFlagAry();
92             if( !mpGraphics->DrawPolyLineBezier( nPoints, pPtAry, pFlgAry, *this ) )
93             {
94                 aPoly = tools::Polygon::SubdivideBezier(aPoly);
95                 pPtAry = aPoly.GetPointAry();
96                 mpGraphics->DrawPolyLine( aPoly.GetSize(), pPtAry, *this );
97             }
98         }
99         else
100         {
101             mpGraphics->DrawPolyLine( nPoints, pPtAry, *this );
102         }
103     }
104 
105     if( mpAlphaVDev )
106         mpAlphaVDev->DrawPolyLine( rPoly );
107 }
108 
DrawPolyLine(const tools::Polygon & rPoly,const LineInfo & rLineInfo)109 void OutputDevice::DrawPolyLine( const tools::Polygon& rPoly, const LineInfo& rLineInfo )
110 {
111     assert(!is_double_buffered_window());
112 
113     if ( rLineInfo.IsDefault() )
114     {
115         DrawPolyLine( rPoly );
116         return;
117     }
118 
119     // #i101491#
120     // Try direct Fallback to B2D-Version of DrawPolyLine
121     if(LineStyle::Solid == rLineInfo.GetStyle())
122     {
123         DrawPolyLine(
124             rPoly.getB2DPolygon(),
125             rLineInfo.GetWidth(),
126             rLineInfo.GetLineJoin(),
127             rLineInfo.GetLineCap(),
128             basegfx::deg2rad(15.0) /* default fMiterMinimumAngle, value not available in LineInfo */);
129         return;
130     }
131 
132     if ( mpMetaFile )
133         mpMetaFile->AddAction( new MetaPolyLineAction( rPoly, rLineInfo ) );
134 
135     drawPolyLine(rPoly, rLineInfo);
136 }
137 
DrawPolyLine(const basegfx::B2DPolygon & rB2DPolygon,double fLineWidth,basegfx::B2DLineJoin eLineJoin,css::drawing::LineCap eLineCap,double fMiterMinimumAngle)138 void OutputDevice::DrawPolyLine( const basegfx::B2DPolygon& rB2DPolygon,
139                                  double fLineWidth,
140                                  basegfx::B2DLineJoin eLineJoin,
141                                  css::drawing::LineCap eLineCap,
142                                  double fMiterMinimumAngle)
143 {
144     assert(!is_double_buffered_window());
145 
146     if( mpMetaFile )
147     {
148         LineInfo aLineInfo;
149         if( fLineWidth != 0.0 )
150             aLineInfo.SetWidth( fLineWidth );
151 
152         const tools::Polygon aToolsPolygon( rB2DPolygon );
153         mpMetaFile->AddAction( new MetaPolyLineAction( aToolsPolygon, aLineInfo ) );
154     }
155 
156     // Do not paint empty PolyPolygons
157     if(!rB2DPolygon.count() || !IsDeviceOutputNecessary())
158         return;
159 
160     // we need a graphics
161     if( !mpGraphics && !AcquireGraphics() )
162         return;
163     assert(mpGraphics);
164 
165     if( mbInitClipRegion )
166         InitClipRegion();
167 
168     if( mbOutputClipped )
169         return;
170 
171     if( mbInitLineColor )
172         InitLineColor();
173 
174     // use b2dpolygon drawing if possible
175     if(DrawPolyLineDirectInternal(
176         basegfx::B2DHomMatrix(),
177         rB2DPolygon,
178         fLineWidth,
179         0.0,
180         nullptr, // MM01
181         eLineJoin,
182         eLineCap,
183         fMiterMinimumAngle))
184     {
185         return;
186     }
187 
188     // #i101491#
189     // no output yet; fallback to geometry decomposition and use filled polygon paint
190     // when line is fat and not too complex. ImplDrawPolyPolygonWithB2DPolyPolygon
191     // will do internal needed AA checks etc.
192     if(fLineWidth >= 2.5 &&
193        rB2DPolygon.count() &&
194        rB2DPolygon.count() <= 1000)
195     {
196         const double fHalfLineWidth((fLineWidth * 0.5) + 0.5);
197         const basegfx::B2DPolyPolygon aAreaPolyPolygon(
198                 basegfx::utils::createAreaGeometry( rB2DPolygon,
199                                                     fHalfLineWidth,
200                                                     eLineJoin,
201                                                     eLineCap,
202                                                     fMiterMinimumAngle));
203         const Color aOldLineColor(maLineColor);
204         const Color aOldFillColor(maFillColor);
205 
206         SetLineColor();
207         InitLineColor();
208         SetFillColor(aOldLineColor);
209         InitFillColor();
210 
211         // draw using a loop; else the topology will paint a PolyPolygon
212         for(auto const& rPolygon : aAreaPolyPolygon)
213         {
214             ImplDrawPolyPolygonWithB2DPolyPolygon(
215                 basegfx::B2DPolyPolygon(rPolygon));
216         }
217 
218         SetLineColor(aOldLineColor);
219         InitLineColor();
220         SetFillColor(aOldFillColor);
221         InitFillColor();
222 
223         // when AA it is necessary to also paint the filled polygon's outline
224         // to avoid optical gaps
225         for(auto const& rPolygon : aAreaPolyPolygon)
226         {
227             (void)DrawPolyLineDirectInternal(
228                 basegfx::B2DHomMatrix(),
229                 rPolygon);
230         }
231     }
232     else
233     {
234         // fallback to old polygon drawing if needed
235         const tools::Polygon aToolsPolygon( rB2DPolygon );
236         LineInfo aLineInfo;
237         if( fLineWidth != 0.0 )
238             aLineInfo.SetWidth( fLineWidth );
239 
240         drawPolyLine( aToolsPolygon, aLineInfo );
241     }
242 }
243 
drawPolyLine(const tools::Polygon & rPoly,const LineInfo & rLineInfo)244 void OutputDevice::drawPolyLine(const tools::Polygon& rPoly, const LineInfo& rLineInfo)
245 {
246     sal_uInt16 nPoints(rPoly.GetSize());
247 
248     if ( !IsDeviceOutputNecessary() || !mbLineColor || ( nPoints < 2 ) || ( LineStyle::NONE == rLineInfo.GetStyle() ) || ImplIsRecordLayout() )
249         return;
250 
251     tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly );
252 
253     // we need a graphics
254     if ( !mpGraphics && !AcquireGraphics() )
255         return;
256     assert(mpGraphics);
257 
258     if ( mbInitClipRegion )
259         InitClipRegion();
260 
261     if ( mbOutputClipped )
262         return;
263 
264     if ( mbInitLineColor )
265         InitLineColor();
266 
267     const LineInfo aInfo( ImplLogicToDevicePixel( rLineInfo ) );
268     const bool bDashUsed(LineStyle::Dash == aInfo.GetStyle());
269     const bool bLineWidthUsed(aInfo.GetWidth() > 1);
270 
271     if(bDashUsed || bLineWidthUsed)
272     {
273         drawLine ( basegfx::B2DPolyPolygon(aPoly.getB2DPolygon()), aInfo );
274     }
275     else
276     {
277         // #100127# the subdivision HAS to be done here since only a pointer
278         // to an array of points is given to the DrawPolyLine method, there is
279         // NO way to find out there that it's a curve.
280         if( aPoly.HasFlags() )
281         {
282             aPoly = tools::Polygon::SubdivideBezier( aPoly );
283             nPoints = aPoly.GetSize();
284         }
285 
286         mpGraphics->DrawPolyLine(nPoints, aPoly.GetPointAry(), *this);
287     }
288 
289     if( mpAlphaVDev )
290         mpAlphaVDev->DrawPolyLine( rPoly, rLineInfo );
291 }
292 
DrawPolyLineDirect(const basegfx::B2DHomMatrix & rObjectTransform,const basegfx::B2DPolygon & rB2DPolygon,double fLineWidth,double fTransparency,const std::vector<double> * pStroke,basegfx::B2DLineJoin eLineJoin,css::drawing::LineCap eLineCap,double fMiterMinimumAngle)293 bool OutputDevice::DrawPolyLineDirect(
294     const basegfx::B2DHomMatrix& rObjectTransform,
295     const basegfx::B2DPolygon& rB2DPolygon,
296     double fLineWidth,
297     double fTransparency,
298     const std::vector< double >* pStroke, // MM01
299     basegfx::B2DLineJoin eLineJoin,
300     css::drawing::LineCap eLineCap,
301     double fMiterMinimumAngle)
302 {
303     if(DrawPolyLineDirectInternal(rObjectTransform, rB2DPolygon, fLineWidth, fTransparency,
304         pStroke, eLineJoin, eLineCap, fMiterMinimumAngle))
305     {
306         // Worked, add metafile action (if recorded). This is done only here,
307         // because this function is public, other OutDev functions already add metafile
308         // actions, so they call the internal function directly.
309         if( mpMetaFile )
310         {
311             LineInfo aLineInfo;
312             if( fLineWidth != 0.0 )
313                 aLineInfo.SetWidth( fLineWidth );
314             // Transport known information, might be needed
315             aLineInfo.SetLineJoin(eLineJoin);
316             aLineInfo.SetLineCap(eLineCap);
317             // MiterMinimumAngle does not exist yet in LineInfo
318             const tools::Polygon aToolsPolygon( rB2DPolygon );
319             mpMetaFile->AddAction( new MetaPolyLineAction( aToolsPolygon, aLineInfo ) );
320         }
321         return true;
322     }
323     return false;
324 }
325 
DrawPolyLineDirectInternal(const basegfx::B2DHomMatrix & rObjectTransform,const basegfx::B2DPolygon & rB2DPolygon,double fLineWidth,double fTransparency,const std::vector<double> * pStroke,basegfx::B2DLineJoin eLineJoin,css::drawing::LineCap eLineCap,double fMiterMinimumAngle)326 bool OutputDevice::DrawPolyLineDirectInternal(
327     const basegfx::B2DHomMatrix& rObjectTransform,
328     const basegfx::B2DPolygon& rB2DPolygon,
329     double fLineWidth,
330     double fTransparency,
331     const std::vector< double >* pStroke, // MM01
332     basegfx::B2DLineJoin eLineJoin,
333     css::drawing::LineCap eLineCap,
334     double fMiterMinimumAngle)
335 {
336     assert(!is_double_buffered_window());
337 
338     // AW: Do NOT paint empty PolyPolygons
339     if(!rB2DPolygon.count())
340         return true;
341 
342     // we need a graphics
343     if( !mpGraphics && !AcquireGraphics() )
344         return false;
345     assert(mpGraphics);
346 
347     if( mbInitClipRegion )
348         InitClipRegion();
349 
350     if( mbOutputClipped )
351         return true;
352 
353     if( mbInitLineColor )
354         InitLineColor();
355 
356     const bool bTryB2d(mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) &&
357                       RasterOp::OverPaint == GetRasterOp() &&
358                       IsLineColor());
359 
360     if(bTryB2d)
361     {
362         // combine rObjectTransform with WorldToDevice
363         const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation() * rObjectTransform);
364         const bool bPixelSnapHairline((mnAntialiasing & AntialiasingFlags::PixelSnapHairline) && rB2DPolygon.count() < 1000);
365 
366         const double fAdjustedTransparency = mpAlphaVDev ? 0 : fTransparency;
367         // draw the polyline
368         bool bDrawSuccess = mpGraphics->DrawPolyLine(
369             aTransform,
370             rB2DPolygon,
371             fAdjustedTransparency,
372             fLineWidth, // tdf#124848 use LineWidth direct, do not try to solve for zero-case (aka hairline)
373             pStroke, // MM01
374             eLineJoin,
375             eLineCap,
376             fMiterMinimumAngle,
377             bPixelSnapHairline,
378             *this);
379 
380         if( bDrawSuccess )
381         {
382             if (mpAlphaVDev)
383                 mpAlphaVDev->DrawPolyLineDirect(rObjectTransform, rB2DPolygon, fLineWidth,
384                                                 fTransparency, pStroke, eLineJoin, eLineCap,
385                                                 fMiterMinimumAngle);
386 
387             return true;
388         }
389     }
390     return false;
391 }
392 
393 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
394