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