android: Update app-specific/MIME type icons
[LibreOffice.git] / drawinglayer / source / processor2d / vclprocessor2d.cxx
blobad680803438d97eac721f0894009de66b180b0ef
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include "vclprocessor2d.hxx"
22 #include "getdigitlanguage.hxx"
23 #include "vclhelperbufferdevice.hxx"
24 #include <cmath>
25 #include <comphelper/string.hxx>
26 #include <svtools/optionsdrawinglayer.hxx>
27 #include <tools/debug.hxx>
28 #include <tools/fract.hxx>
29 #include <utility>
30 #include <vcl/glyphitemcache.hxx>
31 #include <vcl/graph.hxx>
32 #include <vcl/kernarray.hxx>
33 #include <vcl/outdev.hxx>
34 #include <rtl/ustrbuf.hxx>
35 #include <sal/log.hxx>
36 #include <toolkit/helper/vclunohelper.hxx>
37 #include <basegfx/polygon/b2dpolygontools.hxx>
38 #include <basegfx/polygon/b2dpolypolygontools.hxx>
39 #include <basegfx/polygon/b2dpolygonclipper.hxx>
40 #include <basegfx/color/bcolor.hxx>
41 #include <basegfx/matrix/b2dhommatrixtools.hxx>
42 #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
43 #include <drawinglayer/primitive2d/textprimitive2d.hxx>
44 #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
45 #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
46 #include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx>
47 #include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
48 #include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
49 #include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx>
50 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
51 #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
52 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
53 #include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
54 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
55 #include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx>
56 #include <drawinglayer/primitive2d/pagepreviewprimitive2d.hxx>
57 #include <drawinglayer/primitive2d/textenumsprimitive2d.hxx>
58 #include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
59 // for support of Title/Description in all apps when embedding pictures
60 #include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx>
61 // control support
62 #include <drawinglayer/primitive2d/textlayoutdevice.hxx>
64 #include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
65 #include <drawinglayer/primitive2d/epsprimitive2d.hxx>
67 using namespace com::sun::star;
69 namespace
71 sal_uInt32 calculateStepsForSvgGradient(const basegfx::BColor& rColorA,
72 const basegfx::BColor& rColorB, double fDelta,
73 double fDiscreteUnit)
75 // use color distance, assume to do every color step
76 sal_uInt32 nSteps(basegfx::fround(rColorA.getDistance(rColorB) * 255.0));
78 if (nSteps)
80 // calc discrete length to change color each discrete unit (pixel)
81 const sal_uInt32 nDistSteps(basegfx::fround(fDelta / fDiscreteUnit));
83 nSteps = std::min(nSteps, nDistSteps);
86 // reduce quality to 3 discrete units or every 3rd color step for rendering
87 nSteps /= 2;
89 // roughly cut when too big or too small (not full quality, reduce complexity)
90 nSteps = std::min(nSteps, sal_uInt32(255));
91 nSteps = std::max(nSteps, sal_uInt32(1));
93 return nSteps;
97 namespace
99 /** helper to convert a MapMode to a transformation */
100 basegfx::B2DHomMatrix getTransformFromMapMode(const MapMode& rMapMode)
102 basegfx::B2DHomMatrix aMapping;
103 const Fraction aNoScale(1, 1);
104 const Point& rOrigin(rMapMode.GetOrigin());
106 if (0 != rOrigin.X() || 0 != rOrigin.Y())
108 aMapping.translate(rOrigin.X(), rOrigin.Y());
111 if (rMapMode.GetScaleX() != aNoScale || rMapMode.GetScaleY() != aNoScale)
113 aMapping.scale(double(rMapMode.GetScaleX()), double(rMapMode.GetScaleY()));
116 return aMapping;
120 namespace drawinglayer::processor2d
122 // rendering support
124 // directdraw of text simple portion or decorated portion primitive. When decorated, all the extra
125 // information is translated to VCL parameters and set at the font.
126 // Acceptance is restricted to no shearing and positive scaling in X and Y (no font mirroring
127 // for VCL)
128 void VclProcessor2D::RenderTextSimpleOrDecoratedPortionPrimitive2D(
129 const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate)
131 // decompose matrix to have position and size of text
132 basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation
133 * rTextCandidate.getTextTransform());
134 basegfx::B2DVector aFontScaling, aTranslate;
135 double fRotate, fShearX;
136 aLocalTransform.decompose(aFontScaling, aTranslate, fRotate, fShearX);
138 bool bPrimitiveAccepted(false);
140 // tdf#95581: Assume tiny shears are rounding artefacts or whatever and can be ignored,
141 // especially if the effect is less than a pixel.
142 if (std::abs(aFontScaling.getY() * fShearX) < 1)
144 if (basegfx::fTools::less(aFontScaling.getX(), 0.0)
145 && basegfx::fTools::less(aFontScaling.getY(), 0.0))
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;
154 if (basegfx::fTools::more(aFontScaling.getX(), 0.0)
155 && basegfx::fTools::more(aFontScaling.getY(), 0.0))
157 double fIgnoreRotate, fIgnoreShearX;
159 basegfx::B2DVector aFontSize, aTextTranslate;
160 rTextCandidate.getTextTransform().decompose(aFontSize, aTextTranslate, fIgnoreRotate,
161 fIgnoreShearX);
163 // tdf#153092 Ideally we don't have to scale the font and dxarray, but we might have
164 // to nevertheless if dealing with non integer sizes
165 const bool bScaleFont(aFontSize.getY() != std::round(aFontSize.getY()));
166 vcl::Font aFont;
168 // Get the VCL font
169 if (!bScaleFont)
171 aFont = primitive2d::getVclFontFromFontAttribute(
172 rTextCandidate.getFontAttribute(), aFontSize.getX(), aFontSize.getY(), fRotate,
173 rTextCandidate.getLocale());
175 else
177 aFont = primitive2d::getVclFontFromFontAttribute(
178 rTextCandidate.getFontAttribute(), aFontScaling.getX(), aFontScaling.getY(),
179 fRotate, rTextCandidate.getLocale());
182 // Don't draw fonts without height
183 if (aFont.GetFontHeight() <= 0)
184 return;
186 // set FillColor Attribute
187 const Color aFillColor(rTextCandidate.getTextFillColor());
188 if (aFillColor != COL_TRANSPARENT)
190 aFont.SetFillColor(aFillColor);
191 aFont.SetTransparent(false);
194 // handle additional font attributes
195 const primitive2d::TextDecoratedPortionPrimitive2D* pTCPP = nullptr;
196 if (rTextCandidate.getPrimitive2DID() == PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D)
197 pTCPP = static_cast<const primitive2d::TextDecoratedPortionPrimitive2D*>(
198 &rTextCandidate);
200 if (pTCPP != nullptr)
202 // set the color of text decorations
203 const basegfx::BColor aTextlineColor
204 = maBColorModifierStack.getModifiedColor(pTCPP->getTextlineColor());
205 mpOutputDevice->SetTextLineColor(Color(aTextlineColor));
207 // set Overline attribute
208 const FontLineStyle eFontOverline(
209 primitive2d::mapTextLineToFontLineStyle(pTCPP->getFontOverline()));
210 if (eFontOverline != LINESTYLE_NONE)
212 aFont.SetOverline(eFontOverline);
213 const basegfx::BColor aOverlineColor
214 = maBColorModifierStack.getModifiedColor(pTCPP->getOverlineColor());
215 mpOutputDevice->SetOverlineColor(Color(aOverlineColor));
216 if (pTCPP->getWordLineMode())
217 aFont.SetWordLineMode(true);
220 // set Underline attribute
221 const FontLineStyle eFontLineStyle(
222 primitive2d::mapTextLineToFontLineStyle(pTCPP->getFontUnderline()));
223 if (eFontLineStyle != LINESTYLE_NONE)
225 aFont.SetUnderline(eFontLineStyle);
226 if (pTCPP->getWordLineMode())
227 aFont.SetWordLineMode(true);
230 // set Strikeout attribute
231 const FontStrikeout eFontStrikeout(
232 primitive2d::mapTextStrikeoutToFontStrikeout(pTCPP->getTextStrikeout()));
234 if (eFontStrikeout != STRIKEOUT_NONE)
235 aFont.SetStrikeout(eFontStrikeout);
237 // set EmphasisMark attribute
238 FontEmphasisMark eFontEmphasisMark = FontEmphasisMark::NONE;
239 switch (pTCPP->getTextEmphasisMark())
241 default:
242 SAL_WARN("drawinglayer",
243 "Unknown EmphasisMark style " << pTCPP->getTextEmphasisMark());
244 [[fallthrough]];
245 case primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE:
246 eFontEmphasisMark = FontEmphasisMark::NONE;
247 break;
248 case primitive2d::TEXT_FONT_EMPHASIS_MARK_DOT:
249 eFontEmphasisMark = FontEmphasisMark::Dot;
250 break;
251 case primitive2d::TEXT_FONT_EMPHASIS_MARK_CIRCLE:
252 eFontEmphasisMark = FontEmphasisMark::Circle;
253 break;
254 case primitive2d::TEXT_FONT_EMPHASIS_MARK_DISC:
255 eFontEmphasisMark = FontEmphasisMark::Disc;
256 break;
257 case primitive2d::TEXT_FONT_EMPHASIS_MARK_ACCENT:
258 eFontEmphasisMark = FontEmphasisMark::Accent;
259 break;
262 if (eFontEmphasisMark != FontEmphasisMark::NONE)
264 DBG_ASSERT((pTCPP->getEmphasisMarkAbove() != pTCPP->getEmphasisMarkBelow()),
265 "DrawingLayer: Bad EmphasisMark position!");
266 if (pTCPP->getEmphasisMarkAbove())
267 eFontEmphasisMark |= FontEmphasisMark::PosAbove;
268 else
269 eFontEmphasisMark |= FontEmphasisMark::PosBelow;
270 aFont.SetEmphasisMark(eFontEmphasisMark);
273 // set Relief attribute
274 FontRelief eFontRelief = FontRelief::NONE;
275 switch (pTCPP->getTextRelief())
277 default:
278 SAL_WARN("drawinglayer", "Unknown Relief style " << pTCPP->getTextRelief());
279 [[fallthrough]];
280 case primitive2d::TEXT_RELIEF_NONE:
281 eFontRelief = FontRelief::NONE;
282 break;
283 case primitive2d::TEXT_RELIEF_EMBOSSED:
284 eFontRelief = FontRelief::Embossed;
285 break;
286 case primitive2d::TEXT_RELIEF_ENGRAVED:
287 eFontRelief = FontRelief::Engraved;
288 break;
291 if (eFontRelief != FontRelief::NONE)
292 aFont.SetRelief(eFontRelief);
294 // set Shadow attribute
295 if (pTCPP->getShadow())
296 aFont.SetShadow(true);
299 // create integer DXArray
300 KernArray aDXArray;
302 if (!rTextCandidate.getDXArray().empty())
304 double fPixelVectorFactor(1.0);
305 if (bScaleFont)
307 const basegfx::B2DVector aPixelVector(maCurrentTransformation
308 * basegfx::B2DVector(1.0, 0.0));
309 fPixelVectorFactor = aPixelVector.getLength();
312 aDXArray.reserve(rTextCandidate.getDXArray().size());
313 for (auto const& elem : rTextCandidate.getDXArray())
314 aDXArray.push_back(basegfx::fround(elem * fPixelVectorFactor));
317 // set parameters and paint text snippet
318 const basegfx::BColor aRGBFontColor(
319 maBColorModifierStack.getModifiedColor(rTextCandidate.getFontColor()));
320 const vcl::text::ComplexTextLayoutFlags nOldLayoutMode(mpOutputDevice->GetLayoutMode());
322 if (rTextCandidate.getFontAttribute().getRTL())
324 vcl::text::ComplexTextLayoutFlags nRTLLayoutMode(
325 nOldLayoutMode & ~vcl::text::ComplexTextLayoutFlags::BiDiStrong);
326 nRTLLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl
327 | vcl::text::ComplexTextLayoutFlags::TextOriginLeft;
328 mpOutputDevice->SetLayoutMode(nRTLLayoutMode);
331 mpOutputDevice->SetTextColor(Color(aRGBFontColor));
333 OUString aText(rTextCandidate.getText());
334 sal_Int32 nPos = rTextCandidate.getTextPosition();
335 sal_Int32 nLen = rTextCandidate.getTextLength();
337 // this contraption is used in editeng, with format paragraph used to
338 // set a tab with a tab-fill character
339 if (rTextCandidate.isFilled())
341 tools::Long nWidthToFill = rTextCandidate.getWidthToFill();
343 tools::Long nWidth
344 = mpOutputDevice->GetTextArray(rTextCandidate.getText(), &aDXArray, 0, 1);
345 sal_Int32 nChars = 2;
346 if (nWidth)
347 nChars = nWidthToFill / nWidth;
349 OUStringBuffer aFilled(nChars);
350 comphelper::string::padToLength(aFilled, nChars, aText[0]);
351 aText = aFilled.makeStringAndClear();
352 nPos = 0;
353 nLen = nChars;
355 if (!aDXArray.empty())
357 sal_Int32 nDX = aDXArray[0];
358 aDXArray.resize(nLen);
359 for (sal_Int32 i = 1; i < nLen; ++i)
360 aDXArray.set(i, aDXArray[i - 1] + nDX);
364 Point aStartPoint;
365 bool bChangeMapMode(false);
366 if (!bScaleFont)
368 basegfx::B2DHomMatrix aCombinedTransform(
369 getTransformFromMapMode(mpOutputDevice->GetMapMode())
370 * maCurrentTransformation);
372 basegfx::B2DVector aCurrentScaling, aCurrentTranslate;
373 double fCurrentRotate;
374 aCombinedTransform.decompose(aCurrentScaling, aCurrentTranslate, fCurrentRotate,
375 fIgnoreShearX);
377 const Point aOrigin(
378 basegfx::fround(aCurrentTranslate.getX() / aCurrentScaling.getX()),
379 basegfx::fround(aCurrentTranslate.getY() / aCurrentScaling.getY()));
381 Fraction aScaleX(aCurrentScaling.getX());
382 if (!aScaleX.IsValid())
384 SAL_WARN("drawinglayer", "invalid X Scale");
385 return;
388 Fraction aScaleY(aCurrentScaling.getY());
389 if (!aScaleY.IsValid())
391 SAL_WARN("drawinglayer", "invalid Y Scale");
392 return;
395 MapMode aMapMode(mpOutputDevice->GetMapMode().GetMapUnit(), aOrigin, aScaleX,
396 aScaleY);
398 if (fCurrentRotate)
399 aTextTranslate *= basegfx::utils::createRotateB2DHomMatrix(fCurrentRotate);
400 aStartPoint = Point(aTextTranslate.getX(), aTextTranslate.getY());
402 bChangeMapMode = aMapMode != mpOutputDevice->GetMapMode();
403 if (bChangeMapMode)
405 mpOutputDevice->Push(vcl::PushFlags::MAPMODE);
406 mpOutputDevice->SetRelativeMapMode(aMapMode);
409 else
411 const basegfx::B2DPoint aPoint(aLocalTransform * basegfx::B2DPoint(0.0, 0.0));
412 aStartPoint = Point(basegfx::fround(aPoint.getX()), basegfx::fround(aPoint.getY()));
415 // tdf#152990 set the font after the MapMode is (potentially) set so canvas uses the desired
416 // font size
417 mpOutputDevice->SetFont(aFont);
419 if (!aDXArray.empty())
421 const SalLayoutGlyphs* pGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
422 mpOutputDevice, aText, nPos, nLen);
423 mpOutputDevice->DrawTextArray(aStartPoint, aText, aDXArray,
424 rTextCandidate.getKashidaArray(), nPos, nLen,
425 SalLayoutFlags::NONE, pGlyphs);
427 else
429 mpOutputDevice->DrawText(aStartPoint, aText, nPos, nLen);
432 if (rTextCandidate.getFontAttribute().getRTL())
434 mpOutputDevice->SetLayoutMode(nOldLayoutMode);
437 if (bChangeMapMode)
438 mpOutputDevice->Pop();
440 bPrimitiveAccepted = true;
444 if (!bPrimitiveAccepted)
446 // let break down
447 process(rTextCandidate);
451 // direct draw of hairline
452 void VclProcessor2D::RenderPolygonHairlinePrimitive2D(
453 const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate, bool bPixelBased)
455 const basegfx::BColor aHairlineColor(
456 maBColorModifierStack.getModifiedColor(rPolygonCandidate.getBColor()));
457 mpOutputDevice->SetLineColor(Color(aHairlineColor));
458 mpOutputDevice->SetFillColor();
460 basegfx::B2DPolygon aLocalPolygon(rPolygonCandidate.getB2DPolygon());
461 aLocalPolygon.transform(maCurrentTransformation);
463 if (bPixelBased && getViewInformation2D().getPixelSnapHairline())
465 // #i98289#
466 // when a Hairline is painted and AntiAliasing is on the option SnapHorVerLinesToDiscrete
467 // allows to suppress AntiAliasing for pure horizontal or vertical lines. This is done since
468 // not-AntiAliased such lines look more pleasing to the eye (e.g. 2D chart content). This
469 // NEEDS to be done in discrete coordinates, so only useful for pixel based rendering.
470 aLocalPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aLocalPolygon);
473 mpOutputDevice->DrawPolyLine(aLocalPolygon, 0.0);
476 // direct draw of transformed BitmapEx primitive
477 void VclProcessor2D::RenderBitmapPrimitive2D(const primitive2d::BitmapPrimitive2D& rBitmapCandidate)
479 BitmapEx aBitmapEx(rBitmapCandidate.getBitmap());
480 const basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation
481 * rBitmapCandidate.getTransform());
483 if (maBColorModifierStack.count())
485 aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack);
487 if (aBitmapEx.IsEmpty())
489 // color gets completely replaced, get it
490 const basegfx::BColor aModifiedColor(
491 maBColorModifierStack.getModifiedColor(basegfx::BColor()));
492 basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon());
493 aPolygon.transform(aLocalTransform);
495 mpOutputDevice->SetFillColor(Color(aModifiedColor));
496 mpOutputDevice->SetLineColor();
497 mpOutputDevice->DrawPolygon(aPolygon);
499 return;
503 // #122923# do no longer add Alpha channel here; the right place to do this is when really
504 // the own transformer is used (see OutputDevice::DrawTransformedBitmapEx).
506 // draw using OutputDevice'sDrawTransformedBitmapEx
507 mpOutputDevice->DrawTransformedBitmapEx(aLocalTransform, aBitmapEx);
510 void VclProcessor2D::RenderFillGraphicPrimitive2D(
511 const primitive2d::FillGraphicPrimitive2D& rFillBitmapCandidate)
513 bool bPrimitiveAccepted = RenderFillGraphicPrimitive2DImpl(rFillBitmapCandidate);
515 if (!bPrimitiveAccepted)
517 // do not accept, use decomposition
518 process(rFillBitmapCandidate);
522 bool VclProcessor2D::RenderFillGraphicPrimitive2DImpl(
523 const primitive2d::FillGraphicPrimitive2D& rFillBitmapCandidate)
525 const attribute::FillGraphicAttribute& rFillGraphicAttribute(
526 rFillBitmapCandidate.getFillGraphic());
528 // #121194# when tiling is used and content is bitmap-based, do direct tiling in the
529 // renderer on pixel base to ensure tight fitting. Do not do this when
530 // the fill is rotated or sheared.
531 if (!rFillGraphicAttribute.getTiling())
532 return false;
534 // content is bitmap(ex)
536 // for Vector Graphic Data (SVG, EMF+) support, force decomposition when present. This will lead to use
537 // the primitive representation of the vector data directly.
539 // when graphic is animated, force decomposition to use the correct graphic, else
540 // fill style will not be animated
541 if (GraphicType::Bitmap != rFillGraphicAttribute.getGraphic().GetType()
542 || rFillGraphicAttribute.getGraphic().getVectorGraphicData()
543 || rFillGraphicAttribute.getGraphic().IsAnimated())
544 return false;
546 // decompose matrix to check for shear, rotate and mirroring
547 basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation
548 * rFillBitmapCandidate.getTransformation());
549 basegfx::B2DVector aScale, aTranslate;
550 double fRotate, fShearX;
551 aLocalTransform.decompose(aScale, aTranslate, fRotate, fShearX);
553 // when nopt rotated/sheared
554 if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX))
555 return false;
557 // no shear or rotate, draw direct in pixel coordinates
559 // transform object range to device coordinates (pixels). Use
560 // the device transformation for better accuracy
561 basegfx::B2DRange aObjectRange(aTranslate, aTranslate + aScale);
562 aObjectRange.transform(mpOutputDevice->GetViewTransformation());
564 // extract discrete size of object
565 const sal_Int32 nOWidth(basegfx::fround(aObjectRange.getWidth()));
566 const sal_Int32 nOHeight(basegfx::fround(aObjectRange.getHeight()));
568 // only do something when object has a size in discrete units
569 if (nOWidth <= 0 || nOHeight <= 0)
570 return true;
572 // transform graphic range to device coordinates (pixels). Use
573 // the device transformation for better accuracy
574 basegfx::B2DRange aGraphicRange(rFillGraphicAttribute.getGraphicRange());
575 aGraphicRange.transform(mpOutputDevice->GetViewTransformation() * aLocalTransform);
577 // extract discrete size of graphic
578 // caution: when getting to zero, nothing would be painted; thus, do not allow this
579 const sal_Int32 nBWidth(std::max(sal_Int32(1), basegfx::fround(aGraphicRange.getWidth())));
580 const sal_Int32 nBHeight(std::max(sal_Int32(1), basegfx::fround(aGraphicRange.getHeight())));
582 // nBWidth, nBHeight is the pixel size of the needed bitmap. To not need to scale it
583 // in vcl many times, create a size-optimized version
584 const Size aNeededBitmapSizePixel(nBWidth, nBHeight);
585 BitmapEx aBitmapEx(rFillGraphicAttribute.getGraphic().GetBitmapEx());
586 const bool bPreScaled(nBWidth * nBHeight < (250 * 250));
588 // ... but only up to a maximum size, else it gets too expensive
589 if (bPreScaled)
591 // if color depth is below 24bit, expand before scaling for better quality.
592 // This is even needed for low colors, else the scale will produce
593 // a bitmap in gray or Black/White (!)
594 if (isPalettePixelFormat(aBitmapEx.getPixelFormat()))
596 aBitmapEx.Convert(BmpConversion::N24Bit);
599 aBitmapEx.Scale(aNeededBitmapSizePixel, BmpScaleFlag::Interpolate);
602 if (maBColorModifierStack.count())
604 // when color modifier, apply to bitmap
605 aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack);
607 // ModifyBitmapEx uses empty bitmap as sign to return that
608 // the content will be completely replaced to mono color, use shortcut
609 if (aBitmapEx.IsEmpty())
611 // color gets completely replaced, get it
612 const basegfx::BColor aModifiedColor(
613 maBColorModifierStack.getModifiedColor(basegfx::BColor()));
614 basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon());
615 aPolygon.transform(aLocalTransform);
617 mpOutputDevice->SetFillColor(Color(aModifiedColor));
618 mpOutputDevice->SetLineColor();
619 mpOutputDevice->DrawPolygon(aPolygon);
621 return true;
625 sal_Int32 nBLeft(basegfx::fround(aGraphicRange.getMinX()));
626 sal_Int32 nBTop(basegfx::fround(aGraphicRange.getMinY()));
627 const sal_Int32 nOLeft(basegfx::fround(aObjectRange.getMinX()));
628 const sal_Int32 nOTop(basegfx::fround(aObjectRange.getMinY()));
629 sal_Int32 nPosX(0);
630 sal_Int32 nPosY(0);
632 if (nBLeft > nOLeft)
634 const sal_Int32 nDiff((nBLeft / nBWidth) + 1);
636 nPosX -= nDiff;
637 nBLeft -= nDiff * nBWidth;
640 if (nBLeft + nBWidth <= nOLeft)
642 const sal_Int32 nDiff(-nBLeft / nBWidth);
644 nPosX += nDiff;
645 nBLeft += nDiff * nBWidth;
648 if (nBTop > nOTop)
650 const sal_Int32 nDiff((nBTop / nBHeight) + 1);
652 nPosY -= nDiff;
653 nBTop -= nDiff * nBHeight;
656 if (nBTop + nBHeight <= nOTop)
658 const sal_Int32 nDiff(-nBTop / nBHeight);
660 nPosY += nDiff;
661 nBTop += nDiff * nBHeight;
664 // prepare OutDev
665 const Point aEmptyPoint(0, 0);
666 // the visible rect, in pixels
667 const ::tools::Rectangle aVisiblePixel(aEmptyPoint, mpOutputDevice->GetOutputSizePixel());
668 const bool bWasEnabled(mpOutputDevice->IsMapModeEnabled());
669 mpOutputDevice->EnableMapMode(false);
671 // check if offset is used
672 const sal_Int32 nOffsetX(basegfx::fround(rFillGraphicAttribute.getOffsetX() * nBWidth));
674 if (nOffsetX)
676 // offset in X, so iterate over Y first and draw lines
677 for (sal_Int32 nYPos(nBTop); nYPos < nOTop + nOHeight; nYPos += nBHeight, nPosY++)
679 for (sal_Int32 nXPos((nPosY % 2) ? nBLeft - nBWidth + nOffsetX : nBLeft);
680 nXPos < nOLeft + nOWidth; nXPos += nBWidth)
682 const ::tools::Rectangle aOutRectPixel(Point(nXPos, nYPos), aNeededBitmapSizePixel);
684 if (aOutRectPixel.Overlaps(aVisiblePixel))
686 if (bPreScaled)
688 mpOutputDevice->DrawBitmapEx(aOutRectPixel.TopLeft(), aBitmapEx);
690 else
692 mpOutputDevice->DrawBitmapEx(aOutRectPixel.TopLeft(),
693 aNeededBitmapSizePixel, aBitmapEx);
699 else
701 // check if offset is used
702 const sal_Int32 nOffsetY(basegfx::fround(rFillGraphicAttribute.getOffsetY() * nBHeight));
704 // possible offset in Y, so iterate over X first and draw columns
705 for (sal_Int32 nXPos(nBLeft); nXPos < nOLeft + nOWidth; nXPos += nBWidth, nPosX++)
707 for (sal_Int32 nYPos((nPosX % 2) ? nBTop - nBHeight + nOffsetY : nBTop);
708 nYPos < nOTop + nOHeight; nYPos += nBHeight)
710 const ::tools::Rectangle aOutRectPixel(Point(nXPos, nYPos), aNeededBitmapSizePixel);
712 if (aOutRectPixel.Overlaps(aVisiblePixel))
714 if (bPreScaled)
716 mpOutputDevice->DrawBitmapEx(aOutRectPixel.TopLeft(), aBitmapEx);
718 else
720 mpOutputDevice->DrawBitmapEx(aOutRectPixel.TopLeft(),
721 aNeededBitmapSizePixel, aBitmapEx);
728 // restore OutDev
729 mpOutputDevice->EnableMapMode(bWasEnabled);
730 return true;
733 // direct draw of Graphic
734 void VclProcessor2D::RenderPolyPolygonGraphicPrimitive2D(
735 const primitive2d::PolyPolygonGraphicPrimitive2D& rPolygonCandidate)
737 bool bDone(false);
738 const basegfx::B2DPolyPolygon& rPolyPolygon = rPolygonCandidate.getB2DPolyPolygon();
740 // #121194# Todo: check if this works
741 if (!rPolyPolygon.count())
743 // empty polyPolygon, done
744 bDone = true;
746 else
748 const attribute::FillGraphicAttribute& rFillGraphicAttribute
749 = rPolygonCandidate.getFillGraphic();
751 // try to catch cases where the graphic will be color-modified to a single
752 // color (e.g. shadow)
753 switch (rFillGraphicAttribute.getGraphic().GetType())
755 case GraphicType::GdiMetafile:
757 // metafiles are potentially transparent, cannot optimize, not done
758 break;
760 case GraphicType::Bitmap:
762 if (!rFillGraphicAttribute.getGraphic().IsTransparent()
763 && !rFillGraphicAttribute.getGraphic().IsAlpha())
765 // bitmap is not transparent and has no alpha
766 const sal_uInt32 nBColorModifierStackCount(maBColorModifierStack.count());
768 if (nBColorModifierStackCount)
770 const basegfx::BColorModifierSharedPtr& rTopmostModifier
771 = maBColorModifierStack.getBColorModifier(nBColorModifierStackCount
772 - 1);
773 const basegfx::BColorModifier_replace* pReplacer
774 = dynamic_cast<const basegfx::BColorModifier_replace*>(
775 rTopmostModifier.get());
777 if (pReplacer)
779 // the bitmap fill is in unified color, so we can replace it with
780 // a single polygon fill. The form of the fill depends on tiling
781 if (rFillGraphicAttribute.getTiling())
783 // with tiling, fill the whole tools::PolyPolygon with the modifier color
784 basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolyPolygon);
786 aLocalPolyPolygon.transform(maCurrentTransformation);
787 mpOutputDevice->SetLineColor();
788 mpOutputDevice->SetFillColor(Color(pReplacer->getBColor()));
789 mpOutputDevice->DrawPolyPolygon(aLocalPolyPolygon);
791 else
793 // without tiling, only the area common to the bitmap tile and the
794 // tools::PolyPolygon is filled. Create the bitmap tile area in object
795 // coordinates. For this, the object transformation needs to be created
796 // from the already scaled PolyPolygon. The tile area in object
797 // coordinates will always be non-rotated, so it's not necessary to
798 // work with a polygon here
799 basegfx::B2DRange aTileRange(
800 rFillGraphicAttribute.getGraphicRange());
801 const basegfx::B2DRange aPolyPolygonRange(
802 rPolyPolygon.getB2DRange());
803 const basegfx::B2DHomMatrix aNewObjectTransform(
804 basegfx::utils::createScaleTranslateB2DHomMatrix(
805 aPolyPolygonRange.getRange(),
806 aPolyPolygonRange.getMinimum()));
808 aTileRange.transform(aNewObjectTransform);
810 // now clip the object polyPolygon against the tile range
811 // to get the common area
812 basegfx::B2DPolyPolygon aTarget
813 = basegfx::utils::clipPolyPolygonOnRange(
814 rPolyPolygon, aTileRange, true, false);
816 if (aTarget.count())
818 aTarget.transform(maCurrentTransformation);
819 mpOutputDevice->SetLineColor();
820 mpOutputDevice->SetFillColor(Color(pReplacer->getBColor()));
821 mpOutputDevice->DrawPolyPolygon(aTarget);
825 // simplified output executed, we are done
826 bDone = true;
830 break;
832 default: //GraphicType::NONE, GraphicType::Default
834 // empty graphic, we are done
835 bDone = true;
836 break;
841 if (!bDone)
843 // use default decomposition
844 process(rPolygonCandidate);
848 // mask group
849 void VclProcessor2D::RenderMaskPrimitive2DPixel(const primitive2d::MaskPrimitive2D& rMaskCandidate)
851 if (rMaskCandidate.getChildren().empty())
852 return;
854 basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask());
856 if (!aMask.count())
857 return;
859 aMask.transform(maCurrentTransformation);
861 // Unless smooth edges are needed, simply use clipping.
862 if (basegfx::utils::isRectangle(aMask) || !getViewInformation2D().getUseAntiAliasing())
864 mpOutputDevice->Push(vcl::PushFlags::CLIPREGION);
865 mpOutputDevice->IntersectClipRegion(vcl::Region(aMask));
866 process(rMaskCandidate.getChildren());
867 mpOutputDevice->Pop();
868 return;
871 const basegfx::B2DRange aRange(basegfx::utils::getRange(aMask));
872 impBufferDevice aBufferDevice(*mpOutputDevice, aRange);
874 if (!aBufferDevice.isVisible())
875 return;
877 // remember last OutDev and set to content
878 OutputDevice* pLastOutputDevice = mpOutputDevice;
879 mpOutputDevice = &aBufferDevice.getContent();
881 // paint to it
882 process(rMaskCandidate.getChildren());
884 // back to old OutDev
885 mpOutputDevice = pLastOutputDevice;
887 // draw mask
888 VirtualDevice& rMask = aBufferDevice.getTransparence();
889 rMask.SetLineColor();
890 rMask.SetFillColor(COL_BLACK);
891 rMask.DrawPolyPolygon(aMask);
893 // dump buffer to outdev
894 aBufferDevice.paint();
897 // modified color group. Force output to unified color.
898 void VclProcessor2D::RenderModifiedColorPrimitive2D(
899 const primitive2d::ModifiedColorPrimitive2D& rModifiedCandidate)
901 if (!rModifiedCandidate.getChildren().empty())
903 maBColorModifierStack.push(rModifiedCandidate.getColorModifier());
904 process(rModifiedCandidate.getChildren());
905 maBColorModifierStack.pop();
909 // unified sub-transparence. Draw to VDev first.
910 void VclProcessor2D::RenderUnifiedTransparencePrimitive2D(
911 const primitive2d::UnifiedTransparencePrimitive2D& rTransCandidate)
913 if (rTransCandidate.getChildren().empty())
914 return;
916 if (0.0 == rTransCandidate.getTransparence())
918 // no transparence used, so just use the content
919 process(rTransCandidate.getChildren());
921 else if (rTransCandidate.getTransparence() > 0.0 && rTransCandidate.getTransparence() < 1.0)
923 // transparence is in visible range
924 basegfx::B2DRange aRange(rTransCandidate.getChildren().getB2DRange(getViewInformation2D()));
925 aRange.transform(maCurrentTransformation);
926 impBufferDevice aBufferDevice(*mpOutputDevice, aRange);
928 if (aBufferDevice.isVisible())
930 // remember last OutDev and set to content
931 OutputDevice* pLastOutputDevice = mpOutputDevice;
932 mpOutputDevice = &aBufferDevice.getContent();
934 // paint content to it
935 process(rTransCandidate.getChildren());
937 // back to old OutDev
938 mpOutputDevice = pLastOutputDevice;
940 // dump buffer to outdev using given transparence
941 aBufferDevice.paint(rTransCandidate.getTransparence());
946 // sub-transparence group. Draw to VDev first.
947 void VclProcessor2D::RenderTransparencePrimitive2D(
948 const primitive2d::TransparencePrimitive2D& rTransCandidate)
950 if (rTransCandidate.getChildren().empty())
951 return;
953 basegfx::B2DRange aRange(rTransCandidate.getChildren().getB2DRange(getViewInformation2D()));
954 aRange.transform(maCurrentTransformation);
955 impBufferDevice aBufferDevice(*mpOutputDevice, aRange);
957 if (!aBufferDevice.isVisible())
958 return;
960 // remember last OutDev and set to content
961 OutputDevice* pLastOutputDevice = mpOutputDevice;
962 mpOutputDevice = &aBufferDevice.getContent();
964 // paint content to it
965 process(rTransCandidate.getChildren());
967 // set to mask
968 mpOutputDevice = &aBufferDevice.getTransparence();
970 // when painting transparence masks, reset the color stack
971 basegfx::BColorModifierStack aLastBColorModifierStack(maBColorModifierStack);
972 maBColorModifierStack = basegfx::BColorModifierStack();
974 // paint mask to it (always with transparence intensities, evtl. with AA)
975 process(rTransCandidate.getTransparence());
977 // back to old color stack
978 maBColorModifierStack = aLastBColorModifierStack;
980 // back to old OutDev
981 mpOutputDevice = pLastOutputDevice;
983 // dump buffer to outdev
984 aBufferDevice.paint();
987 // transform group.
988 void VclProcessor2D::RenderTransformPrimitive2D(
989 const primitive2d::TransformPrimitive2D& rTransformCandidate)
991 // remember current transformation and ViewInformation
992 const basegfx::B2DHomMatrix aLastCurrentTransformation(maCurrentTransformation);
993 const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
995 // create new transformations for CurrentTransformation
996 // and for local ViewInformation2D
997 maCurrentTransformation = maCurrentTransformation * rTransformCandidate.getTransformation();
998 geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
999 aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation()
1000 * rTransformCandidate.getTransformation());
1001 updateViewInformation(aViewInformation2D);
1003 // process content
1004 process(rTransformCandidate.getChildren());
1006 // restore transformations
1007 maCurrentTransformation = aLastCurrentTransformation;
1008 updateViewInformation(aLastViewInformation2D);
1011 // new XDrawPage for ViewInformation2D
1012 void VclProcessor2D::RenderPagePreviewPrimitive2D(
1013 const primitive2d::PagePreviewPrimitive2D& rPagePreviewCandidate)
1015 // remember current transformation and ViewInformation
1016 const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
1018 // create new local ViewInformation2D
1019 geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
1020 aViewInformation2D.setVisualizedPage(rPagePreviewCandidate.getXDrawPage());
1021 updateViewInformation(aViewInformation2D);
1023 // process decomposed content
1024 process(rPagePreviewCandidate);
1026 // restore transformations
1027 updateViewInformation(aLastViewInformation2D);
1030 // marker
1031 void VclProcessor2D::RenderMarkerArrayPrimitive2D(
1032 const primitive2d::MarkerArrayPrimitive2D& rMarkArrayCandidate)
1034 // get data
1035 const std::vector<basegfx::B2DPoint>& rPositions = rMarkArrayCandidate.getPositions();
1036 const sal_uInt32 nCount(rPositions.size());
1038 if (!nCount || rMarkArrayCandidate.getMarker().IsEmpty())
1039 return;
1041 // get pixel size
1042 const BitmapEx& rMarker(rMarkArrayCandidate.getMarker());
1043 const Size aBitmapSize(rMarker.GetSizePixel());
1045 if (!(aBitmapSize.Width() && aBitmapSize.Height()))
1046 return;
1048 // get discrete half size
1049 const basegfx::B2DVector aDiscreteHalfSize((aBitmapSize.getWidth() - 1.0) * 0.5,
1050 (aBitmapSize.getHeight() - 1.0) * 0.5);
1051 const bool bWasEnabled(mpOutputDevice->IsMapModeEnabled());
1053 // do not forget evtl. moved origin in target device MapMode when
1054 // switching it off; it would be missing and lead to wrong positions.
1055 // All his could be done using logic sizes and coordinates, too, but
1056 // we want a 1:1 bitmap rendering here, so it's more safe and faster
1057 // to work with switching off MapMode usage completely.
1058 const Point aOrigin(mpOutputDevice->GetMapMode().GetOrigin());
1060 mpOutputDevice->EnableMapMode(false);
1062 for (auto const& pos : rPositions)
1064 const basegfx::B2DPoint aDiscreteTopLeft((maCurrentTransformation * pos)
1065 - aDiscreteHalfSize);
1066 const Point aDiscretePoint(basegfx::fround(aDiscreteTopLeft.getX()),
1067 basegfx::fround(aDiscreteTopLeft.getY()));
1069 mpOutputDevice->DrawBitmapEx(aDiscretePoint + aOrigin, rMarker);
1072 mpOutputDevice->EnableMapMode(bWasEnabled);
1075 // point
1076 void VclProcessor2D::RenderPointArrayPrimitive2D(
1077 const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate)
1079 const std::vector<basegfx::B2DPoint>& rPositions = rPointArrayCandidate.getPositions();
1080 const basegfx::BColor aRGBColor(
1081 maBColorModifierStack.getModifiedColor(rPointArrayCandidate.getRGBColor()));
1082 const Color aVCLColor(aRGBColor);
1084 for (auto const& pos : rPositions)
1086 const basegfx::B2DPoint aViewPosition(maCurrentTransformation * pos);
1087 const Point aPos(basegfx::fround(aViewPosition.getX()),
1088 basegfx::fround(aViewPosition.getY()));
1090 mpOutputDevice->DrawPixel(aPos, aVCLColor);
1094 void VclProcessor2D::RenderPolygonStrokePrimitive2D(
1095 const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate)
1097 // #i101491# method restructured to clearly use the DrawPolyLine
1098 // calls starting from a defined line width
1099 const attribute::LineAttribute& rLineAttribute = rPolygonStrokeCandidate.getLineAttribute();
1100 const double fLineWidth(rLineAttribute.getWidth());
1101 bool bDone(false);
1103 if (basegfx::fTools::more(fLineWidth, 0.0))
1105 const basegfx::B2DVector aDiscreteUnit(maCurrentTransformation
1106 * basegfx::B2DVector(fLineWidth, 0.0));
1107 const double fDiscreteLineWidth(aDiscreteUnit.getLength());
1108 const attribute::StrokeAttribute& rStrokeAttribute
1109 = rPolygonStrokeCandidate.getStrokeAttribute();
1110 const basegfx::BColor aHairlineColor(
1111 maBColorModifierStack.getModifiedColor(rLineAttribute.getColor()));
1112 basegfx::B2DPolyPolygon aHairlinePolyPolygon;
1114 mpOutputDevice->SetLineColor(Color(aHairlineColor));
1115 mpOutputDevice->SetFillColor();
1117 if (0.0 == rStrokeAttribute.getFullDotDashLen())
1119 // no line dashing, just copy
1120 aHairlinePolyPolygon.append(rPolygonStrokeCandidate.getB2DPolygon());
1122 else
1124 // else apply LineStyle
1125 basegfx::utils::applyLineDashing(
1126 rPolygonStrokeCandidate.getB2DPolygon(), rStrokeAttribute.getDotDashArray(),
1127 &aHairlinePolyPolygon, nullptr, rStrokeAttribute.getFullDotDashLen());
1130 const sal_uInt32 nCount(aHairlinePolyPolygon.count());
1132 if (nCount)
1134 const bool bAntiAliased(getViewInformation2D().getUseAntiAliasing());
1135 aHairlinePolyPolygon.transform(maCurrentTransformation);
1137 if (bAntiAliased)
1139 if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 1.0))
1141 // line in range ]0.0 .. 1.0[
1142 // paint as simple hairline
1143 for (sal_uInt32 a(0); a < nCount; a++)
1145 mpOutputDevice->DrawPolyLine(aHairlinePolyPolygon.getB2DPolygon(a), 0.0);
1148 bDone = true;
1150 else if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 2.0))
1152 // line in range [1.0 .. 2.0[
1153 // paint as 2x2 with dynamic line distance
1154 basegfx::B2DHomMatrix aMat;
1155 const double fDistance(fDiscreteLineWidth - 1.0);
1156 const double fHalfDistance(fDistance * 0.5);
1158 for (sal_uInt32 a(0); a < nCount; a++)
1160 basegfx::B2DPolygon aCandidate(aHairlinePolyPolygon.getB2DPolygon(a));
1162 aMat.set(0, 2, -fHalfDistance);
1163 aMat.set(1, 2, -fHalfDistance);
1164 aCandidate.transform(aMat);
1165 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1167 aMat.set(0, 2, fDistance);
1168 aMat.set(1, 2, 0.0);
1169 aCandidate.transform(aMat);
1170 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1172 aMat.set(0, 2, 0.0);
1173 aMat.set(1, 2, fDistance);
1174 aCandidate.transform(aMat);
1175 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1177 aMat.set(0, 2, -fDistance);
1178 aMat.set(1, 2, 0.0);
1179 aCandidate.transform(aMat);
1180 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1183 bDone = true;
1185 else if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 3.0))
1187 // line in range [2.0 .. 3.0]
1188 // paint as cross in a 3x3 with dynamic line distance
1189 basegfx::B2DHomMatrix aMat;
1190 const double fDistance((fDiscreteLineWidth - 1.0) * 0.5);
1192 for (sal_uInt32 a(0); a < nCount; a++)
1194 basegfx::B2DPolygon aCandidate(aHairlinePolyPolygon.getB2DPolygon(a));
1196 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1198 aMat.set(0, 2, -fDistance);
1199 aMat.set(1, 2, 0.0);
1200 aCandidate.transform(aMat);
1201 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1203 aMat.set(0, 2, fDistance);
1204 aMat.set(1, 2, -fDistance);
1205 aCandidate.transform(aMat);
1206 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1208 aMat.set(0, 2, fDistance);
1209 aMat.set(1, 2, fDistance);
1210 aCandidate.transform(aMat);
1211 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1213 aMat.set(0, 2, -fDistance);
1214 aMat.set(1, 2, fDistance);
1215 aCandidate.transform(aMat);
1216 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1219 bDone = true;
1221 else
1223 // #i101491# line width above 3.0
1226 else
1228 if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 1.5))
1230 // line width below 1.5, draw the basic hairline polygon
1231 for (sal_uInt32 a(0); a < nCount; a++)
1233 mpOutputDevice->DrawPolyLine(aHairlinePolyPolygon.getB2DPolygon(a), 0.0);
1236 bDone = true;
1238 else if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 2.5))
1240 // line width is in range ]1.5 .. 2.5], use four hairlines
1241 // drawn in a square
1242 for (sal_uInt32 a(0); a < nCount; a++)
1244 basegfx::B2DPolygon aCandidate(aHairlinePolyPolygon.getB2DPolygon(a));
1245 basegfx::B2DHomMatrix aMat;
1247 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1249 aMat.set(0, 2, 1.0);
1250 aMat.set(1, 2, 0.0);
1251 aCandidate.transform(aMat);
1253 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1255 aMat.set(0, 2, 0.0);
1256 aMat.set(1, 2, 1.0);
1257 aCandidate.transform(aMat);
1259 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1261 aMat.set(0, 2, -1.0);
1262 aMat.set(1, 2, 0.0);
1263 aCandidate.transform(aMat);
1265 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1268 bDone = true;
1270 else
1272 // #i101491# line width is above 2.5
1276 if (!bDone && rPolygonStrokeCandidate.getB2DPolygon().count() > 1000)
1278 // #i101491# If the polygon complexity uses more than a given amount, do
1279 // use OutputDevice::DrawPolyLine directly; this will avoid buffering all
1280 // decompositions in primitives (memory) and fallback to old line painting
1281 // for very complex polygons, too
1282 for (sal_uInt32 a(0); a < nCount; a++)
1284 mpOutputDevice->DrawPolyLine(aHairlinePolyPolygon.getB2DPolygon(a),
1285 fDiscreteLineWidth, rLineAttribute.getLineJoin(),
1286 rLineAttribute.getLineCap(),
1287 rLineAttribute.getMiterMinimumAngle());
1290 bDone = true;
1295 if (!bDone)
1297 // remember that we enter a PolygonStrokePrimitive2D decomposition,
1298 // used for AA thick line drawing
1299 mnPolygonStrokePrimitive2D++;
1301 // line width is big enough for standard filled polygon visualisation or zero
1302 process(rPolygonStrokeCandidate);
1304 // leave PolygonStrokePrimitive2D
1305 mnPolygonStrokePrimitive2D--;
1309 void VclProcessor2D::RenderEpsPrimitive2D(const primitive2d::EpsPrimitive2D& rEpsPrimitive2D)
1311 // The new decomposition of Metafiles made it necessary to add an Eps
1312 // primitive to handle embedded Eps data. On some devices, this can be
1313 // painted directly (mac, printer).
1314 // To be able to handle the replacement correctly, i need to handle it myself
1315 // since DrawEPS will not be able e.g. to rotate the replacement. To be able
1316 // to do that, i added a boolean return to OutputDevice::DrawEPS(..)
1317 // to know when EPS was handled directly already.
1318 basegfx::B2DRange aRange(0.0, 0.0, 1.0, 1.0);
1319 aRange.transform(maCurrentTransformation * rEpsPrimitive2D.getEpsTransform());
1321 if (aRange.isEmpty())
1322 return;
1324 const ::tools::Rectangle aRectangle(static_cast<sal_Int32>(floor(aRange.getMinX())),
1325 static_cast<sal_Int32>(floor(aRange.getMinY())),
1326 static_cast<sal_Int32>(ceil(aRange.getMaxX())),
1327 static_cast<sal_Int32>(ceil(aRange.getMaxY())));
1329 if (aRectangle.IsEmpty())
1330 return;
1332 bool bWillReallyRender = mpOutputDevice->IsDeviceOutputNecessary();
1333 // try to paint EPS directly without fallback visualisation
1334 const bool bEPSPaintedDirectly
1335 = bWillReallyRender
1336 && mpOutputDevice->DrawEPS(aRectangle.TopLeft(), aRectangle.GetSize(),
1337 rEpsPrimitive2D.getGfxLink());
1339 if (!bEPSPaintedDirectly)
1341 // use the decomposition which will correctly handle the
1342 // fallback visualisation using full transformation (e.g. rotation)
1343 process(rEpsPrimitive2D);
1347 void VclProcessor2D::RenderSvgLinearAtomPrimitive2D(
1348 const primitive2d::SvgLinearAtomPrimitive2D& rCandidate)
1350 const double fDelta(rCandidate.getOffsetB() - rCandidate.getOffsetA());
1352 if (!basegfx::fTools::more(fDelta, 0.0))
1353 return;
1355 const basegfx::BColor aColorA(maBColorModifierStack.getModifiedColor(rCandidate.getColorA()));
1356 const basegfx::BColor aColorB(maBColorModifierStack.getModifiedColor(rCandidate.getColorB()));
1358 // calculate discrete unit in WorldCoordinates; use diagonal (1.0, 1.0) and divide by sqrt(2)
1359 const basegfx::B2DVector aDiscreteVector(
1360 getViewInformation2D().getInverseObjectToViewTransformation()
1361 * basegfx::B2DVector(1.0, 1.0));
1362 const double fDiscreteUnit(aDiscreteVector.getLength() * (1.0 / M_SQRT2));
1364 // use color distance and discrete lengths to calculate step count
1365 const sal_uInt32 nSteps(calculateStepsForSvgGradient(aColorA, aColorB, fDelta, fDiscreteUnit));
1367 // switch off line painting
1368 mpOutputDevice->SetLineColor();
1370 // prepare polygon in needed width at start position (with discrete overlap)
1371 const basegfx::B2DPolygon aPolygon(basegfx::utils::createPolygonFromRect(
1372 basegfx::B2DRange(rCandidate.getOffsetA() - fDiscreteUnit, 0.0,
1373 rCandidate.getOffsetA() + (fDelta / nSteps) + fDiscreteUnit, 1.0)));
1375 // prepare loop ([0.0 .. 1.0[)
1376 double fUnitScale(0.0);
1377 const double fUnitStep(1.0 / nSteps);
1379 // loop and paint
1380 for (sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep)
1382 basegfx::B2DPolygon aNew(aPolygon);
1384 aNew.transform(maCurrentTransformation
1385 * basegfx::utils::createTranslateB2DHomMatrix(fDelta * fUnitScale, 0.0));
1386 mpOutputDevice->SetFillColor(Color(basegfx::interpolate(aColorA, aColorB, fUnitScale)));
1387 mpOutputDevice->DrawPolyPolygon(basegfx::B2DPolyPolygon(aNew));
1391 void VclProcessor2D::RenderSvgRadialAtomPrimitive2D(
1392 const primitive2d::SvgRadialAtomPrimitive2D& rCandidate)
1394 const double fDeltaScale(rCandidate.getScaleB() - rCandidate.getScaleA());
1396 if (!basegfx::fTools::more(fDeltaScale, 0.0))
1397 return;
1399 const basegfx::BColor aColorA(maBColorModifierStack.getModifiedColor(rCandidate.getColorA()));
1400 const basegfx::BColor aColorB(maBColorModifierStack.getModifiedColor(rCandidate.getColorB()));
1402 // calculate discrete unit in WorldCoordinates; use diagonal (1.0, 1.0) and divide by sqrt(2)
1403 const basegfx::B2DVector aDiscreteVector(
1404 getViewInformation2D().getInverseObjectToViewTransformation()
1405 * basegfx::B2DVector(1.0, 1.0));
1406 const double fDiscreteUnit(aDiscreteVector.getLength() * (1.0 / M_SQRT2));
1408 // use color distance and discrete lengths to calculate step count
1409 const sal_uInt32 nSteps(
1410 calculateStepsForSvgGradient(aColorA, aColorB, fDeltaScale, fDiscreteUnit));
1412 // switch off line painting
1413 mpOutputDevice->SetLineColor();
1415 // prepare loop ([0.0 .. 1.0[, full polygons, no polypolygons with holes)
1416 double fUnitScale(0.0);
1417 const double fUnitStep(1.0 / nSteps);
1419 for (sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep)
1421 basegfx::B2DHomMatrix aTransform;
1422 const double fEndScale(rCandidate.getScaleB() - (fDeltaScale * fUnitScale));
1424 if (rCandidate.isTranslateSet())
1426 const basegfx::B2DVector aTranslate(basegfx::interpolate(
1427 rCandidate.getTranslateB(), rCandidate.getTranslateA(), fUnitScale));
1429 aTransform = basegfx::utils::createScaleTranslateB2DHomMatrix(
1430 fEndScale, fEndScale, aTranslate.getX(), aTranslate.getY());
1432 else
1434 aTransform = basegfx::utils::createScaleB2DHomMatrix(fEndScale, fEndScale);
1437 basegfx::B2DPolygon aNew(basegfx::utils::createPolygonFromUnitCircle());
1439 aNew.transform(maCurrentTransformation * aTransform);
1440 mpOutputDevice->SetFillColor(Color(basegfx::interpolate(aColorB, aColorA, fUnitScale)));
1441 mpOutputDevice->DrawPolyPolygon(basegfx::B2DPolyPolygon(aNew));
1445 void VclProcessor2D::adaptLineToFillDrawMode() const
1447 const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
1449 if (!(nOriginalDrawMode
1450 & (DrawModeFlags::BlackLine | DrawModeFlags::GrayLine | DrawModeFlags::WhiteLine
1451 | DrawModeFlags::SettingsLine)))
1452 return;
1454 DrawModeFlags nAdaptedDrawMode(nOriginalDrawMode);
1456 if (nOriginalDrawMode & DrawModeFlags::BlackLine)
1458 nAdaptedDrawMode |= DrawModeFlags::BlackFill;
1460 else
1462 nAdaptedDrawMode &= ~DrawModeFlags::BlackFill;
1465 if (nOriginalDrawMode & DrawModeFlags::GrayLine)
1467 nAdaptedDrawMode |= DrawModeFlags::GrayFill;
1469 else
1471 nAdaptedDrawMode &= ~DrawModeFlags::GrayFill;
1474 if (nOriginalDrawMode & DrawModeFlags::WhiteLine)
1476 nAdaptedDrawMode |= DrawModeFlags::WhiteFill;
1478 else
1480 nAdaptedDrawMode &= ~DrawModeFlags::WhiteFill;
1483 if (nOriginalDrawMode & DrawModeFlags::SettingsLine)
1485 nAdaptedDrawMode |= DrawModeFlags::SettingsFill;
1487 else
1489 nAdaptedDrawMode &= ~DrawModeFlags::SettingsFill;
1492 mpOutputDevice->SetDrawMode(nAdaptedDrawMode);
1495 void VclProcessor2D::adaptTextToFillDrawMode() const
1497 const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
1498 if (!(nOriginalDrawMode
1499 & (DrawModeFlags::BlackText | DrawModeFlags::GrayText | DrawModeFlags::WhiteText
1500 | DrawModeFlags::SettingsText)))
1501 return;
1503 DrawModeFlags nAdaptedDrawMode(nOriginalDrawMode);
1505 if (nOriginalDrawMode & DrawModeFlags::BlackText)
1507 nAdaptedDrawMode |= DrawModeFlags::BlackFill;
1509 else
1511 nAdaptedDrawMode &= ~DrawModeFlags::BlackFill;
1514 if (nOriginalDrawMode & DrawModeFlags::GrayText)
1516 nAdaptedDrawMode |= DrawModeFlags::GrayFill;
1518 else
1520 nAdaptedDrawMode &= ~DrawModeFlags::GrayFill;
1523 if (nOriginalDrawMode & DrawModeFlags::WhiteText)
1525 nAdaptedDrawMode |= DrawModeFlags::WhiteFill;
1527 else
1529 nAdaptedDrawMode &= ~DrawModeFlags::WhiteFill;
1532 if (nOriginalDrawMode & DrawModeFlags::SettingsText)
1534 nAdaptedDrawMode |= DrawModeFlags::SettingsFill;
1536 else
1538 nAdaptedDrawMode &= ~DrawModeFlags::SettingsFill;
1541 mpOutputDevice->SetDrawMode(nAdaptedDrawMode);
1544 // process support
1546 VclProcessor2D::VclProcessor2D(const geometry::ViewInformation2D& rViewInformation,
1547 OutputDevice& rOutDev, basegfx::BColorModifierStack aInitStack)
1548 : BaseProcessor2D(rViewInformation)
1549 , mpOutputDevice(&rOutDev)
1550 , maBColorModifierStack(std::move(aInitStack))
1551 , mnPolygonStrokePrimitive2D(0)
1553 // set digit language, derived from SvtCTLOptions to have the correct
1554 // number display for arabic/hindi numerals
1555 rOutDev.SetDigitLanguage(drawinglayer::detail::getDigitLanguage());
1558 VclProcessor2D::~VclProcessor2D() {}
1561 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */