/src/libreoffice/cppcanvas/source/mtfrenderer/textaction.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 | | |
21 | | #include <comphelper/diagnose_ex.hxx> |
22 | | |
23 | | #include <com/sun/star/rendering/PathCapType.hpp> |
24 | | #include <com/sun/star/rendering/PathJoinType.hpp> |
25 | | #include <com/sun/star/rendering/XCanvas.hpp> |
26 | | #include <com/sun/star/rendering/XCanvasFont.hpp> |
27 | | |
28 | | #include <basegfx/matrix/b2dhommatrix.hxx> |
29 | | #include <basegfx/range/b2drectangle.hxx> |
30 | | #include <basegfx/vector/b2dsize.hxx> |
31 | | #include <basegfx/polygon/b2dpolypolygontools.hxx> |
32 | | #include <basegfx/polygon/b2dpolygontools.hxx> |
33 | | #include <basegfx/matrix/b2dhommatrixtools.hxx> |
34 | | |
35 | | #include <tools/gen.hxx> |
36 | | #include <utility> |
37 | | #include <vcl/canvastools.hxx> |
38 | | #include <vcl/virdev.hxx> |
39 | | |
40 | | #include <basegfx/utils/canvastools.hxx> |
41 | | #include <canvas/canvastools.hxx> |
42 | | #include <memory> |
43 | | #include <sal/log.hxx> |
44 | | |
45 | | #include "textaction.hxx" |
46 | | #include "textlineshelper.hxx" |
47 | | #include <outdevstate.hxx> |
48 | | #include "mtftools.hxx" |
49 | | |
50 | | |
51 | | using namespace ::com::sun::star; |
52 | | |
53 | | namespace cppcanvas::internal |
54 | | { |
55 | | namespace |
56 | | { |
57 | | void init( rendering::RenderState& o_rRenderState, |
58 | | const ::basegfx::B2DPoint& rStartPoint, |
59 | | const OutDevState& rState, |
60 | | const CanvasSharedPtr& rCanvas ) |
61 | 0 | { |
62 | 0 | cppcanvastools::initRenderState(o_rRenderState,rState); |
63 | | |
64 | | // #i36950# Offset clip back to origin (as it's also moved |
65 | | // by rStartPoint) |
66 | | // #i53964# Also take VCL font rotation into account, |
67 | | // since this, opposed to the FontMatrix rotation |
68 | | // elsewhere, _does_ get incorporated into the render |
69 | | // state transform. |
70 | 0 | cppcanvastools::modifyClip( o_rRenderState, |
71 | 0 | rState, |
72 | 0 | rCanvas, |
73 | 0 | rStartPoint, |
74 | 0 | nullptr, |
75 | 0 | &rState.fontRotation ); |
76 | |
|
77 | 0 | basegfx::B2DHomMatrix aLocalTransformation(basegfx::utils::createRotateB2DHomMatrix(rState.fontRotation)); |
78 | 0 | aLocalTransformation.translate( rStartPoint.getX(), |
79 | 0 | rStartPoint.getY() ); |
80 | 0 | ::canvastools::appendToRenderState( o_rRenderState, |
81 | 0 | aLocalTransformation ); |
82 | |
|
83 | 0 | o_rRenderState.DeviceColor = rState.textColor; |
84 | 0 | } |
85 | | |
86 | | void init( rendering::RenderState& o_rRenderState, |
87 | | const ::basegfx::B2DPoint& rStartPoint, |
88 | | const OutDevState& rState, |
89 | | const CanvasSharedPtr& rCanvas, |
90 | | const ::basegfx::B2DHomMatrix& rTextTransform ) |
91 | 0 | { |
92 | 0 | init( o_rRenderState, rStartPoint, rState, rCanvas ); |
93 | | |
94 | | // TODO(F2): Also inversely-transform clip with |
95 | | // rTextTransform (which is actually rather hard, as the |
96 | | // text transform is _prepended_ to the render state)! |
97 | | |
98 | | // prepend extra font transform to render state |
99 | | // (prepend it, because it's interpreted in the unit |
100 | | // rect coordinate space) |
101 | 0 | ::canvastools::prependToRenderState( o_rRenderState, |
102 | 0 | rTextTransform ); |
103 | 0 | } |
104 | | |
105 | | void init( rendering::RenderState& o_rRenderState, |
106 | | uno::Reference< rendering::XCanvasFont >& o_rFont, |
107 | | const ::basegfx::B2DPoint& rStartPoint, |
108 | | const OutDevState& rState, |
109 | | const CanvasSharedPtr& rCanvas ) |
110 | 0 | { |
111 | | // ensure that o_rFont is valid. It is possible that |
112 | | // text actions are generated without previously |
113 | | // setting a font. Then, just take a default font |
114 | 0 | if( !o_rFont.is() ) |
115 | 0 | { |
116 | | // Use completely default FontRequest |
117 | 0 | const rendering::FontRequest aFontRequest; |
118 | |
|
119 | 0 | geometry::Matrix2D aFontMatrix; |
120 | 0 | ::canvastools::setIdentityMatrix2D( aFontMatrix ); |
121 | |
|
122 | 0 | o_rFont = rCanvas->getUNOCanvas()->createFont( |
123 | 0 | aFontRequest, |
124 | 0 | uno::Sequence< beans::PropertyValue >(), |
125 | 0 | aFontMatrix ); |
126 | 0 | } |
127 | |
|
128 | 0 | init( o_rRenderState, |
129 | 0 | rStartPoint, |
130 | 0 | rState, |
131 | 0 | rCanvas ); |
132 | 0 | } |
133 | | |
134 | | void init( rendering::RenderState& o_rRenderState, |
135 | | uno::Reference< rendering::XCanvasFont >& o_rFont, |
136 | | const ::basegfx::B2DPoint& rStartPoint, |
137 | | const OutDevState& rState, |
138 | | const CanvasSharedPtr& rCanvas, |
139 | | const ::basegfx::B2DHomMatrix& rTextTransform ) |
140 | 0 | { |
141 | 0 | init( o_rRenderState, o_rFont, rStartPoint, rState, rCanvas ); |
142 | | |
143 | | // TODO(F2): Also inversely-transform clip with |
144 | | // rTextTransform (which is actually rather hard, as the |
145 | | // text transform is _prepended_ to the render state)! |
146 | | |
147 | | // prepend extra font transform to render state |
148 | | // (prepend it, because it's interpreted in the unit |
149 | | // rect coordinate space) |
150 | 0 | ::canvastools::prependToRenderState( o_rRenderState, |
151 | 0 | rTextTransform ); |
152 | 0 | } |
153 | | |
154 | | void initLayoutWidth(double& rLayoutWidth, const uno::Sequence<double>& rOffsets) |
155 | 0 | { |
156 | 0 | ENSURE_OR_THROW(rOffsets.hasElements(), |
157 | 0 | "::cppcanvas::internal::initLayoutWidth(): zero-length array" ); |
158 | 0 | rLayoutWidth = *(std::max_element(rOffsets.begin(), rOffsets.end())); |
159 | 0 | } |
160 | | |
161 | | uno::Sequence< double > setupDXArray( KernArraySpan rCharWidths, |
162 | | sal_Int32 nLen, |
163 | | const OutDevState& rState ) |
164 | 0 | { |
165 | | // convert character widths from logical units |
166 | 0 | uno::Sequence< double > aCharWidthSeq( nLen ); |
167 | 0 | double* pOutputWidths( aCharWidthSeq.getArray() ); |
168 | | |
169 | | // #143885# maintain (nearly) full precision of DX |
170 | | // array, by circumventing integer-based |
171 | | // OutDev-mapping |
172 | 0 | const double nScale( rState.mapModeTransform.get(0,0) ); |
173 | 0 | for( int i = 0; i < nLen; ++i ) |
174 | 0 | { |
175 | | // TODO(F2): use correct scale direction |
176 | 0 | *pOutputWidths++ = rCharWidths[i] * nScale; |
177 | 0 | } |
178 | |
|
179 | 0 | return aCharWidthSeq; |
180 | 0 | } |
181 | | |
182 | | uno::Sequence< double > setupDXArray( const OUString& rText, |
183 | | sal_Int32 nStartPos, |
184 | | sal_Int32 nLen, |
185 | | VirtualDevice const & rVDev, |
186 | | const OutDevState& rState ) |
187 | 0 | { |
188 | | // no external DX array given, create one from given |
189 | | // string |
190 | 0 | KernArray aCharWidths; |
191 | |
|
192 | 0 | rVDev.GetTextArray( rText, &aCharWidths, nStartPos, nLen ); |
193 | |
|
194 | 0 | return setupDXArray( aCharWidths, nLen, rState ); |
195 | 0 | } |
196 | | |
197 | | ::basegfx::B2DPoint adaptStartPoint( const ::basegfx::B2DPoint& rStartPoint, |
198 | | const OutDevState& rState, |
199 | | const uno::Sequence< double >& rOffsets ) |
200 | 0 | { |
201 | 0 | ::basegfx::B2DPoint aLocalPoint( rStartPoint ); |
202 | |
|
203 | 0 | if( rState.textAlignment ) |
204 | 0 | { |
205 | | // text origin is right, not left. Modify start point |
206 | | // accordingly, because XCanvas::drawTextLayout() |
207 | | // always aligns left! |
208 | |
|
209 | 0 | const double nOffset( rOffsets[ rOffsets.getLength()-1 ] ); |
210 | | |
211 | | // correct start point for rotated text: rotate around |
212 | | // former start point |
213 | 0 | aLocalPoint.setX( aLocalPoint.getX() + cos( rState.fontRotation )*nOffset ); |
214 | 0 | aLocalPoint.setY( aLocalPoint.getY() + sin( rState.fontRotation )*nOffset ); |
215 | 0 | } |
216 | |
|
217 | 0 | return aLocalPoint; |
218 | 0 | } |
219 | | |
220 | | /** Perform common setup for array text actions |
221 | | |
222 | | This method creates the XTextLayout object and |
223 | | initializes it, e.g. with the logical advancements. |
224 | | */ |
225 | | void initArrayAction( rendering::RenderState& o_rRenderState, |
226 | | uno::Reference< rendering::XTextLayout >& o_rTextLayout, |
227 | | const ::basegfx::B2DPoint& rStartPoint, |
228 | | const OUString& rText, |
229 | | sal_Int32 nStartPos, |
230 | | sal_Int32 nLen, |
231 | | const uno::Sequence< double >& rOffsets, |
232 | | const uno::Sequence< sal_Bool >& rKashidas, |
233 | | const CanvasSharedPtr& rCanvas, |
234 | | const OutDevState& rState, |
235 | | const ::basegfx::B2DHomMatrix* pTextTransform ) |
236 | 0 | { |
237 | 0 | ENSURE_OR_THROW( rOffsets.hasElements(), |
238 | 0 | "::cppcanvas::internal::initArrayAction(): zero-length DX array" ); |
239 | |
|
240 | 0 | const ::basegfx::B2DPoint aLocalStartPoint( |
241 | 0 | adaptStartPoint( rStartPoint, rState, rOffsets ) ); |
242 | |
|
243 | 0 | uno::Reference< rendering::XCanvasFont > xFont( rState.xFont ); |
244 | |
|
245 | 0 | if( pTextTransform ) |
246 | 0 | init( o_rRenderState, xFont, aLocalStartPoint, rState, rCanvas, *pTextTransform ); |
247 | 0 | else |
248 | 0 | init( o_rRenderState, xFont, aLocalStartPoint, rState, rCanvas ); |
249 | |
|
250 | 0 | o_rTextLayout = xFont->createTextLayout( |
251 | 0 | rendering::StringContext( rText, nStartPos, nLen ), |
252 | 0 | rState.textDirection, |
253 | 0 | 0 ); |
254 | |
|
255 | 0 | ENSURE_OR_THROW( o_rTextLayout.is(), |
256 | 0 | "::cppcanvas::internal::initArrayAction(): Invalid font" ); |
257 | |
|
258 | 0 | o_rTextLayout->applyLogicalAdvancements( rOffsets ); |
259 | 0 | o_rTextLayout->applyKashidaPositions( rKashidas ); |
260 | |
|
261 | 0 | } |
262 | | |
263 | | double getLineWidth( ::VirtualDevice const & rVDev, |
264 | | const OutDevState& rState, |
265 | | const rendering::StringContext& rStringContext ) |
266 | 0 | { |
267 | | // TODO(F2): use correct scale direction |
268 | 0 | const ::basegfx::B2DSize aSize( rVDev.GetTextWidth( rStringContext.Text, |
269 | 0 | static_cast<sal_uInt16>(rStringContext.StartPosition), |
270 | 0 | static_cast<sal_uInt16>(rStringContext.Length) ), |
271 | 0 | 0 ); |
272 | |
|
273 | 0 | return (rState.mapModeTransform * aSize).getWidth(); |
274 | 0 | } |
275 | | |
276 | | uno::Sequence< double > |
277 | | calcSubsetOffsets( rendering::RenderState& io_rRenderState, |
278 | | double& o_rMinPos, |
279 | | double& o_rMaxPos, |
280 | | const uno::Reference< rendering::XTextLayout >& rOrigTextLayout, |
281 | | double nLayoutWidth, |
282 | | const ::cppcanvas::internal::Action::Subset& rSubset ) |
283 | 0 | { |
284 | 0 | ENSURE_OR_THROW( rSubset.mnSubsetEnd > rSubset.mnSubsetBegin, |
285 | 0 | "::cppcanvas::internal::calcSubsetOffsets(): invalid subset range range" ); |
286 | |
|
287 | 0 | uno::Sequence< double > aOrigOffsets( rOrigTextLayout->queryLogicalAdvancements() ); |
288 | 0 | const double* pOffsets( aOrigOffsets.getConstArray() ); |
289 | |
|
290 | 0 | ENSURE_OR_THROW( aOrigOffsets.getLength() >= rSubset.mnSubsetEnd, |
291 | 0 | "::cppcanvas::internal::calcSubsetOffsets(): invalid subset range range" ); |
292 | | |
293 | | |
294 | | // determine leftmost position in given subset range - |
295 | | // as the DX array contains the output positions |
296 | | // starting with the second character (the first is |
297 | | // assumed to have output position 0), correct begin |
298 | | // iterator. |
299 | 0 | const double nMinPos( rSubset.mnSubsetBegin <= 0 ? 0 : |
300 | 0 | *(std::min_element( pOffsets+rSubset.mnSubsetBegin-1, |
301 | 0 | pOffsets+rSubset.mnSubsetEnd )) ); |
302 | | |
303 | | // determine rightmost position in given subset range |
304 | | // - as the DX array contains the output positions |
305 | | // starting with the second character (the first is |
306 | | // assumed to have output position 0), correct begin |
307 | | // iterator. |
308 | 0 | const double nMaxPos( |
309 | 0 | *(std::max_element( pOffsets + (rSubset.mnSubsetBegin <= 0 ? |
310 | 0 | 0 : rSubset.mnSubsetBegin-1), |
311 | 0 | pOffsets + rSubset.mnSubsetEnd )) ); |
312 | | |
313 | | // Logical advancements always increase in logical text order. |
314 | | // For RTL text, nMaxPos is the distance from the right edge to |
315 | | // the leftmost position in the subset, so we have to convert |
316 | | // it to the offset from the origin (i.e. left edge ). |
317 | | // LTR: |---- min --->|---- max --->| | |
318 | | // RTL: | |<--- max ----|<--- min ---| |
319 | | // |<- nOffset ->| | |
320 | 0 | const double nOffset = rOrigTextLayout->getMainTextDirection() |
321 | 0 | ? nLayoutWidth - nMaxPos : nMinPos; |
322 | | |
323 | | |
324 | | // adapt render state, to move text output to given offset |
325 | | |
326 | | |
327 | | // TODO(F1): Strictly speaking, we also have to adapt |
328 | | // the clip here, which normally should _not_ move |
329 | | // with the output offset. Neglected for now, as it |
330 | | // does not matter for drawing layer output |
331 | |
|
332 | 0 | if (nOffset > 0.0) |
333 | 0 | { |
334 | 0 | ::basegfx::B2DHomMatrix aTranslation; |
335 | 0 | if( rOrigTextLayout->getFont()->getFontRequest().FontDescription.IsVertical == css::util::TriState_YES ) |
336 | 0 | { |
337 | | // vertical text -> offset in y direction |
338 | 0 | aTranslation.translate(0.0, nOffset); |
339 | 0 | } |
340 | 0 | else |
341 | 0 | { |
342 | | // horizontal text -> offset in x direction |
343 | 0 | aTranslation.translate(nOffset, 0.0); |
344 | 0 | } |
345 | |
|
346 | 0 | ::canvastools::appendToRenderState( io_rRenderState, |
347 | 0 | aTranslation ); |
348 | 0 | } |
349 | | |
350 | | |
351 | | // reduce DX array to given substring |
352 | | |
353 | |
|
354 | 0 | const sal_Int32 nNewElements( rSubset.mnSubsetEnd - rSubset.mnSubsetBegin ); |
355 | 0 | uno::Sequence< double > aAdaptedOffsets( nNewElements ); |
356 | 0 | double* pAdaptedOffsets( aAdaptedOffsets.getArray() ); |
357 | | |
358 | | // move to new output position (subtract nMinPos, |
359 | | // which is the new '0' position), copy only the range |
360 | | // as given by rSubset. |
361 | 0 | std::transform( pOffsets + rSubset.mnSubsetBegin, |
362 | 0 | pOffsets + rSubset.mnSubsetEnd, |
363 | 0 | pAdaptedOffsets, |
364 | 0 | [nMinPos](double aPos) { return aPos - nMinPos; } ); |
365 | |
|
366 | 0 | o_rMinPos = nMinPos; |
367 | 0 | o_rMaxPos = nMaxPos; |
368 | |
|
369 | 0 | return aAdaptedOffsets; |
370 | 0 | } |
371 | | |
372 | | uno::Reference< rendering::XTextLayout > |
373 | | createSubsetLayout( const rendering::StringContext& rOrigContext, |
374 | | const ::cppcanvas::internal::Action::Subset& rSubset, |
375 | | const uno::Reference< rendering::XTextLayout >& rOrigTextLayout ) |
376 | 0 | { |
377 | | // create temporary new text layout with subset string |
378 | | |
379 | |
|
380 | 0 | const sal_Int32 nNewStartPos( rOrigContext.StartPosition + std::min( |
381 | 0 | rSubset.mnSubsetBegin, rOrigContext.Length-1 ) ); |
382 | 0 | const sal_Int32 nNewLength( std::max( |
383 | 0 | std::min( |
384 | 0 | rSubset.mnSubsetEnd - rSubset.mnSubsetBegin, |
385 | 0 | rOrigContext.Length ), |
386 | 0 | sal_Int32( 0 ) ) ); |
387 | |
|
388 | 0 | const rendering::StringContext aContext( rOrigContext.Text, |
389 | 0 | nNewStartPos, |
390 | 0 | nNewLength ); |
391 | |
|
392 | 0 | uno::Reference< rendering::XTextLayout > xTextLayout( |
393 | 0 | rOrigTextLayout->getFont()->createTextLayout( aContext, |
394 | 0 | rOrigTextLayout->getMainTextDirection(), |
395 | 0 | 0 ), |
396 | 0 | uno::UNO_SET_THROW ); |
397 | |
|
398 | 0 | return xTextLayout; |
399 | 0 | } |
400 | | |
401 | | /** Setup subset text layout |
402 | | |
403 | | @param io_rTextLayout |
404 | | Must contain original (full set) text layout on input, |
405 | | will contain subsetted text layout (or empty |
406 | | reference, for empty subsets) on output. |
407 | | |
408 | | @param io_rRenderState |
409 | | Must contain original render state on input, will |
410 | | contain shifted render state concatenated with |
411 | | rTransformation on output. |
412 | | |
413 | | @param rTransformation |
414 | | Additional transformation, to be prepended to render |
415 | | state |
416 | | |
417 | | @param rSubset |
418 | | Subset to prepare |
419 | | */ |
420 | | void createSubsetLayout( uno::Reference< rendering::XTextLayout >& io_rTextLayout, |
421 | | double nLayoutWidth, |
422 | | rendering::RenderState& io_rRenderState, |
423 | | double& o_rMinPos, |
424 | | double& o_rMaxPos, |
425 | | const ::basegfx::B2DHomMatrix& rTransformation, |
426 | | const Action::Subset& rSubset ) |
427 | 0 | { |
428 | 0 | ::canvastools::prependToRenderState(io_rRenderState, rTransformation); |
429 | |
|
430 | 0 | if( rSubset.mnSubsetBegin == rSubset.mnSubsetEnd ) |
431 | 0 | { |
432 | | // empty range, empty layout |
433 | 0 | io_rTextLayout.clear(); |
434 | |
|
435 | 0 | return; |
436 | 0 | } |
437 | | |
438 | 0 | ENSURE_OR_THROW( io_rTextLayout.is(), |
439 | 0 | "createSubsetLayout(): Invalid input layout" ); |
440 | |
|
441 | 0 | const rendering::StringContext aOrigContext( io_rTextLayout->getText() ); |
442 | |
|
443 | 0 | if( rSubset.mnSubsetBegin == 0 && |
444 | 0 | rSubset.mnSubsetEnd == aOrigContext.Length ) |
445 | 0 | { |
446 | | // full range, no need for subsetting |
447 | 0 | return; |
448 | 0 | } |
449 | | |
450 | 0 | uno::Reference< rendering::XTextLayout > xTextLayout( |
451 | 0 | createSubsetLayout( aOrigContext, rSubset, io_rTextLayout ) ); |
452 | |
|
453 | 0 | if( xTextLayout.is() ) |
454 | 0 | { |
455 | 0 | xTextLayout->applyLogicalAdvancements( |
456 | 0 | calcSubsetOffsets( io_rRenderState, |
457 | 0 | o_rMinPos, |
458 | 0 | o_rMaxPos, |
459 | 0 | io_rTextLayout, |
460 | 0 | nLayoutWidth, |
461 | 0 | rSubset ) ); |
462 | 0 | uno::Sequence< sal_Bool > aOrigKashidaPositions(io_rTextLayout->queryKashidaPositions()); |
463 | 0 | uno::Sequence< sal_Bool > aKashidaPositions(aOrigKashidaPositions.getArray() + rSubset.mnSubsetBegin, |
464 | 0 | rSubset.mnSubsetEnd - rSubset.mnSubsetBegin); |
465 | 0 | xTextLayout->applyKashidaPositions(aKashidaPositions); |
466 | 0 | } |
467 | |
|
468 | 0 | io_rTextLayout = std::move(xTextLayout); |
469 | 0 | } |
470 | | |
471 | | |
472 | | /** Interface for renderEffectText functor below. |
473 | | |
474 | | This is interface is used from the renderEffectText() |
475 | | method below, to call the client implementation. |
476 | | */ |
477 | | class TextRenderer |
478 | | { |
479 | | public: |
480 | 0 | virtual ~TextRenderer() {} |
481 | | |
482 | | /// Render text with given RenderState |
483 | | virtual bool operator()( const rendering::RenderState& rRenderState, const ::Color& rTextFillColor, bool bNormalText ) const = 0; |
484 | | }; |
485 | | |
486 | | /** Render effect text. |
487 | | |
488 | | @param rRenderer |
489 | | Functor object, will be called to render the actual |
490 | | part of the text effect (the text itself and the means |
491 | | to render it are unknown to this method) |
492 | | */ |
493 | | bool renderEffectText( const TextRenderer& rRenderer, |
494 | | const rendering::RenderState& rRenderState, |
495 | | const uno::Reference< rendering::XCanvas >& xCanvas, |
496 | | const ::Color& rShadowColor, |
497 | | const ::basegfx::B2DSize& rShadowOffset, |
498 | | const ::Color& rReliefColor, |
499 | | const ::basegfx::B2DSize& rReliefOffset, |
500 | | const ::Color& rTextFillColor ) |
501 | 0 | { |
502 | 0 | ::Color aEmptyColor( COL_AUTO ); |
503 | 0 | uno::Reference<rendering::XColorSpace> xColorSpace( |
504 | 0 | xCanvas->getDevice()->getDeviceColorSpace() ); |
505 | | |
506 | | // draw shadow text, if enabled |
507 | 0 | if( rShadowColor != aEmptyColor ) |
508 | 0 | { |
509 | 0 | rendering::RenderState aShadowState( rRenderState ); |
510 | 0 | ::basegfx::B2DHomMatrix aTranslate; |
511 | |
|
512 | 0 | aTranslate.translate(rShadowOffset.getWidth(), |
513 | 0 | rShadowOffset.getHeight()); |
514 | |
|
515 | 0 | ::canvastools::appendToRenderState(aShadowState, aTranslate); |
516 | |
|
517 | 0 | aShadowState.DeviceColor = |
518 | 0 | vcl::unotools::colorToDoubleSequence( rShadowColor, |
519 | 0 | xColorSpace ); |
520 | |
|
521 | 0 | rRenderer( aShadowState, rTextFillColor, false ); |
522 | 0 | } |
523 | | |
524 | | // draw relief text, if enabled |
525 | 0 | if( rReliefColor != aEmptyColor ) |
526 | 0 | { |
527 | 0 | rendering::RenderState aReliefState( rRenderState ); |
528 | 0 | ::basegfx::B2DHomMatrix aTranslate; |
529 | |
|
530 | 0 | aTranslate.translate(rReliefOffset.getWidth(), |
531 | 0 | rReliefOffset.getHeight()); |
532 | |
|
533 | 0 | ::canvastools::appendToRenderState(aReliefState, aTranslate); |
534 | |
|
535 | 0 | aReliefState.DeviceColor = |
536 | 0 | vcl::unotools::colorToDoubleSequence( rReliefColor, |
537 | 0 | xColorSpace ); |
538 | |
|
539 | 0 | rRenderer( aReliefState, rTextFillColor, false ); |
540 | 0 | } |
541 | | |
542 | | // draw normal text |
543 | 0 | rRenderer( rRenderState, rTextFillColor, true ); |
544 | |
|
545 | 0 | return true; |
546 | 0 | } |
547 | | |
548 | | |
549 | | ::basegfx::B2DRange calcEffectTextBounds( const ::basegfx::B2DRange& rTextBounds, |
550 | | const ::basegfx::B2DRange& rLineBounds, |
551 | | const ::basegfx::B2DSize& rReliefOffset, |
552 | | const ::basegfx::B2DSize& rShadowOffset, |
553 | | const rendering::RenderState& rRenderState, |
554 | | const rendering::ViewState& rViewState ) |
555 | 0 | { |
556 | 0 | ::basegfx::B2DRange aBounds( rTextBounds ); |
557 | | |
558 | | // add extends of text lines |
559 | 0 | aBounds.expand( rLineBounds ); |
560 | | |
561 | | // TODO(Q3): Provide this functionality at the B2DRange |
562 | 0 | ::basegfx::B2DRange aTotalBounds( aBounds ); |
563 | 0 | aTotalBounds.expand( |
564 | 0 | ::basegfx::B2DRange( aBounds.getMinX() + rReliefOffset.getWidth(), |
565 | 0 | aBounds.getMinY() + rReliefOffset.getHeight(), |
566 | 0 | aBounds.getMaxX() + rReliefOffset.getWidth(), |
567 | 0 | aBounds.getMaxY() + rReliefOffset.getHeight() ) ); |
568 | 0 | aTotalBounds.expand( |
569 | 0 | ::basegfx::B2DRange( aBounds.getMinX() + rShadowOffset.getWidth(), |
570 | 0 | aBounds.getMinY() + rShadowOffset.getHeight(), |
571 | 0 | aBounds.getMaxX() + rShadowOffset.getWidth(), |
572 | 0 | aBounds.getMaxY() + rShadowOffset.getHeight() ) ); |
573 | |
|
574 | 0 | return cppcanvastools::calcDevicePixelBounds( aTotalBounds, |
575 | 0 | rViewState, |
576 | 0 | rRenderState ); |
577 | 0 | } |
578 | | |
579 | | void initEffectLinePolyPolygon( ::basegfx::B2DSize& o_rOverallSize, |
580 | | uno::Reference< rendering::XPolyPolygon2D >& o_rTextLines, |
581 | | const CanvasSharedPtr& rCanvas, |
582 | | double nLineWidth, |
583 | | const cppcanvastools::TextLineInfo& rLineInfo ) |
584 | 0 | { |
585 | 0 | const ::basegfx::B2DPolyPolygon aPoly( |
586 | 0 | cppcanvastools::createTextLinesPolyPolygon( 0.0, nLineWidth, |
587 | 0 | rLineInfo ) ); |
588 | 0 | auto aRange = aPoly.getB2DRange().getRange(); |
589 | 0 | o_rOverallSize = basegfx::B2DSize(aRange.getX(), aRange.getY()); |
590 | |
|
591 | 0 | o_rTextLines = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( |
592 | 0 | rCanvas->getUNOCanvas()->getDevice(), |
593 | 0 | aPoly ); |
594 | 0 | } |
595 | | |
596 | | |
597 | | class TextAction : public Action |
598 | | { |
599 | | public: |
600 | | TextAction( const ::basegfx::B2DPoint& rStartPoint, |
601 | | const OUString& rString, |
602 | | sal_Int32 nStartPos, |
603 | | sal_Int32 nLen, |
604 | | const CanvasSharedPtr& rCanvas, |
605 | | const OutDevState& rState ); |
606 | | |
607 | | TextAction( const ::basegfx::B2DPoint& rStartPoint, |
608 | | const OUString& rString, |
609 | | sal_Int32 nStartPos, |
610 | | sal_Int32 nLen, |
611 | | const CanvasSharedPtr& rCanvas, |
612 | | const OutDevState& rState, |
613 | | const ::basegfx::B2DHomMatrix& rTextTransform ); |
614 | | |
615 | | TextAction(const TextAction&) = delete; |
616 | | const TextAction& operator=(const TextAction&) = delete; |
617 | | |
618 | | virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation ) const override; |
619 | | virtual bool renderSubset( const ::basegfx::B2DHomMatrix& rTransformation, |
620 | | const Subset& rSubset ) const override; |
621 | | |
622 | | virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const override; |
623 | | virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation, |
624 | | const Subset& rSubset ) const override; |
625 | | |
626 | | virtual sal_Int32 getActionCount() const override; |
627 | | |
628 | | private: |
629 | | // TODO(P2): This is potentially a real mass object |
630 | | // (every character might be a separate TextAction), |
631 | | // thus, make it as lightweight as possible. For |
632 | | // example, share common RenderState among several |
633 | | // TextActions, maybe using maOffsets for the |
634 | | // translation. |
635 | | |
636 | | uno::Reference< rendering::XCanvasFont > mxFont; |
637 | | const rendering::StringContext maStringContext; |
638 | | const CanvasSharedPtr mpCanvas; |
639 | | rendering::RenderState maState; |
640 | | const sal_Int8 maTextDirection; |
641 | | }; |
642 | | |
643 | | TextAction::TextAction( const ::basegfx::B2DPoint& rStartPoint, |
644 | | const OUString& rString, |
645 | | sal_Int32 nStartPos, |
646 | | sal_Int32 nLen, |
647 | | const CanvasSharedPtr& rCanvas, |
648 | | const OutDevState& rState ) : |
649 | 0 | mxFont( rState.xFont ), |
650 | 0 | maStringContext( rString, nStartPos, nLen ), |
651 | 0 | mpCanvas( rCanvas ), |
652 | 0 | maTextDirection( rState.textDirection ) |
653 | 0 | { |
654 | 0 | init( maState, mxFont, |
655 | 0 | rStartPoint, |
656 | 0 | rState, rCanvas ); |
657 | |
|
658 | 0 | ENSURE_OR_THROW( mxFont.is(), |
659 | 0 | "::cppcanvas::internal::TextAction(): Invalid font" ); |
660 | 0 | } |
661 | | |
662 | | TextAction::TextAction( const ::basegfx::B2DPoint& rStartPoint, |
663 | | const OUString& rString, |
664 | | sal_Int32 nStartPos, |
665 | | sal_Int32 nLen, |
666 | | const CanvasSharedPtr& rCanvas, |
667 | | const OutDevState& rState, |
668 | | const ::basegfx::B2DHomMatrix& rTextTransform ) : |
669 | 0 | mxFont( rState.xFont ), |
670 | 0 | maStringContext( rString, nStartPos, nLen ), |
671 | 0 | mpCanvas( rCanvas ), |
672 | 0 | maTextDirection( rState.textDirection ) |
673 | 0 | { |
674 | 0 | init( maState, mxFont, |
675 | 0 | rStartPoint, |
676 | 0 | rState, rCanvas, rTextTransform ); |
677 | |
|
678 | 0 | ENSURE_OR_THROW( mxFont.is(), |
679 | 0 | "::cppcanvas::internal::TextAction(): Invalid font" ); |
680 | 0 | } |
681 | | |
682 | | bool TextAction::render( const ::basegfx::B2DHomMatrix& rTransformation ) const |
683 | 0 | { |
684 | 0 | SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TextAction::render()" ); |
685 | 0 | SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TextAction: 0x" << std::hex << this ); |
686 | | |
687 | 0 | rendering::RenderState aLocalState( maState ); |
688 | 0 | ::canvastools::prependToRenderState(aLocalState, rTransformation); |
689 | |
|
690 | 0 | mpCanvas->getUNOCanvas()->drawText( maStringContext, mxFont, |
691 | 0 | mpCanvas->getViewState(), aLocalState, maTextDirection ); |
692 | |
|
693 | 0 | return true; |
694 | 0 | } |
695 | | |
696 | | bool TextAction::renderSubset( const ::basegfx::B2DHomMatrix& rTransformation, |
697 | | const Subset& /*rSubset*/ ) const |
698 | 0 | { |
699 | 0 | SAL_WARN( "cppcanvas.emf", "TextAction::renderSubset(): Subset not supported by this object" ); |
700 | | |
701 | | // TODO(P1): Retrieve necessary font metric info for |
702 | | // TextAction from XCanvas. Currently, the |
703 | | // TextActionFactory does not generate this object for |
704 | | // _subsettable_ text |
705 | 0 | return render( rTransformation ); |
706 | 0 | } |
707 | | |
708 | | ::basegfx::B2DRange TextAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const |
709 | 0 | { |
710 | | // create XTextLayout, to have the |
711 | | // XTextLayout::queryTextBounds() method available |
712 | 0 | uno::Reference< rendering::XTextLayout > xTextLayout( |
713 | 0 | mxFont->createTextLayout( |
714 | 0 | maStringContext, |
715 | 0 | maTextDirection, |
716 | 0 | 0 ) ); |
717 | |
|
718 | 0 | rendering::RenderState aLocalState( maState ); |
719 | 0 | ::canvastools::prependToRenderState(aLocalState, rTransformation); |
720 | |
|
721 | 0 | return cppcanvastools::calcDevicePixelBounds( ::basegfx::unotools::b2DRectangleFromRealRectangle2D( |
722 | 0 | xTextLayout->queryTextBounds() ), |
723 | 0 | mpCanvas->getViewState(), |
724 | 0 | aLocalState ); |
725 | 0 | } |
726 | | |
727 | | ::basegfx::B2DRange TextAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation, |
728 | | const Subset& /*rSubset*/ ) const |
729 | 0 | { |
730 | 0 | SAL_WARN( "cppcanvas.emf", "TextAction::getBounds(): Subset not supported by this object" ); |
731 | | |
732 | | // TODO(P1): Retrieve necessary font metric info for |
733 | | // TextAction from XCanvas. Currently, the |
734 | | // TextActionFactory does not generate this object for |
735 | | // _subsettable_ text |
736 | 0 | return getBounds( rTransformation ); |
737 | 0 | } |
738 | | |
739 | | sal_Int32 TextAction::getActionCount() const |
740 | 0 | { |
741 | | // TODO(P1): Retrieve necessary font metric info for |
742 | | // TextAction from XCanvas. Currently, the |
743 | | // TextActionFactory does not generate this object for |
744 | | // _subsettable_ text |
745 | 0 | return 1; |
746 | 0 | } |
747 | | |
748 | | |
749 | | class EffectTextAction : |
750 | | public Action, |
751 | | public TextRenderer |
752 | | { |
753 | | public: |
754 | | EffectTextAction( const ::basegfx::B2DPoint& rStartPoint, |
755 | | const ::basegfx::B2DSize& rReliefOffset, |
756 | | const ::Color& rReliefColor, |
757 | | const ::basegfx::B2DSize& rShadowOffset, |
758 | | const ::Color& rShadowColor, |
759 | | const ::Color& rTextFillColor, |
760 | | const OUString& rText, |
761 | | sal_Int32 nStartPos, |
762 | | sal_Int32 nLen, |
763 | | VirtualDevice const & rVDev, |
764 | | const CanvasSharedPtr& rCanvas, |
765 | | const OutDevState& rState ); |
766 | | |
767 | | EffectTextAction( const ::basegfx::B2DPoint& rStartPoint, |
768 | | const ::basegfx::B2DSize& rReliefOffset, |
769 | | const ::Color& rReliefColor, |
770 | | const ::basegfx::B2DSize& rShadowOffset, |
771 | | const ::Color& rShadowColor, |
772 | | const ::Color& rTextFillColor, |
773 | | const OUString& rText, |
774 | | sal_Int32 nStartPos, |
775 | | sal_Int32 nLen, |
776 | | VirtualDevice const & rVDev, |
777 | | const CanvasSharedPtr& rCanvas, |
778 | | const OutDevState& rState, |
779 | | const ::basegfx::B2DHomMatrix& rTextTransform ); |
780 | | |
781 | | EffectTextAction(const EffectTextAction&) = delete; |
782 | | const EffectTextAction& operator=(const EffectTextAction&) = delete; |
783 | | |
784 | | virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation ) const override; |
785 | | virtual bool renderSubset( const ::basegfx::B2DHomMatrix& rTransformation, |
786 | | const Subset& rSubset ) const override; |
787 | | |
788 | | virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const override; |
789 | | virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation, |
790 | | const Subset& rSubset ) const override; |
791 | | |
792 | | virtual sal_Int32 getActionCount() const override; |
793 | | |
794 | | private: |
795 | | /// Interface TextRenderer |
796 | | virtual bool operator()( const rendering::RenderState& rRenderState, const ::Color& rTextFillColor, bool bNormalText ) const override; |
797 | | |
798 | | geometry::RealRectangle2D queryTextBounds() const; |
799 | | css::uno::Reference<css::rendering::XPolyPolygon2D> queryTextBounds(const uno::Reference<rendering::XCanvas>& rCanvas) const; |
800 | | |
801 | | // TODO(P2): This is potentially a real mass object |
802 | | // (every character might be a separate TextAction), |
803 | | // thus, make it as lightweight as possible. For |
804 | | // example, share common RenderState among several |
805 | | // TextActions, maybe using maOffsets for the |
806 | | // translation. |
807 | | |
808 | | uno::Reference< rendering::XCanvasFont > mxFont; |
809 | | const rendering::StringContext maStringContext; |
810 | | const CanvasSharedPtr mpCanvas; |
811 | | rendering::RenderState maState; |
812 | | const cppcanvastools::TextLineInfo maTextLineInfo; |
813 | | ::basegfx::B2DSize maLinesOverallSize; |
814 | | uno::Reference< rendering::XPolyPolygon2D > mxTextLines; |
815 | | const ::basegfx::B2DSize maReliefOffset; |
816 | | const ::Color maReliefColor; |
817 | | const ::basegfx::B2DSize maShadowOffset; |
818 | | const ::Color maShadowColor; |
819 | | const ::Color maTextFillColor; |
820 | | const sal_Int8 maTextDirection; |
821 | | }; |
822 | | |
823 | | EffectTextAction::EffectTextAction( const ::basegfx::B2DPoint& rStartPoint, |
824 | | const ::basegfx::B2DSize& rReliefOffset, |
825 | | const ::Color& rReliefColor, |
826 | | const ::basegfx::B2DSize& rShadowOffset, |
827 | | const ::Color& rShadowColor, |
828 | | const ::Color& rTextFillColor, |
829 | | const OUString& rText, |
830 | | sal_Int32 nStartPos, |
831 | | sal_Int32 nLen, |
832 | | VirtualDevice const & rVDev, |
833 | | const CanvasSharedPtr& rCanvas, |
834 | | const OutDevState& rState ) : |
835 | 0 | mxFont( rState.xFont ), |
836 | 0 | maStringContext( rText, nStartPos, nLen ), |
837 | 0 | mpCanvas( rCanvas ), |
838 | 0 | maTextLineInfo( cppcanvastools::createTextLineInfo( rVDev, rState ) ), |
839 | 0 | maReliefOffset( rReliefOffset ), |
840 | 0 | maReliefColor( rReliefColor ), |
841 | 0 | maShadowOffset( rShadowOffset ), |
842 | 0 | maShadowColor( rShadowColor ), |
843 | 0 | maTextFillColor( rTextFillColor ), |
844 | 0 | maTextDirection( rState.textDirection ) |
845 | 0 | { |
846 | 0 | const double nLineWidth(getLineWidth( rVDev, rState, maStringContext )); |
847 | 0 | initEffectLinePolyPolygon( maLinesOverallSize, |
848 | 0 | mxTextLines, |
849 | 0 | rCanvas, |
850 | 0 | nLineWidth, |
851 | 0 | maTextLineInfo ); |
852 | |
|
853 | 0 | init( maState, mxFont, |
854 | 0 | rStartPoint, |
855 | 0 | rState, rCanvas ); |
856 | |
|
857 | 0 | ENSURE_OR_THROW( mxFont.is() && mxTextLines.is(), |
858 | 0 | "::cppcanvas::internal::EffectTextAction(): Invalid font or lines" ); |
859 | 0 | } |
860 | | |
861 | | EffectTextAction::EffectTextAction( const ::basegfx::B2DPoint& rStartPoint, |
862 | | const ::basegfx::B2DSize& rReliefOffset, |
863 | | const ::Color& rReliefColor, |
864 | | const ::basegfx::B2DSize& rShadowOffset, |
865 | | const ::Color& rShadowColor, |
866 | | const ::Color& rTextFillColor, |
867 | | const OUString& rText, |
868 | | sal_Int32 nStartPos, |
869 | | sal_Int32 nLen, |
870 | | VirtualDevice const & rVDev, |
871 | | const CanvasSharedPtr& rCanvas, |
872 | | const OutDevState& rState, |
873 | | const ::basegfx::B2DHomMatrix& rTextTransform ) : |
874 | 0 | mxFont( rState.xFont ), |
875 | 0 | maStringContext( rText, nStartPos, nLen ), |
876 | 0 | mpCanvas( rCanvas ), |
877 | 0 | maTextLineInfo( cppcanvastools::createTextLineInfo( rVDev, rState ) ), |
878 | 0 | maReliefOffset( rReliefOffset ), |
879 | 0 | maReliefColor( rReliefColor ), |
880 | 0 | maShadowOffset( rShadowOffset ), |
881 | 0 | maShadowColor( rShadowColor ), |
882 | 0 | maTextFillColor( rTextFillColor ), |
883 | 0 | maTextDirection( rState.textDirection ) |
884 | 0 | { |
885 | 0 | const double nLineWidth( getLineWidth( rVDev, rState, maStringContext ) ); |
886 | 0 | initEffectLinePolyPolygon( maLinesOverallSize, |
887 | 0 | mxTextLines, |
888 | 0 | rCanvas, |
889 | 0 | nLineWidth, |
890 | 0 | maTextLineInfo ); |
891 | |
|
892 | 0 | init( maState, mxFont, |
893 | 0 | rStartPoint, |
894 | 0 | rState, rCanvas, rTextTransform ); |
895 | |
|
896 | 0 | ENSURE_OR_THROW( mxFont.is() && mxTextLines.is(), |
897 | 0 | "::cppcanvas::internal::EffectTextAction(): Invalid font or lines" ); |
898 | 0 | } |
899 | | |
900 | | bool EffectTextAction::operator()( const rendering::RenderState& rRenderState, const ::Color& rTextFillColor, bool /*bNormalText*/ ) const |
901 | 0 | { |
902 | 0 | const rendering::ViewState aViewState( mpCanvas->getViewState() ); |
903 | 0 | const uno::Reference< rendering::XCanvas > aCanvas( mpCanvas->getUNOCanvas() ); |
904 | | |
905 | | //rhbz#1589029 non-transparent text fill background support |
906 | 0 | if (rTextFillColor != COL_AUTO) |
907 | 0 | { |
908 | 0 | rendering::RenderState aLocalState( rRenderState ); |
909 | 0 | aLocalState.DeviceColor = vcl::unotools::colorToDoubleSequence( |
910 | 0 | rTextFillColor, aCanvas->getDevice()->getDeviceColorSpace()); |
911 | 0 | auto xTextBounds = queryTextBounds(aCanvas); |
912 | | // background of text |
913 | 0 | aCanvas->fillPolyPolygon(xTextBounds, aViewState, aLocalState); |
914 | 0 | } |
915 | | |
916 | | // under/over lines |
917 | 0 | aCanvas->fillPolyPolygon( mxTextLines, |
918 | 0 | aViewState, |
919 | 0 | rRenderState ); |
920 | |
|
921 | 0 | aCanvas->drawText( maStringContext, mxFont, |
922 | 0 | aViewState, |
923 | 0 | rRenderState, |
924 | 0 | maTextDirection ); |
925 | |
|
926 | 0 | return true; |
927 | 0 | } |
928 | | |
929 | | bool EffectTextAction::render( const ::basegfx::B2DHomMatrix& rTransformation ) const |
930 | 0 | { |
931 | 0 | SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::EffectTextAction::render()" ); |
932 | 0 | SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::EffectTextAction: 0x" << std::hex << this ); |
933 | | |
934 | 0 | rendering::RenderState aLocalState( maState ); |
935 | 0 | ::canvastools::prependToRenderState(aLocalState, rTransformation); |
936 | |
|
937 | 0 | return renderEffectText( *this, |
938 | 0 | aLocalState, |
939 | 0 | mpCanvas->getUNOCanvas(), |
940 | 0 | maShadowColor, |
941 | 0 | maShadowOffset, |
942 | 0 | maReliefColor, |
943 | 0 | maReliefOffset, |
944 | 0 | maTextFillColor); |
945 | 0 | } |
946 | | |
947 | | bool EffectTextAction::renderSubset( const ::basegfx::B2DHomMatrix& rTransformation, |
948 | | const Subset& /*rSubset*/ ) const |
949 | 0 | { |
950 | 0 | SAL_WARN( "cppcanvas.emf", "EffectTextAction::renderSubset(): Subset not supported by this object" ); |
951 | | |
952 | | // TODO(P1): Retrieve necessary font metric info for |
953 | | // TextAction from XCanvas. Currently, the |
954 | | // TextActionFactory does not generate this object for |
955 | | // subsettable text |
956 | 0 | return render( rTransformation ); |
957 | 0 | } |
958 | | |
959 | | geometry::RealRectangle2D EffectTextAction::queryTextBounds() const |
960 | 0 | { |
961 | | // create XTextLayout, to have the |
962 | | // XTextLayout::queryTextBounds() method available |
963 | 0 | uno::Reference< rendering::XTextLayout > xTextLayout( |
964 | 0 | mxFont->createTextLayout( |
965 | 0 | maStringContext, |
966 | 0 | maTextDirection, |
967 | 0 | 0 ) ); |
968 | |
|
969 | 0 | return xTextLayout->queryTextBounds(); |
970 | 0 | } |
971 | | |
972 | | css::uno::Reference<css::rendering::XPolyPolygon2D> EffectTextAction::queryTextBounds(const uno::Reference<rendering::XCanvas>& rCanvas) const |
973 | 0 | { |
974 | 0 | auto aTextBounds = queryTextBounds(); |
975 | 0 | auto aB2DBounds = ::basegfx::unotools::b2DRectangleFromRealRectangle2D(aTextBounds); |
976 | 0 | auto aTextBoundsPoly = ::basegfx::utils::createPolygonFromRect(aB2DBounds); |
977 | 0 | return ::basegfx::unotools::xPolyPolygonFromB2DPolygon(rCanvas->getDevice(), aTextBoundsPoly); |
978 | 0 | } |
979 | | |
980 | | ::basegfx::B2DRange EffectTextAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const |
981 | 0 | { |
982 | 0 | rendering::RenderState aLocalState( maState ); |
983 | 0 | ::canvastools::prependToRenderState(aLocalState, rTransformation); |
984 | |
|
985 | 0 | return calcEffectTextBounds( ::basegfx::unotools::b2DRectangleFromRealRectangle2D( |
986 | 0 | queryTextBounds() ), |
987 | 0 | ::basegfx::B2DRange( 0,0, |
988 | 0 | maLinesOverallSize.getWidth(), |
989 | 0 | maLinesOverallSize.getHeight() ), |
990 | 0 | maReliefOffset, |
991 | 0 | maShadowOffset, |
992 | 0 | aLocalState, |
993 | 0 | mpCanvas->getViewState() ); |
994 | 0 | } |
995 | | |
996 | | ::basegfx::B2DRange EffectTextAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation, |
997 | | const Subset& /*rSubset*/ ) const |
998 | 0 | { |
999 | 0 | SAL_WARN( "cppcanvas.emf", "EffectTextAction::getBounds(): Subset not supported by this object" ); |
1000 | | |
1001 | | // TODO(P1): Retrieve necessary font metric info for |
1002 | | // TextAction from XCanvas. Currently, the |
1003 | | // TextActionFactory does not generate this object for |
1004 | | // _subsettable_ text |
1005 | 0 | return getBounds( rTransformation ); |
1006 | 0 | } |
1007 | | |
1008 | | sal_Int32 EffectTextAction::getActionCount() const |
1009 | 0 | { |
1010 | | // TODO(P1): Retrieve necessary font metric info for |
1011 | | // TextAction from XCanvas. Currently, the |
1012 | | // TextActionFactory does not generate this object for |
1013 | | // subsettable text |
1014 | 0 | return 1; |
1015 | 0 | } |
1016 | | |
1017 | | |
1018 | | class TextArrayAction : public Action |
1019 | | { |
1020 | | public: |
1021 | | TextArrayAction( const ::basegfx::B2DPoint& rStartPoint, |
1022 | | const OUString& rString, |
1023 | | sal_Int32 nStartPos, |
1024 | | sal_Int32 nLen, |
1025 | | const uno::Sequence< double >& rOffsets, |
1026 | | const uno::Sequence< sal_Bool >& rKashidas, |
1027 | | const CanvasSharedPtr& rCanvas, |
1028 | | const OutDevState& rState ); |
1029 | | |
1030 | | TextArrayAction( const ::basegfx::B2DPoint& rStartPoint, |
1031 | | const OUString& rString, |
1032 | | sal_Int32 nStartPos, |
1033 | | sal_Int32 nLen, |
1034 | | const uno::Sequence< double >& rOffsets, |
1035 | | const uno::Sequence< sal_Bool >& rKashidas, |
1036 | | const CanvasSharedPtr& rCanvas, |
1037 | | const OutDevState& rState, |
1038 | | const ::basegfx::B2DHomMatrix& rTextTransform ); |
1039 | | |
1040 | | TextArrayAction(const TextArrayAction&) = delete; |
1041 | | const TextArrayAction& operator=(const TextArrayAction&) = delete; |
1042 | | |
1043 | | virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation ) const override; |
1044 | | virtual bool renderSubset( const ::basegfx::B2DHomMatrix& rTransformation, |
1045 | | const Subset& rSubset ) const override; |
1046 | | |
1047 | | virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const override; |
1048 | | virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation, |
1049 | | const Subset& rSubset ) const override; |
1050 | | |
1051 | | virtual sal_Int32 getActionCount() const override; |
1052 | | |
1053 | | private: |
1054 | | // TODO(P2): This is potentially a real mass object |
1055 | | // (every character might be a separate TextAction), |
1056 | | // thus, make it as lightweight as possible. For |
1057 | | // example, share common RenderState among several |
1058 | | // TextActions, maybe using maOffsets for the |
1059 | | // translation. |
1060 | | |
1061 | | uno::Reference< rendering::XTextLayout > mxTextLayout; |
1062 | | const CanvasSharedPtr mpCanvas; |
1063 | | rendering::RenderState maState; |
1064 | | double mnLayoutWidth; |
1065 | | }; |
1066 | | |
1067 | | TextArrayAction::TextArrayAction( const ::basegfx::B2DPoint& rStartPoint, |
1068 | | const OUString& rString, |
1069 | | sal_Int32 nStartPos, |
1070 | | sal_Int32 nLen, |
1071 | | const uno::Sequence< double >& rOffsets, |
1072 | | const uno::Sequence< sal_Bool >& rKashidas, |
1073 | | const CanvasSharedPtr& rCanvas, |
1074 | | const OutDevState& rState ) : |
1075 | 0 | mpCanvas( rCanvas ) |
1076 | 0 | { |
1077 | 0 | initLayoutWidth(mnLayoutWidth, rOffsets); |
1078 | |
|
1079 | 0 | initArrayAction( maState, |
1080 | 0 | mxTextLayout, |
1081 | 0 | rStartPoint, |
1082 | 0 | rString, |
1083 | 0 | nStartPos, |
1084 | 0 | nLen, |
1085 | 0 | rOffsets, |
1086 | 0 | rKashidas, |
1087 | 0 | rCanvas, |
1088 | 0 | rState, nullptr ); |
1089 | 0 | } |
1090 | | |
1091 | | TextArrayAction::TextArrayAction( const ::basegfx::B2DPoint& rStartPoint, |
1092 | | const OUString& rString, |
1093 | | sal_Int32 nStartPos, |
1094 | | sal_Int32 nLen, |
1095 | | const uno::Sequence< double >& rOffsets, |
1096 | | const uno::Sequence< sal_Bool >& rKashidas, |
1097 | | const CanvasSharedPtr& rCanvas, |
1098 | | const OutDevState& rState, |
1099 | | const ::basegfx::B2DHomMatrix& rTextTransform ) : |
1100 | 0 | mpCanvas( rCanvas ) |
1101 | 0 | { |
1102 | 0 | initLayoutWidth(mnLayoutWidth, rOffsets); |
1103 | |
|
1104 | 0 | initArrayAction( maState, |
1105 | 0 | mxTextLayout, |
1106 | 0 | rStartPoint, |
1107 | 0 | rString, |
1108 | 0 | nStartPos, |
1109 | 0 | nLen, |
1110 | 0 | rOffsets, |
1111 | 0 | rKashidas, |
1112 | 0 | rCanvas, |
1113 | 0 | rState, |
1114 | 0 | &rTextTransform ); |
1115 | 0 | } |
1116 | | |
1117 | | bool TextArrayAction::render( const ::basegfx::B2DHomMatrix& rTransformation ) const |
1118 | 0 | { |
1119 | 0 | SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TextArrayAction::render()" ); |
1120 | 0 | SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TextArrayAction: 0x" << std::hex << this ); |
1121 | | |
1122 | 0 | rendering::RenderState aLocalState( maState ); |
1123 | 0 | ::canvastools::prependToRenderState(aLocalState, rTransformation); |
1124 | |
|
1125 | 0 | mpCanvas->getUNOCanvas()->drawTextLayout( mxTextLayout, |
1126 | 0 | mpCanvas->getViewState(), |
1127 | 0 | aLocalState ); |
1128 | |
|
1129 | 0 | return true; |
1130 | 0 | } |
1131 | | |
1132 | | bool TextArrayAction::renderSubset( const ::basegfx::B2DHomMatrix& rTransformation, |
1133 | | const Subset& rSubset ) const |
1134 | 0 | { |
1135 | 0 | SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TextArrayAction::renderSubset()" ); |
1136 | 0 | SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TextArrayAction: 0x" << std::hex << this ); |
1137 | | |
1138 | 0 | rendering::RenderState aLocalState( maState ); |
1139 | 0 | uno::Reference< rendering::XTextLayout > xTextLayout( mxTextLayout ); |
1140 | |
|
1141 | 0 | double nDummy0, nDummy1; |
1142 | 0 | createSubsetLayout( xTextLayout, |
1143 | 0 | mnLayoutWidth, |
1144 | 0 | aLocalState, |
1145 | 0 | nDummy0, |
1146 | 0 | nDummy1, |
1147 | 0 | rTransformation, |
1148 | 0 | rSubset ); |
1149 | |
|
1150 | 0 | if( !xTextLayout.is() ) |
1151 | 0 | return true; // empty layout, render nothing |
1152 | | |
1153 | 0 | mpCanvas->getUNOCanvas()->drawTextLayout( xTextLayout, |
1154 | 0 | mpCanvas->getViewState(), |
1155 | 0 | aLocalState ); |
1156 | |
|
1157 | 0 | return true; |
1158 | 0 | } |
1159 | | |
1160 | | ::basegfx::B2DRange TextArrayAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const |
1161 | 0 | { |
1162 | 0 | rendering::RenderState aLocalState( maState ); |
1163 | 0 | ::canvastools::prependToRenderState(aLocalState, rTransformation); |
1164 | |
|
1165 | 0 | return cppcanvastools::calcDevicePixelBounds( ::basegfx::unotools::b2DRectangleFromRealRectangle2D( |
1166 | 0 | mxTextLayout->queryTextBounds() ), |
1167 | 0 | mpCanvas->getViewState(), |
1168 | 0 | aLocalState ); |
1169 | 0 | } |
1170 | | |
1171 | | ::basegfx::B2DRange TextArrayAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation, |
1172 | | const Subset& rSubset ) const |
1173 | 0 | { |
1174 | 0 | SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TextArrayAction::getBounds( subset )" ); |
1175 | 0 | SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TextArrayAction: 0x" << std::hex << this ); |
1176 | | |
1177 | 0 | rendering::RenderState aLocalState( maState ); |
1178 | 0 | uno::Reference< rendering::XTextLayout > xTextLayout( mxTextLayout ); |
1179 | |
|
1180 | 0 | double nDummy0, nDummy1; |
1181 | 0 | createSubsetLayout( xTextLayout, |
1182 | 0 | mnLayoutWidth, |
1183 | 0 | aLocalState, |
1184 | 0 | nDummy0, |
1185 | 0 | nDummy1, |
1186 | 0 | rTransformation, |
1187 | 0 | rSubset ); |
1188 | |
|
1189 | 0 | if( !xTextLayout.is() ) |
1190 | 0 | return ::basegfx::B2DRange(); // empty layout, empty bounds |
1191 | | |
1192 | 0 | return cppcanvastools::calcDevicePixelBounds( ::basegfx::unotools::b2DRectangleFromRealRectangle2D( |
1193 | 0 | xTextLayout->queryTextBounds() ), |
1194 | 0 | mpCanvas->getViewState(), |
1195 | 0 | aLocalState ); |
1196 | 0 | } |
1197 | | |
1198 | | sal_Int32 TextArrayAction::getActionCount() const |
1199 | 0 | { |
1200 | 0 | const rendering::StringContext aOrigContext( mxTextLayout->getText() ); |
1201 | |
|
1202 | 0 | return aOrigContext.Length; |
1203 | 0 | } |
1204 | | |
1205 | | |
1206 | | class EffectTextArrayAction : |
1207 | | public Action, |
1208 | | public TextRenderer |
1209 | | { |
1210 | | public: |
1211 | | EffectTextArrayAction( const ::basegfx::B2DPoint& rStartPoint, |
1212 | | const ::basegfx::B2DSize& rReliefOffset, |
1213 | | const ::Color& rReliefColor, |
1214 | | const ::basegfx::B2DSize& rShadowOffset, |
1215 | | const ::Color& rShadowColor, |
1216 | | const ::Color& rTextFillColor, |
1217 | | const OUString& rText, |
1218 | | sal_Int32 nStartPos, |
1219 | | sal_Int32 nLen, |
1220 | | const uno::Sequence< double >& rOffsets, |
1221 | | const uno::Sequence< sal_Bool >& rKashidas, |
1222 | | VirtualDevice const & rVDev, |
1223 | | const CanvasSharedPtr& rCanvas, |
1224 | | const OutDevState& rState ); |
1225 | | EffectTextArrayAction( const ::basegfx::B2DPoint& rStartPoint, |
1226 | | const ::basegfx::B2DSize& rReliefOffset, |
1227 | | const ::Color& rReliefColor, |
1228 | | const ::basegfx::B2DSize& rShadowOffset, |
1229 | | const ::Color& rShadowColor, |
1230 | | const ::Color& rTextFillColor, |
1231 | | const OUString& rText, |
1232 | | sal_Int32 nStartPos, |
1233 | | sal_Int32 nLen, |
1234 | | const uno::Sequence< double >& rOffsets, |
1235 | | const uno::Sequence< sal_Bool >& rKashidas, |
1236 | | VirtualDevice const & rVDev, |
1237 | | const CanvasSharedPtr& rCanvas, |
1238 | | const OutDevState& rState, |
1239 | | const ::basegfx::B2DHomMatrix& rTextTransform ); |
1240 | | |
1241 | | EffectTextArrayAction(const EffectTextArrayAction&) = delete; |
1242 | | const EffectTextArrayAction& operator=(const EffectTextArrayAction&) = delete; |
1243 | | |
1244 | | virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation ) const override; |
1245 | | virtual bool renderSubset( const ::basegfx::B2DHomMatrix& rTransformation, |
1246 | | const Subset& rSubset ) const override; |
1247 | | |
1248 | | virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const override; |
1249 | | virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation, |
1250 | | const Subset& rSubset ) const override; |
1251 | | |
1252 | | virtual sal_Int32 getActionCount() const override; |
1253 | | |
1254 | | private: |
1255 | | // TextRenderer interface |
1256 | | virtual bool operator()( const rendering::RenderState& rRenderState, const ::Color& rTextFillColor, bool bNormalText ) const override; |
1257 | | |
1258 | | css::uno::Reference<css::rendering::XPolyPolygon2D> queryTextBounds(const uno::Reference<rendering::XCanvas>& rCanvas) const; |
1259 | | |
1260 | | // TODO(P2): This is potentially a real mass object |
1261 | | // (every character might be a separate TextAction), |
1262 | | // thus, make it as lightweight as possible. For |
1263 | | // example, share common RenderState among several |
1264 | | // TextActions, maybe using maOffsets for the |
1265 | | // translation. |
1266 | | |
1267 | | uno::Reference< rendering::XTextLayout > mxTextLayout; |
1268 | | const CanvasSharedPtr mpCanvas; |
1269 | | rendering::RenderState maState; |
1270 | | const cppcanvastools::TextLineInfo maTextLineInfo; |
1271 | | TextLinesHelper maTextLinesHelper; |
1272 | | const ::basegfx::B2DSize maReliefOffset; |
1273 | | const ::Color maReliefColor; |
1274 | | const ::basegfx::B2DSize maShadowOffset; |
1275 | | const ::Color maShadowColor; |
1276 | | const ::Color maTextFillColor; |
1277 | | double mnLayoutWidth; |
1278 | | }; |
1279 | | |
1280 | | EffectTextArrayAction::EffectTextArrayAction( const ::basegfx::B2DPoint& rStartPoint, |
1281 | | const ::basegfx::B2DSize& rReliefOffset, |
1282 | | const ::Color& rReliefColor, |
1283 | | const ::basegfx::B2DSize& rShadowOffset, |
1284 | | const ::Color& rShadowColor, |
1285 | | const ::Color& rTextFillColor, |
1286 | | const OUString& rText, |
1287 | | sal_Int32 nStartPos, |
1288 | | sal_Int32 nLen, |
1289 | | const uno::Sequence< double >& rOffsets, |
1290 | | const uno::Sequence< sal_Bool >& rKashidas, |
1291 | | VirtualDevice const & rVDev, |
1292 | | const CanvasSharedPtr& rCanvas, |
1293 | | const OutDevState& rState ) : |
1294 | 0 | mpCanvas( rCanvas ), |
1295 | 0 | maTextLineInfo( cppcanvastools::createTextLineInfo( rVDev, rState ) ), |
1296 | 0 | maTextLinesHelper(mpCanvas, rState), |
1297 | 0 | maReliefOffset( rReliefOffset ), |
1298 | 0 | maReliefColor( rReliefColor ), |
1299 | 0 | maShadowOffset( rShadowOffset ), |
1300 | 0 | maShadowColor( rShadowColor ), |
1301 | 0 | maTextFillColor( rTextFillColor ) |
1302 | 0 | { |
1303 | 0 | initLayoutWidth(mnLayoutWidth, rOffsets); |
1304 | |
|
1305 | 0 | maTextLinesHelper.init(mnLayoutWidth, maTextLineInfo); |
1306 | |
|
1307 | 0 | initArrayAction( maState, |
1308 | 0 | mxTextLayout, |
1309 | 0 | rStartPoint, |
1310 | 0 | rText, |
1311 | 0 | nStartPos, |
1312 | 0 | nLen, |
1313 | 0 | rOffsets, |
1314 | 0 | rKashidas, |
1315 | 0 | rCanvas, |
1316 | 0 | rState, nullptr ); |
1317 | 0 | } |
1318 | | |
1319 | | EffectTextArrayAction::EffectTextArrayAction( const ::basegfx::B2DPoint& rStartPoint, |
1320 | | const ::basegfx::B2DSize& rReliefOffset, |
1321 | | const ::Color& rReliefColor, |
1322 | | const ::basegfx::B2DSize& rShadowOffset, |
1323 | | const ::Color& rShadowColor, |
1324 | | const ::Color& rTextFillColor, |
1325 | | const OUString& rText, |
1326 | | sal_Int32 nStartPos, |
1327 | | sal_Int32 nLen, |
1328 | | const uno::Sequence< double >& rOffsets, |
1329 | | const uno::Sequence< sal_Bool >& rKashidas, |
1330 | | VirtualDevice const & rVDev, |
1331 | | const CanvasSharedPtr& rCanvas, |
1332 | | const OutDevState& rState, |
1333 | | const ::basegfx::B2DHomMatrix& rTextTransform ) : |
1334 | 0 | mpCanvas( rCanvas ), |
1335 | 0 | maTextLineInfo( cppcanvastools::createTextLineInfo( rVDev, rState ) ), |
1336 | 0 | maTextLinesHelper(mpCanvas, rState), |
1337 | 0 | maReliefOffset( rReliefOffset ), |
1338 | 0 | maReliefColor( rReliefColor ), |
1339 | 0 | maShadowOffset( rShadowOffset ), |
1340 | 0 | maShadowColor( rShadowColor ), |
1341 | 0 | maTextFillColor( rTextFillColor ) |
1342 | 0 | { |
1343 | 0 | initLayoutWidth(mnLayoutWidth, rOffsets); |
1344 | |
|
1345 | 0 | maTextLinesHelper.init(mnLayoutWidth, maTextLineInfo); |
1346 | |
|
1347 | 0 | initArrayAction( maState, |
1348 | 0 | mxTextLayout, |
1349 | 0 | rStartPoint, |
1350 | 0 | rText, |
1351 | 0 | nStartPos, |
1352 | 0 | nLen, |
1353 | 0 | rOffsets, |
1354 | 0 | rKashidas, |
1355 | 0 | rCanvas, |
1356 | 0 | rState, |
1357 | 0 | &rTextTransform ); |
1358 | 0 | } |
1359 | | |
1360 | | css::uno::Reference<css::rendering::XPolyPolygon2D> EffectTextArrayAction::queryTextBounds(const uno::Reference<rendering::XCanvas>& rCanvas) const |
1361 | 0 | { |
1362 | 0 | const geometry::RealRectangle2D aTextBounds(mxTextLayout->queryTextBounds()); |
1363 | 0 | auto aB2DBounds = ::basegfx::unotools::b2DRectangleFromRealRectangle2D(aTextBounds); |
1364 | 0 | auto aTextBoundsPoly = ::basegfx::utils::createPolygonFromRect(aB2DBounds); |
1365 | 0 | return ::basegfx::unotools::xPolyPolygonFromB2DPolygon(rCanvas->getDevice(), aTextBoundsPoly); |
1366 | 0 | } |
1367 | | |
1368 | | bool EffectTextArrayAction::operator()( const rendering::RenderState& rRenderState, const ::Color& rTextFillColor, bool bNormalText) const |
1369 | 0 | { |
1370 | 0 | const rendering::ViewState aViewState( mpCanvas->getViewState() ); |
1371 | 0 | const uno::Reference< rendering::XCanvas > aCanvas( mpCanvas->getUNOCanvas() ); |
1372 | | |
1373 | | //rhbz#1589029 non-transparent text fill background support |
1374 | 0 | if (rTextFillColor != COL_AUTO) |
1375 | 0 | { |
1376 | 0 | rendering::RenderState aLocalState(rRenderState); |
1377 | 0 | aLocalState.DeviceColor = vcl::unotools::colorToDoubleSequence( |
1378 | 0 | rTextFillColor, aCanvas->getDevice()->getDeviceColorSpace()); |
1379 | 0 | auto xTextBounds = queryTextBounds(aCanvas); |
1380 | | // background of text |
1381 | 0 | aCanvas->fillPolyPolygon(xTextBounds, aViewState, aLocalState); |
1382 | 0 | } |
1383 | | |
1384 | | // under/over lines |
1385 | 0 | maTextLinesHelper.render(rRenderState, bNormalText); |
1386 | |
|
1387 | 0 | aCanvas->drawTextLayout( mxTextLayout, |
1388 | 0 | aViewState, |
1389 | 0 | rRenderState ); |
1390 | |
|
1391 | 0 | return true; |
1392 | 0 | } |
1393 | | |
1394 | | bool EffectTextArrayAction::render( const ::basegfx::B2DHomMatrix& rTransformation ) const |
1395 | 0 | { |
1396 | 0 | SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::EffectTextArrayAction::render()" ); |
1397 | 0 | SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::EffectTextArrayAction: 0x" << std::hex << this ); |
1398 | | |
1399 | 0 | rendering::RenderState aLocalState( maState ); |
1400 | 0 | ::canvastools::prependToRenderState(aLocalState, rTransformation); |
1401 | |
|
1402 | 0 | return renderEffectText( *this, |
1403 | 0 | aLocalState, |
1404 | 0 | mpCanvas->getUNOCanvas(), |
1405 | 0 | maShadowColor, |
1406 | 0 | maShadowOffset, |
1407 | 0 | maReliefColor, |
1408 | 0 | maReliefOffset, |
1409 | 0 | maTextFillColor); |
1410 | 0 | } |
1411 | | |
1412 | | class EffectTextArrayRenderHelper : public TextRenderer |
1413 | | { |
1414 | | public: |
1415 | | EffectTextArrayRenderHelper( const uno::Reference< rendering::XCanvas >& rCanvas, |
1416 | | const uno::Reference< rendering::XTextLayout >& rTextLayout, |
1417 | | const TextLinesHelper& rTextLinesHelper, |
1418 | | const rendering::ViewState& rViewState ) : |
1419 | 0 | mrCanvas( rCanvas ), |
1420 | 0 | mrTextLayout( rTextLayout ), |
1421 | 0 | mrTextLinesHelper( rTextLinesHelper ), |
1422 | 0 | mrViewState( rViewState ) |
1423 | 0 | { |
1424 | 0 | } |
1425 | | |
1426 | | // TextRenderer interface |
1427 | | virtual bool operator()( const rendering::RenderState& rRenderState, const ::Color& rTextFillColor,bool bNormalText) const override |
1428 | 0 | { |
1429 | 0 | mrTextLinesHelper.render(rRenderState, bNormalText); |
1430 | | |
1431 | | //rhbz#1589029 non-transparent text fill background support |
1432 | 0 | if (rTextFillColor != COL_AUTO) |
1433 | 0 | { |
1434 | 0 | rendering::RenderState aLocalState(rRenderState); |
1435 | 0 | aLocalState.DeviceColor = vcl::unotools::colorToDoubleSequence( |
1436 | 0 | rTextFillColor, mrCanvas->getDevice()->getDeviceColorSpace()); |
1437 | 0 | auto xTextBounds = queryTextBounds(); |
1438 | | // background of text |
1439 | 0 | mrCanvas->fillPolyPolygon(xTextBounds, mrViewState, aLocalState); |
1440 | 0 | } |
1441 | |
|
1442 | 0 | mrCanvas->drawTextLayout( mrTextLayout, |
1443 | 0 | mrViewState, |
1444 | 0 | rRenderState ); |
1445 | |
|
1446 | 0 | return true; |
1447 | 0 | } |
1448 | | |
1449 | | private: |
1450 | | |
1451 | | css::uno::Reference<css::rendering::XPolyPolygon2D> queryTextBounds() const |
1452 | 0 | { |
1453 | 0 | const geometry::RealRectangle2D aTextBounds(mrTextLayout->queryTextBounds()); |
1454 | 0 | auto aB2DBounds = ::basegfx::unotools::b2DRectangleFromRealRectangle2D(aTextBounds); |
1455 | 0 | auto aTextBoundsPoly = ::basegfx::utils::createPolygonFromRect(aB2DBounds); |
1456 | 0 | return ::basegfx::unotools::xPolyPolygonFromB2DPolygon(mrCanvas->getDevice(), aTextBoundsPoly); |
1457 | 0 | } |
1458 | | |
1459 | | const uno::Reference< rendering::XCanvas >& mrCanvas; |
1460 | | const uno::Reference< rendering::XTextLayout >& mrTextLayout; |
1461 | | const TextLinesHelper& mrTextLinesHelper; |
1462 | | const rendering::ViewState& mrViewState; |
1463 | | }; |
1464 | | |
1465 | | bool EffectTextArrayAction::renderSubset( const ::basegfx::B2DHomMatrix& rTransformation, |
1466 | | const Subset& rSubset ) const |
1467 | 0 | { |
1468 | 0 | SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::EffectTextArrayAction::renderSubset()" ); |
1469 | 0 | SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::EffectTextArrayAction: 0x" << std::hex << this ); |
1470 | | |
1471 | 0 | rendering::RenderState aLocalState( maState ); |
1472 | 0 | uno::Reference< rendering::XTextLayout > xTextLayout( mxTextLayout ); |
1473 | 0 | const geometry::RealRectangle2D aTextBounds( mxTextLayout->queryTextBounds() ); |
1474 | |
|
1475 | 0 | double nMinPos(0.0); |
1476 | 0 | double nMaxPos(aTextBounds.X2 - aTextBounds.X1); |
1477 | |
|
1478 | 0 | createSubsetLayout( xTextLayout, |
1479 | 0 | mnLayoutWidth, |
1480 | 0 | aLocalState, |
1481 | 0 | nMinPos, |
1482 | 0 | nMaxPos, |
1483 | 0 | rTransformation, |
1484 | 0 | rSubset ); |
1485 | |
|
1486 | 0 | if( !xTextLayout.is() ) |
1487 | 0 | return true; // empty layout, render nothing |
1488 | | |
1489 | | |
1490 | | // create and setup local line polygon |
1491 | | // =================================== |
1492 | | |
1493 | 0 | uno::Reference< rendering::XCanvas > xCanvas( mpCanvas->getUNOCanvas() ); |
1494 | 0 | const rendering::ViewState aViewState( mpCanvas->getViewState() ); |
1495 | |
|
1496 | 0 | TextLinesHelper aHelper = maTextLinesHelper; |
1497 | 0 | aHelper.init(nMaxPos - nMinPos, maTextLineInfo); |
1498 | | |
1499 | | |
1500 | | // render everything |
1501 | | // ================= |
1502 | |
|
1503 | 0 | return renderEffectText( |
1504 | 0 | EffectTextArrayRenderHelper( xCanvas, |
1505 | 0 | xTextLayout, |
1506 | 0 | aHelper, |
1507 | 0 | aViewState ), |
1508 | 0 | aLocalState, |
1509 | 0 | xCanvas, |
1510 | 0 | maShadowColor, |
1511 | 0 | maShadowOffset, |
1512 | 0 | maReliefColor, |
1513 | 0 | maReliefOffset, |
1514 | 0 | maTextFillColor); |
1515 | 0 | } |
1516 | | |
1517 | | ::basegfx::B2DRange EffectTextArrayAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const |
1518 | 0 | { |
1519 | 0 | rendering::RenderState aLocalState( maState ); |
1520 | 0 | ::canvastools::prependToRenderState(aLocalState, rTransformation); |
1521 | |
|
1522 | 0 | ::basegfx::B2DSize aSize = maTextLinesHelper.getOverallSize(); |
1523 | |
|
1524 | 0 | return calcEffectTextBounds( ::basegfx::unotools::b2DRectangleFromRealRectangle2D( |
1525 | 0 | mxTextLayout->queryTextBounds() ), |
1526 | 0 | basegfx::B2DRange(0, 0, |
1527 | 0 | aSize.getWidth(), |
1528 | 0 | aSize.getHeight()), |
1529 | 0 | maReliefOffset, |
1530 | 0 | maShadowOffset, |
1531 | 0 | aLocalState, |
1532 | 0 | mpCanvas->getViewState() ); |
1533 | 0 | } |
1534 | | |
1535 | | ::basegfx::B2DRange EffectTextArrayAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation, |
1536 | | const Subset& rSubset ) const |
1537 | 0 | { |
1538 | 0 | SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::EffectTextArrayAction::getBounds( subset )" ); |
1539 | 0 | SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::EffectTextArrayAction: 0x" << std::hex << this ); |
1540 | | |
1541 | 0 | rendering::RenderState aLocalState( maState ); |
1542 | 0 | uno::Reference< rendering::XTextLayout > xTextLayout( mxTextLayout ); |
1543 | 0 | const geometry::RealRectangle2D aTextBounds( mxTextLayout->queryTextBounds() ); |
1544 | |
|
1545 | 0 | double nMinPos(0.0); |
1546 | 0 | double nMaxPos(aTextBounds.X2 - aTextBounds.X1); |
1547 | |
|
1548 | 0 | createSubsetLayout( xTextLayout, |
1549 | 0 | mnLayoutWidth, |
1550 | 0 | aLocalState, |
1551 | 0 | nMinPos, |
1552 | 0 | nMaxPos, |
1553 | 0 | rTransformation, |
1554 | 0 | rSubset ); |
1555 | |
|
1556 | 0 | if( !xTextLayout.is() ) |
1557 | 0 | return ::basegfx::B2DRange(); // empty layout, empty bounds |
1558 | | |
1559 | | |
1560 | | // create and setup local line polygon |
1561 | | // =================================== |
1562 | | |
1563 | 0 | const ::basegfx::B2DPolyPolygon aPoly( |
1564 | 0 | cppcanvastools::createTextLinesPolyPolygon( |
1565 | 0 | 0.0, nMaxPos - nMinPos, |
1566 | 0 | maTextLineInfo ) ); |
1567 | |
|
1568 | 0 | return calcEffectTextBounds( ::basegfx::unotools::b2DRectangleFromRealRectangle2D( |
1569 | 0 | xTextLayout->queryTextBounds() ), |
1570 | 0 | aPoly.getB2DRange(), |
1571 | 0 | maReliefOffset, |
1572 | 0 | maShadowOffset, |
1573 | 0 | aLocalState, |
1574 | 0 | mpCanvas->getViewState() ); |
1575 | 0 | } |
1576 | | |
1577 | | sal_Int32 EffectTextArrayAction::getActionCount() const |
1578 | 0 | { |
1579 | 0 | const rendering::StringContext aOrigContext( mxTextLayout->getText() ); |
1580 | |
|
1581 | 0 | return aOrigContext.Length; |
1582 | 0 | } |
1583 | | |
1584 | | |
1585 | | class OutlineAction : |
1586 | | public Action, |
1587 | | public TextRenderer |
1588 | | { |
1589 | | public: |
1590 | | OutlineAction( const ::basegfx::B2DPoint& rStartPoint, |
1591 | | const ::basegfx::B2DSize& rReliefOffset, |
1592 | | const ::Color& rReliefColor, |
1593 | | const ::basegfx::B2DSize& rShadowOffset, |
1594 | | const ::Color& rShadowColor, |
1595 | | const ::Color& rFillColor, |
1596 | | uno::Reference< rendering::XPolyPolygon2D > xFillPoly, |
1597 | | const ::basegfx::B2DRectangle& rOutlineBounds, |
1598 | | uno::Reference< rendering::XPolyPolygon2D > xTextPoly, |
1599 | | const uno::Sequence< double >& rOffsets, |
1600 | | VirtualDevice const & rVDev, |
1601 | | const CanvasSharedPtr& rCanvas, |
1602 | | const OutDevState& rState ); |
1603 | | OutlineAction( const ::basegfx::B2DPoint& rStartPoint, |
1604 | | const ::basegfx::B2DSize& rReliefOffset, |
1605 | | const ::Color& rReliefColor, |
1606 | | const ::basegfx::B2DSize& rShadowOffset, |
1607 | | const ::Color& rShadowColor, |
1608 | | const ::Color& rFillColor, |
1609 | | uno::Reference< rendering::XPolyPolygon2D > xFillPoly, |
1610 | | const ::basegfx::B2DRectangle& rOutlineBounds, |
1611 | | uno::Reference< rendering::XPolyPolygon2D > xTextPoly, |
1612 | | const uno::Sequence< double >& rOffsets, |
1613 | | VirtualDevice const & rVDev, |
1614 | | const CanvasSharedPtr& rCanvas, |
1615 | | const OutDevState& rState, |
1616 | | const ::basegfx::B2DHomMatrix& rTextTransform ); |
1617 | | |
1618 | | OutlineAction(const OutlineAction&) = delete; |
1619 | | const OutlineAction& operator=(const OutlineAction&) = delete; |
1620 | | |
1621 | | virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation ) const override; |
1622 | | virtual bool renderSubset( const ::basegfx::B2DHomMatrix& rTransformation, |
1623 | | const Subset& rSubset ) const override; |
1624 | | |
1625 | | virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const override; |
1626 | | virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation, |
1627 | | const Subset& rSubset ) const override; |
1628 | | |
1629 | | virtual sal_Int32 getActionCount() const override; |
1630 | | |
1631 | | private: |
1632 | | // TextRenderer interface |
1633 | | virtual bool operator()( const rendering::RenderState& rRenderState, const ::Color& rTextFillColor, bool bNormalText ) const override; |
1634 | | |
1635 | | // TODO(P2): This is potentially a real mass object |
1636 | | // (every character might be a separate TextAction), |
1637 | | // thus, make it as lightweight as possible. For |
1638 | | // example, share common RenderState among several |
1639 | | // TextActions, maybe using maOffsets for the |
1640 | | // translation. |
1641 | | |
1642 | | uno::Reference< rendering::XPolyPolygon2D > mxTextPoly; |
1643 | | |
1644 | | const uno::Sequence< double > maOffsets; |
1645 | | const CanvasSharedPtr mpCanvas; |
1646 | | rendering::RenderState maState; |
1647 | | double mnOutlineWidth; |
1648 | | const uno::Sequence< double > maFillColor; |
1649 | | uno::Reference< rendering::XPolyPolygon2D > mxBackgroundFillPoly; |
1650 | | const cppcanvastools::TextLineInfo maTextLineInfo; |
1651 | | ::basegfx::B2DSize maLinesOverallSize; |
1652 | | const ::basegfx::B2DRectangle maOutlineBounds; |
1653 | | uno::Reference< rendering::XPolyPolygon2D > mxTextLines; |
1654 | | const ::basegfx::B2DSize maReliefOffset; |
1655 | | const ::Color maReliefColor; |
1656 | | const ::basegfx::B2DSize maShadowOffset; |
1657 | | const ::Color maShadowColor; |
1658 | | const ::Color maTextFillColor; |
1659 | | const ::Color maBackgroundFillColor; |
1660 | | }; |
1661 | | |
1662 | | double calcOutlineWidth( const OutDevState& rState, |
1663 | | VirtualDevice const & rVDev ) |
1664 | 0 | { |
1665 | 0 | const ::basegfx::B2DSize aFontSize( 0, |
1666 | 0 | rVDev.GetFont().GetFontHeight() / 64.0 ); |
1667 | |
|
1668 | 0 | const double nOutlineWidth( |
1669 | 0 | (rState.mapModeTransform * aFontSize).getHeight() ); |
1670 | |
|
1671 | 0 | return nOutlineWidth < 1.0 ? 1.0 : nOutlineWidth; |
1672 | 0 | } |
1673 | | |
1674 | | OutlineAction::OutlineAction( const ::basegfx::B2DPoint& rStartPoint, |
1675 | | const ::basegfx::B2DSize& rReliefOffset, |
1676 | | const ::Color& rReliefColor, |
1677 | | const ::basegfx::B2DSize& rShadowOffset, |
1678 | | const ::Color& rShadowColor, |
1679 | | const ::Color& rFillColor, |
1680 | | uno::Reference< rendering::XPolyPolygon2D > xFillPoly, |
1681 | | const ::basegfx::B2DRectangle& rOutlineBounds, |
1682 | | uno::Reference< rendering::XPolyPolygon2D > xTextPoly, |
1683 | | const uno::Sequence< double >& rOffsets, |
1684 | | VirtualDevice const & rVDev, |
1685 | | const CanvasSharedPtr& rCanvas, |
1686 | | const OutDevState& rState ) : |
1687 | 0 | mxTextPoly(std::move( xTextPoly )), |
1688 | 0 | maOffsets( rOffsets ), |
1689 | 0 | mpCanvas( rCanvas ), |
1690 | 0 | mnOutlineWidth( calcOutlineWidth(rState,rVDev) ), |
1691 | | maFillColor( |
1692 | 0 | vcl::unotools::colorToDoubleSequence( |
1693 | 0 | COL_WHITE, |
1694 | 0 | rCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() )), |
1695 | 0 | mxBackgroundFillPoly(std::move( xFillPoly )), |
1696 | 0 | maTextLineInfo( cppcanvastools::createTextLineInfo( rVDev, rState ) ), |
1697 | 0 | maOutlineBounds( rOutlineBounds ), |
1698 | 0 | maReliefOffset( rReliefOffset ), |
1699 | 0 | maReliefColor( rReliefColor ), |
1700 | 0 | maShadowOffset( rShadowOffset ), |
1701 | 0 | maShadowColor( rShadowColor ), |
1702 | 0 | maBackgroundFillColor( rFillColor ) |
1703 | 0 | { |
1704 | 0 | double nLayoutWidth = 0.0; |
1705 | |
|
1706 | 0 | initLayoutWidth(nLayoutWidth, rOffsets); |
1707 | |
|
1708 | 0 | initEffectLinePolyPolygon( maLinesOverallSize, |
1709 | 0 | mxTextLines, |
1710 | 0 | rCanvas, |
1711 | 0 | nLayoutWidth, |
1712 | 0 | maTextLineInfo ); |
1713 | |
|
1714 | 0 | init( maState, |
1715 | 0 | rStartPoint, |
1716 | 0 | rState, |
1717 | 0 | rCanvas ); |
1718 | 0 | } |
1719 | | |
1720 | | OutlineAction::OutlineAction( const ::basegfx::B2DPoint& rStartPoint, |
1721 | | const ::basegfx::B2DSize& rReliefOffset, |
1722 | | const ::Color& rReliefColor, |
1723 | | const ::basegfx::B2DSize& rShadowOffset, |
1724 | | const ::Color& rShadowColor, |
1725 | | const ::Color& rFillColor, |
1726 | | uno::Reference< rendering::XPolyPolygon2D > xFillPoly, |
1727 | | const ::basegfx::B2DRectangle& rOutlineBounds, |
1728 | | uno::Reference< rendering::XPolyPolygon2D > xTextPoly, |
1729 | | const uno::Sequence< double >& rOffsets, |
1730 | | VirtualDevice const & rVDev, |
1731 | | const CanvasSharedPtr& rCanvas, |
1732 | | const OutDevState& rState, |
1733 | | const ::basegfx::B2DHomMatrix& rTextTransform ) : |
1734 | 0 | mxTextPoly(std::move( xTextPoly )), |
1735 | 0 | maOffsets( rOffsets ), |
1736 | 0 | mpCanvas( rCanvas ), |
1737 | 0 | mnOutlineWidth( calcOutlineWidth(rState,rVDev) ), |
1738 | | maFillColor( |
1739 | 0 | vcl::unotools::colorToDoubleSequence( |
1740 | 0 | COL_WHITE, |
1741 | 0 | rCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() )), |
1742 | 0 | mxBackgroundFillPoly(std::move( xFillPoly )), |
1743 | 0 | maTextLineInfo( cppcanvastools::createTextLineInfo( rVDev, rState ) ), |
1744 | 0 | maOutlineBounds( rOutlineBounds ), |
1745 | 0 | maReliefOffset( rReliefOffset ), |
1746 | 0 | maReliefColor( rReliefColor ), |
1747 | 0 | maShadowOffset( rShadowOffset ), |
1748 | 0 | maShadowColor( rShadowColor ), |
1749 | 0 | maBackgroundFillColor( rFillColor ) |
1750 | 0 | { |
1751 | 0 | double nLayoutWidth = 0.0; |
1752 | 0 | initLayoutWidth(nLayoutWidth, rOffsets); |
1753 | |
|
1754 | 0 | initEffectLinePolyPolygon( maLinesOverallSize, |
1755 | 0 | mxTextLines, |
1756 | 0 | rCanvas, |
1757 | 0 | nLayoutWidth, |
1758 | 0 | maTextLineInfo ); |
1759 | |
|
1760 | 0 | init( maState, |
1761 | 0 | rStartPoint, |
1762 | 0 | rState, |
1763 | 0 | rCanvas, |
1764 | 0 | rTextTransform ); |
1765 | 0 | } |
1766 | | |
1767 | | bool OutlineAction::operator()( const rendering::RenderState& rRenderState, const ::Color& /*rTextFillColor*/, bool /*bNormalText*/ ) const |
1768 | 0 | { |
1769 | 0 | const rendering::ViewState aViewState( mpCanvas->getViewState() ); |
1770 | 0 | const uno::Reference< rendering::XCanvas > xCanvas( mpCanvas->getUNOCanvas() ); |
1771 | |
|
1772 | 0 | if (mxBackgroundFillPoly.is()) |
1773 | 0 | { |
1774 | 0 | rendering::RenderState aLocalState( rRenderState ); |
1775 | 0 | aLocalState.DeviceColor = vcl::unotools::colorToDoubleSequence( |
1776 | 0 | maBackgroundFillColor, xCanvas->getDevice()->getDeviceColorSpace()); |
1777 | 0 | xCanvas->fillPolyPolygon(mxBackgroundFillPoly, aViewState, aLocalState); |
1778 | 0 | } |
1779 | |
|
1780 | 0 | rendering::StrokeAttributes aStrokeAttributes; |
1781 | |
|
1782 | 0 | aStrokeAttributes.StrokeWidth = mnOutlineWidth; |
1783 | 0 | aStrokeAttributes.MiterLimit = 1.0; |
1784 | 0 | aStrokeAttributes.StartCapType = rendering::PathCapType::BUTT; |
1785 | 0 | aStrokeAttributes.EndCapType = rendering::PathCapType::BUTT; |
1786 | 0 | aStrokeAttributes.JoinType = rendering::PathJoinType::MITER; |
1787 | |
|
1788 | 0 | rendering::RenderState aLocalState( rRenderState ); |
1789 | 0 | aLocalState.DeviceColor = maFillColor; |
1790 | | |
1791 | | // TODO(P1): implement caching |
1792 | | |
1793 | | // background of text |
1794 | 0 | xCanvas->fillPolyPolygon( mxTextPoly, |
1795 | 0 | aViewState, |
1796 | 0 | aLocalState ); |
1797 | | |
1798 | | // border line of text |
1799 | 0 | xCanvas->strokePolyPolygon( mxTextPoly, |
1800 | 0 | aViewState, |
1801 | 0 | rRenderState, |
1802 | 0 | aStrokeAttributes ); |
1803 | | |
1804 | | // underlines/strikethrough - background |
1805 | 0 | xCanvas->fillPolyPolygon( mxTextLines, |
1806 | 0 | aViewState, |
1807 | 0 | aLocalState ); |
1808 | | // underlines/strikethrough - border |
1809 | 0 | xCanvas->strokePolyPolygon( mxTextLines, |
1810 | 0 | aViewState, |
1811 | 0 | rRenderState, |
1812 | 0 | aStrokeAttributes ); |
1813 | |
|
1814 | 0 | return true; |
1815 | 0 | } |
1816 | | |
1817 | | bool OutlineAction::render( const ::basegfx::B2DHomMatrix& rTransformation ) const |
1818 | 0 | { |
1819 | 0 | SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::EffectTextArrayAction::render()" ); |
1820 | 0 | SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::EffectTextArrayAction: 0x" << std::hex << this ); |
1821 | | |
1822 | 0 | rendering::RenderState aLocalState( maState ); |
1823 | 0 | ::canvastools::prependToRenderState(aLocalState, rTransformation); |
1824 | |
|
1825 | 0 | return renderEffectText( *this, |
1826 | 0 | aLocalState, |
1827 | 0 | mpCanvas->getUNOCanvas(), |
1828 | 0 | maShadowColor, |
1829 | 0 | maShadowOffset, |
1830 | 0 | maReliefColor, |
1831 | 0 | maReliefOffset, |
1832 | 0 | maTextFillColor); |
1833 | 0 | } |
1834 | | |
1835 | | #if 0 // see #if'ed out use in OutlineAction::renderSubset below: |
1836 | | class OutlineTextArrayRenderHelper : public TextRenderer |
1837 | | { |
1838 | | public: |
1839 | | OutlineTextArrayRenderHelper( const uno::Reference< rendering::XCanvas >& rCanvas, |
1840 | | const uno::Reference< rendering::XPolyPolygon2D >& rTextPolygon, |
1841 | | const uno::Reference< rendering::XPolyPolygon2D >& rLinePolygon, |
1842 | | const rendering::ViewState& rViewState, |
1843 | | double nOutlineWidth ) : |
1844 | | maFillColor( |
1845 | | vcl::unotools::colorToDoubleSequence( |
1846 | | ::COL_WHITE, |
1847 | | rCanvas->getDevice()->getDeviceColorSpace() )), |
1848 | | mnOutlineWidth( nOutlineWidth ), |
1849 | | mrCanvas( rCanvas ), |
1850 | | mrTextPolygon( rTextPolygon ), |
1851 | | mrLinePolygon( rLinePolygon ), |
1852 | | mrViewState( rViewState ) |
1853 | | { |
1854 | | } |
1855 | | |
1856 | | // TextRenderer interface |
1857 | | virtual bool operator()( const rendering::RenderState& rRenderState ) const |
1858 | | { |
1859 | | rendering::StrokeAttributes aStrokeAttributes; |
1860 | | |
1861 | | aStrokeAttributes.StrokeWidth = mnOutlineWidth; |
1862 | | aStrokeAttributes.MiterLimit = 1.0; |
1863 | | aStrokeAttributes.StartCapType = rendering::PathCapType::BUTT; |
1864 | | aStrokeAttributes.EndCapType = rendering::PathCapType::BUTT; |
1865 | | aStrokeAttributes.JoinType = rendering::PathJoinType::MITER; |
1866 | | |
1867 | | rendering::RenderState aLocalState( rRenderState ); |
1868 | | aLocalState.DeviceColor = maFillColor; |
1869 | | |
1870 | | // TODO(P1): implement caching |
1871 | | |
1872 | | // background of text |
1873 | | mrCanvas->fillPolyPolygon( mrTextPolygon, |
1874 | | mrViewState, |
1875 | | aLocalState ); |
1876 | | |
1877 | | // border line of text |
1878 | | mrCanvas->strokePolyPolygon( mrTextPolygon, |
1879 | | mrViewState, |
1880 | | rRenderState, |
1881 | | aStrokeAttributes ); |
1882 | | |
1883 | | // underlines/strikethrough - background |
1884 | | mrCanvas->fillPolyPolygon( mrLinePolygon, |
1885 | | mrViewState, |
1886 | | aLocalState ); |
1887 | | // underlines/strikethrough - border |
1888 | | mrCanvas->strokePolyPolygon( mrLinePolygon, |
1889 | | mrViewState, |
1890 | | rRenderState, |
1891 | | aStrokeAttributes ); |
1892 | | |
1893 | | return true; |
1894 | | } |
1895 | | |
1896 | | private: |
1897 | | const uno::Sequence< double > maFillColor; |
1898 | | double mnOutlineWidth; |
1899 | | const uno::Reference< rendering::XCanvas >& mrCanvas; |
1900 | | const uno::Reference< rendering::XPolyPolygon2D >& mrTextPolygon; |
1901 | | const uno::Reference< rendering::XPolyPolygon2D >& mrLinePolygon; |
1902 | | const rendering::ViewState& mrViewState; |
1903 | | }; |
1904 | | #endif |
1905 | | |
1906 | | bool OutlineAction::renderSubset( const ::basegfx::B2DHomMatrix& rTransformation, |
1907 | | const Subset& rSubset ) const |
1908 | 0 | { |
1909 | 0 | SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::OutlineAction::renderSubset()" ); |
1910 | 0 | SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::OutlineAction: 0x" << std::hex << this ); |
1911 | | |
1912 | 0 | if( rSubset.mnSubsetBegin == rSubset.mnSubsetEnd ) |
1913 | 0 | return true; // empty range, render nothing |
1914 | | |
1915 | 0 | #if 1 |
1916 | | // TODO(F3): Subsetting NYI for outline text! |
1917 | 0 | return render( rTransformation ); |
1918 | | #else |
1919 | | const rendering::StringContext rOrigContext( mxTextLayout->getText() ); |
1920 | | |
1921 | | if( rSubset.mnSubsetBegin == 0 && |
1922 | | rSubset.mnSubsetEnd == rOrigContext.Length ) |
1923 | | { |
1924 | | // full range, no need for subsetting |
1925 | | return render( rTransformation ); |
1926 | | } |
1927 | | |
1928 | | rendering::RenderState aLocalState( maState ); |
1929 | | ::canvastools::prependToRenderState(aLocalState, rTransformation); |
1930 | | |
1931 | | |
1932 | | // create and setup local Text polygon |
1933 | | // =================================== |
1934 | | |
1935 | | uno::Reference< rendering::XPolyPolygon2D > xTextPolygon(); |
1936 | | |
1937 | | // TODO(P3): Provide an API method for that! |
1938 | | |
1939 | | if( !xTextLayout.is() ) |
1940 | | return false; |
1941 | | |
1942 | | // render everything |
1943 | | // ================= |
1944 | | |
1945 | | return renderEffectText( |
1946 | | OutlineTextArrayRenderHelper( |
1947 | | xCanvas, |
1948 | | mnOutlineWidth, |
1949 | | xTextLayout, |
1950 | | xTextLines, |
1951 | | rViewState ), |
1952 | | aLocalState, |
1953 | | rViewState, |
1954 | | xCanvas, |
1955 | | maShadowColor, |
1956 | | maShadowOffset, |
1957 | | maReliefColor, |
1958 | | maReliefOffset ); |
1959 | | #endif |
1960 | 0 | } |
1961 | | |
1962 | | ::basegfx::B2DRange OutlineAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const |
1963 | 0 | { |
1964 | 0 | rendering::RenderState aLocalState( maState ); |
1965 | 0 | ::canvastools::prependToRenderState(aLocalState, rTransformation); |
1966 | |
|
1967 | 0 | return calcEffectTextBounds( maOutlineBounds, |
1968 | 0 | ::basegfx::B2DRange(0, 0, |
1969 | 0 | maLinesOverallSize.getWidth(), |
1970 | 0 | maLinesOverallSize.getHeight()), |
1971 | 0 | maReliefOffset, |
1972 | 0 | maShadowOffset, |
1973 | 0 | aLocalState, |
1974 | 0 | mpCanvas->getViewState() ); |
1975 | 0 | } |
1976 | | |
1977 | | ::basegfx::B2DRange OutlineAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation, |
1978 | | const Subset& /*rSubset*/ ) const |
1979 | 0 | { |
1980 | 0 | SAL_WARN( "cppcanvas.emf", "OutlineAction::getBounds(): Subset not yet supported by this object" ); |
1981 | | |
1982 | 0 | return getBounds( rTransformation ); |
1983 | 0 | } |
1984 | | |
1985 | | sal_Int32 OutlineAction::getActionCount() const |
1986 | 0 | { |
1987 | | // TODO(F3): Subsetting NYI for outline text! |
1988 | 0 | return maOffsets.getLength(); |
1989 | 0 | } |
1990 | | |
1991 | | |
1992 | | // Action factory methods |
1993 | | |
1994 | | |
1995 | | /** Create an outline action |
1996 | | |
1997 | | This method extracts the polygonal outline from the |
1998 | | text, and creates a properly setup OutlineAction from |
1999 | | it. |
2000 | | */ |
2001 | | std::shared_ptr<Action> createOutline( const ::basegfx::B2DPoint& rStartPoint, |
2002 | | const ::basegfx::B2DSize& rReliefOffset, |
2003 | | const ::Color& rReliefColor, |
2004 | | const ::basegfx::B2DSize& rShadowOffset, |
2005 | | const ::Color& rShadowColor, |
2006 | | const ::Color& rTextFillColor, |
2007 | | const OUString& rText, |
2008 | | sal_Int32 nStartPos, |
2009 | | sal_Int32 nLen, |
2010 | | KernArraySpan pDXArray, |
2011 | | std::span<const sal_Bool> pKashidaArray, |
2012 | | VirtualDevice& rVDev, |
2013 | | const CanvasSharedPtr& rCanvas, |
2014 | | const OutDevState& rState, |
2015 | | const Renderer::Parameters& rParms ) |
2016 | 0 | { |
2017 | | // operate on raw DX array here (in logical coordinate |
2018 | | // system), to have a higher resolution |
2019 | | // PolyPolygon. That polygon is then converted to |
2020 | | // device coordinate system. |
2021 | | |
2022 | | // #i68512# Temporarily switch off font rotation |
2023 | | // (which is already contained in the render state |
2024 | | // transformation matrix - otherwise, glyph polygons |
2025 | | // will be rotated twice) |
2026 | 0 | const vcl::Font aOrigFont( rVDev.GetFont() ); |
2027 | 0 | vcl::Font aUnrotatedFont( aOrigFont ); |
2028 | 0 | aUnrotatedFont.SetOrientation(0_deg10); |
2029 | 0 | rVDev.SetFont( aUnrotatedFont ); |
2030 | | |
2031 | | // TODO(F3): Don't understand parameter semantics of |
2032 | | // GetTextOutlines() |
2033 | 0 | ::basegfx::B2DPolyPolygon aResultingPolyPolygon; |
2034 | 0 | PolyPolyVector aVCLPolyPolyVector; |
2035 | 0 | const bool bHaveOutlines( rVDev.GetTextOutlines( aVCLPolyPolyVector, rText, |
2036 | 0 | static_cast<sal_uInt16>(nStartPos), |
2037 | 0 | static_cast<sal_uInt16>(nStartPos), |
2038 | 0 | static_cast<sal_uInt16>(nLen), |
2039 | 0 | 0, pDXArray, pKashidaArray ) ); |
2040 | 0 | rVDev.SetFont(aOrigFont); |
2041 | |
|
2042 | 0 | if( !bHaveOutlines ) |
2043 | 0 | return std::shared_ptr<Action>(); |
2044 | | |
2045 | | // remove offsetting from mapmode transformation |
2046 | | // (outline polygons must stay at origin, only need to |
2047 | | // be scaled) |
2048 | 0 | ::basegfx::B2DHomMatrix aMapModeTransform( |
2049 | 0 | rState.mapModeTransform ); |
2050 | 0 | aMapModeTransform.set(0,2, 0.0); |
2051 | 0 | aMapModeTransform.set(1,2, 0.0); |
2052 | |
|
2053 | 0 | for( const auto& rVCLPolyPolygon : aVCLPolyPolyVector ) |
2054 | 0 | { |
2055 | 0 | ::basegfx::B2DPolyPolygon aPolyPolygon = rVCLPolyPolygon.getB2DPolyPolygon(); |
2056 | 0 | aPolyPolygon.transform( aMapModeTransform ); |
2057 | | |
2058 | | // append result to collecting polypoly |
2059 | 0 | for( sal_uInt32 i=0; i<aPolyPolygon.count(); ++i ) |
2060 | 0 | { |
2061 | | // #i47795# Ensure closed polygons (since |
2062 | | // FreeType returns the glyph outlines |
2063 | | // open) |
2064 | 0 | const ::basegfx::B2DPolygon& rPoly( aPolyPolygon.getB2DPolygon( i ) ); |
2065 | 0 | const sal_uInt32 nCount( rPoly.count() ); |
2066 | 0 | if( nCount<3 || |
2067 | 0 | rPoly.isClosed() ) |
2068 | 0 | { |
2069 | | // polygon either degenerate, or |
2070 | | // already closed. |
2071 | 0 | aResultingPolyPolygon.append( rPoly ); |
2072 | 0 | } |
2073 | 0 | else |
2074 | 0 | { |
2075 | 0 | ::basegfx::B2DPolygon aPoly(rPoly); |
2076 | 0 | aPoly.setClosed(true); |
2077 | |
|
2078 | 0 | aResultingPolyPolygon.append( aPoly ); |
2079 | 0 | } |
2080 | 0 | } |
2081 | 0 | } |
2082 | |
|
2083 | 0 | const uno::Sequence< double > aCharWidthSeq( |
2084 | 0 | !pDXArray.empty() ? |
2085 | 0 | setupDXArray( pDXArray, nLen, rState ) : |
2086 | 0 | setupDXArray( rText, |
2087 | 0 | nStartPos, |
2088 | 0 | nLen, |
2089 | 0 | rVDev, |
2090 | 0 | rState )); |
2091 | 0 | const uno::Reference< rendering::XPolyPolygon2D > xTextPoly( |
2092 | 0 | ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( |
2093 | 0 | rCanvas->getUNOCanvas()->getDevice(), |
2094 | 0 | aResultingPolyPolygon ) ); |
2095 | | |
2096 | | // create background color fill polygon? |
2097 | 0 | css::uno::Reference<css::rendering::XPolyPolygon2D> xTextBoundsPoly; |
2098 | 0 | if (rTextFillColor != COL_AUTO) |
2099 | 0 | { |
2100 | 0 | rendering::StringContext aStringContext( rText, nStartPos, nLen ); |
2101 | 0 | uno::Reference< rendering::XTextLayout > xTextLayout( |
2102 | 0 | rState.xFont->createTextLayout( |
2103 | 0 | aStringContext, |
2104 | 0 | rState.textDirection, |
2105 | 0 | 0 ) ); |
2106 | |
|
2107 | 0 | auto aTextBounds = xTextLayout->queryTextBounds(); |
2108 | 0 | auto aB2DBounds = ::basegfx::unotools::b2DRectangleFromRealRectangle2D(aTextBounds); |
2109 | 0 | auto aTextBoundsPoly = ::basegfx::utils::createPolygonFromRect(aB2DBounds); |
2110 | 0 | xTextBoundsPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolygon( |
2111 | 0 | rCanvas->getUNOCanvas()->getDevice(), |
2112 | 0 | aTextBoundsPoly); |
2113 | 0 | } |
2114 | |
|
2115 | 0 | if( rParms.maTextTransformation ) |
2116 | 0 | { |
2117 | 0 | return std::make_shared<OutlineAction>( |
2118 | 0 | rStartPoint, |
2119 | 0 | rReliefOffset, |
2120 | 0 | rReliefColor, |
2121 | 0 | rShadowOffset, |
2122 | 0 | rShadowColor, |
2123 | 0 | rTextFillColor, |
2124 | 0 | xTextBoundsPoly, |
2125 | 0 | aResultingPolyPolygon.getB2DRange(), |
2126 | 0 | xTextPoly, |
2127 | 0 | aCharWidthSeq, |
2128 | 0 | rVDev, |
2129 | 0 | rCanvas, |
2130 | 0 | rState, |
2131 | 0 | *rParms.maTextTransformation ); |
2132 | 0 | } |
2133 | 0 | else |
2134 | 0 | { |
2135 | 0 | return std::make_shared<OutlineAction>( |
2136 | 0 | rStartPoint, |
2137 | 0 | rReliefOffset, |
2138 | 0 | rReliefColor, |
2139 | 0 | rShadowOffset, |
2140 | 0 | rShadowColor, |
2141 | 0 | rTextFillColor, |
2142 | 0 | xTextBoundsPoly, |
2143 | 0 | aResultingPolyPolygon.getB2DRange(), |
2144 | 0 | xTextPoly, |
2145 | 0 | aCharWidthSeq, |
2146 | 0 | rVDev, |
2147 | 0 | rCanvas, |
2148 | 0 | rState ); |
2149 | 0 | } |
2150 | 0 | } |
2151 | | |
2152 | | } // namespace |
2153 | | |
2154 | | |
2155 | | std::shared_ptr<Action> TextActionFactory::createTextAction( const ::Point& rStartPoint, |
2156 | | const ::Size& rReliefOffset, |
2157 | | const ::Color& rReliefColor, |
2158 | | const ::Size& rShadowOffset, |
2159 | | const ::Color& rShadowColor, |
2160 | | const ::Color& rTextFillColor, |
2161 | | const OUString& rText, |
2162 | | sal_Int32 nStartPos, |
2163 | | sal_Int32 nLen, |
2164 | | KernArraySpan pDXArray, |
2165 | | std::span<const sal_Bool> pKashidaArray, |
2166 | | VirtualDevice& rVDev, |
2167 | | const CanvasSharedPtr& rCanvas, |
2168 | | const OutDevState& rState, |
2169 | | const Renderer::Parameters& rParms, |
2170 | | bool bSubsettable ) |
2171 | 0 | { |
2172 | 0 | const ::Size aBaselineOffset( cppcanvastools::getBaselineOffset( rState, |
2173 | 0 | rVDev ) ); |
2174 | | // #143885# maintain (nearly) full precision positioning, |
2175 | | // by circumventing integer-based OutDev-mapping |
2176 | 0 | const ::basegfx::B2DPoint aStartPoint( |
2177 | 0 | rState.mapModeTransform * |
2178 | 0 | ::basegfx::B2DPoint(rStartPoint.X() + aBaselineOffset.Width(), |
2179 | 0 | rStartPoint.Y() + aBaselineOffset.Height()) ); |
2180 | |
|
2181 | 0 | const ::basegfx::B2DSize aReliefOffset( |
2182 | 0 | rState.mapModeTransform * vcl::unotools::b2DSizeFromSize( rReliefOffset ) ); |
2183 | 0 | const ::basegfx::B2DSize aShadowOffset( |
2184 | 0 | rState.mapModeTransform * vcl::unotools::b2DSizeFromSize( rShadowOffset ) ); |
2185 | |
|
2186 | 0 | if( rState.isTextOutlineModeSet ) |
2187 | 0 | { |
2188 | 0 | return createOutline( |
2189 | 0 | aStartPoint, |
2190 | 0 | aReliefOffset, |
2191 | 0 | rReliefColor, |
2192 | 0 | aShadowOffset, |
2193 | 0 | rShadowColor, |
2194 | 0 | rTextFillColor, |
2195 | 0 | rText, |
2196 | 0 | nStartPos, |
2197 | 0 | nLen, |
2198 | 0 | pDXArray, |
2199 | 0 | pKashidaArray, |
2200 | 0 | rVDev, |
2201 | 0 | rCanvas, |
2202 | 0 | rState, |
2203 | 0 | rParms ); |
2204 | 0 | } |
2205 | | |
2206 | | // convert DX array to device coordinate system (and |
2207 | | // create it in the first place, if pDXArray is NULL) |
2208 | 0 | const uno::Sequence< double > aCharWidths( |
2209 | 0 | !pDXArray.empty() ? |
2210 | 0 | setupDXArray( pDXArray, nLen, rState ) : |
2211 | 0 | setupDXArray( rText, |
2212 | 0 | nStartPos, |
2213 | 0 | nLen, |
2214 | 0 | rVDev, |
2215 | 0 | rState )); |
2216 | |
|
2217 | 0 | const uno::Sequence< sal_Bool > aKashidas(pKashidaArray.data(), pKashidaArray.size()); |
2218 | | |
2219 | | // determine type of text action to create |
2220 | | // ======================================= |
2221 | |
|
2222 | 0 | const ::Color aEmptyColor( COL_AUTO ); |
2223 | |
|
2224 | 0 | std::shared_ptr<Action> ret; |
2225 | | |
2226 | | // no DX array, and no need to subset - no need to store |
2227 | | // DX array, then. |
2228 | 0 | if( pDXArray.empty() && !bSubsettable ) |
2229 | 0 | { |
2230 | | // effects, or not? |
2231 | 0 | if( !rState.textOverlineStyle && |
2232 | 0 | !rState.textUnderlineStyle && |
2233 | 0 | !rState.textStrikeoutStyle && |
2234 | 0 | rReliefColor == aEmptyColor && |
2235 | 0 | rShadowColor == aEmptyColor && |
2236 | 0 | rTextFillColor == aEmptyColor ) |
2237 | 0 | { |
2238 | | // nope |
2239 | 0 | if( rParms.maTextTransformation ) |
2240 | 0 | { |
2241 | 0 | ret = std::make_shared<TextAction>( |
2242 | 0 | aStartPoint, |
2243 | 0 | rText, |
2244 | 0 | nStartPos, |
2245 | 0 | nLen, |
2246 | 0 | rCanvas, |
2247 | 0 | rState, |
2248 | 0 | *rParms.maTextTransformation ); |
2249 | 0 | } |
2250 | 0 | else |
2251 | 0 | { |
2252 | 0 | ret = std::make_shared<TextAction>( |
2253 | 0 | aStartPoint, |
2254 | 0 | rText, |
2255 | 0 | nStartPos, |
2256 | 0 | nLen, |
2257 | 0 | rCanvas, |
2258 | 0 | rState ); |
2259 | 0 | } |
2260 | 0 | } |
2261 | 0 | else |
2262 | 0 | { |
2263 | | // at least one of the effects requested |
2264 | 0 | if( rParms.maTextTransformation ) |
2265 | 0 | ret = std::make_shared<EffectTextAction>( |
2266 | 0 | aStartPoint, |
2267 | 0 | aReliefOffset, |
2268 | 0 | rReliefColor, |
2269 | 0 | aShadowOffset, |
2270 | 0 | rShadowColor, |
2271 | 0 | rTextFillColor, |
2272 | 0 | rText, |
2273 | 0 | nStartPos, |
2274 | 0 | nLen, |
2275 | 0 | rVDev, |
2276 | 0 | rCanvas, |
2277 | 0 | rState, |
2278 | 0 | *rParms.maTextTransformation ); |
2279 | 0 | else |
2280 | 0 | ret = std::make_shared<EffectTextAction>( |
2281 | 0 | aStartPoint, |
2282 | 0 | aReliefOffset, |
2283 | 0 | rReliefColor, |
2284 | 0 | aShadowOffset, |
2285 | 0 | rShadowColor, |
2286 | 0 | rTextFillColor, |
2287 | 0 | rText, |
2288 | 0 | nStartPos, |
2289 | 0 | nLen, |
2290 | 0 | rVDev, |
2291 | 0 | rCanvas, |
2292 | 0 | rState ); |
2293 | 0 | } |
2294 | 0 | } |
2295 | 0 | else |
2296 | 0 | { |
2297 | | // DX array necessary - any effects? |
2298 | 0 | if( !rState.textOverlineStyle && |
2299 | 0 | !rState.textUnderlineStyle && |
2300 | 0 | !rState.textStrikeoutStyle && |
2301 | 0 | rReliefColor == aEmptyColor && |
2302 | 0 | rShadowColor == aEmptyColor && |
2303 | 0 | rTextFillColor == aEmptyColor ) |
2304 | 0 | { |
2305 | | // nope |
2306 | 0 | if( rParms.maTextTransformation ) |
2307 | 0 | ret = std::make_shared<TextArrayAction>( |
2308 | 0 | aStartPoint, |
2309 | 0 | rText, |
2310 | 0 | nStartPos, |
2311 | 0 | nLen, |
2312 | 0 | aCharWidths, |
2313 | 0 | aKashidas, |
2314 | 0 | rCanvas, |
2315 | 0 | rState, |
2316 | 0 | *rParms.maTextTransformation ); |
2317 | 0 | else |
2318 | 0 | ret = std::make_shared<TextArrayAction>( |
2319 | 0 | aStartPoint, |
2320 | 0 | rText, |
2321 | 0 | nStartPos, |
2322 | 0 | nLen, |
2323 | 0 | aCharWidths, |
2324 | 0 | aKashidas, |
2325 | 0 | rCanvas, |
2326 | 0 | rState ); |
2327 | 0 | } |
2328 | 0 | else |
2329 | 0 | { |
2330 | | // at least one of the effects requested |
2331 | 0 | if( rParms.maTextTransformation ) |
2332 | 0 | ret = std::make_shared<EffectTextArrayAction>( |
2333 | 0 | aStartPoint, |
2334 | 0 | aReliefOffset, |
2335 | 0 | rReliefColor, |
2336 | 0 | aShadowOffset, |
2337 | 0 | rShadowColor, |
2338 | 0 | rTextFillColor, |
2339 | 0 | rText, |
2340 | 0 | nStartPos, |
2341 | 0 | nLen, |
2342 | 0 | aCharWidths, |
2343 | 0 | aKashidas, |
2344 | 0 | rVDev, |
2345 | 0 | rCanvas, |
2346 | 0 | rState, |
2347 | 0 | *rParms.maTextTransformation ); |
2348 | 0 | else |
2349 | 0 | ret = std::make_shared<EffectTextArrayAction>( |
2350 | 0 | aStartPoint, |
2351 | 0 | aReliefOffset, |
2352 | 0 | rReliefColor, |
2353 | 0 | aShadowOffset, |
2354 | 0 | rShadowColor, |
2355 | 0 | rTextFillColor, |
2356 | 0 | rText, |
2357 | 0 | nStartPos, |
2358 | 0 | nLen, |
2359 | 0 | aCharWidths, |
2360 | 0 | aKashidas, |
2361 | 0 | rVDev, |
2362 | 0 | rCanvas, |
2363 | 0 | rState ); |
2364 | 0 | } |
2365 | 0 | } |
2366 | 0 | return ret; |
2367 | 0 | } |
2368 | | } |
2369 | | |
2370 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |