Coverage Report

Created: 2026-08-14 10:22

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