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 "vclprocessor2d.hxx"
21 
22 #include "getdigitlanguage.hxx"
23 #include "vclhelperbufferdevice.hxx"
24 #include <cmath>
25 #include <comphelper/string.hxx>
26 #include <comphelper/lok.hxx>
27 #include <svtools/optionsdrawinglayer.hxx>
28 #include <tools/debug.hxx>
29 #include <tools/fract.hxx>
30 #include <utility>
31 #include <vcl/glyphitemcache.hxx>
32 #include <vcl/graph.hxx>
33 #include <vcl/kernarray.hxx>
34 #include <vcl/outdev.hxx>
35 #include <rtl/ustrbuf.hxx>
36 #include <sal/log.hxx>
37 #include <toolkit/helper/vclunohelper.hxx>
38 #include <basegfx/polygon/b2dpolygontools.hxx>
39 #include <basegfx/polygon/b2dpolypolygontools.hxx>
40 #include <basegfx/polygon/b2dpolygonclipper.hxx>
41 #include <basegfx/color/bcolor.hxx>
42 #include <basegfx/matrix/b2dhommatrixtools.hxx>
43 #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
44 #include <drawinglayer/primitive2d/textprimitive2d.hxx>
45 #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
46 #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
47 #include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx>
48 #include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
49 #include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
50 #include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx>
51 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
52 #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
53 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
54 #include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
55 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
56 #include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx>
57 #include <drawinglayer/primitive2d/pagepreviewprimitive2d.hxx>
58 #include <drawinglayer/primitive2d/textenumsprimitive2d.hxx>
59 #include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
60 // for support of Title/Description in all apps when embedding pictures
61 #include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx>
62 // control support
63 #include <drawinglayer/primitive2d/textlayoutdevice.hxx>
64 
65 #include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
66 #include <drawinglayer/primitive2d/epsprimitive2d.hxx>
67 
68 using namespace com::sun::star;
69 
70 namespace
71 {
72 sal_uInt32 calculateStepsForSvgGradient(const basegfx::BColor& rColorA,
73                                         const basegfx::BColor& rColorB, double fDelta,
74                                         double fDiscreteUnit)
75 {
76     // use color distance, assume to do every color step
77     sal_uInt32 nSteps(basegfx::fround(rColorA.getDistance(rColorB) * 255.0));
78 
79     if (nSteps)
80     {
81         // calc discrete length to change color each discrete unit (pixel)
82         const sal_uInt32 nDistSteps(basegfx::fround(fDelta / fDiscreteUnit));
83 
84         nSteps = std::min(nSteps, nDistSteps);
85     }
86 
87     // reduce quality to 3 discrete units or every 3rd color step for rendering
88     nSteps /= 2;
89 
90     // roughly cut when too big or too small (not full quality, reduce complexity)
91     nSteps = std::min(nSteps, sal_uInt32(255));
92     nSteps = std::max(nSteps, sal_uInt32(1));
93 
94     return nSteps;
95 }
96 }
97 
98 namespace
99 {
100 /** helper to convert a MapMode to a transformation */
101 basegfx::B2DHomMatrix getTransformFromMapMode(const MapMode& rMapMode)
102 {
103     basegfx::B2DHomMatrix aMapping;
104     const Fraction aNoScale(1, 1);
105     const Point& rOrigin(rMapMode.GetOrigin());
106 
107     if (0 != rOrigin.X() || 0 != rOrigin.Y())
108     {
109         aMapping.translate(rOrigin.X(), rOrigin.Y());
110     }
111 
112     if (rMapMode.GetScaleX() != aNoScale || rMapMode.GetScaleY() != aNoScale)
113     {
114         aMapping.scale(double(rMapMode.GetScaleX()), double(rMapMode.GetScaleY()));
115     }
116 
117     return aMapping;
118 }
119 }
120 
121 namespace drawinglayer::processor2d
122 {
123 // rendering support
124 
125 // directdraw of text simple portion or decorated portion primitive. When decorated, all the extra
126 // information is translated to VCL parameters and set at the font.
127 // Acceptance is restricted to no shearing and positive scaling in X and Y (no font mirroring
128 // for VCL)
129 void VclProcessor2D::RenderTextSimpleOrDecoratedPortionPrimitive2D(
130     const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate)
131 {
132     // decompose matrix to have position and size of text
133     basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation
134                                           * rTextCandidate.getTextTransform());
135     basegfx::B2DVector aFontScaling, aTranslate;
136     double fRotate, fShearX;
137     aLocalTransform.decompose(aFontScaling, aTranslate, fRotate, fShearX);
138 
139     bool bPrimitiveAccepted(false);
140 
141     // tdf#95581: Assume tiny shears are rounding artefacts or whatever and can be ignored,
142     // especially if the effect is less than a pixel.
143     if (std::abs(aFontScaling.getY() * fShearX) < 1)
144     {
145         if (aFontScaling.getX() < 0.0 && aFontScaling.getY() < 0.0)
146         {
147             // handle special case: If scale is negative in (x,y) (3rd quadrant), it can
148             // be expressed as rotation by PI. Use this since the Font rendering will not
149             // apply the negative scales in any form
150             aFontScaling = basegfx::absolute(aFontScaling);
151             fRotate += M_PI;
152         }
153 
154         if (aFontScaling.getX() > 0.0 && aFontScaling.getY() > 0.0)
155         {
156             double fIgnoreRotate, fIgnoreShearX;
157 
158             basegfx::B2DVector aFontSize, aTextTranslate;
159             rTextCandidate.getTextTransform().decompose(aFontSize, aTextTranslate, fIgnoreRotate,
160                                                         fIgnoreShearX);
161 
162             // tdf#153092 Ideally we don't have to scale the font and dxarray, but we might have
163             // to nevertheless if dealing with non integer sizes
164             const bool bScaleFont(aFontSize.getY() != std::round(aFontSize.getY())
165                                   || comphelper::LibreOfficeKit::isActive());
166             vcl::Font aFont;
167 
168             // Get the VCL font
169             if (!bScaleFont)
170             {
171                 aFont = primitive2d::getVclFontFromFontAttribute(
172                     rTextCandidate.getFontAttribute(), aFontSize.getX(), aFontSize.getY(), fRotate,
173                     rTextCandidate.getLocale());
174             }
175             else
176             {
177                 aFont = primitive2d::getVclFontFromFontAttribute(
178                     rTextCandidate.getFontAttribute(), aFontScaling.getX(), aFontScaling.getY(),
179                     fRotate, rTextCandidate.getLocale());
180             }
181 
182             // Don't draw fonts without height
183             Size aResultFontSize = aFont.GetFontSize();
184             if (aResultFontSize.Height() <= 0)
185                 return;
186 
187             // set FillColor Attribute
188             const Color aFillColor(rTextCandidate.getTextFillColor());
189             aFont.SetTransparent(aFillColor.IsTransparent());
190             aFont.SetFillColor(aFillColor);
191 
192             // handle additional font attributes
193             const primitive2d::TextDecoratedPortionPrimitive2D* pTCPP = nullptr;
194             if (rTextCandidate.getPrimitive2DID() == PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D)
195                 pTCPP = static_cast<const primitive2d::TextDecoratedPortionPrimitive2D*>(
196                     &rTextCandidate);
197 
198             if (pTCPP != nullptr)
199             {
200                 // set the color of text decorations
201                 const basegfx::BColor aTextlineColor
202                     = maBColorModifierStack.getModifiedColor(pTCPP->getTextlineColor());
203                 mpOutputDevice->SetTextLineColor(Color(aTextlineColor));
204 
205                 // set Overline attribute
206                 const FontLineStyle eFontOverline(
207                     primitive2d::mapTextLineToFontLineStyle(pTCPP->getFontOverline()));
208                 if (eFontOverline != LINESTYLE_NONE)
209                 {
210                     aFont.SetOverline(eFontOverline);
211                     const basegfx::BColor aOverlineColor
212                         = maBColorModifierStack.getModifiedColor(pTCPP->getOverlineColor());
213                     mpOutputDevice->SetOverlineColor(Color(aOverlineColor));
214                     if (pTCPP->getWordLineMode())
215                         aFont.SetWordLineMode(true);
216                 }
217 
218                 // set Underline attribute
219                 const FontLineStyle eFontLineStyle(
220                     primitive2d::mapTextLineToFontLineStyle(pTCPP->getFontUnderline()));
221                 if (eFontLineStyle != LINESTYLE_NONE)
222                 {
223                     aFont.SetUnderline(eFontLineStyle);
224                     if (pTCPP->getWordLineMode())
225                         aFont.SetWordLineMode(true);
226                 }
227 
228                 // set Strikeout attribute
229                 const FontStrikeout eFontStrikeout(
230                     primitive2d::mapTextStrikeoutToFontStrikeout(pTCPP->getTextStrikeout()));
231 
232                 if (eFontStrikeout != STRIKEOUT_NONE)
233                     aFont.SetStrikeout(eFontStrikeout);
234 
235                 // set EmphasisMark attribute
236                 FontEmphasisMark eFontEmphasisMark = FontEmphasisMark::NONE;
237                 switch (pTCPP->getTextEmphasisMark())
238                 {
239                     default:
240                         SAL_WARN("drawinglayer",
241                                  "Unknown EmphasisMark style " << pTCPP->getTextEmphasisMark());
242                         [[fallthrough]];
243                     case primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE:
244                         eFontEmphasisMark = FontEmphasisMark::NONE;
245                         break;
246                     case primitive2d::TEXT_FONT_EMPHASIS_MARK_DOT:
247                         eFontEmphasisMark = FontEmphasisMark::Dot;
248                         break;
249                     case primitive2d::TEXT_FONT_EMPHASIS_MARK_CIRCLE:
250                         eFontEmphasisMark = FontEmphasisMark::Circle;
251                         break;
252                     case primitive2d::TEXT_FONT_EMPHASIS_MARK_DISC:
253                         eFontEmphasisMark = FontEmphasisMark::Disc;
254                         break;
255                     case primitive2d::TEXT_FONT_EMPHASIS_MARK_ACCENT:
256                         eFontEmphasisMark = FontEmphasisMark::Accent;
257                         break;
258                 }
259 
260                 if (eFontEmphasisMark != FontEmphasisMark::NONE)
261                 {
262                     DBG_ASSERT((pTCPP->getEmphasisMarkAbove() != pTCPP->getEmphasisMarkBelow()),
263                                "DrawingLayer: Bad EmphasisMark position!");
264                     if (pTCPP->getEmphasisMarkAbove())
265                         eFontEmphasisMark |= FontEmphasisMark::PosAbove;
266                     else
267                         eFontEmphasisMark |= FontEmphasisMark::PosBelow;
268                     aFont.SetEmphasisMark(eFontEmphasisMark);
269                 }
270 
271                 // set Relief attribute
272                 FontRelief eFontRelief = FontRelief::NONE;
273                 switch (pTCPP->getTextRelief())
274                 {
275                     default:
276                         SAL_WARN("drawinglayer", "Unknown Relief style " << pTCPP->getTextRelief());
277                         [[fallthrough]];
278                     case primitive2d::TEXT_RELIEF_NONE:
279                         eFontRelief = FontRelief::NONE;
280                         break;
281                     case primitive2d::TEXT_RELIEF_EMBOSSED:
282                         eFontRelief = FontRelief::Embossed;
283                         break;
284                     case primitive2d::TEXT_RELIEF_ENGRAVED:
285                         eFontRelief = FontRelief::Engraved;
286                         break;
287                 }
288 
289                 if (eFontRelief != FontRelief::NONE)
290                     aFont.SetRelief(eFontRelief);
291 
292                 // set Shadow attribute
293                 if (pTCPP->getShadow())
294                     aFont.SetShadow(true);
295             }
296 
297             // create integer DXArray
298             KernArray aDXArray;
299 
300             if (!rTextCandidate.getDXArray().empty())
301             {
302                 double fPixelVectorFactor(1.0);
303                 if (bScaleFont)
304                 {
305                     const basegfx::B2DVector aPixelVector(maCurrentTransformation
306                                                           * basegfx::B2DVector(1.0, 0.0));
307                     fPixelVectorFactor = aPixelVector.getLength();
308                 }
309 
310                 aDXArray.reserve(rTextCandidate.getDXArray().size());
311                 for (auto const& elem : rTextCandidate.getDXArray())
312                     aDXArray.push_back(basegfx::fround(elem * fPixelVectorFactor));
313             }
314 
315             // set parameters and paint text snippet
316             const basegfx::BColor aRGBFontColor(
317                 maBColorModifierStack.getModifiedColor(rTextCandidate.getFontColor()));
318             const vcl::text::ComplexTextLayoutFlags nOldLayoutMode(mpOutputDevice->GetLayoutMode());
319 
320             if (rTextCandidate.getFontAttribute().getRTL())
321             {
322                 vcl::text::ComplexTextLayoutFlags nRTLLayoutMode(
323                     nOldLayoutMode & ~vcl::text::ComplexTextLayoutFlags::BiDiStrong);
324                 nRTLLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl
325                                   | vcl::text::ComplexTextLayoutFlags::TextOriginLeft;
326                 mpOutputDevice->SetLayoutMode(nRTLLayoutMode);
327             }
328 
329             OUString aText(rTextCandidate.getText());
330             sal_Int32 nPos = rTextCandidate.getTextPosition();
331             sal_Int32 nLen = rTextCandidate.getTextLength();
332 
333             // this contraption is used in editeng, with format paragraph used to
334             // set a tab with a tab-fill character
335             if (rTextCandidate.isFilled())
336             {
337                 tools::Long nWidthToFill = rTextCandidate.getWidthToFill();
338 
339                 tools::Long nWidth = basegfx::fround<tools::Long>(
340                     mpOutputDevice->GetTextArray(rTextCandidate.getText(), &aDXArray, 0, 1));
341                 sal_Int32 nChars = 2;
342                 if (nWidth)
343                     nChars = nWidthToFill / nWidth;
344 
345                 OUStringBuffer aFilled(nChars);
346                 comphelper::string::padToLength(aFilled, nChars, aText[0]);
347                 aText = aFilled.makeStringAndClear();
348                 nPos = 0;
349                 nLen = nChars;
350 
351                 if (!aDXArray.empty())
352                 {
353                     sal_Int32 nDX = aDXArray[0];
354                     aDXArray.resize(nLen);
355                     for (sal_Int32 i = 1; i < nLen; ++i)
356                         aDXArray.set(i, aDXArray[i - 1] + nDX);
357                 }
358             }
359 
360             Point aStartPoint;
361             bool bChangeMapMode(false);
362             if (!bScaleFont)
363             {
364                 basegfx::B2DHomMatrix aCombinedTransform(
365                     getTransformFromMapMode(mpOutputDevice->GetMapMode())
366                     * maCurrentTransformation);
367 
368                 basegfx::B2DVector aCurrentScaling, aCurrentTranslate;
369                 double fCurrentRotate;
370                 aCombinedTransform.decompose(aCurrentScaling, aCurrentTranslate, fCurrentRotate,
371                                              fIgnoreShearX);
372 
373                 const Point aOrigin(
374                     basegfx::fround<tools::Long>(aCurrentTranslate.getX() / aCurrentScaling.getX()),
375                     basegfx::fround<tools::Long>(aCurrentTranslate.getY()
376                                                  / aCurrentScaling.getY()));
377 
378                 Fraction aScaleX(aCurrentScaling.getX());
379                 if (!aScaleX.IsValid())
380                 {
381                     SAL_WARN("drawinglayer", "invalid X Scale");
382                     return;
383                 }
384 
385                 Fraction aScaleY(aCurrentScaling.getY());
386                 if (!aScaleY.IsValid())
387                 {
388                     SAL_WARN("drawinglayer", "invalid Y Scale");
389                     return;
390                 }
391 
392                 MapMode aMapMode(mpOutputDevice->GetMapMode().GetMapUnit(), aOrigin, aScaleX,
393                                  aScaleY);
394 
395                 if (fCurrentRotate)
396                     aTextTranslate *= basegfx::utils::createRotateB2DHomMatrix(fCurrentRotate);
397                 aStartPoint = Point(basegfx::fround<tools::Long>(aTextTranslate.getX()),
398                                     basegfx::fround<tools::Long>(aTextTranslate.getY()));
399 
400                 bChangeMapMode = aMapMode != mpOutputDevice->GetMapMode();
401                 if (bChangeMapMode)
402                 {
403                     mpOutputDevice->Push(vcl::PushFlags::MAPMODE);
404                     mpOutputDevice->SetRelativeMapMode(aMapMode);
405                 }
406             }
407             else
408             {
409                 const basegfx::B2DPoint aPoint(aLocalTransform * basegfx::B2DPoint(0.0, 0.0));
410                 double aPointX = aPoint.getX(), aPointY = aPoint.getY();
411 
412                 // aFont has an integer size; we must scale a bit for precision
413                 double nFontScalingFixY = aFontScaling.getY() / aResultFontSize.Height();
414                 double nFontScalingFixX = aFontScaling.getX()
415                                           / (aResultFontSize.Width() ? aResultFontSize.Width()
416                                                                      : aResultFontSize.Height());
417 
418                 if (!rtl_math_approxEqual(nFontScalingFixY, 1.0)
419                     || !rtl_math_approxEqual(nFontScalingFixX, 1.0))
420                 {
421                     MapMode aMapMode = mpOutputDevice->GetMapMode();
422                     aMapMode.SetScaleX(aMapMode.GetScaleX() * nFontScalingFixX);
423                     aMapMode.SetScaleY(aMapMode.GetScaleY() * nFontScalingFixY);
424 
425                     mpOutputDevice->Push(vcl::PushFlags::MAPMODE);
426                     mpOutputDevice->SetRelativeMapMode(aMapMode);
427                     bChangeMapMode = true;
428 
429                     aPointX /= nFontScalingFixX;
430                     aPointY /= nFontScalingFixY;
431                 }
432 
433                 aStartPoint = Point(basegfx::fround<tools::Long>(aPointX),
434                                     basegfx::fround<tools::Long>(aPointY));
435             }
436 
437             // tdf#152990 set the font after the MapMode is (potentially) set so canvas uses the desired
438             // font size
439             mpOutputDevice->SetFont(aFont);
440             mpOutputDevice->SetTextColor(Color(aRGBFontColor));
441 
442             {
443                 // For D2DWriteTextOutRenderer, we must pass a flag to not use font scaling
444                 auto guard = mpOutputDevice->ScopedNoFontScaling();
445                 if (!aDXArray.empty())
446                 {
447                     const SalLayoutGlyphs* pGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
448                         mpOutputDevice, aText, nPos, nLen);
449                     mpOutputDevice->DrawTextArray(aStartPoint, aText, aDXArray,
450                                                   rTextCandidate.getKashidaArray(), nPos, nLen,
451                                                   SalLayoutFlags::NONE, pGlyphs);
452                 }
453                 else
454                 {
455                     mpOutputDevice->DrawText(aStartPoint, aText, nPos, nLen);
456                 }
457             }
458 
459             if (rTextCandidate.getFontAttribute().getRTL())
460             {
461                 mpOutputDevice->SetLayoutMode(nOldLayoutMode);
462             }
463 
464             if (bChangeMapMode)
465                 mpOutputDevice->Pop();
466 
467             bPrimitiveAccepted = true;
468         }
469     }
470 
471     if (!bPrimitiveAccepted)
472     {
473         // let break down
474         process(rTextCandidate);
475     }
476 }
477 
478 // direct draw of hairline
479 void VclProcessor2D::RenderPolygonHairlinePrimitive2D(
480     const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate, bool bPixelBased)
481 {
482     const basegfx::BColor aHairlineColor(
483         maBColorModifierStack.getModifiedColor(rPolygonCandidate.getBColor()));
484     mpOutputDevice->SetLineColor(Color(aHairlineColor));
485     mpOutputDevice->SetFillColor();
486 
487     basegfx::B2DPolygon aLocalPolygon(rPolygonCandidate.getB2DPolygon());
488     aLocalPolygon.transform(maCurrentTransformation);
489 
490     if (bPixelBased && getViewInformation2D().getPixelSnapHairline())
491     {
492         // #i98289#
493         // when a Hairline is painted and AntiAliasing is on the option SnapHorVerLinesToDiscrete
494         // allows to suppress AntiAliasing for pure horizontal or vertical lines. This is done since
495         // not-AntiAliased such lines look more pleasing to the eye (e.g. 2D chart content). This
496         // NEEDS to be done in discrete coordinates, so only useful for pixel based rendering.
497         aLocalPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aLocalPolygon);
498     }
499 
500     mpOutputDevice->DrawPolyLine(aLocalPolygon, 0.0);
501 }
502 
503 // direct draw of transformed BitmapEx primitive
504 void VclProcessor2D::RenderBitmapPrimitive2D(const primitive2d::BitmapPrimitive2D& rBitmapCandidate)
505 {
506     BitmapEx aBitmapEx(rBitmapCandidate.getBitmap());
507     const basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation
508                                                 * rBitmapCandidate.getTransform());
509 
510     if (maBColorModifierStack.count())
511     {
512         aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack);
513 
514         if (aBitmapEx.IsEmpty())
515         {
516             // color gets completely replaced, get it
517             const basegfx::BColor aModifiedColor(
518                 maBColorModifierStack.getModifiedColor(basegfx::BColor()));
519             basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon());
520             aPolygon.transform(aLocalTransform);
521 
522             mpOutputDevice->SetFillColor(Color(aModifiedColor));
523             mpOutputDevice->SetLineColor();
524             mpOutputDevice->DrawPolygon(aPolygon);
525 
526             return;
527         }
528     }
529 
530     // #122923# do no longer add Alpha channel here; the right place to do this is when really
531     // the own transformer is used (see OutputDevice::DrawTransformedBitmapEx).
532 
533     // draw using OutputDevice'sDrawTransformedBitmapEx
534     mpOutputDevice->DrawTransformedBitmapEx(aLocalTransform, aBitmapEx);
535 }
536 
537 void VclProcessor2D::RenderFillGraphicPrimitive2D(
538     const primitive2d::FillGraphicPrimitive2D& rFillBitmapCandidate)
539 {
540     bool bPrimitiveAccepted = RenderFillGraphicPrimitive2DImpl(rFillBitmapCandidate);
541 
542     if (!bPrimitiveAccepted)
543     {
544         // do not accept, use decomposition
545         process(rFillBitmapCandidate);
546     }
547 }
548 
549 bool VclProcessor2D::RenderFillGraphicPrimitive2DImpl(
550     const primitive2d::FillGraphicPrimitive2D& rFillBitmapCandidate)
551 {
552     const attribute::FillGraphicAttribute& rFillGraphicAttribute(
553         rFillBitmapCandidate.getFillGraphic());
554 
555     // #121194# when tiling is used and content is bitmap-based, do direct tiling in the
556     // renderer on pixel base to ensure tight fitting. Do not do this when
557     // the fill is rotated or sheared.
558     if (!rFillGraphicAttribute.getTiling())
559         return false;
560 
561     // content is bitmap(ex)
562     //
563     // for Vector Graphic Data (SVG, EMF+) support, force decomposition when present. This will lead to use
564     // the primitive representation of the vector data directly.
565     //
566     // when graphic is animated, force decomposition to use the correct graphic, else
567     // fill style will not be animated
568     if (GraphicType::Bitmap != rFillGraphicAttribute.getGraphic().GetType()
569         || rFillGraphicAttribute.getGraphic().getVectorGraphicData()
570         || rFillGraphicAttribute.getGraphic().IsAnimated())
571         return false;
572 
573     // decompose matrix to check for shear, rotate and mirroring
574     basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation
575                                           * rFillBitmapCandidate.getTransformation());
576     basegfx::B2DVector aScale, aTranslate;
577     double fRotate, fShearX;
578     aLocalTransform.decompose(aScale, aTranslate, fRotate, fShearX);
579 
580     // when nopt rotated/sheared
581     if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX))
582         return false;
583 
584     // no shear or rotate, draw direct in pixel coordinates
585 
586     // transform object range to device coordinates (pixels). Use
587     // the device transformation for better accuracy
588     basegfx::B2DRange aObjectRange(aTranslate, aTranslate + aScale);
589     aObjectRange.transform(mpOutputDevice->GetViewTransformation());
590 
591     // extract discrete size of object
592     const sal_Int32 nOWidth(basegfx::fround(aObjectRange.getWidth()));
593     const sal_Int32 nOHeight(basegfx::fround(aObjectRange.getHeight()));
594 
595     // only do something when object has a size in discrete units
596     if (nOWidth <= 0 || nOHeight <= 0)
597         return true;
598 
599     // transform graphic range to device coordinates (pixels). Use
600     // the device transformation for better accuracy
601     basegfx::B2DRange aGraphicRange(rFillGraphicAttribute.getGraphicRange());
602     aGraphicRange.transform(mpOutputDevice->GetViewTransformation() * aLocalTransform);
603 
604     // extract discrete size of graphic
605     // caution: when getting to zero, nothing would be painted; thus, do not allow this
606     const sal_Int32 nBWidth(std::max(sal_Int32(1), basegfx::fround(aGraphicRange.getWidth())));
607     const sal_Int32 nBHeight(std::max(sal_Int32(1), basegfx::fround(aGraphicRange.getHeight())));
608 
609     // nBWidth, nBHeight is the pixel size of the needed bitmap. To not need to scale it
610     // in vcl many times, create a size-optimized version
611     const Size aNeededBitmapSizePixel(nBWidth, nBHeight);
612     BitmapEx aBitmapEx(rFillGraphicAttribute.getGraphic().GetBitmapEx());
613     const bool bPreScaled(nBWidth * nBHeight < (250 * 250));
614 
615     // ... but only up to a maximum size, else it gets too expensive
616     if (bPreScaled)
617     {
618         // if color depth is below 24bit, expand before scaling for better quality.
619         // This is even needed for low colors, else the scale will produce
620         // a bitmap in gray or Black/White (!)
621         if (isPalettePixelFormat(aBitmapEx.getPixelFormat()))
622         {
623             aBitmapEx.Convert(BmpConversion::N24Bit);
624         }
625 
626         aBitmapEx.Scale(aNeededBitmapSizePixel, BmpScaleFlag::Interpolate);
627     }
628 
629     if (maBColorModifierStack.count())
630     {
631         // when color modifier, apply to bitmap
632         aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack);
633 
634         // ModifyBitmapEx uses empty bitmap as sign to return that
635         // the content will be completely replaced to mono color, use shortcut
636         if (aBitmapEx.IsEmpty())
637         {
638             // color gets completely replaced, get it
639             const basegfx::BColor aModifiedColor(
640                 maBColorModifierStack.getModifiedColor(basegfx::BColor()));
641             basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon());
642             aPolygon.transform(aLocalTransform);
643 
644             mpOutputDevice->SetFillColor(Color(aModifiedColor));
645             mpOutputDevice->SetLineColor();
646             mpOutputDevice->DrawPolygon(aPolygon);
647 
648             return true;
649         }
650     }
651 
652     sal_Int32 nBLeft(basegfx::fround(aGraphicRange.getMinX()));
653     sal_Int32 nBTop(basegfx::fround(aGraphicRange.getMinY()));
654     const sal_Int32 nOLeft(basegfx::fround(aObjectRange.getMinX()));
655     const sal_Int32 nOTop(basegfx::fround(aObjectRange.getMinY()));
656     sal_Int32 nPosX(0);
657     sal_Int32 nPosY(0);
658 
659     if (nBLeft > nOLeft)
660     {
661         const sal_Int32 nDiff((nBLeft / nBWidth) + 1);
662 
663         nPosX -= nDiff;
664         nBLeft -= nDiff * nBWidth;
665     }
666 
667     if (nBLeft + nBWidth <= nOLeft)
668     {
669         const sal_Int32 nDiff(-nBLeft / nBWidth);
670 
671         nPosX += nDiff;
672         nBLeft += nDiff * nBWidth;
673     }
674 
675     if (nBTop > nOTop)
676     {
677         const sal_Int32 nDiff((nBTop / nBHeight) + 1);
678 
679         nPosY -= nDiff;
680         nBTop -= nDiff * nBHeight;
681     }
682 
683     if (nBTop + nBHeight <= nOTop)
684     {
685         const sal_Int32 nDiff(-nBTop / nBHeight);
686 
687         nPosY += nDiff;
688         nBTop += nDiff * nBHeight;
689     }
690 
691     // prepare OutDev
692     const Point aEmptyPoint(0, 0);
693     // the visible rect, in pixels
694     const ::tools::Rectangle aVisiblePixel(aEmptyPoint, mpOutputDevice->GetOutputSizePixel());
695     const bool bWasEnabled(mpOutputDevice->IsMapModeEnabled());
696     mpOutputDevice->EnableMapMode(false);
697 
698     // check if offset is used
699     const sal_Int32 nOffsetX(basegfx::fround(rFillGraphicAttribute.getOffsetX() * nBWidth));
700 
701     if (nOffsetX)
702     {
703         // offset in X, so iterate over Y first and draw lines
704         for (sal_Int32 nYPos(nBTop); nYPos < nOTop + nOHeight; nYPos += nBHeight, nPosY++)
705         {
706             for (sal_Int32 nXPos((nPosY % 2) ? nBLeft - nBWidth + nOffsetX : nBLeft);
707                  nXPos < nOLeft + nOWidth; nXPos += nBWidth)
708             {
709                 const ::tools::Rectangle aOutRectPixel(Point(nXPos, nYPos), aNeededBitmapSizePixel);
710 
711                 if (aOutRectPixel.Overlaps(aVisiblePixel))
712                 {
713                     if (bPreScaled)
714                     {
715                         mpOutputDevice->DrawBitmapEx(aOutRectPixel.TopLeft(), aBitmapEx);
716                     }
717                     else
718                     {
719                         mpOutputDevice->DrawBitmapEx(aOutRectPixel.TopLeft(),
720                                                      aNeededBitmapSizePixel, aBitmapEx);
721                     }
722                 }
723             }
724         }
725     }
726     else
727     {
728         // check if offset is used
729         const sal_Int32 nOffsetY(basegfx::fround(rFillGraphicAttribute.getOffsetY() * nBHeight));
730 
731         // possible offset in Y, so iterate over X first and draw columns
732         for (sal_Int32 nXPos(nBLeft); nXPos < nOLeft + nOWidth; nXPos += nBWidth, nPosX++)
733         {
734             for (sal_Int32 nYPos((nPosX % 2) ? nBTop - nBHeight + nOffsetY : nBTop);
735                  nYPos < nOTop + nOHeight; nYPos += nBHeight)
736             {
737                 const ::tools::Rectangle aOutRectPixel(Point(nXPos, nYPos), aNeededBitmapSizePixel);
738 
739                 if (aOutRectPixel.Overlaps(aVisiblePixel))
740                 {
741                     if (bPreScaled)
742                     {
743                         mpOutputDevice->DrawBitmapEx(aOutRectPixel.TopLeft(), aBitmapEx);
744                     }
745                     else
746                     {
747                         mpOutputDevice->DrawBitmapEx(aOutRectPixel.TopLeft(),
748                                                      aNeededBitmapSizePixel, aBitmapEx);
749                     }
750                 }
751             }
752         }
753     }
754 
755     // restore OutDev
756     mpOutputDevice->EnableMapMode(bWasEnabled);
757     return true;
758 }
759 
760 // direct draw of Graphic
761 void VclProcessor2D::RenderPolyPolygonGraphicPrimitive2D(
762     const primitive2d::PolyPolygonGraphicPrimitive2D& rPolygonCandidate)
763 {
764     bool bDone(false);
765     const basegfx::B2DPolyPolygon& rPolyPolygon = rPolygonCandidate.getB2DPolyPolygon();
766 
767     // #121194# Todo: check if this works
768     if (!rPolyPolygon.count())
769     {
770         // empty polyPolygon, done
771         bDone = true;
772     }
773     else
774     {
775         const attribute::FillGraphicAttribute& rFillGraphicAttribute
776             = rPolygonCandidate.getFillGraphic();
777 
778         // try to catch cases where the graphic will be color-modified to a single
779         // color (e.g. shadow)
780         switch (rFillGraphicAttribute.getGraphic().GetType())
781         {
782             case GraphicType::GdiMetafile:
783             {
784                 // metafiles are potentially transparent, cannot optimize, not done
785                 break;
786             }
787             case GraphicType::Bitmap:
788             {
789                 if (!rFillGraphicAttribute.getGraphic().IsTransparent()
790                     && !rFillGraphicAttribute.getGraphic().IsAlpha())
791                 {
792                     // bitmap is not transparent and has no alpha
793                     const sal_uInt32 nBColorModifierStackCount(maBColorModifierStack.count());
794 
795                     if (nBColorModifierStackCount)
796                     {
797                         const basegfx::BColorModifierSharedPtr& rTopmostModifier
798                             = maBColorModifierStack.getBColorModifier(nBColorModifierStackCount
799                                                                       - 1);
800                         const basegfx::BColorModifier_replace* pReplacer
801                             = dynamic_cast<const basegfx::BColorModifier_replace*>(
802                                 rTopmostModifier.get());
803 
804                         if (pReplacer)
805                         {
806                             // the bitmap fill is in unified color, so we can replace it with
807                             // a single polygon fill. The form of the fill depends on tiling
808                             if (rFillGraphicAttribute.getTiling())
809                             {
810                                 // with tiling, fill the whole tools::PolyPolygon with the modifier color
811                                 basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolyPolygon);
812 
813                                 aLocalPolyPolygon.transform(maCurrentTransformation);
814                                 mpOutputDevice->SetLineColor();
815                                 mpOutputDevice->SetFillColor(Color(pReplacer->getBColor()));
816                                 mpOutputDevice->DrawPolyPolygon(aLocalPolyPolygon);
817                             }
818                             else
819                             {
820                                 // without tiling, only the area common to the bitmap tile and the
821                                 // tools::PolyPolygon is filled. Create the bitmap tile area in object
822                                 // coordinates. For this, the object transformation needs to be created
823                                 // from the already scaled PolyPolygon. The tile area in object
824                                 // coordinates will always be non-rotated, so it's not necessary to
825                                 // work with a polygon here
826                                 basegfx::B2DRange aTileRange(
827                                     rFillGraphicAttribute.getGraphicRange());
828                                 const basegfx::B2DRange aPolyPolygonRange(
829                                     rPolyPolygon.getB2DRange());
830                                 const basegfx::B2DHomMatrix aNewObjectTransform(
831                                     basegfx::utils::createScaleTranslateB2DHomMatrix(
832                                         aPolyPolygonRange.getRange(),
833                                         aPolyPolygonRange.getMinimum()));
834 
835                                 aTileRange.transform(aNewObjectTransform);
836 
837                                 // now clip the object polyPolygon against the tile range
838                                 // to get the common area
839                                 basegfx::B2DPolyPolygon aTarget
840                                     = basegfx::utils::clipPolyPolygonOnRange(
841                                         rPolyPolygon, aTileRange, true, false);
842 
843                                 if (aTarget.count())
844                                 {
845                                     aTarget.transform(maCurrentTransformation);
846                                     mpOutputDevice->SetLineColor();
847                                     mpOutputDevice->SetFillColor(Color(pReplacer->getBColor()));
848                                     mpOutputDevice->DrawPolyPolygon(aTarget);
849                                 }
850                             }
851 
852                             // simplified output executed, we are done
853                             bDone = true;
854                         }
855                     }
856                 }
857                 break;
858             }
859             default: //GraphicType::NONE, GraphicType::Default
860             {
861                 // empty graphic, we are done
862                 bDone = true;
863                 break;
864             }
865         }
866     }
867 
868     if (!bDone)
869     {
870         // use default decomposition
871         process(rPolygonCandidate);
872     }
873 }
874 
875 // mask group
876 void VclProcessor2D::RenderMaskPrimitive2DPixel(const primitive2d::MaskPrimitive2D& rMaskCandidate)
877 {
878     if (rMaskCandidate.getChildren().empty())
879         return;
880 
881     basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask());
882 
883     if (!aMask.count())
884         return;
885 
886     aMask.transform(maCurrentTransformation);
887 
888     // Unless smooth edges are needed, simply use clipping.
889     if (basegfx::utils::isRectangle(aMask) || !getViewInformation2D().getUseAntiAliasing())
890     {
891         mpOutputDevice->Push(vcl::PushFlags::CLIPREGION);
892         mpOutputDevice->IntersectClipRegion(vcl::Region(aMask));
893         process(rMaskCandidate.getChildren());
894         mpOutputDevice->Pop();
895         return;
896     }
897 
898     const basegfx::B2DRange aRange(basegfx::utils::getRange(aMask));
899     impBufferDevice aBufferDevice(*mpOutputDevice, aRange);
900 
901     if (!aBufferDevice.isVisible())
902         return;
903 
904     // remember last OutDev and set to content
905     OutputDevice* pLastOutputDevice = mpOutputDevice;
906     mpOutputDevice = &aBufferDevice.getContent();
907 
908     // paint to it
909     process(rMaskCandidate.getChildren());
910 
911     // back to old OutDev
912     mpOutputDevice = pLastOutputDevice;
913 
914     // draw mask
915     VirtualDevice& rMask = aBufferDevice.getTransparence();
916     rMask.SetLineColor();
917     rMask.SetFillColor(COL_BLACK);
918     rMask.DrawPolyPolygon(aMask);
919 
920     // dump buffer to outdev
921     aBufferDevice.paint();
922 }
923 
924 // modified color group. Force output to unified color.
925 void VclProcessor2D::RenderModifiedColorPrimitive2D(
926     const primitive2d::ModifiedColorPrimitive2D& rModifiedCandidate)
927 {
928     if (!rModifiedCandidate.getChildren().empty())
929     {
930         maBColorModifierStack.push(rModifiedCandidate.getColorModifier());
931         process(rModifiedCandidate.getChildren());
932         maBColorModifierStack.pop();
933     }
934 }
935 
936 // unified sub-transparence. Draw to VDev first.
937 void VclProcessor2D::RenderUnifiedTransparencePrimitive2D(
938     const primitive2d::UnifiedTransparencePrimitive2D& rTransCandidate)
939 {
940     if (rTransCandidate.getChildren().empty())
941         return;
942 
943     if (0.0 == rTransCandidate.getTransparence())
944     {
945         // no transparence used, so just use the content
946         process(rTransCandidate.getChildren());
947     }
948     else if (rTransCandidate.getTransparence() > 0.0 && rTransCandidate.getTransparence() < 1.0)
949     {
950         // transparence is in visible range
951         basegfx::B2DRange aRange(rTransCandidate.getChildren().getB2DRange(getViewInformation2D()));
952         aRange.transform(maCurrentTransformation);
953         impBufferDevice aBufferDevice(*mpOutputDevice, aRange);
954 
955         if (aBufferDevice.isVisible())
956         {
957             // remember last OutDev and set to content
958             OutputDevice* pLastOutputDevice = mpOutputDevice;
959             mpOutputDevice = &aBufferDevice.getContent();
960 
961             // paint content to it
962             process(rTransCandidate.getChildren());
963 
964             // back to old OutDev
965             mpOutputDevice = pLastOutputDevice;
966 
967             // dump buffer to outdev using given transparence
968             aBufferDevice.paint(rTransCandidate.getTransparence());
969         }
970     }
971 }
972 
973 // sub-transparence group. Draw to VDev first.
974 void VclProcessor2D::RenderTransparencePrimitive2D(
975     const primitive2d::TransparencePrimitive2D& rTransCandidate)
976 {
977     if (rTransCandidate.getChildren().empty())
978         return;
979 
980     basegfx::B2DRange aRange(rTransCandidate.getChildren().getB2DRange(getViewInformation2D()));
981     aRange.transform(maCurrentTransformation);
982     impBufferDevice aBufferDevice(*mpOutputDevice, aRange);
983 
984     if (!aBufferDevice.isVisible())
985         return;
986 
987     // remember last OutDev and set to content
988     OutputDevice* pLastOutputDevice = mpOutputDevice;
989     mpOutputDevice = &aBufferDevice.getContent();
990 
991     // paint content to it
992     process(rTransCandidate.getChildren());
993 
994     // set to mask
995     mpOutputDevice = &aBufferDevice.getTransparence();
996 
997     // when painting transparence masks, reset the color stack
998     basegfx::BColorModifierStack aLastBColorModifierStack(maBColorModifierStack);
999     maBColorModifierStack = basegfx::BColorModifierStack();
1000 
1001     // paint mask to it (always with transparence intensities, evtl. with AA)
1002     process(rTransCandidate.getTransparence());
1003 
1004     // back to old color stack
1005     maBColorModifierStack = std::move(aLastBColorModifierStack);
1006 
1007     // back to old OutDev
1008     mpOutputDevice = pLastOutputDevice;
1009 
1010     // dump buffer to outdev
1011     aBufferDevice.paint();
1012 }
1013 
1014 // transform group.
1015 void VclProcessor2D::RenderTransformPrimitive2D(
1016     const primitive2d::TransformPrimitive2D& rTransformCandidate)
1017 {
1018     // remember current transformation and ViewInformation
1019     const basegfx::B2DHomMatrix aLastCurrentTransformation(maCurrentTransformation);
1020     const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
1021 
1022     // create new transformations for CurrentTransformation
1023     // and for local ViewInformation2D
1024     maCurrentTransformation = maCurrentTransformation * rTransformCandidate.getTransformation();
1025     geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
1026     aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation()
1027                                                * rTransformCandidate.getTransformation());
1028     updateViewInformation(aViewInformation2D);
1029 
1030     // process content
1031     process(rTransformCandidate.getChildren());
1032 
1033     // restore transformations
1034     maCurrentTransformation = aLastCurrentTransformation;
1035     updateViewInformation(aLastViewInformation2D);
1036 }
1037 
1038 // new XDrawPage for ViewInformation2D
1039 void VclProcessor2D::RenderPagePreviewPrimitive2D(
1040     const primitive2d::PagePreviewPrimitive2D& rPagePreviewCandidate)
1041 {
1042     // remember current transformation and ViewInformation
1043     const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
1044 
1045     // create new local ViewInformation2D
1046     geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
1047     aViewInformation2D.setVisualizedPage(rPagePreviewCandidate.getXDrawPage());
1048     updateViewInformation(aViewInformation2D);
1049 
1050     // process decomposed content
1051     process(rPagePreviewCandidate);
1052 
1053     // restore transformations
1054     updateViewInformation(aLastViewInformation2D);
1055 }
1056 
1057 // marker
1058 void VclProcessor2D::RenderMarkerArrayPrimitive2D(
1059     const primitive2d::MarkerArrayPrimitive2D& rMarkArrayCandidate)
1060 {
1061     // get data
1062     const std::vector<basegfx::B2DPoint>& rPositions = rMarkArrayCandidate.getPositions();
1063     const sal_uInt32 nCount(rPositions.size());
1064 
1065     if (!nCount || rMarkArrayCandidate.getMarker().IsEmpty())
1066         return;
1067 
1068     // get pixel size
1069     const BitmapEx& rMarker(rMarkArrayCandidate.getMarker());
1070     const Size aBitmapSize(rMarker.GetSizePixel());
1071 
1072     if (!(aBitmapSize.Width() && aBitmapSize.Height()))
1073         return;
1074 
1075     // get discrete half size
1076     const basegfx::B2DVector aDiscreteHalfSize((aBitmapSize.getWidth() - 1.0) * 0.5,
1077                                                (aBitmapSize.getHeight() - 1.0) * 0.5);
1078     const bool bWasEnabled(mpOutputDevice->IsMapModeEnabled());
1079 
1080     // do not forget evtl. moved origin in target device MapMode when
1081     // switching it off; it would be missing and lead to wrong positions.
1082     // All his could be done using logic sizes and coordinates, too, but
1083     // we want a 1:1 bitmap rendering here, so it's more safe and faster
1084     // to work with switching off MapMode usage completely.
1085     const Point aOrigin(mpOutputDevice->GetMapMode().GetOrigin());
1086 
1087     mpOutputDevice->EnableMapMode(false);
1088 
1089     for (auto const& pos : rPositions)
1090     {
1091         const basegfx::B2DPoint aDiscreteTopLeft((maCurrentTransformation * pos)
1092                                                  - aDiscreteHalfSize);
1093         const Point aDiscretePoint(basegfx::fround<tools::Long>(aDiscreteTopLeft.getX()),
1094                                    basegfx::fround<tools::Long>(aDiscreteTopLeft.getY()));
1095 
1096         mpOutputDevice->DrawBitmapEx(aDiscretePoint + aOrigin, rMarker);
1097     }
1098 
1099     mpOutputDevice->EnableMapMode(bWasEnabled);
1100 }
1101 
1102 // point
1103 void VclProcessor2D::RenderPointArrayPrimitive2D(
1104     const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate)
1105 {
1106     const std::vector<basegfx::B2DPoint>& rPositions = rPointArrayCandidate.getPositions();
1107     const basegfx::BColor aRGBColor(
1108         maBColorModifierStack.getModifiedColor(rPointArrayCandidate.getRGBColor()));
1109     const Color aVCLColor(aRGBColor);
1110 
1111     for (auto const& pos : rPositions)
1112     {
1113         const basegfx::B2DPoint aViewPosition(maCurrentTransformation * pos);
1114         const Point aPos(basegfx::fround<tools::Long>(aViewPosition.getX()),
1115                          basegfx::fround<tools::Long>(aViewPosition.getY()));
1116 
1117         mpOutputDevice->DrawPixel(aPos, aVCLColor);
1118     }
1119 }
1120 
1121 void VclProcessor2D::RenderPolygonStrokePrimitive2D(
1122     const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate)
1123 {
1124     // #i101491# method restructured to clearly use the DrawPolyLine
1125     // calls starting from a defined line width
1126     const attribute::LineAttribute& rLineAttribute = rPolygonStrokeCandidate.getLineAttribute();
1127     const double fLineWidth(rLineAttribute.getWidth());
1128     bool bDone(false);
1129 
1130     if (fLineWidth > 0.0)
1131     {
1132         const basegfx::B2DVector aDiscreteUnit(maCurrentTransformation
1133                                                * basegfx::B2DVector(fLineWidth, 0.0));
1134         const double fDiscreteLineWidth(aDiscreteUnit.getLength());
1135         const attribute::StrokeAttribute& rStrokeAttribute
1136             = rPolygonStrokeCandidate.getStrokeAttribute();
1137         const basegfx::BColor aHairlineColor(
1138             maBColorModifierStack.getModifiedColor(rLineAttribute.getColor()));
1139         basegfx::B2DPolyPolygon aHairlinePolyPolygon;
1140 
1141         mpOutputDevice->SetLineColor(Color(aHairlineColor));
1142         mpOutputDevice->SetFillColor();
1143 
1144         if (0.0 == rStrokeAttribute.getFullDotDashLen())
1145         {
1146             // no line dashing, just copy
1147             aHairlinePolyPolygon.append(rPolygonStrokeCandidate.getB2DPolygon());
1148         }
1149         else
1150         {
1151             // else apply LineStyle
1152             basegfx::utils::applyLineDashing(
1153                 rPolygonStrokeCandidate.getB2DPolygon(), rStrokeAttribute.getDotDashArray(),
1154                 &aHairlinePolyPolygon, nullptr, rStrokeAttribute.getFullDotDashLen());
1155         }
1156 
1157         const sal_uInt32 nCount(aHairlinePolyPolygon.count());
1158 
1159         if (nCount)
1160         {
1161             const bool bAntiAliased(getViewInformation2D().getUseAntiAliasing());
1162             aHairlinePolyPolygon.transform(maCurrentTransformation);
1163 
1164             if (bAntiAliased)
1165             {
1166                 if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 1.0))
1167                 {
1168                     // line in range ]0.0 .. 1.0[
1169                     // paint as simple hairline
1170                     for (sal_uInt32 a(0); a < nCount; a++)
1171                     {
1172                         mpOutputDevice->DrawPolyLine(aHairlinePolyPolygon.getB2DPolygon(a), 0.0);
1173                     }
1174 
1175                     bDone = true;
1176                 }
1177                 else if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 2.0))
1178                 {
1179                     // line in range [1.0 .. 2.0[
1180                     // paint as 2x2 with dynamic line distance
1181                     basegfx::B2DHomMatrix aMat;
1182                     const double fDistance(fDiscreteLineWidth - 1.0);
1183                     const double fHalfDistance(fDistance * 0.5);
1184 
1185                     for (sal_uInt32 a(0); a < nCount; a++)
1186                     {
1187                         basegfx::B2DPolygon aCandidate(aHairlinePolyPolygon.getB2DPolygon(a));
1188 
1189                         aMat.set(0, 2, -fHalfDistance);
1190                         aMat.set(1, 2, -fHalfDistance);
1191                         aCandidate.transform(aMat);
1192                         mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1193 
1194                         aMat.set(0, 2, fDistance);
1195                         aMat.set(1, 2, 0.0);
1196                         aCandidate.transform(aMat);
1197                         mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1198 
1199                         aMat.set(0, 2, 0.0);
1200                         aMat.set(1, 2, fDistance);
1201                         aCandidate.transform(aMat);
1202                         mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1203 
1204                         aMat.set(0, 2, -fDistance);
1205                         aMat.set(1, 2, 0.0);
1206                         aCandidate.transform(aMat);
1207                         mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1208                     }
1209 
1210                     bDone = true;
1211                 }
1212                 else if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 3.0))
1213                 {
1214                     // line in range [2.0 .. 3.0]
1215                     // paint as cross in a 3x3  with dynamic line distance
1216                     basegfx::B2DHomMatrix aMat;
1217                     const double fDistance((fDiscreteLineWidth - 1.0) * 0.5);
1218 
1219                     for (sal_uInt32 a(0); a < nCount; a++)
1220                     {
1221                         basegfx::B2DPolygon aCandidate(aHairlinePolyPolygon.getB2DPolygon(a));
1222 
1223                         mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1224 
1225                         aMat.set(0, 2, -fDistance);
1226                         aMat.set(1, 2, 0.0);
1227                         aCandidate.transform(aMat);
1228                         mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1229 
1230                         aMat.set(0, 2, fDistance);
1231                         aMat.set(1, 2, -fDistance);
1232                         aCandidate.transform(aMat);
1233                         mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1234 
1235                         aMat.set(0, 2, fDistance);
1236                         aMat.set(1, 2, fDistance);
1237                         aCandidate.transform(aMat);
1238                         mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1239 
1240                         aMat.set(0, 2, -fDistance);
1241                         aMat.set(1, 2, fDistance);
1242                         aCandidate.transform(aMat);
1243                         mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1244                     }
1245 
1246                     bDone = true;
1247                 }
1248                 else
1249                 {
1250                     // #i101491# line width above 3.0
1251                 }
1252             }
1253             else
1254             {
1255                 if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 1.5))
1256                 {
1257                     // line width below 1.5, draw the basic hairline polygon
1258                     for (sal_uInt32 a(0); a < nCount; a++)
1259                     {
1260                         mpOutputDevice->DrawPolyLine(aHairlinePolyPolygon.getB2DPolygon(a), 0.0);
1261                     }
1262 
1263                     bDone = true;
1264                 }
1265                 else if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 2.5))
1266                 {
1267                     // line width is in range ]1.5 .. 2.5], use four hairlines
1268                     // drawn in a square
1269                     for (sal_uInt32 a(0); a < nCount; a++)
1270                     {
1271                         basegfx::B2DPolygon aCandidate(aHairlinePolyPolygon.getB2DPolygon(a));
1272                         basegfx::B2DHomMatrix aMat;
1273 
1274                         mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1275 
1276                         aMat.set(0, 2, 1.0);
1277                         aMat.set(1, 2, 0.0);
1278                         aCandidate.transform(aMat);
1279 
1280                         mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1281 
1282                         aMat.set(0, 2, 0.0);
1283                         aMat.set(1, 2, 1.0);
1284                         aCandidate.transform(aMat);
1285 
1286                         mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1287 
1288                         aMat.set(0, 2, -1.0);
1289                         aMat.set(1, 2, 0.0);
1290                         aCandidate.transform(aMat);
1291 
1292                         mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1293                     }
1294 
1295                     bDone = true;
1296                 }
1297                 else
1298                 {
1299                     // #i101491# line width is above 2.5
1300                 }
1301             }
1302 
1303             if (!bDone && rPolygonStrokeCandidate.getB2DPolygon().count() > 1000)
1304             {
1305                 // #i101491# If the polygon complexity uses more than a given amount, do
1306                 // use OutputDevice::DrawPolyLine directly; this will avoid buffering all
1307                 // decompositions in primitives (memory) and fallback to old line painting
1308                 // for very complex polygons, too
1309                 for (sal_uInt32 a(0); a < nCount; a++)
1310                 {
1311                     mpOutputDevice->DrawPolyLine(aHairlinePolyPolygon.getB2DPolygon(a),
1312                                                  fDiscreteLineWidth, rLineAttribute.getLineJoin(),
1313                                                  rLineAttribute.getLineCap(),
1314                                                  rLineAttribute.getMiterMinimumAngle());
1315                 }
1316 
1317                 bDone = true;
1318             }
1319         }
1320     }
1321 
1322     if (!bDone)
1323     {
1324         // remember that we enter a PolygonStrokePrimitive2D decomposition,
1325         // used for AA thick line drawing
1326         mnPolygonStrokePrimitive2D++;
1327 
1328         // line width is big enough for standard filled polygon visualisation or zero
1329         process(rPolygonStrokeCandidate);
1330 
1331         // leave PolygonStrokePrimitive2D
1332         mnPolygonStrokePrimitive2D--;
1333     }
1334 }
1335 
1336 void VclProcessor2D::RenderEpsPrimitive2D(const primitive2d::EpsPrimitive2D& rEpsPrimitive2D)
1337 {
1338     // The new decomposition of Metafiles made it necessary to add an Eps
1339     // primitive to handle embedded Eps data. On some devices, this can be
1340     // painted directly (mac, printer).
1341     // To be able to handle the replacement correctly, i need to handle it myself
1342     // since DrawEPS will not be able e.g. to rotate the replacement. To be able
1343     // to do that, i added a boolean return to OutputDevice::DrawEPS(..)
1344     // to know when EPS was handled directly already.
1345     basegfx::B2DRange aRange(0.0, 0.0, 1.0, 1.0);
1346     aRange.transform(maCurrentTransformation * rEpsPrimitive2D.getEpsTransform());
1347 
1348     if (aRange.isEmpty())
1349         return;
1350 
1351     const ::tools::Rectangle aRectangle(static_cast<sal_Int32>(floor(aRange.getMinX())),
1352                                         static_cast<sal_Int32>(floor(aRange.getMinY())),
1353                                         static_cast<sal_Int32>(ceil(aRange.getMaxX())),
1354                                         static_cast<sal_Int32>(ceil(aRange.getMaxY())));
1355 
1356     if (aRectangle.IsEmpty())
1357         return;
1358 
1359     bool bWillReallyRender = mpOutputDevice->IsDeviceOutputNecessary();
1360     // try to paint EPS directly without fallback visualisation
1361     const bool bEPSPaintedDirectly
1362         = bWillReallyRender
1363           && mpOutputDevice->DrawEPS(aRectangle.TopLeft(), aRectangle.GetSize(),
1364                                      rEpsPrimitive2D.getGfxLink());
1365 
1366     if (!bEPSPaintedDirectly)
1367     {
1368         // use the decomposition which will correctly handle the
1369         // fallback visualisation using full transformation (e.g. rotation)
1370         process(rEpsPrimitive2D);
1371     }
1372 }
1373 
1374 void VclProcessor2D::RenderSvgLinearAtomPrimitive2D(
1375     const primitive2d::SvgLinearAtomPrimitive2D& rCandidate)
1376 {
1377     const double fDelta(rCandidate.getOffsetB() - rCandidate.getOffsetA());
1378 
1379     if (fDelta <= 0.0)
1380         return;
1381 
1382     const basegfx::BColor aColorA(maBColorModifierStack.getModifiedColor(rCandidate.getColorA()));
1383     const basegfx::BColor aColorB(maBColorModifierStack.getModifiedColor(rCandidate.getColorB()));
1384 
1385     // calculate discrete unit in WorldCoordinates; use diagonal (1.0, 1.0) and divide by sqrt(2)
1386     const basegfx::B2DVector aDiscreteVector(
1387         getViewInformation2D().getInverseObjectToViewTransformation()
1388         * basegfx::B2DVector(1.0, 1.0));
1389     const double fDiscreteUnit(aDiscreteVector.getLength() * (1.0 / M_SQRT2));
1390 
1391     // use color distance and discrete lengths to calculate step count
1392     const sal_uInt32 nSteps(calculateStepsForSvgGradient(aColorA, aColorB, fDelta, fDiscreteUnit));
1393 
1394     // switch off line painting
1395     mpOutputDevice->SetLineColor();
1396 
1397     // prepare polygon in needed width at start position (with discrete overlap)
1398     const basegfx::B2DPolygon aPolygon(basegfx::utils::createPolygonFromRect(
1399         basegfx::B2DRange(rCandidate.getOffsetA() - fDiscreteUnit, 0.0,
1400                           rCandidate.getOffsetA() + (fDelta / nSteps) + fDiscreteUnit, 1.0)));
1401 
1402     // prepare loop ([0.0 .. 1.0[)
1403     double fUnitScale(0.0);
1404     const double fUnitStep(1.0 / nSteps);
1405 
1406     // loop and paint
1407     for (sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep)
1408     {
1409         basegfx::B2DPolygon aNew(aPolygon);
1410 
1411         aNew.transform(maCurrentTransformation
1412                        * basegfx::utils::createTranslateB2DHomMatrix(fDelta * fUnitScale, 0.0));
1413         mpOutputDevice->SetFillColor(Color(basegfx::interpolate(aColorA, aColorB, fUnitScale)));
1414         mpOutputDevice->DrawPolyPolygon(basegfx::B2DPolyPolygon(aNew));
1415     }
1416 }
1417 
1418 void VclProcessor2D::RenderSvgRadialAtomPrimitive2D(
1419     const primitive2d::SvgRadialAtomPrimitive2D& rCandidate)
1420 {
1421     const double fDeltaScale(rCandidate.getScaleB() - rCandidate.getScaleA());
1422 
1423     if (fDeltaScale <= 0.0)
1424         return;
1425 
1426     const basegfx::BColor aColorA(maBColorModifierStack.getModifiedColor(rCandidate.getColorA()));
1427     const basegfx::BColor aColorB(maBColorModifierStack.getModifiedColor(rCandidate.getColorB()));
1428 
1429     // calculate discrete unit in WorldCoordinates; use diagonal (1.0, 1.0) and divide by sqrt(2)
1430     const basegfx::B2DVector aDiscreteVector(
1431         getViewInformation2D().getInverseObjectToViewTransformation()
1432         * basegfx::B2DVector(1.0, 1.0));
1433     const double fDiscreteUnit(aDiscreteVector.getLength() * (1.0 / M_SQRT2));
1434 
1435     // use color distance and discrete lengths to calculate step count
1436     const sal_uInt32 nSteps(
1437         calculateStepsForSvgGradient(aColorA, aColorB, fDeltaScale, fDiscreteUnit));
1438 
1439     // switch off line painting
1440     mpOutputDevice->SetLineColor();
1441 
1442     // prepare loop ([0.0 .. 1.0[, full polygons, no polypolygons with holes)
1443     double fUnitScale(0.0);
1444     const double fUnitStep(1.0 / nSteps);
1445 
1446     for (sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep)
1447     {
1448         basegfx::B2DHomMatrix aTransform;
1449         const double fEndScale(rCandidate.getScaleB() - (fDeltaScale * fUnitScale));
1450 
1451         if (rCandidate.isTranslateSet())
1452         {
1453             const basegfx::B2DVector aTranslate(basegfx::interpolate(
1454                 rCandidate.getTranslateB(), rCandidate.getTranslateA(), fUnitScale));
1455 
1456             aTransform = basegfx::utils::createScaleTranslateB2DHomMatrix(
1457                 fEndScale, fEndScale, aTranslate.getX(), aTranslate.getY());
1458         }
1459         else
1460         {
1461             aTransform = basegfx::utils::createScaleB2DHomMatrix(fEndScale, fEndScale);
1462         }
1463 
1464         basegfx::B2DPolygon aNew(basegfx::utils::createPolygonFromUnitCircle());
1465 
1466         aNew.transform(maCurrentTransformation * aTransform);
1467         mpOutputDevice->SetFillColor(Color(basegfx::interpolate(aColorB, aColorA, fUnitScale)));
1468         mpOutputDevice->DrawPolyPolygon(basegfx::B2DPolyPolygon(aNew));
1469     }
1470 }
1471 
1472 void VclProcessor2D::adaptLineToFillDrawMode() const
1473 {
1474     const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
1475 
1476     if (!(nOriginalDrawMode
1477           & (DrawModeFlags::BlackLine | DrawModeFlags::GrayLine | DrawModeFlags::WhiteLine
1478              | DrawModeFlags::SettingsLine)))
1479         return;
1480 
1481     DrawModeFlags nAdaptedDrawMode(nOriginalDrawMode);
1482 
1483     if (nOriginalDrawMode & DrawModeFlags::BlackLine)
1484     {
1485         nAdaptedDrawMode |= DrawModeFlags::BlackFill;
1486     }
1487     else
1488     {
1489         nAdaptedDrawMode &= ~DrawModeFlags::BlackFill;
1490     }
1491 
1492     if (nOriginalDrawMode & DrawModeFlags::GrayLine)
1493     {
1494         nAdaptedDrawMode |= DrawModeFlags::GrayFill;
1495     }
1496     else
1497     {
1498         nAdaptedDrawMode &= ~DrawModeFlags::GrayFill;
1499     }
1500 
1501     if (nOriginalDrawMode & DrawModeFlags::WhiteLine)
1502     {
1503         nAdaptedDrawMode |= DrawModeFlags::WhiteFill;
1504     }
1505     else
1506     {
1507         nAdaptedDrawMode &= ~DrawModeFlags::WhiteFill;
1508     }
1509 
1510     if (nOriginalDrawMode & DrawModeFlags::SettingsLine)
1511     {
1512         nAdaptedDrawMode |= DrawModeFlags::SettingsFill;
1513     }
1514     else
1515     {
1516         nAdaptedDrawMode &= ~DrawModeFlags::SettingsFill;
1517     }
1518 
1519     mpOutputDevice->SetDrawMode(nAdaptedDrawMode);
1520 }
1521 
1522 void VclProcessor2D::adaptTextToFillDrawMode() const
1523 {
1524     const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
1525     if (!(nOriginalDrawMode
1526           & (DrawModeFlags::BlackText | DrawModeFlags::GrayText | DrawModeFlags::WhiteText
1527              | DrawModeFlags::SettingsText)))
1528         return;
1529 
1530     DrawModeFlags nAdaptedDrawMode(nOriginalDrawMode);
1531 
1532     if (nOriginalDrawMode & DrawModeFlags::BlackText)
1533     {
1534         nAdaptedDrawMode |= DrawModeFlags::BlackFill;
1535     }
1536     else
1537     {
1538         nAdaptedDrawMode &= ~DrawModeFlags::BlackFill;
1539     }
1540 
1541     if (nOriginalDrawMode & DrawModeFlags::GrayText)
1542     {
1543         nAdaptedDrawMode |= DrawModeFlags::GrayFill;
1544     }
1545     else
1546     {
1547         nAdaptedDrawMode &= ~DrawModeFlags::GrayFill;
1548     }
1549 
1550     if (nOriginalDrawMode & DrawModeFlags::WhiteText)
1551     {
1552         nAdaptedDrawMode |= DrawModeFlags::WhiteFill;
1553     }
1554     else
1555     {
1556         nAdaptedDrawMode &= ~DrawModeFlags::WhiteFill;
1557     }
1558 
1559     if (nOriginalDrawMode & DrawModeFlags::SettingsText)
1560     {
1561         nAdaptedDrawMode |= DrawModeFlags::SettingsFill;
1562     }
1563     else
1564     {
1565         nAdaptedDrawMode &= ~DrawModeFlags::SettingsFill;
1566     }
1567 
1568     mpOutputDevice->SetDrawMode(nAdaptedDrawMode);
1569 }
1570 
1571 // process support
1572 
1573 VclProcessor2D::VclProcessor2D(const geometry::ViewInformation2D& rViewInformation,
1574                                OutputDevice& rOutDev, basegfx::BColorModifierStack aInitStack)
1575     : BaseProcessor2D(rViewInformation)
1576     , mpOutputDevice(&rOutDev)
1577     , maBColorModifierStack(std::move(aInitStack))
1578     , mnPolygonStrokePrimitive2D(0)
1579 {
1580     // set digit language, derived from SvtCTLOptions to have the correct
1581     // number display for arabic/hindi numerals
1582     rOutDev.SetDigitLanguage(drawinglayer::detail::getDigitLanguage());
1583 }
1584 
1585 VclProcessor2D::~VclProcessor2D() {}
1586 }
1587 
1588 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1589