/src/libreoffice/svx/source/svdraw/svdotextpathdecomposition.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 <sal/config.h> |
21 | | |
22 | | #include <o3tl/safeint.hxx> |
23 | | #include <svx/svdotext.hxx> |
24 | | #include <svx/svdoutl.hxx> |
25 | | #include <basegfx/vector/b2dvector.hxx> |
26 | | #include <sdr/primitive2d/sdrtextprimitive2d.hxx> |
27 | | #include <basegfx/polygon/b2dpolygontools.hxx> |
28 | | #include <basegfx/polygon/b2dpolygon.hxx> |
29 | | #include <algorithm> |
30 | | #include <com/sun/star/i18n/BreakIterator.hpp> |
31 | | #include <comphelper/processfactory.hxx> |
32 | | #include <com/sun/star/i18n/CharacterIteratorMode.hpp> |
33 | | #include <drawinglayer/primitive2d/textlayoutdevice.hxx> |
34 | | #include <drawinglayer/primitive2d/textprimitive2d.hxx> |
35 | | #include <basegfx/color/bcolor.hxx> |
36 | | #include <editeng/StripPortionsHelper.hxx> |
37 | | |
38 | | // primitive decomposition helpers |
39 | | #include <drawinglayer/attribute/strokeattribute.hxx> |
40 | | #include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> |
41 | | #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> |
42 | | #include <svx/unoapi.hxx> |
43 | | #include <drawinglayer/geometry/viewinformation2d.hxx> |
44 | | #include <sdr/attribute/sdrformtextoutlineattribute.hxx> |
45 | | #include <utility> |
46 | | |
47 | | using namespace com::sun::star; |
48 | | |
49 | | // PathTextPortion helper |
50 | | |
51 | | namespace |
52 | | { |
53 | | class impPathTextPortion |
54 | | { |
55 | | basegfx::B2DVector maOffset; |
56 | | OUString maText; |
57 | | sal_Int32 mnTextStart; |
58 | | sal_Int32 mnTextLength; |
59 | | sal_Int32 mnParagraph; |
60 | | SvxFont maFont; |
61 | | ::std::vector< double > maDblDXArray; // double DXArray, font size independent -> unit coordinate system |
62 | | ::std::vector< sal_Bool > maKashidaArray; |
63 | | lang::Locale maLocale; |
64 | | |
65 | | bool mbRTL : 1; |
66 | | |
67 | | public: |
68 | | explicit impPathTextPortion(const DrawPortionInfo& rInfo) |
69 | 0 | : maOffset(rInfo.mrStartPos.X(), rInfo.mrStartPos.Y()), |
70 | 0 | maText(rInfo.maText), |
71 | 0 | mnTextStart(rInfo.mnTextStart), |
72 | 0 | mnTextLength(rInfo.mnTextLen), |
73 | 0 | mnParagraph(rInfo.mnPara), |
74 | 0 | maFont(rInfo.mrFont), |
75 | 0 | maKashidaArray(rInfo.mpKashidaArray.begin(), rInfo.mpKashidaArray.end()), |
76 | 0 | maLocale(rInfo.mpLocale ? *rInfo.mpLocale : lang::Locale()), |
77 | 0 | mbRTL(!rInfo.mrFont.IsVertical() && rInfo.IsRTL()) |
78 | 0 | { |
79 | 0 | if(mnTextLength && !rInfo.mpDXArray.empty()) |
80 | 0 | { |
81 | 0 | maDblDXArray.reserve(mnTextLength); |
82 | |
|
83 | 0 | for(sal_Int32 a=0; a < mnTextLength; a++) |
84 | 0 | { |
85 | 0 | maDblDXArray.push_back(rInfo.mpDXArray[a]); |
86 | 0 | } |
87 | 0 | } |
88 | 0 | } |
89 | | |
90 | | // for ::std::sort |
91 | | bool operator<(const impPathTextPortion& rComp) const |
92 | 0 | { |
93 | 0 | if(mnParagraph < rComp.mnParagraph) |
94 | 0 | { |
95 | 0 | return true; |
96 | 0 | } |
97 | | |
98 | 0 | if(maOffset.getX() < rComp.maOffset.getX()) |
99 | 0 | { |
100 | 0 | return true; |
101 | 0 | } |
102 | | |
103 | 0 | return (maOffset.getY() < rComp.maOffset.getY()); |
104 | 0 | } |
105 | | |
106 | 0 | const OUString& getText() const { return maText; } |
107 | 0 | sal_Int32 getTextStart() const { return mnTextStart; } |
108 | 0 | sal_Int32 getTextLength() const { return mnTextLength; } |
109 | 0 | sal_Int32 getParagraph() const { return mnParagraph; } |
110 | 0 | const SvxFont& getFont() const { return maFont; } |
111 | 0 | bool isRTL() const { return mbRTL; } |
112 | 0 | const ::std::vector< double >& getDoubleDXArray() const { return maDblDXArray; } |
113 | 0 | const ::std::vector< sal_Bool >& getKashidaArray() const { return maKashidaArray; } |
114 | 0 | const lang::Locale& getLocale() const { return maLocale; } |
115 | | |
116 | | sal_Int32 getPortionIndex(sal_Int32 nIndex, sal_Int32 nLength) const |
117 | 0 | { |
118 | 0 | if(mbRTL) |
119 | 0 | { |
120 | 0 | return (mnTextStart + (mnTextLength - (nIndex + nLength))); |
121 | 0 | } |
122 | 0 | else |
123 | 0 | { |
124 | 0 | return (mnTextStart + nIndex); |
125 | 0 | } |
126 | 0 | } |
127 | | |
128 | | double getDisplayLength(sal_Int32 nIndex, sal_Int32 nLength) const |
129 | 0 | { |
130 | 0 | drawinglayer::primitive2d::TextLayouterDevice aTextLayouter; |
131 | 0 | double fRetval(0.0); |
132 | |
|
133 | 0 | if(maFont.IsVertical()) |
134 | 0 | { |
135 | 0 | fRetval = aTextLayouter.getTextHeight() * static_cast<double>(nLength); |
136 | 0 | } |
137 | 0 | else |
138 | 0 | { |
139 | 0 | fRetval = aTextLayouter.getTextWidth(maText, getPortionIndex(nIndex, nLength), nLength); |
140 | 0 | } |
141 | |
|
142 | 0 | return fRetval; |
143 | 0 | } |
144 | | }; |
145 | | } // end of anonymous namespace |
146 | | |
147 | | |
148 | | // TextBreakup helper |
149 | | |
150 | | namespace |
151 | | { |
152 | | class TextHierarchyBreakupPathTextPortions : public StripPortionsHelper |
153 | | { |
154 | | ::std::vector< impPathTextPortion > maPathTextPortions; |
155 | | |
156 | | public: |
157 | | virtual void processDrawPortionInfo(const DrawPortionInfo& rDrawPortionInfo) override |
158 | 0 | { |
159 | | // extract and add data for TextOnPath further processing |
160 | 0 | maPathTextPortions.emplace_back(rDrawPortionInfo); |
161 | 0 | } |
162 | | |
163 | | virtual void processDrawBulletInfo(const DrawBulletInfo&) override |
164 | 0 | { |
165 | | // nothing to do here, bullets are for now ignored for TextOnLine |
166 | 0 | } |
167 | | |
168 | | virtual void directlyAddB2DPrimitive(const drawinglayer::primitive2d::Primitive2DReference&) override |
169 | 0 | { |
170 | | // nothing to do here, no support for directly adding Primitives |
171 | 0 | } |
172 | | |
173 | | const ::std::vector< impPathTextPortion >& sortAndGetPathTextPortions() |
174 | 0 | { |
175 | 0 | if(!maPathTextPortions.empty()) |
176 | 0 | { |
177 | | // sort portions by paragraph, x and y |
178 | 0 | ::std::sort(maPathTextPortions.begin(), maPathTextPortions.end()); |
179 | 0 | } |
180 | |
|
181 | 0 | return maPathTextPortions; |
182 | 0 | } |
183 | | }; |
184 | | } // end of anonymous namespace |
185 | | |
186 | | |
187 | | // TextBreakup one poly and one paragraph helper |
188 | | |
189 | | namespace |
190 | | { |
191 | | class impPolygonParagraphHandler |
192 | | { |
193 | | const drawinglayer::attribute::SdrFormTextAttribute maSdrFormTextAttribute; // FormText parameters |
194 | | drawinglayer::primitive2d::Primitive2DContainer& mrDecomposition; // destination primitive list |
195 | | drawinglayer::primitive2d::Primitive2DContainer& mrShadowDecomposition; // destination primitive list for shadow |
196 | | uno::Reference<i18n::XBreakIterator> mxBreak; // break iterator |
197 | | |
198 | | static double getParagraphTextLength(const ::std::vector< const impPathTextPortion* >& rTextPortions) |
199 | 0 | { |
200 | 0 | drawinglayer::primitive2d::TextLayouterDevice aTextLayouter; |
201 | 0 | double fRetval(0.0); |
202 | |
|
203 | 0 | for(const impPathTextPortion* pCandidate : rTextPortions) |
204 | 0 | { |
205 | 0 | if(pCandidate && pCandidate->getTextLength()) |
206 | 0 | { |
207 | 0 | aTextLayouter.setFont(pCandidate->getFont()); |
208 | 0 | fRetval += pCandidate->getDisplayLength(0, pCandidate->getTextLength()); |
209 | 0 | } |
210 | 0 | } |
211 | |
|
212 | 0 | return fRetval; |
213 | 0 | } |
214 | | |
215 | | sal_Int32 getNextGlyphLen(const impPathTextPortion* pCandidate, sal_Int32 nPosition, const lang::Locale& rFontLocale) |
216 | 0 | { |
217 | 0 | sal_Int32 nNextGlyphLen(1); |
218 | |
|
219 | 0 | if(mxBreak.is()) |
220 | 0 | { |
221 | 0 | sal_Int32 nDone(0); |
222 | 0 | nNextGlyphLen = mxBreak->nextCharacters(pCandidate->getText(), nPosition, |
223 | 0 | rFontLocale, i18n::CharacterIteratorMode::SKIPCELL, 1, nDone) - nPosition; |
224 | 0 | } |
225 | |
|
226 | 0 | return nNextGlyphLen; |
227 | 0 | } |
228 | | |
229 | | public: |
230 | | impPolygonParagraphHandler( |
231 | | drawinglayer::attribute::SdrFormTextAttribute aSdrFormTextAttribute, |
232 | | drawinglayer::primitive2d::Primitive2DContainer& rDecomposition, |
233 | | drawinglayer::primitive2d::Primitive2DContainer& rShadowDecomposition) |
234 | 0 | : maSdrFormTextAttribute(std::move(aSdrFormTextAttribute)), |
235 | 0 | mrDecomposition(rDecomposition), |
236 | 0 | mrShadowDecomposition(rShadowDecomposition) |
237 | 0 | { |
238 | | // prepare BreakIterator |
239 | 0 | const uno::Reference<uno::XComponentContext>& xContext = ::comphelper::getProcessComponentContext(); |
240 | 0 | mxBreak = i18n::BreakIterator::create(xContext); |
241 | 0 | } |
242 | | |
243 | | void HandlePair(const basegfx::B2DPolygon& rPolygonCandidate, const ::std::vector< const impPathTextPortion* >& rTextPortions) |
244 | 0 | { |
245 | | // prepare polygon geometry, take into account as many parameters as possible |
246 | 0 | basegfx::B2DPolygon aPolygonCandidate(rPolygonCandidate); |
247 | 0 | const double fPolyLength(basegfx::utils::getLength(aPolygonCandidate)); |
248 | 0 | double fPolyEnd(fPolyLength); |
249 | 0 | double fPolyStart(0.0); |
250 | 0 | double fAutosizeScaleFactor(1.0); |
251 | 0 | bool bAutosizeScale(false); |
252 | |
|
253 | 0 | if(maSdrFormTextAttribute.getFormTextMirror()) |
254 | 0 | { |
255 | 0 | aPolygonCandidate.flip(); |
256 | 0 | } |
257 | |
|
258 | 0 | if(maSdrFormTextAttribute.getFormTextStart() |
259 | 0 | && (XFormTextAdjust::Left == maSdrFormTextAttribute.getFormTextAdjust() |
260 | 0 | || XFormTextAdjust::Right == maSdrFormTextAttribute.getFormTextAdjust())) |
261 | 0 | { |
262 | 0 | if(XFormTextAdjust::Left == maSdrFormTextAttribute.getFormTextAdjust()) |
263 | 0 | { |
264 | 0 | fPolyStart += maSdrFormTextAttribute.getFormTextStart(); |
265 | |
|
266 | 0 | if(fPolyStart > fPolyEnd) |
267 | 0 | { |
268 | 0 | fPolyStart = fPolyEnd; |
269 | 0 | } |
270 | 0 | } |
271 | 0 | else |
272 | 0 | { |
273 | 0 | fPolyEnd -= maSdrFormTextAttribute.getFormTextStart(); |
274 | |
|
275 | 0 | if(fPolyEnd < fPolyStart) |
276 | 0 | { |
277 | 0 | fPolyEnd = fPolyStart; |
278 | 0 | } |
279 | 0 | } |
280 | 0 | } |
281 | |
|
282 | 0 | if(XFormTextAdjust::Left != maSdrFormTextAttribute.getFormTextAdjust()) |
283 | 0 | { |
284 | | // calculate total text length of this paragraph, some layout needs to be done |
285 | 0 | const double fParagraphTextLength(getParagraphTextLength(rTextPortions)); |
286 | | |
287 | | // check if text is too long for paragraph. If yes, handle as if left aligned (default), |
288 | | // but still take care of XFormTextAdjust::AutoSize in that case |
289 | 0 | const bool bTextTooLong(fParagraphTextLength > (fPolyEnd - fPolyStart)); |
290 | |
|
291 | 0 | if(XFormTextAdjust::Right == maSdrFormTextAttribute.getFormTextAdjust()) |
292 | 0 | { |
293 | 0 | if(!bTextTooLong) |
294 | 0 | { |
295 | | // if right aligned, add difference to polygon start |
296 | 0 | fPolyStart += ((fPolyEnd - fPolyStart) - fParagraphTextLength); |
297 | 0 | } |
298 | 0 | } |
299 | 0 | else if(XFormTextAdjust::Center == maSdrFormTextAttribute.getFormTextAdjust()) |
300 | 0 | { |
301 | 0 | if(!bTextTooLong) |
302 | 0 | { |
303 | | // if centered, add half of difference to polygon start |
304 | 0 | fPolyStart += ((fPolyEnd - fPolyStart) - fParagraphTextLength) / 2.0; |
305 | 0 | } |
306 | 0 | } |
307 | 0 | else if(XFormTextAdjust::AutoSize == maSdrFormTextAttribute.getFormTextAdjust()) |
308 | 0 | { |
309 | | // if scale, prepare scale factor between curve length and text length |
310 | 0 | if(0.0 != fParagraphTextLength) |
311 | 0 | { |
312 | 0 | fAutosizeScaleFactor = (fPolyEnd - fPolyStart) / fParagraphTextLength; |
313 | 0 | bAutosizeScale = true; |
314 | 0 | } |
315 | 0 | } |
316 | 0 | } |
317 | | |
318 | | // handle text portions for this paragraph |
319 | 0 | for(auto a = rTextPortions.begin(); a != rTextPortions.end() && fPolyStart < fPolyEnd; ++a) |
320 | 0 | { |
321 | 0 | const impPathTextPortion* pCandidate = *a; |
322 | 0 | basegfx::B2DVector aFontScaling; |
323 | |
|
324 | 0 | if(pCandidate && pCandidate->getTextLength()) |
325 | 0 | { |
326 | 0 | const drawinglayer::attribute::FontAttribute aCandidateFontAttribute( |
327 | 0 | drawinglayer::primitive2d::getFontAttributeFromVclFont( |
328 | 0 | aFontScaling, |
329 | 0 | pCandidate->getFont(), |
330 | 0 | pCandidate->isRTL(), |
331 | 0 | false)); |
332 | |
|
333 | 0 | drawinglayer::primitive2d::TextLayouterDevice aTextLayouter; |
334 | 0 | aTextLayouter.setFont(pCandidate->getFont()); |
335 | 0 | sal_Int32 nUsedTextLength(0); |
336 | |
|
337 | 0 | while(nUsedTextLength < pCandidate->getTextLength() && fPolyStart < fPolyEnd) |
338 | 0 | { |
339 | 0 | sal_Int32 nNextGlyphLen(getNextGlyphLen(pCandidate, pCandidate->getTextStart() + nUsedTextLength, pCandidate->getLocale())); |
340 | | |
341 | | // prepare portion length. Takes RTL sections into account. |
342 | 0 | double fPortionLength(pCandidate->getDisplayLength(nUsedTextLength, nNextGlyphLen)); |
343 | |
|
344 | 0 | if(bAutosizeScale) |
345 | 0 | { |
346 | | // when autosize scaling, expand portion length |
347 | 0 | fPortionLength *= fAutosizeScaleFactor; |
348 | 0 | } |
349 | | |
350 | | // create transformation |
351 | 0 | basegfx::B2DHomMatrix aNewTransformA, aNewTransformB, aNewShadowTransform; |
352 | 0 | basegfx::B2DPoint aStartPos(basegfx::utils::getPositionAbsolute(aPolygonCandidate, fPolyStart, fPolyLength)); |
353 | 0 | basegfx::B2DPoint aEndPos(aStartPos); |
354 | | |
355 | | // add font scaling |
356 | 0 | aNewTransformA.scale(aFontScaling.getX(), aFontScaling.getY()); |
357 | | |
358 | | // prepare scaling of text primitive |
359 | 0 | if(bAutosizeScale) |
360 | 0 | { |
361 | | // when autosize scaling, expand text primitive scaling to it |
362 | 0 | aNewTransformA.scale(fAutosizeScaleFactor, fAutosizeScaleFactor); |
363 | 0 | } |
364 | | |
365 | | // eventually create shadow primitives from aDecomposition and add to rDecomposition |
366 | 0 | const bool bShadow(XFormTextShadow::NONE != maSdrFormTextAttribute.getFormTextShadow()); |
367 | |
|
368 | 0 | if(bShadow) |
369 | 0 | { |
370 | 0 | if(XFormTextShadow::Normal == maSdrFormTextAttribute.getFormTextShadow()) |
371 | 0 | { |
372 | 0 | aNewShadowTransform.translate( |
373 | 0 | maSdrFormTextAttribute.getFormTextShdwXVal(), |
374 | 0 | -maSdrFormTextAttribute.getFormTextShdwYVal()); |
375 | 0 | } |
376 | 0 | else // XFormTextShadow::Slant |
377 | 0 | { |
378 | 0 | double fScaleValue(maSdrFormTextAttribute.getFormTextShdwYVal() / 100.0); |
379 | 0 | double fShearValue(-basegfx::deg2rad<10>(maSdrFormTextAttribute.getFormTextShdwXVal())); |
380 | |
|
381 | 0 | aNewShadowTransform.scale(1.0, fScaleValue); |
382 | 0 | aNewShadowTransform.shearX(sin(fShearValue)); |
383 | 0 | aNewShadowTransform.scale(1.0, cos(fShearValue)); |
384 | 0 | } |
385 | 0 | } |
386 | |
|
387 | 0 | switch(maSdrFormTextAttribute.getFormTextStyle()) |
388 | 0 | { |
389 | 0 | case XFormTextStyle::Rotate : |
390 | 0 | { |
391 | 0 | aEndPos = basegfx::utils::getPositionAbsolute(aPolygonCandidate, fPolyStart + fPortionLength, fPolyLength); |
392 | 0 | const basegfx::B2DVector aDirection(aEndPos - aStartPos); |
393 | 0 | aNewTransformB.rotate(atan2(aDirection.getY(), aDirection.getX())); |
394 | 0 | aNewTransformB.translate(aStartPos.getX(), aStartPos.getY()); |
395 | |
|
396 | 0 | break; |
397 | 0 | } |
398 | 0 | case XFormTextStyle::Upright : |
399 | 0 | { |
400 | 0 | aNewTransformB.translate(aStartPos.getX() - (fPortionLength / 2.0), aStartPos.getY()); |
401 | |
|
402 | 0 | break; |
403 | 0 | } |
404 | 0 | case XFormTextStyle::SlantX : |
405 | 0 | { |
406 | 0 | aEndPos = basegfx::utils::getPositionAbsolute(aPolygonCandidate, fPolyStart + fPortionLength, fPolyLength); |
407 | 0 | const basegfx::B2DVector aDirection(aEndPos - aStartPos); |
408 | 0 | const double fShearValue(atan2(aDirection.getY(), aDirection.getX())); |
409 | 0 | const double fSin(sin(fShearValue)); |
410 | 0 | const double fCos(cos(fShearValue)); |
411 | |
|
412 | 0 | aNewTransformB.shearX(-fSin); |
413 | | |
414 | | // Scale may lead to objects without height since fCos == 0.0 is possible. |
415 | | // Renderers need to handle that, it's not a forbidden value and does not |
416 | | // need to be avoided |
417 | 0 | aNewTransformB.scale(1.0, fCos); |
418 | 0 | aNewTransformB.translate(aStartPos.getX() - (fPortionLength / 2.0), aStartPos.getY()); |
419 | |
|
420 | 0 | break; |
421 | 0 | } |
422 | 0 | case XFormTextStyle::SlantY : |
423 | 0 | { |
424 | 0 | aEndPos = basegfx::utils::getPositionAbsolute(aPolygonCandidate, fPolyStart + fPortionLength, fPolyLength); |
425 | 0 | const basegfx::B2DVector aDirection(aEndPos - aStartPos); |
426 | 0 | const double fShearValue(atan2(aDirection.getY(), aDirection.getX())); |
427 | 0 | const double fCos(cos(fShearValue)); |
428 | 0 | const double fTan(tan(fShearValue)); |
429 | | |
430 | | // shear to 'stand' on the curve |
431 | 0 | aNewTransformB.shearY(fTan); |
432 | | |
433 | | // scale in X to make as tight as needed. As with XFT_SLANT_X, this may |
434 | | // lead to primitives without width which the renderers will handle |
435 | 0 | aNewTransformA.scale(fCos, 1.0); |
436 | |
|
437 | 0 | aNewTransformB.translate(aStartPos.getX(), aStartPos.getY()); |
438 | |
|
439 | 0 | break; |
440 | 0 | } |
441 | 0 | default : break; // XFormTextStyle::NONE |
442 | 0 | } |
443 | | |
444 | | // distance from path? |
445 | 0 | if(maSdrFormTextAttribute.getFormTextDistance()) |
446 | 0 | { |
447 | 0 | if(aEndPos.equal(aStartPos)) |
448 | 0 | { |
449 | 0 | aEndPos = basegfx::utils::getPositionAbsolute(aPolygonCandidate, fPolyStart + fPortionLength, fPolyLength); |
450 | 0 | } |
451 | | |
452 | | // use back vector (aStartPos - aEndPos) here to get mirrored perpendicular as in old stuff |
453 | 0 | const basegfx::B2DVector aPerpendicular( |
454 | 0 | basegfx::getNormalizedPerpendicular(aStartPos - aEndPos) * |
455 | 0 | maSdrFormTextAttribute.getFormTextDistance()); |
456 | 0 | aNewTransformB.translate(aPerpendicular.getX(), aPerpendicular.getY()); |
457 | 0 | } |
458 | |
|
459 | 0 | if(!pCandidate->getText().isEmpty() && nNextGlyphLen) |
460 | 0 | { |
461 | 0 | const sal_Int32 nPortionIndex(pCandidate->getPortionIndex(nUsedTextLength, nNextGlyphLen)); |
462 | 0 | ::std::vector< double > aNewDXArray; |
463 | |
|
464 | 0 | if(nNextGlyphLen > 1 && !pCandidate->getDoubleDXArray().empty()) |
465 | 0 | { |
466 | | // copy DXArray for portion |
467 | 0 | aNewDXArray.insert( |
468 | 0 | aNewDXArray.begin(), |
469 | 0 | pCandidate->getDoubleDXArray().begin() + nPortionIndex, |
470 | 0 | pCandidate->getDoubleDXArray().begin() + (nPortionIndex + nNextGlyphLen)); |
471 | |
|
472 | 0 | if(nPortionIndex > 0) |
473 | 0 | { |
474 | | // adapt to portion start |
475 | 0 | double fDXOffset= *(pCandidate->getDoubleDXArray().begin() + (nPortionIndex - 1)); |
476 | 0 | ::std::transform( |
477 | 0 | aNewDXArray.begin(), aNewDXArray.end(), |
478 | 0 | aNewDXArray.begin(), [fDXOffset](double x) { return x - fDXOffset; }); |
479 | 0 | } |
480 | |
|
481 | 0 | if(bAutosizeScale) |
482 | 0 | { |
483 | | // when autosize scaling, adapt to DXArray, too |
484 | 0 | ::std::transform( |
485 | 0 | aNewDXArray.begin(), aNewDXArray.end(), |
486 | 0 | aNewDXArray.begin(), [fAutosizeScaleFactor](double x) { return x * fAutosizeScaleFactor; }); |
487 | 0 | } |
488 | 0 | } |
489 | |
|
490 | 0 | if(bShadow) |
491 | 0 | { |
492 | | // shadow primitive creation |
493 | 0 | const Color aShadowColor(maSdrFormTextAttribute.getFormTextShdwColor()); |
494 | 0 | const basegfx::BColor aRGBShadowColor(aShadowColor.getBColor()); |
495 | |
|
496 | 0 | mrShadowDecomposition.push_back( |
497 | 0 | new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( |
498 | 0 | aNewTransformB * aNewShadowTransform * aNewTransformA, |
499 | 0 | pCandidate->getText(), |
500 | 0 | nPortionIndex, |
501 | 0 | nNextGlyphLen, |
502 | 0 | std::vector(aNewDXArray), |
503 | 0 | std::vector(pCandidate->getKashidaArray()), |
504 | 0 | aCandidateFontAttribute, |
505 | 0 | pCandidate->getLocale(), |
506 | 0 | aRGBShadowColor) ); |
507 | 0 | } |
508 | |
|
509 | 0 | { |
510 | | // primitive creation |
511 | 0 | const Color aColor(pCandidate->getFont().GetColor()); |
512 | 0 | const basegfx::BColor aRGBColor(aColor.getBColor()); |
513 | |
|
514 | 0 | mrDecomposition.push_back( |
515 | 0 | new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( |
516 | 0 | aNewTransformB * aNewTransformA, |
517 | 0 | pCandidate->getText(), |
518 | 0 | nPortionIndex, |
519 | 0 | nNextGlyphLen, |
520 | 0 | std::move(aNewDXArray), |
521 | 0 | std::vector(pCandidate->getKashidaArray()), |
522 | 0 | aCandidateFontAttribute, |
523 | 0 | pCandidate->getLocale(), |
524 | 0 | aRGBColor) ); |
525 | 0 | } |
526 | 0 | } |
527 | | |
528 | | // consume from portion |
529 | 0 | nUsedTextLength += nNextGlyphLen; |
530 | | |
531 | | // consume from polygon |
532 | 0 | fPolyStart += fPortionLength; |
533 | 0 | } |
534 | 0 | } |
535 | 0 | } |
536 | 0 | } |
537 | | }; |
538 | | } // end of anonymous namespace |
539 | | |
540 | | |
541 | | // primitive decomposition helpers |
542 | | |
543 | | namespace |
544 | | { |
545 | | void impAddPolygonStrokePrimitives( |
546 | | const basegfx::B2DPolyPolygonVector& rB2DPolyPolyVector, |
547 | | const basegfx::B2DHomMatrix& rTransform, |
548 | | const drawinglayer::attribute::LineAttribute& rLineAttribute, |
549 | | const drawinglayer::attribute::StrokeAttribute& rStrokeAttribute, |
550 | | drawinglayer::primitive2d::Primitive2DContainer& rTarget) |
551 | 0 | { |
552 | 0 | for(const auto& rB2DPolyPolygon : rB2DPolyPolyVector) |
553 | 0 | { |
554 | | // prepare PolyPolygons |
555 | 0 | basegfx::B2DPolyPolygon aB2DPolyPolygon = rB2DPolyPolygon; |
556 | 0 | aB2DPolyPolygon.transform(rTransform); |
557 | |
|
558 | 0 | for(auto const& rPolygon : std::as_const(aB2DPolyPolygon)) |
559 | 0 | { |
560 | | // create one primitive per polygon |
561 | 0 | rTarget.push_back( |
562 | 0 | new drawinglayer::primitive2d::PolygonStrokePrimitive2D( |
563 | 0 | rPolygon, rLineAttribute, rStrokeAttribute) ); |
564 | 0 | } |
565 | 0 | } |
566 | 0 | } |
567 | | |
568 | | drawinglayer::primitive2d::Primitive2DContainer impAddPathTextOutlines( |
569 | | const drawinglayer::primitive2d::Primitive2DContainer& rSource, |
570 | | const drawinglayer::attribute::SdrFormTextOutlineAttribute& rOutlineAttribute) |
571 | 0 | { |
572 | 0 | drawinglayer::primitive2d::Primitive2DContainer aNewPrimitives; |
573 | |
|
574 | 0 | for(const drawinglayer::primitive2d::Primitive2DReference& a : rSource) |
575 | 0 | { |
576 | 0 | const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pTextCandidate = dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* >(a.get()); |
577 | |
|
578 | 0 | if(pTextCandidate) |
579 | 0 | { |
580 | 0 | basegfx::B2DPolyPolygonVector aB2DPolyPolyVector; |
581 | 0 | basegfx::B2DHomMatrix aPolygonTransform; |
582 | | |
583 | | // get text outlines and their object transformation |
584 | 0 | pTextCandidate->getTextOutlinesAndTransformation(aB2DPolyPolyVector, aPolygonTransform); |
585 | |
|
586 | 0 | if(!aB2DPolyPolyVector.empty()) |
587 | 0 | { |
588 | | // create stroke primitives |
589 | 0 | drawinglayer::primitive2d::Primitive2DContainer aStrokePrimitives; |
590 | 0 | impAddPolygonStrokePrimitives( |
591 | 0 | aB2DPolyPolyVector, |
592 | 0 | aPolygonTransform, |
593 | 0 | rOutlineAttribute.getLineAttribute(), |
594 | 0 | rOutlineAttribute.getStrokeAttribute(), |
595 | 0 | aStrokePrimitives); |
596 | |
|
597 | 0 | if(!aStrokePrimitives.empty()) |
598 | 0 | { |
599 | 0 | if(rOutlineAttribute.getTransparence()) |
600 | 0 | { |
601 | | // create UnifiedTransparencePrimitive2D |
602 | 0 | aNewPrimitives.push_back( |
603 | 0 | new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D( |
604 | 0 | std::move(aStrokePrimitives), |
605 | 0 | static_cast<double>(rOutlineAttribute.getTransparence()) / 100.0) ); |
606 | 0 | } |
607 | 0 | else |
608 | 0 | { |
609 | | // add polygons to rDecomposition as polygonStrokePrimitives |
610 | 0 | aNewPrimitives.append( std::move(aStrokePrimitives) ); |
611 | 0 | } |
612 | 0 | } |
613 | 0 | } |
614 | 0 | } |
615 | 0 | } |
616 | |
|
617 | 0 | return aNewPrimitives; |
618 | 0 | } |
619 | | } // end of anonymous namespace |
620 | | |
621 | | |
622 | | // primitive decomposition |
623 | | |
624 | | void SdrTextObj::impDecomposePathTextPrimitive( |
625 | | drawinglayer::primitive2d::Primitive2DContainer& rTarget, |
626 | | const drawinglayer::primitive2d::SdrPathTextPrimitive2D& rSdrPathTextPrimitive, |
627 | | const drawinglayer::geometry::ViewInformation2D& aViewInformation) const |
628 | 0 | { |
629 | 0 | drawinglayer::primitive2d::Primitive2DContainer aRetvalA; |
630 | 0 | drawinglayer::primitive2d::Primitive2DContainer aRetvalB; |
631 | | |
632 | | // prepare outliner |
633 | 0 | SdrOutliner& rOutliner = ImpGetDrawOutliner(); |
634 | 0 | rOutliner.SetUpdateLayout(true); |
635 | 0 | rOutliner.Clear(); |
636 | 0 | rOutliner.SetPaperSize(Size(LONG_MAX,LONG_MAX)); |
637 | 0 | rOutliner.SetText(rSdrPathTextPrimitive.getOutlinerParaObject()); |
638 | | |
639 | | // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition |
640 | 0 | rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage())); |
641 | | |
642 | | // now break up to text portions |
643 | 0 | TextHierarchyBreakupPathTextPortions aBreakup; |
644 | 0 | rOutliner.StripPortions(aBreakup); |
645 | 0 | const ::std::vector< impPathTextPortion > rPathTextPortions(aBreakup.sortAndGetPathTextPortions()); |
646 | |
|
647 | 0 | if(!rPathTextPortions.empty()) |
648 | 0 | { |
649 | | // get FormText and polygon values |
650 | 0 | const drawinglayer::attribute::SdrFormTextAttribute& rFormTextAttribute = rSdrPathTextPrimitive.getSdrFormTextAttribute(); |
651 | 0 | const basegfx::B2DPolyPolygon& rPathPolyPolygon(rSdrPathTextPrimitive.getPathPolyPolygon()); |
652 | | |
653 | | // get loop count |
654 | 0 | sal_uInt32 nLoopCount(rPathPolyPolygon.count()); |
655 | |
|
656 | 0 | if(o3tl::make_unsigned(rOutliner.GetParagraphCount()) < nLoopCount) |
657 | 0 | { |
658 | 0 | nLoopCount = rOutliner.GetParagraphCount(); |
659 | 0 | } |
660 | |
|
661 | 0 | if(nLoopCount) |
662 | 0 | { |
663 | | // prepare common decomposition stuff |
664 | 0 | drawinglayer::primitive2d::Primitive2DContainer aRegularDecomposition; |
665 | 0 | drawinglayer::primitive2d::Primitive2DContainer aShadowDecomposition; |
666 | 0 | impPolygonParagraphHandler aPolygonParagraphHandler( |
667 | 0 | rFormTextAttribute, |
668 | 0 | aRegularDecomposition, |
669 | 0 | aShadowDecomposition); |
670 | 0 | sal_uInt32 a; |
671 | |
|
672 | 0 | for(a = 0; a < nLoopCount; a++) |
673 | 0 | { |
674 | | // filter text portions for this paragraph |
675 | 0 | ::std::vector< const impPathTextPortion* > aParagraphTextPortions; |
676 | |
|
677 | 0 | for(const auto & rCandidate : rPathTextPortions) |
678 | 0 | { |
679 | 0 | if(static_cast<sal_uInt32>(rCandidate.getParagraph()) == a) |
680 | 0 | { |
681 | 0 | aParagraphTextPortions.push_back(&rCandidate); |
682 | 0 | } |
683 | 0 | } |
684 | | |
685 | | // handle data pair polygon/ParagraphTextPortions |
686 | 0 | if(!aParagraphTextPortions.empty()) |
687 | 0 | { |
688 | 0 | aPolygonParagraphHandler.HandlePair(rPathPolyPolygon.getB2DPolygon(a), aParagraphTextPortions); |
689 | 0 | } |
690 | 0 | } |
691 | |
|
692 | 0 | const sal_uInt32 nShadowCount(aShadowDecomposition.size()); |
693 | 0 | const sal_uInt32 nRegularCount(aRegularDecomposition.size()); |
694 | |
|
695 | 0 | if(nShadowCount) |
696 | 0 | { |
697 | | // add shadow primitives to decomposition |
698 | | |
699 | | // if necessary, add shadow outlines |
700 | 0 | if(rFormTextAttribute.getFormTextOutline() |
701 | 0 | && !rFormTextAttribute.getShadowOutline().isDefault()) |
702 | 0 | { |
703 | 0 | aRetvalA = aShadowDecomposition; |
704 | 0 | const drawinglayer::primitive2d::Primitive2DContainer aOutlines( |
705 | 0 | impAddPathTextOutlines( |
706 | 0 | aShadowDecomposition, |
707 | 0 | rFormTextAttribute.getShadowOutline())); |
708 | |
|
709 | 0 | aRetvalA.append(aOutlines); |
710 | 0 | } |
711 | 0 | else |
712 | 0 | aRetvalA = std::move(aShadowDecomposition); |
713 | 0 | } |
714 | |
|
715 | 0 | if(nRegularCount) |
716 | 0 | { |
717 | | // add normal primitives to decomposition |
718 | | |
719 | | // if necessary, add outlines |
720 | 0 | if(rFormTextAttribute.getFormTextOutline() |
721 | 0 | && !rFormTextAttribute.getOutline().isDefault()) |
722 | 0 | { |
723 | 0 | aRetvalB = aRegularDecomposition; |
724 | 0 | const drawinglayer::primitive2d::Primitive2DContainer aOutlines( |
725 | 0 | impAddPathTextOutlines( |
726 | 0 | aRegularDecomposition, |
727 | 0 | rFormTextAttribute.getOutline())); |
728 | |
|
729 | 0 | aRetvalB.append(aOutlines); |
730 | 0 | } |
731 | 0 | else |
732 | 0 | aRetvalB = std::move(aRegularDecomposition); |
733 | 0 | } |
734 | 0 | } |
735 | 0 | } |
736 | | |
737 | | // clean up outliner |
738 | 0 | rOutliner.Clear(); |
739 | 0 | rOutliner.setVisualizedPage(nullptr); |
740 | | |
741 | | // concatenate all results |
742 | 0 | rTarget.append(std::move(aRetvalA)); |
743 | 0 | rTarget.append(std::move(aRetvalB)); |
744 | 0 | } |
745 | | |
746 | | |
747 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |