1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 2 /* 3 * This file is part of the LibreOffice project. 4 * 5 * This Source Code Form is subject to the terms of the Mozilla Public 6 * License, v. 2.0. If a copy of the MPL was not distributed with this 7 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 * 9 * This file incorporates work covered by the following license notice: 10 * 11 * Licensed to the Apache Software Foundation (ASF) under one or more 12 * contributor license agreements. See the NOTICE file distributed 13 * with this work for additional information regarding copyright 14 * ownership. The ASF licenses this file to you under the Apache 15 * License, Version 2.0 (the "License"); you may not use this file 16 * except in compliance with the License. You may obtain a copy of 17 * the License at http://www.apache.org/licenses/LICENSE-2.0 . 18 */ 19 20 #include "EnhancedCustomShapeFontWork.hxx" 21 #include <svl/itemset.hxx> 22 #include <svx/svddef.hxx> 23 #include <svx/svdopath.hxx> 24 #include <vcl/metric.hxx> 25 #include <svx/sdasitm.hxx> 26 #include <svx/sdtfsitm.hxx> 27 #include <vcl/virdev.hxx> 28 #include <svx/svditer.hxx> 29 #include <editeng/eeitem.hxx> 30 #include <editeng/frmdiritem.hxx> 31 #include <editeng/fontitem.hxx> 32 #include <editeng/postitem.hxx> 33 #include <editeng/wghtitem.hxx> 34 #include <editeng/fhgtitem.hxx> 35 #include <editeng/charscaleitem.hxx> 36 #include <svx/svdoashp.hxx> 37 #include <svx/sdshitm.hxx> 38 #include <editeng/outlobj.hxx> 39 #include <editeng/editobj.hxx> 40 #include <o3tl/numeric.hxx> 41 #include <vector> 42 #include <numeric> 43 #include <algorithm> 44 #include <memory> 45 #include <comphelper/processfactory.hxx> 46 #include <com/sun/star/i18n/BreakIterator.hpp> 47 #include <com/sun/star/i18n/ScriptType.hpp> 48 #include <basegfx/polygon/b2dpolypolygontools.hxx> 49 #include <basegfx/polygon/b2dpolygontools.hxx> 50 #include <sal/log.hxx> 51 #include <rtl/math.hxx> 52 53 using namespace com::sun::star; 54 using namespace com::sun::star::uno; 55 56 namespace { 57 58 struct FWCharacterData // representing a single character 59 { 60 std::vector< tools::PolyPolygon > vOutlines; 61 tools::Rectangle aBoundRect; 62 }; 63 struct FWParagraphData // representing a single paragraph 64 { 65 OUString aString; 66 std::vector< FWCharacterData > vCharacters; 67 tools::Rectangle aBoundRect; 68 SvxFrameDirection nFrameDirection; 69 }; 70 struct FWTextArea // representing multiple concluding paragraphs 71 { 72 std::vector< FWParagraphData > vParagraphs; 73 tools::Rectangle aBoundRect; 74 }; 75 struct FWData // representing the whole text 76 { 77 std::vector< FWTextArea > vTextAreas; 78 double fHorizontalTextScaling; 79 double fVerticalTextScaling; 80 sal_uInt32 nMaxParagraphsPerTextArea; 81 sal_Int32 nSingleLineHeight; 82 bool bSingleLineMode; 83 bool bScaleX; 84 }; 85 86 } 87 88 static bool InitializeFontWorkData( 89 const SdrObjCustomShape& rSdrObjCustomShape, 90 const sal_uInt16 nOutlinesCount2d, 91 FWData& rFWData) 92 { 93 bool bNoErr = false; 94 bool bSingleLineMode = false; 95 sal_uInt16 nTextAreaCount = nOutlinesCount2d; 96 if ( nOutlinesCount2d & 1 ) 97 bSingleLineMode = true; 98 else 99 nTextAreaCount >>= 1; 100 101 const SdrCustomShapeGeometryItem& rGeometryItem( rSdrObjCustomShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) ); 102 const css::uno::Any* pAny = rGeometryItem.GetPropertyValueByName( "TextPath", "ScaleX" ); 103 if (pAny) 104 *pAny >>= rFWData.bScaleX; 105 else 106 rFWData.bScaleX = false; 107 108 if ( nTextAreaCount ) 109 { 110 rFWData.bSingleLineMode = bSingleLineMode; 111 112 // setting the strings 113 OutlinerParaObject* pParaObj(rSdrObjCustomShape.GetOutlinerParaObject()); 114 115 if ( pParaObj ) 116 { 117 const EditTextObject& rTextObj = pParaObj->GetTextObject(); 118 sal_Int32 nParagraphsLeft = rTextObj.GetParagraphCount(); 119 120 rFWData.nMaxParagraphsPerTextArea = ( ( nParagraphsLeft - 1 ) / nTextAreaCount ) + 1; 121 sal_Int32 j = 0; 122 while( nParagraphsLeft && nTextAreaCount ) 123 { 124 FWTextArea aTextArea; 125 sal_Int32 i, nParagraphs = ( ( nParagraphsLeft - 1 ) / nTextAreaCount ) + 1; 126 for ( i = 0; i < nParagraphs; ++i, ++j ) 127 { 128 FWParagraphData aParagraphData; 129 aParagraphData.aString = rTextObj.GetText( j ); 130 131 const SfxItemSet& rParaSet = rTextObj.GetParaAttribs( j ); // retrieving some paragraph attributes 132 aParagraphData.nFrameDirection = rParaSet.Get( EE_PARA_WRITINGDIR ).GetValue(); 133 aTextArea.vParagraphs.push_back( aParagraphData ); 134 } 135 rFWData.vTextAreas.push_back( aTextArea ); 136 nParagraphsLeft -= nParagraphs; 137 nTextAreaCount--; 138 } 139 bNoErr = true; 140 } 141 } 142 return bNoErr; 143 } 144 145 static double GetLength( const tools::Polygon& rPolygon ) 146 { 147 double fLength = 0; 148 if ( rPolygon.GetSize() > 1 ) 149 { 150 sal_uInt16 nCount = rPolygon.GetSize(); 151 while( --nCount ) 152 fLength += rPolygon.CalcDistance( nCount, nCount - 1 ); 153 } 154 return fLength; 155 } 156 157 158 /* CalculateHorizontalScalingFactor returns the horizontal scaling factor for 159 the whole text object, so that each text will match its corresponding 2d Outline */ 160 static void CalculateHorizontalScalingFactor( 161 const SdrObjCustomShape& rSdrObjCustomShape, 162 FWData& rFWData, 163 const tools::PolyPolygon& rOutline2d) 164 { 165 double fScalingFactor = 1.0; 166 bool bScalingFactorDefined = false; 167 rFWData.fVerticalTextScaling = 1.0; 168 169 sal_uInt16 i = 0; 170 bool bSingleLineMode = false; 171 sal_uInt16 nOutlinesCount2d = rOutline2d.Count(); 172 173 vcl::Font aFont; 174 const SvxFontItem& rFontItem( rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTINFO ) ); 175 const SvxFontHeightItem& rFontHeight( rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTHEIGHT ) ); 176 sal_Int32 nFontSize = rFontHeight.GetHeight(); 177 178 if (rFWData.bScaleX) 179 aFont.SetFontHeight( nFontSize ); 180 else 181 aFont.SetFontHeight( rSdrObjCustomShape.GetLogicRect().GetHeight() / rFWData.nMaxParagraphsPerTextArea ); 182 183 aFont.SetAlignment( ALIGN_TOP ); 184 aFont.SetFamilyName( rFontItem.GetFamilyName() ); 185 aFont.SetFamily( rFontItem.GetFamily() ); 186 aFont.SetStyleName( rFontItem.GetStyleName() ); 187 const SvxPostureItem& rPostureItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_ITALIC ); 188 aFont.SetItalic( rPostureItem.GetPosture() ); 189 190 const SvxWeightItem& rWeightItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_WEIGHT ); 191 aFont.SetWeight( rWeightItem.GetWeight() ); 192 aFont.SetOrientation( 0 ); 193 // initializing virtual device 194 195 ScopedVclPtrInstance< VirtualDevice > pVirDev(DeviceFormat::BITMASK); 196 pVirDev->SetMapMode(MapMode(MapUnit::Map100thMM)); 197 pVirDev->SetFont( aFont ); 198 199 if ( nOutlinesCount2d & 1 ) 200 bSingleLineMode = true; 201 202 do 203 { 204 i = 0; 205 for( const auto& rTextArea : rFWData.vTextAreas ) 206 { 207 // calculating the width of the corresponding 2d text area 208 double fWidth = GetLength( rOutline2d.GetObject( i++ ) ); 209 if ( !bSingleLineMode ) 210 { 211 fWidth += GetLength( rOutline2d.GetObject( i++ ) ); 212 fWidth /= 2.0; 213 } 214 215 for( const auto& rParagraph : rTextArea.vParagraphs ) 216 { 217 double fTextWidth = pVirDev->GetTextWidth( rParagraph.aString ); 218 if ( fTextWidth > 0.0 ) 219 { 220 double fScale = fWidth / fTextWidth; 221 if ( !bScalingFactorDefined ) 222 { 223 fScalingFactor = fScale; 224 bScalingFactorDefined = true; 225 } 226 else if ( fScale < fScalingFactor || ( rFWData.bScaleX && fScalingFactor < 1.0 ) ) 227 { 228 fScalingFactor = fScale; 229 } 230 } 231 } 232 } 233 234 if (fScalingFactor < 1.0) 235 { 236 nFontSize--; 237 aFont.SetFontHeight( nFontSize ); 238 pVirDev->SetFont( aFont ); 239 } 240 } 241 while (rFWData.bScaleX && fScalingFactor < 1.0 && nFontSize > 1 ); 242 243 if (nFontSize > 1) 244 rFWData.fVerticalTextScaling = static_cast<double>(nFontSize) / rFontHeight.GetHeight(); 245 // Add some padding 246 if (rFWData.bScaleX) 247 fScalingFactor *= 1.1; 248 249 rFWData.fHorizontalTextScaling = fScalingFactor; 250 } 251 252 static void GetTextAreaOutline( 253 const FWData& rFWData, 254 const SdrObjCustomShape& rSdrObjCustomShape, 255 FWTextArea& rTextArea, 256 bool bSameLetterHeights) 257 { 258 bool bIsVertical(rSdrObjCustomShape.IsVerticalWriting()); 259 sal_Int32 nVerticalOffset = rFWData.nMaxParagraphsPerTextArea > rTextArea.vParagraphs.size() 260 ? rFWData.nSingleLineHeight / 2 : 0; 261 262 for( auto& rParagraph : rTextArea.vParagraphs ) 263 { 264 const OUString& rText = rParagraph.aString; 265 if ( !rText.isEmpty() ) 266 { 267 // generating vcl/font 268 sal_uInt16 nScriptType = i18n::ScriptType::LATIN; 269 Reference< i18n::XBreakIterator > xBI( EnhancedCustomShapeFontWork::GetBreakIterator() ); 270 if ( xBI.is() ) 271 { 272 nScriptType = xBI->getScriptType( rText, 0 ); 273 if( i18n::ScriptType::WEAK == nScriptType ) 274 { 275 sal_Int32 nChg = xBI->endOfScript( rText, 0, nScriptType ); 276 if (nChg < rText.getLength() && nChg >= 0) 277 nScriptType = xBI->getScriptType( rText, nChg ); 278 else 279 nScriptType = i18n::ScriptType::LATIN; 280 } 281 } 282 sal_uInt16 nFntItm = EE_CHAR_FONTINFO; 283 if ( nScriptType == i18n::ScriptType::COMPLEX ) 284 nFntItm = EE_CHAR_FONTINFO_CTL; 285 else if ( nScriptType == i18n::ScriptType::ASIAN ) 286 nFntItm = EE_CHAR_FONTINFO_CJK; 287 const SvxFontItem& rFontItem = static_cast<const SvxFontItem&>(rSdrObjCustomShape.GetMergedItem( nFntItm )); 288 vcl::Font aFont; 289 290 aFont.SetFontHeight( rFWData.nSingleLineHeight ); 291 292 aFont.SetAlignment( ALIGN_TOP ); 293 294 aFont.SetFamilyName( rFontItem.GetFamilyName() ); 295 aFont.SetFamily( rFontItem.GetFamily() ); 296 aFont.SetStyleName( rFontItem.GetStyleName() ); 297 aFont.SetOrientation( 0 ); 298 299 const SvxPostureItem& rPostureItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_ITALIC ); 300 aFont.SetItalic( rPostureItem.GetPosture() ); 301 302 const SvxWeightItem& rWeightItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_WEIGHT ); 303 aFont.SetWeight( rWeightItem.GetWeight() ); 304 305 // initializing virtual device 306 ScopedVclPtrInstance< VirtualDevice > pVirDev(DeviceFormat::BITMASK); 307 pVirDev->SetMapMode(MapMode(MapUnit::Map100thMM)); 308 pVirDev->SetFont( aFont ); 309 pVirDev->EnableRTL(); 310 if ( rParagraph.nFrameDirection == SvxFrameDirection::Horizontal_RL_TB ) 311 pVirDev->SetLayoutMode( ComplexTextLayoutFlags::BiDiRtl ); 312 313 const SvxCharScaleWidthItem& rCharScaleWidthItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTWIDTH ); 314 sal_uInt16 nCharScaleWidth = rCharScaleWidthItem.GetValue(); 315 std::unique_ptr<long[]> pDXArry; 316 sal_Int32 nWidth = 0; 317 318 // VERTICAL 319 if ( bIsVertical ) 320 { 321 // vertical _> each single character needs to be rotated by 90 322 sal_Int32 i; 323 sal_Int32 nHeight = 0; 324 tools::Rectangle aSingleCharacterUnion; 325 for ( i = 0; i < rText.getLength(); i++ ) 326 { 327 FWCharacterData aCharacterData; 328 OUString aCharText( rText[ i ] ); 329 if ( pVirDev->GetTextOutlines( aCharacterData.vOutlines, aCharText, 0, 0, -1, nWidth, pDXArry.get() ) ) 330 { 331 sal_Int32 nTextWidth = pVirDev->GetTextWidth( aCharText); 332 if ( aCharacterData.vOutlines.empty() ) 333 { 334 nHeight += rFWData.nSingleLineHeight; 335 } 336 else 337 { 338 for ( auto& rOutline : aCharacterData.vOutlines ) 339 { 340 // rotating 341 rOutline.Rotate( Point( nTextWidth / 2, rFWData.nSingleLineHeight / 2 ), 900 ); 342 aCharacterData.aBoundRect.Union( rOutline.GetBoundRect() ); 343 } 344 for ( auto& rOutline : aCharacterData.vOutlines ) 345 { 346 sal_Int32 nM = - aCharacterData.aBoundRect.Left() + nHeight; 347 rOutline.Move( nM, 0 ); 348 aCharacterData.aBoundRect.Move( nM, 0 ); 349 } 350 nHeight += aCharacterData.aBoundRect.GetWidth() + ( rFWData.nSingleLineHeight / 5 ); 351 aSingleCharacterUnion.Union( aCharacterData.aBoundRect ); 352 } 353 } 354 rParagraph.vCharacters.push_back( aCharacterData ); 355 } 356 for ( auto& rCharacter : rParagraph.vCharacters ) 357 { 358 for ( auto& rOutline : rCharacter.vOutlines ) 359 { 360 rOutline.Move( ( aSingleCharacterUnion.GetWidth() - rCharacter.aBoundRect.GetWidth() ) / 2, 0 ); 361 } 362 } 363 } 364 else 365 { 366 if ( ( nCharScaleWidth != 100 ) && nCharScaleWidth ) 367 { // applying character spacing 368 pDXArry.reset(new long[ rText.getLength() ]); 369 pVirDev->GetTextArray( rText, pDXArry.get()); 370 FontMetric aFontMetric( pVirDev->GetFontMetric() ); 371 aFont.SetAverageFontWidth( static_cast<sal_Int32>( static_cast<double>(aFontMetric.GetAverageFontWidth()) * ( double(100) / static_cast<double>(nCharScaleWidth) ) ) ); 372 pVirDev->SetFont( aFont ); 373 } 374 FWCharacterData aCharacterData; 375 if ( pVirDev->GetTextOutlines( aCharacterData.vOutlines, rText, 0, 0, -1, nWidth, pDXArry.get() ) ) 376 { 377 rParagraph.vCharacters.push_back( aCharacterData ); 378 } 379 } 380 381 // vertical alignment 382 for ( auto& rCharacter : rParagraph.vCharacters ) 383 { 384 for( tools::PolyPolygon& rPolyPoly : rCharacter.vOutlines ) 385 { 386 if ( nVerticalOffset ) 387 rPolyPoly.Move( 0, nVerticalOffset ); 388 389 // retrieving the boundrect for the paragraph 390 tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() ); 391 rParagraph.aBoundRect.Union( aBoundRect ); 392 } 393 } 394 } 395 // updating the boundrect for the text area by merging the current paragraph boundrect 396 if ( rParagraph.aBoundRect.IsEmpty() ) 397 { 398 if ( rTextArea.aBoundRect.IsEmpty() ) 399 rTextArea.aBoundRect = tools::Rectangle( Point( 0, 0 ), Size( 1, rFWData.nSingleLineHeight ) ); 400 else 401 rTextArea.aBoundRect.AdjustBottom(rFWData.nSingleLineHeight ); 402 } 403 else 404 { 405 tools::Rectangle& rParagraphBoundRect = rParagraph.aBoundRect; 406 rTextArea.aBoundRect.Union( rParagraphBoundRect ); 407 408 if ( bSameLetterHeights ) 409 { 410 for ( auto& rCharacter : rParagraph.vCharacters ) 411 { 412 for( auto& rOutline : rCharacter.vOutlines ) 413 { 414 tools::Rectangle aPolyPolyBoundRect( rOutline.GetBoundRect() ); 415 if (aPolyPolyBoundRect.GetHeight() != rParagraphBoundRect.GetHeight() && aPolyPolyBoundRect.GetHeight()) 416 rOutline.Scale( 1.0, static_cast<double>(rParagraphBoundRect.GetHeight()) / aPolyPolyBoundRect.GetHeight() ); 417 aPolyPolyBoundRect = rOutline.GetBoundRect(); 418 sal_Int32 nMove = aPolyPolyBoundRect.Top() - rParagraphBoundRect.Top(); 419 if ( nMove ) 420 rOutline.Move( 0, -nMove ); 421 } 422 } 423 } 424 } 425 if ( bIsVertical ) 426 nVerticalOffset -= rFWData.nSingleLineHeight; 427 else 428 nVerticalOffset += rFWData.nSingleLineHeight; 429 } 430 } 431 432 static bool GetFontWorkOutline( 433 FWData& rFWData, 434 const SdrObjCustomShape& rSdrObjCustomShape) 435 { 436 SdrTextHorzAdjust eHorzAdjust(rSdrObjCustomShape.GetMergedItem( SDRATTR_TEXT_HORZADJUST ).GetValue()); 437 drawing::TextFitToSizeType const eFTS(rSdrObjCustomShape.GetMergedItem( SDRATTR_TEXT_FITTOSIZE ).GetValue()); 438 439 bool bSameLetterHeights = false; 440 const SdrCustomShapeGeometryItem& rGeometryItem(rSdrObjCustomShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY )); 441 const css::uno::Any* pAny = rGeometryItem.GetPropertyValueByName( "TextPath", "SameLetterHeights" ); 442 if ( pAny ) 443 *pAny >>= bSameLetterHeights; 444 445 const SvxFontHeightItem& rFontHeight( rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTHEIGHT ) ); 446 if (rFWData.bScaleX) 447 rFWData.nSingleLineHeight = rFWData.fVerticalTextScaling * rFontHeight.GetHeight(); 448 else 449 rFWData.nSingleLineHeight = static_cast<sal_Int32>( ( static_cast<double>( rSdrObjCustomShape.GetLogicRect().GetHeight() ) 450 / rFWData.nMaxParagraphsPerTextArea ) * rFWData.fHorizontalTextScaling ); 451 452 if (rFWData.nSingleLineHeight == SAL_MIN_INT32) 453 return false; 454 455 for ( auto& rTextArea : rFWData.vTextAreas ) 456 { 457 GetTextAreaOutline( 458 rFWData, 459 rSdrObjCustomShape, 460 rTextArea, 461 bSameLetterHeights); 462 463 if (eFTS == drawing::TextFitToSizeType_ALLLINES || 464 // tdf#97630 interpret PROPORTIONAL same as ALLLINES so we don't 465 // need another ODF attribute! 466 eFTS == drawing::TextFitToSizeType_PROPORTIONAL) 467 { 468 for ( auto& rParagraph : rTextArea.vParagraphs ) 469 { 470 sal_Int32 nParaWidth = rParagraph.aBoundRect.GetWidth(); 471 if ( nParaWidth ) 472 { 473 double fScale = static_cast<double>(rTextArea.aBoundRect.GetWidth()) / nParaWidth; 474 475 for ( auto& rCharacter : rParagraph.vCharacters ) 476 { 477 for( auto& rOutline : rCharacter.vOutlines ) 478 { 479 rOutline.Scale( fScale, 1.0 ); 480 } 481 } 482 } 483 } 484 } 485 else if (rFWData.bScaleX) 486 { 487 const SdrTextVertAdjust nVertJustify = rSdrObjCustomShape.GetMergedItem( SDRATTR_TEXT_VERTADJUST ).GetValue(); 488 double fFactor = nVertJustify == SdrTextVertAdjust::SDRTEXTVERTADJUST_BOTTOM ? -0.5 : ( nVertJustify == SdrTextVertAdjust::SDRTEXTVERTADJUST_TOP ? 0.5 : 0 ); 489 490 for ( auto& rParagraph : rTextArea.vParagraphs ) 491 { 492 sal_Int32 nHorzDiff = 0; 493 sal_Int32 nVertDiff = static_cast<double>( rFWData.nSingleLineHeight ) * fFactor * ( rTextArea.vParagraphs.size() - 1 ); 494 495 if ( eHorzAdjust == SDRTEXTHORZADJUST_CENTER ) 496 nHorzDiff = ( rFWData.fHorizontalTextScaling * rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() ) / 2; 497 else if ( eHorzAdjust == SDRTEXTHORZADJUST_RIGHT ) 498 nHorzDiff = ( rFWData.fHorizontalTextScaling * rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() ); 499 500 if (nHorzDiff) 501 { 502 for ( auto& rCharacter : rParagraph.vCharacters ) 503 { 504 for( auto& rOutline : rCharacter.vOutlines ) 505 { 506 rOutline.Move( nHorzDiff, nVertDiff ); 507 } 508 } 509 } 510 } 511 } 512 else 513 { 514 switch( eHorzAdjust ) 515 { 516 case SDRTEXTHORZADJUST_RIGHT : 517 case SDRTEXTHORZADJUST_CENTER: 518 { 519 for ( auto& rParagraph : rTextArea.vParagraphs ) 520 { 521 sal_Int32 nHorzDiff = 0; 522 if ( eHorzAdjust == SDRTEXTHORZADJUST_CENTER ) 523 nHorzDiff = ( rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() ) / 2; 524 else if ( eHorzAdjust == SDRTEXTHORZADJUST_RIGHT ) 525 nHorzDiff = ( rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() ); 526 if ( nHorzDiff ) 527 { 528 for ( auto& rCharacter : rParagraph.vCharacters ) 529 { 530 for( auto& rOutline : rCharacter.vOutlines ) 531 { 532 rOutline.Move( nHorzDiff, 0 ); 533 } 534 } 535 } 536 } 537 } 538 break; 539 default: 540 case SDRTEXTHORZADJUST_BLOCK : break; // don't know 541 case SDRTEXTHORZADJUST_LEFT : break; // already left aligned -> nothing to do 542 } 543 } 544 } 545 546 return true; 547 } 548 549 static basegfx::B2DPolyPolygon GetOutlinesFromShape2d( const SdrObject* pShape2d ) 550 { 551 basegfx::B2DPolyPolygon aOutlines2d; 552 553 SdrObjListIter aObjListIter( *pShape2d, SdrIterMode::DeepWithGroups ); 554 while( aObjListIter.IsMore() ) 555 { 556 SdrObject* pPartObj = aObjListIter.Next(); 557 if ( dynamic_cast<const SdrPathObj*>( pPartObj) != nullptr ) 558 { 559 basegfx::B2DPolyPolygon aCandidate(static_cast<SdrPathObj*>(pPartObj)->GetPathPoly()); 560 if(aCandidate.areControlPointsUsed()) 561 { 562 aCandidate = basegfx::utils::adaptiveSubdivideByAngle(aCandidate); 563 } 564 aOutlines2d.append(aCandidate); 565 } 566 } 567 568 return aOutlines2d; 569 } 570 571 static void CalcDistances( const tools::Polygon& rPoly, std::vector< double >& rDistances ) 572 { 573 sal_uInt16 i, nCount = rPoly.GetSize(); 574 if ( nCount <= 1 ) 575 return; 576 577 for ( i = 0; i < nCount; i++ ) 578 { 579 double fDistance = i ? rPoly.CalcDistance( i, i - 1 ) : 0.0; 580 rDistances.push_back( fDistance ); 581 } 582 std::partial_sum( rDistances.begin(), rDistances.end(), rDistances.begin() ); 583 double fLength = rDistances[ rDistances.size() - 1 ]; 584 if ( fLength > 0.0 ) 585 { 586 for ( auto& rDistance : rDistances ) 587 rDistance /= fLength; 588 } 589 } 590 591 static void InsertMissingOutlinePoints( const std::vector< double >& rDistances, 592 const tools::Rectangle& rTextAreaBoundRect, tools::Polygon& rPoly ) 593 { 594 sal_uInt16 nSize = rPoly.GetSize(); 595 if (nSize == 0) 596 return; 597 598 long nTextWidth = rTextAreaBoundRect.GetWidth(); 599 600 if (nTextWidth == 0) 601 throw o3tl::divide_by_zero(); 602 603 double fLastDistance = 0.0; 604 for (sal_uInt16 i = 0; i < nSize; ++i) 605 { 606 Point& rPoint = rPoly[ i ]; 607 double fDistance = static_cast<double>( rPoint.X() - rTextAreaBoundRect.Left() ) / static_cast<double>(nTextWidth); 608 if ( i ) 609 { 610 if ( fDistance > fLastDistance ) 611 { 612 std::vector< double >::const_iterator aIter = std::upper_bound( rDistances.begin(), rDistances.end(), fLastDistance ); 613 if ( aIter != rDistances.end() && ( *aIter > fLastDistance ) && ( *aIter < fDistance ) ) 614 { 615 Point& rPt0 = rPoly[ i - 1 ]; 616 sal_Int32 fX = rPoint.X() - rPt0.X(); 617 sal_Int32 fY = rPoint.Y() - rPt0.Y(); 618 double fd = ( 1.0 / ( fDistance - fLastDistance ) ) * ( *aIter - fLastDistance ); 619 rPoly.Insert( i, Point( static_cast<sal_Int32>( rPt0.X() + fX * fd ), static_cast<sal_Int32>( rPt0.Y() + fY * fd ) ) ); 620 fDistance = *aIter; 621 } 622 } 623 else if ( fDistance < fLastDistance ) 624 { 625 std::vector< double >::const_iterator aIter = std::lower_bound( rDistances.begin(), rDistances.end(), fLastDistance ); 626 if ( aIter != rDistances.begin() ) 627 { 628 --aIter; 629 if ( ( *aIter > fDistance ) && ( *aIter < fLastDistance ) ) 630 { 631 Point& rPt0 = rPoly[ i - 1 ]; 632 sal_Int32 fX = rPoint.X() - rPt0.X(); 633 sal_Int32 fY = rPoint.Y() - rPt0.Y(); 634 double fd = ( 1.0 / ( fDistance - fLastDistance ) ) * ( *aIter - fLastDistance ); 635 rPoly.Insert( i, Point( static_cast<sal_Int32>( rPt0.X() + fX * fd ), static_cast<sal_Int32>( rPt0.Y() + fY * fd ) ) ); 636 fDistance = *aIter; 637 } 638 } 639 } 640 } 641 fLastDistance = fDistance; 642 } 643 } 644 645 static void GetPoint( const tools::Polygon& rPoly, const std::vector< double >& rDistances, const double& fX, double& fx1, double& fy1 ) 646 { 647 fy1 = fx1 = 0.0; 648 if ( rPoly.GetSize() <= 1 ) 649 return; 650 651 std::vector< double >::const_iterator aIter = std::lower_bound( rDistances.begin(), rDistances.end(), fX ); 652 sal_uInt16 nIdx = sal::static_int_cast<sal_uInt16>( std::distance( rDistances.begin(), aIter ) ); 653 if ( aIter == rDistances.end() ) 654 nIdx--; 655 const Point& rPt = rPoly[ nIdx ]; 656 fx1 = rPt.X(); 657 fy1 = rPt.Y(); 658 if ( !nIdx || ( aIter == rDistances.end() ) || rtl::math::approxEqual( *aIter, fX ) ) 659 return; 660 661 nIdx = sal::static_int_cast<sal_uInt16>( std::distance( rDistances.begin(), aIter ) ); 662 double fDist0 = *( aIter - 1 ); 663 double fd = ( 1.0 / ( *aIter - fDist0 ) ) * ( fX - fDist0 ); 664 const Point& rPt2 = rPoly[ nIdx - 1 ]; 665 double fWidth = rPt.X() - rPt2.X(); 666 double fHeight= rPt.Y() - rPt2.Y(); 667 fWidth *= fd; 668 fHeight*= fd; 669 fx1 = rPt2.X() + fWidth; 670 fy1 = rPt2.Y() + fHeight; 671 } 672 673 static void FitTextOutlinesToShapeOutlines( const tools::PolyPolygon& aOutlines2d, FWData& rFWData ) 674 { 675 sal_uInt16 nOutline2dIdx = 0; 676 for( auto& rTextArea : rFWData.vTextAreas ) 677 { 678 tools::Rectangle rTextAreaBoundRect = rTextArea.aBoundRect; 679 sal_Int32 nLeft = rTextAreaBoundRect.Left(); 680 sal_Int32 nTop = rTextAreaBoundRect.Top(); 681 sal_Int32 nWidth = rTextAreaBoundRect.GetWidth(); 682 sal_Int32 nHeight= rTextAreaBoundRect.GetHeight(); 683 684 if (rFWData.bScaleX) 685 { 686 nWidth *= rFWData.fHorizontalTextScaling; 687 } 688 689 if ( rFWData.bSingleLineMode && nHeight && nWidth ) 690 { 691 if ( nOutline2dIdx >= aOutlines2d.Count() ) 692 break; 693 const tools::Polygon& rOutlinePoly( aOutlines2d[ nOutline2dIdx++ ] ); 694 const sal_uInt16 nPointCount = rOutlinePoly.GetSize(); 695 if ( nPointCount > 1 ) 696 { 697 std::vector< double > vDistances; 698 vDistances.reserve( nPointCount ); 699 CalcDistances( rOutlinePoly, vDistances ); 700 if ( !vDistances.empty() ) 701 { 702 for( auto& rParagraph : rTextArea.vParagraphs ) 703 { 704 for ( auto& rCharacter : rParagraph.vCharacters ) 705 { 706 for( tools::PolyPolygon& rPolyPoly : rCharacter.vOutlines ) 707 { 708 tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() ); 709 double fx1 = aBoundRect.Left() - nLeft; 710 double fx2 = aBoundRect.Right() - nLeft; 711 double fy1, fy2; 712 double fM1 = fx1 / static_cast<double>(nWidth); 713 double fM2 = fx2 / static_cast<double>(nWidth); 714 715 GetPoint( rOutlinePoly, vDistances, fM1, fx1, fy1 ); 716 GetPoint( rOutlinePoly, vDistances, fM2, fx2, fy2 ); 717 718 double fvx = fy2 - fy1; 719 double fvy = - ( fx2 - fx1 ); 720 fx1 = fx1 + ( ( fx2 - fx1 ) * 0.5 ); 721 fy1 = fy1 + ( ( fy2 - fy1 ) * 0.5 ); 722 723 double fAngle = atan2( -fvx, -fvy ); 724 double fL = hypot( fvx, fvy ); 725 if (fL == 0.0) 726 { 727 SAL_WARN("svx", "FitTextOutlinesToShapeOutlines div-by-zero, abandon fit"); 728 break; 729 } 730 fvx = fvx / fL; 731 fvy = fvy / fL; 732 fL = rTextArea.aBoundRect.GetHeight() / 2.0 + rTextArea.aBoundRect.Top() - rParagraph.aBoundRect.Center().Y(); 733 fvx *= fL; 734 fvy *= fL; 735 rPolyPoly.Rotate( Point( aBoundRect.Center().X(), rParagraph.aBoundRect.Center().Y() ), sin( fAngle ), cos( fAngle ) ); 736 rPolyPoly.Move( static_cast<sal_Int32>( ( fx1 + fvx )- aBoundRect.Center().X() ), static_cast<sal_Int32>( ( fy1 + fvy ) - rParagraph.aBoundRect.Center().Y() ) ); 737 } 738 } 739 } 740 } 741 } 742 } 743 else 744 { 745 if ( ( nOutline2dIdx + 1 ) >= aOutlines2d.Count() ) 746 break; 747 const tools::Polygon& rOutlinePoly( aOutlines2d[ nOutline2dIdx++ ] ); 748 const tools::Polygon& rOutlinePoly2( aOutlines2d[ nOutline2dIdx++ ] ); 749 const sal_uInt16 nPointCount = rOutlinePoly.GetSize(); 750 const sal_uInt16 nPointCount2 = rOutlinePoly2.GetSize(); 751 if ( ( nPointCount > 1 ) && ( nPointCount2 > 1 ) ) 752 { 753 std::vector< double > vDistances; 754 vDistances.reserve( nPointCount ); 755 std::vector< double > vDistances2; 756 vDistances2.reserve( nPointCount2 ); 757 CalcDistances( rOutlinePoly, vDistances ); 758 CalcDistances( rOutlinePoly2, vDistances2 ); 759 for( auto& rParagraph : rTextArea.vParagraphs ) 760 { 761 for ( auto& rCharacter : rParagraph.vCharacters ) 762 { 763 for( tools::PolyPolygon& rPolyPoly : rCharacter.vOutlines ) 764 { 765 sal_uInt16 i, nPolyCount = rPolyPoly.Count(); 766 for ( i = 0; i < nPolyCount; i++ ) 767 { 768 // #i35928# 769 basegfx::B2DPolygon aCandidate(rPolyPoly[ i ].getB2DPolygon()); 770 771 if(aCandidate.areControlPointsUsed()) 772 { 773 aCandidate = basegfx::utils::adaptiveSubdivideByAngle(aCandidate); 774 } 775 776 // create local polygon copy to work on 777 tools::Polygon aLocalPoly(aCandidate); 778 779 InsertMissingOutlinePoints( vDistances, rTextAreaBoundRect, aLocalPoly ); 780 InsertMissingOutlinePoints( vDistances2, rTextAreaBoundRect, aLocalPoly ); 781 782 sal_uInt16 _nPointCount = aLocalPoly.GetSize(); 783 if (_nPointCount) 784 { 785 if (!nWidth || !nHeight) 786 throw o3tl::divide_by_zero(); 787 for (sal_uInt16 j = 0; j < _nPointCount; ++j) 788 { 789 Point& rPoint = aLocalPoly[ j ]; 790 rPoint.AdjustX( -nLeft ); 791 rPoint.AdjustY( -nTop ); 792 double fX = static_cast<double>(rPoint.X()) / static_cast<double>(nWidth); 793 double fY = static_cast<double>(rPoint.Y()) / static_cast<double>(nHeight); 794 795 double fx1, fy1, fx2, fy2; 796 GetPoint( rOutlinePoly, vDistances, fX, fx1, fy1 ); 797 GetPoint( rOutlinePoly2, vDistances2, fX, fx2, fy2 ); 798 double fWidth = fx2 - fx1; 799 double fHeight= fy2 - fy1; 800 rPoint.setX( static_cast<sal_Int32>( fx1 + fWidth * fY ) ); 801 rPoint.setY( static_cast<sal_Int32>( fy1 + fHeight* fY ) ); 802 } 803 } 804 805 // write back polygon 806 rPolyPoly[ i ] = aLocalPoly; 807 } 808 } 809 } 810 } 811 } 812 } 813 } 814 } 815 816 static SdrObject* CreateSdrObjectFromParagraphOutlines( 817 const FWData& rFWData, 818 const SdrObjCustomShape& rSdrObjCustomShape) 819 { 820 SdrObject* pRet = nullptr; 821 basegfx::B2DPolyPolygon aPolyPoly; 822 if ( !rFWData.vTextAreas.empty() ) 823 { 824 for ( const auto& rTextArea : rFWData.vTextAreas ) 825 { 826 for ( const auto& rParagraph : rTextArea.vParagraphs ) 827 { 828 for ( const auto& rCharacter : rParagraph.vCharacters ) 829 { 830 for( const auto& rOutline : rCharacter.vOutlines ) 831 { 832 aPolyPoly.append( rOutline.getB2DPolyPolygon() ); 833 } 834 } 835 } 836 } 837 838 pRet = new SdrPathObj( 839 rSdrObjCustomShape.getSdrModelFromSdrObject(), 840 OBJ_POLY, 841 aPolyPoly); 842 843 SfxItemSet aSet(rSdrObjCustomShape.GetMergedItemSet()); 844 aSet.ClearItem( SDRATTR_TEXTDIRECTION ); //SJ: vertical writing is not required, by removing this item no outliner is created 845 aSet.Put(makeSdrShadowItem(false)); // #i37011# NO shadow for FontWork geometry 846 pRet->SetMergedItemSet( aSet ); // * otherwise we would crash, because the outliner tries to create a Paraobject, but there is no model 847 } 848 849 return pRet; 850 } 851 852 Reference < i18n::XBreakIterator > EnhancedCustomShapeFontWork::mxBreakIterator; 853 854 Reference < i18n::XBreakIterator > const & EnhancedCustomShapeFontWork::GetBreakIterator() 855 { 856 if ( !mxBreakIterator.is() ) 857 { 858 Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); 859 mxBreakIterator = i18n::BreakIterator::create(xContext); 860 } 861 return mxBreakIterator; 862 } 863 864 SdrObject* EnhancedCustomShapeFontWork::CreateFontWork( 865 const SdrObject* pShape2d, 866 const SdrObjCustomShape& rSdrObjCustomShape) 867 { 868 SdrObject* pRet = nullptr; 869 870 tools::PolyPolygon aOutlines2d( GetOutlinesFromShape2d( pShape2d ) ); 871 sal_uInt16 nOutlinesCount2d = aOutlines2d.Count(); 872 if ( nOutlinesCount2d ) 873 { 874 FWData aFWData; 875 876 if(InitializeFontWorkData(rSdrObjCustomShape, nOutlinesCount2d, aFWData)) 877 { 878 /* retrieves the horizontal scaling factor that has to be used 879 to fit each paragraph text into its corresponding 2d outline */ 880 CalculateHorizontalScalingFactor( 881 rSdrObjCustomShape, 882 aFWData, 883 aOutlines2d); 884 885 /* retrieving the Outlines for the each Paragraph. */ 886 if(!GetFontWorkOutline( 887 aFWData, 888 rSdrObjCustomShape)) 889 { 890 return nullptr; 891 } 892 893 FitTextOutlinesToShapeOutlines( aOutlines2d, aFWData ); 894 895 pRet = CreateSdrObjectFromParagraphOutlines( 896 aFWData, 897 rSdrObjCustomShape); 898 } 899 } 900 return pRet; 901 } 902 903 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 904
