1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 #include "precompiled_svx.hxx" 25 #include <svx/sdr/primitive2d/sdrdecompositiontools.hxx> 26 #include <drawinglayer/primitive2d/baseprimitive2d.hxx> 27 #include <drawinglayer/primitive2d/polypolygonprimitive2d.hxx> 28 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> 29 #include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> 30 #include <basegfx/polygon/b2dpolypolygontools.hxx> 31 #include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx> 32 #include <drawinglayer/attribute/strokeattribute.hxx> 33 #include <drawinglayer/attribute/linestartendattribute.hxx> 34 #include <drawinglayer/primitive2d/polygonprimitive2d.hxx> 35 #include <drawinglayer/attribute/sdrfillgraphicattribute.hxx> 36 #include <basegfx/matrix/b2dhommatrix.hxx> 37 #include <drawinglayer/primitive2d/shadowprimitive2d.hxx> 38 #include <svx/sdr/attribute/sdrtextattribute.hxx> 39 #include <svx/sdr/primitive2d/sdrtextprimitive2d.hxx> 40 #include <svx/svdotext.hxx> 41 #include <basegfx/polygon/b2dpolygontools.hxx> 42 #include <drawinglayer/primitive2d/animatedprimitive2d.hxx> 43 #include <drawinglayer/animation/animationtiming.hxx> 44 #include <drawinglayer/primitive2d/maskprimitive2d.hxx> 45 #include <basegfx/tools/canvastools.hxx> 46 #include <drawinglayer/geometry/viewinformation2d.hxx> 47 #include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx> 48 #include <drawinglayer/attribute/sdrfillattribute.hxx> 49 #include <drawinglayer/attribute/sdrlineattribute.hxx> 50 #include <drawinglayer/attribute/sdrlinestartendattribute.hxx> 51 #include <drawinglayer/attribute/sdrshadowattribute.hxx> 52 53 ////////////////////////////////////////////////////////////////////////////// 54 55 using namespace com::sun::star; 56 57 ////////////////////////////////////////////////////////////////////////////// 58 59 namespace drawinglayer 60 { 61 namespace primitive2d 62 { createPolyPolygonFillPrimitive(const basegfx::B2DPolyPolygon & rPolyPolygon,const attribute::SdrFillAttribute & rFill,const attribute::FillGradientAttribute & rFillGradient)63 Primitive2DReference createPolyPolygonFillPrimitive( 64 const basegfx::B2DPolyPolygon& rPolyPolygon, 65 const attribute::SdrFillAttribute& rFill, 66 const attribute::FillGradientAttribute& rFillGradient) 67 { 68 // when we have no given definition range, use the range of the given geometry 69 // also for definition (simplest case) 70 const basegfx::B2DRange aRange(basegfx::tools::getRange(rPolyPolygon)); 71 72 return createPolyPolygonFillPrimitive( 73 rPolyPolygon, 74 aRange, 75 rFill, 76 rFillGradient); 77 } 78 createPolyPolygonFillPrimitive(const basegfx::B2DPolyPolygon & rPolyPolygon,const basegfx::B2DRange & rDefinitionRange,const attribute::SdrFillAttribute & rFill,const attribute::FillGradientAttribute & rFillGradient)79 Primitive2DReference createPolyPolygonFillPrimitive( 80 const basegfx::B2DPolyPolygon& rPolyPolygon, 81 const basegfx::B2DRange& rDefinitionRange, 82 const attribute::SdrFillAttribute& rFill, 83 const attribute::FillGradientAttribute& rFillGradient) 84 { 85 if(basegfx::fTools::moreOrEqual(rFill.getTransparence(), 1.0)) 86 { 87 return Primitive2DReference(); 88 } 89 90 // prepare fully scaled polygon 91 BasePrimitive2D* pNewFillPrimitive = 0; 92 93 if(!rFill.getGradient().isDefault()) 94 { 95 pNewFillPrimitive = new PolyPolygonGradientPrimitive2D( 96 rPolyPolygon, 97 rDefinitionRange, 98 rFill.getGradient()); 99 } 100 else if(!rFill.getHatch().isDefault()) 101 { 102 pNewFillPrimitive = new PolyPolygonHatchPrimitive2D( 103 rPolyPolygon, 104 rDefinitionRange, 105 rFill.getColor(), 106 rFill.getHatch()); 107 } 108 else if(!rFill.getFillGraphic().isDefault()) 109 { 110 pNewFillPrimitive = new PolyPolygonGraphicPrimitive2D( 111 rPolyPolygon, 112 rDefinitionRange, 113 rFill.getFillGraphic().createFillGraphicAttribute(rDefinitionRange)); 114 } 115 else 116 { 117 pNewFillPrimitive = new PolyPolygonColorPrimitive2D( 118 rPolyPolygon, 119 rFill.getColor()); 120 } 121 122 if(0.0 != rFill.getTransparence()) 123 { 124 // create simpleTransparencePrimitive, add created fill primitive 125 const Primitive2DReference xRefA(pNewFillPrimitive); 126 const Primitive2DSequence aContent(&xRefA, 1L); 127 return Primitive2DReference(new UnifiedTransparencePrimitive2D(aContent, rFill.getTransparence())); 128 } 129 else if(!rFillGradient.isDefault()) 130 { 131 // create sequence with created fill primitive 132 const Primitive2DReference xRefA(pNewFillPrimitive); 133 const Primitive2DSequence aContent(&xRefA, 1L); 134 135 // create FillGradientPrimitive2D for transparence and add to new sequence 136 // fillGradientPrimitive is enough here (compared to PolyPolygonGradientPrimitive2D) since float transparence will be masked anyways 137 const basegfx::B2DRange aRange(basegfx::tools::getRange(rPolyPolygon)); 138 const Primitive2DReference xRefB( 139 new FillGradientPrimitive2D( 140 aRange, 141 rDefinitionRange, 142 rFillGradient)); 143 const Primitive2DSequence aAlpha(&xRefB, 1L); 144 145 // create TransparencePrimitive2D using alpha and content 146 return Primitive2DReference(new TransparencePrimitive2D(aContent, aAlpha)); 147 } 148 else 149 { 150 // add to decomposition 151 return Primitive2DReference(pNewFillPrimitive); 152 } 153 } 154 createPolygonLinePrimitive(const basegfx::B2DPolygon & rPolygon,const attribute::SdrLineAttribute & rLine,const attribute::SdrLineStartEndAttribute & rStroke)155 Primitive2DReference createPolygonLinePrimitive( 156 const basegfx::B2DPolygon& rPolygon, 157 const attribute::SdrLineAttribute& rLine, 158 const attribute::SdrLineStartEndAttribute& rStroke) 159 { 160 // create line and stroke attribute 161 const attribute::LineAttribute aLineAttribute(rLine.getColor(), rLine.getWidth(), rLine.getJoin(), rLine.getCap()); 162 const attribute::StrokeAttribute aStrokeAttribute(rLine.getDotDashArray(), rLine.getFullDotDashLen()); 163 BasePrimitive2D* pNewLinePrimitive = 0L; 164 165 if(!rPolygon.isClosed() && !rStroke.isDefault()) 166 { 167 attribute::LineStartEndAttribute aStart(rStroke.getStartWidth(), rStroke.getStartPolyPolygon(), rStroke.isStartCentered()); 168 attribute::LineStartEndAttribute aEnd(rStroke.getEndWidth(), rStroke.getEndPolyPolygon(), rStroke.isEndCentered()); 169 170 // create data 171 pNewLinePrimitive = new PolygonStrokeArrowPrimitive2D(rPolygon, aLineAttribute, aStrokeAttribute, aStart, aEnd); 172 } 173 else 174 { 175 // create data 176 pNewLinePrimitive = new PolygonStrokePrimitive2D(rPolygon, aLineAttribute, aStrokeAttribute); 177 } 178 179 if(0.0 != rLine.getTransparence()) 180 { 181 // create simpleTransparencePrimitive, add created fill primitive 182 const Primitive2DReference xRefA(pNewLinePrimitive); 183 const Primitive2DSequence aContent(&xRefA, 1L); 184 return Primitive2DReference(new UnifiedTransparencePrimitive2D(aContent, rLine.getTransparence())); 185 } 186 else 187 { 188 // add to decomposition 189 return Primitive2DReference(pNewLinePrimitive); 190 } 191 } 192 createTextPrimitive(const basegfx::B2DPolyPolygon & rUnitPolyPolygon,const basegfx::B2DHomMatrix & rObjectTransform,const attribute::SdrTextAttribute & rText,const attribute::SdrLineAttribute & rStroke,bool bCellText,bool bWordWrap,bool bClipOnBounds)193 Primitive2DReference createTextPrimitive( 194 const basegfx::B2DPolyPolygon& rUnitPolyPolygon, 195 const basegfx::B2DHomMatrix& rObjectTransform, 196 const attribute::SdrTextAttribute& rText, 197 const attribute::SdrLineAttribute& rStroke, 198 bool bCellText, 199 bool bWordWrap, 200 bool bClipOnBounds) 201 { 202 basegfx::B2DHomMatrix aAnchorTransform(rObjectTransform); 203 SdrTextPrimitive2D* pNew = 0; 204 205 if(rText.isContour()) 206 { 207 // contour text 208 if(!rStroke.isDefault() && 0.0 != rStroke.getWidth()) 209 { 210 // take line width into account and shrink contour polygon accordingly 211 // decompose to get scale 212 basegfx::B2DVector aScale, aTranslate; 213 double fRotate, fShearX; 214 rObjectTransform.decompose(aScale, aTranslate, fRotate, fShearX); 215 216 // scale outline to object's size to allow growing with value relative to that size 217 // and also to keep aspect ratio 218 basegfx::B2DPolyPolygon aScaledUnitPolyPolygon(rUnitPolyPolygon); 219 aScaledUnitPolyPolygon.transform(basegfx::tools::createScaleB2DHomMatrix( 220 fabs(aScale.getX()), fabs(aScale.getY()))); 221 222 // grow the polygon. To shrink, use negative value (half width) 223 aScaledUnitPolyPolygon = basegfx::tools::growInNormalDirection(aScaledUnitPolyPolygon, -(rStroke.getWidth() * 0.5)); 224 225 // scale back to unit polygon 226 aScaledUnitPolyPolygon.transform(basegfx::tools::createScaleB2DHomMatrix( 227 0.0 != aScale.getX() ? 1.0 / aScale.getX() : 1.0, 228 0.0 != aScale.getY() ? 1.0 / aScale.getY() : 1.0)); 229 230 // create with unit polygon 231 pNew = new SdrContourTextPrimitive2D( 232 &rText.getSdrText(), 233 rText.getOutlinerParaObject(), 234 aScaledUnitPolyPolygon, 235 rObjectTransform); 236 } 237 else 238 { 239 // create with unit polygon 240 pNew = new SdrContourTextPrimitive2D( 241 &rText.getSdrText(), 242 rText.getOutlinerParaObject(), 243 rUnitPolyPolygon, 244 rObjectTransform); 245 } 246 } 247 else if(!rText.getSdrFormTextAttribute().isDefault()) 248 { 249 // text on path, use scaled polygon 250 basegfx::B2DPolyPolygon aScaledPolyPolygon(rUnitPolyPolygon); 251 aScaledPolyPolygon.transform(rObjectTransform); 252 pNew = new SdrPathTextPrimitive2D( 253 &rText.getSdrText(), 254 rText.getOutlinerParaObject(), 255 aScaledPolyPolygon, 256 rText.getSdrFormTextAttribute()); 257 } 258 else 259 { 260 // rObjectTransform is the whole SdrObject transformation from unit rectangle 261 // to it's size and position. Decompose to allow working with single values. 262 basegfx::B2DVector aScale, aTranslate; 263 double fRotate, fShearX; 264 rObjectTransform.decompose(aScale, aTranslate, fRotate, fShearX); 265 266 // extract mirroring 267 const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0)); 268 const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0)); 269 aScale = basegfx::absolute(aScale); 270 271 // Get the real size, since polygon ountline and scale 272 // from the object transformation may vary (e.g. ellipse segments) 273 basegfx::B2DHomMatrix aJustScaleTransform; 274 aJustScaleTransform.set(0, 0, aScale.getX()); 275 aJustScaleTransform.set(1, 1, aScale.getY()); 276 basegfx::B2DPolyPolygon aScaledUnitPolyPolygon(rUnitPolyPolygon); 277 aScaledUnitPolyPolygon.transform(aJustScaleTransform); 278 const basegfx::B2DRange aSnapRange(basegfx::tools::getRange(aScaledUnitPolyPolygon)); 279 280 // create a range describing the wanted text position and size (aTextAnchorRange). This 281 // means to use the text distance values here 282 const basegfx::B2DPoint aTopLeft(aSnapRange.getMinX() + rText.getTextLeftDistance(), aSnapRange.getMinY() + rText.getTextUpperDistance()); 283 const basegfx::B2DPoint aBottomRight(aSnapRange.getMaxX() - rText.getTextRightDistance(), aSnapRange.getMaxY() - rText.getTextLowerDistance()); 284 basegfx::B2DRange aTextAnchorRange; 285 aTextAnchorRange.expand(aTopLeft); 286 aTextAnchorRange.expand(aBottomRight); 287 288 // now create a transformation from this basic range (aTextAnchorRange) 289 // #121494# if we have no scale use at least 1.0 to have a carrier e.g. for 290 // mirror values, else these will get lost 291 aAnchorTransform = basegfx::tools::createScaleTranslateB2DHomMatrix( 292 basegfx::fTools::equalZero(aTextAnchorRange.getWidth()) ? 1.0 : aTextAnchorRange.getWidth(), 293 basegfx::fTools::equalZero(aTextAnchorRange.getHeight()) ? 1.0 : aTextAnchorRange.getHeight(), 294 aTextAnchorRange.getMinX(), aTextAnchorRange.getMinY()); 295 296 // apply mirroring 297 aAnchorTransform.scale(bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0); 298 299 // apply object's other transforms 300 aAnchorTransform = basegfx::tools::createShearXRotateTranslateB2DHomMatrix(fShearX, fRotate, aTranslate) 301 * aAnchorTransform; 302 303 if(rText.isFitToSize()) 304 { 305 // streched text in range 306 pNew = new SdrStretchTextPrimitive2D( 307 &rText.getSdrText(), 308 rText.getOutlinerParaObject(), 309 aAnchorTransform, 310 rText.isFixedCellHeight()); 311 } 312 else // text in range 313 { 314 // build new primitive 315 pNew = new SdrBlockTextPrimitive2D( 316 &rText.getSdrText(), 317 rText.getOutlinerParaObject(), 318 aAnchorTransform, 319 rText.getSdrTextHorzAdjust(), 320 rText.getSdrTextVertAdjust(), 321 rText.isFixedCellHeight(), 322 rText.isScroll(), 323 bCellText, 324 bWordWrap, 325 bClipOnBounds); 326 } 327 } 328 329 OSL_ENSURE(pNew != 0, "createTextPrimitive: no text primitive created (!)"); 330 331 if(rText.isBlink()) 332 { 333 // prepare animation and primitive list 334 drawinglayer::animation::AnimationEntryList aAnimationList; 335 rText.getBlinkTextTiming(aAnimationList); 336 337 if(0.0 != aAnimationList.getDuration()) 338 { 339 // create content sequence 340 const Primitive2DReference xRefA(pNew); 341 const Primitive2DSequence aContent(&xRefA, 1L); 342 343 // create and add animated switch primitive 344 return Primitive2DReference(new AnimatedBlinkPrimitive2D(aAnimationList, aContent, true)); 345 } 346 else 347 { 348 // add to decomposition 349 return Primitive2DReference(pNew); 350 } 351 } 352 353 if(rText.isScroll()) 354 { 355 // suppress scroll when FontWork 356 if(rText.getSdrFormTextAttribute().isDefault()) 357 { 358 // get scroll direction 359 const SdrTextAniDirection eDirection(rText.getSdrText().GetObject().GetTextAniDirection()); 360 const bool bHorizontal(SDRTEXTANI_LEFT == eDirection || SDRTEXTANI_RIGHT == eDirection); 361 362 // decompose to get separated values for the scroll box 363 basegfx::B2DVector aScale, aTranslate; 364 double fRotate, fShearX; 365 aAnchorTransform.decompose(aScale, aTranslate, fRotate, fShearX); 366 367 // build transform from scaled only to full AnchorTransform and inverse 368 const basegfx::B2DHomMatrix aSRT(basegfx::tools::createShearXRotateTranslateB2DHomMatrix( 369 fShearX, fRotate, aTranslate)); 370 basegfx::B2DHomMatrix aISRT(aSRT); 371 aISRT.invert(); 372 373 // bring the primitive back to scaled only and get scaled range, create new clone for this 374 SdrTextPrimitive2D* pNew2 = pNew->createTransformedClone(aISRT); 375 OSL_ENSURE(pNew2, "createTextPrimitive: Could not create transformed clone of text primitive (!)"); 376 delete pNew; 377 pNew = pNew2; 378 379 // create neutral geometry::ViewInformation2D for local range and decompose calls. This is okay 380 // since the decompose is view-independent 381 const uno::Sequence< beans::PropertyValue > xViewParameters; 382 geometry::ViewInformation2D aViewInformation2D(xViewParameters); 383 384 // get range 385 const basegfx::B2DRange aScaledRange(pNew->getB2DRange(aViewInformation2D)); 386 387 // create left outside and right outside transformations. Also take care 388 // of the clip rectangle 389 basegfx::B2DHomMatrix aLeft, aRight; 390 basegfx::B2DPoint aClipTopLeft(0.0, 0.0); 391 basegfx::B2DPoint aClipBottomRight(aScale.getX(), aScale.getY()); 392 393 if(bHorizontal) 394 { 395 aClipTopLeft.setY(aScaledRange.getMinY()); 396 aClipBottomRight.setY(aScaledRange.getMaxY()); 397 aLeft.translate(-aScaledRange.getMaxX(), 0.0); 398 aRight.translate(aScale.getX() - aScaledRange.getMinX(), 0.0); 399 } 400 else 401 { 402 aClipTopLeft.setX(aScaledRange.getMinX()); 403 aClipBottomRight.setX(aScaledRange.getMaxX()); 404 aLeft.translate(0.0, -aScaledRange.getMaxY()); 405 aRight.translate(0.0, aScale.getY() - aScaledRange.getMinY()); 406 } 407 408 aLeft *= aSRT; 409 aRight *= aSRT; 410 411 // prepare animation list 412 drawinglayer::animation::AnimationEntryList aAnimationList; 413 414 if(bHorizontal) 415 { 416 rText.getScrollTextTiming(aAnimationList, aScale.getX(), aScaledRange.getWidth()); 417 } 418 else 419 { 420 rText.getScrollTextTiming(aAnimationList, aScale.getY(), aScaledRange.getHeight()); 421 } 422 423 if(0.0 != aAnimationList.getDuration()) 424 { 425 // create a new Primitive2DSequence containing the animated text in it's scaled only state. 426 // use the decomposition to force to simple text primitives, those will no longer 427 // need the outliner for formatting (alternatively it is also possible to just add 428 // pNew to aNewPrimitiveSequence) 429 Primitive2DSequence aAnimSequence(pNew->get2DDecomposition(aViewInformation2D)); 430 delete pNew; 431 432 // create a new animatedInterpolatePrimitive and add it 433 std::vector< basegfx::B2DHomMatrix > aMatrixStack; 434 aMatrixStack.push_back(aLeft); 435 aMatrixStack.push_back(aRight); 436 const Primitive2DReference xRefA(new AnimatedInterpolatePrimitive2D(aMatrixStack, aAnimationList, aAnimSequence, true)); 437 const Primitive2DSequence aContent(&xRefA, 1L); 438 439 // scrolling needs an encapsulating clipping primitive 440 const basegfx::B2DRange aClipRange(aClipTopLeft, aClipBottomRight); 441 basegfx::B2DPolygon aClipPolygon(basegfx::tools::createPolygonFromRect(aClipRange)); 442 aClipPolygon.transform(aSRT); 443 return Primitive2DReference(new MaskPrimitive2D(basegfx::B2DPolyPolygon(aClipPolygon), aContent)); 444 } 445 else 446 { 447 // add to decomposition 448 return Primitive2DReference(pNew); 449 } 450 } 451 } 452 453 if(rText.isInEditMode()) 454 { 455 // #i97628# 456 // encapsulate with TextHierarchyEditPrimitive2D to allow renderers 457 // to suppress actively edited content if needed 458 const Primitive2DReference xRefA(pNew); 459 const Primitive2DSequence aContent(&xRefA, 1L); 460 461 // create and add TextHierarchyEditPrimitive2D primitive 462 return Primitive2DReference(new TextHierarchyEditPrimitive2D(aContent)); 463 } 464 else 465 { 466 // add to decomposition 467 return Primitive2DReference(pNew); 468 } 469 } 470 createEmbeddedShadowPrimitive(const Primitive2DSequence & rContent,const attribute::SdrShadowAttribute & rShadow)471 Primitive2DSequence createEmbeddedShadowPrimitive( 472 const Primitive2DSequence& rContent, 473 const attribute::SdrShadowAttribute& rShadow) 474 { 475 if(rContent.hasElements()) 476 { 477 Primitive2DSequence aRetval(2); 478 basegfx::B2DHomMatrix aShadowOffset; 479 480 // prepare shadow offset 481 aShadowOffset.set(0, 2, rShadow.getOffset().getX()); 482 aShadowOffset.set(1, 2, rShadow.getOffset().getY()); 483 484 // create shadow primitive and add content 485 aRetval[0] = Primitive2DReference( 486 new ShadowPrimitive2D( 487 aShadowOffset, 488 rShadow.getColor(), 489 rContent)); 490 491 if(0.0 != rShadow.getTransparence()) 492 { 493 // create SimpleTransparencePrimitive2D 494 const Primitive2DSequence aTempContent(&aRetval[0], 1); 495 496 aRetval[0] = Primitive2DReference( 497 new UnifiedTransparencePrimitive2D( 498 aTempContent, 499 rShadow.getTransparence())); 500 } 501 502 aRetval[1] = Primitive2DReference(new GroupPrimitive2D(rContent)); 503 return aRetval; 504 } 505 else 506 { 507 return rContent; 508 } 509 } 510 } // end of namespace primitive2d 511 } // end of namespace drawinglayer 512 513 ////////////////////////////////////////////////////////////////////////////// 514 // eof 515