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