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