/src/libreoffice/svx/source/customshapes/EnhancedCustomShapeFontWork.cxx
Line | Count | Source |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* |
3 | | * This file is part of the LibreOffice project. |
4 | | * |
5 | | * This Source Code Form is subject to the terms of the Mozilla Public |
6 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
7 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
8 | | * |
9 | | * This file incorporates work covered by the following license notice: |
10 | | * |
11 | | * Licensed to the Apache Software Foundation (ASF) under one or more |
12 | | * contributor license agreements. See the NOTICE file distributed |
13 | | * with this work for additional information regarding copyright |
14 | | * ownership. The ASF licenses this file to you under the Apache |
15 | | * License, Version 2.0 (the "License"); you may not use this file |
16 | | * except in compliance with the License. You may obtain a copy of |
17 | | * the License at http://www.apache.org/licenses/LICENSE-2.0 . |
18 | | */ |
19 | | |
20 | | #include "EnhancedCustomShapeFontWork.hxx" |
21 | | #include <svl/itemset.hxx> |
22 | | #include <svx/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> |
32 | | #include <editeng/frmdiritem.hxx> |
33 | | #include <editeng/fontitem.hxx> |
34 | | #include <editeng/postitem.hxx> |
35 | | #include <editeng/wghtitem.hxx> |
36 | | #include <editeng/fhgtitem.hxx> |
37 | | #include <editeng/charscaleitem.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> |
47 | | #include <comphelper/processfactory.hxx> |
48 | | #include <com/sun/star/i18n/BreakIterator.hpp> |
49 | | #include <com/sun/star/i18n/ScriptType.hpp> |
50 | | #include <basegfx/polygon/b2dpolypolygontools.hxx> |
51 | | #include <basegfx/polygon/b2dpolygontools.hxx> |
52 | | #include <sal/log.hxx> |
53 | | #include <rtl/math.hxx> |
54 | | #include <comphelper/configuration.hxx> |
55 | | |
56 | | using namespace com::sun::star; |
57 | | using namespace com::sun::star::uno; |
58 | | |
59 | | namespace { |
60 | | |
61 | | struct FWCharacterData // representing a single character |
62 | | { |
63 | | std::vector< tools::PolyPolygon > vOutlines; |
64 | | tools::Rectangle aBoundRect; |
65 | | }; |
66 | | struct FWParagraphData // representing a single paragraph |
67 | | { |
68 | | OUString aString; |
69 | | std::vector< FWCharacterData > vCharacters; |
70 | | tools::Rectangle aBoundRect; |
71 | | SvxFrameDirection nFrameDirection; |
72 | | }; |
73 | | struct FWTextArea // representing multiple concluding paragraphs |
74 | | { |
75 | | std::vector< FWParagraphData > vParagraphs; |
76 | | tools::Rectangle aBoundRect; |
77 | | sal_Int32 nHAlignMove = 0; |
78 | | }; |
79 | | struct 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 | | |
92 | | static bool InitializeFontWorkData( |
93 | | const SdrObjCustomShape& rSdrObjCustomShape, |
94 | | const sal_uInt16 nOutlinesCount2d, |
95 | | FWData& rFWData) |
96 | 0 | { |
97 | 0 | bool bNoErr = false; |
98 | 0 | bool bSingleLineMode = false; |
99 | 0 | sal_uInt16 nTextAreaCount = nOutlinesCount2d; |
100 | 0 | if ( nOutlinesCount2d & 1 ) |
101 | 0 | bSingleLineMode = true; |
102 | 0 | else |
103 | 0 | nTextAreaCount >>= 1; |
104 | 0 |
|
105 | 0 | const SdrCustomShapeGeometryItem& rGeometryItem( rSdrObjCustomShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) ); |
106 | 0 | const css::uno::Any* pAny = rGeometryItem.GetPropertyValueByName( u"TextPath"_ustr, u"ScaleX"_ustr ); |
107 | 0 | if (pAny) |
108 | 0 | *pAny >>= rFWData.bScaleX; |
109 | 0 | else |
110 | 0 | rFWData.bScaleX = false; |
111 | 0 |
|
112 | 0 | if ( nTextAreaCount ) |
113 | 0 | { |
114 | 0 | rFWData.bSingleLineMode = bSingleLineMode; |
115 | 0 |
|
116 | 0 | // setting the strings |
117 | 0 | OutlinerParaObject* pParaObj(rSdrObjCustomShape.GetOutlinerParaObject()); |
118 | 0 |
|
119 | 0 | if ( pParaObj ) |
120 | 0 | { |
121 | 0 | const EditTextObject& rTextObj = pParaObj->GetTextObject(); |
122 | 0 | sal_Int32 nParagraphsCount = rTextObj.GetParagraphCount(); |
123 | 0 |
|
124 | 0 | // Collect all the lines from all paragraphs |
125 | 0 | std::vector<int> aLineParaID; // which para this line is in |
126 | 0 | std::vector<int> aLineStart; // where this line start in that para |
127 | 0 | std::vector<int> aLineLength; |
128 | 0 | std::vector<OUString> aParaText; |
129 | 0 | for (sal_Int32 nPara = 0; nPara < nParagraphsCount; ++nPara) |
130 | 0 | { |
131 | 0 | aParaText.push_back(rTextObj.GetText(nPara)); |
132 | 0 | sal_Int32 nPos = 0; |
133 | 0 | sal_Int32 nPrevPos = 0; |
134 | 0 | do |
135 | 0 | { |
136 | 0 | // search line break. |
137 | 0 | if (!rSdrObjCustomShape.getSdrModelFromSdrObject().GetCompatibilityFlag( |
138 | 0 | SdrCompatibilityFlag::LegacyFontwork)) |
139 | 0 | nPos = aParaText[nPara].indexOf(sal_Unicode(u'\1'), nPrevPos); |
140 | 0 | else |
141 | 0 | nPos = -1; // tdf#148000: ignore line breaks in legacy fontworks |
142 | 0 |
|
143 | 0 | aLineParaID.push_back(nPara); |
144 | 0 | aLineStart.push_back(nPrevPos); |
145 | 0 | aLineLength.push_back((nPos >= 0 ? nPos : aParaText[nPara].getLength()) |
146 | 0 | - nPrevPos); |
147 | 0 | nPrevPos = nPos + 1; |
148 | 0 | } while (nPos >= 0); |
149 | 0 | } |
150 | 0 |
|
151 | 0 | sal_Int32 nLinesLeft = aLineParaID.size(); |
152 | 0 |
|
153 | 0 | rFWData.nMaxParagraphsPerTextArea = ((nLinesLeft - 1) / nTextAreaCount) + 1; |
154 | 0 | sal_Int32 nLine = 0; |
155 | 0 | while (nLinesLeft && nTextAreaCount) |
156 | 0 | { |
157 | 0 | FWTextArea aTextArea; |
158 | 0 | sal_Int32 nLinesInPara = ((nLinesLeft - 1) / nTextAreaCount) + 1; |
159 | 0 | for (sal_Int32 i = 0; i < nLinesInPara; ++i, ++nLine) |
160 | 0 | { |
161 | 0 | FWParagraphData aParagraphData; |
162 | 0 | aParagraphData.aString = aParaText[aLineParaID[nLine]].subView( |
163 | 0 | aLineStart[nLine], aLineLength[nLine]); |
164 | 0 |
|
165 | 0 | // retrieving some paragraph attributes |
166 | 0 | const SfxItemSet& rParaSet = rTextObj.GetParaAttribs(aLineParaID[nLine]); |
167 | 0 | aParagraphData.nFrameDirection = rParaSet.Get(EE_PARA_WRITINGDIR).GetValue(); |
168 | 0 | aTextArea.vParagraphs.push_back(std::move(aParagraphData)); |
169 | 0 | } |
170 | 0 | rFWData.vTextAreas.push_back(std::move(aTextArea)); |
171 | 0 | nLinesLeft -= nLinesInPara; |
172 | 0 | nTextAreaCount--; |
173 | 0 | } |
174 | 0 |
|
175 | 0 | bNoErr = true; |
176 | 0 | } |
177 | 0 | } |
178 | 0 | return bNoErr; |
179 | 0 | } |
180 | | |
181 | | static double GetLength( const tools::Polygon& rPolygon ) |
182 | 0 | { |
183 | 0 | double fLength = 0; |
184 | 0 | if ( rPolygon.GetSize() > 1 ) |
185 | 0 | { |
186 | 0 | sal_uInt16 nCount = rPolygon.GetSize(); |
187 | 0 | while( --nCount ) |
188 | 0 | fLength += rPolygon.CalcDistance( nCount, nCount - 1 ); |
189 | 0 | } |
190 | 0 | return fLength; |
191 | 0 | } |
192 | | |
193 | | |
194 | | /* CalculateHorizontalScalingFactor returns the horizontal scaling factor for |
195 | | the whole text object, so that each text will match its corresponding 2d Outline */ |
196 | | static void CalculateHorizontalScalingFactor( |
197 | | const SdrObjCustomShape& rSdrObjCustomShape, |
198 | | FWData& rFWData, |
199 | | const tools::PolyPolygon& rOutline2d) |
200 | 0 | { |
201 | 0 | double fScalingFactor = 1.0; |
202 | 0 | rFWData.fVerticalTextScaling = 1.0; |
203 | 0 |
|
204 | 0 | sal_uInt16 i = 0; |
205 | 0 | bool bSingleLineMode = false; |
206 | 0 | sal_uInt16 nOutlinesCount2d = rOutline2d.Count(); |
207 | 0 |
|
208 | 0 | vcl::Font aFont; |
209 | 0 | const SvxFontItem& rFontItem( rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTINFO ) ); |
210 | 0 | const SvxFontHeightItem& rFontHeight( rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTHEIGHT ) ); |
211 | 0 | sal_Int32 nFontSize = rFontHeight.GetHeight(); |
212 | 0 |
|
213 | 0 | if (rFWData.bScaleX) |
214 | 0 | aFont.SetFontHeight( nFontSize ); |
215 | 0 | else |
216 | 0 | aFont.SetFontHeight( rSdrObjCustomShape.GetLogicRect().GetHeight() / rFWData.nMaxParagraphsPerTextArea ); |
217 | 0 |
|
218 | 0 | aFont.SetAlignment( ALIGN_TOP ); |
219 | 0 | aFont.SetFamilyName( rFontItem.GetFamilyName() ); |
220 | 0 | aFont.SetFamily( rFontItem.GetFamily() ); |
221 | 0 | aFont.SetStyleName( rFontItem.GetStyleName() ); |
222 | 0 | const SvxPostureItem& rPostureItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_ITALIC ); |
223 | 0 | aFont.SetItalic( rPostureItem.GetPosture() ); |
224 | 0 |
|
225 | 0 | const SvxWeightItem& rWeightItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_WEIGHT ); |
226 | 0 | aFont.SetWeight( rWeightItem.GetWeight() ); |
227 | 0 | aFont.SetOrientation( 0_deg10 ); |
228 | 0 | // initializing virtual device |
229 | 0 |
|
230 | 0 | ScopedVclPtrInstance< VirtualDevice > pVirDev(DeviceFormat::WITHOUT_ALPHA); |
231 | 0 | pVirDev->SetMapMode(MapMode(MapUnit::Map100thMM)); |
232 | 0 | pVirDev->SetFont( aFont ); |
233 | 0 | pVirDev->SetAntialiasing( AntialiasingFlags::DisableText ); |
234 | 0 |
|
235 | 0 | if ( nOutlinesCount2d & 1 ) |
236 | 0 | bSingleLineMode = true; |
237 | 0 |
|
238 | 0 | // In case of rFWData.bScaleX == true it loops with reduced font size until the current run |
239 | 0 | // results in a fScalingFactor >=1.0. The fact, that case rFWData.bScaleX == true keeps font |
240 | 0 | // size if possible, is not done here with scaling factor 1 but is done in method |
241 | 0 | // FitTextOutlinesToShapeOutlines() |
242 | 0 | do |
243 | 0 | { |
244 | 0 | i = 0; |
245 | 0 | bool bScalingFactorDefined = false; // New calculation for each font size |
246 | 0 | for( const auto& rTextArea : rFWData.vTextAreas ) |
247 | 0 | { |
248 | 0 | // calculating the width of the corresponding 2d text area |
249 | 0 | double fWidth = GetLength( rOutline2d.GetObject( i++ ) ); |
250 | 0 | if ( !bSingleLineMode ) |
251 | 0 | { |
252 | 0 | fWidth += GetLength( rOutline2d.GetObject( i++ ) ); |
253 | 0 | fWidth /= 2.0; |
254 | 0 | } |
255 | 0 |
|
256 | 0 | for( const auto& rParagraph : rTextArea.vParagraphs ) |
257 | 0 | { |
258 | 0 | double fTextWidth = pVirDev->GetTextWidth( rParagraph.aString ); |
259 | 0 | if ( fTextWidth > 0.0 ) |
260 | 0 | { |
261 | 0 | double fScale = fWidth / fTextWidth; |
262 | 0 | if ( !bScalingFactorDefined ) |
263 | 0 | { |
264 | 0 | fScalingFactor = fScale; |
265 | 0 | bScalingFactorDefined = true; |
266 | 0 | } |
267 | 0 | else if (fScale < fScalingFactor) |
268 | 0 | { |
269 | 0 | fScalingFactor = fScale; |
270 | 0 | } |
271 | 0 | } |
272 | 0 | } |
273 | 0 | } |
274 | 0 |
|
275 | 0 | if (fScalingFactor < 1.0) |
276 | 0 | { |
277 | 0 | nFontSize--; |
278 | 0 | aFont.SetFontHeight( nFontSize ); |
279 | 0 | pVirDev->SetFont( aFont ); |
280 | 0 | } |
281 | 0 | } |
282 | 0 | while (rFWData.bScaleX && fScalingFactor < 1.0 && nFontSize > 1 ); |
283 | 0 |
|
284 | 0 | if (nFontSize > 1) |
285 | 0 | rFWData.fVerticalTextScaling = static_cast<double>(nFontSize) / rFontHeight.GetHeight(); |
286 | 0 |
|
287 | 0 | rFWData.fHorizontalTextScaling = fScalingFactor; |
288 | 0 | } |
289 | | |
290 | | static void GetTextAreaOutline( |
291 | | const FWData& rFWData, |
292 | | const SdrObjCustomShape& rSdrObjCustomShape, |
293 | | FWTextArea& rTextArea, |
294 | | bool bSameLetterHeights) |
295 | 0 | { |
296 | 0 | bool bIsVertical(rSdrObjCustomShape.IsVerticalWriting()); |
297 | 0 | sal_Int32 nVerticalOffset = rFWData.nMaxParagraphsPerTextArea > rTextArea.vParagraphs.size() |
298 | 0 | ? rFWData.nSingleLineHeight / 2 : 0; |
299 | 0 |
|
300 | 0 | for( auto& rParagraph : rTextArea.vParagraphs ) |
301 | 0 | { |
302 | 0 | const OUString& rText = rParagraph.aString; |
303 | 0 | if ( !rText.isEmpty() ) |
304 | 0 | { |
305 | 0 | // generating vcl/font |
306 | 0 | sal_uInt16 nScriptType = i18n::ScriptType::LATIN; |
307 | 0 | Reference< i18n::XBreakIterator > xBI( EnhancedCustomShapeFontWork::GetBreakIterator() ); |
308 | 0 | if ( xBI.is() ) |
309 | 0 | { |
310 | 0 | nScriptType = xBI->getScriptType( rText, 0 ); |
311 | 0 | if( i18n::ScriptType::WEAK == nScriptType ) |
312 | 0 | { |
313 | 0 | sal_Int32 nChg = xBI->endOfScript( rText, 0, nScriptType ); |
314 | 0 | if (nChg < rText.getLength() && nChg >= 0) |
315 | 0 | nScriptType = xBI->getScriptType( rText, nChg ); |
316 | 0 | else |
317 | 0 | nScriptType = i18n::ScriptType::LATIN; |
318 | 0 | } |
319 | 0 | } |
320 | 0 | sal_uInt16 nFntItm = EE_CHAR_FONTINFO; |
321 | 0 | if ( nScriptType == i18n::ScriptType::COMPLEX ) |
322 | 0 | nFntItm = EE_CHAR_FONTINFO_CTL; |
323 | 0 | else if ( nScriptType == i18n::ScriptType::ASIAN ) |
324 | 0 | nFntItm = EE_CHAR_FONTINFO_CJK; |
325 | 0 | const SvxFontItem& rFontItem = static_cast<const SvxFontItem&>(rSdrObjCustomShape.GetMergedItem( nFntItm )); |
326 | 0 | vcl::Font aFont; |
327 | 0 |
|
328 | 0 | aFont.SetFontHeight( rFWData.nSingleLineHeight ); |
329 | 0 |
|
330 | 0 | aFont.SetAlignment( ALIGN_TOP ); |
331 | 0 |
|
332 | 0 | aFont.SetFamilyName( rFontItem.GetFamilyName() ); |
333 | 0 | aFont.SetFamily( rFontItem.GetFamily() ); |
334 | 0 | aFont.SetStyleName( rFontItem.GetStyleName() ); |
335 | 0 | aFont.SetOrientation( 0_deg10 ); |
336 | 0 |
|
337 | 0 | const SvxPostureItem& rPostureItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_ITALIC ); |
338 | 0 | aFont.SetItalic( rPostureItem.GetPosture() ); |
339 | 0 |
|
340 | 0 | const SvxWeightItem& rWeightItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_WEIGHT ); |
341 | 0 | aFont.SetWeight( rWeightItem.GetWeight() ); |
342 | 0 |
|
343 | 0 | // initializing virtual device |
344 | 0 | ScopedVclPtrInstance< VirtualDevice > pVirDev(DeviceFormat::WITHOUT_ALPHA); |
345 | 0 | pVirDev->SetMapMode(MapMode(MapUnit::Map100thMM)); |
346 | 0 | pVirDev->SetFont( aFont ); |
347 | 0 | pVirDev->SetAntialiasing( AntialiasingFlags::DisableText ); |
348 | 0 |
|
349 | 0 | pVirDev->EnableRTL(); |
350 | 0 | if ( rParagraph.nFrameDirection == SvxFrameDirection::Horizontal_RL_TB ) |
351 | 0 | pVirDev->SetLayoutMode( vcl::text::ComplexTextLayoutFlags::BiDiRtl ); |
352 | 0 |
|
353 | 0 | const SvxCharScaleWidthItem& rCharScaleWidthItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTWIDTH ); |
354 | 0 | sal_uInt16 nCharScaleWidth = rCharScaleWidthItem.GetValue(); |
355 | 0 | sal_Int32 nWidth = 0; |
356 | 0 |
|
357 | 0 | // VERTICAL |
358 | 0 | if ( bIsVertical ) |
359 | 0 | { |
360 | 0 | // vertical _> each single character needs to be rotated by 90 |
361 | 0 | sal_Int32 i; |
362 | 0 | sal_Int32 nHeight = 0; |
363 | 0 | tools::Rectangle aSingleCharacterUnion; |
364 | 0 | for ( i = 0; i < rText.getLength(); i++ ) |
365 | 0 | { |
366 | 0 | FWCharacterData aCharacterData; |
367 | 0 | OUString aCharText( rText[ i ] ); |
368 | 0 | if ( pVirDev->GetTextOutlines( aCharacterData.vOutlines, aCharText, 0, 0, -1, nWidth, {} ) ) |
369 | 0 | { |
370 | 0 | sal_Int32 nTextWidth = pVirDev->GetTextWidth( aCharText); |
371 | 0 | if ( aCharacterData.vOutlines.empty() ) |
372 | 0 | { |
373 | 0 | nHeight += rFWData.nSingleLineHeight; |
374 | 0 | } |
375 | 0 | else |
376 | 0 | { |
377 | 0 | for ( auto& rOutline : aCharacterData.vOutlines ) |
378 | 0 | { |
379 | 0 | // rotating |
380 | 0 | rOutline.Rotate( Point( nTextWidth / 2, rFWData.nSingleLineHeight / 2 ), 900_deg10 ); |
381 | 0 | aCharacterData.aBoundRect.Union( rOutline.GetBoundRect() ); |
382 | 0 | } |
383 | 0 | for ( auto& rOutline : aCharacterData.vOutlines ) |
384 | 0 | { |
385 | 0 | sal_Int32 nM = - aCharacterData.aBoundRect.Left() + nHeight; |
386 | 0 | rOutline.Move( nM, 0 ); |
387 | 0 | aCharacterData.aBoundRect.Move( nM, 0 ); |
388 | 0 | } |
389 | 0 | nHeight += aCharacterData.aBoundRect.GetWidth() + ( rFWData.nSingleLineHeight / 5 ); |
390 | 0 | aSingleCharacterUnion.Union( aCharacterData.aBoundRect ); |
391 | 0 | } |
392 | 0 | } |
393 | 0 | rParagraph.vCharacters.push_back( aCharacterData ); |
394 | 0 | } |
395 | 0 | for ( auto& rCharacter : rParagraph.vCharacters ) |
396 | 0 | { |
397 | 0 | for ( auto& rOutline : rCharacter.vOutlines ) |
398 | 0 | { |
399 | 0 | rOutline.Move( ( aSingleCharacterUnion.GetWidth() - rCharacter.aBoundRect.GetWidth() ) / 2, 0 ); |
400 | 0 | } |
401 | 0 | } |
402 | 0 | } |
403 | 0 | else |
404 | 0 | { |
405 | 0 | KernArray aDXArray; |
406 | 0 | if ( ( nCharScaleWidth != 100 ) && nCharScaleWidth ) |
407 | 0 | { // applying character spacing |
408 | 0 | pVirDev->GetTextArray( rText, &aDXArray); |
409 | 0 | FontMetric aFontMetric( pVirDev->GetFontMetric() ); |
410 | 0 | aFont.SetAverageFontWidth( static_cast<sal_Int32>( static_cast<double>(aFontMetric.GetAverageFontWidth()) * ( double(100) / static_cast<double>(nCharScaleWidth) ) ) ); |
411 | 0 | pVirDev->SetFont( aFont ); |
412 | 0 | } |
413 | 0 | FWCharacterData aCharacterData; |
414 | 0 | if ( pVirDev->GetTextOutlines( aCharacterData.vOutlines, rText, 0, 0, -1, nWidth, aDXArray ) ) |
415 | 0 | { |
416 | 0 | rParagraph.vCharacters.push_back( aCharacterData ); |
417 | 0 | } |
418 | 0 | else |
419 | 0 | { |
420 | 0 | // GetTextOutlines failed what usually means that it is |
421 | 0 | // not implemented. To make FontWork not fail (it is |
422 | 0 | // dependent of graphic content to get a Range) create |
423 | 0 | // a rectangle substitution for now |
424 | 0 | pVirDev->GetTextArray( rText, &aDXArray); |
425 | 0 | aCharacterData.vOutlines.clear(); |
426 | 0 |
|
427 | 0 | if(!aDXArray.empty()) |
428 | 0 | { |
429 | 0 | for(size_t a(0); a < aDXArray.size(); a++) |
430 | 0 | { |
431 | 0 | const basegfx::B2DPolygon aPolygon( |
432 | 0 | basegfx::utils::createPolygonFromRect( |
433 | 0 | basegfx::B2DRange( |
434 | 0 | 0 == a ? 0 : aDXArray[a - 1], |
435 | 0 | 0, |
436 | 0 | aDXArray[a], |
437 | 0 | aFont.GetFontHeight() |
438 | 0 | ))); |
439 | 0 | aCharacterData.vOutlines.emplace_back(tools::Polygon(aPolygon)); |
440 | 0 | } |
441 | 0 | } |
442 | 0 | else |
443 | 0 | { |
444 | 0 | const basegfx::B2DPolygon aPolygon( |
445 | 0 | basegfx::utils::createPolygonFromRect( |
446 | 0 | basegfx::B2DRange( |
447 | 0 | 0, |
448 | 0 | 0, |
449 | 0 | 10, |
450 | 0 | aFont.GetFontHeight() |
451 | 0 | ))); |
452 | 0 | aCharacterData.vOutlines.emplace_back(tools::Polygon(aPolygon)); |
453 | 0 | } |
454 | 0 |
|
455 | 0 |
|
456 | 0 | rParagraph.vCharacters.push_back( aCharacterData ); |
457 | 0 | } |
458 | 0 | } |
459 | 0 |
|
460 | 0 | // vertical alignment |
461 | 0 | for ( auto& rCharacter : rParagraph.vCharacters ) |
462 | 0 | { |
463 | 0 | for( tools::PolyPolygon& rPolyPoly : rCharacter.vOutlines ) |
464 | 0 | { |
465 | 0 | if ( nVerticalOffset ) |
466 | 0 | rPolyPoly.Move( 0, nVerticalOffset ); |
467 | 0 |
|
468 | 0 | // retrieving the boundrect for the paragraph |
469 | 0 | tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() ); |
470 | 0 | rParagraph.aBoundRect.Union( aBoundRect ); |
471 | 0 | } |
472 | 0 | } |
473 | 0 | } |
474 | 0 | // updating the boundrect for the text area by merging the current paragraph boundrect |
475 | 0 | if ( rParagraph.aBoundRect.IsEmpty() ) |
476 | 0 | { |
477 | 0 | if ( rTextArea.aBoundRect.IsEmpty() ) |
478 | 0 | rTextArea.aBoundRect = tools::Rectangle( Point( 0, 0 ), Size( 1, rFWData.nSingleLineHeight ) ); |
479 | 0 | else |
480 | 0 | rTextArea.aBoundRect.AdjustBottom(rFWData.nSingleLineHeight ); |
481 | 0 | } |
482 | 0 | else |
483 | 0 | { |
484 | 0 | tools::Rectangle& rParagraphBoundRect = rParagraph.aBoundRect; |
485 | 0 | rTextArea.aBoundRect.Union( rParagraphBoundRect ); |
486 | 0 |
|
487 | 0 | if ( bSameLetterHeights ) |
488 | 0 | { |
489 | 0 | for ( auto& rCharacter : rParagraph.vCharacters ) |
490 | 0 | { |
491 | 0 | for( auto& rOutline : rCharacter.vOutlines ) |
492 | 0 | { |
493 | 0 | tools::Rectangle aPolyPolyBoundRect( rOutline.GetBoundRect() ); |
494 | 0 | if (aPolyPolyBoundRect.GetHeight() != rParagraphBoundRect.GetHeight() && aPolyPolyBoundRect.GetHeight()) |
495 | 0 | rOutline.Scale( 1.0, static_cast<double>(rParagraphBoundRect.GetHeight()) / aPolyPolyBoundRect.GetHeight() ); |
496 | 0 | aPolyPolyBoundRect = rOutline.GetBoundRect(); |
497 | 0 | sal_Int32 nMove = aPolyPolyBoundRect.Top() - rParagraphBoundRect.Top(); |
498 | 0 | if ( nMove ) |
499 | 0 | rOutline.Move( 0, -nMove ); |
500 | 0 | } |
501 | 0 | } |
502 | 0 | } |
503 | 0 | } |
504 | 0 | if ( bIsVertical ) |
505 | 0 | nVerticalOffset -= rFWData.nSingleLineHeight; |
506 | 0 | else |
507 | 0 | nVerticalOffset += rFWData.nSingleLineHeight; |
508 | 0 | } |
509 | 0 | } |
510 | | |
511 | | static bool GetFontWorkOutline( |
512 | | FWData& rFWData, |
513 | | const SdrObjCustomShape& rSdrObjCustomShape) |
514 | 0 | { |
515 | 0 | SdrTextHorzAdjust eHorzAdjust(rSdrObjCustomShape.GetMergedItem( SDRATTR_TEXT_HORZADJUST ).GetValue()); |
516 | 0 | drawing::TextFitToSizeType const eFTS(rSdrObjCustomShape.GetMergedItem( SDRATTR_TEXT_FITTOSIZE ).GetValue()); |
517 | 0 |
|
518 | 0 | bool bSameLetterHeights = false; |
519 | 0 | const SdrCustomShapeGeometryItem& rGeometryItem(rSdrObjCustomShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY )); |
520 | 0 | const css::uno::Any* pAny = rGeometryItem.GetPropertyValueByName( u"TextPath"_ustr, u"SameLetterHeights"_ustr ); |
521 | 0 | if ( pAny ) |
522 | 0 | *pAny >>= bSameLetterHeights; |
523 | 0 |
|
524 | 0 | const SvxFontHeightItem& rFontHeight( rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTHEIGHT ) ); |
525 | 0 | if (rFWData.bScaleX) |
526 | 0 | rFWData.nSingleLineHeight = rFWData.fVerticalTextScaling * rFontHeight.GetHeight(); |
527 | 0 | else |
528 | 0 | rFWData.nSingleLineHeight = static_cast<sal_Int32>( ( static_cast<double>( rSdrObjCustomShape.GetLogicRect().GetHeight() ) |
529 | 0 | / rFWData.nMaxParagraphsPerTextArea ) * rFWData.fHorizontalTextScaling ); |
530 | 0 |
|
531 | 0 | if (rFWData.nSingleLineHeight == SAL_MIN_INT32) |
532 | 0 | return false; |
533 | 0 |
|
534 | 0 | for ( auto& rTextArea : rFWData.vTextAreas ) |
535 | 0 | { |
536 | 0 | GetTextAreaOutline( |
537 | 0 | rFWData, |
538 | 0 | rSdrObjCustomShape, |
539 | 0 | rTextArea, |
540 | 0 | bSameLetterHeights); |
541 | 0 |
|
542 | 0 | if (eFTS == drawing::TextFitToSizeType_ALLLINES || |
543 | 0 | // tdf#97630 interpret PROPORTIONAL same as ALLLINES so we don't |
544 | 0 | // need another ODF attribute! |
545 | 0 | eFTS == drawing::TextFitToSizeType_PROPORTIONAL) |
546 | 0 | { |
547 | 0 | for ( auto& rParagraph : rTextArea.vParagraphs ) |
548 | 0 | { |
549 | 0 | sal_Int32 nParaWidth = rParagraph.aBoundRect.GetWidth(); |
550 | 0 | if ( nParaWidth ) |
551 | 0 | { |
552 | 0 | double fScale = static_cast<double>(rTextArea.aBoundRect.GetWidth()) / nParaWidth; |
553 | 0 |
|
554 | 0 | for ( auto& rCharacter : rParagraph.vCharacters ) |
555 | 0 | { |
556 | 0 | for( auto& rOutline : rCharacter.vOutlines ) |
557 | 0 | { |
558 | 0 | rOutline.Scale( fScale, 1.0 ); |
559 | 0 | } |
560 | 0 | } |
561 | 0 | } |
562 | 0 | } |
563 | 0 | } |
564 | 0 | else if (rFWData.bScaleX) |
565 | 0 | { |
566 | 0 | const SdrTextVertAdjust nVertJustify = rSdrObjCustomShape.GetMergedItem( SDRATTR_TEXT_VERTADJUST ).GetValue(); |
567 | 0 | double fFactor = nVertJustify == SdrTextVertAdjust::SDRTEXTVERTADJUST_BOTTOM ? -0.5 : ( nVertJustify == SdrTextVertAdjust::SDRTEXTVERTADJUST_TOP ? 0.5 : 0 ); |
568 | 0 |
|
569 | 0 | for ( auto& rParagraph : rTextArea.vParagraphs ) |
570 | 0 | { |
571 | 0 | sal_Int32 nHorzDiff = 0; |
572 | 0 | sal_Int32 nVertDiff = static_cast<double>( rFWData.nSingleLineHeight ) * fFactor * ( rTextArea.vParagraphs.size() - 1 ); |
573 | 0 | rTextArea.nHAlignMove = nVertDiff; |
574 | 0 |
|
575 | 0 | if ( eHorzAdjust == SDRTEXTHORZADJUST_CENTER ) |
576 | 0 | nHorzDiff = ( rFWData.fHorizontalTextScaling * rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() ) / 2; |
577 | 0 | else if ( eHorzAdjust == SDRTEXTHORZADJUST_RIGHT ) |
578 | 0 | nHorzDiff = ( rFWData.fHorizontalTextScaling * rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() ); |
579 | 0 |
|
580 | 0 | if (nHorzDiff || nVertDiff) |
581 | 0 | { |
582 | 0 | for ( auto& rCharacter : rParagraph.vCharacters ) |
583 | 0 | { |
584 | 0 | for( auto& rOutline : rCharacter.vOutlines ) |
585 | 0 | { |
586 | 0 | rOutline.Move( nHorzDiff, nVertDiff ); |
587 | 0 | } |
588 | 0 | } |
589 | 0 | } |
590 | 0 | } |
591 | 0 | } |
592 | 0 | else |
593 | 0 | { |
594 | 0 | switch( eHorzAdjust ) |
595 | 0 | { |
596 | 0 | case SDRTEXTHORZADJUST_RIGHT : |
597 | 0 | case SDRTEXTHORZADJUST_CENTER: |
598 | 0 | { |
599 | 0 | for ( auto& rParagraph : rTextArea.vParagraphs ) |
600 | 0 | { |
601 | 0 | sal_Int32 nHorzDiff = 0; |
602 | 0 | if ( eHorzAdjust == SDRTEXTHORZADJUST_CENTER ) |
603 | 0 | nHorzDiff = ( rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() ) / 2; |
604 | 0 | else if ( eHorzAdjust == SDRTEXTHORZADJUST_RIGHT ) |
605 | 0 | nHorzDiff = ( rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() ); |
606 | 0 | if ( nHorzDiff ) |
607 | 0 | { |
608 | 0 | for ( auto& rCharacter : rParagraph.vCharacters ) |
609 | 0 | { |
610 | 0 | for( auto& rOutline : rCharacter.vOutlines ) |
611 | 0 | { |
612 | 0 | rOutline.Move( nHorzDiff, 0 ); |
613 | 0 | } |
614 | 0 | } |
615 | 0 | } |
616 | 0 | } |
617 | 0 | } |
618 | 0 | break; |
619 | 0 | default: |
620 | 0 | case SDRTEXTHORZADJUST_BLOCK : break; // don't know |
621 | 0 | case SDRTEXTHORZADJUST_LEFT : break; // already left aligned -> nothing to do |
622 | 0 | } |
623 | 0 | } |
624 | 0 | } |
625 | 0 |
|
626 | 0 | return true; |
627 | 0 | } |
628 | | |
629 | | static basegfx::B2DPolyPolygon GetOutlinesFromShape2d( const SdrObject* pShape2d ) |
630 | 0 | { |
631 | 0 | basegfx::B2DPolyPolygon aOutlines2d; |
632 | 0 |
|
633 | 0 | SdrObjListIter aObjListIter( *pShape2d, SdrIterMode::DeepWithGroups ); |
634 | 0 | while( aObjListIter.IsMore() ) |
635 | 0 | { |
636 | 0 | SdrObject* pPartObj = aObjListIter.Next(); |
637 | 0 | if ( auto pPathObj = dynamic_cast<const SdrPathObj*>( pPartObj)) |
638 | 0 | { |
639 | 0 | basegfx::B2DPolyPolygon aCandidate(pPathObj->GetPathPoly()); |
640 | 0 | if(aCandidate.areControlPointsUsed()) |
641 | 0 | { |
642 | 0 | aCandidate = basegfx::utils::adaptiveSubdivideByAngle(aCandidate); |
643 | 0 | } |
644 | 0 | aOutlines2d.append(aCandidate); |
645 | 0 | } |
646 | 0 | } |
647 | 0 |
|
648 | 0 | return aOutlines2d; |
649 | 0 | } |
650 | | |
651 | | static void CalcDistances( const tools::Polygon& rPoly, std::vector< double >& rDistances ) |
652 | 0 | { |
653 | 0 | sal_uInt16 i, nCount = rPoly.GetSize(); |
654 | 0 | if ( nCount <= 1 ) |
655 | 0 | return; |
656 | 0 |
|
657 | 0 | for ( i = 0; i < nCount; i++ ) |
658 | 0 | { |
659 | 0 | double fDistance = i ? rPoly.CalcDistance( i, i - 1 ) : 0.0; |
660 | 0 | rDistances.push_back( fDistance ); |
661 | 0 | } |
662 | 0 | std::partial_sum( rDistances.begin(), rDistances.end(), rDistances.begin() ); |
663 | 0 | double fLength = rDistances[ rDistances.size() - 1 ]; |
664 | 0 | if ( fLength > 0.0 ) |
665 | 0 | { |
666 | 0 | for ( auto& rDistance : rDistances ) |
667 | 0 | rDistance /= fLength; |
668 | 0 | } |
669 | 0 | } |
670 | | |
671 | | static void InsertMissingOutlinePoints( const std::vector< double >& rDistances, |
672 | | const tools::Rectangle& rTextAreaBoundRect, tools::Polygon& rPoly ) |
673 | 0 | { |
674 | 0 | sal_uInt16 nSize = rPoly.GetSize(); |
675 | 0 | if (nSize == 0) |
676 | 0 | return; |
677 | 0 |
|
678 | 0 | tools::Long nTextWidth = rTextAreaBoundRect.GetWidth(); |
679 | 0 |
|
680 | 0 | if (nTextWidth == 0) |
681 | 0 | throw o3tl::divide_by_zero(); |
682 | 0 |
|
683 | 0 | double fLastDistance = 0.0; |
684 | 0 | for (sal_uInt16 i = 0; i < nSize; ++i) |
685 | 0 | { |
686 | 0 | Point& rPoint = rPoly[ i ]; |
687 | 0 | double fDistance = static_cast<double>( rPoint.X() - rTextAreaBoundRect.Left() ) / static_cast<double>(nTextWidth); |
688 | 0 | if ( i ) |
689 | 0 | { |
690 | 0 | if ( fDistance > fLastDistance ) |
691 | 0 | { |
692 | 0 | std::vector< double >::const_iterator aIter = std::upper_bound( rDistances.begin(), rDistances.end(), fLastDistance ); |
693 | 0 | if ( aIter != rDistances.end() && ( *aIter > fLastDistance ) && ( *aIter < fDistance ) ) |
694 | 0 | { |
695 | 0 | Point& rPt0 = rPoly[ i - 1 ]; |
696 | 0 | sal_Int32 fX = rPoint.X() - rPt0.X(); |
697 | 0 | sal_Int32 fY = rPoint.Y() - rPt0.Y(); |
698 | 0 | double fd = ( 1.0 / ( fDistance - fLastDistance ) ) * ( *aIter - fLastDistance ); |
699 | 0 | rPoly.Insert( i, Point( static_cast<sal_Int32>( rPt0.X() + fX * fd ), static_cast<sal_Int32>( rPt0.Y() + fY * fd ) ) ); |
700 | 0 | fDistance = *aIter; |
701 | 0 | } |
702 | 0 | } |
703 | 0 | else if ( fDistance < fLastDistance ) |
704 | 0 | { |
705 | 0 | std::vector< double >::const_iterator aIter = std::lower_bound( rDistances.begin(), rDistances.end(), fLastDistance ); |
706 | 0 | if ( aIter != rDistances.begin() ) |
707 | 0 | { |
708 | 0 | --aIter; |
709 | 0 | if ( ( *aIter > fDistance ) && ( *aIter < fLastDistance ) ) |
710 | 0 | { |
711 | 0 | Point& rPt0 = rPoly[ i - 1 ]; |
712 | 0 | sal_Int32 fX = rPoint.X() - rPt0.X(); |
713 | 0 | sal_Int32 fY = rPoint.Y() - rPt0.Y(); |
714 | 0 | double fd = ( 1.0 / ( fDistance - fLastDistance ) ) * ( *aIter - fLastDistance ); |
715 | 0 | rPoly.Insert( i, Point( static_cast<sal_Int32>( rPt0.X() + fX * fd ), static_cast<sal_Int32>( rPt0.Y() + fY * fd ) ) ); |
716 | 0 | fDistance = *aIter; |
717 | 0 | } |
718 | 0 | } |
719 | 0 | } |
720 | 0 | } |
721 | 0 | fLastDistance = fDistance; |
722 | 0 | } |
723 | 0 | } |
724 | | |
725 | | //only 2 types used: 'const tools::Polygon&' and 'const std::vector<Point>&' |
726 | | template <class T> |
727 | | static void GetPoint( T rPoly, const std::vector< double >& rDistances, const double& fX, double& fx1, double& fy1 ) |
728 | 0 | { |
729 | 0 | fy1 = fx1 = 0.0; |
730 | 0 | if (rPoly.size() <= 1) |
731 | 0 | return; |
732 | 0 |
|
733 | 0 | std::vector< double >::const_iterator aIter = std::lower_bound( rDistances.begin(), rDistances.end(), fX ); |
734 | 0 | sal_uInt16 nIdx = sal::static_int_cast<sal_uInt16>( std::distance( rDistances.begin(), aIter ) ); |
735 | 0 | if ( aIter == rDistances.end() ) |
736 | 0 | nIdx--; |
737 | 0 | const Point& rPt = rPoly[ nIdx ]; |
738 | 0 | fx1 = rPt.X(); |
739 | 0 | fy1 = rPt.Y(); |
740 | 0 | if ( !nIdx || ( aIter == rDistances.end() ) || rtl::math::approxEqual( *aIter, fX ) ) |
741 | 0 | return; |
742 | 0 |
|
743 | 0 | nIdx = sal::static_int_cast<sal_uInt16>( std::distance( rDistances.begin(), aIter ) ); |
744 | 0 | double fDist0 = *( aIter - 1 ); |
745 | 0 | double fd = ( 1.0 / ( *aIter - fDist0 ) ) * ( fX - fDist0 ); |
746 | 0 | const Point& rPt2 = rPoly[ nIdx - 1 ]; |
747 | 0 | double fWidth = rPt.X() - rPt2.X(); |
748 | 0 | double fHeight= rPt.Y() - rPt2.Y(); |
749 | 0 | fWidth *= fd; |
750 | 0 | fHeight*= fd; |
751 | 0 | fx1 = rPt2.X() + fWidth; |
752 | 0 | fy1 = rPt2.Y() + fHeight; |
753 | 0 | } Unexecuted instantiation: EnhancedCustomShapeFontWork.cxx:void GetPoint<std::__1::vector<Point, std::__1::allocator<Point> > >(std::__1::vector<Point, std::__1::allocator<Point> >, std::__1::vector<double, std::__1::allocator<double> > const&, double const&, double&, double&) Unexecuted instantiation: EnhancedCustomShapeFontWork.cxx:void GetPoint<tools::Polygon>(tools::Polygon, std::__1::vector<double, std::__1::allocator<double> > const&, double const&, double&, double&) |
754 | | |
755 | | static void FitTextOutlinesToShapeOutlines(const tools::PolyPolygon& aOutlines2d, FWData& rFWData, |
756 | | SdrTextHorzAdjust eHorzAdjust, bool bPPFontwork) |
757 | 0 | { |
758 | 0 | sal_uInt16 nOutline2dIdx = 0; |
759 | 0 | for( auto& rTextArea : rFWData.vTextAreas ) |
760 | 0 | { |
761 | 0 | tools::Rectangle rTextAreaBoundRect = rTextArea.aBoundRect; |
762 | 0 | sal_Int32 nLeft = rTextAreaBoundRect.Left(); |
763 | 0 | sal_Int32 nTop = rTextAreaBoundRect.Top(); |
764 | 0 | sal_Int32 nWidth = rTextAreaBoundRect.GetWidth(); |
765 | 0 | sal_Int32 nHeight= rTextAreaBoundRect.GetHeight(); |
766 | 0 |
|
767 | 0 | if (rFWData.bScaleX) |
768 | 0 | { |
769 | 0 | nWidth *= rFWData.fHorizontalTextScaling; |
770 | 0 | } |
771 | 0 |
|
772 | 0 | if ( rFWData.bSingleLineMode && nHeight && nWidth ) |
773 | 0 | { |
774 | 0 | if ( nOutline2dIdx >= aOutlines2d.Count() ) |
775 | 0 | break; |
776 | 0 | const tools::Polygon& rOutlinePoly( aOutlines2d[ nOutline2dIdx++ ] ); |
777 | 0 | const sal_uInt16 nPointCount = rOutlinePoly.GetSize(); |
778 | 0 | if ( nPointCount > 1 ) |
779 | 0 | { |
780 | 0 | std::vector< double > vDistances; |
781 | 0 | vDistances.reserve( nPointCount ); |
782 | 0 | CalcDistances( rOutlinePoly, vDistances ); |
783 | 0 |
|
784 | 0 | if ( !vDistances.empty() ) |
785 | 0 | { |
786 | 0 | // horizontal alignment: how much we have to move text to the right. |
787 | 0 | int nAdjust = -1; |
788 | 0 | switch (eHorzAdjust) |
789 | 0 | { |
790 | 0 | case SDRTEXTHORZADJUST_RIGHT: |
791 | 0 | nAdjust = 2; // 2 half of the possible |
792 | 0 | break; |
793 | 0 | case SDRTEXTHORZADJUST_CENTER: |
794 | 0 | nAdjust = 1; // 1 half of the possible |
795 | 0 | break; |
796 | 0 | case SDRTEXTHORZADJUST_BLOCK: |
797 | 0 | nAdjust = -1; // don't know what it is, so don't even align |
798 | 0 | break; |
799 | 0 | case SDRTEXTHORZADJUST_LEFT: |
800 | 0 | nAdjust = 0; // no need to move |
801 | 0 | break; |
802 | 0 | } |
803 | 0 |
|
804 | 0 | if (bPPFontwork && rTextArea.vParagraphs.size() > 1 && nAdjust >= 0) |
805 | 0 | { |
806 | 0 | // If we have multiple lines of text to fit to the outline (curve) |
807 | 0 | // then we have to be able to calculate outer versions of the outline |
808 | 0 | // where we can fit the next lines of texts |
809 | 0 | // those outer lines will be wider (or shorter) as the original outline |
810 | 0 | // and probably will looks different as the original outline. |
811 | 0 | // |
812 | 0 | // for example if we have an outline like this: |
813 | 0 | // <____> |
814 | 0 | // then the middle part will have the same normals, so distances there, |
815 | 0 | // will not change for an outer outline |
816 | 0 | // while the points near the edge will have different normals, |
817 | 0 | // distances around there will increase for an outer (wider) outline |
818 | 0 |
|
819 | 0 | //Normal vectors for every rOutlinePoly point. 1024 long |
820 | 0 | std::vector<Point> vNorm; |
821 | 0 | //wider curve path points, for current paragraph (rOutlinePoly + vNorm*line) |
822 | 0 | std::vector<Point> vCurOutline; |
823 | 0 | //distances between points of this wider curve |
824 | 0 | std::vector<double> vCurDistances; |
825 | 0 |
|
826 | 0 | vCurDistances.reserve(nPointCount); |
827 | 0 | vCurOutline.reserve(nPointCount); |
828 | 0 | vNorm.reserve(nPointCount); |
829 | 0 |
|
830 | 0 | // Calculate Normal vectors, and allocate curve data |
831 | 0 | sal_uInt16 i; |
832 | 0 | for (i = 0; i < nPointCount; i++) |
833 | 0 | { |
834 | 0 | //Normal vector for a point will be calculated from its neighbour points |
835 | 0 | //except if it is in the start/end of the vector |
836 | 0 | sal_uInt16 nPointIdx1 = i == 0 ? i : i - 1; |
837 | 0 | sal_uInt16 nPointIdx2 = i == nPointCount - 1 ? i : i + 1; |
838 | 0 |
|
839 | 0 | Point aPoint = rOutlinePoly.GetPoint(nPointIdx2) |
840 | 0 | - rOutlinePoly.GetPoint(nPointIdx1); |
841 | 0 |
|
842 | 0 | double fLen = hypot(aPoint.X(), aPoint.Y()); |
843 | 0 |
|
844 | 0 | if (fLen > 0) |
845 | 0 | { |
846 | 0 | //Rotate by 90 degree, and divide by length, to get normal vector |
847 | 0 | vNorm.emplace_back(aPoint.getY() * 1024 / fLen, |
848 | 0 | -aPoint.getX() * 1024 / fLen); |
849 | 0 | } |
850 | 0 | else |
851 | 0 | { |
852 | 0 | vNorm.emplace_back(0, 0); |
853 | 0 | } |
854 | 0 | vCurOutline.emplace_back(Point()); |
855 | 0 | vCurDistances.push_back(0); |
856 | 0 |
|
857 | 0 | } |
858 | 0 |
|
859 | 0 | for( auto& rParagraph : rTextArea.vParagraphs ) |
860 | 0 | { |
861 | 0 | //calculate the actual outline length, and its align adjustments |
862 | 0 | double fAdjust; |
863 | 0 | double fCurWidth; |
864 | 0 |
|
865 | 0 | // distance between the original and the current curve |
866 | 0 | double fCurvesDist = rTextArea.aBoundRect.GetHeight() / 2.0 |
867 | 0 | + rTextArea.aBoundRect.Top() |
868 | 0 | - rParagraph.aBoundRect.Center().Y(); |
869 | 0 | // vertical alignment adjust |
870 | 0 | fCurvesDist -= rTextArea.nHAlignMove; |
871 | 0 |
|
872 | 0 | for (i = 0; i < nPointCount; i++) |
873 | 0 | { |
874 | 0 | vCurOutline[i] |
875 | 0 | = rOutlinePoly.GetPoint(i) + vNorm[i] * fCurvesDist / 1024.0; |
876 | 0 | if (i > 0) |
877 | 0 | { |
878 | 0 | //calculate distances between points on the outer outline |
879 | 0 | const double fDx = vCurOutline[i].X() - vCurOutline[i - 1].X(); |
880 | 0 | const double fDy = vCurOutline[i].Y() - vCurOutline[i - 1].Y(); |
881 | 0 | vCurDistances[i] = hypot(fDx, fDy); |
882 | 0 | } |
883 | 0 | else |
884 | 0 | vCurDistances[i] = 0; |
885 | 0 | } |
886 | 0 | std::partial_sum(vCurDistances.begin(), vCurDistances.end(), |
887 | 0 | vCurDistances.begin()); |
888 | 0 | fCurWidth = vCurDistances[vCurDistances.size() - 1]; |
889 | 0 | if (fCurWidth > 0.0) |
890 | 0 | { |
891 | 0 | for (auto& rDistance : vCurDistances) |
892 | 0 | rDistance /= fCurWidth; |
893 | 0 | } |
894 | 0 |
|
895 | 0 | // if the current outline is longer then the text to fit in, |
896 | 0 | // then we have to divide the bonus space between the |
897 | 0 | // before-/after- text area. |
898 | 0 | // fAdjust means how much space we put before the text. |
899 | 0 | if (fCurWidth > rParagraph.aBoundRect.GetWidth()) |
900 | 0 | { |
901 | 0 | fAdjust |
902 | 0 | = nAdjust * (fCurWidth - rParagraph.aBoundRect.GetWidth()) / 2; |
903 | 0 | } |
904 | 0 | else |
905 | 0 | fAdjust = -1; // we need to shrink the text to fit the curve |
906 | 0 |
|
907 | 0 | for ( auto& rCharacter : rParagraph.vCharacters ) |
908 | 0 | { |
909 | 0 | for (tools::PolyPolygon& rPolyPoly : rCharacter.vOutlines) |
910 | 0 | { |
911 | 0 | tools::Rectangle aBoundRect(rPolyPoly.GetBoundRect()); |
912 | 0 | double fx1 = aBoundRect.Left() - nLeft; |
913 | 0 | double fx2 = aBoundRect.Right() - nLeft; |
914 | 0 |
|
915 | 0 | double fParaRectWidth = rParagraph.aBoundRect.GetWidth(); |
916 | 0 | // Undo Horizontal alignment, hacked into poly coords, |
917 | 0 | // so we can calculate it the right way |
918 | 0 | double fHA = (rFWData.fHorizontalTextScaling |
919 | 0 | * rTextArea.aBoundRect.GetWidth() |
920 | 0 | - rParagraph.aBoundRect.GetWidth()) |
921 | 0 | * nAdjust / 2; |
922 | 0 |
|
923 | 0 | fx1 -= fHA; |
924 | 0 | fx2 -= fHA; |
925 | 0 |
|
926 | 0 | double fy1, fy2; |
927 | 0 | double fM1 = fx1 / fParaRectWidth; |
928 | 0 | double fM2 = fx2 / fParaRectWidth; |
929 | 0 |
|
930 | 0 | // if fAdjust<0, then it means, the text was longer, as |
931 | 0 | // the current outline, so we will skip the text scaling, and |
932 | 0 | // the text horizontal alignment adjustment |
933 | 0 | // so the text will be rendered just as long as the curve is. |
934 | 0 | if (fAdjust >= 0) |
935 | 0 | { |
936 | 0 | fM1 = (fM1 * fParaRectWidth + fAdjust) / fCurWidth; |
937 | 0 | fM2 = (fM2 * fParaRectWidth + fAdjust) / fCurWidth; |
938 | 0 | } |
939 | 0 | // 0 <= fM1,fM2 <= 1 should be true, but rounding errors can |
940 | 0 | // make a small mistake. |
941 | 0 | // make sure they are >0 because GetPoint() need that |
942 | 0 | if (fM1 < 0) fM1 = 0; |
943 | 0 | if (fM2 < 0) fM2 = 0; |
944 | 0 |
|
945 | 0 | GetPoint(vCurOutline, vCurDistances, fM1, fx1, fy1); |
946 | 0 | GetPoint(vCurOutline, vCurDistances, fM2, fx2, fy2); |
947 | 0 |
|
948 | 0 | double fvx = fy2 - fy1; |
949 | 0 | double fvy = - ( fx2 - fx1 ); |
950 | 0 | fx1 = fx1 + ( ( fx2 - fx1 ) * 0.5 ); |
951 | 0 | fy1 = fy1 + ( ( fy2 - fy1 ) * 0.5 ); |
952 | 0 |
|
953 | 0 | double fAngle = atan2( -fvx, -fvy ); |
954 | 0 | double fL = hypot( fvx, fvy ); |
955 | 0 | if (fL == 0.0) |
956 | 0 | { |
957 | 0 | SAL_WARN("svx", "FitTextOutlinesToShapeOutlines div-by-zero, abandon fit"); |
958 | 0 | break; |
959 | 0 | } |
960 | 0 | fvx = fvx / fL; |
961 | 0 | fvy = fvy / fL; |
962 | 0 | // Undo Vertical alignment hacked into poly coords |
963 | 0 | // We already calculated the right alignment into the curve |
964 | 0 | fL = rTextArea.nHAlignMove; |
965 | 0 | fvx *= fL; |
966 | 0 | fvy *= fL; |
967 | 0 | rPolyPoly.Rotate( Point( aBoundRect.Center().X(), rParagraph.aBoundRect.Center().Y() ), sin( fAngle ), cos( fAngle ) ); |
968 | 0 | rPolyPoly.Move( static_cast<sal_Int32>( ( fx1 + fvx )- aBoundRect.Center().X() ), static_cast<sal_Int32>( ( fy1 + fvy ) - rParagraph.aBoundRect.Center().Y() ) ); |
969 | 0 | } |
970 | 0 | } |
971 | 0 | } |
972 | 0 | } |
973 | 0 | else |
974 | 0 | { |
975 | 0 | // Fallback / old way to handle multiple lines: |
976 | 0 | // Every text lines use the same original outline (curve), |
977 | 0 | // it just scale character coordinates to fit to the right text line |
978 | 0 | // (curve), resulting wider/thinner space between characters |
979 | 0 | for (auto& rParagraph : rTextArea.vParagraphs) |
980 | 0 | { |
981 | 0 | for (auto& rCharacter : rParagraph.vCharacters) |
982 | 0 | { |
983 | 0 | for (tools::PolyPolygon& rPolyPoly : rCharacter.vOutlines) |
984 | 0 | { |
985 | 0 | tools::Rectangle aBoundRect(rPolyPoly.GetBoundRect()); |
986 | 0 | double fx1 = aBoundRect.Left() - nLeft; |
987 | 0 | double fx2 = aBoundRect.Right() - nLeft; |
988 | 0 | double fy1, fy2; |
989 | 0 | double fM1 = fx1 / static_cast<double>(nWidth); |
990 | 0 | double fM2 = fx2 / static_cast<double>(nWidth); |
991 | 0 |
|
992 | 0 | GetPoint(rOutlinePoly, vDistances, fM1, fx1, fy1); |
993 | 0 | GetPoint(rOutlinePoly, vDistances, fM2, fx2, fy2); |
994 | 0 |
|
995 | 0 | double fvx = fy2 - fy1; |
996 | 0 | double fvy = -(fx2 - fx1); |
997 | 0 | fx1 = fx1 + ((fx2 - fx1) * 0.5); |
998 | 0 | fy1 = fy1 + ((fy2 - fy1) * 0.5); |
999 | 0 |
|
1000 | 0 | double fAngle = atan2(-fvx, -fvy); |
1001 | 0 | double fL = hypot(fvx, fvy); |
1002 | 0 | if (fL == 0.0) |
1003 | 0 | { |
1004 | 0 | SAL_WARN("svx", "FitTextOutlinesToShapeOutlines div-by-zero, abandon fit"); |
1005 | 0 | break; |
1006 | 0 | } |
1007 | 0 | fvx = fvx / fL; |
1008 | 0 | fvy = fvy / fL; |
1009 | 0 | fL = rTextArea.aBoundRect.GetHeight() / 2.0 + rTextArea.aBoundRect.Top() - rParagraph.aBoundRect.Center().Y(); |
1010 | 0 | fvx *= fL; |
1011 | 0 | fvy *= fL; |
1012 | 0 | rPolyPoly.Rotate( Point( aBoundRect.Center().X(), rParagraph.aBoundRect.Center().Y() ), sin( fAngle ), cos( fAngle ) ); |
1013 | 0 | rPolyPoly.Move( static_cast<sal_Int32>( ( fx1 + fvx )- aBoundRect.Center().X() ), static_cast<sal_Int32>( ( fy1 + fvy ) - rParagraph.aBoundRect.Center().Y() ) ); |
1014 | 0 | } |
1015 | 0 | } |
1016 | 0 | } |
1017 | 0 | } |
1018 | 0 |
|
1019 | 0 | } |
1020 | 0 | } |
1021 | 0 | } |
1022 | 0 | else |
1023 | 0 | { |
1024 | 0 | if ( ( nOutline2dIdx + 1 ) >= aOutlines2d.Count() ) |
1025 | 0 | break; |
1026 | 0 | const tools::Polygon& rOutlinePoly( aOutlines2d[ nOutline2dIdx++ ] ); |
1027 | 0 | const tools::Polygon& rOutlinePoly2( aOutlines2d[ nOutline2dIdx++ ] ); |
1028 | 0 | const sal_uInt16 nPointCount = rOutlinePoly.GetSize(); |
1029 | 0 | const sal_uInt16 nPointCount2 = rOutlinePoly2.GetSize(); |
1030 | 0 | if ( ( nPointCount > 1 ) && ( nPointCount2 > 1 ) ) |
1031 | 0 | { |
1032 | 0 | std::vector< double > vDistances; |
1033 | 0 | vDistances.reserve( nPointCount ); |
1034 | 0 | std::vector< double > vDistances2; |
1035 | 0 | vDistances2.reserve( nPointCount2 ); |
1036 | 0 | CalcDistances( rOutlinePoly, vDistances ); |
1037 | 0 | CalcDistances( rOutlinePoly2, vDistances2 ); |
1038 | 0 | for( auto& rParagraph : rTextArea.vParagraphs ) |
1039 | 0 | { |
1040 | 0 | for ( auto& rCharacter : rParagraph.vCharacters ) |
1041 | 0 | { |
1042 | 0 | for( tools::PolyPolygon& rPolyPoly : rCharacter.vOutlines ) |
1043 | 0 | { |
1044 | 0 | sal_uInt16 i, nPolyCount = rPolyPoly.Count(); |
1045 | 0 | for ( i = 0; i < nPolyCount; i++ ) |
1046 | 0 | { |
1047 | 0 | // #i35928# |
1048 | 0 | basegfx::B2DPolygon aCandidate(rPolyPoly[ i ].getB2DPolygon()); |
1049 | 0 |
|
1050 | 0 | if(aCandidate.areControlPointsUsed()) |
1051 | 0 | { |
1052 | 0 | aCandidate = basegfx::utils::adaptiveSubdivideByAngle(aCandidate); |
1053 | 0 | } |
1054 | 0 |
|
1055 | 0 | // create local polygon copy to work on |
1056 | 0 | tools::Polygon aLocalPoly(aCandidate); |
1057 | 0 |
|
1058 | 0 | InsertMissingOutlinePoints( vDistances, rTextAreaBoundRect, aLocalPoly ); |
1059 | 0 | InsertMissingOutlinePoints( vDistances2, rTextAreaBoundRect, aLocalPoly ); |
1060 | 0 |
|
1061 | 0 | sal_uInt16 _nPointCount = aLocalPoly.GetSize(); |
1062 | 0 | if (_nPointCount) |
1063 | 0 | { |
1064 | 0 | if (!nWidth || !nHeight) |
1065 | 0 | throw o3tl::divide_by_zero(); |
1066 | 0 | for (sal_uInt16 j = 0; j < _nPointCount; ++j) |
1067 | 0 | { |
1068 | 0 | Point& rPoint = aLocalPoly[ j ]; |
1069 | 0 | rPoint.AdjustX( -nLeft ); |
1070 | 0 | rPoint.AdjustY( -nTop ); |
1071 | 0 | double fX = static_cast<double>(rPoint.X()) / static_cast<double>(nWidth); |
1072 | 0 | double fY = static_cast<double>(rPoint.Y()) / static_cast<double>(nHeight); |
1073 | 0 |
|
1074 | 0 | double fx1, fy1, fx2, fy2; |
1075 | 0 | GetPoint( rOutlinePoly, vDistances, fX, fx1, fy1 ); |
1076 | 0 | GetPoint( rOutlinePoly2, vDistances2, fX, fx2, fy2 ); |
1077 | 0 | double fWidth = fx2 - fx1; |
1078 | 0 | double fHeight= fy2 - fy1; |
1079 | 0 | rPoint.setX( static_cast<sal_Int32>( fx1 + fWidth * fY ) ); |
1080 | 0 | rPoint.setY( static_cast<sal_Int32>( fy1 + fHeight* fY ) ); |
1081 | 0 | } |
1082 | 0 | } |
1083 | 0 |
|
1084 | 0 | // write back polygon |
1085 | 0 | rPolyPoly[i] = std::move(aLocalPoly); |
1086 | 0 | } |
1087 | 0 | } |
1088 | 0 | } |
1089 | 0 | } |
1090 | 0 | } |
1091 | 0 | } |
1092 | 0 | } |
1093 | 0 | } |
1094 | | |
1095 | | static rtl::Reference<SdrObject> CreateSdrObjectFromParagraphOutlines( |
1096 | | const FWData& rFWData, |
1097 | | const SdrObjCustomShape& rSdrObjCustomShape) |
1098 | 0 | { |
1099 | 0 | rtl::Reference<SdrObject> pRet; |
1100 | 0 | basegfx::B2DPolyPolygon aPolyPoly; |
1101 | 0 | if ( !rFWData.vTextAreas.empty() ) |
1102 | 0 | { |
1103 | 0 | for ( const auto& rTextArea : rFWData.vTextAreas ) |
1104 | 0 | { |
1105 | 0 | for ( const auto& rParagraph : rTextArea.vParagraphs ) |
1106 | 0 | { |
1107 | 0 | for ( const auto& rCharacter : rParagraph.vCharacters ) |
1108 | 0 | { |
1109 | 0 | for( const auto& rOutline : rCharacter.vOutlines ) |
1110 | 0 | { |
1111 | 0 | aPolyPoly.append( rOutline.getB2DPolyPolygon() ); |
1112 | 0 | } |
1113 | 0 | } |
1114 | 0 | } |
1115 | 0 | } |
1116 | 0 |
|
1117 | 0 | pRet = new SdrPathObj( |
1118 | 0 | rSdrObjCustomShape.getSdrModelFromSdrObject(), |
1119 | 0 | SdrObjKind::Polygon, |
1120 | 0 | std::move(aPolyPoly)); |
1121 | 0 |
|
1122 | 0 | SfxItemSet aSet(rSdrObjCustomShape.GetMergedItemSet()); |
1123 | 0 | aSet.ClearItem( SDRATTR_TEXTDIRECTION ); //SJ: vertical writing is not required, by removing this item no outliner is created |
1124 | 0 | aSet.Put(makeSdrShadowItem(false)); // #i37011# NO shadow for FontWork geometry |
1125 | 0 | pRet->SetMergedItemSet( aSet ); // * otherwise we would crash, because the outliner tries to create a Paraobject, but there is no model |
1126 | 0 | } |
1127 | 0 |
|
1128 | 0 | return pRet; |
1129 | 0 | } |
1130 | | |
1131 | | Reference < i18n::XBreakIterator > EnhancedCustomShapeFontWork::mxBreakIterator; |
1132 | | |
1133 | | Reference < i18n::XBreakIterator > const & EnhancedCustomShapeFontWork::GetBreakIterator() |
1134 | 0 | { |
1135 | 0 | if ( !mxBreakIterator.is() ) |
1136 | 0 | { |
1137 | 0 | const Reference< uno::XComponentContext >& xContext = ::comphelper::getProcessComponentContext(); |
1138 | 0 | mxBreakIterator = i18n::BreakIterator::create(xContext); |
1139 | 0 | } |
1140 | 0 | return mxBreakIterator; |
1141 | 0 | } |
1142 | | |
1143 | | rtl::Reference<SdrObject> EnhancedCustomShapeFontWork::CreateFontWork( |
1144 | | const SdrObject* pShape2d, |
1145 | | const SdrObjCustomShape& rSdrObjCustomShape) |
1146 | 2.69k | { |
1147 | 2.69k | rtl::Reference<SdrObject> pRet; |
1148 | | |
1149 | | // calculating scaling factor is too slow |
1150 | 2.69k | if (comphelper::IsFuzzing()) |
1151 | 2.69k | return pRet; |
1152 | | |
1153 | 0 | tools::PolyPolygon aOutlines2d( GetOutlinesFromShape2d( pShape2d ) ); |
1154 | 0 | sal_uInt16 nOutlinesCount2d = aOutlines2d.Count(); |
1155 | 0 | if ( nOutlinesCount2d ) |
1156 | 0 | { |
1157 | 0 | FWData aFWData; |
1158 | |
|
1159 | 0 | if(InitializeFontWorkData(rSdrObjCustomShape, nOutlinesCount2d, aFWData)) |
1160 | 0 | { |
1161 | | /* retrieves the horizontal scaling factor that has to be used |
1162 | | to fit each paragraph text into its corresponding 2d outline */ |
1163 | 0 | CalculateHorizontalScalingFactor( |
1164 | 0 | rSdrObjCustomShape, |
1165 | 0 | aFWData, |
1166 | 0 | aOutlines2d); |
1167 | | |
1168 | | /* retrieving the Outlines for the each Paragraph. */ |
1169 | 0 | if(!GetFontWorkOutline( |
1170 | 0 | aFWData, |
1171 | 0 | rSdrObjCustomShape)) |
1172 | 0 | { |
1173 | 0 | return nullptr; |
1174 | 0 | } |
1175 | | |
1176 | 0 | SdrTextHorzAdjust eHorzAdjust( |
1177 | 0 | rSdrObjCustomShape.GetMergedItem(SDRATTR_TEXT_HORZADJUST).GetValue()); |
1178 | 0 | bool bPPFontwork = !rSdrObjCustomShape.getSdrModelFromSdrObject().GetCompatibilityFlag( |
1179 | 0 | SdrCompatibilityFlag::LegacyFontwork); |
1180 | 0 | FitTextOutlinesToShapeOutlines( aOutlines2d, aFWData, eHorzAdjust, bPPFontwork ); |
1181 | |
|
1182 | 0 | pRet = CreateSdrObjectFromParagraphOutlines( |
1183 | 0 | aFWData, |
1184 | 0 | rSdrObjCustomShape); |
1185 | 0 | } |
1186 | 0 | } |
1187 | 0 | return pRet; |
1188 | 0 | } |
1189 | | |
1190 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |