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 #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 
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 */
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;
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  {
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  {
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 
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  {
576  for ( i = 0; i < nCount; i++ )
577  {
578  double fDistance = i ? rPoly.CalcDistance( i, i - 1 ) : 0.0;
579  rDistances.push_back( fDistance );
580  }
581  std::partial_sum( rDistances.begin(), rDistances.end(), rDistances.begin() );
582  double fLength = rDistances[ rDistances.size() - 1 ];
583  if ( fLength > 0.0 )
584  {
585  for ( auto& rDistance : rDistances )
586  rDistance /= fLength;
587  }
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  {
650  std::vector< double >::const_iterator aIter = std::lower_bound( rDistances.begin(), rDistances.end(), fX );
651  sal_uInt16 nIdx = sal::static_int_cast<sal_uInt16>( std::distance( rDistances.begin(), aIter ) );
652  if ( aIter == rDistances.end() )
653  nIdx--;
654  const Point& rPt = rPoly[ nIdx ];
655  fx1 = rPt.X();
656  fy1 = rPt.Y();
657  if ( nIdx && ( aIter != rDistances.end() ) && !rtl::math::approxEqual( *aIter, fX ) )
658  {
659  nIdx = sal::static_int_cast<sal_uInt16>( std::distance( rDistances.begin(), aIter ) );
660  double fDist0 = *( aIter - 1 );
661  double fd = ( 1.0 / ( *aIter - fDist0 ) ) * ( fX - fDist0 );
662  const Point& rPt2 = rPoly[ nIdx - 1 ];
663  double fWidth = rPt.X() - rPt2.X();
664  double fHeight= rPt.Y() - rPt2.Y();
665  fWidth *= fd;
666  fHeight*= fd;
667  fx1 = rPt2.X() + fWidth;
668  fy1 = rPt2.Y() + fHeight;
669  }
670  }
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 
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 
853 
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 
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 */
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 
896  aFWData,
897  rSdrObjCustomShape);
898  }
899  }
900  return pRet;
901 }
902 
903 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
void SetFamily(FontFamily)
sal_uInt16 Count() const
long GetWidth() const
constexpr TypedWhichId< SvxFontItem > EE_CHAR_FONTINFO_CJK(EE_CHAR_START+17)
constexpr TypedWhichId< SdrTextHorzAdjustItem > SDRATTR_TEXT_HORZADJUST(SDRATTR_MISC_FIRST+13)
const tools::Polygon & GetObject(sal_uInt16 nPos) const
static void CalculateHorizontalScalingFactor(const SdrObjCustomShape &rSdrObjCustomShape, FWData &rFWData, const tools::PolyPolygon &rOutline2d)
long GetHeight() const
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
constexpr TypedWhichId< SvxPostureItem > EE_CHAR_ITALIC(EE_CHAR_START+7)
void SetWeight(FontWeight)
bool areControlPointsUsed() const
SvxFrameDirection
circle cut
Definition: svdobj.hxx:125
void SetOrientation(short nLineOrientation)
constexpr TypedWhichId< SvxFontHeightItem > EE_CHAR_FONTHEIGHT(EE_CHAR_START+2)
FontFamily GetFamily() const
constexpr TypedWhichId< SvxFrameDirectionItem > EE_PARA_WRITINGDIR(EE_PARA_START+0)
FontItalic GetPosture() const
void Move(long nHorzMoveDelta, long nVertMoveDelta)
bool IsMore() const
Definition: svditer.hxx:62
constexpr TypedWhichId< SvxFontItem > EE_CHAR_FONTINFO_CTL(EE_CHAR_START+18)
void Rotate(const Point &rCenter, double fSin, double fCos)
int nCount
void SetMergedItemSet(const SfxItemSet &rSet, bool bClearAllItems=false)
Definition: svdobj.cxx:1914
const SfxItemSet & GetParaAttribs(sal_Int32 nPara) const
long Top() const
constexpr TypedWhichId< SvxWeightItem > EE_CHAR_WEIGHT(EE_CHAR_START+4)
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)
void SetAlignment(FontAlign)
constexpr TypedWhichId< SvxWritingModeItem > SDRATTR_TEXTDIRECTION(SDRATTR_NOTPERSIST_FIRST+34)
constexpr TypedWhichId< SdrTextFitToSizeTypeItem > SDRATTR_TEXT_FITTOSIZE(SDRATTR_MISC_FIRST+3)
void SetFamilyName(const OUString &rFamilyName)
constexpr TypedWhichId< SdrCustomShapeGeometryItem > SDRATTR_CUSTOMSHAPE_GEOMETRY(SDRATTR_CUSTOMSHAPE_FIRST+2)
int i
FontWeight GetWeight() const
static void CalcDistances(const tools::Polygon &rPoly, std::vector< double > &rDistances)
static bool GetFontWorkOutline(FWData &rFWData, const SdrObjCustomShape &rSdrObjCustomShape)
constexpr TypedWhichId< SvxCharScaleWidthItem > EE_CHAR_FONTWIDTH(EE_CHAR_START+3)
constexpr TypedWhichId< SdrTextVertAdjustItem > SDRATTR_TEXT_VERTADJUST(SDRATTR_MISC_FIRST+8)
#define SAL_MIN_INT32
static void FitTextOutlinesToShapeOutlines(const tools::PolyPolygon &aOutlines2d, FWData &rFWData)
SdrModel & getSdrModelFromSdrObject() const
Definition: svdobj.cxx:273
sal_uInt16 GetSize() const
static SdrObject * CreateSdrObjectFromParagraphOutlines(const FWData &rFWData, const SdrObjCustomShape &rSdrObjCustomShape)
const SfxPoolItem & GetMergedItem(const sal_uInt16 nWhich) const
Definition: svdobj.cxx:1924
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
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
static SdrObject * CreateFontWork(const SdrObject *pShape2d, const SdrObjCustomShape &rSdrObjCustomShape)
const SfxItemSet & GetMergedItemSet() const
Definition: svdobj.cxx:1889
void SetFontHeight(long nHeight)
SdrOnOffItem makeSdrShadowItem(bool bShadow)
Definition: sdshitm.hxx:25
constexpr TypedWhichId< SvxFontItem > EE_CHAR_FONTINFO(EE_CHAR_START+1)
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)
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)