1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
22 #include "vclmetafileprocessor2d.hxx"
23 #include "vclpixelprocessor2d.hxx"
24 #include <rtl/ustring.hxx>
25 #include <tools/gen.hxx>
26 #include <tools/stream.hxx>
27 #include <comphelper/diagnose_ex.hxx>
28 #include <comphelper/flagguard.hxx>
29 #include <comphelper/processfactory.hxx>
30 #include <config_global.h>
31 #include <basegfx/polygon/b2dpolygonclipper.hxx>
32 #include <basegfx/polygon/b2dpolypolygontools.hxx>
33 #include <basegfx/polygon/b2dpolygontools.hxx>
34 #include <basegfx/polygon/b2dlinegeometry.hxx>
35 #include <basegfx/utils/gradienttools.hxx>
36 #include <vcl/virdev.hxx>
37 #include <vcl/gdimtf.hxx>
38 #include <vcl/gradient.hxx>
39 #include <vcl/graphictools.hxx>
40 #include <vcl/metaact.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/converters.hxx>
68 #include <basegfx/matrix/b2dhommatrixtools.hxx>
69 #include <tools/vcompat.hxx>
71 #include <com/sun/star/awt/XControl.hpp>
72 #include <com/sun/star/i18n/BreakIterator.hpp>
73 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
74 #include <com/sun/star/i18n/WordType.hpp>
75 #include <com/sun/star/beans/XPropertySet.hpp>
77 using namespace com::sun::star
;
79 // #112245# definition for maximum allowed point count due to Metafile target.
80 // To be on the safe side with the old tools polygon, use slightly less than
81 // the theoretical maximum (bad experiences with tools polygon)
83 #define MAX_POLYGON_POINT_COUNT_METAFILE (0x0000fff0)
87 // #112245# helper to split line polygon in half
88 void splitLinePolygon(const basegfx::B2DPolygon
& rBasePolygon
, basegfx::B2DPolygon
& o_aLeft
,
89 basegfx::B2DPolygon
& o_aRight
)
91 const sal_uInt32
nCount(rBasePolygon
.count());
95 const sal_uInt32
nHalfCount((nCount
- 1) >> 1);
97 o_aLeft
= basegfx::B2DPolygon(rBasePolygon
, 0, nHalfCount
+ 1);
98 o_aLeft
.setClosed(false);
100 o_aRight
= basegfx::B2DPolygon(rBasePolygon
, nHalfCount
, nCount
- nHalfCount
);
101 o_aRight
.setClosed(false);
103 if (rBasePolygon
.isClosed())
105 o_aRight
.append(rBasePolygon
.getB2DPoint(0));
107 if (rBasePolygon
.areControlPointsUsed())
109 o_aRight
.setControlPoints(o_aRight
.count() - 1, rBasePolygon
.getPrevControlPoint(0),
110 rBasePolygon
.getNextControlPoint(0));
121 // #112245# helper to evtl. split filled polygons to maximum metafile point count
122 void fillPolyPolygonNeededToBeSplit(basegfx::B2DPolyPolygon
& rPolyPolygon
)
124 const sal_uInt32
nPolyCount(rPolyPolygon
.count());
129 basegfx::B2DPolyPolygon aSplitted
;
131 for (sal_uInt32
a(0); a
< nPolyCount
; a
++)
133 const basegfx::B2DPolygon
& aCandidate(rPolyPolygon
.getB2DPolygon(a
));
134 const sal_uInt32
nPointCount(aCandidate
.count());
135 bool bNeedToSplit(false);
137 if (aCandidate
.areControlPointsUsed())
139 // compare with the maximum for bezier curved polygons
140 bNeedToSplit
= nPointCount
> ((MAX_POLYGON_POINT_COUNT_METAFILE
/ 3L) - 1);
144 // compare with the maximum for simple point polygons
145 bNeedToSplit
= nPointCount
> (MAX_POLYGON_POINT_COUNT_METAFILE
- 1);
150 // need to split the partial polygon
151 const basegfx::B2DRange
aRange(aCandidate
.getB2DRange());
152 const basegfx::B2DPoint
aCenter(aRange
.getCenter());
154 if (aRange
.getWidth() > aRange
.getHeight())
156 // clip in left and right
157 const basegfx::B2DPolyPolygon
aLeft(basegfx::utils::clipPolygonOnParallelAxis(
158 aCandidate
, false, true, aCenter
.getX(), false));
159 const basegfx::B2DPolyPolygon
aRight(basegfx::utils::clipPolygonOnParallelAxis(
160 aCandidate
, false, false, aCenter
.getX(), false));
162 aSplitted
.append(aLeft
);
163 aSplitted
.append(aRight
);
167 // clip in top and bottom
168 const basegfx::B2DPolyPolygon
aTop(basegfx::utils::clipPolygonOnParallelAxis(
169 aCandidate
, true, true, aCenter
.getY(), false));
170 const basegfx::B2DPolyPolygon
aBottom(basegfx::utils::clipPolygonOnParallelAxis(
171 aCandidate
, true, false, aCenter
.getY(), false));
173 aSplitted
.append(aTop
);
174 aSplitted
.append(aBottom
);
179 aSplitted
.append(aCandidate
);
183 if (aSplitted
.count() != nPolyCount
)
185 rPolyPolygon
= aSplitted
;
189 /** Filter input polypolygon for effectively empty sub-fills
191 Needed to fix fdo#37559
194 tools::PolyPolygon to filter
196 @return converted tools PolyPolygon, w/o one-point fills
198 tools::PolyPolygon
getFillPolyPolygon(const ::basegfx::B2DPolyPolygon
& rPoly
)
200 // filter input rPoly
201 basegfx::B2DPolyPolygon aPoly
;
202 sal_uInt32
nCount(rPoly
.count());
203 for (sal_uInt32 i
= 0; i
< nCount
; ++i
)
205 const basegfx::B2DPolygon
& aCandidate(rPoly
.getB2DPolygon(i
));
206 if (!aCandidate
.isClosed() || aCandidate
.count() > 1)
207 aPoly
.append(aCandidate
);
209 return tools::PolyPolygon(aPoly
);
212 } // end of anonymous namespace
214 namespace drawinglayer::processor2d
217 VclMetafileProcessor2D::impDumpToMetaFile(const primitive2d::Primitive2DContainer
& rContent
,
218 GDIMetaFile
& o_rContentMetafile
)
220 // Prepare VDev, MetaFile and connections
221 OutputDevice
* pLastOutputDevice
= mpOutputDevice
;
222 GDIMetaFile
* pLastMetafile
= mpMetaFile
;
223 basegfx::B2DRange
aPrimitiveRange(rContent
.getB2DRange(getViewInformation2D()));
225 // transform primitive range with current transformation (e.g shadow offset)
226 aPrimitiveRange
.transform(maCurrentTransformation
);
228 const tools::Rectangle
aPrimitiveRectangle(
229 basegfx::fround(aPrimitiveRange
.getMinX()), basegfx::fround(aPrimitiveRange
.getMinY()),
230 basegfx::fround(aPrimitiveRange
.getMaxX()), basegfx::fround(aPrimitiveRange
.getMaxY()));
231 ScopedVclPtrInstance
<VirtualDevice
> aContentVDev
;
232 MapMode
aNewMapMode(pLastOutputDevice
->GetMapMode());
234 mpOutputDevice
= aContentVDev
.get();
235 mpMetaFile
= &o_rContentMetafile
;
236 aContentVDev
->EnableOutput(false);
237 aContentVDev
->SetMapMode(pLastOutputDevice
->GetMapMode());
238 o_rContentMetafile
.Record(aContentVDev
.get());
239 aContentVDev
->SetLineColor(pLastOutputDevice
->GetLineColor());
240 aContentVDev
->SetFillColor(pLastOutputDevice
->GetFillColor());
241 aContentVDev
->SetFont(pLastOutputDevice
->GetFont());
242 aContentVDev
->SetDrawMode(pLastOutputDevice
->GetDrawMode());
243 aContentVDev
->SetSettings(pLastOutputDevice
->GetSettings());
244 aContentVDev
->SetRefPoint(pLastOutputDevice
->GetRefPoint());
250 o_rContentMetafile
.Stop();
251 o_rContentMetafile
.WindStart();
252 aNewMapMode
.SetOrigin(aPrimitiveRectangle
.TopLeft());
253 o_rContentMetafile
.SetPrefMapMode(aNewMapMode
);
254 o_rContentMetafile
.SetPrefSize(aPrimitiveRectangle
.GetSize());
255 mpOutputDevice
= pLastOutputDevice
;
256 mpMetaFile
= pLastMetafile
;
258 return aPrimitiveRectangle
;
261 void VclMetafileProcessor2D::impConvertFillGradientAttributeToVCLGradient(
262 Gradient
& o_rVCLGradient
, const attribute::FillGradientAttribute
& rFiGrAtt
,
263 bool bIsTransparenceGradient
) const
265 const basegfx::BColor
aStartColor(rFiGrAtt
.getColorStops().front().getStopColor());
266 const basegfx::BColor
aEndColor(rFiGrAtt
.getColorStops().back().getStopColor());
268 if (bIsTransparenceGradient
)
270 // it's about transparence channel intensities (black/white), do not use color modifier
271 o_rVCLGradient
.SetStartColor(Color(aStartColor
));
272 o_rVCLGradient
.SetEndColor(Color(aEndColor
));
276 // use color modifier to influence start/end color of gradient
277 o_rVCLGradient
.SetStartColor(Color(maBColorModifierStack
.getModifiedColor(aStartColor
)));
278 o_rVCLGradient
.SetEndColor(Color(maBColorModifierStack
.getModifiedColor(aEndColor
)));
281 o_rVCLGradient
.SetAngle(
282 Degree10(static_cast<sal_uInt32
>(basegfx::rad2deg
<10>(rFiGrAtt
.getAngle()))));
283 o_rVCLGradient
.SetBorder(static_cast<sal_uInt16
>(rFiGrAtt
.getBorder() * 100.0));
284 o_rVCLGradient
.SetOfsX(static_cast<sal_uInt16
>(rFiGrAtt
.getOffsetX() * 100.0));
285 o_rVCLGradient
.SetOfsY(static_cast<sal_uInt16
>(rFiGrAtt
.getOffsetY() * 100.0));
286 o_rVCLGradient
.SetSteps(rFiGrAtt
.getSteps());
288 // defaults for intensity; those were computed into the start/end colors already
289 o_rVCLGradient
.SetStartIntensity(100);
290 o_rVCLGradient
.SetEndIntensity(100);
291 o_rVCLGradient
.SetStyle(rFiGrAtt
.getStyle());
294 void VclMetafileProcessor2D::impStartSvtGraphicFill(SvtGraphicFill
const* pSvtGraphicFill
)
296 if (pSvtGraphicFill
&& !mnSvtGraphicFillCount
)
298 SvMemoryStream aMemStm
;
300 WriteSvtGraphicFill(aMemStm
, *pSvtGraphicFill
);
301 mpMetaFile
->AddAction(new MetaCommentAction(
302 "XPATHFILL_SEQ_BEGIN", 0, static_cast<const sal_uInt8
*>(aMemStm
.GetData()),
304 mnSvtGraphicFillCount
++;
308 void VclMetafileProcessor2D::impEndSvtGraphicFill(SvtGraphicFill
const* pSvtGraphicFill
)
310 if (pSvtGraphicFill
&& mnSvtGraphicFillCount
)
312 mnSvtGraphicFillCount
--;
313 mpMetaFile
->AddAction(new MetaCommentAction("XPATHFILL_SEQ_END"));
317 double VclMetafileProcessor2D::getTransformedLineWidth(double fWidth
) const
319 // #i113922# the LineWidth is duplicated in the MetaPolylineAction,
320 // and also inside the SvtGraphicStroke and needs transforming into
321 // the same space as its coordinates here cf. fdo#61789
322 // This is a partial fix. When an object transformation is used which
323 // e.g. contains a scaleX != scaleY, an unproportional scaling will happen.
324 const basegfx::B2DVector
aDiscreteUnit(maCurrentTransformation
325 * basegfx::B2DVector(fWidth
, 0.0));
327 return aDiscreteUnit
.getLength();
330 std::unique_ptr
<SvtGraphicStroke
> VclMetafileProcessor2D::impTryToCreateSvtGraphicStroke(
331 const basegfx::B2DPolygon
& rB2DPolygon
, const basegfx::BColor
* pColor
,
332 const attribute::LineAttribute
* pLineAttribute
,
333 const attribute::StrokeAttribute
* pStrokeAttribute
,
334 const attribute::LineStartEndAttribute
* pStart
, const attribute::LineStartEndAttribute
* pEnd
)
336 std::unique_ptr
<SvtGraphicStroke
> pRetval
;
338 if (rB2DPolygon
.count() && !mnSvtGraphicStrokeCount
)
340 basegfx::B2DPolygon
aLocalPolygon(rB2DPolygon
);
341 basegfx::BColor aStrokeColor
;
342 basegfx::B2DPolyPolygon aStartArrow
;
343 basegfx::B2DPolyPolygon aEndArrow
;
347 aStrokeColor
= *pColor
;
349 else if (pLineAttribute
)
351 aStrokeColor
= maBColorModifierStack
.getModifiedColor(pLineAttribute
->getColor());
354 // It IS needed to record the stroke color at all in the metafile,
355 // SvtGraphicStroke has NO entry for stroke color(!)
356 mpOutputDevice
->SetLineColor(Color(aStrokeColor
));
358 if (!aLocalPolygon
.isClosed())
360 double fPolyLength(0.0);
364 if (pStart
&& pStart
->isActive())
366 fPolyLength
= basegfx::utils::getLength(aLocalPolygon
);
368 aStartArrow
= basegfx::utils::createAreaGeometryForLineStartEnd(
369 aLocalPolygon
, pStart
->getB2DPolyPolygon(), true, pStart
->getWidth(),
370 fPolyLength
, pStart
->isCentered() ? 0.5 : 0.0, &fStart
);
373 if (pEnd
&& pEnd
->isActive())
375 if (basegfx::fTools::equalZero(fPolyLength
))
377 fPolyLength
= basegfx::utils::getLength(aLocalPolygon
);
380 aEndArrow
= basegfx::utils::createAreaGeometryForLineStartEnd(
381 aLocalPolygon
, pEnd
->getB2DPolyPolygon(), false, pEnd
->getWidth(), fPolyLength
,
382 pEnd
->isCentered() ? 0.5 : 0.0, &fEnd
);
385 if (0.0 != fStart
|| 0.0 != fEnd
)
387 // build new poly, consume something from old poly
388 aLocalPolygon
= basegfx::utils::getSnippetAbsolute(aLocalPolygon
, fStart
,
389 fPolyLength
- fEnd
, fPolyLength
);
393 SvtGraphicStroke::JoinType
eJoin(SvtGraphicStroke::joinNone
);
394 SvtGraphicStroke::CapType
eCap(SvtGraphicStroke::capButt
);
395 double fLineWidth(0.0);
396 double fMiterLength(0.0);
397 SvtGraphicStroke::DashArray aDashArray
;
401 fLineWidth
= fMiterLength
= getTransformedLineWidth(pLineAttribute
->getWidth());
404 switch (pLineAttribute
->getLineJoin())
406 case basegfx::B2DLineJoin::NONE
:
408 eJoin
= SvtGraphicStroke::joinNone
;
411 case basegfx::B2DLineJoin::Bevel
:
413 eJoin
= SvtGraphicStroke::joinBevel
;
416 case basegfx::B2DLineJoin::Miter
:
418 eJoin
= SvtGraphicStroke::joinMiter
;
419 // ATM 15 degrees is assumed
420 // TODO wait for P1383R0 and C++20's std::numbers::pi
421 fMiterLength
/= std::sin(M_PI
/ 12);
424 case basegfx::B2DLineJoin::Round
:
426 eJoin
= SvtGraphicStroke::joinRound
;
432 switch (pLineAttribute
->getLineCap())
434 default: /* css::drawing::LineCap_BUTT */
436 eCap
= SvtGraphicStroke::capButt
;
439 case css::drawing::LineCap_ROUND
:
441 eCap
= SvtGraphicStroke::capRound
;
444 case css::drawing::LineCap_SQUARE
:
446 eCap
= SvtGraphicStroke::capSquare
;
452 if (pStrokeAttribute
)
455 aDashArray
= pStrokeAttribute
->getDotDashArray();
458 // #i101734# apply current object transformation to created geometry.
459 // This is a partial fix. When an object transformation is used which
460 // e.g. contains a scaleX != scaleY, an unproportional scaling would
461 // have to be applied to the evtl. existing fat line. The current
462 // concept of PDF export and SvtGraphicStroke usage does simply not
463 // allow handling such definitions. The only clean way would be to
464 // add the transformation to SvtGraphicStroke and to handle it there
465 aLocalPolygon
.transform(maCurrentTransformation
);
466 aStartArrow
.transform(maCurrentTransformation
);
467 aEndArrow
.transform(maCurrentTransformation
);
470 new SvtGraphicStroke(tools::Polygon(aLocalPolygon
), tools::PolyPolygon(aStartArrow
),
471 tools::PolyPolygon(aEndArrow
), mfCurrentUnifiedTransparence
,
472 fLineWidth
, eCap
, eJoin
, fMiterLength
, std::move(aDashArray
)));
478 void VclMetafileProcessor2D::impStartSvtGraphicStroke(SvtGraphicStroke
const* pSvtGraphicStroke
)
480 if (pSvtGraphicStroke
&& !mnSvtGraphicStrokeCount
)
482 SvMemoryStream aMemStm
;
484 WriteSvtGraphicStroke(aMemStm
, *pSvtGraphicStroke
);
485 mpMetaFile
->AddAction(new MetaCommentAction(
486 "XPATHSTROKE_SEQ_BEGIN", 0, static_cast<const sal_uInt8
*>(aMemStm
.GetData()),
488 mnSvtGraphicStrokeCount
++;
492 void VclMetafileProcessor2D::impEndSvtGraphicStroke(SvtGraphicStroke
const* pSvtGraphicStroke
)
494 if (pSvtGraphicStroke
&& mnSvtGraphicStrokeCount
)
496 mnSvtGraphicStrokeCount
--;
497 mpMetaFile
->AddAction(new MetaCommentAction("XPATHSTROKE_SEQ_END"));
501 void VclMetafileProcessor2D::popStructureElement(vcl::PDFWriter::StructElement eElem
)
503 if (!maListElements
.empty() && maListElements
.top() == eElem
)
505 maListElements
.pop();
506 mpPDFExtOutDevData
->EndStructureElement();
510 void VclMetafileProcessor2D::popListItem()
512 popStructureElement(vcl::PDFWriter::LIBody
);
513 popStructureElement(vcl::PDFWriter::ListItem
);
516 void VclMetafileProcessor2D::popList()
519 popStructureElement(vcl::PDFWriter::List
);
522 // init static break iterator
523 uno::Reference
<css::i18n::XBreakIterator
> VclMetafileProcessor2D::mxBreakIterator
;
525 VclMetafileProcessor2D::VclMetafileProcessor2D(const geometry::ViewInformation2D
& rViewInformation
,
526 OutputDevice
& rOutDev
)
527 : VclProcessor2D(rViewInformation
, rOutDev
)
528 , mpMetaFile(rOutDev
.GetConnectMetaFile())
529 , mnSvtGraphicFillCount(0)
530 , mnSvtGraphicStrokeCount(0)
531 , mfCurrentUnifiedTransparence(0.0)
532 , mpPDFExtOutDevData(dynamic_cast<vcl::PDFExtOutDevData
*>(rOutDev
.GetExtOutDevData()))
533 , mnCurrentOutlineLevel(-1)
534 , mbInListItem(false)
535 , mbBulletPresent(false)
537 OSL_ENSURE(rOutDev
.GetConnectMetaFile(),
538 "VclMetafileProcessor2D: Used on OutDev which has no MetaFile Target (!)");
539 // draw to logic coordinates, do not initialize maCurrentTransformation to viewTransformation
540 // but only to ObjectTransformation. Do not change MapMode of destination.
541 maCurrentTransformation
= rViewInformation
.getObjectTransformation();
544 VclMetafileProcessor2D::~VclMetafileProcessor2D()
546 // MapMode was not changed, no restore necessary
549 /***********************************************************************************************
551 Support of MetaCommentActions in the VclMetafileProcessor2D
552 Found MetaCommentActions and how they are supported:
554 XGRAD_SEQ_BEGIN, XGRAD_SEQ_END:
556 Used inside OutputDevice::DrawGradient to mark the start and end of a MetaGradientEx action.
557 It is used in various exporters/importers to have direct access to the gradient before it
558 is rendered by VCL (and thus fragmented to polygon color actions and others). On that base, e.g.
559 the Metafile to SdrObject import creates its gradient objects.
560 Best (and safest) way to support it here is to use PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D,
561 map it back to the corresponding tools tools::PolyPolygon and the Gradient and just call
562 OutputDevice::DrawGradient which creates the necessary compatible actions.
564 XPATHFILL_SEQ_BEGIN, XPATHFILL_SEQ_END:
566 Two producers, one is vcl/source/gdi/gdimtf.cxx, line 1273. There, it is transformed
567 inside GDIMetaFile::Rotate, nothing to take care of here.
568 The second producer is in graphics/svx/source/svdraw/impgrfll.cxx, line 374. This is used
569 with each incarnation of Imp_GraphicFill when a metafile is recorded, fillstyle is not
570 XFILL_NONE and not completely transparent. It creates a SvtGraphicFill and streams it
571 to the comment action. A closing end token is created in the destructor.
572 Usages of Imp_GraphicFill are in Do_Paint_Object-methods of SdrCircObj, SdrPathObj and
574 The token users pick various actions from SvtGraphicFill, so it may need to be added for all kind
575 of filled objects, even simple colored polygons. It is added as extra information; the
576 Metafile actions between the two tokens are interpreted as output generated from those
577 fills. Thus, users have the choice to use the SvtGraphicFill info or the created output
579 Even for XFillTransparenceItem it is used, thus it may need to be supported in
580 UnifiedTransparencePrimitive2D, too, when interpreted as normally filled PolyPolygon.
582 PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D,
583 PRIMITIVE2D_ID_POLYPOLYGONHATCHPRIMITIVE2D,
584 PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D,
585 PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D,
586 and for PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D when detected unified transparence
588 XPATHSTROKE_SEQ_BEGIN, XPATHSTROKE_SEQ_END:
590 Similar to pathfill, but using SvtGraphicStroke instead. It also has two producers where one
591 is also the GDIMetaFile::Rotate. Another user is MetaCommentAction::Move which modifies the
592 contained path accordingly.
593 The other one is SdrObject::Imp_DrawLineGeometry. It's done when MetaFile is set at OutDev and
594 only when geometry is a single polygon (!). I see no reason for that; in the PS exporter this
595 would hinder to make use of tools::PolyPolygon strokes. I will need to add support at:
596 PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D
597 PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D
598 PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D
599 This can be done hierarchical, too.
600 Okay, base implementation done based on those three primitives.
602 FIELD_SEQ_BEGIN, FIELD_SEQ_END
604 Used from slideshow for URLs, created from diverse SvxField implementations inside
605 createBeginComment()/createEndComment(). createBeginComment() is used from editeng\impedit3.cxx
606 inside ImpEditEngine::Paint.
607 Created TextHierarchyFieldPrimitive2D and added needed infos there; it is a group primitive and wraps
608 text primitives (but is not limited to that). It contains the field type if special actions for the
609 support of FIELD_SEQ_BEGIN/END are needed; this is the case for Page and URL fields. If more is
610 needed, it may be supported there.
611 FIELD_SEQ_BEGIN;PageField
613 Okay, these are now completely supported by TextHierarchyFieldPrimitive2D. URL works, too.
617 XTEXT_EOC(i) end of character
618 XTEXT_EOW(i) end of word
619 XTEXT_EOS(i) end of sentence
621 this three are with index and are created with the help of an i18n::XBreakIterator in
622 ImplDrawWithComments. Simplifying, moving out text painting, reworking to create some
623 data structure for holding those TEXT infos.
624 Supported directly by TextSimplePortionPrimitive2D with adding a Locale to the basic text
625 primitive. In the MetaFileRenderer, the creation is now done (see below). This has the advantage
626 that this creations do not need to be done for all paints all the time. This would be
627 expensive since the BreakIterator and it's usage is expensive and for each paint also the
628 whole character stops would need to be created.
629 Created only for TextDecoratedPortionPrimitive2D due to XTEXT_EOL and XTEXT_EOP (see below)
631 XTEXT_EOL() end of line
632 XTEXT_EOP() end of paragraph
634 First try with boolean marks at TextDecoratedPortionPrimitive2D did not work too well,
635 i decided to solve it with structure. I added the TextHierarchyPrimitives for this,
637 - TextHierarchyLinePrimitive2D: Encapsulates single line
638 - TextHierarchyParagraphPrimitive2D: Encapsulates single paragraph
639 - TextHierarchyBlockPrimitive2D: encapsulates object texts (only one ATM)
640 Those are now supported in hierarchy. This means the MetaFile renderer will support them
641 by using them, recursively using their content and adding MetaFile comments as needed.
642 This also means that when another text layouter will be used it will be necessary to
643 create/support the same HierarchyPrimitives to support users.
644 To transport the information using this hierarchy is best suited to all future needs;
645 the slideshow will be able to profit from it directly when using primitives; all other
646 renderers not interested in the text structure will just ignore the encapsulations.
648 XTEXT_PAINTSHAPE_BEGIN, XTEXT_PAINTSHAPE_END
649 Supported now by the TextHierarchyBlockPrimitive2D.
651 EPSReplacementGraphic:
652 Only used in goodies\source\filter.vcl\ieps\ieps.cxx and svx\source\xml\xmlgrhlp.cxx to
653 hold the original EPS which was imported in the same MetaFile as first 2 entries. Only
654 used to export the original again (if exists).
655 Not necessary to support with MetaFileRenderer.
657 XTEXT_SCROLLRECT, XTEXT_PAINTRECT
658 Currently used to get extra MetaFile infos using GraphicExporter which again uses
659 SdrTextObj::GetTextScrollMetaFileAndRectangle(). ATM works with primitives since
660 the rectangle data is added directly by the GraphicsExporter as comment. Does not need
661 to be adapted at once.
662 When adapting later, the only user - the diashow - should directly use the provided
663 Animation infos in the appropriate primitives (e.g. AnimatedSwitchPrimitive2D)
665 PRNSPOOL_TRANSPARENTBITMAP_BEGIN, PRNSPOOL_TRANSPARENTBITMAP_END
666 VCL usage when printing PL -> THB. Okay, THB confirms that it is only used as
667 a fix (hack) while VCL printing. It is needed to not downscale a bitmap which
668 was explicitly created for the printer already again to some default maximum
670 Nothing to do here for the primitive renderer.
672 Support for vcl::PDFExtOutDevData:
673 PL knows that SJ did that stuff, it's used to hold a pointer to PDFExtOutDevData at
674 the OutDev. When set, some extra data is written there. Trying simple PDF export and
675 watching if I get those infos.
676 Well, a PDF export does not use e.g. ImpEditEngine::Paint since the PdfFilter uses
677 the SdXImpressDocument::render and thus uses the VclMetafileProcessor2D. I will check
678 if I get a PDFExtOutDevData at the target output device.
679 Indeed, I get one. Checking what all may be done when that extra-device-info is there.
681 All in all I have to talk to SJ. I will need to emulate some of those actions, but
682 i need to discuss which ones.
683 In the future, all those infos would be taken from the primitive sequence anyways,
684 thus these extensions would potentially be temporary, too.
685 Discussed with SJ, added the necessary support and tested it. Details follow.
687 - In ImpEditEngine::Paint, paragraph infos and URL stuff is added.
688 Added in primitive MetaFile renderer.
689 Checking URL: Indeed, current version exports it, but it is missing in primitive
690 CWS version. Adding support.
691 Okay, URLs work. Checked, Done.
693 - UnoControlPDFExportContact is only created when PDFExtOutDevData is used at the
694 target and uno control data is created in UnoControlPDFExportContact::do_PaintObject.
695 This was added in primitive MetaFile renderer.
696 Checked form control export, it works well. Done.
698 - In goodies, in GraphicObject::Draw, when the used Graphic is linked, infos are
699 generated. I will need to check what happens here with primitives.
700 To support, use of GraphicPrimitive2D (PRIMITIVE2D_ID_GRAPHICPRIMITIVE2D) may be needed.
701 Added support, but feature is broken in main version, so i cannot test at all.
702 Writing a bug to CL (or SJ) and seeing what happens (#i80380#).
703 SJ took a look and we got it working. Tested VCL MetaFile Renderer based export,
704 as intended, the original file is exported. Works, Done.
709 - Maybe there are more places to take care of for vcl::PDFExtOutDevData!
712 ****************************************************************************************************/
714 void VclMetafileProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D
& rCandidate
)
716 switch (rCandidate
.getPrimitive2DID())
718 case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D
:
720 // directdraw of wrong spell primitive
721 // Ignore for VclMetafileProcessor2D, this is for printing and MetaFile recording only
724 case PRIMITIVE2D_ID_GRAPHICPRIMITIVE2D
:
726 processGraphicPrimitive2D(
727 static_cast<const primitive2d::GraphicPrimitive2D
&>(rCandidate
));
730 case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D
:
732 processControlPrimitive2D(
733 static_cast<const primitive2d::ControlPrimitive2D
&>(rCandidate
));
736 case PRIMITIVE2D_ID_TEXTHIERARCHYFIELDPRIMITIVE2D
:
738 processTextHierarchyFieldPrimitive2D(
739 static_cast<const primitive2d::TextHierarchyFieldPrimitive2D
&>(rCandidate
));
742 case PRIMITIVE2D_ID_TEXTHIERARCHYLINEPRIMITIVE2D
:
744 processTextHierarchyLinePrimitive2D(
745 static_cast<const primitive2d::TextHierarchyLinePrimitive2D
&>(rCandidate
));
748 case PRIMITIVE2D_ID_TEXTHIERARCHYBULLETPRIMITIVE2D
:
750 processTextHierarchyBulletPrimitive2D(
751 static_cast<const primitive2d::TextHierarchyBulletPrimitive2D
&>(rCandidate
));
754 case PRIMITIVE2D_ID_TEXTHIERARCHYPARAGRAPHPRIMITIVE2D
:
756 processTextHierarchyParagraphPrimitive2D(
757 static_cast<const primitive2d::TextHierarchyParagraphPrimitive2D
&>(rCandidate
));
760 case PRIMITIVE2D_ID_TEXTHIERARCHYBLOCKPRIMITIVE2D
:
762 processTextHierarchyBlockPrimitive2D(
763 static_cast<const primitive2d::TextHierarchyBlockPrimitive2D
&>(rCandidate
));
766 case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D
:
767 case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D
:
769 // for supporting TEXT_ MetaFile actions there is more to do here; get the candidate
770 processTextSimplePortionPrimitive2D(
771 static_cast<const primitive2d::TextSimplePortionPrimitive2D
&>(rCandidate
));
774 case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D
:
776 processPolygonHairlinePrimitive2D(
777 static_cast<const primitive2d::PolygonHairlinePrimitive2D
&>(rCandidate
));
780 case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D
:
782 processPolygonStrokePrimitive2D(
783 static_cast<const primitive2d::PolygonStrokePrimitive2D
&>(rCandidate
));
786 case PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D
:
788 processPolygonStrokeArrowPrimitive2D(
789 static_cast<const primitive2d::PolygonStrokeArrowPrimitive2D
&>(rCandidate
));
792 case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D
:
794 // direct draw of transformed BitmapEx primitive; use default processing, but without
795 // former testing if graphic content is inside discrete local viewport; this is not
796 // setup for metafile targets (metafile renderer tries to render in logic coordinates,
797 // the mapping is kept to the OutputDevice for better Metafile recording)
798 RenderBitmapPrimitive2D(static_cast<const primitive2d::BitmapPrimitive2D
&>(rCandidate
));
801 case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D
:
803 if (maBColorModifierStack
.count())
805 // tdf#151104 unfortunately processPolyPolygonGraphicPrimitive2D below
806 // does not support an active BColorModifierStack, so use the default
811 processPolyPolygonGraphicPrimitive2D(
812 static_cast<const primitive2d::PolyPolygonGraphicPrimitive2D
&>(rCandidate
));
816 case PRIMITIVE2D_ID_POLYPOLYGONHATCHPRIMITIVE2D
:
818 processPolyPolygonHatchPrimitive2D(
819 static_cast<const primitive2d::PolyPolygonHatchPrimitive2D
&>(rCandidate
));
822 case PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D
:
824 processPolyPolygonGradientPrimitive2D(
825 static_cast<const primitive2d::PolyPolygonGradientPrimitive2D
&>(rCandidate
));
828 case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D
:
830 processPolyPolygonColorPrimitive2D(
831 static_cast<const primitive2d::PolyPolygonColorPrimitive2D
&>(rCandidate
));
834 case PRIMITIVE2D_ID_MASKPRIMITIVE2D
:
836 processMaskPrimitive2D(static_cast<const primitive2d::MaskPrimitive2D
&>(rCandidate
));
839 case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D
:
841 // modified color group. Force output to unified color. Use default processing.
842 RenderModifiedColorPrimitive2D(
843 static_cast<const primitive2d::ModifiedColorPrimitive2D
&>(rCandidate
));
846 case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D
:
848 processUnifiedTransparencePrimitive2D(
849 static_cast<const primitive2d::UnifiedTransparencePrimitive2D
&>(rCandidate
));
852 case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D
:
854 processTransparencePrimitive2D(
855 static_cast<const primitive2d::TransparencePrimitive2D
&>(rCandidate
));
858 case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D
:
860 // use default transform group processing
861 RenderTransformPrimitive2D(
862 static_cast<const primitive2d::TransformPrimitive2D
&>(rCandidate
));
865 case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D
:
867 // new XDrawPage for ViewInformation2D
868 RenderPagePreviewPrimitive2D(
869 static_cast<const primitive2d::PagePreviewPrimitive2D
&>(rCandidate
));
872 case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D
:
874 // use default marker array processing
875 RenderMarkerArrayPrimitive2D(
876 static_cast<const primitive2d::MarkerArrayPrimitive2D
&>(rCandidate
));
879 case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D
:
881 // use default point array processing
882 RenderPointArrayPrimitive2D(
883 static_cast<const primitive2d::PointArrayPrimitive2D
&>(rCandidate
));
886 case PRIMITIVE2D_ID_STRUCTURETAGPRIMITIVE2D
:
888 processStructureTagPrimitive2D(
889 static_cast<const primitive2d::StructureTagPrimitive2D
&>(rCandidate
));
892 case PRIMITIVE2D_ID_TEXTHIERARCHYEDITPRIMITIVE2D
:
894 // This primitive is created if a text edit is active and contains it's
895 // current content, not from model data itself.
896 // Pixel renderers need to suppress that content, it gets displayed by the active
897 // TextEdit in the EditView. Suppression is done by decomposing to nothing.
898 // MetaFile renderers have to show it, so that the edited text is part of the
899 // MetaFile, e.g. needed for presentation previews and exports.
900 // So take action here and process it's content:
901 // Note: Former error was #i97628#
902 process(static_cast<const primitive2d::TextHierarchyEditPrimitive2D
&>(rCandidate
)
906 case PRIMITIVE2D_ID_EPSPRIMITIVE2D
:
908 RenderEpsPrimitive2D(static_cast<const primitive2d::EpsPrimitive2D
&>(rCandidate
));
911 case PRIMITIVE2D_ID_OBJECTINFOPRIMITIVE2D
:
913 processObjectInfoPrimitive2D(
914 static_cast<const primitive2d::ObjectInfoPrimitive2D
&>(rCandidate
));
919 // process recursively
926 void VclMetafileProcessor2D::processObjectInfoPrimitive2D(
927 primitive2d::ObjectInfoPrimitive2D
const& rObjectInfoPrimitive2D
)
929 // tdf#154982 process content first, so this object overrides any nested one
930 process(rObjectInfoPrimitive2D
.getChildren());
932 // currently StructureTagPrimitive2D is only used for SdrObjects - have to
933 // avoid adding Alt text if the SdrObject is not actually tagged, as it
934 // would then end up on an unrelated structure element.
935 if (mpCurrentStructureTag
&& mpCurrentStructureTag
->isTaggedSdrObject())
937 // Create image alternative description from ObjectInfoPrimitive2D info
938 // for PDF export, for the currently active SdrObject's structure element
939 if (mpPDFExtOutDevData
->GetIsExportTaggedPDF())
941 OUString aAlternateDescription
;
943 if (!rObjectInfoPrimitive2D
.getTitle().isEmpty())
945 aAlternateDescription
+= rObjectInfoPrimitive2D
.getTitle();
948 if (!rObjectInfoPrimitive2D
.getDesc().isEmpty())
950 if (!aAlternateDescription
.isEmpty())
952 aAlternateDescription
+= " - ";
955 aAlternateDescription
+= rObjectInfoPrimitive2D
.getDesc();
958 // Use SetAlternateText to set it. This will work as long as some
959 // structure is used (see PDFWriterImpl::setAlternateText and
960 // m_nCurrentStructElement - tagged PDF export works with this in
961 // Draw/Impress/Writer, but not in Calc due to too less structure...)
962 //Z maybe add structure to Calc PDF export, may need some BeginGroup/EndGroup stuff ..?
963 if (!aAlternateDescription
.isEmpty())
965 mpPDFExtOutDevData
->SetAlternateText(aAlternateDescription
);
971 void VclMetafileProcessor2D::processGraphicPrimitive2D(
972 const primitive2d::GraphicPrimitive2D
& rGraphicPrimitive
)
974 bool bUsingPDFExtOutDevData(false);
975 basegfx::B2DVector aTranslate
, aScale
;
976 static bool bSuppressPDFExtOutDevDataSupport(false); // loplugin:constvars:ignore
978 if (mpPDFExtOutDevData
&& !bSuppressPDFExtOutDevDataSupport
)
980 // emulate data handling from UnoControlPDFExportContact, original see
981 // svtools/source/graphic/grfmgr.cxx
982 const Graphic
& rGraphic
= rGraphicPrimitive
.getGraphicObject().GetGraphic();
984 if (rGraphic
.IsGfxLink())
986 const GraphicAttr
& rAttr
= rGraphicPrimitive
.getGraphicAttr();
988 if (!rAttr
.IsSpecialDrawMode() && !rAttr
.IsAdjusted())
990 const basegfx::B2DHomMatrix
& rTransform
= rGraphicPrimitive
.getTransform();
991 double fRotate
, fShearX
;
992 rTransform
.decompose(aScale
, aTranslate
, fRotate
, fShearX
);
994 if (basegfx::fTools::equalZero(fRotate
) && (aScale
.getX() > 0.0)
995 && (aScale
.getY() > 0.0))
997 bUsingPDFExtOutDevData
= true;
998 mpPDFExtOutDevData
->BeginGroup();
1004 // process recursively and add MetaFile comment
1005 process(rGraphicPrimitive
);
1007 if (!bUsingPDFExtOutDevData
)
1010 // emulate data handling from UnoControlPDFExportContact, original see
1011 // svtools/source/graphic/grfmgr.cxx
1012 const basegfx::B2DRange
aCurrentRange(aTranslate
.getX(), aTranslate
.getY(),
1013 aTranslate
.getX() + aScale
.getX(),
1014 aTranslate
.getY() + aScale
.getY());
1015 const tools::Rectangle
aCurrentRect(
1016 sal_Int32(floor(aCurrentRange
.getMinX())), sal_Int32(floor(aCurrentRange
.getMinY())),
1017 sal_Int32(ceil(aCurrentRange
.getMaxX())), sal_Int32(ceil(aCurrentRange
.getMaxY())));
1018 const GraphicAttr
& rAttr
= rGraphicPrimitive
.getGraphicAttr();
1019 // fdo#72530 don't pass empty Rectangle to EndGroup
1020 tools::Rectangle
aCropRect(aCurrentRect
);
1022 if (rAttr
.IsCropped())
1024 // calculate scalings between real image size and logic object size. This
1025 // is necessary since the crop values are relative to original bitmap size
1026 double fFactorX(1.0);
1027 double fFactorY(1.0);
1030 const MapMode
aMapMode100thmm(MapUnit::Map100thMM
);
1031 const Size
aBitmapSize(OutputDevice::LogicToLogic(
1032 rGraphicPrimitive
.getGraphicObject().GetPrefSize(),
1033 rGraphicPrimitive
.getGraphicObject().GetPrefMapMode(), aMapMode100thmm
));
1034 const double fDivX(aBitmapSize
.Width() - rAttr
.GetLeftCrop() - rAttr
.GetRightCrop());
1035 const double fDivY(aBitmapSize
.Height() - rAttr
.GetTopCrop() - rAttr
.GetBottomCrop());
1037 if (!basegfx::fTools::equalZero(fDivX
))
1039 fFactorX
= aScale
.getX() / fDivX
;
1042 if (!basegfx::fTools::equalZero(fDivY
))
1044 fFactorY
= aScale
.getY() / fDivY
;
1048 // calculate crop range and rect
1049 basegfx::B2DRange aCropRange
;
1051 aCurrentRange
.getMinimum()
1052 - basegfx::B2DPoint(rAttr
.GetLeftCrop() * fFactorX
, rAttr
.GetTopCrop() * fFactorY
));
1054 aCurrentRange
.getMaximum()
1055 + basegfx::B2DPoint(rAttr
.GetRightCrop() * fFactorX
, rAttr
.GetBottomCrop() * fFactorY
));
1057 aCropRect
= tools::Rectangle(
1058 sal_Int32(floor(aCropRange
.getMinX())), sal_Int32(floor(aCropRange
.getMinY())),
1059 sal_Int32(ceil(aCropRange
.getMaxX())), sal_Int32(ceil(aCropRange
.getMaxY())));
1062 // #i123295# 3rd param is uncropped rect, 4th is cropped. The primitive has the cropped
1063 // object transformation, thus aCurrentRect *is* the clip region while aCropRect is the expanded,
1064 // uncropped region. Thus, correct order is aCropRect, aCurrentRect
1065 mpPDFExtOutDevData
->EndGroup(rGraphicPrimitive
.getGraphicObject().GetGraphic(),
1066 255 - rAttr
.GetAlpha(), aCropRect
, aCurrentRect
);
1069 void VclMetafileProcessor2D::processControlPrimitive2D(
1070 const primitive2d::ControlPrimitive2D
& rControlPrimitive
)
1072 const uno::Reference
<awt::XControl
>& rXControl(rControlPrimitive
.getXControl());
1073 bool bIsPrintableControl(false);
1075 // find out if control is printable
1080 uno::Reference
<beans::XPropertySet
> xModelProperties(rXControl
->getModel(),
1082 uno::Reference
<beans::XPropertySetInfo
> xPropertyInfo(
1083 xModelProperties
.is() ? xModelProperties
->getPropertySetInfo()
1084 : uno::Reference
<beans::XPropertySetInfo
>());
1085 static const OUStringLiteral
sPrintablePropertyName(u
"Printable");
1087 if (xPropertyInfo
.is() && xPropertyInfo
->hasPropertyByName(sPrintablePropertyName
))
1089 OSL_VERIFY(xModelProperties
->getPropertyValue(sPrintablePropertyName
)
1090 >>= bIsPrintableControl
);
1093 catch (const uno::Exception
&)
1095 TOOLS_WARN_EXCEPTION("drawinglayer",
1096 "VclMetafileProcessor2D: No access to printable flag of Control");
1100 // PDF export and printing only for printable controls
1101 if (!bIsPrintableControl
)
1104 ::std::optional
<sal_Int32
> oAnchorParent
;
1105 if (mpPDFExtOutDevData
)
1107 if (rControlPrimitive
.GetAnchorStructureElementKey())
1109 sal_Int32
const id
= mpPDFExtOutDevData
->EnsureStructureElement(
1110 rControlPrimitive
.GetAnchorStructureElementKey());
1111 oAnchorParent
.emplace(mpPDFExtOutDevData
->GetCurrentStructureElement());
1112 mpPDFExtOutDevData
->SetCurrentStructureElement(id
);
1116 const bool bPDFExport(mpPDFExtOutDevData
&& mpPDFExtOutDevData
->GetIsExportFormFields());
1117 bool bDoProcessRecursively(true);
1121 // PDF export. Emulate data handling from UnoControlPDFExportContact
1122 std::unique_ptr
<vcl::PDFWriter::AnyWidget
> pPDFControl(
1123 ::toolkitform::describePDFControl(rXControl
, *mpPDFExtOutDevData
));
1127 // still need to fill in the location (is a class Rectangle)
1128 const basegfx::B2DRange
aRangeLogic(
1129 rControlPrimitive
.getB2DRange(getViewInformation2D()));
1130 const tools::Rectangle
aRectLogic(static_cast<sal_Int32
>(floor(aRangeLogic
.getMinX())),
1131 static_cast<sal_Int32
>(floor(aRangeLogic
.getMinY())),
1132 static_cast<sal_Int32
>(ceil(aRangeLogic
.getMaxX())),
1133 static_cast<sal_Int32
>(ceil(aRangeLogic
.getMaxY())));
1134 pPDFControl
->Location
= aRectLogic
;
1136 Size
aFontSize(pPDFControl
->TextFont
.GetFontSize());
1137 aFontSize
= OutputDevice::LogicToLogic(aFontSize
, MapMode(MapUnit::MapPoint
),
1138 mpOutputDevice
->GetMapMode());
1139 pPDFControl
->TextFont
.SetFontSize(aFontSize
);
1141 mpPDFExtOutDevData
->WrapBeginStructureElement(vcl::PDFWriter::Form
);
1142 vcl::PDFWriter::StructAttributeValue role
;
1143 switch (pPDFControl
->Type
)
1145 case vcl::PDFWriter::PushButton
:
1146 role
= vcl::PDFWriter::Pb
;
1148 case vcl::PDFWriter::RadioButton
:
1149 role
= vcl::PDFWriter::Rb
;
1151 case vcl::PDFWriter::CheckBox
:
1152 role
= vcl::PDFWriter::Cb
;
1154 default: // there is a paucity of roles, tv is the catch-all one
1155 role
= vcl::PDFWriter::Tv
;
1158 // ISO 14289-1:2014, Clause: 7.18.4
1159 mpPDFExtOutDevData
->SetStructureAttribute(vcl::PDFWriter::Role
, role
);
1160 // ISO 14289-1:2014, Clause: 7.18.1
1161 OUString
const& rAltText(rControlPrimitive
.GetAltText());
1162 if (!rAltText
.isEmpty())
1164 mpPDFExtOutDevData
->SetAlternateText(rAltText
);
1166 mpPDFExtOutDevData
->CreateControl(*pPDFControl
);
1167 mpPDFExtOutDevData
->EndStructureElement();
1170 mpPDFExtOutDevData
->SetCurrentStructureElement(*oAnchorParent
);
1173 // no normal paint needed (see original UnoControlPDFExportContact::do_PaintObject);
1174 // do not process recursively
1175 bDoProcessRecursively
= false;
1179 // PDF export did not work, try simple output.
1180 // Fallback to printer output by not setting bDoProcessRecursively
1185 if (!bDoProcessRecursively
)
1190 if (mpPDFExtOutDevData
)
1191 { // no corresponding PDF Form, use Figure instead
1192 mpPDFExtOutDevData
->WrapBeginStructureElement(vcl::PDFWriter::Figure
);
1193 mpPDFExtOutDevData
->SetStructureAttribute(vcl::PDFWriter::Placement
, vcl::PDFWriter::Block
);
1194 auto const range(rControlPrimitive
.getB2DRange(getViewInformation2D()));
1195 tools::Rectangle
const aLogicRect(
1196 basegfx::fround(range
.getMinX()), basegfx::fround(range
.getMinY()),
1197 basegfx::fround(range
.getMaxX()), basegfx::fround(range
.getMaxY()));
1198 mpPDFExtOutDevData
->SetStructureBoundingBox(aLogicRect
);
1199 OUString
const& rAltText(rControlPrimitive
.GetAltText());
1200 if (!rAltText
.isEmpty())
1202 mpPDFExtOutDevData
->SetAlternateText(rAltText
);
1206 // #i93169# used flag the wrong way; true means that nothing was done yet
1207 if (bDoProcessRecursively
)
1212 // remember old graphics and create new
1213 uno::Reference
<awt::XView
> xControlView(rXControl
, uno::UNO_QUERY_THROW
);
1214 const uno::Reference
<awt::XGraphics
> xOriginalGraphics(xControlView
->getGraphics());
1215 const uno::Reference
<awt::XGraphics
> xNewGraphics(mpOutputDevice
->CreateUnoGraphics());
1217 if (xNewGraphics
.is())
1219 // link graphics and view
1220 xControlView
->setGraphics(xNewGraphics
);
1223 const basegfx::B2DHomMatrix
aObjectToDiscrete(
1224 getViewInformation2D().getObjectToViewTransformation()
1225 * rControlPrimitive
.getTransform());
1226 const basegfx::B2DPoint
aTopLeftDiscrete(aObjectToDiscrete
1227 * basegfx::B2DPoint(0.0, 0.0));
1230 xControlView
->draw(basegfx::fround(aTopLeftDiscrete
.getX()),
1231 basegfx::fround(aTopLeftDiscrete
.getY()));
1232 bDoProcessRecursively
= false;
1234 // restore original graphics
1235 xControlView
->setGraphics(xOriginalGraphics
);
1238 catch (const uno::Exception
&)
1240 TOOLS_WARN_EXCEPTION("drawinglayer",
1241 "VclMetafileProcessor2D: Printing of Control failed");
1245 // process recursively if not done yet to export as decomposition (bitmap)
1246 if (bDoProcessRecursively
)
1248 process(rControlPrimitive
);
1251 if (mpPDFExtOutDevData
)
1253 mpPDFExtOutDevData
->EndStructureElement();
1256 mpPDFExtOutDevData
->SetCurrentStructureElement(*oAnchorParent
);
1261 void VclMetafileProcessor2D::processTextHierarchyFieldPrimitive2D(
1262 const primitive2d::TextHierarchyFieldPrimitive2D
& rFieldPrimitive
)
1264 // support for FIELD_SEQ_BEGIN, FIELD_SEQ_END and URL. It wraps text primitives (but is not limited to)
1265 // thus do the MetafileAction embedding stuff but just handle recursively.
1266 const OString
aCommentStringCommon("FIELD_SEQ_BEGIN");
1269 switch (rFieldPrimitive
.getType())
1271 default: // case drawinglayer::primitive2d::FIELD_TYPE_COMMON :
1273 mpMetaFile
->AddAction(new MetaCommentAction(aCommentStringCommon
));
1276 case drawinglayer::primitive2d::FIELD_TYPE_PAGE
:
1278 mpMetaFile
->AddAction(new MetaCommentAction("FIELD_SEQ_BEGIN;PageField"));
1281 case drawinglayer::primitive2d::FIELD_TYPE_URL
:
1283 aURL
= rFieldPrimitive
.getValue("URL");
1285 if (!aURL
.isEmpty())
1287 mpMetaFile
->AddAction(new MetaCommentAction(
1288 aCommentStringCommon
, 0, reinterpret_cast<const sal_uInt8
*>(aURL
.getStr()),
1289 2 * aURL
.getLength()));
1296 // process recursively
1297 primitive2d::Primitive2DContainer rContent
;
1298 rFieldPrimitive
.get2DDecomposition(rContent
, getViewInformation2D());
1301 // for the end comment the type is not relevant yet, they are all the same. Just add.
1302 mpMetaFile
->AddAction(new MetaCommentAction("FIELD_SEQ_END"));
1304 if (!(mpPDFExtOutDevData
1305 && drawinglayer::primitive2d::FIELD_TYPE_URL
== rFieldPrimitive
.getType()))
1308 // emulate data handling from ImpEditEngine::Paint
1309 const basegfx::B2DRange
aViewRange(rContent
.getB2DRange(getViewInformation2D()));
1310 const tools::Rectangle
aRectLogic(static_cast<sal_Int32
>(floor(aViewRange
.getMinX())),
1311 static_cast<sal_Int32
>(floor(aViewRange
.getMinY())),
1312 static_cast<sal_Int32
>(ceil(aViewRange
.getMaxX())),
1313 static_cast<sal_Int32
>(ceil(aViewRange
.getMaxY())));
1314 vcl::PDFExtOutDevBookmarkEntry aBookmark
;
1315 OUString
const content(rFieldPrimitive
.getValue("Representation"));
1316 aBookmark
.nLinkId
= mpPDFExtOutDevData
->CreateLink(aRectLogic
, content
);
1317 aBookmark
.aBookmark
= aURL
;
1318 std::vector
<vcl::PDFExtOutDevBookmarkEntry
>& rBookmarks
= mpPDFExtOutDevData
->GetBookmarks();
1319 rBookmarks
.push_back(aBookmark
);
1322 void VclMetafileProcessor2D::processTextHierarchyLinePrimitive2D(
1323 const primitive2d::TextHierarchyLinePrimitive2D
& rLinePrimitive
)
1325 // process recursively and add MetaFile comment
1326 process(rLinePrimitive
);
1327 mpMetaFile
->AddAction(new MetaCommentAction("XTEXT_EOL"));
1330 void VclMetafileProcessor2D::processTextHierarchyBulletPrimitive2D(
1331 const primitive2d::TextHierarchyBulletPrimitive2D
& rBulletPrimitive
)
1333 // this is a part of list item, start LILabel ( = bullet)
1336 maListElements
.push(vcl::PDFWriter::LILabel
);
1337 mpPDFExtOutDevData
->WrapBeginStructureElement(vcl::PDFWriter::LILabel
);
1340 // process recursively and add MetaFile comment
1341 process(rBulletPrimitive
);
1342 // in Outliner::PaintBullet(), a MetafileComment for bullets is added, too. The
1343 // "XTEXT_EOC" is used, use here, too.
1344 mpMetaFile
->AddAction(new MetaCommentAction("XTEXT_EOC"));
1348 if (maListElements
.top() == vcl::PDFWriter::LILabel
)
1350 maListElements
.pop();
1351 mpPDFExtOutDevData
->EndStructureElement(); // end LILabel
1352 mbBulletPresent
= true;
1357 void VclMetafileProcessor2D::processTextHierarchyParagraphPrimitive2D(
1358 const primitive2d::TextHierarchyParagraphPrimitive2D
& rParagraphPrimitive
)
1360 const OString
aCommentString("XTEXT_EOP");
1361 static bool bSuppressPDFExtOutDevDataSupport(false); // loplugin:constvars:ignore
1363 if (nullptr == mpPDFExtOutDevData
|| bSuppressPDFExtOutDevDataSupport
)
1365 // Non-PDF export behaviour (metafile only).
1366 // Process recursively and add MetaFile comment.
1367 process(rParagraphPrimitive
);
1368 mpMetaFile
->AddAction(new MetaCommentAction(aCommentString
));
1372 if (!mpPDFExtOutDevData
->GetIsExportTaggedPDF())
1374 // No Tagged PDF -> Dump as Paragraph
1375 // Emulate data handling from old ImpEditEngine::Paint
1376 mpPDFExtOutDevData
->WrapBeginStructureElement(vcl::PDFWriter::Paragraph
);
1378 // Process recursively and add MetaFile comment
1379 process(rParagraphPrimitive
);
1380 mpMetaFile
->AddAction(new MetaCommentAction(aCommentString
));
1382 // Emulate data handling from ImpEditEngine::Paint
1383 mpPDFExtOutDevData
->EndStructureElement();
1387 // Create Tagged PDF -> deeper tagged data using StructureElements.
1388 // Use OutlineLevel from ParagraphPrimitive, ensure not below -1 what
1389 // means 'not active'
1390 const sal_Int16
nNewOutlineLevel(
1391 std::max(static_cast<sal_Int16
>(-1), rParagraphPrimitive
.getOutlineLevel()));
1393 // Do we have a change in OutlineLevel compared to the current one?
1394 if (nNewOutlineLevel
!= mnCurrentOutlineLevel
)
1396 if (nNewOutlineLevel
> mnCurrentOutlineLevel
)
1398 // increase List level
1399 for (sal_Int16
a(mnCurrentOutlineLevel
); a
!= nNewOutlineLevel
; ++a
)
1401 maListElements
.push(vcl::PDFWriter::List
);
1402 mpPDFExtOutDevData
->WrapBeginStructureElement(vcl::PDFWriter::List
);
1405 else // if(nNewOutlineLevel < mnCurrentOutlineLevel)
1407 // close list levels below nNewOutlineLevel completely by removing
1408 // list items as well as list tag itself
1409 for (sal_Int16
a(nNewOutlineLevel
); a
< mnCurrentOutlineLevel
; ++a
)
1411 popList(); // end LBody LI and L
1414 // on nNewOutlineLevel close the previous list item (LBody and LI)
1418 // Remember new current OutlineLevel
1419 mnCurrentOutlineLevel
= nNewOutlineLevel
;
1421 else // the same list level
1423 // close the previous list item (LBody and LI)
1427 const bool bDumpAsListItem(-1 != mnCurrentOutlineLevel
);
1429 if (bDumpAsListItem
)
1432 maListElements
.push(vcl::PDFWriter::ListItem
);
1433 mpPDFExtOutDevData
->WrapBeginStructureElement(vcl::PDFWriter::ListItem
);
1434 mbInListItem
= true;
1438 // Dump as Paragraph
1439 mpPDFExtOutDevData
->WrapBeginStructureElement(vcl::PDFWriter::Paragraph
);
1442 // Process recursively and add MetaFile comment
1443 process(rParagraphPrimitive
);
1444 mpMetaFile
->AddAction(new MetaCommentAction(aCommentString
));
1446 if (bDumpAsListItem
)
1447 mbInListItem
= false;
1449 mpPDFExtOutDevData
->EndStructureElement(); // end Paragraph
1452 void VclMetafileProcessor2D::processTextHierarchyBlockPrimitive2D(
1453 const primitive2d::TextHierarchyBlockPrimitive2D
& rBlockPrimitive
)
1455 // add MetaFile comment, process recursively and add MetaFile comment
1456 mpMetaFile
->AddAction(new MetaCommentAction("XTEXT_PAINTSHAPE_BEGIN"));
1457 process(rBlockPrimitive
);
1459 if (mnCurrentOutlineLevel
>= 0)
1461 // end any opened List structure elements (LBody, LI, L)
1462 for (sal_Int16
a(0); a
<= mnCurrentOutlineLevel
; ++a
)
1468 mpMetaFile
->AddAction(new MetaCommentAction("XTEXT_PAINTSHAPE_END"));
1471 void VclMetafileProcessor2D::processTextSimplePortionPrimitive2D(
1472 const primitive2d::TextSimplePortionPrimitive2D
& rTextCandidate
)
1474 // Adapt evtl. used special DrawMode
1475 const DrawModeFlags
nOriginalDrawMode(mpOutputDevice
->GetDrawMode());
1476 adaptTextToFillDrawMode();
1478 // this is a 2nd portion of list item
1479 // bullet has been already processed, start LIBody
1480 if (mbInListItem
&& mbBulletPresent
)
1482 maListElements
.push(vcl::PDFWriter::LIBody
);
1483 mpPDFExtOutDevData
->WrapBeginStructureElement(vcl::PDFWriter::LIBody
);
1486 // directdraw of text simple portion; use default processing
1487 RenderTextSimpleOrDecoratedPortionPrimitive2D(rTextCandidate
);
1489 if (mbInListItem
&& mbBulletPresent
)
1490 mbBulletPresent
= false;
1493 mpOutputDevice
->SetDrawMode(nOriginalDrawMode
);
1495 // #i101169# if(pTextDecoratedCandidate)
1497 // support for TEXT_ MetaFile actions only for decorated texts
1498 if (!mxBreakIterator
.is())
1500 uno::Reference
<uno::XComponentContext
> xContext(
1501 ::comphelper::getProcessComponentContext());
1502 mxBreakIterator
= i18n::BreakIterator::create(xContext
);
1505 const OUString
& rTxt
= rTextCandidate
.getText();
1506 const sal_Int32
nTextLength(rTextCandidate
.getTextLength()); // rTxt.getLength());
1510 const css::lang::Locale
& rLocale
= rTextCandidate
.getLocale();
1511 const sal_Int32
nTextPosition(rTextCandidate
.getTextPosition());
1514 sal_Int32
nNextCellBreak(mxBreakIterator
->nextCharacters(
1515 rTxt
, nTextPosition
, rLocale
, css::i18n::CharacterIteratorMode::SKIPCELL
, 0,
1517 css::i18n::Boundary
nNextWordBoundary(mxBreakIterator
->getWordBoundary(
1518 rTxt
, nTextPosition
, rLocale
, css::i18n::WordType::ANY_WORD
, true));
1519 sal_Int32
nNextSentenceBreak(
1520 mxBreakIterator
->endOfSentence(rTxt
, nTextPosition
, rLocale
));
1521 const OString
aCommentStringA("XTEXT_EOC");
1522 const OString
aCommentStringB("XTEXT_EOW");
1523 const OString
aCommentStringC("XTEXT_EOS");
1525 for (sal_Int32
i(nTextPosition
); i
< nTextPosition
+ nTextLength
; i
++)
1527 // create the entries for the respective break positions
1528 if (i
== nNextCellBreak
)
1530 mpMetaFile
->AddAction(
1531 new MetaCommentAction(aCommentStringA
, i
- nTextPosition
));
1532 nNextCellBreak
= mxBreakIterator
->nextCharacters(
1533 rTxt
, i
, rLocale
, css::i18n::CharacterIteratorMode::SKIPCELL
, 1, nDone
);
1535 if (i
== nNextWordBoundary
.endPos
)
1537 mpMetaFile
->AddAction(
1538 new MetaCommentAction(aCommentStringB
, i
- nTextPosition
));
1539 nNextWordBoundary
= mxBreakIterator
->getWordBoundary(
1540 rTxt
, i
+ 1, rLocale
, css::i18n::WordType::ANY_WORD
, true);
1542 if (i
== nNextSentenceBreak
)
1544 mpMetaFile
->AddAction(
1545 new MetaCommentAction(aCommentStringC
, i
- nTextPosition
));
1546 nNextSentenceBreak
= mxBreakIterator
->endOfSentence(rTxt
, i
+ 1, rLocale
);
1553 void VclMetafileProcessor2D::processPolygonHairlinePrimitive2D(
1554 const primitive2d::PolygonHairlinePrimitive2D
& rHairlinePrimitive
)
1556 const basegfx::B2DPolygon
& rBasePolygon
= rHairlinePrimitive
.getB2DPolygon();
1558 if (rBasePolygon
.count() > (MAX_POLYGON_POINT_COUNT_METAFILE
- 1))
1560 // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points
1561 // per polygon. If there are more, split the polygon in half and call recursively
1562 basegfx::B2DPolygon aLeft
, aRight
;
1563 splitLinePolygon(rBasePolygon
, aLeft
, aRight
);
1564 rtl::Reference
<primitive2d::PolygonHairlinePrimitive2D
> xPLeft(
1565 new primitive2d::PolygonHairlinePrimitive2D(std::move(aLeft
),
1566 rHairlinePrimitive
.getBColor()));
1567 rtl::Reference
<primitive2d::PolygonHairlinePrimitive2D
> xPRight(
1568 new primitive2d::PolygonHairlinePrimitive2D(std::move(aRight
),
1569 rHairlinePrimitive
.getBColor()));
1571 processBasePrimitive2D(*xPLeft
);
1572 processBasePrimitive2D(*xPRight
);
1576 // direct draw of hairline; use default processing
1577 // support SvtGraphicStroke MetaCommentAction
1578 const basegfx::BColor
aLineColor(
1579 maBColorModifierStack
.getModifiedColor(rHairlinePrimitive
.getBColor()));
1580 std::unique_ptr
<SvtGraphicStroke
> pSvtGraphicStroke
;
1582 // #i121267# Not needed, does not give better quality compared with
1583 // the MetaActionType::POLYPOLYGON written by RenderPolygonHairlinePrimitive2D
1585 const bool bSupportSvtGraphicStroke(false);
1587 if (bSupportSvtGraphicStroke
)
1590 = impTryToCreateSvtGraphicStroke(rHairlinePrimitive
.getB2DPolygon(), &aLineColor
,
1591 nullptr, nullptr, nullptr, nullptr);
1593 impStartSvtGraphicStroke(pSvtGraphicStroke
.get());
1596 RenderPolygonHairlinePrimitive2D(rHairlinePrimitive
, false);
1598 if (bSupportSvtGraphicStroke
)
1600 impEndSvtGraphicStroke(pSvtGraphicStroke
.get());
1605 void VclMetafileProcessor2D::processPolygonStrokePrimitive2D(
1606 const primitive2d::PolygonStrokePrimitive2D
& rStrokePrimitive
)
1608 const basegfx::B2DPolygon
& rBasePolygon
= rStrokePrimitive
.getB2DPolygon();
1610 if (rBasePolygon
.count() > (MAX_POLYGON_POINT_COUNT_METAFILE
- 1))
1612 // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points
1613 // per polygon. If there are more, split the polygon in half and call recursively
1614 basegfx::B2DPolygon aLeft
, aRight
;
1615 splitLinePolygon(rBasePolygon
, aLeft
, aRight
);
1616 rtl::Reference
<primitive2d::PolygonStrokePrimitive2D
> xPLeft(
1617 new primitive2d::PolygonStrokePrimitive2D(std::move(aLeft
),
1618 rStrokePrimitive
.getLineAttribute(),
1619 rStrokePrimitive
.getStrokeAttribute()));
1620 rtl::Reference
<primitive2d::PolygonStrokePrimitive2D
> xPRight(
1621 new primitive2d::PolygonStrokePrimitive2D(std::move(aRight
),
1622 rStrokePrimitive
.getLineAttribute(),
1623 rStrokePrimitive
.getStrokeAttribute()));
1625 processBasePrimitive2D(*xPLeft
);
1626 processBasePrimitive2D(*xPRight
);
1630 mpOutputDevice
->Push(vcl::PushFlags::LINECOLOR
| vcl::PushFlags::FILLCOLOR
);
1632 // support SvtGraphicStroke MetaCommentAction
1633 std::unique_ptr
<SvtGraphicStroke
> pSvtGraphicStroke
= impTryToCreateSvtGraphicStroke(
1634 rBasePolygon
, nullptr, &rStrokePrimitive
.getLineAttribute(),
1635 &rStrokePrimitive
.getStrokeAttribute(), nullptr, nullptr);
1637 impStartSvtGraphicStroke(pSvtGraphicStroke
.get());
1638 const attribute::LineAttribute
& rLine
= rStrokePrimitive
.getLineAttribute();
1640 // create MetaPolyLineActions, but without LineStyle::Dash
1641 if (basegfx::fTools::more(rLine
.getWidth(), 0.0))
1643 const attribute::StrokeAttribute
& rStroke
= rStrokePrimitive
.getStrokeAttribute();
1645 const basegfx::BColor
aHairlineColor(
1646 maBColorModifierStack
.getModifiedColor(rLine
.getColor()));
1647 mpOutputDevice
->SetLineColor(Color(aHairlineColor
));
1648 mpOutputDevice
->SetFillColor();
1650 // use the transformed line width
1651 LineInfo
aLineInfo(LineStyle::Solid
,
1652 basegfx::fround(getTransformedLineWidth(rLine
.getWidth())));
1653 aLineInfo
.SetLineJoin(rLine
.getLineJoin());
1654 aLineInfo
.SetLineCap(rLine
.getLineCap());
1656 basegfx::B2DPolyPolygon aHairLinePolyPolygon
;
1657 if (0.0 == rStroke
.getFullDotDashLen())
1659 aHairLinePolyPolygon
.append(rBasePolygon
);
1664 const std::vector
<double>& array
= rStroke
.getDotDashArray();
1665 // The dotdash array should generally have the form
1666 // (<dashLen> <distance>)+ (<dotLen> <distance>)*
1667 // (where (,),+ and * have their regex meaning).
1668 // Find out what the lengths and their counts are.
1669 if (!array
.empty() && array
.size() % 2 == 0)
1671 double dashLen
= array
[0];
1672 double distance
= array
[1];
1677 while (pos
+ 2 <= array
.size())
1679 if (array
[pos
] != dashLen
|| array
[pos
+ 1] != distance
)
1684 if (pos
+ 2 <= array
.size() && array
[pos
+ 1] == distance
)
1686 dotLen
= array
[pos
];
1689 while (pos
+ 2 <= array
.size())
1691 if (array
[pos
] != dotLen
|| array
[pos
+ 1] != distance
)
1697 if (array
.size() == pos
)
1699 aHairLinePolyPolygon
.append(rBasePolygon
);
1700 // This will be used by setupStrokeAttributes() in cppcanvas.
1701 aLineInfo
.SetStyle(LineStyle::Dash
);
1702 aLineInfo
.SetDashCount(dashCount
);
1703 aLineInfo
.SetDashLen(getTransformedLineWidth(dashLen
));
1704 aLineInfo
.SetDistance(getTransformedLineWidth(distance
));
1707 aLineInfo
.SetDotCount(dotCount
);
1708 aLineInfo
.SetDotLen(getTransformedLineWidth(dotLen
));
1715 // LineInfo can hold only limited info about dashing, apply dashing manually
1716 // if LineInfo cannot describe it. That should not happen though.
1717 SAL_WARN("drawinglayer", "dotdash array cannot be converted to LineInfo");
1718 basegfx::utils::applyLineDashing(rBasePolygon
, rStroke
.getDotDashArray(),
1719 &aHairLinePolyPolygon
, nullptr,
1720 rStroke
.getFullDotDashLen());
1723 aHairLinePolyPolygon
.transform(maCurrentTransformation
);
1725 for (sal_uInt32
a(0); a
< aHairLinePolyPolygon
.count(); a
++)
1727 const basegfx::B2DPolygon
& aCandidate(aHairLinePolyPolygon
.getB2DPolygon(a
));
1729 if (aCandidate
.count() > 1)
1731 const tools::Polygon
aToolsPolygon(aCandidate
);
1733 mpMetaFile
->AddAction(new MetaPolyLineAction(aToolsPolygon
, aLineInfo
));
1739 process(rStrokePrimitive
);
1742 impEndSvtGraphicStroke(pSvtGraphicStroke
.get());
1744 mpOutputDevice
->Pop();
1748 void VclMetafileProcessor2D::processPolygonStrokeArrowPrimitive2D(
1749 const primitive2d::PolygonStrokeArrowPrimitive2D
& rStrokeArrowPrimitive
)
1751 const basegfx::B2DPolygon
& rBasePolygon
= rStrokeArrowPrimitive
.getB2DPolygon();
1753 if (rBasePolygon
.count() > (MAX_POLYGON_POINT_COUNT_METAFILE
- 1))
1755 // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points
1756 // per polygon. If there are more, split the polygon in half and call recursively
1757 basegfx::B2DPolygon aLeft
, aRight
;
1758 splitLinePolygon(rBasePolygon
, aLeft
, aRight
);
1759 const attribute::LineStartEndAttribute aEmpty
;
1760 rtl::Reference
<primitive2d::PolygonStrokeArrowPrimitive2D
> xPLeft(
1761 new primitive2d::PolygonStrokeArrowPrimitive2D(
1762 aLeft
, rStrokeArrowPrimitive
.getLineAttribute(),
1763 rStrokeArrowPrimitive
.getStrokeAttribute(), rStrokeArrowPrimitive
.getStart(),
1765 rtl::Reference
<primitive2d::PolygonStrokeArrowPrimitive2D
> xPRight(
1766 new primitive2d::PolygonStrokeArrowPrimitive2D(
1767 aRight
, rStrokeArrowPrimitive
.getLineAttribute(),
1768 rStrokeArrowPrimitive
.getStrokeAttribute(), aEmpty
,
1769 rStrokeArrowPrimitive
.getEnd()));
1771 processBasePrimitive2D(*xPLeft
);
1772 processBasePrimitive2D(*xPRight
);
1776 // support SvtGraphicStroke MetaCommentAction
1777 std::unique_ptr
<SvtGraphicStroke
> pSvtGraphicStroke
= impTryToCreateSvtGraphicStroke(
1778 rBasePolygon
, nullptr, &rStrokeArrowPrimitive
.getLineAttribute(),
1779 &rStrokeArrowPrimitive
.getStrokeAttribute(), &rStrokeArrowPrimitive
.getStart(),
1780 &rStrokeArrowPrimitive
.getEnd());
1782 // write LineGeometry start marker
1783 impStartSvtGraphicStroke(pSvtGraphicStroke
.get());
1785 // #i116162# When B&W is set as DrawMode, DrawModeFlags::WhiteFill is used
1786 // to let all fills be just white; for lines DrawModeFlags::BlackLine is used
1787 // so all line geometry is supposed to get black. Since in the in-between
1788 // stages of line geometry drawing filled polygons are used (e.g. line
1789 // start/ends) it is necessary to change these drawmodes to preserve
1790 // that lines shall be black; thus change DrawModeFlags::WhiteFill to
1791 // DrawModeFlags::BlackFill during line geometry processing to have line geometry
1792 // parts filled black.
1793 const DrawModeFlags
nOldDrawMode(mpOutputDevice
->GetDrawMode());
1794 const bool bDrawmodeChange(nOldDrawMode
& DrawModeFlags::WhiteFill
1795 && mnSvtGraphicStrokeCount
);
1797 if (bDrawmodeChange
)
1799 mpOutputDevice
->SetDrawMode((nOldDrawMode
& ~DrawModeFlags::WhiteFill
)
1800 | DrawModeFlags::BlackFill
);
1803 // process sub-line geometry (evtl. filled PolyPolygons)
1804 process(rStrokeArrowPrimitive
);
1806 if (bDrawmodeChange
)
1808 mpOutputDevice
->SetDrawMode(nOldDrawMode
);
1811 // write LineGeometry end marker
1812 impEndSvtGraphicStroke(pSvtGraphicStroke
.get());
1816 void VclMetafileProcessor2D::processPolyPolygonGraphicPrimitive2D(
1817 const primitive2d::PolyPolygonGraphicPrimitive2D
& rBitmapCandidate
)
1819 // need to handle PolyPolygonGraphicPrimitive2D here to support XPATHFILL_SEQ_BEGIN/XPATHFILL_SEQ_END
1820 basegfx::B2DPolyPolygon
aLocalPolyPolygon(rBitmapCandidate
.getB2DPolyPolygon());
1822 if (!rBitmapCandidate
.getDefinitionRange().isEmpty()
1823 && aLocalPolyPolygon
.getB2DRange() != rBitmapCandidate
.getDefinitionRange())
1825 // The range which defines the bitmap fill is defined and different from the
1826 // range of the defining geometry (e.g. used for FillStyle UseSlideBackground).
1827 // This cannot be done calling vcl, thus use decomposition here directly
1828 process(rBitmapCandidate
);
1832 fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon
);
1834 std::unique_ptr
<SvtGraphicFill
> pSvtGraphicFill
;
1836 if (!mnSvtGraphicFillCount
&& aLocalPolyPolygon
.count())
1838 // #121194# Changed implementation and checked usages of convert to metafile,
1839 // presentation start (uses SvtGraphicFill) and printing.
1841 // calculate transformation. Get real object size, all values in FillGraphicAttribute
1842 // are relative to the unified object
1843 aLocalPolyPolygon
.transform(maCurrentTransformation
);
1844 const basegfx::B2DVector
aOutlineSize(aLocalPolyPolygon
.getB2DRange().getRange());
1846 // the scaling needs scale from pixel to logic coordinate system
1847 const attribute::FillGraphicAttribute
& rFillGraphicAttribute
1848 = rBitmapCandidate
.getFillGraphic();
1849 const Size
aBmpSizePixel(rFillGraphicAttribute
.getGraphic().GetSizePixel());
1851 // setup transformation like in impgrfll. Multiply with aOutlineSize
1852 // to get from unit coordinates in rFillGraphicAttribute.getGraphicRange()
1853 // to object coordinates with object's top left being at (0,0). Divide
1854 // by pixel size so that scale from pixel to logic will work in SvtGraphicFill.
1855 const basegfx::B2DVector
aTransformScale(
1856 rFillGraphicAttribute
.getGraphicRange().getRange()
1857 / basegfx::B2DVector(std::max(1.0, double(aBmpSizePixel
.Width())),
1858 std::max(1.0, double(aBmpSizePixel
.Height())))
1860 const basegfx::B2DPoint
aTransformPosition(
1861 rFillGraphicAttribute
.getGraphicRange().getMinimum() * aOutlineSize
);
1863 // setup transformation like in impgrfll
1864 SvtGraphicFill::Transform aTransform
;
1866 // scale values are divided by bitmap pixel sizes
1867 aTransform
.matrix
[0] = aTransformScale
.getX();
1868 aTransform
.matrix
[4] = aTransformScale
.getY();
1870 // translates are absolute
1871 aTransform
.matrix
[2] = aTransformPosition
.getX();
1872 aTransform
.matrix
[5] = aTransformPosition
.getY();
1874 pSvtGraphicFill
.reset(new SvtGraphicFill(
1875 getFillPolyPolygon(aLocalPolyPolygon
), Color(), 0.0, SvtGraphicFill::fillEvenOdd
,
1876 SvtGraphicFill::fillTexture
, aTransform
, rFillGraphicAttribute
.getTiling(),
1877 SvtGraphicFill::hatchSingle
, Color(), SvtGraphicFill::GradientType::Linear
, Color(),
1878 Color(), 0, rFillGraphicAttribute
.getGraphic()));
1881 // Do use decomposition; encapsulate with SvtGraphicFill
1882 impStartSvtGraphicFill(pSvtGraphicFill
.get());
1883 process(rBitmapCandidate
);
1884 impEndSvtGraphicFill(pSvtGraphicFill
.get());
1887 void VclMetafileProcessor2D::processPolyPolygonHatchPrimitive2D(
1888 const primitive2d::PolyPolygonHatchPrimitive2D
& rHatchCandidate
)
1890 // need to handle PolyPolygonHatchPrimitive2D here to support XPATHFILL_SEQ_BEGIN/XPATHFILL_SEQ_END
1891 const attribute::FillHatchAttribute
& rFillHatchAttribute
= rHatchCandidate
.getFillHatch();
1892 basegfx::B2DPolyPolygon
aLocalPolyPolygon(rHatchCandidate
.getB2DPolyPolygon());
1894 if (aLocalPolyPolygon
.getB2DRange() != rHatchCandidate
.getDefinitionRange())
1896 // the range which defines the hatch is different from the range of the
1897 // geometry (used for writer frames). This cannot be done calling vcl, thus use
1898 // decomposition here
1899 process(rHatchCandidate
);
1903 // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points
1904 // per polygon. Split polygon until there are less than that
1905 fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon
);
1907 if (rFillHatchAttribute
.isFillBackground())
1909 // with fixing #i111954# (see below) the possible background
1910 // fill of a hatched object was lost.Generate a background fill
1911 // primitive and render it
1912 const primitive2d::Primitive2DReference
xBackground(
1913 new primitive2d::PolyPolygonColorPrimitive2D(aLocalPolyPolygon
,
1914 rHatchCandidate
.getBackgroundColor()));
1916 process(primitive2d::Primitive2DContainer
{ xBackground
});
1919 std::unique_ptr
<SvtGraphicFill
> pSvtGraphicFill
;
1920 aLocalPolyPolygon
.transform(maCurrentTransformation
);
1922 if (!mnSvtGraphicFillCount
&& aLocalPolyPolygon
.count())
1924 // re-create a VCL hatch as base data
1925 SvtGraphicFill::HatchType
eHatch(SvtGraphicFill::hatchSingle
);
1927 switch (rFillHatchAttribute
.getStyle())
1929 default: // attribute::HatchStyle::Single :
1931 eHatch
= SvtGraphicFill::hatchSingle
;
1934 case attribute::HatchStyle::Double
:
1936 eHatch
= SvtGraphicFill::hatchDouble
;
1939 case attribute::HatchStyle::Triple
:
1941 eHatch
= SvtGraphicFill::hatchTriple
;
1946 SvtGraphicFill::Transform aTransform
;
1949 aTransform
.matrix
[0] *= rFillHatchAttribute
.getDistance();
1950 aTransform
.matrix
[4] *= rFillHatchAttribute
.getDistance();
1952 // rotate (was never correct in impgrfll anyways, use correct angle now)
1953 aTransform
.matrix
[0] *= cos(rFillHatchAttribute
.getAngle());
1954 aTransform
.matrix
[1] *= -sin(rFillHatchAttribute
.getAngle());
1955 aTransform
.matrix
[3] *= sin(rFillHatchAttribute
.getAngle());
1956 aTransform
.matrix
[4] *= cos(rFillHatchAttribute
.getAngle());
1958 pSvtGraphicFill
.reset(new SvtGraphicFill(
1959 getFillPolyPolygon(aLocalPolyPolygon
), Color(), 0.0, SvtGraphicFill::fillEvenOdd
,
1960 SvtGraphicFill::fillHatch
, aTransform
, false, eHatch
,
1961 Color(maBColorModifierStack
.getModifiedColor(rFillHatchAttribute
.getColor())),
1962 SvtGraphicFill::GradientType::Linear
, Color(), Color(), 0, Graphic()));
1965 // Do use decomposition; encapsulate with SvtGraphicFill
1966 impStartSvtGraphicFill(pSvtGraphicFill
.get());
1968 // #i111954# do NOT use decomposition, but use direct VCL-command
1969 // process(rCandidate.get2DDecomposition(getViewInformation2D()));
1970 const tools::PolyPolygon
aToolsPolyPolygon(
1971 basegfx::utils::adaptiveSubdivideByAngle(aLocalPolyPolygon
));
1972 const HatchStyle
aHatchStyle(
1973 attribute::HatchStyle::Single
== rFillHatchAttribute
.getStyle()
1974 ? HatchStyle::Single
1975 : attribute::HatchStyle::Double
== rFillHatchAttribute
.getStyle() ? HatchStyle::Double
1976 : HatchStyle::Triple
);
1978 mpOutputDevice
->DrawHatch(
1981 Color(maBColorModifierStack
.getModifiedColor(rFillHatchAttribute
.getColor())),
1982 basegfx::fround(rFillHatchAttribute
.getDistance()),
1983 Degree10(basegfx::fround(basegfx::rad2deg
<10>(rFillHatchAttribute
.getAngle())))));
1985 impEndSvtGraphicFill(pSvtGraphicFill
.get());
1988 void VclMetafileProcessor2D::processPolyPolygonGradientPrimitive2D(
1989 const primitive2d::PolyPolygonGradientPrimitive2D
& rGradientCandidate
)
1991 bool useDecompose(false);
1995 basegfx::B2DVector aScale
, aTranslate
;
1996 double fRotate
, fShearX
;
1998 maCurrentTransformation
.decompose(aScale
, aTranslate
, fRotate
, fShearX
);
2000 // detect if transformation is rotated, sheared or mirrored in X and/or Y
2001 if (!basegfx::fTools::equalZero(fRotate
) || !basegfx::fTools::equalZero(fShearX
)
2002 || aScale
.getX() < 0.0 || aScale
.getY() < 0.0)
2004 // #i121185# When rotation or shear is used, a VCL Gradient cannot be used directly.
2005 // This is because VCL Gradient mechanism does *not* support to rotate the gradient
2006 // with objects and this case is not expressible in a Metafile (and cannot be added
2007 // since the FileFormats used, e.g. *.wmf, do not support it either).
2008 // Such cases happen when a graphic object uses a Metafile as graphic information or
2009 // a fill style definition uses a Metafile. In this cases the graphic content is
2010 // rotated with the graphic or filled object; this is not supported by the target
2011 // format of this conversion renderer - Metafiles.
2012 // To solve this, not a Gradient is written, but the decomposition of this object
2013 // is written to the Metafile. This is the PolyPolygons building the gradient fill.
2014 // These will need more space and time, but the result will be as if the Gradient
2015 // was rotated with the object.
2016 // This mechanism is used by all exporters still not using Primitives (e.g. Print,
2017 // Slideshow, Export rto PDF, export to Picture, ...) but relying on Metafile
2018 // transfers. One more reason to *change* these to primitives.
2019 // BTW: One more example how useful the principles of primitives are; the decomposition
2020 // is by definition a simpler, maybe more expensive representation of the same content.
2021 useDecompose
= true;
2025 // tdf#150551 for PDF export, use the decomposition for better gradient visualization
2026 if (!useDecompose
&& nullptr != mpPDFExtOutDevData
)
2028 useDecompose
= true;
2031 basegfx::B2DPolyPolygon
aLocalPolyPolygon(rGradientCandidate
.getB2DPolyPolygon());
2033 if (!useDecompose
&& aLocalPolyPolygon
.getB2DRange() != rGradientCandidate
.getDefinitionRange())
2035 // the range which defines the gradient 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 useDecompose
= true;
2041 const attribute::FillGradientAttribute
& rFillGradient(rGradientCandidate
.getFillGradient());
2043 if (!useDecompose
&& rFillGradient
.cannotBeHandledByVCL())
2045 // MCGR: if we have ColorStops, do not try to fallback to old VCL-Gradient,
2046 // that will *not* be capable of representing this properly. Use the
2047 // correct decomposition instead
2048 useDecompose
= true;
2053 GDIMetaFile
* pMetaFile(mpOutputDevice
->GetConnectMetaFile());
2055 // tdf#155479 only add 'BGRAD_SEQ_BEGIN' if SVG export
2056 if (nullptr != pMetaFile
&& pMetaFile
->getSVG())
2058 // write the color stops to a memory stream
2059 SvMemoryStream aMemStm
;
2060 VersionCompatWrite
aCompat(aMemStm
, 1);
2062 const basegfx::BColorStops
& rColorStops(rFillGradient
.getColorStops());
2063 sal_uInt16
nTmp(sal::static_int_cast
<sal_uInt16
>(rColorStops
.size()));
2064 aMemStm
.WriteUInt16(nTmp
);
2066 for (auto const& rCand
: rColorStops
)
2068 aMemStm
.WriteDouble(rCand
.getStopOffset());
2069 const basegfx::BColor
& rColor(rCand
.getStopColor());
2070 aMemStm
.WriteDouble(rColor
.getRed());
2071 aMemStm
.WriteDouble(rColor
.getGreen());
2072 aMemStm
.WriteDouble(rColor
.getBlue());
2075 // Add a new MetaCommentAction section of type 'BGRAD_SEQ_BEGIN/BGRAD_SEQ_END'
2076 // that is capable of holding the new color step information, plus the
2077 // already used MetaActionType::GRADIENTEX.
2078 // With that combination only places that know about that new BGRAD_SEQ_* will
2079 // use it while all others will work on the created decomposition of the
2080 // gradient for compatibility - which are single-color filled polygons
2081 pMetaFile
->AddAction(new MetaCommentAction(
2082 "BGRAD_SEQ_BEGIN", 0, static_cast<const sal_uInt8
*>(aMemStm
.GetData()),
2083 aMemStm
.TellEnd()));
2085 // create MetaActionType::GRADIENTEX
2086 // NOTE: with the new BGRAD_SEQ_* we could use basegfx::B2DPolygon and
2087 // basegfx::BGradient here directly, but may have to add streaming OPs
2088 // for these, so for now just go with what we use all the time. The real
2089 // work for improvement should not go to this 'compromize' but to a real
2090 // re-work of the SVG export (or/and others) to no longer work on metafiles
2091 // but on UNO API or primitives (whatever fits best to the specific export)
2092 fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon
);
2093 Gradient aVCLGradient
;
2094 impConvertFillGradientAttributeToVCLGradient(aVCLGradient
, rFillGradient
, false);
2095 aLocalPolyPolygon
.transform(maCurrentTransformation
);
2096 const tools::PolyPolygon
aToolsPolyPolygon(
2097 getFillPolyPolygon(basegfx::utils::adaptiveSubdivideByAngle(aLocalPolyPolygon
)));
2098 mpOutputDevice
->DrawGradient(aToolsPolyPolygon
, aVCLGradient
);
2101 // use decompose to draw, will create PolyPolygon ColorFill actions
2102 process(rGradientCandidate
);
2104 // tdf#155479 only add 'BGRAD_SEQ_END' if SVG export
2105 if (nullptr != pMetaFile
&& pMetaFile
->getSVG())
2107 // close the BGRAD_SEQ_* actions range
2108 pMetaFile
->AddAction(new MetaCommentAction("BGRAD_SEQ_END"));
2114 // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points
2115 // per polygon. Split polygon until there are less than that
2116 fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon
);
2118 // for support of MetaCommentActions of the form XGRAD_SEQ_BEGIN, XGRAD_SEQ_END
2119 // it is safest to use the VCL OutputDevice::DrawGradient method which creates those.
2120 // re-create a VCL-gradient from FillGradientPrimitive2D and the needed tools PolyPolygon
2121 Gradient aVCLGradient
;
2122 impConvertFillGradientAttributeToVCLGradient(aVCLGradient
, rFillGradient
, false);
2123 aLocalPolyPolygon
.transform(maCurrentTransformation
);
2125 // #i82145# ATM VCL printing of gradients using curved shapes does not work,
2126 // i submitted the bug with the given ID to THB. When that task is fixed it is
2127 // necessary to again remove this subdivision since it decreases possible
2128 // printing quality (not even resolution-dependent for now). THB will tell
2129 // me when that task is fixed in the master
2130 const tools::PolyPolygon
aToolsPolyPolygon(
2131 getFillPolyPolygon(basegfx::utils::adaptiveSubdivideByAngle(aLocalPolyPolygon
)));
2133 // XPATHFILL_SEQ_BEGIN/XPATHFILL_SEQ_END support
2134 std::unique_ptr
<SvtGraphicFill
> pSvtGraphicFill
;
2136 if (!mnSvtGraphicFillCount
&& aLocalPolyPolygon
.count())
2138 // setup gradient stuff like in impgrfll
2139 SvtGraphicFill::GradientType
eGrad(SvtGraphicFill::GradientType::Linear
);
2141 switch (aVCLGradient
.GetStyle())
2143 default: // css::awt::GradientStyle_LINEAR:
2144 case css::awt::GradientStyle_AXIAL
:
2145 eGrad
= SvtGraphicFill::GradientType::Linear
;
2147 case css::awt::GradientStyle_RADIAL
:
2148 case css::awt::GradientStyle_ELLIPTICAL
:
2149 eGrad
= SvtGraphicFill::GradientType::Radial
;
2151 case css::awt::GradientStyle_SQUARE
:
2152 case css::awt::GradientStyle_RECT
:
2153 eGrad
= SvtGraphicFill::GradientType::Rectangular
;
2157 pSvtGraphicFill
.reset(new SvtGraphicFill(
2158 aToolsPolyPolygon
, Color(), 0.0, SvtGraphicFill::fillEvenOdd
,
2159 SvtGraphicFill::fillGradient
, SvtGraphicFill::Transform(), false,
2160 SvtGraphicFill::hatchSingle
, Color(), eGrad
, aVCLGradient
.GetStartColor(),
2161 aVCLGradient
.GetEndColor(), aVCLGradient
.GetSteps(), Graphic()));
2164 // call VCL directly; encapsulate with SvtGraphicFill
2165 impStartSvtGraphicFill(pSvtGraphicFill
.get());
2166 mpOutputDevice
->DrawGradient(aToolsPolyPolygon
, aVCLGradient
);
2167 impEndSvtGraphicFill(pSvtGraphicFill
.get());
2170 void VclMetafileProcessor2D::processPolyPolygonColorPrimitive2D(
2171 const primitive2d::PolyPolygonColorPrimitive2D
& rPolygonCandidate
)
2173 mpOutputDevice
->Push(vcl::PushFlags::LINECOLOR
| vcl::PushFlags::FILLCOLOR
);
2174 basegfx::B2DPolyPolygon
aLocalPolyPolygon(rPolygonCandidate
.getB2DPolyPolygon());
2176 // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points
2177 // per polygon. Split polygon until there are less than that
2178 fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon
);
2180 const basegfx::BColor
aPolygonColor(
2181 maBColorModifierStack
.getModifiedColor(rPolygonCandidate
.getBColor()));
2182 aLocalPolyPolygon
.transform(maCurrentTransformation
);
2184 // set line and fill color
2185 mpOutputDevice
->SetFillColor(Color(aPolygonColor
));
2186 mpOutputDevice
->SetLineColor();
2188 mpOutputDevice
->DrawPolyPolygon(aLocalPolyPolygon
);
2190 mpOutputDevice
->Pop();
2193 void VclMetafileProcessor2D::processMaskPrimitive2D(
2194 const primitive2d::MaskPrimitive2D
& rMaskCandidate
)
2196 // mask group. Special handling for MetaFiles.
2197 if (rMaskCandidate
.getChildren().empty())
2200 basegfx::B2DPolyPolygon
aMask(rMaskCandidate
.getMask());
2204 // prepare new mask polygon and rescue current one
2205 aMask
.transform(maCurrentTransformation
);
2206 const basegfx::B2DPolyPolygon
aLastClipPolyPolygon(maClipPolyPolygon
);
2208 if (maClipPolyPolygon
.count())
2210 // there is already a clip polygon set; build clipped union of
2211 // current mask polygon and new one
2212 maClipPolyPolygon
= basegfx::utils::clipPolyPolygonOnPolyPolygon(
2213 aMask
, maClipPolyPolygon
,
2214 true, // #i106516# we want the inside of aMask, not the outside
2219 // use mask directly
2220 maClipPolyPolygon
= aMask
;
2223 if (maClipPolyPolygon
.count())
2225 // set VCL clip region; subdivide before conversion to tools polygon. Subdivision necessary (!)
2226 // Removed subdivision and fixed in vcl::Region::ImplPolyPolyRegionToBandRegionFunc() in VCL where
2227 // the ClipRegion is built from the Polygon. An AdaptiveSubdivide on the source polygon was missing there
2228 mpOutputDevice
->Push(vcl::PushFlags::CLIPREGION
);
2229 mpOutputDevice
->SetClipRegion(vcl::Region(maClipPolyPolygon
));
2231 // recursively paint content
2232 // #i121267# Only need to process sub-content when clip polygon is *not* empty.
2233 // If it is empty, the clip is empty and there can be nothing inside.
2234 process(rMaskCandidate
.getChildren());
2236 // restore VCL clip region
2237 mpOutputDevice
->Pop();
2240 // restore to rescued clip polygon
2241 maClipPolyPolygon
= aLastClipPolyPolygon
;
2245 // no mask, no clipping. recursively paint content
2246 process(rMaskCandidate
.getChildren());
2250 void VclMetafileProcessor2D::processUnifiedTransparencePrimitive2D(
2251 const primitive2d::UnifiedTransparencePrimitive2D
& rUniTransparenceCandidate
)
2253 mpOutputDevice
->Push(vcl::PushFlags::LINECOLOR
| vcl::PushFlags::FILLCOLOR
);
2254 // for metafile: Need to examine what the pure vcl version is doing here actually
2255 // - uses DrawTransparent with metafile for content and a gradient
2256 // - uses DrawTransparent for single PolyPolygons directly. Can be detected by
2257 // checking the content for single PolyPolygonColorPrimitive2D
2258 const primitive2d::Primitive2DContainer
& rContent
= rUniTransparenceCandidate
.getChildren();
2260 if (!rContent
.empty())
2262 if (0.0 == rUniTransparenceCandidate
.getTransparence())
2264 // not transparent at all, use content
2265 process(rUniTransparenceCandidate
.getChildren());
2267 else if (rUniTransparenceCandidate
.getTransparence() > 0.0
2268 && rUniTransparenceCandidate
.getTransparence() < 1.0)
2270 // try to identify a single PolyPolygonColorPrimitive2D in the
2271 // content part of the transparence primitive
2272 const primitive2d::PolyPolygonColorPrimitive2D
* pPoPoColor
= nullptr;
2273 static bool bForceToMetafile(false); // loplugin:constvars:ignore
2275 if (!bForceToMetafile
&& 1 == rContent
.size())
2277 const primitive2d::Primitive2DReference
xReference(rContent
[0]);
2278 pPoPoColor
= dynamic_cast<const primitive2d::PolyPolygonColorPrimitive2D
*>(
2282 // PolyPolygonGradientPrimitive2D, PolyPolygonHatchPrimitive2D and
2283 // PolyPolygonGraphicPrimitive2D are derived from PolyPolygonColorPrimitive2D.
2284 // Check also for correct ID to exclude derived implementations
2286 && PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D
== pPoPoColor
->getPrimitive2DID())
2288 // single transparent tools::PolyPolygon identified, use directly
2289 const basegfx::BColor
aPolygonColor(
2290 maBColorModifierStack
.getModifiedColor(pPoPoColor
->getBColor()));
2291 basegfx::B2DPolyPolygon
aLocalPolyPolygon(pPoPoColor
->getB2DPolyPolygon());
2293 // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points
2294 // per polygon. Split polygon until there are less than that
2295 fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon
);
2298 aLocalPolyPolygon
.transform(maCurrentTransformation
);
2300 // set line and fill color
2301 const sal_uInt16
nTransPercentVcl(static_cast<sal_uInt16
>(
2302 basegfx::fround(rUniTransparenceCandidate
.getTransparence() * 100.0)));
2303 mpOutputDevice
->SetFillColor(Color(aPolygonColor
));
2304 mpOutputDevice
->SetLineColor();
2306 mpOutputDevice
->DrawTransparent(tools::PolyPolygon(aLocalPolyPolygon
),
2311 // save old mfCurrentUnifiedTransparence and set new one
2312 // so that contained SvtGraphicStroke may use the current one
2313 const double fLastCurrentUnifiedTransparence(mfCurrentUnifiedTransparence
);
2314 // #i105377# paint the content metafile opaque as the transparency gets
2315 // split of into the gradient below
2316 // mfCurrentUnifiedTransparence = rUniTransparenceCandidate.getTransparence();
2317 mfCurrentUnifiedTransparence
= 0;
2319 // various content, create content-metafile
2320 GDIMetaFile aContentMetafile
;
2322 // tdf#155479 always forward propagate SVG flag for sub-content,
2323 // it may contain cannotBeHandledByVCL gradients or transparencyGradients
2324 aContentMetafile
.setSVG(mpOutputDevice
->GetConnectMetaFile()->getSVG());
2326 const tools::Rectangle
aPrimitiveRectangle(
2327 impDumpToMetaFile(rContent
, aContentMetafile
));
2329 // restore mfCurrentUnifiedTransparence; it may have been used
2330 // while processing the sub-content in impDumpToMetaFile
2331 mfCurrentUnifiedTransparence
= fLastCurrentUnifiedTransparence
;
2333 // create uniform VCL gradient for uniform transparency
2334 Gradient aVCLGradient
;
2335 const sal_uInt8
nTransPercentVcl(static_cast<sal_uInt8
>(
2336 basegfx::fround(rUniTransparenceCandidate
.getTransparence() * 255.0)));
2337 const Color
aTransColor(nTransPercentVcl
, nTransPercentVcl
, nTransPercentVcl
);
2339 aVCLGradient
.SetStyle(css::awt::GradientStyle_LINEAR
);
2340 aVCLGradient
.SetStartColor(aTransColor
);
2341 aVCLGradient
.SetEndColor(aTransColor
);
2342 aVCLGradient
.SetAngle(0_deg10
);
2343 aVCLGradient
.SetBorder(0);
2344 aVCLGradient
.SetOfsX(0);
2345 aVCLGradient
.SetOfsY(0);
2346 aVCLGradient
.SetStartIntensity(100);
2347 aVCLGradient
.SetEndIntensity(100);
2348 aVCLGradient
.SetSteps(2);
2351 mpOutputDevice
->DrawTransparent(aContentMetafile
, aPrimitiveRectangle
.TopLeft(),
2352 aPrimitiveRectangle
.GetSize(), aVCLGradient
);
2357 mpOutputDevice
->Pop();
2360 void VclMetafileProcessor2D::processTransparencePrimitive2D(
2361 const primitive2d::TransparencePrimitive2D
& rTransparenceCandidate
)
2363 // for metafile: Need to examine what the pure vcl version is doing here actually
2364 // - uses DrawTransparent with metafile for content and a gradient
2365 // i can detect this here with checking the gradient part for a single
2366 // FillGradientPrimitive2D and reconstruct the gradient.
2367 // If that detection goes wrong, I have to create a transparence-blended bitmap. Eventually
2368 // do that in stripes, else RenderTransparencePrimitive2D may just be used
2369 const primitive2d::Primitive2DContainer
& rContent(rTransparenceCandidate
.getChildren());
2370 const primitive2d::Primitive2DContainer
& rTransparence(
2371 rTransparenceCandidate
.getTransparence());
2373 if (rContent
.empty() || rTransparence
.empty())
2376 // try to identify a single FillGradientPrimitive2D in the
2377 // transparence part of the primitive. The hope is to handle
2378 // the more specific case in a better way than the general
2379 // TransparencePrimitive2D which has strongly separated
2380 // definitions for transparency and content, both completely
2381 // free definable by primitives
2382 const primitive2d::FillGradientPrimitive2D
* pFiGradient(nullptr);
2383 static bool bForceToBigTransparentVDev(false); // loplugin:constvars:ignore
2385 // check for single FillGradientPrimitive2D
2386 if (!bForceToBigTransparentVDev
&& 1 == rTransparence
.size())
2389 = dynamic_cast<const primitive2d::FillGradientPrimitive2D
*>(rTransparence
[0].get());
2391 // check also for correct ID to exclude derived implementations
2393 && PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D
!= pFiGradient
->getPrimitive2DID())
2394 pFiGradient
= nullptr;
2397 // tdf#155479 preps for holding extra-MCGR infos
2398 bool bSVGTransparencyColorStops(false);
2399 basegfx::BColorStops aSVGTransparencyColorStops
;
2401 // MCGR: tdf#155437 If we have identified a transparency gradient,
2402 // check if VCL is able to handle it at all
2403 if (nullptr != pFiGradient
&& pFiGradient
->getFillGradient().cannotBeHandledByVCL())
2405 // If not, reset the pointer and do not make use of this special case.
2406 // Adding a gradient in incomplete state that can not be handled by vcl
2407 // makes no sense and will knowingly lead to errors, especially with
2408 // MCGR extended possibilities. I checked what happens with the
2409 // MetaFloatTransparentAction added by OutputDevice::DrawTransparent, but
2410 // in most cases it gets converted to bitmap or even ignored, see e.g.
2411 // - vcl/source/gdi/pdfwriter_impl2.cxx for PDF export
2412 // - vcl/source/filter/wmf/wmfwr.cxx -> does ignore TransparenceGradient completely
2413 // - vcl/source/filter/wmf/emfwr.cxx -> same
2414 // - vcl/source/filter/eps/eps.cxx -> same
2415 // NOTE: Theoretically it would be possible to make the new extended Gradient data
2416 // available in metafiles, with the known limitations (not backward comp, all
2417 // places using it would need adaption, ...), but combined with knowing that nearly
2418 // all usages ignore or render it locally anyways makes that a non-option.
2420 // tdf#155479 Yepp, as already mentionmed above we need to add
2421 // some MCGR infos in case of SVG export, prepare that here
2422 if (mpOutputDevice
->GetConnectMetaFile()->getSVG())
2424 // for SVG, do not use decompose & prep extra data
2425 bSVGTransparencyColorStops
= true;
2426 aSVGTransparencyColorStops
= pFiGradient
->getFillGradient().getColorStops();
2430 // use decomposition
2431 pFiGradient
= nullptr;
2435 if (nullptr != pFiGradient
)
2437 // this combination of Gradient can be expressed/handled by
2438 // vcl/metafile, so add it directly. various content, create content-metafile
2439 GDIMetaFile aContentMetafile
;
2441 // tdf#155479 always forward propagate SVG flag for sub-content,
2442 // it may contain cannotBeHandledByVCL gradients or transparencyGradients
2443 aContentMetafile
.setSVG(mpOutputDevice
->GetConnectMetaFile()->getSVG());
2445 const tools::Rectangle
aPrimitiveRectangle(impDumpToMetaFile(rContent
, aContentMetafile
));
2447 // re-create a VCL-gradient from FillGradientPrimitive2D
2448 Gradient aVCLGradient
;
2449 impConvertFillGradientAttributeToVCLGradient(aVCLGradient
, pFiGradient
->getFillGradient(),
2452 if (bSVGTransparencyColorStops
)
2454 // tdf#155479 create action directly & add extra
2455 // MCGR infos to the metafile, do that by adding - ONLY in
2456 // case of SVG export - to the MetaFileAction. For that
2457 // reason, do what OutputDevice::DrawTransparent will do,
2459 // NOTE: That would be good for this whole
2460 // VclMetafileProcessor2D anyways to allow to get it
2461 // completely independent from OutputDevice in the long run
2462 GDIMetaFile
* pMetaFile(mpOutputDevice
->GetConnectMetaFile());
2463 rtl::Reference
<::MetaFloatTransparentAction
> pAction(
2464 new MetaFloatTransparentAction(aContentMetafile
, aPrimitiveRectangle
.TopLeft(),
2465 aPrimitiveRectangle
.GetSize(), aVCLGradient
));
2467 pAction
->addSVGTransparencyColorStops(aSVGTransparencyColorStops
);
2468 pMetaFile
->AddAction(pAction
);
2472 // render it to VCL (creates MetaFloatTransparentAction)
2473 mpOutputDevice
->DrawTransparent(aContentMetafile
, aPrimitiveRectangle
.TopLeft(),
2474 aPrimitiveRectangle
.GetSize(), aVCLGradient
);
2479 // Here we need to create a correct replacement visualization for the
2480 // TransparencePrimitive2D for the target metafile.
2481 // I replaced the n'th iteration to convert-to-bitmap which was
2482 // used here by using the existing tooling. The orig here was also producing
2483 // transparency errors with test-file from tdf#155437 on the right part of the
2485 // Just rely on existing tooling doing the right thing in one place, so also
2486 // corrections/optimizations can be in one single place
2488 // Start by getting logic range of content, transform object-to-world, then world-to-view
2489 // to get to discrete values ('pixels'). Matrix multiplication is right-to-left (and not
2491 basegfx::B2DRange
aLogicRange(rTransparenceCandidate
.getB2DRange(getViewInformation2D()));
2492 aLogicRange
.transform(mpOutputDevice
->GetViewTransformation() * maCurrentTransformation
);
2494 // expand in discrete coordinates to next-bigger 'pixel' boundaries and remember
2495 // created discrete range
2497 basegfx::B2DPoint(floor(aLogicRange
.getMinX()), floor(aLogicRange
.getMinY())));
2498 aLogicRange
.expand(basegfx::B2DPoint(ceil(aLogicRange
.getMaxX()), ceil(aLogicRange
.getMaxY())));
2499 const basegfx::B2DRange
aDiscreteRange(aLogicRange
);
2501 // transform back from discrete to world coordinates: this creates the
2502 // pixel-boundaries extended logic range we need to cover all content
2504 aLogicRange
.transform(mpOutputDevice
->GetInverseViewTransformation());
2506 // create transform embedding for renderer. Goal is to translate what we
2507 // want to paint to top/left 0/0 and the calculated discrete size
2508 basegfx::B2DHomMatrix
aEmbedding(basegfx::utils::createTranslateB2DHomMatrix(
2509 -aLogicRange
.getMinX(), -aLogicRange
.getMinY()));
2510 const double fLogicWidth(
2511 basegfx::fTools::equalZero(aLogicRange
.getWidth()) ? 1.0 : aLogicRange
.getWidth());
2512 const double fLogicHeight(
2513 basegfx::fTools::equalZero(aLogicRange
.getHeight()) ? 1.0 : aLogicRange
.getHeight());
2514 aEmbedding
.scale(aDiscreteRange
.getWidth() / fLogicWidth
,
2515 aDiscreteRange
.getHeight() / fLogicHeight
);
2517 // use the whole TransparencePrimitive2D as input (no need to create a new
2518 // one with the sub-contents, these are ref-counted) and add to embedding
2519 // primitive2d::TransparencePrimitive2D& rTrCand();
2520 primitive2d::Primitive2DContainer xEmbedSeq
{ &const_cast<primitive2d::TransparencePrimitive2D
&>(
2521 rTransparenceCandidate
) };
2522 xEmbedSeq
= primitive2d::Primitive2DContainer
{ new primitive2d::TransformPrimitive2D(
2523 aEmbedding
, std::move(xEmbedSeq
)) };
2525 // use empty ViewInformation & a useful MaximumQuadraticPixels
2526 // limitation to paint the content
2527 const auto aViewInformation2D(geometry::createViewInformation2D({}));
2528 const sal_uInt32
nMaximumQuadraticPixels(500000);
2529 const BitmapEx
aBitmapEx(convertToBitmapEx(
2530 std::move(xEmbedSeq
), aViewInformation2D
, basegfx::fround(aDiscreteRange
.getWidth()),
2531 basegfx::fround(aDiscreteRange
.getHeight()), nMaximumQuadraticPixels
));
2533 // add to target metafile (will create MetaFloatTransparentAction)
2534 mpOutputDevice
->DrawBitmapEx(
2535 Point(basegfx::fround(aLogicRange
.getMinX()), basegfx::fround(aLogicRange
.getMinY())),
2536 Size(basegfx::fround(aLogicRange
.getWidth()), basegfx::fround(aLogicRange
.getHeight())),
2540 void VclMetafileProcessor2D::processStructureTagPrimitive2D(
2541 const primitive2d::StructureTagPrimitive2D
& rStructureTagCandidate
)
2543 ::comphelper::ValueRestorationGuard
const g(mpCurrentStructureTag
, &rStructureTagCandidate
);
2545 // structured tag primitive
2546 const vcl::PDFWriter::StructElement
& rTagElement(rStructureTagCandidate
.getStructureElement());
2547 bool bTagUsed((vcl::PDFWriter::NonStructElement
!= rTagElement
));
2548 ::std::optional
<sal_Int32
> oAnchorParent
;
2550 if (!rStructureTagCandidate
.isTaggedSdrObject())
2555 if (mpPDFExtOutDevData
&& bTagUsed
)
2557 // foreground object: tag as regular structure element
2558 if (!rStructureTagCandidate
.isBackground())
2560 if (rStructureTagCandidate
.GetAnchorStructureElementKey() != nullptr)
2562 sal_Int32
const id
= mpPDFExtOutDevData
->EnsureStructureElement(
2563 rStructureTagCandidate
.GetAnchorStructureElementKey());
2564 oAnchorParent
.emplace(mpPDFExtOutDevData
->GetCurrentStructureElement());
2565 mpPDFExtOutDevData
->SetCurrentStructureElement(id
);
2567 mpPDFExtOutDevData
->WrapBeginStructureElement(rTagElement
);
2568 switch (rTagElement
)
2570 case vcl::PDFWriter::H1
:
2571 case vcl::PDFWriter::H2
:
2572 case vcl::PDFWriter::H3
:
2573 case vcl::PDFWriter::H4
:
2574 case vcl::PDFWriter::H5
:
2575 case vcl::PDFWriter::H6
:
2576 case vcl::PDFWriter::Paragraph
:
2577 case vcl::PDFWriter::Heading
:
2578 case vcl::PDFWriter::Caption
:
2579 case vcl::PDFWriter::BlockQuote
:
2580 case vcl::PDFWriter::Table
:
2581 case vcl::PDFWriter::TableRow
:
2582 case vcl::PDFWriter::Formula
:
2583 case vcl::PDFWriter::Figure
:
2584 mpPDFExtOutDevData
->SetStructureAttribute(vcl::PDFWriter::Placement
,
2585 vcl::PDFWriter::Block
);
2587 case vcl::PDFWriter::TableData
:
2588 case vcl::PDFWriter::TableHeader
:
2589 mpPDFExtOutDevData
->SetStructureAttribute(vcl::PDFWriter::Placement
,
2590 vcl::PDFWriter::Inline
);
2595 switch (rTagElement
)
2597 case vcl::PDFWriter::Table
:
2598 case vcl::PDFWriter::Formula
:
2599 case vcl::PDFWriter::Figure
:
2600 case vcl::PDFWriter::Annot
:
2602 auto const range(rStructureTagCandidate
.getB2DRange(getViewInformation2D()));
2603 tools::Rectangle
const aLogicRect(
2604 basegfx::fround(range
.getMinX()), basegfx::fround(range
.getMinY()),
2605 basegfx::fround(range
.getMaxX()), basegfx::fround(range
.getMaxY()));
2606 mpPDFExtOutDevData
->SetStructureBoundingBox(aLogicRect
);
2612 if (rTagElement
== vcl::PDFWriter::Annot
)
2614 mpPDFExtOutDevData
->SetStructureAnnotIds(rStructureTagCandidate
.GetAnnotIds());
2616 if (rTagElement
== vcl::PDFWriter::TableHeader
)
2618 mpPDFExtOutDevData
->SetStructureAttribute(vcl::PDFWriter::Scope
,
2619 vcl::PDFWriter::Column
);
2622 // background object
2625 // background image: tag as artifact
2626 if (rStructureTagCandidate
.isImage())
2627 mpPDFExtOutDevData
->WrapBeginStructureElement(vcl::PDFWriter::NonStructElement
);
2628 // any other background object: do not tag
2634 // process children normally
2635 process(rStructureTagCandidate
.getChildren());
2637 if (mpPDFExtOutDevData
&& bTagUsed
)
2640 mpPDFExtOutDevData
->EndStructureElement();
2643 mpPDFExtOutDevData
->SetCurrentStructureElement(*oAnchorParent
);
2648 } // end of namespace
2650 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */