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