Coverage Report

Created: 2026-02-14 09:37

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