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/sdrfillbitmapattribute.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 { 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.getBitmap().isDefault()) 83 { 84 const basegfx::B2DRange aRange(basegfx::tools::getRange(aScaledPolyPolygon)); 85 pNewFillPrimitive = new PolyPolygonBitmapPrimitive2D(aScaledPolyPolygon, rFill.getBitmap().getFillBitmapAttribute(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 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()); 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 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 aAnchorTransform = basegfx::tools::createScaleTranslateB2DHomMatrix( 261 aTextAnchorRange.getWidth(), aTextAnchorRange.getHeight(), 262 aTextAnchorRange.getMinX(), aTextAnchorRange.getMinY()); 263 264 // apply mirroring 265 aAnchorTransform.scale(bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0); 266 267 // apply object's other transforms 268 aAnchorTransform = basegfx::tools::createShearXRotateTranslateB2DHomMatrix(fShearX, fRotate, aTranslate) 269 * aAnchorTransform; 270 271 if(rText.isFitToSize()) 272 { 273 // streched text in range 274 pNew = new SdrStretchTextPrimitive2D( 275 &rText.getSdrText(), 276 rText.getOutlinerParaObject(), 277 aAnchorTransform, 278 rText.isFixedCellHeight()); 279 } 280 else // text in range 281 { 282 // build new primitive 283 pNew = new SdrBlockTextPrimitive2D( 284 &rText.getSdrText(), 285 rText.getOutlinerParaObject(), 286 aAnchorTransform, 287 rText.getSdrTextHorzAdjust(), 288 rText.getSdrTextVertAdjust(), 289 rText.isFixedCellHeight(), 290 rText.isScroll(), 291 bCellText, 292 bWordWrap, 293 bClipOnBounds); 294 } 295 } 296 297 OSL_ENSURE(pNew != 0, "createTextPrimitive: no text primitive created (!)"); 298 299 if(rText.isBlink()) 300 { 301 // prepare animation and primitive list 302 drawinglayer::animation::AnimationEntryList aAnimationList; 303 rText.getBlinkTextTiming(aAnimationList); 304 305 if(0.0 != aAnimationList.getDuration()) 306 { 307 // create content sequence 308 const Primitive2DReference xRefA(pNew); 309 const Primitive2DSequence aContent(&xRefA, 1L); 310 311 // create and add animated switch primitive 312 return Primitive2DReference(new AnimatedBlinkPrimitive2D(aAnimationList, aContent, true)); 313 } 314 else 315 { 316 // add to decomposition 317 return Primitive2DReference(pNew); 318 } 319 } 320 321 if(rText.isScroll()) 322 { 323 // suppress scroll when FontWork 324 if(rText.getSdrFormTextAttribute().isDefault()) 325 { 326 // get scroll direction 327 const SdrTextAniDirection eDirection(rText.getSdrText().GetObject().GetTextAniDirection()); 328 const bool bHorizontal(SDRTEXTANI_LEFT == eDirection || SDRTEXTANI_RIGHT == eDirection); 329 330 // decompose to get separated values for the scroll box 331 basegfx::B2DVector aScale, aTranslate; 332 double fRotate, fShearX; 333 aAnchorTransform.decompose(aScale, aTranslate, fRotate, fShearX); 334 335 // build transform from scaled only to full AnchorTransform and inverse 336 const basegfx::B2DHomMatrix aSRT(basegfx::tools::createShearXRotateTranslateB2DHomMatrix( 337 fShearX, fRotate, aTranslate)); 338 basegfx::B2DHomMatrix aISRT(aSRT); 339 aISRT.invert(); 340 341 // bring the primitive back to scaled only and get scaled range, create new clone for this 342 SdrTextPrimitive2D* pNew2 = pNew->createTransformedClone(aISRT); 343 OSL_ENSURE(pNew2, "createTextPrimitive: Could not create transformed clone of text primitive (!)"); 344 delete pNew; 345 pNew = pNew2; 346 347 // create neutral geometry::ViewInformation2D for local range and decompose calls. This is okay 348 // since the decompose is view-independent 349 const uno::Sequence< beans::PropertyValue > xViewParameters; 350 geometry::ViewInformation2D aViewInformation2D(xViewParameters); 351 352 // get range 353 const basegfx::B2DRange aScaledRange(pNew->getB2DRange(aViewInformation2D)); 354 355 // create left outside and right outside transformations. Also take care 356 // of the clip rectangle 357 basegfx::B2DHomMatrix aLeft, aRight; 358 basegfx::B2DPoint aClipTopLeft(0.0, 0.0); 359 basegfx::B2DPoint aClipBottomRight(aScale.getX(), aScale.getY()); 360 361 if(bHorizontal) 362 { 363 aClipTopLeft.setY(aScaledRange.getMinY()); 364 aClipBottomRight.setY(aScaledRange.getMaxY()); 365 aLeft.translate(-aScaledRange.getMaxX(), 0.0); 366 aRight.translate(aScale.getX() - aScaledRange.getMinX(), 0.0); 367 } 368 else 369 { 370 aClipTopLeft.setX(aScaledRange.getMinX()); 371 aClipBottomRight.setX(aScaledRange.getMaxX()); 372 aLeft.translate(0.0, -aScaledRange.getMaxY()); 373 aRight.translate(0.0, aScale.getY() - aScaledRange.getMinY()); 374 } 375 376 aLeft *= aSRT; 377 aRight *= aSRT; 378 379 // prepare animation list 380 drawinglayer::animation::AnimationEntryList aAnimationList; 381 382 if(bHorizontal) 383 { 384 rText.getScrollTextTiming(aAnimationList, aScale.getX(), aScaledRange.getWidth()); 385 } 386 else 387 { 388 rText.getScrollTextTiming(aAnimationList, aScale.getY(), aScaledRange.getHeight()); 389 } 390 391 if(0.0 != aAnimationList.getDuration()) 392 { 393 // create a new Primitive2DSequence containing the animated text in it's scaled only state. 394 // use the decomposition to force to simple text primitives, those will no longer 395 // need the outliner for formatting (alternatively it is also possible to just add 396 // pNew to aNewPrimitiveSequence) 397 Primitive2DSequence aAnimSequence(pNew->get2DDecomposition(aViewInformation2D)); 398 delete pNew; 399 400 // create a new animatedInterpolatePrimitive and add it 401 std::vector< basegfx::B2DHomMatrix > aMatrixStack; 402 aMatrixStack.push_back(aLeft); 403 aMatrixStack.push_back(aRight); 404 const Primitive2DReference xRefA(new AnimatedInterpolatePrimitive2D(aMatrixStack, aAnimationList, aAnimSequence, true)); 405 const Primitive2DSequence aContent(&xRefA, 1L); 406 407 // scrolling needs an encapsulating clipping primitive 408 const basegfx::B2DRange aClipRange(aClipTopLeft, aClipBottomRight); 409 basegfx::B2DPolygon aClipPolygon(basegfx::tools::createPolygonFromRect(aClipRange)); 410 aClipPolygon.transform(aSRT); 411 return Primitive2DReference(new MaskPrimitive2D(basegfx::B2DPolyPolygon(aClipPolygon), aContent)); 412 } 413 else 414 { 415 // add to decomposition 416 return Primitive2DReference(pNew); 417 } 418 } 419 } 420 421 if(rText.isInEditMode()) 422 { 423 // #i97628# 424 // encapsulate with TextHierarchyEditPrimitive2D to allow renderers 425 // to suppress actively edited content if needed 426 const Primitive2DReference xRefA(pNew); 427 const Primitive2DSequence aContent(&xRefA, 1L); 428 429 // create and add TextHierarchyEditPrimitive2D primitive 430 return Primitive2DReference(new TextHierarchyEditPrimitive2D(aContent)); 431 } 432 else 433 { 434 // add to decomposition 435 return Primitive2DReference(pNew); 436 } 437 } 438 439 Primitive2DSequence createEmbeddedShadowPrimitive( 440 const Primitive2DSequence& rContent, 441 const attribute::SdrShadowAttribute& rShadow) 442 { 443 if(rContent.hasElements()) 444 { 445 Primitive2DSequence aRetval(2); 446 basegfx::B2DHomMatrix aShadowOffset; 447 448 // prepare shadow offset 449 aShadowOffset.set(0, 2, rShadow.getOffset().getX()); 450 aShadowOffset.set(1, 2, rShadow.getOffset().getY()); 451 452 // create shadow primitive and add content 453 aRetval[0] = Primitive2DReference( 454 new ShadowPrimitive2D( 455 aShadowOffset, 456 rShadow.getColor(), 457 rContent)); 458 459 if(0.0 != rShadow.getTransparence()) 460 { 461 // create SimpleTransparencePrimitive2D 462 const Primitive2DSequence aTempContent(&aRetval[0], 1); 463 464 aRetval[0] = Primitive2DReference( 465 new UnifiedTransparencePrimitive2D( 466 aTempContent, 467 rShadow.getTransparence())); 468 } 469 470 aRetval[1] = Primitive2DReference(new GroupPrimitive2D(rContent)); 471 return aRetval; 472 } 473 else 474 { 475 return rContent; 476 } 477 } 478 } // end of namespace primitive2d 479 } // end of namespace drawinglayer 480 481 ////////////////////////////////////////////////////////////////////////////// 482 // eof 483