Coverage Report

Created: 2026-10-14 09:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/gdi/gdimetafiletools.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 <vcl/alpha.hxx>
21
#include <vcl/gdimetafiletools.hxx>
22
#include <vcl/metaact.hxx>
23
#include <vcl/metaactiontypes.hxx>
24
#include <vcl/canvastools.hxx>
25
#include <basegfx/polygon/b2dpolygonclipper.hxx>
26
#include <basegfx/matrix/b2dhommatrixtools.hxx>
27
#include <basegfx/polygon/b2dpolypolygontools.hxx>
28
#include <basegfx/polygon/b2dpolygontools.hxx>
29
#include <vcl/virdev.hxx>
30
#include <vcl/svapp.hxx>
31
#include <vcl/graphictools.hxx>
32
#include <osl/diagnose.h>
33
#include <tools/stream.hxx>
34
#include <tools/mapunit.hxx>
35
36
// helpers
37
38
namespace
39
{
40
    bool handleGeometricContent(
41
        const basegfx::B2DPolyPolygon& rClip,
42
        const basegfx::B2DPolyPolygon& rSource,
43
        GDIMetaFile& rTarget,
44
        bool bStroke)
45
0
    {
46
0
        if(rSource.count() && rClip.count())
47
0
        {
48
0
            const basegfx::B2DPolyPolygon aResult(
49
0
                basegfx::utils::clipPolyPolygonOnPolyPolygon(
50
0
                    rSource,
51
0
                    rClip,
52
0
                    true, // inside
53
0
                    bStroke));
54
55
0
            if(aResult.count())
56
0
            {
57
0
                if(aResult == rSource)
58
0
                {
59
                    // not clipped, but inside. Add original
60
0
                    return false;
61
0
                }
62
0
                else
63
0
                {
64
                    // add clipped geometry
65
0
                    if(bStroke)
66
0
                    {
67
0
                        for(auto const& rB2DPolygon : aResult)
68
0
                        {
69
0
                            rTarget.AddAction(
70
0
                                new MetaPolyLineAction(
71
0
                                        tools::Polygon(rB2DPolygon)));
72
0
                        }
73
0
                    }
74
0
                    else
75
0
                    {
76
0
                        rTarget.AddAction(
77
0
                            new MetaPolyPolygonAction(
78
0
                                tools::PolyPolygon(aResult)));
79
0
                    }
80
0
                }
81
0
            }
82
0
        }
83
84
0
        return true;
85
0
    }
86
87
    bool handleGradientContent(
88
        const basegfx::B2DPolyPolygon& rClip,
89
        const basegfx::B2DPolyPolygon& rSource,
90
        const Gradient& rGradient,
91
        GDIMetaFile& rTarget)
92
0
    {
93
0
        if(rSource.count() && rClip.count())
94
0
        {
95
0
            const basegfx::B2DPolyPolygon aResult(
96
0
                basegfx::utils::clipPolyPolygonOnPolyPolygon(
97
0
                    rSource,
98
0
                    rClip,
99
0
                    true, // inside
100
0
                    false)); // stroke
101
102
0
            if(aResult.count())
103
0
            {
104
0
                if(aResult == rSource)
105
0
                {
106
                    // not clipped, but inside. Add original
107
0
                    return false;
108
0
                }
109
0
                else
110
0
                {
111
                    // add clipped geometry
112
0
                    rTarget.AddAction(
113
0
                        new MetaGradientExAction(
114
0
                            tools::PolyPolygon(aResult),
115
0
                            rGradient));
116
0
                }
117
0
            }
118
0
        }
119
120
0
        return true;
121
0
    }
122
123
    bool handleBitmapContent(
124
        const basegfx::B2DPolyPolygon& rClip,
125
        const Point& rPoint,
126
        const Size& rSize,
127
        const Bitmap& rBitmap,
128
        GDIMetaFile& rTarget)
129
0
    {
130
0
        if(!rSize.Width() || !rSize.Height() || rBitmap.IsEmpty())
131
0
        {
132
            // bitmap or size is empty
133
0
            return true;
134
0
        }
135
136
0
        const basegfx::B2DRange aLogicBitmapRange(
137
0
            rPoint.X(), rPoint.Y(),
138
0
            rPoint.X() + rSize.Width(), rPoint.Y() + rSize.Height());
139
0
        const basegfx::B2DPolyPolygon aClipOfBitmap(
140
0
            basegfx::utils::clipPolyPolygonOnRange(
141
0
                rClip,
142
0
                aLogicBitmapRange,
143
0
                true,
144
0
                false)); // stroke
145
146
0
        if(!aClipOfBitmap.count())
147
0
        {
148
            // outside clip region
149
0
            return true;
150
0
        }
151
152
        // inside or overlapping. Use area to find out if it is completely
153
        // covering (inside) or overlapping
154
0
        const double fClipArea(basegfx::utils::getArea(aClipOfBitmap));
155
0
        const double fBitmapArea(
156
0
            aLogicBitmapRange.getWidth() * aLogicBitmapRange.getWidth() +
157
0
            aLogicBitmapRange.getHeight() * aLogicBitmapRange.getHeight());
158
0
        const double fFactor(fClipArea / fBitmapArea);
159
160
0
        if(basegfx::fTools::more(fFactor, 1.0 - 0.001))
161
0
        {
162
            // completely covering (with 0.1% tolerance)
163
0
            return false;
164
0
        }
165
166
        // needs clipping (with 0.1% tolerance). Prepare VirtualDevice
167
        // in pixel mode for alpha channel painting (black is transparent,
168
        // white to paint 100% opacity)
169
0
        const Size aSizePixel(rBitmap.GetSizePixel());
170
0
        ScopedVclPtrInstance< VirtualDevice > aVDev;
171
172
0
        aVDev->SetOutputSizePixel(aSizePixel);
173
0
        aVDev->EnableMapMode(false);
174
0
        aVDev->SetFillColor( COL_WHITE);
175
0
        aVDev->SetLineColor();
176
177
0
        Bitmap aColorBitmap(rBitmap);
178
0
        if(rBitmap.HasAlpha())
179
0
        {
180
            // use given alpha channel
181
0
            AlphaMask aAlpha;
182
0
            std::tie(aColorBitmap, aAlpha) = rBitmap.SplitIntoColorAndAlpha();
183
0
            aVDev->DrawBitmap(Point(0, 0), aAlpha.GetBitmap());
184
0
        }
185
0
        else
186
0
        {
187
            // reset alpha channel
188
0
            aVDev->SetBackground(Wallpaper(COL_BLACK));
189
0
            aVDev->Erase();
190
0
        }
191
192
        // transform polygon from clipping to pixel coordinates
193
0
        basegfx::B2DPolyPolygon aPixelPoly(aClipOfBitmap);
194
0
        basegfx::B2DHomMatrix aTransform;
195
196
0
        aTransform.translate(-aLogicBitmapRange.getMinX(), -aLogicBitmapRange.getMinY());
197
0
        aTransform.scale(
198
0
            static_cast< double >(aSizePixel.Width()) / aLogicBitmapRange.getWidth(),
199
0
            static_cast< double >(aSizePixel.Height()) / aLogicBitmapRange.getHeight());
200
0
        aPixelPoly.transform(aTransform);
201
202
        // to fill the non-covered parts, use the Xor fill rule of
203
        // tools::PolyPolygon painting. Start with an all-covering polygon and
204
        // add the clip polygon one
205
0
        basegfx::B2DPolyPolygon aInvertPixelPoly;
206
207
0
        aInvertPixelPoly.append(
208
0
            basegfx::utils::createPolygonFromRect(
209
0
                basegfx::B2DRange(
210
0
                    0.0, 0.0,
211
0
                    aSizePixel.Width(), aSizePixel.Height())));
212
0
        aInvertPixelPoly.append(aPixelPoly);
213
214
        // paint as alpha
215
0
        aVDev->DrawPolyPolygon(aInvertPixelPoly);
216
217
        // get created alpha mask and set defaults
218
0
        AlphaMask aAlpha(
219
0
            aVDev->GetBitmap(
220
0
                Point(0, 0),
221
0
                aSizePixel));
222
223
0
        aAlpha.SetPrefSize(rBitmap.GetPrefSize());
224
0
        aAlpha.SetPrefMapMode(rBitmap.GetPrefMapMode());
225
226
        // add new action replacing the old one
227
0
        rTarget.AddAction(
228
0
            new MetaBmpExScaleAction(
229
0
                Point(
230
0
                    basegfx::fround<tools::Long>(aLogicBitmapRange.getMinX()),
231
0
                    basegfx::fround<tools::Long>(aLogicBitmapRange.getMinY())),
232
0
                Size(
233
0
                    basegfx::fround<tools::Long>(aLogicBitmapRange.getWidth()),
234
0
                    basegfx::fround<tools::Long>(aLogicBitmapRange.getHeight())),
235
0
                Bitmap(aColorBitmap, aAlpha)));
236
237
0
        return true;
238
0
    }
239
240
    void addSvtGraphicStroke(const SvtGraphicStroke& rStroke, GDIMetaFile& rTarget)
241
0
    {
242
        // write SvtGraphicFill
243
0
        SvMemoryStream aMemStm;
244
0
        WriteSvtGraphicStroke( aMemStm, rStroke );
245
0
        rTarget.AddAction(
246
0
            new MetaCommentAction(
247
0
                "XPATHSTROKE_SEQ_BEGIN"_ostr,
248
0
                0,
249
0
                static_cast< const sal_uInt8* >(aMemStm.GetData()),
250
0
                aMemStm.TellEnd()));
251
0
    }
252
253
    void addSvtGraphicFill(const SvtGraphicFill &rFilling, GDIMetaFile& rTarget)
254
0
    {
255
        // write SvtGraphicFill
256
0
        SvMemoryStream aMemStm;
257
0
        WriteSvtGraphicFill( aMemStm, rFilling );
258
0
        rTarget.AddAction(
259
0
            new MetaCommentAction(
260
0
                "XPATHFILL_SEQ_BEGIN"_ostr,
261
0
                0,
262
0
                static_cast< const sal_uInt8* >(aMemStm.GetData()),
263
0
                aMemStm.TellEnd()));
264
0
    }
265
} // end of anonymous namespace
266
267
// #i121267# Tooling to internally clip geometry against internal clip regions
268
269
void clipMetafileContentAgainstOwnRegions(GDIMetaFile& rSource)
270
0
{
271
0
    const sal_uLong nObjCount(rSource.GetActionSize());
272
273
0
    if(!nObjCount)
274
0
    {
275
0
        return;
276
0
    }
277
278
    // prepare target data container and push/pop stack data
279
0
    GDIMetaFile aTarget;
280
0
    bool bChanged(false);
281
0
    std::vector< basegfx::B2DPolyPolygon > aClips;
282
0
    std::vector< vcl::PushFlags > aPushFlags;
283
0
    std::vector< MapMode > aMapModes;
284
285
    // start with empty region
286
0
    aClips.emplace_back();
287
288
    // start with default MapMode (MapUnit::MapPixel)
289
0
    aMapModes.emplace_back();
290
291
0
    for(sal_uLong i(0); i < nObjCount; ++i)
292
0
    {
293
0
        const MetaAction* pAction(rSource.GetAction(i));
294
0
        const MetaActionType nType(pAction->GetType());
295
0
        bool bDone(false);
296
297
        // basic operation takes care of clipregion actions (four) and push/pop of these
298
        // to steer the currently set clip region. There *is* an active
299
        // clip region when (aClips.size() && aClips.back().count()), see
300
        // below
301
0
        switch(nType)
302
0
        {
303
0
            case MetaActionType::CLIPREGION :
304
0
            {
305
0
                const MetaClipRegionAction* pA = static_cast< const MetaClipRegionAction* >(pAction);
306
307
0
                if(pA->IsClipping())
308
0
                {
309
0
                    const vcl::Region& rRegion = pA->GetRegion();
310
0
                    const basegfx::B2DPolyPolygon aNewClip(rRegion.GetAsB2DPolyPolygon());
311
312
0
                    aClips.back() = aNewClip;
313
0
                }
314
0
                else
315
0
                {
316
0
                    aClips.back() = basegfx::B2DPolyPolygon();
317
0
                }
318
319
0
                break;
320
0
            }
321
322
0
            case MetaActionType::ISECTRECTCLIPREGION :
323
0
            {
324
0
                const MetaISectRectClipRegionAction* pA = static_cast< const MetaISectRectClipRegionAction* >(pAction);
325
0
                const tools::Rectangle& rRect = pA->GetRect();
326
327
0
                if(!rRect.IsEmpty() && !aClips.empty() && aClips.back().count())
328
0
                {
329
0
                    const basegfx::B2DRange aClipRange(vcl::unotools::b2DRectangleFromRectangle(rRect));
330
331
0
                    aClips.back() = basegfx::utils::clipPolyPolygonOnRange(
332
0
                        aClips.back(),
333
0
                        aClipRange,
334
0
                        true, // inside
335
0
                        false); // stroke
336
0
                }
337
0
                break;
338
0
            }
339
340
0
            case MetaActionType::ISECTREGIONCLIPREGION :
341
0
            {
342
0
                const MetaISectRegionClipRegionAction* pA = static_cast< const MetaISectRegionClipRegionAction* >(pAction);
343
0
                const vcl::Region& rRegion = pA->GetRegion();
344
345
0
                if(!rRegion.IsEmpty() && !aClips.empty() && aClips.back().count())
346
0
                {
347
0
                    const basegfx::B2DPolyPolygon aNewClip(rRegion.GetAsB2DPolyPolygon());
348
349
0
                    aClips.back() = basegfx::utils::clipPolyPolygonOnPolyPolygon(
350
0
                        aClips.back(),
351
0
                        aNewClip,
352
0
                        true,  // inside
353
0
                        false); // stroke
354
0
                }
355
0
                break;
356
0
            }
357
358
0
            case MetaActionType::MOVECLIPREGION :
359
0
            {
360
0
                const MetaMoveClipRegionAction* pA = static_cast< const MetaMoveClipRegionAction* >(pAction);
361
0
                const tools::Long aHorMove(pA->GetHorzMove());
362
0
                const tools::Long aVerMove(pA->GetVertMove());
363
364
0
                if((aHorMove || aVerMove) && !aClips.empty() && aClips.back().count())
365
0
                {
366
0
                    aClips.back().translate(aHorMove, aVerMove);
367
0
                }
368
0
                break;
369
0
            }
370
371
0
            case MetaActionType::PUSH :
372
0
            {
373
0
                const MetaPushAction* pA = static_cast< const MetaPushAction* >(pAction);
374
0
                const vcl::PushFlags nFlags(pA->GetFlags());
375
376
0
                aPushFlags.push_back(nFlags);
377
378
0
                if(nFlags & vcl::PushFlags::CLIPREGION)
379
0
                {
380
0
                    aClips.push_back(aClips.back());
381
0
                }
382
383
0
                if(nFlags & vcl::PushFlags::MAPMODE)
384
0
                {
385
0
                    aMapModes.push_back(aMapModes.back());
386
0
                }
387
0
                break;
388
0
            }
389
390
0
            case MetaActionType::POP :
391
0
            {
392
393
0
                if(!aPushFlags.empty())
394
0
                {
395
0
                    const vcl::PushFlags nFlags(aPushFlags.back());
396
0
                    aPushFlags.pop_back();
397
398
0
                    if(nFlags & vcl::PushFlags::CLIPREGION)
399
0
                    {
400
0
                        if(aClips.size() > 1)
401
0
                        {
402
0
                            aClips.pop_back();
403
0
                        }
404
0
                        else
405
0
                        {
406
0
                            OSL_ENSURE(false, "Wrong POP() in ClipRegions (!)");
407
0
                        }
408
0
                    }
409
410
0
                    if(nFlags & vcl::PushFlags::MAPMODE)
411
0
                    {
412
0
                        if(aMapModes.size() > 1)
413
0
                        {
414
0
                            aMapModes.pop_back();
415
0
                        }
416
0
                        else
417
0
                        {
418
0
                            OSL_ENSURE(false, "Wrong POP() in MapModes (!)");
419
0
                        }
420
0
                    }
421
0
                }
422
0
                else
423
0
                {
424
0
                    OSL_ENSURE(false, "Invalid pop() without push() (!)");
425
0
                }
426
427
0
                break;
428
0
            }
429
430
0
            case MetaActionType::MAPMODE :
431
0
            {
432
0
                const MetaMapModeAction* pA = static_cast< const MetaMapModeAction* >(pAction);
433
434
0
                aMapModes.back() = pA->GetMapMode();
435
0
                break;
436
0
            }
437
438
0
            default:
439
0
            {
440
0
                break;
441
0
            }
442
0
        }
443
444
        // this area contains all actions which could potentially be clipped. Since
445
        // this tooling is only a fallback (see comments in header), only the needed
446
        // actions will be implemented. Extend using the pattern for the already
447
        // implemented actions.
448
0
        if(!aClips.empty() && aClips.back().count())
449
0
        {
450
0
            switch(nType)
451
0
            {
452
453
                // pixel actions, just check on inside
454
455
0
                case MetaActionType::PIXEL :
456
0
                {
457
0
                    const MetaPixelAction* pA = static_cast< const MetaPixelAction* >(pAction);
458
0
                    const Point& rPoint = pA->GetPoint();
459
460
0
                    if(!basegfx::utils::isInside(
461
0
                        aClips.back(),
462
0
                        basegfx::B2DPoint(rPoint.X(), rPoint.Y())))
463
0
                    {
464
                        // when not inside, do not add original
465
0
                        bDone = true;
466
0
                    }
467
0
                    break;
468
0
                }
469
470
0
                case MetaActionType::POINT :
471
0
                {
472
0
                    const MetaPointAction* pA = static_cast< const MetaPointAction* >(pAction);
473
0
                    const Point& rPoint = pA->GetPoint();
474
475
0
                    if(!basegfx::utils::isInside(
476
0
                        aClips.back(),
477
0
                        basegfx::B2DPoint(rPoint.X(), rPoint.Y())))
478
0
                    {
479
                        // when not inside, do not add original
480
0
                        bDone = true;
481
0
                    }
482
0
                    break;
483
0
                }
484
485
                // geometry actions
486
487
0
                case MetaActionType::LINE :
488
0
                {
489
0
                    const MetaLineAction* pA = static_cast< const MetaLineAction* >(pAction);
490
0
                    const Point& rStart(pA->GetStartPoint());
491
0
                    const Point& rEnd(pA->GetEndPoint());
492
0
                    basegfx::B2DPolygon aLine;
493
494
0
                    aLine.append(basegfx::B2DPoint(rStart.X(), rStart.Y()));
495
0
                    aLine.append(basegfx::B2DPoint(rEnd.X(), rEnd.Y()));
496
497
0
                    bDone = handleGeometricContent(
498
0
                        aClips.back(),
499
0
                        basegfx::B2DPolyPolygon(aLine),
500
0
                        aTarget,
501
0
                        true); // stroke
502
0
                    break;
503
0
                }
504
505
0
                case MetaActionType::RECT :
506
0
                {
507
0
                    const MetaRectAction* pA = static_cast< const MetaRectAction* >(pAction);
508
0
                    const tools::Rectangle& rRect = pA->GetRect();
509
510
0
                    if(rRect.IsEmpty())
511
0
                    {
512
0
                        bDone = true;
513
0
                    }
514
0
                    else
515
0
                    {
516
517
0
                        bDone = handleGeometricContent(
518
0
                            aClips.back(),
519
0
                            basegfx::B2DPolyPolygon(
520
0
                                basegfx::utils::createPolygonFromRect(
521
0
                                        vcl::unotools::b2DRectangleFromRectangle(rRect))),
522
0
                            aTarget,
523
0
                            false); // stroke
524
0
                    }
525
0
                    break;
526
0
                }
527
528
0
                case MetaActionType::ROUNDRECT :
529
0
                {
530
0
                    const MetaRoundRectAction* pA = static_cast< const MetaRoundRectAction* >(pAction);
531
0
                    const tools::Rectangle& rRect = pA->GetRect();
532
533
0
                    if(rRect.IsEmpty())
534
0
                    {
535
0
                        bDone = true;
536
0
                    }
537
0
                    else
538
0
                    {
539
0
                        const sal_uInt32 nHor(pA->GetHorzRound());
540
0
                        const sal_uInt32 nVer(pA->GetVertRound());
541
0
                        const basegfx::B2DRange aRange(vcl::unotools::b2DRectangleFromRectangle(rRect));
542
0
                        basegfx::B2DPolygon aOutline;
543
544
0
                        if(nHor || nVer)
545
0
                        {
546
0
                            double fRadiusX((nHor * 2.0) / (aRange.getWidth() > 0.0 ? aRange.getWidth() : 1.0));
547
0
                            double fRadiusY((nVer * 2.0) / (aRange.getHeight() > 0.0 ? aRange.getHeight() : 1.0));
548
0
                            fRadiusX = std::clamp(fRadiusX, 0.0, 1.0);
549
0
                            fRadiusY = std::clamp(fRadiusY, 0.0, 1.0);
550
551
0
                            aOutline = basegfx::utils::createPolygonFromRect(aRange, fRadiusX, fRadiusY);
552
0
                        }
553
0
                        else
554
0
                        {
555
0
                            aOutline = basegfx::utils::createPolygonFromRect(aRange);
556
0
                        }
557
558
0
                        bDone = handleGeometricContent(
559
0
                            aClips.back(),
560
0
                            basegfx::B2DPolyPolygon(aOutline),
561
0
                            aTarget,
562
0
                            false); // stroke
563
0
                    }
564
0
                    break;
565
0
                }
566
567
0
                case MetaActionType::ELLIPSE :
568
0
                {
569
0
                    const MetaEllipseAction* pA = static_cast< const MetaEllipseAction* >(pAction);
570
0
                    const tools::Rectangle& rRect = pA->GetRect();
571
572
0
                    if(rRect.IsEmpty())
573
0
                    {
574
0
                        bDone = true;
575
0
                    }
576
0
                    else
577
0
                    {
578
0
                        const basegfx::B2DRange aRange(vcl::unotools::b2DRectangleFromRectangle(rRect));
579
580
0
                        bDone = handleGeometricContent(
581
0
                            aClips.back(),
582
0
                            basegfx::B2DPolyPolygon(
583
0
                                basegfx::utils::createPolygonFromEllipse(
584
0
                                    aRange.getCenter(),
585
0
                                    aRange.getWidth() * 0.5,
586
0
                                    aRange.getHeight() * 0.5)),
587
0
                            aTarget,
588
0
                            false); // stroke
589
0
                    }
590
0
                    break;
591
0
                }
592
593
0
                case MetaActionType::ARC :
594
0
                {
595
0
                    const MetaArcAction* pA = static_cast< const MetaArcAction* >(pAction);
596
0
                    const tools::Rectangle& rRect = pA->GetRect();
597
598
0
                    if(rRect.IsEmpty())
599
0
                    {
600
0
                        bDone = true;
601
0
                    }
602
0
                    else
603
0
                    {
604
0
                        const tools::Polygon aToolsPoly(
605
0
                                rRect,
606
0
                                pA->GetStartPoint(),
607
0
                                pA->GetEndPoint(),
608
0
                                PolyStyle::Arc);
609
610
0
                        bDone = handleGeometricContent(
611
0
                            aClips.back(),
612
0
                            basegfx::B2DPolyPolygon(aToolsPoly.getB2DPolygon()),
613
0
                            aTarget,
614
0
                            true); // stroke
615
0
                    }
616
0
                    break;
617
0
                }
618
619
0
                case MetaActionType::PIE :
620
0
                {
621
0
                    const MetaPieAction* pA = static_cast< const MetaPieAction* >(pAction);
622
0
                    const tools::Rectangle& rRect = pA->GetRect();
623
624
0
                    if(rRect.IsEmpty())
625
0
                    {
626
0
                        bDone = true;
627
0
                    }
628
0
                    else
629
0
                    {
630
0
                        const tools::Polygon aToolsPoly(
631
0
                                rRect,
632
0
                                pA->GetStartPoint(),
633
0
                                pA->GetEndPoint(),
634
0
                                PolyStyle::Pie);
635
636
0
                        bDone = handleGeometricContent(
637
0
                            aClips.back(),
638
0
                            basegfx::B2DPolyPolygon(aToolsPoly.getB2DPolygon()),
639
0
                            aTarget,
640
0
                            false); // stroke
641
0
                    }
642
0
                    break;
643
0
                }
644
645
0
                case MetaActionType::CHORD :
646
0
                {
647
0
                    const MetaChordAction* pA = static_cast< const MetaChordAction* >(pAction);
648
0
                    const tools::Rectangle& rRect = pA->GetRect();
649
650
0
                    if(rRect.IsEmpty())
651
0
                    {
652
0
                        bDone = true;
653
0
                    }
654
0
                    else
655
0
                    {
656
0
                        const tools::Polygon aToolsPoly(
657
0
                                rRect,
658
0
                                pA->GetStartPoint(),
659
0
                                pA->GetEndPoint(),
660
0
                                PolyStyle::Chord);
661
662
0
                        bDone = handleGeometricContent(
663
0
                            aClips.back(),
664
0
                            basegfx::B2DPolyPolygon(aToolsPoly.getB2DPolygon()),
665
0
                            aTarget,
666
0
                            false); // stroke
667
0
                    }
668
0
                    break;
669
0
                }
670
671
0
                case MetaActionType::POLYLINE :
672
0
                {
673
0
                    const MetaPolyLineAction* pA = static_cast< const MetaPolyLineAction* >(pAction);
674
675
0
                    bDone = handleGeometricContent(
676
0
                        aClips.back(),
677
0
                        basegfx::B2DPolyPolygon(pA->GetPolygon().getB2DPolygon()),
678
0
                        aTarget,
679
0
                        true); // stroke
680
0
                    break;
681
0
                }
682
683
0
                case MetaActionType::POLYGON :
684
0
                {
685
0
                    const MetaPolygonAction* pA = static_cast< const MetaPolygonAction* >(pAction);
686
687
0
                    bDone = handleGeometricContent(
688
0
                        aClips.back(),
689
0
                        basegfx::B2DPolyPolygon(pA->GetPolygon().getB2DPolygon()),
690
0
                        aTarget,
691
0
                        false); // stroke
692
0
                    break;
693
0
                }
694
695
0
                case MetaActionType::POLYPOLYGON :
696
0
                {
697
0
                    const MetaPolyPolygonAction* pA = static_cast< const MetaPolyPolygonAction* >(pAction);
698
0
                    const tools::PolyPolygon& rPoly = pA->GetPolyPolygon();
699
700
0
                    bDone = handleGeometricContent(
701
0
                        aClips.back(),
702
0
                        rPoly.getB2DPolyPolygon(),
703
0
                        aTarget,
704
0
                        false); // stroke
705
0
                    break;
706
0
                }
707
708
                // bitmap actions, create Bitmap with alpha channel derived
709
                // from clipping
710
711
0
                case MetaActionType::BMPEX :
712
0
                {
713
0
                    const MetaBmpExAction* pA = static_cast< const MetaBmpExAction* >(pAction);
714
0
                    const Bitmap& rBitmap = pA->GetBitmap();
715
716
                    // the logical size depends on the PrefSize of the given bitmap in
717
                    // combination with the current MapMode
718
0
                    Size aLogicalSize(rBitmap.GetPrefSize());
719
720
0
                    if(MapUnit::MapPixel == rBitmap.GetPrefMapMode().GetMapUnit())
721
0
                    {
722
0
                        aLogicalSize = Application::GetDefaultDevice()->PixelToLogic(aLogicalSize, aMapModes.back());
723
0
                    }
724
0
                    else
725
0
                    {
726
0
                        aLogicalSize = OutputDevice::LogicToLogic(aLogicalSize, rBitmap.GetPrefMapMode(), aMapModes.back());
727
0
                    }
728
729
0
                    bDone = handleBitmapContent(
730
0
                        aClips.back(),
731
0
                        pA->GetPoint(),
732
0
                        aLogicalSize,
733
0
                        rBitmap,
734
0
                        aTarget);
735
0
                    break;
736
0
                }
737
738
0
                case MetaActionType::BMP :
739
0
                {
740
0
                    const MetaBmpAction* pA = static_cast< const MetaBmpAction* >(pAction);
741
0
                    const Bitmap& rBitmap = pA->GetBitmap();
742
743
                    // the logical size depends on the PrefSize of the given bitmap in
744
                    // combination with the current MapMode
745
0
                    Size aLogicalSize(rBitmap.GetPrefSize());
746
747
0
                    if(MapUnit::MapPixel == rBitmap.GetPrefMapMode().GetMapUnit())
748
0
                    {
749
0
                        aLogicalSize = Application::GetDefaultDevice()->PixelToLogic(aLogicalSize, aMapModes.back());
750
0
                    }
751
0
                    else
752
0
                    {
753
0
                        aLogicalSize = OutputDevice::LogicToLogic(aLogicalSize, rBitmap.GetPrefMapMode(), aMapModes.back());
754
0
                    }
755
756
0
                    bDone = handleBitmapContent(
757
0
                        aClips.back(),
758
0
                        pA->GetPoint(),
759
0
                        aLogicalSize,
760
0
                        rBitmap,
761
0
                        aTarget);
762
0
                    break;
763
0
                }
764
765
0
                case MetaActionType::BMPEXSCALE :
766
0
                {
767
0
                    const MetaBmpExScaleAction* pA = static_cast< const MetaBmpExScaleAction* >(pAction);
768
769
0
                    bDone = handleBitmapContent(
770
0
                        aClips.back(),
771
0
                        pA->GetPoint(),
772
0
                        pA->GetSize(),
773
0
                        pA->GetBitmap(),
774
0
                        aTarget);
775
0
                    break;
776
0
                }
777
778
0
                case MetaActionType::BMPSCALE :
779
0
                {
780
0
                    const MetaBmpScaleAction* pA = static_cast< const MetaBmpScaleAction* >(pAction);
781
782
0
                    bDone = handleBitmapContent(
783
0
                        aClips.back(),
784
0
                        pA->GetPoint(),
785
0
                        pA->GetSize(),
786
0
                        pA->GetBitmap(),
787
0
                        aTarget);
788
0
                    break;
789
0
                }
790
791
0
                case MetaActionType::BMPEXSCALEPART :
792
0
                {
793
0
                    const MetaBmpExScalePartAction* pA = static_cast< const MetaBmpExScalePartAction* >(pAction);
794
0
                    const Bitmap& rBitmap = pA->GetBitmap();
795
796
0
                    if(rBitmap.IsEmpty())
797
0
                    {
798
                        // empty content
799
0
                        bDone = true;
800
0
                    }
801
0
                    else
802
0
                    {
803
0
                        Bitmap aCroppedBitmap(rBitmap);
804
0
                        const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize());
805
806
0
                        if(aCropRectangle.IsEmpty())
807
0
                        {
808
                            // empty content
809
0
                            bDone = true;
810
0
                        }
811
0
                        else
812
0
                        {
813
0
                            aCroppedBitmap.Crop(aCropRectangle);
814
0
                            bDone = handleBitmapContent(
815
0
                                aClips.back(),
816
0
                                pA->GetDestPoint(),
817
0
                                pA->GetDestSize(),
818
0
                                aCroppedBitmap,
819
0
                                aTarget);
820
0
                        }
821
0
                    }
822
0
                    break;
823
0
                }
824
825
0
                case MetaActionType::BMPSCALEPART :
826
0
                {
827
0
                    const MetaBmpScalePartAction* pA = static_cast< const MetaBmpScalePartAction* >(pAction);
828
0
                    const Bitmap& rBitmap = pA->GetBitmap();
829
830
0
                    if(rBitmap.IsEmpty())
831
0
                    {
832
                        // empty content
833
0
                        bDone = true;
834
0
                    }
835
0
                    else
836
0
                    {
837
0
                        Bitmap aCroppedBitmap(rBitmap);
838
0
                        const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize());
839
840
0
                        if(aCropRectangle.IsEmpty())
841
0
                        {
842
                            // empty content
843
0
                            bDone = true;
844
0
                        }
845
0
                        else
846
0
                        {
847
0
                            aCroppedBitmap.Crop(aCropRectangle);
848
0
                            bDone = handleBitmapContent(
849
0
                                aClips.back(),
850
0
                                pA->GetDestPoint(),
851
0
                                pA->GetDestSize(),
852
0
                                aCroppedBitmap,
853
0
                                aTarget);
854
0
                        }
855
0
                    }
856
0
                    break;
857
0
                }
858
859
                // need to handle all those 'hacks' which hide data in comments
860
861
0
                case MetaActionType::COMMENT :
862
0
                {
863
0
                    const MetaCommentAction* pA = static_cast< const MetaCommentAction* >(pAction);
864
0
                    const OString& rComment = pA->GetComment();
865
866
0
                    if(rComment.equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN"))
867
0
                    {
868
                        // nothing to do; this just means that between here and XGRAD_SEQ_END
869
                        // there exists a MetaActionType::GRADIENTEX mixed with Xor-tricked painting
870
                        // commands. This comment is used to scan over these and filter for
871
                        // the gradient action. It is needed to support MetaActionType::GRADIENTEX
872
                        // in this processor to solve usages.
873
0
                    }
874
0
                    else if(rComment.equalsIgnoreAsciiCase("XPATHFILL_SEQ_BEGIN"))
875
0
                    {
876
0
                        SvtGraphicFill aFilling;
877
0
                        tools::PolyPolygon aPath;
878
879
0
                        {   // read SvtGraphicFill
880
0
                            SvMemoryStream aMemStm(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(),StreamMode::READ);
881
0
                            ReadSvtGraphicFill( aMemStm, aFilling );
882
0
                        }
883
884
0
                        aFilling.getPath(aPath);
885
886
0
                        if(aPath.Count())
887
0
                        {
888
0
                            const basegfx::B2DPolyPolygon aSource(aPath.getB2DPolyPolygon());
889
0
                            const basegfx::B2DPolyPolygon aResult(
890
0
                                basegfx::utils::clipPolyPolygonOnPolyPolygon(
891
0
                                    aSource,
892
0
                                    aClips.back(),
893
0
                                    true, // inside
894
0
                                    false)); // stroke
895
896
0
                            if(aResult.count())
897
0
                            {
898
0
                                if(aResult != aSource)
899
0
                                {
900
                                    // add clipped geometry
901
0
                                    aFilling.setPath(tools::PolyPolygon(aResult));
902
0
                                    addSvtGraphicFill(aFilling, aTarget);
903
0
                                    bDone = true;
904
0
                                }
905
0
                            }
906
0
                            else
907
0
                            {
908
                                // exchange with empty polygon
909
0
                                aFilling.setPath(tools::PolyPolygon());
910
0
                                addSvtGraphicFill(aFilling, aTarget);
911
0
                                bDone = true;
912
0
                            }
913
0
                        }
914
0
                    }
915
0
                    else if(rComment.equalsIgnoreAsciiCase("XPATHSTROKE_SEQ_BEGIN"))
916
0
                    {
917
0
                        SvtGraphicStroke aStroke;
918
0
                        tools::Polygon aPath;
919
920
0
                        {   // read SvtGraphicFill
921
0
                            SvMemoryStream aMemStm(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(),StreamMode::READ);
922
0
                            ReadSvtGraphicStroke( aMemStm, aStroke );
923
0
                        }
924
925
0
                        aStroke.getPath(aPath);
926
927
0
                        if(aPath.GetSize())
928
0
                        {
929
0
                            const basegfx::B2DPolygon aSource(aPath.getB2DPolygon());
930
0
                            const basegfx::B2DPolyPolygon aResult(
931
0
                                basegfx::utils::clipPolygonOnPolyPolygon(
932
0
                                    aSource,
933
0
                                    aClips.back(),
934
0
                                    true, // inside
935
0
                                    true)); // stroke
936
937
0
                            if(aResult.count())
938
0
                            {
939
0
                                if(aResult.count() > 1 || aResult.getB2DPolygon(0) != aSource)
940
0
                                {
941
                                    // add clipped geometry
942
0
                                    for(auto const& rB2DPolygon : aResult)
943
0
                                    {
944
0
                                        aStroke.setPath(tools::Polygon(rB2DPolygon));
945
0
                                        addSvtGraphicStroke(aStroke, aTarget);
946
0
                                    }
947
948
0
                                    bDone = true;
949
0
                                }
950
0
                            }
951
0
                            else
952
0
                            {
953
                                // exchange with empty polygon
954
0
                                aStroke.setPath(tools::Polygon());
955
0
                                addSvtGraphicStroke(aStroke, aTarget);
956
0
                                bDone = true;
957
0
                            }
958
959
0
                        }
960
0
                    }
961
0
                    break;
962
0
                }
963
964
                // need to handle gradient fills (hopefully only unrotated ones)
965
966
0
                case MetaActionType::GRADIENT :
967
0
                {
968
0
                    const MetaGradientAction* pA = static_cast< const MetaGradientAction* >(pAction);
969
0
                    const tools::Rectangle& rRect = pA->GetRect();
970
971
0
                    if(rRect.IsEmpty())
972
0
                    {
973
0
                        bDone = true;
974
0
                    }
975
0
                    else
976
0
                    {
977
0
                        bDone = handleGradientContent(
978
0
                            aClips.back(),
979
0
                            basegfx::B2DPolyPolygon(
980
0
                                basegfx::utils::createPolygonFromRect(
981
0
                                        vcl::unotools::b2DRectangleFromRectangle(rRect))),
982
0
                            pA->GetGradient(),
983
0
                            aTarget);
984
0
                    }
985
986
0
                    break;
987
0
                }
988
989
0
                case MetaActionType::GRADIENTEX :
990
0
                {
991
0
                    const MetaGradientExAction* pA = static_cast< const MetaGradientExAction* >(pAction);
992
0
                    const tools::PolyPolygon& rPolyPoly = pA->GetPolyPolygon();
993
994
0
                    bDone = handleGradientContent(
995
0
                        aClips.back(),
996
0
                        rPolyPoly.getB2DPolyPolygon(),
997
0
                        pA->GetGradient(),
998
0
                        aTarget);
999
0
                    break;
1000
0
                }
1001
1002
                // not (yet) supported actions
1003
1004
                // MetaActionType::NONE
1005
                // MetaActionType::TEXT
1006
                // MetaActionType::TEXTARRAY
1007
                // MetaActionType::STRETCHTEXT
1008
                // MetaActionType::TEXTRECT
1009
                // MetaActionType::MASK
1010
                // MetaActionType::MASKSCALE
1011
                // MetaActionType::MASKSCALEPART
1012
                // MetaActionType::HATCH
1013
                // MetaActionType::WALLPAPER
1014
                // MetaActionType::FILLCOLOR
1015
                // MetaActionType::TEXTCOLOR
1016
                // MetaActionType::TEXTFILLCOLOR
1017
                // MetaActionType::TEXTALIGN
1018
                // MetaActionType::MAPMODE
1019
                // MetaActionType::FONT
1020
                // MetaActionType::Transparent
1021
                // MetaActionType::EPS
1022
                // MetaActionType::REFPOINT
1023
                // MetaActionType::TEXTLINECOLOR
1024
                // MetaActionType::TEXTLINE
1025
                // MetaActionType::FLOATTRANSPARENT
1026
                // MetaActionType::LAYOUTMODE
1027
                // MetaActionType::TEXTLANGUAGE
1028
                // MetaActionType::OVERLINECOLOR
1029
1030
                // if an action is not handled at all, it will simply get copied to the
1031
                // target (see below). This is the default for all non-implemented actions
1032
0
                default:
1033
0
                {
1034
0
                    break;
1035
0
                }
1036
0
            }
1037
0
        }
1038
1039
0
        if(bDone)
1040
0
        {
1041
0
            bChanged = true;
1042
0
        }
1043
0
        else
1044
0
        {
1045
0
            aTarget.AddAction(const_cast< MetaAction* >(pAction));
1046
0
        }
1047
0
    }
