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