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
