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