/src/libreoffice/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx
Line | Count | Source |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* |
3 | | * This file is part of the LibreOffice project. |
4 | | * |
5 | | * This Source Code Form is subject to the terms of the Mozilla Public |
6 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
7 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
8 | | * |
9 | | * This file incorporates work covered by the following license notice: |
10 | | * |
11 | | * Licensed to the Apache Software Foundation (ASF) under one or more |
12 | | * contributor license agreements. See the NOTICE file distributed |
13 | | * with this work for additional information regarding copyright |
14 | | * ownership. The ASF licenses this file to you under the Apache |
15 | | * License, Version 2.0 (the "License"); you may not use this file |
16 | | * except in compliance with the License. You may obtain a copy of |
17 | | * the License at http://www.apache.org/licenses/LICENSE-2.0 . |
18 | | */ |
19 | | |
20 | | #include <cmath> |
21 | | #include <memory> |
22 | | #include "vclmetafileprocessor2d.hxx" |
23 | | #include <rtl/ustring.hxx> |
24 | | #include <tools/gen.hxx> |
25 | | #include <tools/mapunit.hxx> |
26 | | #include <tools/stream.hxx> |
27 | | #include <comphelper/diagnose_ex.hxx> |
28 | | #include <comphelper/flagguard.hxx> |
29 | | #include <comphelper/processfactory.hxx> |
30 | | #include <basegfx/polygon/b2dpolygonclipper.hxx> |
31 | | #include <basegfx/polygon/b2dpolypolygontools.hxx> |
32 | | #include <basegfx/polygon/b2dpolygontools.hxx> |
33 | | #include <basegfx/polygon/b2dlinegeometry.hxx> |
34 | | #include <vcl/virdev.hxx> |
35 | | #include <vcl/gdimtf.hxx> |
36 | | #include <vcl/gradient.hxx> |
37 | | #include <vcl/graphictools.hxx> |
38 | | #include <tools/lazydelete.hxx> |
39 | | #include <vcl/metaact.hxx> |
40 | | #include <vcl/rendercontext/DrawModeFlags.hxx> |
41 | | #include <vcl/graph.hxx> // for PDFExtOutDevData Graphic support |
42 | | #include <vcl/formpdfexport.hxx> // for PDFExtOutDevData Graphic support |
43 | | #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> |
44 | | #include <drawinglayer/primitive2d/textprimitive2d.hxx> |
45 | | #include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> |
46 | | #include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx> |
47 | | #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> |
48 | | #include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx> |
49 | | #include <drawinglayer/primitive2d/PolyPolygonHatchPrimitive2D.hxx> |
50 | | #include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx> |
51 | | #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> |
52 | | #include <drawinglayer/primitive2d/maskprimitive2d.hxx> |
53 | | #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> |
54 | | #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> |
55 | | #include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> |
56 | | #include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx> |
57 | | #include <drawinglayer/primitive2d/transformprimitive2d.hxx> |
58 | | #include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx> |
59 | | #include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> |
60 | | #include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx> |
61 | | #include <drawinglayer/primitive2d/controlprimitive2d.hxx> |
62 | | #include <drawinglayer/primitive2d/graphicprimitive2d.hxx> |
63 | | #include <drawinglayer/primitive2d/pagepreviewprimitive2d.hxx> |
64 | | #include <drawinglayer/primitive2d/epsprimitive2d.hxx> |
65 | | #include <drawinglayer/primitive2d/structuretagprimitive2d.hxx> |
66 | | #include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx> // for Title/Description metadata |
67 | | #include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx> |
68 | | #include <drawinglayer/converters.hxx> |
69 | | #include <basegfx/matrix/b2dhommatrixtools.hxx> |
70 | | #include <tools/vcompat.hxx> |
71 | | |
72 | | #include <com/sun/star/awt/XControl.hpp> |
73 | | #include <com/sun/star/i18n/BreakIterator.hpp> |
74 | | #include <com/sun/star/i18n/CharacterIteratorMode.hpp> |
75 | | #include <com/sun/star/i18n/WordType.hpp> |
76 | | #include <com/sun/star/beans/XPropertySet.hpp> |
77 | | |
78 | | using namespace com::sun::star; |
79 | | |
80 | | // #112245# definition for maximum allowed point count due to Metafile target. |
81 | | // To be on the safe side with the old tools polygon, use slightly less than |
82 | | // the theoretical maximum (bad experiences with tools polygon) |
83 | | |
84 | 4.98k | #define MAX_POLYGON_POINT_COUNT_METAFILE (0x0000fff0) |
85 | | |
86 | | namespace |
87 | | { |
88 | | // #112245# helper to split line polygon in half |
89 | | void splitLinePolygon(const basegfx::B2DPolygon& rBasePolygon, basegfx::B2DPolygon& o_aLeft, |
90 | | basegfx::B2DPolygon& o_aRight) |
91 | 0 | { |
92 | 0 | const sal_uInt32 nCount(rBasePolygon.count()); |
93 | |
|
94 | 0 | if (nCount) |
95 | 0 | { |
96 | 0 | const sal_uInt32 nHalfCount((nCount - 1) >> 1); |
97 | |
|
98 | 0 | o_aLeft = basegfx::B2DPolygon(rBasePolygon, 0, nHalfCount + 1); |
99 | 0 | o_aLeft.setClosed(false); |
100 | |
|
101 | 0 | o_aRight = basegfx::B2DPolygon(rBasePolygon, nHalfCount, nCount - nHalfCount); |
102 | 0 | o_aRight.setClosed(false); |
103 | |
|
104 | 0 | if (rBasePolygon.isClosed()) |
105 | 0 | { |
106 | 0 | o_aRight.append(rBasePolygon.getB2DPoint(0)); |
107 | |
|
108 | 0 | if (rBasePolygon.areControlPointsUsed()) |
109 | 0 | { |
110 | 0 | o_aRight.setControlPoints(o_aRight.count() - 1, rBasePolygon.getPrevControlPoint(0), |
111 | 0 | rBasePolygon.getNextControlPoint(0)); |
112 | 0 | } |
113 | 0 | } |
114 | 0 | } |
115 | 0 | else |
116 | 0 | { |
117 | 0 | o_aLeft.clear(); |
118 | 0 | o_aRight.clear(); |
119 | 0 | } |
120 | 0 | } |
121 | | |
122 | | // #112245# helper to evtl. split filled polygons to maximum metafile point count |
123 | | void fillPolyPolygonNeededToBeSplit(basegfx::B2DPolyPolygon& rPolyPolygon) |
124 | 469 | { |
125 | 469 | const sal_uInt32 nPolyCount(rPolyPolygon.count()); |
126 | | |
127 | 469 | if (!nPolyCount) |
128 | 0 | return; |
129 | | |
130 | 469 | basegfx::B2DPolyPolygon aSplitted; |
131 | | |
132 | 938 | for (sal_uInt32 a(0); a < nPolyCount; a++) |
133 | 469 | { |
134 | 469 | const basegfx::B2DPolygon& aCandidate(rPolyPolygon.getB2DPolygon(a)); |
135 | 469 | const sal_uInt32 nPointCount(aCandidate.count()); |
136 | 469 | bool bNeedToSplit(false); |
137 | | |
138 | 469 | if (aCandidate.areControlPointsUsed()) |
139 | 0 | { |
140 | | // compare with the maximum for bezier curved polygons |
141 | 0 | bNeedToSplit = nPointCount > ((MAX_POLYGON_POINT_COUNT_METAFILE / 3L) - 1); |
142 | 0 | } |
143 | 469 | else |
144 | 469 | { |
145 | | // compare with the maximum for simple point polygons |
146 | 469 | bNeedToSplit = nPointCount > (MAX_POLYGON_POINT_COUNT_METAFILE - 1); |
147 | 469 | } |
148 | | |
149 | 469 | if (bNeedToSplit) |
150 | 0 | { |
151 | | // need to split the partial polygon |
152 | 0 | const basegfx::B2DRange aRange(aCandidate.getB2DRange()); |
153 | 0 | const basegfx::B2DPoint aCenter(aRange.getCenter()); |
154 | |
|
155 | 0 | if (aRange.getWidth() > aRange.getHeight()) |
156 | 0 | { |
157 | | // clip in left and right |
158 | 0 | const basegfx::B2DPolyPolygon aLeft(basegfx::utils::clipPolygonOnParallelAxis( |
159 | 0 | aCandidate, false, true, aCenter.getX(), false)); |
160 | 0 | const basegfx::B2DPolyPolygon aRight(basegfx::utils::clipPolygonOnParallelAxis( |
161 | 0 | aCandidate, false, false, aCenter.getX(), false)); |
162 | |
|
163 | 0 | aSplitted.append(aLeft); |
164 | 0 | aSplitted.append(aRight); |
165 | 0 | } |
166 | 0 | else |
167 | 0 | { |
168 | | // clip in top and bottom |
169 | 0 | const basegfx::B2DPolyPolygon aTop(basegfx::utils::clipPolygonOnParallelAxis( |
170 | 0 | aCandidate, true, true, aCenter.getY(), false)); |
171 | 0 | const basegfx::B2DPolyPolygon aBottom(basegfx::utils::clipPolygonOnParallelAxis( |
172 | 0 | aCandidate, true, false, aCenter.getY(), false)); |
173 | |
|
174 | 0 | aSplitted.append(aTop); |
175 | 0 | aSplitted.append(aBottom); |
176 | 0 | } |
177 | 0 | } |
178 | 469 | else |
179 | 469 | { |
180 | 469 | aSplitted.append(aCandidate); |
181 | 469 | } |
182 | 469 | } |
183 | | |
184 | 469 | if (aSplitted.count() != nPolyCount) |
185 | 0 | { |
186 | 0 | rPolyPolygon = std::move(aSplitted); |
187 | 0 | } |
188 | 469 | } |
189 | | |
190 | | /** Filter input polypolygon for effectively empty sub-fills |
191 | | |
192 | | Needed to fix fdo#37559 |
193 | | |
194 | | @param rPoly |
195 | | tools::PolyPolygon to filter |
196 | | |
197 | | @return converted tools PolyPolygon, w/o one-point fills |
198 | | */ |
199 | | tools::PolyPolygon getFillPolyPolygon(const ::basegfx::B2DPolyPolygon& rPoly) |
200 | 0 | { |
201 | | // filter input rPoly |
202 | 0 | basegfx::B2DPolyPolygon aPoly; |
203 | 0 | sal_uInt32 nCount(rPoly.count()); |
204 | 0 | for (sal_uInt32 i = 0; i < nCount; ++i) |
205 | 0 | { |
206 | 0 | const basegfx::B2DPolygon& aCandidate(rPoly.getB2DPolygon(i)); |
207 | 0 | if (!aCandidate.isClosed() || aCandidate.count() > 1) |
208 | 0 | aPoly.append(aCandidate); |
209 | 0 | } |
210 | 0 | return tools::PolyPolygon(aPoly); |
211 | 0 | } |
212 | | |
213 | | } // end of anonymous namespace |
214 | | |
215 | | namespace drawinglayer::processor2d |
216 | | { |
217 | | tools::Rectangle |
218 | | VclMetafileProcessor2D::impDumpToMetaFile(const primitive2d::Primitive2DContainer& rContent, |
219 | | GDIMetaFile& o_rContentMetafile) |
220 | 0 | { |
221 | | // Prepare VDev, MetaFile and connections |
222 | 0 | OutputDevice* pLastOutputDevice = mpOutputDevice; |
223 | 0 | GDIMetaFile* pLastMetafile = mpMetaFile; |
224 | 0 | basegfx::B2DRange aPrimitiveRange(rContent.getB2DRange(getViewInformation2D())); |
225 | | |
226 | | // transform primitive range with current transformation (e.g shadow offset) |
227 | 0 | aPrimitiveRange.transform(maCurrentTransformation); |
228 | |
|
229 | 0 | const tools::Rectangle aPrimitiveRectangle( |
230 | 0 | basegfx::fround<tools::Long>(aPrimitiveRange.getMinX()), |
231 | 0 | basegfx::fround<tools::Long>(aPrimitiveRange.getMinY()), |
232 | 0 | basegfx::fround<tools::Long>(aPrimitiveRange.getMaxX()), |
233 | 0 | basegfx::fround<tools::Long>(aPrimitiveRange.getMaxY())); |
234 | 0 | ScopedVclPtrInstance<VirtualDevice> aContentVDev; |
235 | 0 | MapMode aNewMapMode(pLastOutputDevice->GetMapMode()); |
236 | |
|
237 | 0 | mpOutputDevice = aContentVDev.get(); |
238 | 0 | mpMetaFile = &o_rContentMetafile; |
239 | 0 | aContentVDev->EnableOutput(false); |
240 | 0 | aContentVDev->SetMapMode(pLastOutputDevice->GetMapMode()); |
241 | 0 | o_rContentMetafile.Record(aContentVDev.get()); |
242 | 0 | aContentVDev->SetLineColor(pLastOutputDevice->GetLineColor()); |
243 | 0 | aContentVDev->SetFillColor(pLastOutputDevice->GetFillColor()); |
244 | 0 | aContentVDev->SetFont(pLastOutputDevice->GetFont()); |
245 | 0 | aContentVDev->SetDrawMode(pLastOutputDevice->GetDrawMode()); |
246 | 0 | aContentVDev->SetSettings(pLastOutputDevice->GetSettings()); |
247 | 0 | aContentVDev->SetRefPoint(pLastOutputDevice->GetRefPoint()); |
248 | | |
249 | | // dump to MetaFile |
250 | 0 | process(rContent); |
251 | | |
252 | | // cleanups |
253 | 0 | o_rContentMetafile.Stop(); |
254 | 0 | o_rContentMetafile.WindStart(); |
255 | 0 | aNewMapMode.SetOrigin(aPrimitiveRectangle.TopLeft()); |
256 | 0 | o_rContentMetafile.SetPrefMapMode(aNewMapMode); |
257 | 0 | o_rContentMetafile.SetPrefSize(aPrimitiveRectangle.GetSize()); |
258 | 0 | mpOutputDevice = pLastOutputDevice; |
259 | 0 | mpMetaFile = pLastMetafile; |
260 | |
|
261 | 0 | return aPrimitiveRectangle; |
262 | 0 | } |
263 | | |
264 | | void VclMetafileProcessor2D::impConvertFillGradientAttributeToVCLGradient( |
265 | | Gradient& o_rVCLGradient, const attribute::FillGradientAttribute& rFiGrAtt, |
266 | | bool bIsTransparenceGradient) const |
267 | 0 | { |
268 | 0 | const basegfx::BColor aStartColor(rFiGrAtt.getColorStops().front().getStopColor()); |
269 | 0 | const basegfx::BColor aEndColor(rFiGrAtt.getColorStops().back().getStopColor()); |
270 | |
|
271 | 0 | if (bIsTransparenceGradient) |
272 | 0 | { |
273 | | // it's about transparence channel intensities (black/white), do not use color modifier |
274 | 0 | o_rVCLGradient.SetStartColor(Color(aStartColor)); |
275 | 0 | o_rVCLGradient.SetEndColor(Color(aEndColor)); |
276 | 0 | } |
277 | 0 | else |
278 | 0 | { |
279 | | // use color modifier to influence start/end color of gradient |
280 | 0 | o_rVCLGradient.SetStartColor(Color(maBColorModifierStack.getModifiedColor(aStartColor))); |
281 | 0 | o_rVCLGradient.SetEndColor(Color(maBColorModifierStack.getModifiedColor(aEndColor))); |
282 | 0 | } |
283 | |
|
284 | 0 | o_rVCLGradient.SetAngle( |
285 | 0 | Degree10(static_cast<sal_uInt32>(basegfx::rad2deg<10>(rFiGrAtt.getAngle())))); |
286 | 0 | o_rVCLGradient.SetBorder(static_cast<sal_uInt16>(rFiGrAtt.getBorder() * 100.0)); |
287 | 0 | o_rVCLGradient.SetOfsX(static_cast<sal_uInt16>(rFiGrAtt.getOffsetX() * 100.0)); |
288 | 0 | o_rVCLGradient.SetOfsY(static_cast<sal_uInt16>(rFiGrAtt.getOffsetY() * 100.0)); |
289 | 0 | o_rVCLGradient.SetSteps(rFiGrAtt.getSteps()); |
290 | | |
291 | | // defaults for intensity; those were computed into the start/end colors already |
292 | 0 | o_rVCLGradient.SetStartIntensity(100); |
293 | 0 | o_rVCLGradient.SetEndIntensity(100); |
294 | 0 | o_rVCLGradient.SetStyle(rFiGrAtt.getStyle()); |
295 | 0 | } |
296 | | |
297 | | void VclMetafileProcessor2D::impStartSvtGraphicFill(SvtGraphicFill const* pSvtGraphicFill) |
298 | 0 | { |
299 | 0 | if (pSvtGraphicFill && !mnSvtGraphicFillCount) |
300 | 0 | { |
301 | 0 | SvMemoryStream aMemStm; |
302 | |
|
303 | 0 | WriteSvtGraphicFill(aMemStm, *pSvtGraphicFill); |
304 | 0 | mpMetaFile->AddAction(new MetaCommentAction( |
305 | 0 | "XPATHFILL_SEQ_BEGIN"_ostr, 0, static_cast<const sal_uInt8*>(aMemStm.GetData()), |
306 | 0 | aMemStm.TellEnd())); |
307 | 0 | mnSvtGraphicFillCount++; |
308 | 0 | } |
309 | 0 | } |
310 | | |
311 | | void VclMetafileProcessor2D::impEndSvtGraphicFill(SvtGraphicFill const* pSvtGraphicFill) |
312 | 0 | { |
313 | 0 | if (pSvtGraphicFill && mnSvtGraphicFillCount) |
314 | 0 | { |
315 | 0 | mnSvtGraphicFillCount--; |
316 | 0 | mpMetaFile->AddAction(new MetaCommentAction("XPATHFILL_SEQ_END"_ostr)); |
317 | 0 | } |
318 | 0 | } |
319 | | |
320 | | double VclMetafileProcessor2D::getTransformedLineWidth(double fWidth) const |
321 | 8.44k | { |
322 | | // #i113922# the LineWidth is duplicated in the MetaPolylineAction, |
323 | | // and also inside the SvtGraphicStroke and needs transforming into |
324 | | // the same space as its coordinates here cf. fdo#61789 |
325 | | // This is a partial fix. When an object transformation is used which |
326 | | // e.g. contains a scaleX != scaleY, an unproportional scaling will happen. |
327 | 8.44k | const basegfx::B2DVector aDiscreteUnit(maCurrentTransformation |
328 | 8.44k | * basegfx::B2DVector(fWidth, 0.0)); |
329 | | |
330 | 8.44k | return aDiscreteUnit.getLength(); |
331 | 8.44k | } |
332 | | |
333 | | std::unique_ptr<SvtGraphicStroke> VclMetafileProcessor2D::impTryToCreateSvtGraphicStroke( |
334 | | const basegfx::B2DPolygon& rB2DPolygon, const basegfx::BColor* pColor, |
335 | | const attribute::LineAttribute* pLineAttribute, |
336 | | const attribute::StrokeAttribute* pStrokeAttribute, |
337 | | const attribute::LineStartEndAttribute* pStart, const attribute::LineStartEndAttribute* pEnd) |
338 | 4.31k | { |
339 | 4.31k | std::unique_ptr<SvtGraphicStroke> pRetval; |
340 | | |
341 | 4.31k | if (rB2DPolygon.count() && !mnSvtGraphicStrokeCount) |
342 | 4.31k | { |
343 | 4.31k | basegfx::B2DPolygon aLocalPolygon(rB2DPolygon); |
344 | 4.31k | basegfx::BColor aStrokeColor; |
345 | 4.31k | basegfx::B2DPolyPolygon aStartArrow; |
346 | 4.31k | basegfx::B2DPolyPolygon aEndArrow; |
347 | | |
348 | 4.31k | if (pColor) |
349 | 0 | { |
350 | 0 | aStrokeColor = *pColor; |
351 | 0 | } |
352 | 4.31k | else if (pLineAttribute) |
353 | 4.31k | { |
354 | 4.31k | aStrokeColor = maBColorModifierStack.getModifiedColor(pLineAttribute->getColor()); |
355 | 4.31k | } |
356 | | |
357 | | // It IS needed to record the stroke color at all in the metafile, |
358 | | // SvtGraphicStroke has NO entry for stroke color(!) |
359 | 4.31k | mpOutputDevice->SetLineColor(Color(aStrokeColor)); |
360 | | |
361 | 4.31k | if (!aLocalPolygon.isClosed()) |
362 | 4.31k | { |
363 | 4.31k | const bool bCalcStart = pStart && pStart->isActive(); |
364 | 4.31k | const bool bCalcEnd = pEnd && pEnd->isActive(); |
365 | 4.31k | if (bCalcStart || bCalcEnd) |
366 | 0 | { |
367 | 0 | double fPolyLength = basegfx::utils::getLength(aLocalPolygon); |
368 | 0 | double fStart(0.0); |
369 | 0 | double fEnd(0.0); |
370 | |
|
371 | 0 | if (bCalcStart) |
372 | 0 | { |
373 | 0 | aStartArrow = basegfx::utils::createAreaGeometryForLineStartEnd( |
374 | 0 | aLocalPolygon, pStart->getB2DPolyPolygon(), true, pStart->getWidth(), |
375 | 0 | fPolyLength, pStart->isCentered() ? 0.5 : 0.0, &fStart); |
376 | 0 | } |
377 | |
|
378 | 0 | if (bCalcEnd) |
379 | 0 | { |
380 | 0 | aEndArrow = basegfx::utils::createAreaGeometryForLineStartEnd( |
381 | 0 | aLocalPolygon, pEnd->getB2DPolyPolygon(), false, pEnd->getWidth(), |
382 | 0 | fPolyLength, pEnd->isCentered() ? 0.5 : 0.0, &fEnd); |
383 | 0 | } |
384 | |
|
385 | 0 | if (0.0 != fStart || 0.0 != fEnd) |
386 | 0 | { |
387 | | // build new poly, consume something from old poly |
388 | 0 | aLocalPolygon = basegfx::utils::getSnippetAbsolute( |
389 | 0 | aLocalPolygon, fStart, fPolyLength - fEnd, fPolyLength); |
390 | 0 | } |
391 | 0 | } |
392 | 4.31k | } |
393 | | |
394 | 4.31k | SvtGraphicStroke::JoinType eJoin(SvtGraphicStroke::joinNone); |
395 | 4.31k | SvtGraphicStroke::CapType eCap(SvtGraphicStroke::capButt); |
396 | 4.31k | double fLineWidth(0.0); |
397 | 4.31k | double fMiterLength(0.0); |
398 | 4.31k | SvtGraphicStroke::DashArray aDashArray; |
399 | | |
400 | 4.31k | if (pLineAttribute) |
401 | 4.31k | { |
402 | 4.31k | fLineWidth = fMiterLength = getTransformedLineWidth(pLineAttribute->getWidth()); |
403 | | |
404 | | // get Join |
405 | 4.31k | switch (pLineAttribute->getLineJoin()) |
406 | 4.31k | { |
407 | 0 | case basegfx::B2DLineJoin::NONE: |
408 | 0 | { |
409 | 0 | eJoin = SvtGraphicStroke::joinNone; |
410 | 0 | break; |
411 | 0 | } |
412 | 0 | case basegfx::B2DLineJoin::Bevel: |
413 | 0 | { |
414 | 0 | eJoin = SvtGraphicStroke::joinBevel; |
415 | 0 | break; |
416 | 0 | } |
417 | 3 | case basegfx::B2DLineJoin::Miter: |
418 | 3 | { |
419 | 3 | eJoin = SvtGraphicStroke::joinMiter; |
420 | | // ATM 15 degrees is assumed |
421 | | // TODO wait for P1383R0 and C++20's std::numbers::pi |
422 | 3 | fMiterLength /= std::sin(M_PI / 12); |
423 | 3 | break; |
424 | 0 | } |
425 | 4.31k | case basegfx::B2DLineJoin::Round: |
426 | 4.31k | { |
427 | 4.31k | eJoin = SvtGraphicStroke::joinRound; |
428 | 4.31k | break; |
429 | 0 | } |
430 | 4.31k | } |
431 | | |
432 | | // get stroke |
433 | 4.31k | switch (pLineAttribute->getLineCap()) |
434 | 4.31k | { |
435 | 4.31k | default: /* css::drawing::LineCap_BUTT */ |
436 | 4.31k | { |
437 | 4.31k | eCap = SvtGraphicStroke::capButt; |
438 | 4.31k | break; |
439 | 0 | } |
440 | 1 | case css::drawing::LineCap_ROUND: |
441 | 1 | { |
442 | 1 | eCap = SvtGraphicStroke::capRound; |
443 | 1 | break; |
444 | 0 | } |
445 | 0 | case css::drawing::LineCap_SQUARE: |
446 | 0 | { |
447 | 0 | eCap = SvtGraphicStroke::capSquare; |
448 | 0 | break; |
449 | 0 | } |
450 | 4.31k | } |
451 | 4.31k | } |
452 | | |
453 | 4.31k | if (pStrokeAttribute) |
454 | 4.31k | { |
455 | | // copy dash array |
456 | 4.31k | aDashArray = pStrokeAttribute->getDotDashArray(); |
457 | 4.31k | } |
458 | | |
459 | | // #i101734# apply current object transformation to created geometry. |
460 | | // This is a partial fix. When an object transformation is used which |
461 | | // e.g. contains a scaleX != scaleY, an unproportional scaling would |
462 | | // have to be applied to the evtl. existing fat line. The current |
463 | | // concept of PDF export and SvtGraphicStroke usage does simply not |
464 | | // allow handling such definitions. The only clean way would be to |
465 | | // add the transformation to SvtGraphicStroke and to handle it there |
466 | 4.31k | aLocalPolygon.transform(maCurrentTransformation); |
467 | 4.31k | aStartArrow.transform(maCurrentTransformation); |
468 | 4.31k | aEndArrow.transform(maCurrentTransformation); |
469 | | |
470 | 4.31k | pRetval.reset( |
471 | 4.31k | new SvtGraphicStroke(tools::Polygon(aLocalPolygon), tools::PolyPolygon(aStartArrow), |
472 | 4.31k | tools::PolyPolygon(aEndArrow), mfCurrentUnifiedTransparence, |
473 | 4.31k | fLineWidth, eCap, eJoin, fMiterLength, std::move(aDashArray))); |
474 | 4.31k | } |
475 | | |
476 | 4.31k | return pRetval; |
477 | 4.31k | } |
478 | | |
479 | | void VclMetafileProcessor2D::impStartSvtGraphicStroke(SvtGraphicStroke const* pSvtGraphicStroke) |
480 | 4.31k | { |
481 | 4.31k | if (pSvtGraphicStroke && !mnSvtGraphicStrokeCount) |
482 | 4.31k | { |
483 | 4.31k | SvMemoryStream aMemStm; |
484 | | |
485 | 4.31k | WriteSvtGraphicStroke(aMemStm, *pSvtGraphicStroke); |
486 | 4.31k | mpMetaFile->AddAction(new MetaCommentAction( |
487 | 4.31k | "XPATHSTROKE_SEQ_BEGIN"_ostr, 0, static_cast<const sal_uInt8*>(aMemStm.GetData()), |
488 | 4.31k | aMemStm.TellEnd())); |
489 | 4.31k | mnSvtGraphicStrokeCount++; |
490 | 4.31k | } |
491 | 4.31k | } |
492 | | |
493 | | void VclMetafileProcessor2D::impEndSvtGraphicStroke(SvtGraphicStroke const* pSvtGraphicStroke) |
494 | 4.31k | { |
495 | 4.31k | if (pSvtGraphicStroke && mnSvtGraphicStrokeCount) |
496 | 4.31k | { |
497 | 4.31k | mnSvtGraphicStrokeCount--; |
498 | 4.31k | mpMetaFile->AddAction(new MetaCommentAction("XPATHSTROKE_SEQ_END"_ostr)); |
499 | 4.31k | } |
500 | 4.31k | } |
501 | | |
502 | | void VclMetafileProcessor2D::popStructureElement(vcl::pdf::StructElement eElem) |
503 | 0 | { |
504 | 0 | if (!maListElements.empty() && maListElements.top() == eElem) |
505 | 0 | { |
506 | 0 | maListElements.pop(); |
507 | 0 | mpPDFExtOutDevData->EndStructureElement(); |
508 | 0 | } |
509 | 0 | } |
510 | | |
511 | | void VclMetafileProcessor2D::popListItem() |
512 | 0 | { |
513 | 0 | popStructureElement(vcl::pdf::StructElement::LIBody); |
514 | 0 | popStructureElement(vcl::pdf::StructElement::ListItem); |
515 | 0 | } |
516 | | |
517 | | void VclMetafileProcessor2D::popList() |
518 | 0 | { |
519 | 0 | popListItem(); |
520 | 0 | popStructureElement(vcl::pdf::StructElement::List); |
521 | 0 | } |
522 | | |
523 | | VclMetafileProcessor2D::VclMetafileProcessor2D(const geometry::ViewInformation2D& rViewInformation, |
524 | | OutputDevice& rOutDev) |
525 | 56.9k | : VclProcessor2D(rViewInformation, rOutDev) |
526 | 56.9k | , mpMetaFile(rOutDev.GetConnectMetaFile()) |
527 | 56.9k | , mnSvtGraphicFillCount(0) |
528 | 56.9k | , mnSvtGraphicStrokeCount(0) |
529 | 56.9k | , mfCurrentUnifiedTransparence(0.0) |
530 | 56.9k | , mpPDFExtOutDevData(dynamic_cast<vcl::PDFExtOutDevData*>(rOutDev.GetExtOutDevData())) |
531 | 56.9k | , mnCurrentOutlineLevel(-1) |
532 | 56.9k | , mbInListItem(false) |
533 | 56.9k | , mbBulletPresent(false) |
534 | 56.9k | { |
535 | 56.9k | OSL_ENSURE(rOutDev.GetConnectMetaFile(), |
536 | 56.9k | "VclMetafileProcessor2D: Used on OutDev which has no MetaFile Target (!)"); |
537 | | // draw to logic coordinates, do not initialize maCurrentTransformation to viewTransformation |
538 | | // but only to ObjectTransformation. Do not change MapMode of destination. |
539 | 56.9k | maCurrentTransformation = rViewInformation.getObjectTransformation(); |
540 | 56.9k | } |
541 | | |
542 | | VclMetafileProcessor2D::~VclMetafileProcessor2D() |
543 | 56.9k | { |
544 | | // MapMode was not changed, no restore necessary |
545 | 56.9k | } |
546 | | |
547 | | /*********************************************************************************************** |
548 | | |
549 | | Support of MetaCommentActions in the VclMetafileProcessor2D |
550 | | Found MetaCommentActions and how they are supported: |
551 | | |
552 | | XGRAD_SEQ_BEGIN, XGRAD_SEQ_END: |
553 | | |
554 | | Used inside OutputDevice::DrawGradient to mark the start and end of a MetaGradientEx action. |
555 | | It is used in various exporters/importers to have direct access to the gradient before it |
556 | | is rendered by VCL (and thus fragmented to polygon color actions and others). On that base, e.g. |
557 | | the Metafile to SdrObject import creates its gradient objects. |
558 | | Best (and safest) way to support it here is to use PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D, |
559 | | map it back to the corresponding tools tools::PolyPolygon and the Gradient and just call |
560 | | OutputDevice::DrawGradient which creates the necessary compatible actions. |
561 | | |
562 | | XPATHFILL_SEQ_BEGIN, XPATHFILL_SEQ_END: |
563 | | |
564 | | Two producers, one is vcl/source/gdi/gdimtf.cxx, line 1273. There, it is transformed |
565 | | inside GDIMetaFile::Rotate, nothing to take care of here. |
566 | | The second producer is in graphics/svx/source/svdraw/impgrfll.cxx, line 374. This is used |
567 | | with each incarnation of Imp_GraphicFill when a metafile is recorded, fillstyle is not |
568 | | XFILL_NONE and not completely transparent. It creates a SvtGraphicFill and streams it |
569 | | to the comment action. A closing end token is created in the destructor. |
570 | | Usages of Imp_GraphicFill are in Do_Paint_Object-methods of SdrCircObj, SdrPathObj and |
571 | | SdrRectObj. |
572 | | The token users pick various actions from SvtGraphicFill, so it may need to be added for all kind |
573 | | of filled objects, even simple colored polygons. It is added as extra information; the |
574 | | Metafile actions between the two tokens are interpreted as output generated from those |
575 | | fills. Thus, users have the choice to use the SvtGraphicFill info or the created output |
576 | | actions. |
577 | | Even for XFillTransparenceItem it is used, thus it may need to be supported in |
578 | | UnifiedTransparencePrimitive2D, too, when interpreted as normally filled PolyPolygon. |
579 | | Implemented for: |
580 | | PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D, |
581 | | PRIMITIVE2D_ID_POLYPOLYGONHATCHPRIMITIVE2D, |
582 | | PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D, |
583 | | PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D, |
584 | | and for PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D when detected unified transparence |
585 | | |
586 | | XPATHSTROKE_SEQ_BEGIN, XPATHSTROKE_SEQ_END: |
587 | | |
588 | | Similar to pathfill, but using SvtGraphicStroke instead. It also has two producers where one |
589 | | is also the GDIMetaFile::Rotate. Another user is MetaCommentAction::Move which modifies the |
590 | | contained path accordingly. |
591 | | The other one is SdrObject::Imp_DrawLineGeometry. It's done when MetaFile is set at OutDev and |
592 | | only when geometry is a single polygon (!). I see no reason for that; in the PS exporter this |
593 | | would hinder to make use of tools::PolyPolygon strokes. I will need to add support at: |
594 | | PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D |
595 | | PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D |
596 | | PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D |
597 | | This can be done hierarchical, too. |
598 | | Okay, base implementation done based on those three primitives. |
599 | | |
600 | | FIELD_SEQ_BEGIN, FIELD_SEQ_END |
601 | | |
602 | | Used from slideshow for URLs, created from diverse SvxField implementations inside |
603 | | createBeginComment()/createEndComment(). createBeginComment() is used from editeng\impedit3.cxx |
604 | | inside ImpEditEngine::Paint. |
605 | | Created TextHierarchyFieldPrimitive2D and added needed infos there; it is a group primitive and wraps |
606 | | text primitives (but is not limited to that). It contains the field type if special actions for the |
607 | | support of FIELD_SEQ_BEGIN/END are needed; this is the case for Page and URL fields. If more is |
608 | | needed, it may be supported there. |
609 | | FIELD_SEQ_BEGIN;PageField |
610 | | FIELD_SEQ_END |
611 | | Okay, these are now completely supported by TextHierarchyFieldPrimitive2D. URL works, too. |
612 | | |
613 | | XTEXT |
614 | | |
615 | | XTEXT_EOC(i) end of character |
616 | | XTEXT_EOW(i) end of word |
617 | | XTEXT_EOS(i) end of sentence |
618 | | |
619 | | this three are with index and are created with the help of an i18n::XBreakIterator in |
620 | | ImplDrawWithComments. Simplifying, moving out text painting, reworking to create some |
621 | | data structure for holding those TEXT infos. |
622 | | Supported directly by TextSimplePortionPrimitive2D with adding a Locale to the basic text |
623 | | primitive. In the MetaFileRenderer, the creation is now done (see below). This has the advantage |
624 | | that this creations do not need to be done for all paints all the time. This would be |
625 | | expensive since the BreakIterator and it's usage is expensive and for each paint also the |
626 | | whole character stops would need to be created. |
627 | | Created only for TextDecoratedPortionPrimitive2D due to XTEXT_EOL and XTEXT_EOP (see below) |
628 | | |
629 | | XTEXT_EOL() end of line |
630 | | XTEXT_EOP() end of paragraph |
631 | | |
632 | | First try with boolean marks at TextDecoratedPortionPrimitive2D did not work too well, |
633 | | i decided to solve it with structure. I added the TextHierarchyPrimitives for this, |
634 | | namely: |
635 | | - TextHierarchyLinePrimitive2D: Encapsulates single line |
636 | | - TextHierarchyParagraphPrimitive2D: Encapsulates single paragraph |
637 | | - TextHierarchyBlockPrimitive2D: encapsulates object texts (only one ATM) |
638 | | Those are now supported in hierarchy. This means the MetaFile renderer will support them |
639 | | by using them, recursively using their content and adding MetaFile comments as needed. |
640 | | This also means that when another text layouter will be used it will be necessary to |
641 | | create/support the same HierarchyPrimitives to support users. |
642 | | To transport the information using this hierarchy is best suited to all future needs; |
643 | | the slideshow will be able to profit from it directly when using primitives; all other |
644 | | renderers not interested in the text structure will just ignore the encapsulations. |
645 | | |
646 | | XTEXT_PAINTSHAPE_BEGIN, XTEXT_PAINTSHAPE_END |
647 | | Supported now by the TextHierarchyBlockPrimitive2D. |
648 | | |
649 | | EPSReplacementGraphic: |
650 | | Only used in goodies\source\filter.vcl\ieps\ieps.cxx and svx\source\xml\xmlgrhlp.cxx to |
651 | | hold the original EPS which was imported in the same MetaFile as first 2 entries. Only |
652 | | used to export the original again (if exists). |
653 | | Not necessary to support with MetaFileRenderer. |
654 | | |
655 | | XTEXT_SCROLLRECT, XTEXT_PAINTRECT |
656 | | Currently used to get extra MetaFile infos using GraphicExporter which again uses |
657 | | SdrTextObj::GetTextScrollMetaFileAndRectangle(). ATM works with primitives since |
658 | | the rectangle data is added directly by the GraphicsExporter as comment. Does not need |
659 | | to be adapted at once. |
660 | | When adapting later, the only user - the diashow - should directly use the provided |
661 | | Animation infos in the appropriate primitives (e.g. AnimatedSwitchPrimitive2D) |
662 | | |
663 | | PRNSPOOL_TRANSPARENTBITMAP_BEGIN, PRNSPOOL_TRANSPARENTBITMAP_END |
664 | | VCL usage when printing PL -> THB. Okay, THB confirms that it is only used as |
665 | | a fix (hack) while VCL printing. It is needed to not downscale a bitmap which |
666 | | was explicitly created for the printer already again to some default maximum |
667 | | bitmap sizes. |
668 | | Nothing to do here for the primitive renderer. |
669 | | |
670 | | Support for vcl::PDFExtOutDevData: |
671 | | PL knows that SJ did that stuff, it's used to hold a pointer to PDFExtOutDevData at |
672 | | the OutDev. When set, some extra data is written there. Trying simple PDF export and |
673 | | watching if I get those infos. |
674 | | Well, a PDF export does not use e.g. ImpEditEngine::Paint since the PdfFilter uses |
675 | | the SdXImpressDocument::render and thus uses the VclMetafileProcessor2D. I will check |
676 | | if I get a PDFExtOutDevData at the target output device. |
677 | | Indeed, I get one. Checking what all may be done when that extra-device-info is there. |
678 | | |
679 | | All in all I have to talk to SJ. I will need to emulate some of those actions, but |
680 | | i need to discuss which ones. |
681 | | In the future, all those infos would be taken from the primitive sequence anyways, |
682 | | thus these extensions would potentially be temporary, too. |
683 | | Discussed with SJ, added the necessary support and tested it. Details follow. |
684 | | |
685 | | - In ImpEditEngine::Paint, paragraph infos and URL stuff is added. |
686 | | Added in primitive MetaFile renderer. |
687 | | Checking URL: Indeed, current version exports it, but it is missing in primitive |
688 | | CWS version. Adding support. |
689 | | Okay, URLs work. Checked, Done. |
690 | | |
691 | | - UnoControlPDFExportContact is only created when PDFExtOutDevData is used at the |
692 | | target and uno control data is created in UnoControlPDFExportContact::do_PaintObject. |
693 | | This was added in primitive MetaFile renderer. |
694 | | Checked form control export, it works well. Done. |
695 | | |
696 | | - In goodies, in GraphicObject::Draw, when the used Graphic is linked, infos are |
697 | | generated. I will need to check what happens here with primitives. |
698 | | To support, use of GraphicPrimitive2D (PRIMITIVE2D_ID_GRAPHICPRIMITIVE2D) may be needed. |
699 | | Added support, but feature is broken in main version, so i cannot test at all. |
700 | | Writing a bug to CL (or SJ) and seeing what happens (#i80380#). |
701 | | SJ took a look and we got it working. Tested VCL MetaFile Renderer based export, |
702 | | as intended, the original file is exported. Works, Done. |
703 | | |
704 | | |
705 | | To be done: |
706 | | |
707 | | - Maybe there are more places to take care of for vcl::PDFExtOutDevData! |
708 | | |
709 | | |
710 | | ****************************************************************************************************/ |
711 | | |
712 | | void VclMetafileProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) |
713 | 335k | { |
714 | 335k | switch (rCandidate.getPrimitive2DID()) |
715 | 335k | { |
716 | 0 | case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D: |
717 | 0 | { |
718 | | // directdraw of wrong spell primitive |
719 | | // Ignore for VclMetafileProcessor2D, this is for printing and MetaFile recording only |
720 | 0 | break; |
721 | 0 | } |
722 | 97 | case PRIMITIVE2D_ID_GRAPHICPRIMITIVE2D: |
723 | 97 | { |
724 | 97 | processGraphicPrimitive2D( |
725 | 97 | static_cast<const primitive2d::GraphicPrimitive2D&>(rCandidate)); |
726 | 97 | break; |
727 | 0 | } |
728 | 0 | case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D: |
729 | 0 | { |
730 | 0 | processControlPrimitive2D( |
731 | 0 | static_cast<const primitive2d::ControlPrimitive2D&>(rCandidate)); |
732 | 0 | break; |
733 | 0 | } |
734 | 0 | case PRIMITIVE2D_ID_TEXTHIERARCHYFIELDPRIMITIVE2D: |
735 | 0 | { |
736 | 0 | processTextHierarchyFieldPrimitive2D( |
737 | 0 | static_cast<const primitive2d::TextHierarchyFieldPrimitive2D&>(rCandidate)); |
738 | 0 | break; |
739 | 0 | } |
740 | 764 | case PRIMITIVE2D_ID_TEXTHIERARCHYLINEPRIMITIVE2D: |
741 | 764 | { |
742 | 764 | processTextHierarchyLinePrimitive2D( |
743 | 764 | static_cast<const primitive2d::TextHierarchyLinePrimitive2D&>(rCandidate)); |
744 | 764 | break; |
745 | 0 | } |
746 | 0 | case PRIMITIVE2D_ID_TEXTHIERARCHYBULLETPRIMITIVE2D: |
747 | 0 | { |
748 | 0 | processTextHierarchyBulletPrimitive2D( |
749 | 0 | static_cast<const primitive2d::TextHierarchyBulletPrimitive2D&>(rCandidate)); |
750 | 0 | break; |
751 | 0 | } |
752 | 359 | case PRIMITIVE2D_ID_TEXTHIERARCHYPARAGRAPHPRIMITIVE2D: |
753 | 359 | { |
754 | 359 | processTextHierarchyParagraphPrimitive2D( |
755 | 359 | static_cast<const primitive2d::TextHierarchyParagraphPrimitive2D&>(rCandidate)); |
756 | 359 | break; |
757 | 0 | } |
758 | 349 | case PRIMITIVE2D_ID_TEXTHIERARCHYBLOCKPRIMITIVE2D: |
759 | 349 | { |
760 | 349 | processTextHierarchyBlockPrimitive2D( |
761 | 349 | static_cast<const primitive2d::TextHierarchyBlockPrimitive2D&>(rCandidate)); |
762 | 349 | break; |
763 | 0 | } |
764 | 771 | case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D: |
765 | 771 | case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D: |
766 | 771 | { |
767 | | // for supporting TEXT_ MetaFile actions there is more to do here; get the candidate |
768 | 771 | processTextSimplePortionPrimitive2D( |
769 | 771 | static_cast<const primitive2d::TextSimplePortionPrimitive2D&>(rCandidate)); |
770 | 771 | break; |
771 | 771 | } |
772 | 197 | case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D: |
773 | 197 | { |
774 | 197 | processPolygonHairlinePrimitive2D( |
775 | 197 | static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate)); |
776 | 197 | break; |
777 | 771 | } |
778 | 4.31k | case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D: |
779 | 4.31k | { |
780 | 4.31k | processPolygonStrokePrimitive2D( |
781 | 4.31k | static_cast<const primitive2d::PolygonStrokePrimitive2D&>(rCandidate)); |
782 | 4.31k | break; |
783 | 771 | } |
784 | 0 | case PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D: |
785 | 0 | { |
786 | 0 | processPolygonStrokeArrowPrimitive2D( |
787 | 0 | static_cast<const primitive2d::PolygonStrokeArrowPrimitive2D&>(rCandidate)); |
788 | 0 | break; |
789 | 771 | } |
790 | 0 | case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D: |
791 | 0 | { |
792 | | // direct draw of transformed Bitmap primitive; use default processing, but without |
793 | | // former testing if graphic content is inside discrete local viewport; this is not |
794 | | // setup for metafile targets (metafile renderer tries to render in logic coordinates, |
795 | | // the mapping is kept to the OutputDevice for better Metafile recording) |
796 | 0 | RenderBitmapPrimitive2D(static_cast<const primitive2d::BitmapPrimitive2D&>(rCandidate)); |
797 | 0 | break; |
798 | 771 | } |
799 | 0 | case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D: |
800 | 0 | { |
801 | 0 | if (maBColorModifierStack.count()) |
802 | 0 | { |
803 | | // tdf#151104 unfortunately processPolyPolygonGraphicPrimitive2D below |
804 | | // does not support an active BColorModifierStack, so use the default |
805 | 0 | process(rCandidate); |
806 | 0 | } |
807 | 0 | else |
808 | 0 | { |
809 | 0 | processPolyPolygonGraphicPrimitive2D( |
810 | 0 | static_cast<const primitive2d::PolyPolygonGraphicPrimitive2D&>(rCandidate)); |
811 | 0 | } |
812 | 0 | break; |
813 | 771 | } |
814 | 0 | case PRIMITIVE2D_ID_POLYPOLYGONHATCHPRIMITIVE2D: |
815 | 0 | { |
816 | 0 | processPolyPolygonHatchPrimitive2D( |
817 | 0 | static_cast<const primitive2d::PolyPolygonHatchPrimitive2D&>(rCandidate)); |
818 | 0 | break; |
819 | 771 | } |
820 | 0 | case PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D: |
821 | 0 | { |
822 | 0 | processPolyPolygonGradientPrimitive2D( |
823 | 0 | static_cast<const primitive2d::PolyPolygonGradientPrimitive2D&>(rCandidate)); |
824 | 0 | break; |
825 | 771 | } |
826 | 469 | case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D: |
827 | 469 | { |
828 | 469 | processPolyPolygonColorPrimitive2D( |
829 | 469 | static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate)); |
830 | 469 | break; |
831 | 771 | } |
832 | 99 | case PRIMITIVE2D_ID_MASKPRIMITIVE2D: |
833 | 99 | { |
834 | 99 | processMaskPrimitive2D(static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate)); |
835 | 99 | break; |
836 | 771 | } |
837 | 0 | case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D: |
838 | 0 | { |
839 | | // modified color group. Force output to unified color. Use default processing. |
840 | 0 | RenderModifiedColorPrimitive2D( |
841 | 0 | static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate)); |
842 | 0 | break; |
843 | 771 | } |
844 | 0 | case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D: |
845 | 0 | { |
846 | 0 | processUnifiedTransparencePrimitive2D( |
847 | 0 | static_cast<const primitive2d::UnifiedTransparencePrimitive2D&>(rCandidate)); |
848 | 0 | break; |
849 | 771 | } |
850 | 0 | case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D: |
851 | 0 | { |
852 | 0 | processTransparencePrimitive2D( |
853 | 0 | static_cast<const primitive2d::TransparencePrimitive2D&>(rCandidate)); |
854 | 0 | break; |
855 | 771 | } |
856 | 153 | case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D: |
857 | 153 | { |
858 | | // use default transform group processing |
859 | 153 | RenderTransformPrimitive2D( |
860 | 153 | static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate)); |
861 | 153 | break; |
862 | 771 | } |
863 | 0 | case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D: |
864 | 0 | { |
865 | | // new XDrawPage for ViewInformation2D |
866 | 0 | RenderPagePreviewPrimitive2D( |
867 | 0 | static_cast<const primitive2d::PagePreviewPrimitive2D&>(rCandidate)); |
868 | 0 | break; |
869 | 771 | } |
870 | 0 | case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D: |
871 | 0 | { |
872 | | // use default marker array processing |
873 | 0 | RenderMarkerArrayPrimitive2D( |
874 | 0 | static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate)); |
875 | 0 | break; |
876 | 771 | } |
877 | 0 | case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D: |
878 | 0 | { |
879 | | // use default point array processing |
880 | 0 | RenderPointArrayPrimitive2D( |
881 | 0 | static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate)); |
882 | 0 | break; |
883 | 771 | } |
884 | 0 | case PRIMITIVE2D_ID_STRUCTURETAGPRIMITIVE2D: |
885 | 0 | { |
886 | 0 | processStructureTagPrimitive2D( |
887 | 0 | static_cast<const primitive2d::StructureTagPrimitive2D&>(rCandidate)); |
888 | 0 | break; |
889 | 771 | } |
890 | 0 | case PRIMITIVE2D_ID_EPSPRIMITIVE2D: |
891 | 0 | { |
892 | 0 | RenderEpsPrimitive2D(static_cast<const primitive2d::EpsPrimitive2D&>(rCandidate)); |
893 | 0 | break; |
894 | 771 | } |
895 | 906 | case PRIMITIVE2D_ID_OBJECTINFOPRIMITIVE2D: |
896 | 906 | { |
897 | 906 | processObjectInfoPrimitive2D( |
898 | 906 | static_cast<const primitive2d::ObjectInfoPrimitive2D&>(rCandidate)); |
899 | 906 | break; |
900 | 771 | } |
901 | 0 | case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D: |
902 | 0 | { |
903 | 0 | processFillGraphicPrimitive2D( |
904 | 0 | static_cast<const primitive2d::FillGraphicPrimitive2D&>(rCandidate)); |
905 | 0 | break; |
906 | 771 | } |
907 | 0 | case PRIMITIVE2D_ID_TEXTHIERARCHYEMPHASISMARKPRIMITIVE2D: |
908 | 0 | { |
909 | | // EmphasisMarks are traditionally not added to Metafiles, see |
910 | | // OutputDevice::ImplDrawEmphasisMarks which resets GDIMetaFile* |
911 | | // while painting these, so just ignore these |
912 | 0 | break; |
913 | 771 | } |
914 | 326k | default: |
915 | 326k | { |
916 | | // process recursively |
917 | 326k | process(rCandidate); |
918 | 326k | break; |
919 | 771 | } |
920 | 335k | } |
921 | 335k | } |
922 | | |
923 | | void VclMetafileProcessor2D::processObjectInfoPrimitive2D( |
924 | | primitive2d::ObjectInfoPrimitive2D const& rObjectInfoPrimitive2D) |
925 | 906 | { |
926 | | // tdf#154982 process content first, so this object overrides any nested one |
927 | 906 | process(rObjectInfoPrimitive2D.getChildren()); |
928 | | |
929 | | // currently StructureTagPrimitive2D is only used for SdrObjects - have to |
930 | | // avoid adding Alt text if the SdrObject is not actually tagged, as it |
931 | | // would then end up on an unrelated structure element. |
932 | 906 | if (mpCurrentStructureTag && mpCurrentStructureTag->isTaggedSdrObject()) |
933 | 0 | { |
934 | | // Create image alternative description from ObjectInfoPrimitive2D info |
935 | | // for PDF export, for the currently active SdrObject's structure element |
936 | 0 | if (mpPDFExtOutDevData->GetIsExportTaggedPDF()) |
937 | 0 | { |
938 | 0 | OUString aAlternateDescription; |
939 | |
|
940 | 0 | if (!rObjectInfoPrimitive2D.getTitle().isEmpty()) |
941 | 0 | { |
942 | 0 | aAlternateDescription += rObjectInfoPrimitive2D.getTitle(); |
943 | 0 | } |
944 | |
|
945 | 0 | if (!rObjectInfoPrimitive2D.getDesc().isEmpty()) |
946 | 0 | { |
947 | 0 | if (!aAlternateDescription.isEmpty()) |
948 | 0 | { |
949 | 0 | aAlternateDescription += " - "; |
950 | 0 | } |
951 | |
|
952 | 0 | aAlternateDescription += rObjectInfoPrimitive2D.getDesc(); |
953 | 0 | } |
954 | | |
955 | | // Use SetAlternateText to set it. This will work as long as some |
956 | | // structure is used (see PDFWriterImpl::setAlternateText and |
957 | | // m_nCurrentStructElement - tagged PDF export works with this in |
958 | | // Draw/Impress/Writer, but not in Calc due to too less structure...) |
959 | | //Z maybe add structure to Calc PDF export, may need some BeginGroup/EndGroup stuff ..? |
960 | 0 | if (!aAlternateDescription.isEmpty()) |
961 | 0 | { |
962 | 0 | mpPDFExtOutDevData->SetAlternateText(aAlternateDescription); |
963 | 0 | } |
964 | 0 | } |
965 | 0 | } |
966 | 906 | } |
967 | | |
968 | | void VclMetafileProcessor2D::processFillGraphicPrimitive2D( |
969 | | primitive2d::FillGraphicPrimitive2D const& rFillGraphicPrimitive2D) |
970 | 0 | { |
971 | | // tdf#166709 check if we have to make an exception handling this |
972 | | // FillGraphicPrimitive2D. If it |
973 | | // - has transparency |
974 | | // - is tiled |
975 | | // - is a Bitmap |
976 | | // - is not animated |
977 | | // - is no embedded SVG |
978 | | // we have to, see below |
979 | 0 | if (!basegfx::fTools::equalZero(rFillGraphicPrimitive2D.getTransparency(), 0.0)) |
980 | 0 | { |
981 | | // we have transparency |
982 | 0 | const attribute::FillGraphicAttribute& rAttribute(rFillGraphicPrimitive2D.getFillGraphic()); |
983 | |
|
984 | 0 | if (rAttribute.getTiling()) |
985 | 0 | { |
986 | | // we have tiling |
987 | 0 | const Graphic& rGraphic(rAttribute.getGraphic()); |
988 | |
|
989 | 0 | if (GraphicType::Bitmap == rGraphic.GetType() && !rGraphic.IsAnimated() |
990 | 0 | && !rGraphic.getVectorGraphicData()) |
991 | 0 | { |
992 | | // tdf#166709 it is a Bitmap, not animated & not |
993 | | // embedded SVG. |
994 | | |
995 | | // conditions are met. Unfortunately for metafile |
996 | | // and for PDF export we *need* all tiles which are |
997 | | // potentially created by the decomposition of the |
998 | | // FillGraphicPrimitive2D to be embedded to a single |
999 | | // UnifiedTransparencePrimitive2D that holds that |
1000 | | // transparency - as it was before adding more |
1001 | | // possibilities for direct unified transparency. |
1002 | | |
1003 | | // Despite the decomposition being correct and creating |
1004 | | // now BitmapAlphaPrimitive2D with every one holding the |
1005 | | // correct alpha, the 'old' way with encapsulating to |
1006 | | // a UnifiedTransparencePrimitive2D is needed here |
1007 | | // to create a single bitmap representation that then |
1008 | | // gets used. When not doing this a potentially high |
1009 | | // number of BitmapAlphaPrimitive2D will be exported, |
1010 | | // which is not an error but needs too much resources, |
1011 | | // prevents loading of the created PDF for some viewers |
1012 | | // and bloats the PDF file. |
1013 | | |
1014 | | // NOTE: I thought if only doing this for the PDF export |
1015 | | // case would make sense, but all exports still based |
1016 | | // on metafile potentially have this problem, so better |
1017 | | // do it in general at metafile creation already. |
1018 | | |
1019 | | // NOTE: This shows how urgent it would be to create a |
1020 | | // PDF export using a PrimitiveRenderer instead of |
1021 | | // Metafile - that could do the right thing and use |
1022 | | // a representation in the PDF that is capable of |
1023 | | // tiling. No chance to do that with the existing |
1024 | | // metafile stuff we have. |
1025 | | |
1026 | | // NOTE: The creation of the possible MetafileAction |
1027 | | // for this is done here locally in method |
1028 | | // processUnifiedTransparencePrimitive2D. Use that |
1029 | | // directly if necessary. |
1030 | | |
1031 | | // So: create a FillGraphicPrimitive2D without transparency |
1032 | | // embedded to a UnifiedTransparencePrimitive2D representing |
1033 | | // the transparency and process it directly |
1034 | 0 | rtl::Reference<primitive2d::BasePrimitive2D> aPrimitive( |
1035 | 0 | new primitive2d::UnifiedTransparencePrimitive2D( |
1036 | 0 | primitive2d::Primitive2DContainer{ |
1037 | 0 | rtl::Reference<primitive2d::FillGraphicPrimitive2D>( |
1038 | 0 | new primitive2d::FillGraphicPrimitive2D( |
1039 | 0 | rFillGraphicPrimitive2D.getTransformation(), |
1040 | 0 | attribute::FillGraphicAttribute( |
1041 | 0 | rGraphic, rAttribute.getGraphicRange(), |
1042 | 0 | rAttribute.getTiling(), rAttribute.getOffsetX(), |
1043 | 0 | rAttribute.getOffsetY()))) }, |
1044 | 0 | rFillGraphicPrimitive2D.getTransparency())); |
1045 | | |
1046 | | // tdf#166709 see comments 21-23, we have two possibilities here: |
1047 | | // (a) use UnifiedTransparencePrimitive2D: test PDF is 47.5kb |
1048 | | // (b) use TransparencePrimitive2D: test PDF is 30.8 kb |
1049 | | // differences are described in the task. Due to (b) being smaller |
1050 | | // and is better re-loadable I opt for that. To be able to change |
1051 | | // this easily I let both versions stand here |
1052 | 0 | static bool bTransparencePrimitive2DUse(true); |
1053 | |
|
1054 | 0 | if (bTransparencePrimitive2DUse) |
1055 | 0 | { |
1056 | | // process recursively. Since this first gets the decomposition |
1057 | | // (else a primitive processor would loop recursively) this will |
1058 | | // use TransparencePrimitive2D, created by |
1059 | | // UnifiedTransparencePrimitive2D::get2DDecomposition. This could |
1060 | | // also be done here, but that decompose already has needed stuff |
1061 | | // and we keep it in one place |
1062 | 0 | process(*aPrimitive); |
1063 | 0 | } |
1064 | 0 | else |
1065 | 0 | { |
1066 | | // process UnifiedTransparencePrimitive2D primitive directly |
1067 | 0 | processUnifiedTransparencePrimitive2D( |
1068 | 0 | static_cast<const primitive2d::UnifiedTransparencePrimitive2D&>( |
1069 | 0 | *aPrimitive)); |
1070 | 0 | } |
1071 | | |
1072 | | // we are done, return |
1073 | 0 | return; |
1074 | 0 | } |
1075 | 0 | } |
1076 | 0 | } |
1077 | | |
1078 | | // all other cases: process recursively with original primitive |
1079 | 0 | process(rFillGraphicPrimitive2D); |
1080 | 0 | } |
1081 | | |
1082 | | void VclMetafileProcessor2D::processGraphicPrimitive2D( |
1083 | | const primitive2d::GraphicPrimitive2D& rGraphicPrimitive) |
1084 | 97 | { |
1085 | 97 | bool bUsingPDFExtOutDevData(false); |
1086 | 97 | basegfx::B2DVector aTranslate, aScale; |
1087 | 97 | static bool bSuppressPDFExtOutDevDataSupport(false); // loplugin:constvars:ignore |
1088 | | |
1089 | 97 | if (mpPDFExtOutDevData && !bSuppressPDFExtOutDevDataSupport) |
1090 | 97 | { |
1091 | | // emulate data handling from UnoControlPDFExportContact, original see |
1092 | | // svtools/source/graphic/grfmgr.cxx |
1093 | 97 | const Graphic& rGraphic = rGraphicPrimitive.getGraphicObject().GetGraphic(); |
1094 | | |
1095 | 97 | if (rGraphic.IsGfxLink()) |
1096 | 4 | { |
1097 | 4 | const GraphicAttr& rAttr = rGraphicPrimitive.getGraphicAttr(); |
1098 | | |
1099 | 4 | if (!rAttr.IsSpecialDrawMode() && !rAttr.IsAdjusted()) |
1100 | 4 | { |
1101 | 4 | const basegfx::B2DHomMatrix& rTransform = rGraphicPrimitive.getTransform(); |
1102 | 4 | double fRotate, fShearX; |
1103 | 4 | rTransform.decompose(aScale, aTranslate, fRotate, fShearX); |
1104 | | |
1105 | 4 | if (basegfx::fTools::equalZero(fRotate) && (aScale.getX() > 0.0) |
1106 | 4 | && (aScale.getY() > 0.0)) |
1107 | 4 | { |
1108 | 4 | bUsingPDFExtOutDevData = true; |
1109 | 4 | mpPDFExtOutDevData->BeginGroup(); |
1110 | 4 | } |
1111 | 4 | } |
1112 | 4 | } |
1113 | 97 | } |
1114 | | |
1115 | | // process recursively and add MetaFile comment |
1116 | 97 | process(rGraphicPrimitive); |
1117 | | |
1118 | 97 | if (!bUsingPDFExtOutDevData) |
1119 | 93 | return; |
1120 | | |
1121 | | // emulate data handling from UnoControlPDFExportContact, original see |
1122 | | // svtools/source/graphic/grfmgr.cxx |
1123 | 4 | const basegfx::B2DRange aCurrentRange(aTranslate.getX(), aTranslate.getY(), |
1124 | 4 | aTranslate.getX() + aScale.getX(), |
1125 | 4 | aTranslate.getY() + aScale.getY()); |
1126 | 4 | const tools::Rectangle aCurrentRect( |
1127 | 4 | sal_Int32(floor(aCurrentRange.getMinX())), sal_Int32(floor(aCurrentRange.getMinY())), |
1128 | 4 | sal_Int32(ceil(aCurrentRange.getMaxX())), sal_Int32(ceil(aCurrentRange.getMaxY()))); |
1129 | 4 | const GraphicAttr& rAttr = rGraphicPrimitive.getGraphicAttr(); |
1130 | | // fdo#72530 don't pass empty Rectangle to EndGroup |
1131 | 4 | tools::Rectangle aCropRect(aCurrentRect); |
1132 | | |
1133 | 4 | if (rAttr.IsCropped()) |
1134 | 0 | { |
1135 | | // calculate scalings between real image size and logic object size. This |
1136 | | // is necessary since the crop values are relative to original bitmap size |
1137 | 0 | double fFactorX(1.0); |
1138 | 0 | double fFactorY(1.0); |
1139 | |
|
1140 | 0 | { |
1141 | 0 | const MapMode aMapMode100thmm(MapUnit::Map100thMM); |
1142 | 0 | const Size aBitmapSize(OutputDevice::LogicToLogic( |
1143 | 0 | rGraphicPrimitive.getGraphicObject().GetPrefSize(), |
1144 | 0 | rGraphicPrimitive.getGraphicObject().GetPrefMapMode(), aMapMode100thmm)); |
1145 | 0 | const double fDivX(aBitmapSize.Width() - rAttr.GetLeftCrop() - rAttr.GetRightCrop()); |
1146 | 0 | const double fDivY(aBitmapSize.Height() - rAttr.GetTopCrop() - rAttr.GetBottomCrop()); |
1147 | |
|
1148 | 0 | if (!basegfx::fTools::equalZero(fDivX)) |
1149 | 0 | { |
1150 | 0 | fFactorX = aScale.getX() / fDivX; |
1151 | 0 | } |
1152 | |
|
1153 | 0 | if (!basegfx::fTools::equalZero(fDivY)) |
1154 | 0 | { |
1155 | 0 | fFactorY = aScale.getY() / fDivY; |
1156 | 0 | } |
1157 | 0 | } |
1158 | | |
1159 | | // calculate crop range and rect |
1160 | 0 | basegfx::B2DRange aCropRange; |
1161 | 0 | aCropRange.expand( |
1162 | 0 | aCurrentRange.getMinimum() |
1163 | 0 | - basegfx::B2DPoint(rAttr.GetLeftCrop() * fFactorX, rAttr.GetTopCrop() * fFactorY)); |
1164 | 0 | aCropRange.expand( |
1165 | 0 | aCurrentRange.getMaximum() |
1166 | 0 | + basegfx::B2DPoint(rAttr.GetRightCrop() * fFactorX, rAttr.GetBottomCrop() * fFactorY)); |
1167 | |
|
1168 | 0 | aCropRect = tools::Rectangle( |
1169 | 0 | sal_Int32(floor(aCropRange.getMinX())), sal_Int32(floor(aCropRange.getMinY())), |
1170 | 0 | sal_Int32(ceil(aCropRange.getMaxX())), sal_Int32(ceil(aCropRange.getMaxY()))); |
1171 | 0 | } |
1172 | | |
1173 | | // #i123295# 3rd param is uncropped rect, 4th is cropped. The primitive has the cropped |
1174 | | // object transformation, thus aCurrentRect *is* the clip region while aCropRect is the expanded, |
1175 | | // uncropped region. Thus, correct order is aCropRect, aCurrentRect |
1176 | 4 | mpPDFExtOutDevData->EndGroup(rGraphicPrimitive.getGraphicObject().GetGraphic(), |
1177 | 4 | 255 - rAttr.GetAlpha(), aCropRect, aCurrentRect); |
1178 | 4 | } |
1179 | | |
1180 | | void VclMetafileProcessor2D::processControlPrimitive2D( |
1181 | | const primitive2d::ControlPrimitive2D& rControlPrimitive) |
1182 | 0 | { |
1183 | 0 | const uno::Reference<awt::XControl>& rXControl(rControlPrimitive.getXControl()); |
1184 | 0 | bool bIsPrintableControl(false); |
1185 | | |
1186 | | // find out if control is printable |
1187 | 0 | if (rXControl.is()) |
1188 | 0 | { |
1189 | 0 | try |
1190 | 0 | { |
1191 | 0 | uno::Reference<beans::XPropertySet> xModelProperties(rXControl->getModel(), |
1192 | 0 | uno::UNO_QUERY); |
1193 | 0 | uno::Reference<beans::XPropertySetInfo> xPropertyInfo( |
1194 | 0 | xModelProperties.is() ? xModelProperties->getPropertySetInfo() |
1195 | 0 | : uno::Reference<beans::XPropertySetInfo>()); |
1196 | 0 | static constexpr OUString sPrintablePropertyName(u"Printable"_ustr); |
1197 | |
|
1198 | 0 | if (xPropertyInfo.is() && xPropertyInfo->hasPropertyByName(sPrintablePropertyName)) |
1199 | 0 | { |
1200 | 0 | OSL_VERIFY(xModelProperties->getPropertyValue(sPrintablePropertyName) |
1201 | 0 | >>= bIsPrintableControl); |
1202 | 0 | } |
1203 | 0 | } |
1204 | 0 | catch (const uno::Exception&) |
1205 | 0 | { |
1206 | 0 | TOOLS_WARN_EXCEPTION("drawinglayer", |
1207 | 0 | "VclMetafileProcessor2D: No access to printable flag of Control"); |
1208 | 0 | } |
1209 | 0 | } |
1210 | | |
1211 | | // PDF export and printing only for printable controls |
1212 | 0 | if (!bIsPrintableControl) |
1213 | 0 | return; |
1214 | | |
1215 | 0 | ::std::optional<sal_Int32> oAnchorParent; |
1216 | 0 | if (mpPDFExtOutDevData) |
1217 | 0 | { |
1218 | 0 | if (rControlPrimitive.GetAnchorStructureElementKey()) |
1219 | 0 | { |
1220 | 0 | sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement( |
1221 | 0 | rControlPrimitive.GetAnchorStructureElementKey()); |
1222 | 0 | oAnchorParent.emplace(mpPDFExtOutDevData->GetCurrentStructureElement()); |
1223 | 0 | mpPDFExtOutDevData->SetCurrentStructureElement(id); |
1224 | 0 | } |
1225 | 0 | } |
1226 | |
|
1227 | 0 | const bool bPDFExport(mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportFormFields()); |
1228 | 0 | bool bDoProcessRecursively(true); |
1229 | 0 | bool bDecorative = (mpCurrentStructureTag && mpCurrentStructureTag->isDecorative()); |
1230 | |
|
1231 | 0 | if (bPDFExport && !bDecorative) |
1232 | 0 | { |
1233 | | // PDF export. Emulate data handling from UnoControlPDFExportContact |
1234 | 0 | std::unique_ptr<vcl::pdf::PDFWriter::AnyWidget> pPDFControl( |
1235 | 0 | ::toolkitform::describePDFControl(rXControl, *mpPDFExtOutDevData)); |
1236 | |
|
1237 | 0 | if (pPDFControl) |
1238 | 0 | { |
1239 | | // still need to fill in the location (is a class Rectangle) |
1240 | 0 | const basegfx::B2DRange aRangeLogic( |
1241 | 0 | rControlPrimitive.getB2DRange(getViewInformation2D())); |
1242 | 0 | const tools::Rectangle aRectLogic(static_cast<sal_Int32>(floor(aRangeLogic.getMinX())), |
1243 | 0 | static_cast<sal_Int32>(floor(aRangeLogic.getMinY())), |
1244 | 0 | static_cast<sal_Int32>(ceil(aRangeLogic.getMaxX())), |
1245 | 0 | static_cast<sal_Int32>(ceil(aRangeLogic.getMaxY()))); |
1246 | 0 | pPDFControl->Location = aRectLogic; |
1247 | |
|
1248 | 0 | Size aFontSize(pPDFControl->TextFont.GetFontSize()); |
1249 | 0 | aFontSize = OutputDevice::LogicToLogic(aFontSize, MapMode(MapUnit::MapPoint), |
1250 | 0 | mpOutputDevice->GetMapMode()); |
1251 | 0 | pPDFControl->TextFont.SetFontSize(aFontSize); |
1252 | |
|
1253 | 0 | mpPDFExtOutDevData->WrapBeginStructureElement(vcl::pdf::StructElement::Form); |
1254 | 0 | vcl::pdf::PDFWriter::StructAttributeValue role; |
1255 | 0 | switch (pPDFControl->Type) |
1256 | 0 | { |
1257 | 0 | case vcl::pdf::PDFWriter::PushButton: |
1258 | 0 | role = vcl::pdf::PDFWriter::Pb; |
1259 | 0 | break; |
1260 | 0 | case vcl::pdf::PDFWriter::RadioButton: |
1261 | 0 | role = vcl::pdf::PDFWriter::Rb; |
1262 | 0 | break; |
1263 | 0 | case vcl::pdf::PDFWriter::CheckBox: |
1264 | 0 | role = vcl::pdf::PDFWriter::Cb; |
1265 | 0 | break; |
1266 | 0 | default: // there is a paucity of roles, tv is the catch-all one |
1267 | 0 | role = vcl::pdf::PDFWriter::Tv; |
1268 | 0 | break; |
1269 | 0 | } |
1270 | | // ISO 14289-1:2014, Clause: 7.18.4 |
1271 | 0 | mpPDFExtOutDevData->SetStructureAttribute(vcl::pdf::PDFWriter::Role, role); |
1272 | | // ISO 14289-1:2014, Clause: 7.18.1 |
1273 | 0 | OUString const& rAltText(rControlPrimitive.GetAltText()); |
1274 | 0 | if (!rAltText.isEmpty()) |
1275 | 0 | { |
1276 | 0 | mpPDFExtOutDevData->SetAlternateText(rAltText); |
1277 | 0 | } |
1278 | 0 | mpPDFExtOutDevData->CreateControl(*pPDFControl); |
1279 | 0 | mpPDFExtOutDevData->EndStructureElement(); |
1280 | 0 | if (oAnchorParent) |
1281 | 0 | { |
1282 | 0 | mpPDFExtOutDevData->SetCurrentStructureElement(*oAnchorParent); |
1283 | 0 | } |
1284 | | |
1285 | | // no normal paint needed (see original UnoControlPDFExportContact::do_PaintObject); |
1286 | | // do not process recursively |
1287 | 0 | bDoProcessRecursively = false; |
1288 | 0 | } |
1289 | 0 | else |
1290 | 0 | { |
1291 | | // PDF export did not work, try simple output. |
1292 | | // Fallback to printer output by not setting bDoProcessRecursively |
1293 | | // to false. |
1294 | 0 | } |
1295 | 0 | } |
1296 | | |
1297 | 0 | if (!bDoProcessRecursively) |
1298 | 0 | { |
1299 | 0 | return; |
1300 | 0 | } |
1301 | | |
1302 | 0 | if (mpPDFExtOutDevData) |
1303 | 0 | { // no corresponding PDF Form, use Figure instead |
1304 | 0 | if (!bDecorative) |
1305 | 0 | mpPDFExtOutDevData->WrapBeginStructureElement(vcl::pdf::StructElement::Figure); |
1306 | 0 | else |
1307 | 0 | mpPDFExtOutDevData->WrapBeginStructureElement( |
1308 | 0 | vcl::pdf::StructElement::NonStructElement); |
1309 | 0 | mpPDFExtOutDevData->SetStructureAttribute(vcl::pdf::PDFWriter::Placement, |
1310 | 0 | vcl::pdf::PDFWriter::Block); |
1311 | 0 | auto const range(rControlPrimitive.getB2DRange(getViewInformation2D())); |
1312 | 0 | tools::Rectangle const aLogicRect(basegfx::fround<tools::Long>(range.getMinX()), |
1313 | 0 | basegfx::fround<tools::Long>(range.getMinY()), |
1314 | 0 | basegfx::fround<tools::Long>(range.getMaxX()), |
1315 | 0 | basegfx::fround<tools::Long>(range.getMaxY())); |
1316 | 0 | mpPDFExtOutDevData->SetStructureBoundingBox(aLogicRect); |
1317 | 0 | OUString const& rAltText(rControlPrimitive.GetAltText()); |
1318 | 0 | if (!rAltText.isEmpty() && !bDecorative) |
1319 | 0 | { |
1320 | 0 | mpPDFExtOutDevData->SetAlternateText(rAltText); |
1321 | 0 | } |
1322 | 0 | } |
1323 | | |
1324 | | // #i93169# used flag the wrong way; true means that nothing was done yet |
1325 | 0 | if (bDoProcessRecursively) |
1326 | 0 | { |
1327 | | // printer output |
1328 | 0 | try |
1329 | 0 | { |
1330 | | // remember old graphics and create new |
1331 | 0 | uno::Reference<awt::XView> xControlView(rXControl, uno::UNO_QUERY_THROW); |
1332 | 0 | const uno::Reference<awt::XGraphics> xOriginalGraphics(xControlView->getGraphics()); |
1333 | 0 | const uno::Reference<awt::XGraphics> xNewGraphics(mpOutputDevice->CreateUnoGraphics()); |
1334 | |
|
1335 | 0 | if (xNewGraphics.is()) |
1336 | 0 | { |
1337 | | // link graphics and view |
1338 | 0 | xControlView->setGraphics(xNewGraphics); |
1339 | | |
1340 | | // get position |
1341 | 0 | const basegfx::B2DHomMatrix aObjectToDiscrete( |
1342 | 0 | getViewInformation2D().getObjectToViewTransformation() |
1343 | 0 | * rControlPrimitive.getTransform()); |
1344 | 0 | const basegfx::B2DPoint aTopLeftDiscrete(aObjectToDiscrete |
1345 | 0 | * basegfx::B2DPoint(0.0, 0.0)); |
1346 | | |
1347 | | // draw it |
1348 | 0 | xControlView->draw(basegfx::fround(aTopLeftDiscrete.getX()), |
1349 | 0 | basegfx::fround(aTopLeftDiscrete.getY())); |
1350 | 0 | bDoProcessRecursively = false; |
1351 | | |
1352 | | // restore original graphics |
1353 | 0 | xControlView->setGraphics(xOriginalGraphics); |
1354 | 0 | } |
1355 | 0 | } |
1356 | 0 | catch (const uno::Exception&) |
1357 | 0 | { |
1358 | 0 | TOOLS_WARN_EXCEPTION("drawinglayer", |
1359 | 0 | "VclMetafileProcessor2D: Printing of Control failed"); |
1360 | 0 | } |
1361 | 0 | } |
1362 | | |
1363 | | // process recursively if not done yet to export as decomposition (bitmap) |
1364 | 0 | if (bDoProcessRecursively) |
1365 | 0 | { |
1366 | 0 | process(rControlPrimitive); |
1367 | 0 | } |
1368 | |
|
1369 | 0 | if (mpPDFExtOutDevData) |
1370 | 0 | { |
1371 | 0 | mpPDFExtOutDevData->EndStructureElement(); |
1372 | 0 | if (oAnchorParent) |
1373 | 0 | { |
1374 | 0 | mpPDFExtOutDevData->SetCurrentStructureElement(*oAnchorParent); |
1375 | 0 | } |
1376 | 0 | } |
1377 | 0 | } |
1378 | | |
1379 | | void VclMetafileProcessor2D::processTextHierarchyFieldPrimitive2D( |
1380 | | const primitive2d::TextHierarchyFieldPrimitive2D& rFieldPrimitive) |
1381 | 0 | { |
1382 | | // support for FIELD_SEQ_BEGIN, FIELD_SEQ_END and URL. It wraps text primitives (but is not limited to) |
1383 | | // thus do the MetafileAction embedding stuff but just handle recursively. |
1384 | 0 | static constexpr OString aCommentStringCommon("FIELD_SEQ_BEGIN"_ostr); |
1385 | 0 | OUString aURL; |
1386 | 0 | const bool bIsExportTaggedPDF(mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportTaggedPDF()); |
1387 | |
|
1388 | 0 | switch (rFieldPrimitive.getType()) |
1389 | 0 | { |
1390 | 0 | default: // case drawinglayer::primitive2d::FIELD_TYPE_COMMON : |
1391 | 0 | { |
1392 | 0 | mpMetaFile->AddAction(new MetaCommentAction(aCommentStringCommon)); |
1393 | 0 | break; |
1394 | 0 | } |
1395 | 0 | case drawinglayer::primitive2d::FIELD_TYPE_PAGE: |
1396 | 0 | { |
1397 | 0 | mpMetaFile->AddAction(new MetaCommentAction("FIELD_SEQ_BEGIN;PageField"_ostr)); |
1398 | 0 | break; |
1399 | 0 | } |
1400 | 0 | case drawinglayer::primitive2d::FIELD_TYPE_URL: |
1401 | 0 | { |
1402 | 0 | aURL = rFieldPrimitive.getValue(u"URL"_ustr); |
1403 | |
|
1404 | 0 | if (!aURL.isEmpty()) |
1405 | 0 | { |
1406 | 0 | mpMetaFile->AddAction(new MetaCommentAction( |
1407 | 0 | aCommentStringCommon, 0, reinterpret_cast<const sal_uInt8*>(aURL.getStr()), |
1408 | 0 | 2 * aURL.getLength())); |
1409 | |
|
1410 | 0 | if (bIsExportTaggedPDF) |
1411 | 0 | mpPDFExtOutDevData->WrapBeginStructureElement(vcl::pdf::StructElement::Link, |
1412 | 0 | u"Link"_ustr); |
1413 | 0 | } |
1414 | |
|
1415 | 0 | break; |
1416 | 0 | } |
1417 | 0 | } |
1418 | | |
1419 | | // process recursively |
1420 | 0 | primitive2d::Primitive2DContainer rContent; |
1421 | 0 | rFieldPrimitive.get2DDecomposition(rContent, getViewInformation2D()); |
1422 | 0 | process(rContent); |
1423 | | |
1424 | | // for the end comment the type is not relevant yet, they are all the same. Just add. |
1425 | 0 | mpMetaFile->AddAction(new MetaCommentAction("FIELD_SEQ_END"_ostr)); |
1426 | |
|
1427 | 0 | if (!(mpPDFExtOutDevData |
1428 | 0 | && drawinglayer::primitive2d::FIELD_TYPE_URL == rFieldPrimitive.getType())) |
1429 | 0 | return; |
1430 | | |
1431 | | // emulate data handling from ImpEditEngine::Paint |
1432 | 0 | const basegfx::B2DRange aViewRange(rContent.getB2DRange(getViewInformation2D())); |
1433 | 0 | const tools::Rectangle aRectLogic(static_cast<sal_Int32>(floor(aViewRange.getMinX())), |
1434 | 0 | static_cast<sal_Int32>(floor(aViewRange.getMinY())), |
1435 | 0 | static_cast<sal_Int32>(ceil(aViewRange.getMaxX())), |
1436 | 0 | static_cast<sal_Int32>(ceil(aViewRange.getMaxY()))); |
1437 | 0 | vcl::PDFExtOutDevBookmarkEntry aBookmark; |
1438 | 0 | OUString const altText(rFieldPrimitive.getValue(u"AltText"_ustr)); |
1439 | 0 | aBookmark.nLinkId = mpPDFExtOutDevData->CreateLink(aRectLogic, altText); |
1440 | 0 | aBookmark.aBookmark = aURL; |
1441 | 0 | std::vector<vcl::PDFExtOutDevBookmarkEntry>& rBookmarks = mpPDFExtOutDevData->GetBookmarks(); |
1442 | 0 | rBookmarks.push_back(aBookmark); |
1443 | |
|
1444 | 0 | if (bIsExportTaggedPDF) |
1445 | 0 | { |
1446 | 0 | mpPDFExtOutDevData->SetStructureAttributeNumerical(vcl::pdf::PDFWriter::LinkAnnotation, |
1447 | 0 | aBookmark.nLinkId); |
1448 | 0 | mpPDFExtOutDevData->EndStructureElement(); |
1449 | 0 | } |
1450 | 0 | } |
1451 | | |
1452 | | void VclMetafileProcessor2D::processTextHierarchyLinePrimitive2D( |
1453 | | const primitive2d::TextHierarchyLinePrimitive2D& rLinePrimitive) |
1454 | 764 | { |
1455 | | // process recursively and add MetaFile comment |
1456 | 764 | process(rLinePrimitive); |
1457 | 764 | mpMetaFile->AddAction(new MetaCommentAction("XTEXT_EOL"_ostr)); |
1458 | 764 | } |
1459 | | |
1460 | | void VclMetafileProcessor2D::processTextHierarchyBulletPrimitive2D( |
1461 | | const primitive2d::TextHierarchyBulletPrimitive2D& rBulletPrimitive) |
1462 | 0 | { |
1463 | | // this is a part of list item, start LILabel ( = bullet) |
1464 | 0 | if (mbInListItem) |
1465 | 0 | { |
1466 | 0 | maListElements.push(vcl::pdf::StructElement::LILabel); |
1467 | 0 | mpPDFExtOutDevData->WrapBeginStructureElement(vcl::pdf::StructElement::LILabel); |
1468 | 0 | } |
1469 | | |
1470 | | // process recursively and add MetaFile comment |
1471 | 0 | process(rBulletPrimitive); |
1472 | | // in Outliner::StripBullet(), a MetafileComment for bullets is added, too. The |
1473 | | // "XTEXT_EOC" is used, use here, too. |
1474 | 0 | mpMetaFile->AddAction(new MetaCommentAction("XTEXT_EOC"_ostr)); |
1475 | |
|
1476 | 0 | if (mbInListItem) |
1477 | 0 | { |
1478 | 0 | if (maListElements.top() == vcl::pdf::StructElement::LILabel) |
1479 | 0 | { |
1480 | 0 | maListElements.pop(); |
1481 | 0 | mpPDFExtOutDevData->EndStructureElement(); // end LILabel |
1482 | 0 | mbBulletPresent = true; |
1483 | 0 | } |
1484 | 0 | } |
1485 | 0 | } |
1486 | | |
1487 | | void VclMetafileProcessor2D::processTextHierarchyParagraphPrimitive2D( |
1488 | | const primitive2d::TextHierarchyParagraphPrimitive2D& rParagraphPrimitive) |
1489 | 359 | { |
1490 | 359 | static constexpr OString aCommentString("XTEXT_EOP"_ostr); |
1491 | 359 | static bool bSuppressPDFExtOutDevDataSupport(false); // loplugin:constvars:ignore |
1492 | | |
1493 | 359 | if (nullptr == mpPDFExtOutDevData || bSuppressPDFExtOutDevDataSupport) |
1494 | 359 | { |
1495 | | // Non-PDF export behaviour (metafile only). |
1496 | | // Process recursively and add MetaFile comment. |
1497 | 359 | process(rParagraphPrimitive); |
1498 | 359 | mpMetaFile->AddAction(new MetaCommentAction(aCommentString)); |
1499 | 359 | return; |
1500 | 359 | } |
1501 | | |
1502 | 0 | if (!mpPDFExtOutDevData->GetIsExportTaggedPDF()) |
1503 | 0 | { |
1504 | | // No Tagged PDF -> Dump as Paragraph |
1505 | | // Emulate data handling from old ImpEditEngine::Paint |
1506 | 0 | mpPDFExtOutDevData->WrapBeginStructureElement(vcl::pdf::StructElement::Paragraph); |
1507 | | |
1508 | | // Process recursively and add MetaFile comment |
1509 | 0 | process(rParagraphPrimitive); |
1510 | 0 | mpMetaFile->AddAction(new MetaCommentAction(aCommentString)); |
1511 | | |
1512 | | // Emulate data handling from ImpEditEngine::Paint |
1513 | 0 | mpPDFExtOutDevData->EndStructureElement(); |
1514 | 0 | return; |
1515 | 0 | } |
1516 | | |
1517 | | // Create Tagged PDF -> deeper tagged data using StructureElements. |
1518 | | // Use OutlineLevel from ParagraphPrimitive, ensure not below -1 what |
1519 | | // means 'not active' |
1520 | 0 | const sal_Int16 nNewOutlineLevel( |
1521 | 0 | std::max(static_cast<sal_Int16>(-1), rParagraphPrimitive.getOutlineLevel())); |
1522 | | |
1523 | | // Do we have a change in OutlineLevel compared to the current one? |
1524 | 0 | if (nNewOutlineLevel != mnCurrentOutlineLevel) |
1525 | 0 | { |
1526 | 0 | if (nNewOutlineLevel > mnCurrentOutlineLevel) |
1527 | 0 | { |
1528 | | // increase List level |
1529 | 0 | for (sal_Int16 a(mnCurrentOutlineLevel); a != nNewOutlineLevel; ++a) |
1530 | 0 | { |
1531 | 0 | maListElements.push(vcl::pdf::StructElement::List); |
1532 | 0 | mpPDFExtOutDevData->WrapBeginStructureElement(vcl::pdf::StructElement::List); |
1533 | 0 | } |
1534 | 0 | } |
1535 | 0 | else // if(nNewOutlineLevel < mnCurrentOutlineLevel) |
1536 | 0 | { |
1537 | | // close list levels below nNewOutlineLevel completely by removing |
1538 | | // list items as well as list tag itself |
1539 | 0 | for (sal_Int16 a(nNewOutlineLevel); a < mnCurrentOutlineLevel; ++a) |
1540 | 0 | { |
1541 | 0 | popList(); // end LBody LI and L |
1542 | 0 | } |
1543 | | |
1544 | | // on nNewOutlineLevel close the previous list item (LBody and LI) |
1545 | 0 | popListItem(); |
1546 | 0 | } |
1547 | | |
1548 | | // Remember new current OutlineLevel |
1549 | 0 | mnCurrentOutlineLevel = nNewOutlineLevel; |
1550 | 0 | } |
1551 | 0 | else // the same list level |
1552 | 0 | { |
1553 | | // close the previous list item (LBody and LI) |
1554 | 0 | popListItem(); |
1555 | 0 | } |
1556 | |
|
1557 | 0 | const bool bDumpAsListItem(-1 != mnCurrentOutlineLevel); |
1558 | |
|
1559 | 0 | if (bDumpAsListItem) |
1560 | 0 | { |
1561 | | // Dump as ListItem |
1562 | 0 | maListElements.push(vcl::pdf::StructElement::ListItem); |
1563 | 0 | mpPDFExtOutDevData->WrapBeginStructureElement(vcl::pdf::StructElement::ListItem); |
1564 | 0 | mbInListItem = true; |
1565 | 0 | } |
1566 | 0 | else |
1567 | 0 | { |
1568 | | // Dump as Paragraph |
1569 | 0 | mpPDFExtOutDevData->WrapBeginStructureElement(vcl::pdf::StructElement::Paragraph); |
1570 | 0 | } |
1571 | | |
1572 | | // Process recursively and add MetaFile comment |
1573 | 0 | process(rParagraphPrimitive); |
1574 | 0 | mpMetaFile->AddAction(new MetaCommentAction(aCommentString)); |
1575 | |
|
1576 | 0 | if (bDumpAsListItem) |
1577 | 0 | mbInListItem = false; |
1578 | 0 | else |
1579 | 0 | mpPDFExtOutDevData->EndStructureElement(); // end Paragraph |
1580 | 0 | } |
1581 | | |
1582 | | void VclMetafileProcessor2D::processTextHierarchyBlockPrimitive2D( |
1583 | | const primitive2d::TextHierarchyBlockPrimitive2D& rBlockPrimitive) |
1584 | 349 | { |
1585 | | // add MetaFile comment, process recursively and add MetaFile comment |
1586 | 349 | mpMetaFile->AddAction(new MetaCommentAction("XTEXT_PAINTSHAPE_BEGIN"_ostr)); |
1587 | 349 | process(rBlockPrimitive); |
1588 | | |
1589 | 349 | if (mnCurrentOutlineLevel >= 0) |
1590 | 0 | { |
1591 | | // end any opened List structure elements (LBody, LI, L) |
1592 | 0 | for (sal_Int16 a(0); a <= mnCurrentOutlineLevel; ++a) |
1593 | 0 | { |
1594 | 0 | popList(); |
1595 | 0 | } |
1596 | 0 | } |
1597 | | |
1598 | 349 | mpMetaFile->AddAction(new MetaCommentAction("XTEXT_PAINTSHAPE_END"_ostr)); |
1599 | 349 | } |
1600 | | |
1601 | | void VclMetafileProcessor2D::processTextSimplePortionPrimitive2D( |
1602 | | const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate) |
1603 | 771 | { |
1604 | | // Adapt evtl. used special DrawMode |
1605 | 771 | const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode()); |
1606 | 771 | adaptTextToFillDrawMode(); |
1607 | | |
1608 | | // this is a 2nd portion of list item |
1609 | | // bullet has been already processed, start LIBody |
1610 | 771 | if (mbInListItem && mbBulletPresent) |
1611 | 0 | { |
1612 | 0 | maListElements.push(vcl::pdf::StructElement::LIBody); |
1613 | 0 | mpPDFExtOutDevData->WrapBeginStructureElement(vcl::pdf::StructElement::LIBody); |
1614 | 0 | } |
1615 | | |
1616 | | // directdraw of text simple portion; use default processing |
1617 | 771 | RenderTextSimpleOrDecoratedPortionPrimitive2D(rTextCandidate); |
1618 | | |
1619 | 771 | if (mbInListItem && mbBulletPresent) |
1620 | 0 | mbBulletPresent = false; |
1621 | | |
1622 | | // restore DrawMode |
1623 | 771 | mpOutputDevice->SetDrawMode(nOriginalDrawMode); |
1624 | | |
1625 | | // #i101169# if(pTextDecoratedCandidate) |
1626 | 771 | { |
1627 | | /* break iterator support |
1628 | | made static so it only needs to be fetched once, even with many single |
1629 | | constructed VclMetafileProcessor2D. It's still incarnated on demand, |
1630 | | but exists for OOo runtime now by purpose. |
1631 | | */ |
1632 | 771 | static tools::DeleteOnDeinit<css::uno::Reference<css::i18n::XBreakIterator>> |
1633 | 771 | gxBreakIterator; |
1634 | | |
1635 | | // support for TEXT_ MetaFile actions only for decorated texts |
1636 | 771 | if (!gxBreakIterator.get() || !gxBreakIterator.get()->get()) |
1637 | 3 | { |
1638 | 3 | const uno::Reference<uno::XComponentContext>& xContext( |
1639 | 3 | ::comphelper::getProcessComponentContext()); |
1640 | 3 | gxBreakIterator.set(i18n::BreakIterator::create(xContext)); |
1641 | 3 | } |
1642 | 771 | auto& rBreakIterator = *gxBreakIterator.get()->get(); |
1643 | | |
1644 | 771 | const OUString& rTxt = rTextCandidate.getText(); |
1645 | 771 | const sal_Int32 nTextLength(rTextCandidate.getTextLength()); // rTxt.getLength()); |
1646 | | |
1647 | 771 | if (nTextLength) |
1648 | 771 | { |
1649 | 771 | const css::lang::Locale& rLocale = rTextCandidate.getLocale(); |
1650 | 771 | const sal_Int32 nTextPosition(rTextCandidate.getTextPosition()); |
1651 | | |
1652 | 771 | sal_Int32 nDone; |
1653 | 771 | sal_Int32 nNextCellBreak(rBreakIterator.nextCharacters( |
1654 | 771 | rTxt, nTextPosition, rLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 0, |
1655 | 771 | nDone)); |
1656 | 771 | css::i18n::Boundary nNextWordBoundary(rBreakIterator.getWordBoundary( |
1657 | 771 | rTxt, nTextPosition, rLocale, css::i18n::WordType::ANY_WORD, true)); |
1658 | 771 | sal_Int32 nNextSentenceBreak( |
1659 | 771 | rBreakIterator.endOfSentence(rTxt, nTextPosition, rLocale)); |
1660 | 771 | static constexpr OString aCommentStringA("XTEXT_EOC"_ostr); |
1661 | 771 | static constexpr OString aCommentStringB("XTEXT_EOW"_ostr); |
1662 | 771 | static constexpr OString aCommentStringC("XTEXT_EOS"_ostr); |
1663 | | |
1664 | 4.22k | for (sal_Int32 i(nTextPosition); i < nTextPosition + nTextLength; i++) |
1665 | 3.45k | { |
1666 | | // create the entries for the respective break positions |
1667 | 3.45k | if (i == nNextCellBreak) |
1668 | 3.45k | { |
1669 | 3.45k | mpMetaFile->AddAction( |
1670 | 3.45k | new MetaCommentAction(aCommentStringA, i - nTextPosition)); |
1671 | 3.45k | nNextCellBreak = rBreakIterator.nextCharacters( |
1672 | 3.45k | rTxt, i, rLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone); |
1673 | 3.45k | } |
1674 | 3.45k | if (i == nNextWordBoundary.endPos) |
1675 | 687 | { |
1676 | 687 | mpMetaFile->AddAction( |
1677 | 687 | new MetaCommentAction(aCommentStringB, i - nTextPosition)); |
1678 | 687 | nNextWordBoundary = rBreakIterator.getWordBoundary( |
1679 | 687 | rTxt, i + 1, rLocale, css::i18n::WordType::ANY_WORD, true); |
1680 | 687 | } |
1681 | 3.45k | if (i == nNextSentenceBreak) |
1682 | 35 | { |
1683 | 35 | mpMetaFile->AddAction( |
1684 | 35 | new MetaCommentAction(aCommentStringC, i - nTextPosition)); |
1685 | 35 | nNextSentenceBreak = rBreakIterator.endOfSentence(rTxt, i + 1, rLocale); |
1686 | 35 | } |
1687 | 3.45k | } |
1688 | 771 | } |
1689 | 771 | } |
1690 | 771 | } |
1691 | | |
1692 | | void VclMetafileProcessor2D::processPolygonHairlinePrimitive2D( |
1693 | | const primitive2d::PolygonHairlinePrimitive2D& rHairlinePrimitive) |
1694 | 197 | { |
1695 | 197 | const basegfx::B2DPolygon& rBasePolygon = rHairlinePrimitive.getB2DPolygon(); |
1696 | | |
1697 | 197 | if (rBasePolygon.count() > (MAX_POLYGON_POINT_COUNT_METAFILE - 1)) |
1698 | 0 | { |
1699 | | // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points |
1700 | | // per polygon. If there are more, split the polygon in half and call recursively |
1701 | 0 | basegfx::B2DPolygon aLeft, aRight; |
1702 | 0 | splitLinePolygon(rBasePolygon, aLeft, aRight); |
1703 | 0 | rtl::Reference<primitive2d::PolygonHairlinePrimitive2D> xPLeft( |
1704 | 0 | new primitive2d::PolygonHairlinePrimitive2D(std::move(aLeft), |
1705 | 0 | rHairlinePrimitive.getBColor())); |
1706 | 0 | rtl::Reference<primitive2d::PolygonHairlinePrimitive2D> xPRight( |
1707 | 0 | new primitive2d::PolygonHairlinePrimitive2D(std::move(aRight), |
1708 | 0 | rHairlinePrimitive.getBColor())); |
1709 | |
|
1710 | 0 | processBasePrimitive2D(*xPLeft); |
1711 | 0 | processBasePrimitive2D(*xPRight); |
1712 | 0 | } |
1713 | 197 | else |
1714 | 197 | { |
1715 | | // direct draw of hairline; use default processing |
1716 | | // support SvtGraphicStroke MetaCommentAction |
1717 | 197 | const basegfx::BColor aLineColor( |
1718 | 197 | maBColorModifierStack.getModifiedColor(rHairlinePrimitive.getBColor())); |
1719 | 197 | std::unique_ptr<SvtGraphicStroke> pSvtGraphicStroke; |
1720 | | |
1721 | | // #i121267# Not needed, does not give better quality compared with |
1722 | | // the MetaActionType::POLYPOLYGON written by RenderPolygonHairlinePrimitive2D |
1723 | | // below |
1724 | 197 | const bool bSupportSvtGraphicStroke(false); |
1725 | | |
1726 | 197 | if (bSupportSvtGraphicStroke) |
1727 | 0 | { |
1728 | 0 | pSvtGraphicStroke |
1729 | 0 | = impTryToCreateSvtGraphicStroke(rHairlinePrimitive.getB2DPolygon(), &aLineColor, |
1730 | 0 | nullptr, nullptr, nullptr, nullptr); |
1731 | |
|
1732 | 0 | impStartSvtGraphicStroke(pSvtGraphicStroke.get()); |
1733 | 0 | } |
1734 | | |
1735 | 197 | RenderPolygonHairlinePrimitive2D(rHairlinePrimitive, false); |
1736 | | |
1737 | 197 | if (bSupportSvtGraphicStroke) |
1738 | 0 | { |
1739 | 0 | impEndSvtGraphicStroke(pSvtGraphicStroke.get()); |
1740 | 0 | } |
1741 | 197 | } |
1742 | 197 | } |
1743 | | |
1744 | | void VclMetafileProcessor2D::processPolygonStrokePrimitive2D( |
1745 | | const primitive2d::PolygonStrokePrimitive2D& rStrokePrimitive) |
1746 | 4.31k | { |
1747 | 4.31k | const basegfx::B2DPolygon& rBasePolygon = rStrokePrimitive.getB2DPolygon(); |
1748 | | |
1749 | 4.31k | if (rBasePolygon.count() > (MAX_POLYGON_POINT_COUNT_METAFILE - 1)) |
1750 | 0 | { |
1751 | | // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points |
1752 | | // per polygon. If there are more, split the polygon in half and call recursively |
1753 | 0 | basegfx::B2DPolygon aLeft, aRight; |
1754 | 0 | splitLinePolygon(rBasePolygon, aLeft, aRight); |
1755 | 0 | rtl::Reference<primitive2d::PolygonStrokePrimitive2D> xPLeft( |
1756 | 0 | new primitive2d::PolygonStrokePrimitive2D(std::move(aLeft), |
1757 | 0 | rStrokePrimitive.getLineAttribute(), |
1758 | 0 | rStrokePrimitive.getStrokeAttribute())); |
1759 | 0 | rtl::Reference<primitive2d::PolygonStrokePrimitive2D> xPRight( |
1760 | 0 | new primitive2d::PolygonStrokePrimitive2D(std::move(aRight), |
1761 | 0 | rStrokePrimitive.getLineAttribute(), |
1762 | 0 | rStrokePrimitive.getStrokeAttribute())); |
1763 | |
|
1764 | 0 | processBasePrimitive2D(*xPLeft); |
1765 | 0 | processBasePrimitive2D(*xPRight); |
1766 | 0 | } |
1767 | 4.31k | else |
1768 | 4.31k | { |
1769 | 4.31k | auto popIt |
1770 | 4.31k | = mpOutputDevice->ScopedPush(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR); |
1771 | | |
1772 | | // support SvtGraphicStroke MetaCommentAction |
1773 | 4.31k | std::unique_ptr<SvtGraphicStroke> pSvtGraphicStroke = impTryToCreateSvtGraphicStroke( |
1774 | 4.31k | rBasePolygon, nullptr, &rStrokePrimitive.getLineAttribute(), |
1775 | 4.31k | &rStrokePrimitive.getStrokeAttribute(), nullptr, nullptr); |
1776 | | |
1777 | 4.31k | impStartSvtGraphicStroke(pSvtGraphicStroke.get()); |
1778 | 4.31k | const attribute::LineAttribute& rLine = rStrokePrimitive.getLineAttribute(); |
1779 | | |
1780 | | // create MetaPolyLineActions, but without LineStyle::Dash |
1781 | 4.31k | if (rLine.getWidth() > 0.0) |
1782 | 4.12k | { |
1783 | 4.12k | const attribute::StrokeAttribute& rStroke = rStrokePrimitive.getStrokeAttribute(); |
1784 | | |
1785 | 4.12k | const basegfx::BColor aHairlineColor( |
1786 | 4.12k | maBColorModifierStack.getModifiedColor(rLine.getColor())); |
1787 | 4.12k | mpOutputDevice->SetLineColor(Color(aHairlineColor)); |
1788 | 4.12k | mpOutputDevice->SetFillColor(); |
1789 | | |
1790 | | // use the transformed line width |
1791 | 4.12k | LineInfo aLineInfo(LineStyle::Solid, |
1792 | 4.12k | std::round(getTransformedLineWidth(rLine.getWidth()))); |
1793 | 4.12k | aLineInfo.SetLineJoin(rLine.getLineJoin()); |
1794 | 4.12k | aLineInfo.SetLineCap(rLine.getLineCap()); |
1795 | | |
1796 | 4.12k | basegfx::B2DPolyPolygon aHairLinePolyPolygon; |
1797 | 4.12k | if (0.0 == rStroke.getFullDotDashLen()) |
1798 | 4.12k | { |
1799 | 4.12k | aHairLinePolyPolygon.append(rBasePolygon); |
1800 | 4.12k | } |
1801 | 0 | else |
1802 | 0 | { |
1803 | 0 | bool done = false; |
1804 | 0 | const std::vector<double>& array = rStroke.getDotDashArray(); |
1805 | | // The dotdash array should generally have the form |
1806 | | // (<dashLen> <distance>)+ (<dotLen> <distance>)* |
1807 | | // (where (,),+ and * have their regex meaning). |
1808 | | // Find out what the lengths and their counts are. |
1809 | 0 | if (!array.empty() && array.size() % 2 == 0) |
1810 | 0 | { |
1811 | 0 | double dashLen = array[0]; |
1812 | 0 | double distance = array[1]; |
1813 | 0 | int dashCount = 1; |
1814 | 0 | double dotLen = 0; |
1815 | 0 | int dotCount = 0; |
1816 | 0 | size_t pos = 2; |
1817 | 0 | while (pos + 2 <= array.size()) |
1818 | 0 | { |
1819 | 0 | if (array[pos] != dashLen || array[pos + 1] != distance) |
1820 | 0 | break; |
1821 | 0 | ++dashCount; |
1822 | 0 | pos += 2; |
1823 | 0 | } |
1824 | 0 | if (pos + 2 <= array.size() && array[pos + 1] == distance) |
1825 | 0 | { |
1826 | 0 | dotLen = array[pos]; |
1827 | 0 | ++dotCount; |
1828 | 0 | pos += 2; |
1829 | 0 | while (pos + 2 <= array.size()) |
1830 | 0 | { |
1831 | 0 | if (array[pos] != dotLen || array[pos + 1] != distance) |
1832 | 0 | break; |
1833 | 0 | ++dotCount; |
1834 | 0 | pos += 2; |
1835 | 0 | } |
1836 | 0 | } |
1837 | 0 | if (array.size() == pos) |
1838 | 0 | { |
1839 | 0 | aHairLinePolyPolygon.append(rBasePolygon); |
1840 | | // This will be used by setupStrokeAttributes() in cppcanvas. |
1841 | 0 | aLineInfo.SetStyle(LineStyle::Dash); |
1842 | 0 | aLineInfo.SetDashCount(dashCount); |
1843 | 0 | aLineInfo.SetDashLen(getTransformedLineWidth(dashLen)); |
1844 | 0 | aLineInfo.SetDistance(getTransformedLineWidth(distance)); |
1845 | 0 | if (dotCount != 0) |
1846 | 0 | { |
1847 | 0 | aLineInfo.SetDotCount(dotCount); |
1848 | 0 | aLineInfo.SetDotLen(getTransformedLineWidth(dotLen)); |
1849 | 0 | } |
1850 | 0 | done = true; |
1851 | 0 | } |
1852 | 0 | } |
1853 | 0 | if (!done) |
1854 | 0 | { |
1855 | | // LineInfo can hold only limited info about dashing, apply dashing manually |
1856 | | // if LineInfo cannot describe it. That should not happen though. |
1857 | 0 | SAL_WARN("drawinglayer", "dotdash array cannot be converted to LineInfo"); |
1858 | 0 | basegfx::utils::applyLineDashing(rBasePolygon, rStroke.getDotDashArray(), |
1859 | 0 | &aHairLinePolyPolygon, nullptr, |
1860 | 0 | rStroke.getFullDotDashLen()); |
1861 | 0 | } |
1862 | 0 | } |
1863 | 4.12k | aHairLinePolyPolygon.transform(maCurrentTransformation); |
1864 | | |
1865 | 8.24k | for (sal_uInt32 a(0); a < aHairLinePolyPolygon.count(); a++) |
1866 | 4.12k | { |
1867 | 4.12k | const basegfx::B2DPolygon& aCandidate(aHairLinePolyPolygon.getB2DPolygon(a)); |
1868 | | |
1869 | 4.12k | if (aCandidate.count() > 1) |
1870 | 4.12k | { |
1871 | 4.12k | const tools::Polygon aToolsPolygon(aCandidate); |
1872 | | |
1873 | 4.12k | mpMetaFile->AddAction(new MetaPolyLineAction(aToolsPolygon, aLineInfo)); |
1874 | 4.12k | } |
1875 | 4.12k | } |
1876 | 4.12k | } |
1877 | 196 | else |
1878 | 196 | { |
1879 | 196 | process(rStrokePrimitive); |
1880 | 196 | } |
1881 | | |
1882 | 4.31k | impEndSvtGraphicStroke(pSvtGraphicStroke.get()); |
1883 | 4.31k | } |
1884 | 4.31k | } |
1885 | | |
1886 | | void VclMetafileProcessor2D::processPolygonStrokeArrowPrimitive2D( |
1887 | | const primitive2d::PolygonStrokeArrowPrimitive2D& rStrokeArrowPrimitive) |
1888 | 0 | { |
1889 | 0 | const basegfx::B2DPolygon& rBasePolygon = rStrokeArrowPrimitive.getB2DPolygon(); |
1890 | |
|
1891 | 0 | if (rBasePolygon.count() > (MAX_POLYGON_POINT_COUNT_METAFILE - 1)) |
1892 | 0 | { |
1893 | | // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points |
1894 | | // per polygon. If there are more, split the polygon in half and call recursively |
1895 | 0 | basegfx::B2DPolygon aLeft, aRight; |
1896 | 0 | splitLinePolygon(rBasePolygon, aLeft, aRight); |
1897 | 0 | const attribute::LineStartEndAttribute aEmpty; |
1898 | 0 | rtl::Reference<primitive2d::PolygonStrokeArrowPrimitive2D> xPLeft( |
1899 | 0 | new primitive2d::PolygonStrokeArrowPrimitive2D( |
1900 | 0 | aLeft, rStrokeArrowPrimitive.getLineAttribute(), |
1901 | 0 | rStrokeArrowPrimitive.getStrokeAttribute(), rStrokeArrowPrimitive.getStart(), |
1902 | 0 | aEmpty)); |
1903 | 0 | rtl::Reference<primitive2d::PolygonStrokeArrowPrimitive2D> xPRight( |
1904 | 0 | new primitive2d::PolygonStrokeArrowPrimitive2D( |
1905 | 0 | aRight, rStrokeArrowPrimitive.getLineAttribute(), |
1906 | 0 | rStrokeArrowPrimitive.getStrokeAttribute(), aEmpty, |
1907 | 0 | rStrokeArrowPrimitive.getEnd())); |
1908 | |
|
1909 | 0 | processBasePrimitive2D(*xPLeft); |
1910 | 0 | processBasePrimitive2D(*xPRight); |
1911 | 0 | } |
1912 | 0 | else |
1913 | 0 | { |
1914 | | // support SvtGraphicStroke MetaCommentAction |
1915 | 0 | std::unique_ptr<SvtGraphicStroke> pSvtGraphicStroke = impTryToCreateSvtGraphicStroke( |
1916 | 0 | rBasePolygon, nullptr, &rStrokeArrowPrimitive.getLineAttribute(), |
1917 | 0 | &rStrokeArrowPrimitive.getStrokeAttribute(), &rStrokeArrowPrimitive.getStart(), |
1918 | 0 | &rStrokeArrowPrimitive.getEnd()); |
1919 | | |
1920 | | // write LineGeometry start marker |
1921 | 0 | impStartSvtGraphicStroke(pSvtGraphicStroke.get()); |
1922 | | |
1923 | | // #i116162# When B&W is set as DrawMode, DrawModeFlags::WhiteFill is used |
1924 | | // to let all fills be just white; for lines DrawModeFlags::BlackLine is used |
1925 | | // so all line geometry is supposed to get black. Since in the in-between |
1926 | | // stages of line geometry drawing filled polygons are used (e.g. line |
1927 | | // start/ends) it is necessary to change these drawmodes to preserve |
1928 | | // that lines shall be black; thus change DrawModeFlags::WhiteFill to |
1929 | | // DrawModeFlags::BlackFill during line geometry processing to have line geometry |
1930 | | // parts filled black. |
1931 | 0 | const DrawModeFlags nOldDrawMode(mpOutputDevice->GetDrawMode()); |
1932 | 0 | const bool bDrawmodeChange(nOldDrawMode & DrawModeFlags::WhiteFill |
1933 | 0 | && mnSvtGraphicStrokeCount); |
1934 | |
|
1935 | 0 | if (bDrawmodeChange) |
1936 | 0 | { |
1937 | 0 | mpOutputDevice->SetDrawMode((nOldDrawMode & ~DrawModeFlags::WhiteFill) |
1938 | 0 | | DrawModeFlags::BlackFill); |
1939 | 0 | } |
1940 | | |
1941 | | // process sub-line geometry (evtl. filled PolyPolygons) |
1942 | 0 | process(rStrokeArrowPrimitive); |
1943 | |
|
1944 | 0 | if (bDrawmodeChange) |
1945 | 0 | { |
1946 | 0 | mpOutputDevice->SetDrawMode(nOldDrawMode); |
1947 | 0 | } |
1948 | | |
1949 | | // write LineGeometry end marker |
1950 | 0 | impEndSvtGraphicStroke(pSvtGraphicStroke.get()); |
1951 | 0 | } |
1952 | 0 | } |
1953 | | |
1954 | | void VclMetafileProcessor2D::processPolyPolygonGraphicPrimitive2D( |
1955 | | const primitive2d::PolyPolygonGraphicPrimitive2D& rBitmapCandidate) |
1956 | 0 | { |
1957 | | // need to handle PolyPolygonGraphicPrimitive2D here to support XPATHFILL_SEQ_BEGIN/XPATHFILL_SEQ_END |
1958 | 0 | basegfx::B2DPolyPolygon aLocalPolyPolygon(rBitmapCandidate.getB2DPolyPolygon()); |
1959 | |
|
1960 | 0 | if (!rBitmapCandidate.getDefinitionRange().isEmpty() |
1961 | 0 | && aLocalPolyPolygon.getB2DRange() != rBitmapCandidate.getDefinitionRange()) |
1962 | 0 | { |
1963 | | // The range which defines the bitmap fill is defined and different from the |
1964 | | // range of the defining geometry (e.g. used for FillStyle UseSlideBackground). |
1965 | | // This cannot be done calling vcl, thus use decomposition here directly |
1966 | 0 | process(rBitmapCandidate); |
1967 | 0 | return; |
1968 | 0 | } |
1969 | | |
1970 | 0 | fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); |
1971 | |
|
1972 | 0 | std::unique_ptr<SvtGraphicFill> pSvtGraphicFill; |
1973 | |
|
1974 | 0 | if (!mnSvtGraphicFillCount && aLocalPolyPolygon.count()) |
1975 | 0 | { |
1976 | | // #121194# Changed implementation and checked usages of convert to metafile, |
1977 | | // presentation start (uses SvtGraphicFill) and printing. |
1978 | | |
1979 | | // calculate transformation. Get real object size, all values in FillGraphicAttribute |
1980 | | // are relative to the unified object |
1981 | 0 | aLocalPolyPolygon.transform(maCurrentTransformation); |
1982 | 0 | const basegfx::B2DVector aOutlineSize(aLocalPolyPolygon.getB2DRange().getRange()); |
1983 | | |
1984 | | // the scaling needs scale from pixel to logic coordinate system |
1985 | 0 | const attribute::FillGraphicAttribute& rFillGraphicAttribute |
1986 | 0 | = rBitmapCandidate.getFillGraphic(); |
1987 | 0 | const Size aBmpSizePixel(rFillGraphicAttribute.getGraphic().GetSizePixel()); |
1988 | | |
1989 | | // setup transformation like in impgrfll. Multiply with aOutlineSize |
1990 | | // to get from unit coordinates in rFillGraphicAttribute.getGraphicRange() |
1991 | | // to object coordinates with object's top left being at (0,0). Divide |
1992 | | // by pixel size so that scale from pixel to logic will work in SvtGraphicFill. |
1993 | 0 | const basegfx::B2DVector aTransformScale( |
1994 | 0 | rFillGraphicAttribute.getGraphicRange().getRange() |
1995 | 0 | / basegfx::B2DVector(std::max(1.0, double(aBmpSizePixel.Width())), |
1996 | 0 | std::max(1.0, double(aBmpSizePixel.Height()))) |
1997 | 0 | * aOutlineSize); |
1998 | 0 | const basegfx::B2DPoint aTransformPosition( |
1999 | 0 | rFillGraphicAttribute.getGraphicRange().getMinimum() * aOutlineSize); |
2000 | | |
2001 | | // setup transformation like in impgrfll |
2002 | 0 | SvtGraphicFill::Transform aTransform; |
2003 | | |
2004 | | // scale values are divided by bitmap pixel sizes |
2005 | 0 | aTransform.matrix[0] = aTransformScale.getX(); |
2006 | 0 | aTransform.matrix[4] = aTransformScale.getY(); |
2007 | | |
2008 | | // translates are absolute |
2009 | 0 | aTransform.matrix[2] = aTransformPosition.getX(); |
2010 | 0 | aTransform.matrix[5] = aTransformPosition.getY(); |
2011 | |
|
2012 | 0 | pSvtGraphicFill.reset(new SvtGraphicFill( |
2013 | 0 | getFillPolyPolygon(aLocalPolyPolygon), Color(), rBitmapCandidate.getTransparency(), |
2014 | 0 | SvtGraphicFill::fillEvenOdd, SvtGraphicFill::fillTexture, aTransform, |
2015 | 0 | rFillGraphicAttribute.getTiling(), SvtGraphicFill::hatchSingle, Color(), |
2016 | 0 | SvtGraphicFill::GradientType::Linear, Color(), Color(), 0, |
2017 | 0 | rFillGraphicAttribute.getGraphic())); |
2018 | 0 | } |
2019 | | |
2020 | | // Do use decomposition; encapsulate with SvtGraphicFill |
2021 | 0 | impStartSvtGraphicFill(pSvtGraphicFill.get()); |
2022 | 0 | process(rBitmapCandidate); |
2023 | 0 | impEndSvtGraphicFill(pSvtGraphicFill.get()); |
2024 | 0 | } |
2025 | | |
2026 | | void VclMetafileProcessor2D::processPolyPolygonHatchPrimitive2D( |
2027 | | const primitive2d::PolyPolygonHatchPrimitive2D& rHatchCandidate) |
2028 | 0 | { |
2029 | | // need to handle PolyPolygonHatchPrimitive2D here to support XPATHFILL_SEQ_BEGIN/XPATHFILL_SEQ_END |
2030 | 0 | const attribute::FillHatchAttribute& rFillHatchAttribute = rHatchCandidate.getFillHatch(); |
2031 | 0 | basegfx::B2DPolyPolygon aLocalPolyPolygon(rHatchCandidate.getB2DPolyPolygon()); |
2032 | |
|
2033 | 0 | if (aLocalPolyPolygon.getB2DRange() != rHatchCandidate.getDefinitionRange()) |
2034 | 0 | { |
2035 | | // the range which defines the hatch is different from the range of the |
2036 | | // geometry (used for writer frames). This cannot be done calling vcl, thus use |
2037 | | // decomposition here |
2038 | 0 | process(rHatchCandidate); |
2039 | 0 | return; |
2040 | 0 | } |
2041 | | |
2042 | | // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points |
2043 | | // per polygon. Split polygon until there are less than that |
2044 | 0 | fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); |
2045 | |
|
2046 | 0 | if (rFillHatchAttribute.isFillBackground()) |
2047 | 0 | { |
2048 | | // with fixing #i111954# (see below) the possible background |
2049 | | // fill of a hatched object was lost.Generate a background fill |
2050 | | // primitive and render it |
2051 | 0 | const primitive2d::Primitive2DReference xBackground( |
2052 | 0 | new primitive2d::PolyPolygonColorPrimitive2D(aLocalPolyPolygon, |
2053 | 0 | rHatchCandidate.getBackgroundColor())); |
2054 | |
|
2055 | 0 | process(primitive2d::Primitive2DContainer{ xBackground }); |
2056 | 0 | } |
2057 | |
|
2058 | 0 | std::unique_ptr<SvtGraphicFill> pSvtGraphicFill; |
2059 | 0 | aLocalPolyPolygon.transform(maCurrentTransformation); |
2060 | |
|
2061 | 0 | if (!mnSvtGraphicFillCount && aLocalPolyPolygon.count()) |
2062 | 0 | { |
2063 | | // re-create a VCL hatch as base data |
2064 | 0 | SvtGraphicFill::HatchType eHatch(SvtGraphicFill::hatchSingle); |
2065 | |
|
2066 | 0 | switch (rFillHatchAttribute.getStyle()) |
2067 | 0 | { |
2068 | 0 | default: // attribute::HatchStyle::Single : |
2069 | 0 | { |
2070 | 0 | eHatch = SvtGraphicFill::hatchSingle; |
2071 | 0 | break; |
2072 | 0 | } |
2073 | 0 | case attribute::HatchStyle::Double: |
2074 | 0 | { |
2075 | 0 | eHatch = SvtGraphicFill::hatchDouble; |
2076 | 0 | break; |
2077 | 0 | } |
2078 | 0 | case attribute::HatchStyle::Triple: |
2079 | 0 | { |
2080 | 0 | eHatch = SvtGraphicFill::hatchTriple; |
2081 | 0 | break; |
2082 | 0 | } |
2083 | 0 | } |
2084 | | |
2085 | 0 | SvtGraphicFill::Transform aTransform; |
2086 | | |
2087 | | // scale |
2088 | 0 | aTransform.matrix[0] *= rFillHatchAttribute.getDistance(); |
2089 | 0 | aTransform.matrix[4] *= rFillHatchAttribute.getDistance(); |
2090 | | |
2091 | | // rotate (was never correct in impgrfll anyways, use correct angle now) |
2092 | 0 | aTransform.matrix[0] *= cos(rFillHatchAttribute.getAngle()); |
2093 | 0 | aTransform.matrix[1] *= -sin(rFillHatchAttribute.getAngle()); |
2094 | 0 | aTransform.matrix[3] *= sin(rFillHatchAttribute.getAngle()); |
2095 | 0 | aTransform.matrix[4] *= cos(rFillHatchAttribute.getAngle()); |
2096 | |
|
2097 | 0 | pSvtGraphicFill.reset(new SvtGraphicFill( |
2098 | 0 | getFillPolyPolygon(aLocalPolyPolygon), Color(), 0.0, SvtGraphicFill::fillEvenOdd, |
2099 | 0 | SvtGraphicFill::fillHatch, aTransform, false, eHatch, |
2100 | 0 | Color(maBColorModifierStack.getModifiedColor(rFillHatchAttribute.getColor())), |
2101 | 0 | SvtGraphicFill::GradientType::Linear, Color(), Color(), 0, Graphic())); |
2102 | 0 | } |
2103 | | |
2104 | | // Do use decomposition; encapsulate with SvtGraphicFill |
2105 | 0 | impStartSvtGraphicFill(pSvtGraphicFill.get()); |
2106 | | |
2107 | | // #i111954# do NOT use decomposition, but use direct VCL-command |
2108 | | // process(rCandidate.get2DDecomposition(getViewInformation2D())); |
2109 | 0 | const tools::PolyPolygon aToolsPolyPolygon( |
2110 | 0 | basegfx::utils::adaptiveSubdivideByAngle(aLocalPolyPolygon)); |
2111 | 0 | const HatchStyle aHatchStyle( |
2112 | 0 | attribute::HatchStyle::Single == rFillHatchAttribute.getStyle() |
2113 | 0 | ? HatchStyle::Single |
2114 | 0 | : attribute::HatchStyle::Double == rFillHatchAttribute.getStyle() ? HatchStyle::Double |
2115 | 0 | : HatchStyle::Triple); |
2116 | |
|
2117 | 0 | mpOutputDevice->DrawHatch( |
2118 | 0 | aToolsPolyPolygon, |
2119 | 0 | Hatch(aHatchStyle, |
2120 | 0 | Color(maBColorModifierStack.getModifiedColor(rFillHatchAttribute.getColor())), |
2121 | 0 | basegfx::fround<tools::Long>(rFillHatchAttribute.getDistance()), |
2122 | 0 | Degree10(basegfx::fround(basegfx::rad2deg<10>(rFillHatchAttribute.getAngle()))))); |
2123 | |
|
2124 | 0 | impEndSvtGraphicFill(pSvtGraphicFill.get()); |
2125 | 0 | } |
2126 | | |
2127 | | void VclMetafileProcessor2D::processPolyPolygonGradientPrimitive2D( |
2128 | | const primitive2d::PolyPolygonGradientPrimitive2D& rGradientCandidate) |
2129 | 0 | { |
2130 | | // SDPR: Caution: metafile export cannot handle added TransparencyGradient |
2131 | 0 | if (rGradientCandidate.hasAlphaGradient() || rGradientCandidate.hasTransparency()) |
2132 | 0 | { |
2133 | | // if it has alpha added directly we need to use the decomposition. |
2134 | | // unfortunately VclMetafileProcessor2D does *not* support the |
2135 | | // primitive created in the decomposition, the FillGradientPrimitive2D. |
2136 | | // at the same time extra stuff like adding gradient info to the |
2137 | | // metafile (BGRAD_SEQ_BEGIN) only is done HERE. To solve that and to |
2138 | | // not add PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D now, create a temporary |
2139 | | // decomposition to again get a PolyPolygonGradientPrimitive2D, but |
2140 | | // *without* directly added alpha |
2141 | 0 | primitive2d::Primitive2DReference aRetval(new primitive2d::PolyPolygonGradientPrimitive2D( |
2142 | 0 | rGradientCandidate.getB2DPolyPolygon(), rGradientCandidate.getDefinitionRange(), |
2143 | 0 | rGradientCandidate.getFillGradient())); |
2144 | |
|
2145 | 0 | if (rGradientCandidate.hasAlphaGradient()) |
2146 | 0 | { |
2147 | 0 | const basegfx::B2DRange aPolyPolygonRange( |
2148 | 0 | rGradientCandidate.getB2DPolyPolygon().getB2DRange()); |
2149 | 0 | primitive2d::Primitive2DContainer aAlpha{ new primitive2d::FillGradientPrimitive2D( |
2150 | 0 | aPolyPolygonRange, rGradientCandidate.getDefinitionRange(), |
2151 | 0 | rGradientCandidate.getAlphaGradient()) }; |
2152 | |
|
2153 | 0 | aRetval = new primitive2d::TransparencePrimitive2D( |
2154 | 0 | primitive2d::Primitive2DContainer{ aRetval }, std::move(aAlpha)); |
2155 | 0 | } |
2156 | |
|
2157 | 0 | if (rGradientCandidate.hasTransparency()) |
2158 | 0 | { |
2159 | 0 | aRetval = new primitive2d::UnifiedTransparencePrimitive2D( |
2160 | 0 | primitive2d::Primitive2DContainer{ aRetval }, rGradientCandidate.getTransparency()); |
2161 | 0 | } |
2162 | |
|
2163 | 0 | process(primitive2d::Primitive2DContainer{ aRetval }); |
2164 | 0 | return; |
2165 | 0 | } |
2166 | | |
2167 | 0 | bool useDecompose(false); |
2168 | |
|
2169 | 0 | if (!useDecompose) |
2170 | 0 | { |
2171 | 0 | basegfx::B2DVector aScale, aTranslate; |
2172 | 0 | double fRotate, fShearX; |
2173 | |
|
2174 | 0 | maCurrentTransformation.decompose(aScale, aTranslate, fRotate, fShearX); |
2175 | | |
2176 | | // detect if transformation is rotated, sheared or mirrored in X and/or Y |
2177 | 0 | if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX) |
2178 | 0 | || aScale.getX() < 0.0 || aScale.getY() < 0.0) |
2179 | 0 | { |
2180 | | // #i121185# When rotation or shear is used, a VCL Gradient cannot be used directly. |
2181 | | // This is because VCL Gradient mechanism does *not* support to rotate the gradient |
2182 | | // with objects and this case is not expressible in a Metafile (and cannot be added |
2183 | | // since the FileFormats used, e.g. *.wmf, do not support it either). |
2184 | | // Such cases happen when a graphic object uses a Metafile as graphic information or |
2185 | | // a fill style definition uses a Metafile. In this cases the graphic content is |
2186 | | // rotated with the graphic or filled object; this is not supported by the target |
2187 | | // format of this conversion renderer - Metafiles. |
2188 | | // To solve this, not a Gradient is written, but the decomposition of this object |
2189 | | // is written to the Metafile. This is the PolyPolygons building the gradient fill. |
2190 | | // These will need more space and time, but the result will be as if the Gradient |
2191 | | // was rotated with the object. |
2192 | | // This mechanism is used by all exporters still not using Primitives (e.g. Print, |
2193 | | // Slideshow, Export rto PDF, export to Picture, ...) but relying on Metafile |
2194 | | // transfers. One more reason to *change* these to primitives. |
2195 | | // BTW: One more example how useful the principles of primitives are; the decomposition |
2196 | | // is by definition a simpler, maybe more expensive representation of the same content. |
2197 | 0 | useDecompose = true; |
2198 | 0 | } |
2199 | 0 | } |
2200 | | |
2201 | | // tdf#150551 for PDF export, use the decomposition for better gradient visualization |
2202 | 0 | if (!useDecompose && nullptr != mpPDFExtOutDevData) |
2203 | 0 | { |
2204 | 0 | useDecompose = true; |
2205 | 0 | } |
2206 | |
|
2207 | 0 | basegfx::B2DPolyPolygon aLocalPolyPolygon(rGradientCandidate.getB2DPolyPolygon()); |
2208 | |
|
2209 | 0 | if (!useDecompose && aLocalPolyPolygon.getB2DRange() != rGradientCandidate.getDefinitionRange()) |
2210 | 0 | { |
2211 | | // the range which defines the gradient is different from the range of the |
2212 | | // geometry (used for writer frames). This cannot be done calling vcl, thus use |
2213 | | // decomposition here |
2214 | 0 | useDecompose = true; |
2215 | 0 | } |
2216 | |
|
2217 | 0 | const attribute::FillGradientAttribute& rFillGradient(rGradientCandidate.getFillGradient()); |
2218 | |
|
2219 | 0 | if (!useDecompose && rFillGradient.cannotBeHandledByVCL()) |
2220 | 0 | { |
2221 | | // MCGR: if we have ColorStops, do not try to fallback to old VCL-Gradient, |
2222 | | // that will *not* be capable of representing this properly. Use the |
2223 | | // correct decomposition instead |
2224 | 0 | useDecompose = true; |
2225 | 0 | } |
2226 | |
|
2227 | 0 | if (useDecompose) |
2228 | 0 | { |
2229 | 0 | GDIMetaFile* pMetaFile(mpOutputDevice->GetConnectMetaFile()); |
2230 | | |
2231 | | // tdf#155479 only add 'BGRAD_SEQ_BEGIN' if SVG export |
2232 | | // SDPR: Caution: metafile export cannot handle added TransparencyGradient |
2233 | 0 | if (nullptr != pMetaFile && pMetaFile->getSVG() && !rGradientCandidate.hasAlphaGradient()) |
2234 | 0 | { |
2235 | | // write the color stops to a memory stream |
2236 | 0 | SvMemoryStream aMemStm; |
2237 | 0 | VersionCompatWrite aCompat(aMemStm, 1); |
2238 | |
|
2239 | 0 | const basegfx::BColorStops& rColorStops(rFillGradient.getColorStops()); |
2240 | 0 | sal_uInt16 nTmp(sal::static_int_cast<sal_uInt16>(rColorStops.size())); |
2241 | 0 | aMemStm.WriteUInt16(nTmp); |
2242 | |
|
2243 | 0 | for (auto const& rCand : rColorStops) |
2244 | 0 | { |
2245 | 0 | aMemStm.WriteDouble(rCand.getStopOffset()); |
2246 | 0 | const basegfx::BColor& rColor(rCand.getStopColor()); |
2247 | 0 | aMemStm.WriteDouble(rColor.getRed()); |
2248 | 0 | aMemStm.WriteDouble(rColor.getGreen()); |
2249 | 0 | aMemStm.WriteDouble(rColor.getBlue()); |
2250 | 0 | } |
2251 | | |
2252 | | // Add a new MetaCommentAction section of type 'BGRAD_SEQ_BEGIN/BGRAD_SEQ_END' |
2253 | | // that is capable of holding the new color step information, plus the |
2254 | | // already used MetaActionType::GRADIENTEX. |
2255 | | // With that combination only places that know about that new BGRAD_SEQ_* will |
2256 | | // use it while all others will work on the created decomposition of the |
2257 | | // gradient for compatibility - which are single-color filled polygons |
2258 | 0 | pMetaFile->AddAction(new MetaCommentAction( |
2259 | 0 | "BGRAD_SEQ_BEGIN"_ostr, 0, static_cast<const sal_uInt8*>(aMemStm.GetData()), |
2260 | 0 | aMemStm.TellEnd())); |
2261 | | |
2262 | | // create MetaActionType::GRADIENTEX |
2263 | | // NOTE: with the new BGRAD_SEQ_* we could use basegfx::B2DPolygon and |
2264 | | // basegfx::BGradient here directly, but may have to add streaming OPs |
2265 | | // for these, so for now just go with what we use all the time. The real |
2266 | | // work for improvement should not go to this 'compromise' but to a real |
2267 | | // re-work of the SVG export (or/and others) to no longer work on metafiles |
2268 | | // but on UNO API or primitives (whatever fits best to the specific export) |
2269 | 0 | fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); |
2270 | 0 | Gradient aVCLGradient; |
2271 | 0 | impConvertFillGradientAttributeToVCLGradient(aVCLGradient, rFillGradient, false); |
2272 | 0 | aLocalPolyPolygon.transform(maCurrentTransformation); |
2273 | 0 | const tools::PolyPolygon aToolsPolyPolygon( |
2274 | 0 | getFillPolyPolygon(basegfx::utils::adaptiveSubdivideByAngle(aLocalPolyPolygon))); |
2275 | 0 | mpOutputDevice->DrawGradient(aToolsPolyPolygon, aVCLGradient); |
2276 | 0 | } |
2277 | | |
2278 | | // use decompose to draw, will create PolyPolygon ColorFill actions |
2279 | 0 | process(rGradientCandidate); |
2280 | | |
2281 | | // tdf#155479 only add 'BGRAD_SEQ_END' if SVG export |
2282 | 0 | if (nullptr != pMetaFile && pMetaFile->getSVG()) |
2283 | 0 | { |
2284 | | // close the BGRAD_SEQ_* actions range |
2285 | 0 | pMetaFile->AddAction(new MetaCommentAction("BGRAD_SEQ_END"_ostr)); |
2286 | 0 | } |
2287 | |
|
2288 | 0 | return; |
2289 | 0 | } |
2290 | | |
2291 | | // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points |
2292 | | // per polygon. Split polygon until there are less than that |
2293 | 0 | fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); |
2294 | | |
2295 | | // for support of MetaCommentActions of the form XGRAD_SEQ_BEGIN, XGRAD_SEQ_END |
2296 | | // it is safest to use the VCL OutputDevice::DrawGradient method which creates those. |
2297 | | // re-create a VCL-gradient from FillGradientPrimitive2D and the needed tools PolyPolygon |
2298 | 0 | Gradient aVCLGradient; |
2299 | 0 | impConvertFillGradientAttributeToVCLGradient(aVCLGradient, rFillGradient, false); |
2300 | 0 | aLocalPolyPolygon.transform(maCurrentTransformation); |
2301 | | |
2302 | | // #i82145# ATM VCL printing of gradients using curved shapes does not work, |
2303 | | // i submitted the bug with the given ID to THB. When that task is fixed it is |
2304 | | // necessary to again remove this subdivision since it decreases possible |
2305 | | // printing quality (not even resolution-dependent for now). THB will tell |
2306 | | // me when that task is fixed in the master |
2307 | 0 | const tools::PolyPolygon aToolsPolyPolygon( |
2308 | 0 | getFillPolyPolygon(basegfx::utils::adaptiveSubdivideByAngle(aLocalPolyPolygon))); |
2309 | | |
2310 | | // XPATHFILL_SEQ_BEGIN/XPATHFILL_SEQ_END support |
2311 | 0 | std::unique_ptr<SvtGraphicFill> pSvtGraphicFill; |
2312 | |
|
2313 | 0 | if (!mnSvtGraphicFillCount && aLocalPolyPolygon.count()) |
2314 | 0 | { |
2315 | | // setup gradient stuff like in impgrfll |
2316 | 0 | SvtGraphicFill::GradientType eGrad(SvtGraphicFill::GradientType::Linear); |
2317 | |
|
2318 | 0 | switch (aVCLGradient.GetStyle()) |
2319 | 0 | { |
2320 | 0 | default: // css::awt::GradientStyle_LINEAR: |
2321 | 0 | case css::awt::GradientStyle_AXIAL: |
2322 | 0 | eGrad = SvtGraphicFill::GradientType::Linear; |
2323 | 0 | break; |
2324 | 0 | case css::awt::GradientStyle_RADIAL: |
2325 | 0 | case css::awt::GradientStyle_ELLIPTICAL: |
2326 | 0 | eGrad = SvtGraphicFill::GradientType::Radial; |
2327 | 0 | break; |
2328 | 0 | case css::awt::GradientStyle_SQUARE: |
2329 | 0 | case css::awt::GradientStyle_RECT: |
2330 | 0 | eGrad = SvtGraphicFill::GradientType::Rectangular; |
2331 | 0 | break; |
2332 | 0 | } |
2333 | | |
2334 | 0 | pSvtGraphicFill.reset(new SvtGraphicFill( |
2335 | 0 | aToolsPolyPolygon, Color(), 0.0, SvtGraphicFill::fillEvenOdd, |
2336 | 0 | SvtGraphicFill::fillGradient, SvtGraphicFill::Transform(), false, |
2337 | 0 | SvtGraphicFill::hatchSingle, Color(), eGrad, aVCLGradient.GetStartColor(), |
2338 | 0 | aVCLGradient.GetEndColor(), aVCLGradient.GetSteps(), Graphic())); |
2339 | 0 | } |
2340 | | |
2341 | | // call VCL directly; encapsulate with SvtGraphicFill |
2342 | 0 | impStartSvtGraphicFill(pSvtGraphicFill.get()); |
2343 | 0 | mpOutputDevice->DrawGradient(aToolsPolyPolygon, aVCLGradient); |
2344 | 0 | impEndSvtGraphicFill(pSvtGraphicFill.get()); |
2345 | 0 | } |
2346 | | |
2347 | | void VclMetafileProcessor2D::processPolyPolygonColorPrimitive2D( |
2348 | | const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate) |
2349 | 469 | { |
2350 | 469 | auto popIt = mpOutputDevice->ScopedPush(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR); |
2351 | 469 | basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolygonCandidate.getB2DPolyPolygon()); |
2352 | | |
2353 | | // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points |
2354 | | // per polygon. Split polygon until there are less than that |
2355 | 469 | fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); |
2356 | | |
2357 | 469 | const basegfx::BColor aPolygonColor( |
2358 | 469 | maBColorModifierStack.getModifiedColor(rPolygonCandidate.getBColor())); |
2359 | 469 | aLocalPolyPolygon.transform(maCurrentTransformation); |
2360 | | |
2361 | | // set line and fill color |
2362 | 469 | mpOutputDevice->SetFillColor(Color(aPolygonColor)); |
2363 | 469 | mpOutputDevice->SetLineColor(); |
2364 | | |
2365 | 469 | mpOutputDevice->DrawPolyPolygon(aLocalPolyPolygon); |
2366 | 469 | } |
2367 | | |
2368 | | void VclMetafileProcessor2D::processMaskPrimitive2D( |
2369 | | const primitive2d::MaskPrimitive2D& rMaskCandidate) |
2370 | 99 | { |
2371 | | // mask group. Special handling for MetaFiles. |
2372 | 99 | if (rMaskCandidate.getChildren().empty()) |
2373 | 0 | return; |
2374 | | |
2375 | 99 | basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask()); |
2376 | | |
2377 | 99 | if (aMask.count()) |
2378 | 99 | { |
2379 | | // A clipping path is a set of closed vector path that may consist of an arbitrary number |
2380 | | // of straight and curved segments. The region(s) enclosed by the path define(s) the visible area, |
2381 | | // i.e. after applying a clipping path, only those portions of the subsequently drawn graphics that |
2382 | | // fall inside the enclosed area are visible, everything else is cut away. |
2383 | 99 | if (!aMask.isClosed()) |
2384 | 0 | aMask.setClosed(true); |
2385 | | // prepare new mask polygon and rescue current one |
2386 | 99 | aMask.transform(maCurrentTransformation); |
2387 | 99 | const basegfx::B2DPolyPolygon aLastClipPolyPolygon(maClipPolyPolygon); |
2388 | | |
2389 | 99 | if (maClipPolyPolygon.count()) |
2390 | 0 | { |
2391 | | // there is already a clip polygon set; build clipped union of |
2392 | | // current mask polygon and new one |
2393 | 0 | maClipPolyPolygon = basegfx::utils::clipPolyPolygonOnPolyPolygon( |
2394 | 0 | aMask, maClipPolyPolygon, |
2395 | 0 | true, // #i106516# we want the inside of aMask, not the outside |
2396 | 0 | false); |
2397 | 0 | } |
2398 | 99 | else |
2399 | 99 | { |
2400 | | // use mask directly |
2401 | 99 | maClipPolyPolygon = std::move(aMask); |
2402 | 99 | } |
2403 | | |
2404 | 99 | if (maClipPolyPolygon.count()) |
2405 | 99 | { |
2406 | | // set VCL clip region; subdivide before conversion to tools polygon. Subdivision necessary (!) |
2407 | | // Removed subdivision and fixed in vcl::Region::ImplPolyPolyRegionToBandRegionFunc() in VCL where |
2408 | | // the ClipRegion is built from the Polygon. An AdaptiveSubdivide on the source polygon was missing there |
2409 | 99 | auto popIt = mpOutputDevice->ScopedPush(vcl::PushFlags::CLIPREGION); |
2410 | 99 | mpOutputDevice->SetClipRegion(vcl::Region(maClipPolyPolygon)); |
2411 | | |
2412 | | // recursively paint content |
2413 | | // #i121267# Only need to process sub-content when clip polygon is *not* empty. |
2414 | | // If it is empty, the clip is empty and there can be nothing inside. |
2415 | 99 | process(rMaskCandidate.getChildren()); |
2416 | 99 | } |
2417 | | |
2418 | | // restore to rescued clip polygon |
2419 | 99 | maClipPolyPolygon = aLastClipPolyPolygon; |
2420 | 99 | } |
2421 | 0 | else |
2422 | 0 | { |
2423 | | // no mask, no clipping. recursively paint content |
2424 | 0 | process(rMaskCandidate.getChildren()); |
2425 | 0 | } |
2426 | 99 | } |
2427 | | |
2428 | | void VclMetafileProcessor2D::processUnifiedTransparencePrimitive2D( |
2429 | | const primitive2d::UnifiedTransparencePrimitive2D& rUniTransparenceCandidate) |
2430 | 0 | { |
2431 | 0 | auto popIt = mpOutputDevice->ScopedPush(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR); |
2432 | | // for metafile: Need to examine what the pure vcl version is doing here actually |
2433 | | // - uses DrawTransparent with metafile for content and a gradient |
2434 | | // - uses DrawTransparent for single PolyPolygons directly. Can be detected by |
2435 | | // checking the content for single PolyPolygonColorPrimitive2D |
2436 | 0 | const primitive2d::Primitive2DContainer& rContent = rUniTransparenceCandidate.getChildren(); |
2437 | |
|
2438 | 0 | if (!rContent.empty()) |
2439 | 0 | { |
2440 | 0 | if (0.0 == rUniTransparenceCandidate.getTransparence()) |
2441 | 0 | { |
2442 | | // not transparent at all, use content |
2443 | 0 | process(rUniTransparenceCandidate.getChildren()); |
2444 | 0 | } |
2445 | 0 | else if (rUniTransparenceCandidate.getTransparence() > 0.0 |
2446 | 0 | && rUniTransparenceCandidate.getTransparence() < 1.0) |
2447 | 0 | { |
2448 | | // try to identify a single PolyPolygonColorPrimitive2D in the |
2449 | | // content part of the transparence primitive |
2450 | 0 | const primitive2d::PolyPolygonColorPrimitive2D* pPoPoColor = nullptr; |
2451 | 0 | static bool bForceToMetafile(false); // loplugin:constvars:ignore |
2452 | |
|
2453 | 0 | if (!bForceToMetafile && 1 == rContent.size()) |
2454 | 0 | { |
2455 | 0 | const primitive2d::Primitive2DReference& xReference(rContent.front()); |
2456 | 0 | pPoPoColor = dynamic_cast<const primitive2d::PolyPolygonColorPrimitive2D*>( |
2457 | 0 | xReference.get()); |
2458 | 0 | } |
2459 | | |
2460 | | // PolyPolygonGradientPrimitive2D, PolyPolygonHatchPrimitive2D and |
2461 | | // PolyPolygonGraphicPrimitive2D are derived from PolyPolygonColorPrimitive2D. |
2462 | | // Check also for correct ID to exclude derived implementations |
2463 | 0 | if (pPoPoColor |
2464 | 0 | && PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D == pPoPoColor->getPrimitive2DID()) |
2465 | 0 | { |
2466 | | // single transparent tools::PolyPolygon identified, use directly |
2467 | 0 | const basegfx::BColor aPolygonColor( |
2468 | 0 | maBColorModifierStack.getModifiedColor(pPoPoColor->getBColor())); |
2469 | 0 | basegfx::B2DPolyPolygon aLocalPolyPolygon(pPoPoColor->getB2DPolyPolygon()); |
2470 | | |
2471 | | // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points |
2472 | | // per polygon. Split polygon until there are less than that |
2473 | 0 | fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); |
2474 | | |
2475 | | // now transform |
2476 | 0 | aLocalPolyPolygon.transform(maCurrentTransformation); |
2477 | | |
2478 | | // set line and fill color |
2479 | 0 | const sal_uInt16 nTransPercentVcl(static_cast<sal_uInt16>( |
2480 | 0 | basegfx::fround(rUniTransparenceCandidate.getTransparence() * 100.0))); |
2481 | 0 | mpOutputDevice->SetFillColor(Color(aPolygonColor)); |
2482 | 0 | mpOutputDevice->SetLineColor(); |
2483 | |
|
2484 | 0 | mpOutputDevice->DrawTransparent(tools::PolyPolygon(aLocalPolyPolygon), |
2485 | 0 | nTransPercentVcl); |
2486 | 0 | } |
2487 | 0 | else |
2488 | 0 | { |
2489 | | // save old mfCurrentUnifiedTransparence and set new one |
2490 | | // so that contained SvtGraphicStroke may use the current one |
2491 | 0 | const double fLastCurrentUnifiedTransparence(mfCurrentUnifiedTransparence); |
2492 | | // #i105377# paint the content metafile opaque as the transparency gets |
2493 | | // split of into the gradient below |
2494 | | // mfCurrentUnifiedTransparence = rUniTransparenceCandidate.getTransparence(); |
2495 | 0 | mfCurrentUnifiedTransparence = 0; |
2496 | | |
2497 | | // various content, create content-metafile |
2498 | 0 | GDIMetaFile aContentMetafile; |
2499 | | |
2500 | | // tdf#155479 always forward propagate SVG flag for sub-content, |
2501 | | // it may contain cannotBeHandledByVCL gradients or transparencyGradients |
2502 | 0 | aContentMetafile.setSVG(mpOutputDevice->GetConnectMetaFile()->getSVG()); |
2503 | |
|
2504 | 0 | const tools::Rectangle aPrimitiveRectangle( |
2505 | 0 | impDumpToMetaFile(rContent, aContentMetafile)); |
2506 | | |
2507 | | // restore mfCurrentUnifiedTransparence; it may have been used |
2508 | | // while processing the sub-content in impDumpToMetaFile |
2509 | 0 | mfCurrentUnifiedTransparence = fLastCurrentUnifiedTransparence; |
2510 | | |
2511 | | // create uniform VCL gradient for uniform transparency |
2512 | 0 | Gradient aVCLGradient; |
2513 | 0 | const sal_uInt8 nTransPercentVcl(static_cast<sal_uInt8>( |
2514 | 0 | basegfx::fround(rUniTransparenceCandidate.getTransparence() * 255.0))); |
2515 | 0 | const Color aTransColor(nTransPercentVcl, nTransPercentVcl, nTransPercentVcl); |
2516 | |
|
2517 | 0 | aVCLGradient.SetStyle(css::awt::GradientStyle_LINEAR); |
2518 | 0 | aVCLGradient.SetStartColor(aTransColor); |
2519 | 0 | aVCLGradient.SetEndColor(aTransColor); |
2520 | 0 | aVCLGradient.SetAngle(0_deg10); |
2521 | 0 | aVCLGradient.SetBorder(0); |
2522 | 0 | aVCLGradient.SetOfsX(0); |
2523 | 0 | aVCLGradient.SetOfsY(0); |
2524 | 0 | aVCLGradient.SetStartIntensity(100); |
2525 | 0 | aVCLGradient.SetEndIntensity(100); |
2526 | 0 | aVCLGradient.SetSteps(2); |
2527 | | |
2528 | | // render it to VCL |
2529 | 0 | mpOutputDevice->DrawTransparent(aContentMetafile, aPrimitiveRectangle.TopLeft(), |
2530 | 0 | aPrimitiveRectangle.GetSize(), aVCLGradient); |
2531 | 0 | } |
2532 | 0 | } |
2533 | 0 | } |
2534 | 0 | } |
2535 | | |
2536 | | void VclMetafileProcessor2D::processTransparencePrimitive2D( |
2537 | | const primitive2d::TransparencePrimitive2D& rTransparenceCandidate) |
2538 | 0 | { |
2539 | | // for metafile: Need to examine what the pure vcl version is doing here actually |
2540 | | // - uses DrawTransparent with metafile for content and a gradient |
2541 | | // i can detect this here with checking the gradient part for a single |
2542 | | // FillGradientPrimitive2D and reconstruct the gradient. |
2543 | | // If that detection goes wrong, I have to create a transparence-blended bitmap. Eventually |
2544 | | // do that in stripes, else RenderTransparencePrimitive2D may just be used |
2545 | 0 | const primitive2d::Primitive2DContainer& rContent(rTransparenceCandidate.getChildren()); |
2546 | 0 | const primitive2d::Primitive2DContainer& rTransparence( |
2547 | 0 | rTransparenceCandidate.getTransparence()); |
2548 | |
|
2549 | 0 | if (rContent.empty() || rTransparence.empty()) |
2550 | 0 | return; |
2551 | | |
2552 | | // try to identify a single FillGradientPrimitive2D in the |
2553 | | // transparence part of the primitive. The hope is to handle |
2554 | | // the more specific case in a better way than the general |
2555 | | // TransparencePrimitive2D which has strongly separated |
2556 | | // definitions for transparency and content, both completely |
2557 | | // free definable by primitives |
2558 | 0 | const primitive2d::FillGradientPrimitive2D* pFiGradient(nullptr); |
2559 | 0 | static bool bForceToBigTransparentVDev(false); // loplugin:constvars:ignore |
2560 | | |
2561 | | // check for single FillGradientPrimitive2D |
2562 | 0 | if (!bForceToBigTransparentVDev && 1 == rTransparence.size()) |
2563 | 0 | { |
2564 | 0 | pFiGradient = dynamic_cast<const primitive2d::FillGradientPrimitive2D*>( |
2565 | 0 | rTransparence.front().get()); |
2566 | | |
2567 | | // check also for correct ID to exclude derived implementations |
2568 | 0 | if (pFiGradient |
2569 | 0 | && PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D != pFiGradient->getPrimitive2DID()) |
2570 | 0 | pFiGradient = nullptr; |
2571 | 0 | } |
2572 | | |
2573 | | // tdf#155479 preps for holding extra-MCGR infos |
2574 | 0 | bool bSVGTransparencyColorStops(false); |
2575 | 0 | basegfx::BColorStops aSVGTransparencyColorStops; |
2576 | | |
2577 | | // MCGR: tdf#155437 If we have identified a transparency gradient, |
2578 | | // check if VCL is able to handle it at all |
2579 | 0 | if (nullptr != pFiGradient && pFiGradient->getFillGradient().cannotBeHandledByVCL()) |
2580 | 0 | { |
2581 | | // If not, reset the pointer and do not make use of this special case. |
2582 | | // Adding a gradient in incomplete state that can not be handled by vcl |
2583 | | // makes no sense and will knowingly lead to errors, especially with |
2584 | | // MCGR extended possibilities. I checked what happens with the |
2585 | | // MetaFloatTransparentAction added by OutputDevice::DrawTransparent, but |
2586 | | // in most cases it gets converted to bitmap or even ignored, see e.g. |
2587 | | // - vcl/source/pdf/pdfwriter_impl2.cxx for PDF export |
2588 | | // - vcl/source/filter/wmf/wmfwr.cxx -> does ignore TransparenceGradient completely |
2589 | | // - vcl/source/filter/wmf/emfwr.cxx -> same |
2590 | | // - vcl/source/filter/eps/eps.cxx -> same |
2591 | | // NOTE: Theoretically it would be possible to make the new extended Gradient data |
2592 | | // available in metafiles, with the known limitations (not backward comp, all |
2593 | | // places using it would need adaption, ...), but combined with knowing that nearly |
2594 | | // all usages ignore or render it locally anyways makes that a non-option. |
2595 | | |
2596 | | // tdf#155479 Yepp, as already mentioned above we need to add |
2597 | | // some MCGR infos in case of SVG export, prepare that here |
2598 | 0 | if (mpOutputDevice->GetConnectMetaFile()->getSVG()) |
2599 | 0 | { |
2600 | | // for SVG, do not use decompose & prep extra data |
2601 | 0 | bSVGTransparencyColorStops = true; |
2602 | 0 | aSVGTransparencyColorStops = pFiGradient->getFillGradient().getColorStops(); |
2603 | 0 | } |
2604 | 0 | else |
2605 | 0 | { |
2606 | | // use decomposition |
2607 | 0 | pFiGradient = nullptr; |
2608 | 0 | } |
2609 | 0 | } |
2610 | |
|
2611 | 0 | if (nullptr != pFiGradient) |
2612 | 0 | { |
2613 | | // this combination of Gradient can be expressed/handled by |
2614 | | // vcl/metafile, so add it directly. various content, create content-metafile |
2615 | 0 | GDIMetaFile aContentMetafile; |
2616 | | |
2617 | | // tdf#155479 always forward propagate SVG flag for sub-content, |
2618 | | // it may contain cannotBeHandledByVCL gradients or transparencyGradients |
2619 | 0 | aContentMetafile.setSVG(mpOutputDevice->GetConnectMetaFile()->getSVG()); |
2620 | |
|
2621 | 0 | const tools::Rectangle aPrimitiveRectangle(impDumpToMetaFile(rContent, aContentMetafile)); |
2622 | | |
2623 | | // re-create a VCL-gradient from FillGradientPrimitive2D |
2624 | 0 | Gradient aVCLGradient; |
2625 | 0 | impConvertFillGradientAttributeToVCLGradient(aVCLGradient, pFiGradient->getFillGradient(), |
2626 | 0 | true); |
2627 | |
|
2628 | 0 | if (bSVGTransparencyColorStops) |
2629 | 0 | { |
2630 | | // tdf#155479 create action directly & add extra |
2631 | | // MCGR infos to the metafile, do that by adding - ONLY in |
2632 | | // case of SVG export - to the MetaFileAction. For that |
2633 | | // reason, do what OutputDevice::DrawTransparent will do, |
2634 | | // but locally. |
2635 | | // NOTE: That would be good for this whole |
2636 | | // VclMetafileProcessor2D anyways to allow to get it |
2637 | | // completely independent from OutputDevice in the long run |
2638 | 0 | GDIMetaFile* pMetaFile(mpOutputDevice->GetConnectMetaFile()); |
2639 | 0 | rtl::Reference<::MetaFloatTransparentAction> pAction(new MetaFloatTransparentAction( |
2640 | 0 | aContentMetafile, aPrimitiveRectangle.TopLeft(), aPrimitiveRectangle.GetSize(), |
2641 | 0 | std::move(aVCLGradient), std::move(aSVGTransparencyColorStops))); |
2642 | |
|
2643 | 0 | pMetaFile->AddAction(pAction); |
2644 | 0 | } |
2645 | 0 | else |
2646 | 0 | { |
2647 | | // render it to VCL (creates MetaFloatTransparentAction) |
2648 | 0 | mpOutputDevice->DrawTransparent(aContentMetafile, aPrimitiveRectangle.TopLeft(), |
2649 | 0 | aPrimitiveRectangle.GetSize(), aVCLGradient); |
2650 | 0 | } |
2651 | 0 | return; |
2652 | 0 | } |
2653 | | |
2654 | | // Here we need to create a correct replacement visualization for the |
2655 | | // TransparencePrimitive2D for the target metafile. |
2656 | | // I replaced the n'th iteration to convert-to-bitmap which was |
2657 | | // used here by using the existing tooling. The orig here was also producing |
2658 | | // transparency errors with test-file from tdf#155437 on the right part of the |
2659 | | // image. |
2660 | | // Just rely on existing tooling doing the right thing in one place, so also |
2661 | | // corrections/optimizations can be in one single place |
2662 | | |
2663 | | // Start by getting logic range of content, transform object-to-world, then world-to-view |
2664 | | // to get to discrete values ('pixels'). Matrix multiplication is right-to-left (and not |
2665 | | // commutative) |
2666 | 0 | basegfx::B2DRange aLogicRange(rTransparenceCandidate.getB2DRange(getViewInformation2D())); |
2667 | 0 | aLogicRange.transform(mpOutputDevice->GetViewTransformation() * maCurrentTransformation); |
2668 | | |
2669 | | // expand in discrete coordinates to next-bigger 'pixel' boundaries and remember |
2670 | | // created discrete range |
2671 | 0 | aLogicRange.expand( |
2672 | 0 | basegfx::B2DPoint(floor(aLogicRange.getMinX()), floor(aLogicRange.getMinY()))); |
2673 | 0 | aLogicRange.expand(basegfx::B2DPoint(ceil(aLogicRange.getMaxX()), ceil(aLogicRange.getMaxY()))); |
2674 | 0 | const basegfx::B2DRange aDiscreteRange(aLogicRange); |
2675 | | |
2676 | | // transform back from discrete to world coordinates: this creates the |
2677 | | // pixel-boundaries extended logic range we need to cover all content |
2678 | | // reliably |
2679 | 0 | aLogicRange.transform(mpOutputDevice->GetInverseViewTransformation()); |
2680 | | |
2681 | | // create transform embedding for renderer. Goal is to translate what we |
2682 | | // want to paint to top/left 0/0 and the calculated discrete size |
2683 | 0 | basegfx::B2DHomMatrix aEmbedding(basegfx::utils::createTranslateB2DHomMatrix( |
2684 | 0 | -aLogicRange.getMinX(), -aLogicRange.getMinY())); |
2685 | 0 | const double fLogicWidth( |
2686 | 0 | basegfx::fTools::equalZero(aLogicRange.getWidth()) ? 1.0 : aLogicRange.getWidth()); |
2687 | 0 | const double fLogicHeight( |
2688 | 0 | basegfx::fTools::equalZero(aLogicRange.getHeight()) ? 1.0 : aLogicRange.getHeight()); |
2689 | 0 | aEmbedding.scale(aDiscreteRange.getWidth() / fLogicWidth, |
2690 | 0 | aDiscreteRange.getHeight() / fLogicHeight); |
2691 | | |
2692 | | // use the whole TransparencePrimitive2D as input (no need to create a new |
2693 | | // one with the sub-contents, these are ref-counted) and add to embedding |
2694 | | // primitive2d::TransparencePrimitive2D& rTrCand(); |
2695 | 0 | primitive2d::Primitive2DContainer xEmbedSeq{ &const_cast<primitive2d::TransparencePrimitive2D&>( |
2696 | 0 | rTransparenceCandidate) }; |
2697 | | |
2698 | | // tdf#158743 when embedding, do not forget to 1st apply the evtl. used |
2699 | | // CurrentTransformation (right-to-left, apply that 1st) |
2700 | 0 | xEmbedSeq = primitive2d::Primitive2DContainer{ new primitive2d::TransformPrimitive2D( |
2701 | 0 | aEmbedding * maCurrentTransformation, std::move(xEmbedSeq)) }; |
2702 | | |
2703 | | // use empty ViewInformation & a useful MaximumQuadraticPixels |
2704 | | // limitation to paint the content |
2705 | 0 | const auto aViewInformation2D(geometry::createViewInformation2D({})); |
2706 | 0 | const sal_uInt32 nMaximumQuadraticPixels(500000); |
2707 | 0 | const Bitmap aBitmap(convertToBitmap( |
2708 | 0 | std::move(xEmbedSeq), aViewInformation2D, basegfx::fround(aDiscreteRange.getWidth()), |
2709 | 0 | basegfx::fround(aDiscreteRange.getHeight()), nMaximumQuadraticPixels)); |
2710 | | |
2711 | | // add to target metafile (will create MetaFloatTransparentAction) |
2712 | 0 | mpOutputDevice->DrawBitmap(Point(basegfx::fround<tools::Long>(aLogicRange.getMinX()), |
2713 | 0 | basegfx::fround<tools::Long>(aLogicRange.getMinY())), |
2714 | 0 | Size(basegfx::fround<tools::Long>(aLogicRange.getWidth()), |
2715 | 0 | basegfx::fround<tools::Long>(aLogicRange.getHeight())), |
2716 | 0 | aBitmap); |
2717 | 0 | } |
2718 | | |
2719 | | void VclMetafileProcessor2D::processStructureTagPrimitive2D( |
2720 | | const primitive2d::StructureTagPrimitive2D& rStructureTagCandidate) |
2721 | 0 | { |
2722 | 0 | ::comphelper::ValueRestorationGuard const g(mpCurrentStructureTag, &rStructureTagCandidate); |
2723 | | |
2724 | | // structured tag primitive |
2725 | 0 | const vcl::pdf::StructElement& rTagElement(rStructureTagCandidate.getStructureElement()); |
2726 | 0 | bool bTagUsed((vcl::pdf::StructElement::NonStructElement != rTagElement)); |
2727 | 0 | ::std::optional<sal_Int32> oAnchorParent; |
2728 | |
|
2729 | 0 | if (!rStructureTagCandidate.isTaggedSdrObject()) |
2730 | 0 | { |
2731 | 0 | bTagUsed = false; |
2732 | 0 | } |
2733 | |
|
2734 | 0 | if (mpPDFExtOutDevData && bTagUsed) |
2735 | 0 | { |
2736 | | // foreground object: tag as regular structure element |
2737 | 0 | if (!rStructureTagCandidate.isBackground()) |
2738 | 0 | { |
2739 | 0 | if (rStructureTagCandidate.GetAnchorStructureElementKey() != nullptr) |
2740 | 0 | { |
2741 | 0 | sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement( |
2742 | 0 | rStructureTagCandidate.GetAnchorStructureElementKey()); |
2743 | 0 | oAnchorParent.emplace(mpPDFExtOutDevData->GetCurrentStructureElement()); |
2744 | 0 | mpPDFExtOutDevData->SetCurrentStructureElement(id); |
2745 | 0 | } |
2746 | 0 | mpPDFExtOutDevData->WrapBeginStructureElement(rTagElement); |
2747 | 0 | switch (rTagElement) |
2748 | 0 | { |
2749 | 0 | case vcl::pdf::StructElement::H1: |
2750 | 0 | case vcl::pdf::StructElement::H2: |
2751 | 0 | case vcl::pdf::StructElement::H3: |
2752 | 0 | case vcl::pdf::StructElement::H4: |
2753 | 0 | case vcl::pdf::StructElement::H5: |
2754 | 0 | case vcl::pdf::StructElement::H6: |
2755 | 0 | case vcl::pdf::StructElement::Paragraph: |
2756 | 0 | case vcl::pdf::StructElement::Heading: |
2757 | 0 | case vcl::pdf::StructElement::Title: |
2758 | 0 | case vcl::pdf::StructElement::Caption: |
2759 | 0 | case vcl::pdf::StructElement::BlockQuote: |
2760 | 0 | case vcl::pdf::StructElement::Table: |
2761 | 0 | case vcl::pdf::StructElement::TableRow: |
2762 | 0 | case vcl::pdf::StructElement::Formula: |
2763 | 0 | case vcl::pdf::StructElement::Figure: |
2764 | 0 | case vcl::pdf::StructElement::Annot: |
2765 | 0 | mpPDFExtOutDevData->SetStructureAttribute(vcl::pdf::PDFWriter::Placement, |
2766 | 0 | vcl::pdf::PDFWriter::Block); |
2767 | 0 | break; |
2768 | 0 | case vcl::pdf::StructElement::TableData: |
2769 | 0 | case vcl::pdf::StructElement::TableHeader: |
2770 | 0 | mpPDFExtOutDevData->SetStructureAttribute(vcl::pdf::PDFWriter::Placement, |
2771 | 0 | vcl::pdf::PDFWriter::Inline); |
2772 | 0 | break; |
2773 | 0 | default: |
2774 | 0 | break; |
2775 | 0 | } |
2776 | 0 | switch (rTagElement) |
2777 | 0 | { |
2778 | 0 | case vcl::pdf::StructElement::Table: |
2779 | 0 | case vcl::pdf::StructElement::Formula: |
2780 | 0 | case vcl::pdf::StructElement::Figure: |
2781 | 0 | case vcl::pdf::StructElement::Annot: |
2782 | 0 | { |
2783 | 0 | auto const range(rStructureTagCandidate.getB2DRange(getViewInformation2D())); |
2784 | 0 | tools::Rectangle const aLogicRect( |
2785 | 0 | basegfx::fround<tools::Long>(range.getMinX()), |
2786 | 0 | basegfx::fround<tools::Long>(range.getMinY()), |
2787 | 0 | basegfx::fround<tools::Long>(range.getMaxX()), |
2788 | 0 | basegfx::fround<tools::Long>(range.getMaxY())); |
2789 | 0 | mpPDFExtOutDevData->SetStructureBoundingBox(aLogicRect); |
2790 | 0 | break; |
2791 | 0 | } |
2792 | 0 | default: |
2793 | 0 | break; |
2794 | 0 | } |
2795 | 0 | if (rTagElement == vcl::pdf::StructElement::Annot) |
2796 | 0 | { |
2797 | 0 | mpPDFExtOutDevData->SetStructureAnnotIds(rStructureTagCandidate.GetAnnotIds()); |
2798 | 0 | } |
2799 | 0 | if (rTagElement == vcl::pdf::StructElement::TableHeader) |
2800 | 0 | { |
2801 | 0 | mpPDFExtOutDevData->SetStructureAttribute(vcl::pdf::PDFWriter::Scope, |
2802 | 0 | vcl::pdf::PDFWriter::Column); |
2803 | 0 | } |
2804 | 0 | } |
2805 | | // background object |
2806 | 0 | else |
2807 | 0 | { |
2808 | | // background image: tag as artifact |
2809 | 0 | if (rStructureTagCandidate.isImage()) |
2810 | 0 | mpPDFExtOutDevData->WrapBeginStructureElement( |
2811 | 0 | vcl::pdf::StructElement::NonStructElement); |
2812 | | // any other background object: do not tag |
2813 | 0 | else |
2814 | 0 | assert(false); |
2815 | 0 | } |
2816 | 0 | } |
2817 | | |
2818 | | // process children normally |
2819 | 0 | process(rStructureTagCandidate.getChildren()); |
2820 | |
|
2821 | 0 | if (mpPDFExtOutDevData && bTagUsed) |
2822 | 0 | { |
2823 | | // write end tag |
2824 | 0 | mpPDFExtOutDevData->EndStructureElement(); |
2825 | 0 | if (oAnchorParent) |
2826 | 0 | { |
2827 | 0 | mpPDFExtOutDevData->SetCurrentStructureElement(*oAnchorParent); |
2828 | 0 | } |
2829 | 0 | } |
2830 | 0 | } |
2831 | | |
2832 | | } // end of namespace |
2833 | | |
2834 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |