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 // MARKER(update_precomp.py): autogen include statement, do not remove 25 #include "precompiled_drawinglayer.hxx" 26 27 #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx> 28 #include <drawinglayer/primitive2d/textlayoutdevice.hxx> 29 #include <drawinglayer/primitive2d/polygonprimitive2d.hxx> 30 #include <drawinglayer/attribute/strokeattribute.hxx> 31 #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> 32 #include <basegfx/matrix/b2dhommatrixtools.hxx> 33 #include <comphelper/processfactory.hxx> 34 #include <com/sun/star/i18n/WordType.hpp> 35 #include <drawinglayer/primitive2d/texteffectprimitive2d.hxx> 36 #include <drawinglayer/primitive2d/shadowprimitive2d.hxx> 37 #include <com/sun/star/i18n/XBreakIterator.hpp> 38 #include <drawinglayer/primitive2d/transformprimitive2d.hxx> 39 #include <drawinglayer/primitive2d/textlineprimitive2d.hxx> 40 #include <drawinglayer/primitive2d/textstrikeoutprimitive2d.hxx> 41 42 ////////////////////////////////////////////////////////////////////////////// 43 44 namespace drawinglayer 45 { 46 namespace primitive2d 47 { 48 void TextDecoratedPortionPrimitive2D::impCreateGeometryContent( 49 std::vector< Primitive2DReference >& rTarget, 50 basegfx::tools::B2DHomMatrixBufferedOnDemandDecompose& rDecTrans, 51 const String& rText, 52 xub_StrLen aTextPosition, 53 xub_StrLen aTextLength, 54 const ::std::vector< double >& rDXArray, 55 const attribute::FontAttribute& rFontAttribute) const 56 { 57 // create the SimpleTextPrimitive needed in any case 58 rTarget.push_back(Primitive2DReference( 59 new TextSimplePortionPrimitive2D( 60 rDecTrans.getB2DHomMatrix(), 61 rText, 62 aTextPosition, 63 aTextLength, 64 rDXArray, 65 rFontAttribute, 66 getLocale(), 67 getFontColor()))); 68 69 // see if something else needs to be done 70 const bool bOverlineUsed(TEXT_LINE_NONE != getFontOverline()); 71 const bool bUnderlineUsed(TEXT_LINE_NONE != getFontUnderline()); 72 const bool bStrikeoutUsed(TEXT_STRIKEOUT_NONE != getTextStrikeout()); 73 74 if(bUnderlineUsed || bStrikeoutUsed || bOverlineUsed) 75 { 76 // common preparations 77 TextLayouterDevice aTextLayouter; 78 79 // TextLayouterDevice is needed to get metrics for text decorations like 80 // underline/strikeout/emphasis marks from it. For setup, the font size is needed 81 aTextLayouter.setFontAttribute( 82 getFontAttribute(), 83 rDecTrans.getScale().getX(), 84 rDecTrans.getScale().getY(), 85 getLocale()); 86 87 // get text width 88 double fTextWidth(0.0); 89 90 if(rDXArray.empty()) 91 { 92 fTextWidth = aTextLayouter.getTextWidth(rText, aTextPosition, aTextLength); 93 } 94 else 95 { 96 fTextWidth = rDXArray.back() * rDecTrans.getScale().getX(); 97 const double fFontScaleX(rDecTrans.getScale().getX()); 98 99 if(!basegfx::fTools::equal(fFontScaleX, 1.0) 100 && !basegfx::fTools::equalZero(fFontScaleX)) 101 { 102 // need to take FontScaling out of the DXArray 103 fTextWidth /= fFontScaleX; 104 } 105 } 106 107 if(bOverlineUsed) 108 { 109 // create primitive geometry for overline 110 rTarget.push_back(Primitive2DReference( 111 new TextLinePrimitive2D( 112 rDecTrans.getB2DHomMatrix(), 113 fTextWidth, 114 aTextLayouter.getOverlineOffset(), 115 aTextLayouter.getOverlineHeight(), 116 getFontOverline(), 117 getOverlineColor()))); 118 } 119 120 if(bUnderlineUsed) 121 { 122 // create primitive geometry for underline 123 rTarget.push_back(Primitive2DReference( 124 new TextLinePrimitive2D( 125 rDecTrans.getB2DHomMatrix(), 126 fTextWidth, 127 aTextLayouter.getUnderlineOffset(), 128 aTextLayouter.getUnderlineHeight(), 129 getFontUnderline(), 130 getTextlineColor()))); 131 } 132 133 if(bStrikeoutUsed) 134 { 135 // create primitive geometry for strikeout 136 if(TEXT_STRIKEOUT_SLASH == getTextStrikeout() || TEXT_STRIKEOUT_X == getTextStrikeout()) 137 { 138 // strikeout with character 139 const sal_Unicode aStrikeoutChar(TEXT_STRIKEOUT_SLASH == getTextStrikeout() ? '/' : 'X'); 140 141 rTarget.push_back(Primitive2DReference( 142 new TextCharacterStrikeoutPrimitive2D( 143 rDecTrans.getB2DHomMatrix(), 144 fTextWidth, 145 getFontColor(), 146 aStrikeoutChar, 147 getFontAttribute(), 148 getLocale()))); 149 } 150 else 151 { 152 // strikeout with geometry 153 rTarget.push_back(Primitive2DReference( 154 new TextGeometryStrikeoutPrimitive2D( 155 rDecTrans.getB2DHomMatrix(), 156 fTextWidth, 157 getFontColor(), 158 aTextLayouter.getUnderlineHeight(), 159 aTextLayouter.getStrikeoutOffset(), 160 getTextStrikeout()))); 161 } 162 } 163 } 164 165 // TODO: Handle Font Emphasis Above/Below 166 } 167 168 void TextDecoratedPortionPrimitive2D::impCorrectTextBoundary(::com::sun::star::i18n::Boundary& rNextWordBoundary) const 169 { 170 // truncate aNextWordBoundary to min/max possible values. This is necessary since the word start may be 171 // before/after getTextPosition() when a long string is the content and getTextPosition() 172 // is right inside a word. Same for end. 173 const sal_Int32 aMinPos(static_cast< sal_Int32 >(getTextPosition())); 174 const sal_Int32 aMaxPos(aMinPos + static_cast< sal_Int32 >(getTextLength())); 175 176 if(rNextWordBoundary.startPos < aMinPos) 177 { 178 rNextWordBoundary.startPos = aMinPos; 179 } 180 else if(rNextWordBoundary.startPos > aMaxPos) 181 { 182 rNextWordBoundary.startPos = aMaxPos; 183 } 184 185 if(rNextWordBoundary.endPos < aMinPos) 186 { 187 rNextWordBoundary.endPos = aMinPos; 188 } 189 else if(rNextWordBoundary.endPos > aMaxPos) 190 { 191 rNextWordBoundary.endPos = aMaxPos; 192 } 193 } 194 195 void TextDecoratedPortionPrimitive2D::impSplitSingleWords( 196 std::vector< Primitive2DReference >& rTarget, 197 basegfx::tools::B2DHomMatrixBufferedOnDemandDecompose& rDecTrans) const 198 { 199 // break iterator support 200 // made static so it only needs to be fetched once, even with many single 201 // constructed VclMetafileProcessor2D. It's still incarnated on demand, 202 // but exists for OOo runtime now by purpose. 203 static ::com::sun::star::uno::Reference< ::com::sun::star::i18n::XBreakIterator > xLocalBreakIterator; 204 205 if(!xLocalBreakIterator.is()) 206 { 207 ::com::sun::star::uno::Reference< ::com::sun::star::lang::XMultiServiceFactory > xMSF(::comphelper::getProcessServiceFactory()); 208 xLocalBreakIterator.set(xMSF->createInstance(rtl::OUString::createFromAscii("com.sun.star.i18n.BreakIterator")), ::com::sun::star::uno::UNO_QUERY); 209 } 210 211 if(xLocalBreakIterator.is() && getTextLength()) 212 { 213 // init word iterator, get first word and truncate to possibilities 214 ::com::sun::star::i18n::Boundary aNextWordBoundary(xLocalBreakIterator->getWordBoundary( 215 getText(), getTextPosition(), getLocale(), ::com::sun::star::i18n::WordType::ANYWORD_IGNOREWHITESPACES, sal_True)); 216 217 if(aNextWordBoundary.endPos == getTextPosition()) 218 { 219 // backward hit, force next word 220 aNextWordBoundary = xLocalBreakIterator->getWordBoundary( 221 getText(), getTextPosition() + 1, getLocale(), ::com::sun::star::i18n::WordType::ANYWORD_IGNOREWHITESPACES, sal_True); 222 } 223 224 impCorrectTextBoundary(aNextWordBoundary); 225 226 // prepare new font attributes WITHOUT outline 227 const attribute::FontAttribute aNewFontAttribute( 228 getFontAttribute().getFamilyName(), 229 getFontAttribute().getStyleName(), 230 getFontAttribute().getWeight(), 231 getFontAttribute().getSymbol(), 232 getFontAttribute().getVertical(), 233 getFontAttribute().getItalic(), 234 false, // no outline anymore, handled locally 235 getFontAttribute().getRTL(), 236 getFontAttribute().getBiDiStrong()); 237 238 if(aNextWordBoundary.startPos == getTextPosition() && aNextWordBoundary.endPos == getTextLength()) 239 { 240 // it IS only a single word, handle as one word 241 impCreateGeometryContent(rTarget, rDecTrans, getText(), getTextPosition(), getTextLength(), getDXArray(), aNewFontAttribute); 242 } 243 else 244 { 245 // prepare TextLayouter 246 const bool bNoDXArray(getDXArray().empty()); 247 TextLayouterDevice aTextLayouter; 248 249 if(bNoDXArray) 250 { 251 // ..but only completely when no DXArray 252 aTextLayouter.setFontAttribute( 253 getFontAttribute(), 254 rDecTrans.getScale().getX(), 255 rDecTrans.getScale().getY(), 256 getLocale()); 257 } 258 259 // do iterate over single words 260 while(aNextWordBoundary.startPos != aNextWordBoundary.endPos) 261 { 262 // prepare values for new portion 263 const xub_StrLen nNewTextStart(static_cast< xub_StrLen >(aNextWordBoundary.startPos)); 264 const xub_StrLen nNewTextEnd(static_cast< xub_StrLen >(aNextWordBoundary.endPos)); 265 266 // prepare transform for the single word 267 basegfx::B2DHomMatrix aNewTransform; 268 ::std::vector< double > aNewDXArray; 269 const bool bNewStartIsNotOldStart(nNewTextStart > getTextPosition()); 270 271 if(!bNoDXArray) 272 { 273 // prepare new DXArray for the single word 274 aNewDXArray = ::std::vector< double >( 275 getDXArray().begin() + static_cast< sal_uInt32 >(nNewTextStart - getTextPosition()), 276 getDXArray().begin() + static_cast< sal_uInt32 >(nNewTextEnd - getTextPosition())); 277 } 278 279 if(bNewStartIsNotOldStart) 280 { 281 // needs to be moved to a new start position 282 double fOffset(0.0); 283 284 if(bNoDXArray) 285 { 286 // evaluate using TextLayouter 287 fOffset = aTextLayouter.getTextWidth(getText(), getTextPosition(), nNewTextStart); 288 } 289 else 290 { 291 // get from DXArray 292 const sal_uInt32 nIndex(static_cast< sal_uInt32 >(nNewTextStart - getTextPosition())); 293 fOffset = getDXArray()[nIndex - 1]; 294 } 295 296 // need offset without FontScale for building the new transformation. The 297 // new transformation will be multiplied with the current text transformation 298 // so FontScale would be double 299 double fOffsetNoScale(fOffset); 300 const double fFontScaleX(rDecTrans.getScale().getX()); 301 302 if(!basegfx::fTools::equal(fFontScaleX, 1.0) 303 && !basegfx::fTools::equalZero(fFontScaleX)) 304 { 305 fOffsetNoScale /= fFontScaleX; 306 } 307 308 // apply needed offset to transformation 309 aNewTransform.translate(fOffsetNoScale, 0.0); 310 311 if(!bNoDXArray) 312 { 313 // DXArray values need to be corrected with the offset, too. Here, 314 // take the scaled offset since the DXArray is scaled 315 const sal_uInt32 nArraySize(aNewDXArray.size()); 316 317 for(sal_uInt32 a(0); a < nArraySize; a++) 318 { 319 aNewDXArray[a] -= fOffset; 320 } 321 } 322 } 323 324 // add text transformation to new transformation 325 aNewTransform *= rDecTrans.getB2DHomMatrix(); 326 327 // create geometry content for the single word. Do not forget 328 // to use the new transformation 329 basegfx::tools::B2DHomMatrixBufferedOnDemandDecompose aDecTrans(aNewTransform); 330 331 impCreateGeometryContent(rTarget, aDecTrans, getText(), nNewTextStart, 332 nNewTextEnd - nNewTextStart, aNewDXArray, aNewFontAttribute); 333 334 if(aNextWordBoundary.endPos >= getTextPosition() + getTextLength()) 335 { 336 // end reached 337 aNextWordBoundary.startPos = aNextWordBoundary.endPos; 338 } 339 else 340 { 341 // get new word portion 342 const sal_Int32 nLastEndPos(aNextWordBoundary.endPos); 343 344 aNextWordBoundary = xLocalBreakIterator->getWordBoundary( 345 getText(), aNextWordBoundary.endPos, getLocale(), 346 ::com::sun::star::i18n::WordType::ANYWORD_IGNOREWHITESPACES, sal_True); 347 348 if(nLastEndPos == aNextWordBoundary.endPos) 349 { 350 // backward hit, force next word 351 aNextWordBoundary = xLocalBreakIterator->getWordBoundary( 352 getText(), nLastEndPos + 1, getLocale(), 353 ::com::sun::star::i18n::WordType::ANYWORD_IGNOREWHITESPACES, sal_True); 354 } 355 356 impCorrectTextBoundary(aNextWordBoundary); 357 } 358 } 359 } 360 } 361 } 362 363 Primitive2DSequence TextDecoratedPortionPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const 364 { 365 std::vector< Primitive2DReference > aNewPrimitives; 366 basegfx::tools::B2DHomMatrixBufferedOnDemandDecompose aDecTrans(getTextTransform()); 367 Primitive2DSequence aRetval; 368 369 // create basic geometry such as SimpleTextPrimitive, Overline, Underline, 370 // Strikeout, etc... 371 if(getWordLineMode()) 372 { 373 // support for single word mode 374 impSplitSingleWords(aNewPrimitives, aDecTrans); 375 } 376 else 377 { 378 // prepare new font attributes WITHOUT outline 379 const attribute::FontAttribute aNewFontAttribute( 380 getFontAttribute().getFamilyName(), 381 getFontAttribute().getStyleName(), 382 getFontAttribute().getWeight(), 383 getFontAttribute().getSymbol(), 384 getFontAttribute().getVertical(), 385 getFontAttribute().getItalic(), 386 false, // no outline anymore, handled locally 387 getFontAttribute().getRTL(), 388 getFontAttribute().getBiDiStrong()); 389 390 // handle as one word 391 impCreateGeometryContent(aNewPrimitives, aDecTrans, getText(), getTextPosition(), getTextLength(), getDXArray(), aNewFontAttribute); 392 } 393 394 // convert to Primitive2DSequence 395 const sal_uInt32 nMemberCount(aNewPrimitives.size()); 396 397 if(nMemberCount) 398 { 399 aRetval.realloc(nMemberCount); 400 401 for(sal_uInt32 a(0); a < nMemberCount; a++) 402 { 403 aRetval[a] = aNewPrimitives[a]; 404 } 405 } 406 407 // Handle Shadow, Outline and TextRelief 408 if(aRetval.hasElements()) 409 { 410 // outline AND shadow depend on NO TextRelief (see dialog) 411 const bool bHasTextRelief(TEXT_RELIEF_NONE != getTextRelief()); 412 const bool bHasShadow(!bHasTextRelief && getShadow()); 413 const bool bHasOutline(!bHasTextRelief && getFontAttribute().getOutline()); 414 415 if(bHasShadow || bHasTextRelief || bHasOutline) 416 { 417 Primitive2DReference aShadow; 418 419 if(bHasShadow) 420 { 421 // create shadow with current content (in aRetval). Text shadow 422 // is constant, relative to font size, rotated with the text and has a 423 // constant color. 424 // shadow parameter values 425 static double fFactor(1.0 / 24.0); 426 const double fTextShadowOffset(aDecTrans.getScale().getY() * fFactor); 427 static basegfx::BColor aShadowColor(0.3, 0.3, 0.3); 428 429 // preapare shadow transform matrix 430 const basegfx::B2DHomMatrix aShadowTransform(basegfx::tools::createTranslateB2DHomMatrix( 431 fTextShadowOffset, fTextShadowOffset)); 432 433 // create shadow primitive 434 aShadow = Primitive2DReference(new ShadowPrimitive2D( 435 aShadowTransform, 436 aShadowColor, 437 aRetval)); 438 } 439 440 if(bHasTextRelief) 441 { 442 // create emboss using an own helper primitive since this will 443 // be view-dependent 444 const basegfx::BColor aBBlack(0.0, 0.0, 0.0); 445 const bool bDefaultTextColor(aBBlack == getFontColor()); 446 TextEffectStyle2D aTextEffectStyle2D(TEXTEFFECTSTYLE2D_RELIEF_EMBOSSED); 447 448 if(bDefaultTextColor) 449 { 450 if(TEXT_RELIEF_ENGRAVED == getTextRelief()) 451 { 452 aTextEffectStyle2D = TEXTEFFECTSTYLE2D_RELIEF_ENGRAVED_DEFAULT; 453 } 454 else 455 { 456 aTextEffectStyle2D = TEXTEFFECTSTYLE2D_RELIEF_EMBOSSED_DEFAULT; 457 } 458 } 459 else 460 { 461 if(TEXT_RELIEF_ENGRAVED == getTextRelief()) 462 { 463 aTextEffectStyle2D = TEXTEFFECTSTYLE2D_RELIEF_ENGRAVED; 464 } 465 else 466 { 467 aTextEffectStyle2D = TEXTEFFECTSTYLE2D_RELIEF_EMBOSSED; 468 } 469 } 470 471 Primitive2DReference aNewTextEffect(new TextEffectPrimitive2D( 472 aRetval, 473 aDecTrans.getTranslate(), 474 aDecTrans.getRotate(), 475 aTextEffectStyle2D)); 476 aRetval = Primitive2DSequence(&aNewTextEffect, 1); 477 } 478 else if(bHasOutline) 479 { 480 // create outline using an own helper primitive since this will 481 // be view-dependent 482 Primitive2DReference aNewTextEffect(new TextEffectPrimitive2D( 483 aRetval, 484 aDecTrans.getTranslate(), 485 aDecTrans.getRotate(), 486 TEXTEFFECTSTYLE2D_OUTLINE)); 487 aRetval = Primitive2DSequence(&aNewTextEffect, 1); 488 } 489 490 if(aShadow.is()) 491 { 492 // put shadow in front if there is one to paint timely before 493 // but placed behind content 494 const Primitive2DSequence aContent(aRetval); 495 aRetval = Primitive2DSequence(&aShadow, 1); 496 appendPrimitive2DSequenceToPrimitive2DSequence(aRetval, aContent); 497 } 498 } 499 } 500 501 return aRetval; 502 } 503 504 TextDecoratedPortionPrimitive2D::TextDecoratedPortionPrimitive2D( 505 506 // TextSimplePortionPrimitive2D parameters 507 const basegfx::B2DHomMatrix& rNewTransform, 508 const String& rText, 509 xub_StrLen aTextPosition, 510 xub_StrLen aTextLength, 511 const ::std::vector< double >& rDXArray, 512 const attribute::FontAttribute& rFontAttribute, 513 const ::com::sun::star::lang::Locale& rLocale, 514 const basegfx::BColor& rFontColor, 515 516 // local parameters 517 const basegfx::BColor& rOverlineColor, 518 const basegfx::BColor& rTextlineColor, 519 TextLine eFontOverline, 520 TextLine eFontUnderline, 521 bool bUnderlineAbove, 522 TextStrikeout eTextStrikeout, 523 bool bWordLineMode, 524 TextEmphasisMark eTextEmphasisMark, 525 bool bEmphasisMarkAbove, 526 bool bEmphasisMarkBelow, 527 TextRelief eTextRelief, 528 bool bShadow) 529 : TextSimplePortionPrimitive2D(rNewTransform, rText, aTextPosition, aTextLength, rDXArray, rFontAttribute, rLocale, rFontColor), 530 maOverlineColor(rOverlineColor), 531 maTextlineColor(rTextlineColor), 532 meFontOverline(eFontOverline), 533 meFontUnderline(eFontUnderline), 534 meTextStrikeout(eTextStrikeout), 535 meTextEmphasisMark(eTextEmphasisMark), 536 meTextRelief(eTextRelief), 537 mbUnderlineAbove(bUnderlineAbove), 538 mbWordLineMode(bWordLineMode), 539 mbEmphasisMarkAbove(bEmphasisMarkAbove), 540 mbEmphasisMarkBelow(bEmphasisMarkBelow), 541 mbShadow(bShadow) 542 { 543 } 544 545 bool TextDecoratedPortionPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const 546 { 547 if(TextSimplePortionPrimitive2D::operator==(rPrimitive)) 548 { 549 const TextDecoratedPortionPrimitive2D& rCompare = (TextDecoratedPortionPrimitive2D&)rPrimitive; 550 551 return (getOverlineColor() == rCompare.getOverlineColor() 552 && getTextlineColor() == rCompare.getTextlineColor() 553 && getFontOverline() == rCompare.getFontOverline() 554 && getFontUnderline() == rCompare.getFontUnderline() 555 && getTextStrikeout() == rCompare.getTextStrikeout() 556 && getTextEmphasisMark() == rCompare.getTextEmphasisMark() 557 && getTextRelief() == rCompare.getTextRelief() 558 && getUnderlineAbove() == rCompare.getUnderlineAbove() 559 && getWordLineMode() == rCompare.getWordLineMode() 560 && getEmphasisMarkAbove() == rCompare.getEmphasisMarkAbove() 561 && getEmphasisMarkBelow() == rCompare.getEmphasisMarkBelow() 562 && getShadow() == rCompare.getShadow()); 563 } 564 565 return false; 566 } 567 568 // #i96475# 569 // Added missing implementation. Decorations may (will) stick out of the text's 570 // inking area, so add them if needed 571 basegfx::B2DRange TextDecoratedPortionPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const 572 { 573 const bool bDecoratedIsNeeded( 574 TEXT_LINE_NONE != getFontOverline() 575 || TEXT_LINE_NONE != getFontUnderline() 576 || TEXT_STRIKEOUT_NONE != getTextStrikeout() 577 || TEXT_EMPHASISMARK_NONE != getTextEmphasisMark() 578 || TEXT_RELIEF_NONE != getTextRelief() 579 || getShadow()); 580 581 if(bDecoratedIsNeeded) 582 { 583 // decoration is used, fallback to BufferedDecompositionPrimitive2D::getB2DRange which uses 584 // the own local decomposition for computation and thus creates all necessary 585 // geometric objects 586 return BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation); 587 } 588 else 589 { 590 // no relevant decoration used, fallback to TextSimplePortionPrimitive2D::getB2DRange 591 return TextSimplePortionPrimitive2D::getB2DRange(rViewInformation); 592 } 593 } 594 595 // provide unique ID 596 ImplPrimitrive2DIDBlock(TextDecoratedPortionPrimitive2D, PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D) 597 598 } // end of namespace primitive2d 599 } // end of namespace drawinglayer 600 601 ////////////////////////////////////////////////////////////////////////////// 602 // eof 603