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