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