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