1048
1049
0
    if(bChanged)
1050
0
    {
1051
        // when changed, copy back and do not forget to set MapMode
1052
        // and PrefSize
1053
0
        aTarget.SetPrefMapMode(rSource.GetPrefMapMode());
1054
0
        aTarget.SetPrefSize(rSource.GetPrefSize());
1055
0
        rSource = aTarget;
1056
0
    }
1057
0
}
1058
1059
bool usesClipActions(const GDIMetaFile& rSource)
1060
0
{
1061
0
    const sal_uLong nObjCount(rSource.GetActionSize());
1062
1063
0
    for(sal_uLong i(0); i < nObjCount; ++i)
1064
0
    {
1065
0
        const MetaAction* pAction(rSource.GetAction(i));
1066
0
        const MetaActionType nType(pAction->GetType());
1067
1068
0
        switch(nType)
1069
0
        {
1070
0
            case MetaActionType::CLIPREGION :
1071
0
            case MetaActionType::ISECTRECTCLIPREGION :
1072
0
            case MetaActionType::ISECTREGIONCLIPREGION :
1073
0
            case MetaActionType::MOVECLIPREGION :
1074
0
            {
1075
0
                return true;
1076
0
            }
1077
1078
0
            default: break;
1079
0
        }
1080
0
    }
1081
1082
0
    return false;
1083
0
}
1084
1085
MetafileAccessor::~MetafileAccessor()
1086
28.3k
{
1087
28.3k
}
1088
1089
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */