Coverage Report

Created: 2026-05-16 09:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/outdev/transparent.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 <sal/types.h>
21
#include <osl/diagnose.h>
22
#include <rtl/math.hxx>
23
#include <basegfx/polygon/b2dpolygontools.hxx>
24
#include <tools/helpers.hxx>
25
#include <tools/mapunit.hxx>
26
#include <officecfg/Office/Common.hxx>
27
28
#include <vcl/BitmapTools.hxx>
29
#include <vcl/metaact.hxx>
30
#include <vcl/metaactiontypes.hxx>
31
#include <vcl/print.hxx>
32
#include <vcl/rendercontext/AntialiasingFlags.hxx>
33
#include <vcl/rendercontext/DrawModeFlags.hxx>
34
#include <vcl/settings.hxx>
35
#include <vcl/svapp.hxx>
36
#include <vcl/virdev.hxx>
37
#include <vcl/BitmapWriteAccess.hxx>
38
#include <pdf/pdfwriter_impl.hxx>
39
#include <salgdi.hxx>
40
41
#include <list>
42
#include <memory>
43
44
0
#define MAX_TILE_WIDTH  1024
45
0
#define MAX_TILE_HEIGHT 1024
46
47
namespace
48
{
49
    /**
50
     * Perform a safe approximation of a polygon from double-precision
51
     * coordinates to integer coordinates, to ensure that it has at least 2
52
     * pixels in both X and Y directions.
53
     */
54
    tools::Polygon toPolygon( const basegfx::B2DPolygon& rPoly )
55
0
    {
56
0
        basegfx::B2DRange aRange = rPoly.getB2DRange();
57
0
        double fW = aRange.getWidth(), fH = aRange.getHeight();
58
0
        if (0.0 < fW && 0.0 < fH && (fW <= 1.0 || fH <= 1.0))
59
0
        {
60
            // This polygon not empty but is too small to display.  Approximate it
61
            // with a rectangle large enough to be displayed.
62
0
            double nX = aRange.getMinX(), nY = aRange.getMinY();
63
0
            double nW = std::max<double>(1.0, rtl::math::round(fW));
64
0
            double nH = std::max<double>(1.0, rtl::math::round(fH));
65
66
0
            tools::Polygon aTarget;
67
0
            aTarget.Insert(0, Point(nX, nY));
68
0
            aTarget.Insert(1, Point(nX+nW, nY));
69
0
            aTarget.Insert(2, Point(nX+nW, nY+nH));
70
0
            aTarget.Insert(3, Point(nX, nY+nH));
71
0
            aTarget.Insert(4, Point(nX, nY));
72
0
            return aTarget;
73
0
        }
74
0
        return tools::Polygon(rPoly);
75
0
    }
76
77
    tools::PolyPolygon toPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly )
78
0
    {
79
0
        tools::PolyPolygon aTarget;
80
0
        for (auto const& rB2DPolygon : rPolyPoly)
81
0
            aTarget.Insert(toPolygon(rB2DPolygon));
82
83
0
        return aTarget;
84
0
    }
85
}
86
87
// Caution: This method is nearly the same as
88
// void OutputDevice::DrawPolyPolygon( const basegfx::B2DPolyPolygon& rB2DPolyPoly )
89
// so when changes are made here do not forget to make changes there, too
90
91
void OutputDevice::DrawTransparent(
92
    const basegfx::B2DHomMatrix& rObjectTransform,
93
    const basegfx::B2DPolyPolygon& rB2DPolyPoly,
94
    double fTransparency)
95
0
{
96
0
    assert(!is_double_buffered_window());
97
98
    // AW: Do NOT paint empty PolyPolygons
99
0
    if(!rB2DPolyPoly.count())
100
0
        return;
101
102
    // we need a graphics
103
0
    if( !mpGraphics && !AcquireGraphics() )
104
0
        return;
105
0
    assert(mpGraphics);
106
107
0
    if( mbInitClipRegion )
108
0
        InitClipRegion();
109
110
0
    if( mbOutputClipped )
111
0
        return;
112
113
0
    if( mbInitLineColor )
114
0
        InitLineColor();
115
116
0
    if( mbInitFillColor )
117
0
        InitFillColor();
118
119
0
    if (RasterOp::OverPaint == GetRasterOp())
120
0
    {
121
        // b2dpolygon support not implemented yet on non-UNX platforms
122
0
        basegfx::B2DPolyPolygon aB2DPolyPolygon(rB2DPolyPoly);
123
124
        // ensure it is closed
125
0
        if(!aB2DPolyPolygon.isClosed())
126
0
        {
127
            // maybe assert, prevents buffering due to making a copy
128
0
            aB2DPolyPolygon.setClosed( true );
129
0
        }
130
131
        // create ObjectToDevice transformation
132
0
        const basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rObjectTransform);
133
        // TODO: this must not drop transparency for mpAlphaVDev case, but instead use premultiplied
134
        // alpha... but that requires using premultiplied alpha also for already drawn data
135
136
0
        if (IsFillColor())
137
0
        {
138
0
            mpGraphics->DrawPolyPolygon(
139
0
                aFullTransform,
140
0
                aB2DPolyPolygon,
141
0
                fTransparency,
142
0
                *this);
143
0
        }
144
145
0
        if (IsLineColor())
146
0
        {
147
0
            const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
148
149
0
            for(auto const& rPolygon : std::as_const(aB2DPolyPolygon))
150
0
            {
151
0
                mpGraphics->DrawPolyLine(
152
0
                    aFullTransform,
153
0
                    rPolygon,
154
0
                    fTransparency,
155
0
                    0.0, // tdf#124848 hairline
156
0
                    nullptr, // MM01
157
0
                    basegfx::B2DLineJoin::NONE,
158
0
                    css::drawing::LineCap_BUTT,
159
0
                    basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
160
0
                    bPixelSnapHairline,
161
0
                    *this );
162
0
            }
163
0
        }
164
165
0
        if( mpMetaFile )
166
0
        {
167
            // tdf#119843 need transformed Polygon here
168
0
            basegfx::B2DPolyPolygon aB2DPolyPoly(rB2DPolyPoly);
169
0
            aB2DPolyPoly.transform(rObjectTransform);
170
0
            mpMetaFile->AddAction(
171
0
                new MetaTransparentAction(
172
0
                    tools::PolyPolygon(aB2DPolyPoly),
173
0
                    static_cast< sal_uInt16 >(fTransparency * 100.0)));
174
0
        }
175
176
0
        return;
177
0
    }
178
179
    // fallback to old polygon drawing if needed
180
    // tdf#119843 need transformed Polygon here
181
0
    basegfx::B2DPolyPolygon aB2DPolyPoly(rB2DPolyPoly);
182
0
    aB2DPolyPoly.transform(rObjectTransform);
183
0
    DrawTransparent(
184
0
        toPolyPolygon(aB2DPolyPoly),
185
0
        static_cast<sal_uInt16>(fTransparency * 100.0));
186
0
}
187
188
bool OutputDevice::DrawTransparentNatively ( const tools::PolyPolygon& rPolyPoly,
189
                                             sal_uInt16 nTransparencePercent )
190
1.92k
{
191
1.92k
    assert(!is_double_buffered_window());
192
193
1.92k
    bool bDrawn = false;
194
195
1.92k
    if (true
196
1.92k
#if defined UNX && ! defined MACOSX && ! defined IOS
197
1.92k
        && GetBitCount() > 8
198
1.92k
#endif
199
#ifdef _WIN32
200
        // workaround bad dithering on remote displaying when using GDI+ with toolbar button highlighting
201
        && !rPolyPoly.IsRect()
202
#endif
203
1.92k
        )
204
1.92k
    {
205
        // prepare the graphics device
206
1.92k
        if( mbInitClipRegion )
207
740
            InitClipRegion();
208
209
1.92k
        if( mbOutputClipped )
210
178
            return true;
211
212
1.74k
        if( mbInitLineColor )
213
151
            InitLineColor();
214
215
1.74k
        if( mbInitFillColor )
216
975
            InitFillColor();
217
218
        // get the polygon in device coordinates
219
1.74k
        basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPoly.getB2DPolyPolygon());
220
1.74k
        const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
221
222
1.74k
        const double fTransparency = 0.01 * nTransparencePercent;
223
1.74k
        if( mbFillColor )
224
1.34k
        {
225
            // #i121591#
226
            // CAUTION: Only non printing (pixel-renderer) VCL commands from OutputDevices
227
            // should be used when printing. Normally this is avoided by the printer being
228
            // non-AAed and thus e.g. on WIN GdiPlus calls are not used. It may be necessary
229
            // to figure out a way of moving this code to its own function that is
230
            // overridden by the Print class, which will mean we deliberately override the
231
            // functionality and we use the fallback some lines below (which is not very good,
232
            // though. For now, WinSalGraphics::drawPolyPolygon will detect printer usage and
233
            // correct the wrong mapping (see there for details)
234
1.34k
            mpGraphics->DrawPolyPolygon(
235
1.34k
                aTransform,
236
1.34k
                aB2DPolyPolygon,
237
1.34k
                fTransparency,
238
1.34k
                *this);
239
1.34k
            bDrawn = true;
240
1.34k
        }
241
242
1.74k
        if( mbLineColor )
243
1.67k
        {
244
            // disable the fill color for now
245
1.67k
            mpGraphics->SetFillColor();
246
247
            // draw the border line
248
1.67k
            const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
249
250
1.67k
            for(auto const& rPolygon : std::as_const(aB2DPolyPolygon))
251
2.49k
            {
252
2.49k
                bDrawn = mpGraphics->DrawPolyLine(
253
2.49k
                    aTransform,
254
2.49k
                    rPolygon,
255
2.49k
                    fTransparency,
256
2.49k
                    0.0, // tdf#124848 hairline
257
2.49k
                    nullptr, // MM01
258
2.49k
                    basegfx::B2DLineJoin::NONE,
259
2.49k
                    css::drawing::LineCap_BUTT,
260
2.49k
                    basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
261
2.49k
                    bPixelSnapHairline,
262
2.49k
                    *this );
263
2.49k
            }
264
265
            // prepare to restore the fill color
266
1.67k
            mbInitFillColor = mbFillColor;
267
1.67k
        }
268
1.74k
    }
269
270
1.74k
    return bDrawn;
271
1.92k
}
272
273
void OutputDevice::EmulateDrawTransparent ( const tools::PolyPolygon& rPolyPoly,
274
                                            sal_uInt16 nTransparencePercent )
275
344
{
276
344
    GDIMetaFile* pOldMetaFile = mpMetaFile;
277
344
    mpMetaFile = nullptr;
278
279
344
    tools::PolyPolygon aPolyPoly( LogicToPixel( rPolyPoly ) );
280
344
    tools::Rectangle aPolyRect( aPolyPoly.GetBoundRect() );
281
344
    tools::Rectangle aDstRect( Point(), GetOutputSizePixel() );
282
283
344
    aDstRect.Intersection( aPolyRect );
284
285
344
    ClipToPaintRegion( aDstRect );
286
287
344
    if( !aDstRect.IsEmpty() )
288
0
    {
289
0
        bool bDrawn = false;
290
291
        // #i66849# Added fast path for exactly rectangular
292
        // polygons
293
0
        if( aPolyPoly.IsRect() )
294
0
        {
295
            // setup Graphics only here (other cases delegate
296
            // to basic OutDev methods)
297
0
            if ( mbInitClipRegion )
298
0
                InitClipRegion();
299
300
0
            if ( mbInitLineColor )
301
0
                InitLineColor();
302
303
0
            if ( mbInitFillColor )
304
0
                InitFillColor();
305
306
0
            tools::Rectangle aLogicPolyRect( rPolyPoly.GetBoundRect() );
307
0
            tools::Rectangle aPixelRect(LogicToDevicePixel(aLogicPolyRect));
308
309
0
            if( !mbOutputClipped )
310
0
            {
311
0
                bDrawn = mpGraphics->DrawAlphaRect( aPixelRect.Left(), aPixelRect.Top(),
312
                    // #i98405# use methods with small g, else one pixel too much will be painted.
313
                    // This is because the source is a polygon which when painted would not paint
314
                    // the rightmost and lowest pixel line(s), so use one pixel less for the
315
                    // rectangle, too.
316
0
                                                    aPixelRect.getOpenWidth(), aPixelRect.getOpenHeight(),
317
0
                                                    sal::static_int_cast<sal_uInt8>(nTransparencePercent),
318
0
                                                    *this );
319
0
            }
320
0
            else
321
0
            {
322
0
                bDrawn = true;
323
0
            }
324
0
        }
325
326
0
        if( !bDrawn )
327
0
        {
328
0
            ScopedVclPtrInstance< VirtualDevice > aVDev(*this);
329
0
            const Size aDstSz( aDstRect.GetSize() );
330
0
            const sal_uInt8 cTrans = basegfx::fround<sal_uInt8>(nTransparencePercent * 2.55);
331
332
0
            if( aDstRect.Left() || aDstRect.Top() )
333
0
                aPolyPoly.Move( -aDstRect.Left(), -aDstRect.Top() );
334
335
0
            if( aVDev->SetOutputSizePixel( aDstSz ) )
336
0
            {
337
0
                const bool bOldMap = mbMap;
338
339
0
                EnableMapMode( false );
340
341
0
                aVDev->SetLineColor( COL_BLACK );
342
0
                aVDev->SetFillColor( COL_BLACK );
343
0
                aVDev->DrawPolyPolygon( aPolyPoly );
344
345
0
                Bitmap aPaint( GetBitmap( aDstRect.TopLeft(), aDstSz ) );
346
0
                Bitmap aPolyMask( aVDev->GetBitmap( Point(), aDstSz ) );
347
348
                // #107766# check for non-empty bitmaps before accessing them
349
0
                if( !aPaint.IsEmpty() && !aPolyMask.IsEmpty() )
350
0
                {
351
0
                    BitmapScopedWriteAccess pW(aPaint);
352
0
                    BitmapScopedReadAccess pR(aPolyMask);
353
354
0
                    if( pW && pR )
355
0
                    {
356
0
                        BitmapColor aPixCol;
357
0
                        const BitmapColor aFillCol( GetFillColor() );
358
0
                        const BitmapColor aBlack( pR->GetBestMatchingColor( COL_BLACK ) );
359
0
                        const tools::Long nWidth = pW->Width();
360
0
                        const tools::Long nHeight = pW->Height();
361
0
                        tools::Long nX, nY;
362
363
0
                        if (vcl::isPalettePixelFormat(aPaint.getPixelFormat()))
364
0
                        {
365
0
                            const BitmapPalette& rPal = pW->GetPalette();
366
0
                            const sal_uInt16 nCount = rPal.GetEntryCount();
367
0
                            std::unique_ptr<sal_uInt8[]> xMap(new sal_uInt8[ nCount * sizeof( BitmapColor )]);
368
0
                            BitmapColor* pMap = reinterpret_cast<BitmapColor*>(xMap.get());
369
370
0
                            for( sal_uInt16 i = 0; i < nCount; i++ )
371
0
                            {
372
0
                                BitmapColor aCol( rPal[ i ] );
373
0
                                aCol.Merge( aFillCol, cTrans );
374
0
                                pMap[ i ] = BitmapColor( static_cast<sal_uInt8>(rPal.GetBestIndex( aCol )) );
375
0
                            }
376
377
0
                            for( nY = 0; nY < nHeight; nY++ )
378
0
                            {
379
0
                                Scanline pScanline = pW->GetScanline(nY);
380
0
                                Scanline pScanlineRead = pR->GetScanline(nY);
381
0
                                for( nX = 0; nX < nWidth; nX++ )
382
0
                                {
383
0
                                    if( pR->GetPixelFromData( pScanlineRead, nX ) == aBlack )
384
0
                                    {
385
0
                                        pW->SetPixelOnData( pScanline, nX, pMap[ pW->GetIndexFromData( pScanline, nX ) ] );
386
0
                                    }
387
0
                                }
388
0
                            }
389
0
                        }
390
0
                        else
391
0
                        {
392
0
                            for( nY = 0; nY < nHeight; nY++ )
393
0
                            {
394
0
                                Scanline pScanline = pW->GetScanline(nY);
395
0
                                Scanline pScanlineRead = pR->GetScanline(nY);
396
0
                                for( nX = 0; nX < nWidth; nX++ )
397
0
                                {
398
0
                                    if( pR->GetPixelFromData( pScanlineRead, nX ) == aBlack )
399
0
                                    {
400
0
                                        aPixCol = pW->GetColor( nY, nX );
401
0
                                        aPixCol.Merge(aFillCol, cTrans);
402
0
                                        pW->SetPixelOnData(pScanline, nX, aPixCol);
403
0
                                    }
404
0
                                }
405
0
                            }
406
0
                        }
407
0
                    }
408
409
0
                    pR.reset();
410
0
                    pW.reset();
411
412
0
                    DrawBitmap( aDstRect.TopLeft(), aPaint );
413
414
0
                    EnableMapMode( bOldMap );
415
416
0
                    if( mbLineColor )
417
0
                    {
418
0
                        auto popIt = ScopedPush(vcl::PushFlags::FILLCOLOR);
419
0
                        SetFillColor();
420
0
                        DrawPolyPolygon( rPolyPoly );
421
0
                    }
422
0
                }
423
0
            }
424
0
            else
425
0
            {
426
0
                DrawPolyPolygon( rPolyPoly );
427
0
            }
428
0
        }
429
0
    }
430
431
344
    mpMetaFile = pOldMetaFile;
432
344
}
433
434
void OutputDevice::DrawTransparent( const tools::PolyPolygon& rPolyPoly,
435
                                    sal_uInt16 nTransparencePercent )
436
11.3k
{
437
11.3k
    assert(!is_double_buffered_window());
438
439
    // short circuit for drawing an opaque polygon
440
11.3k
    if( (nTransparencePercent < 1) || (mnDrawMode & DrawModeFlags::NoTransparency) )
441
8.55k
    {
442
8.55k
        DrawPolyPolygon( rPolyPoly );
443
8.55k
        return;
444
8.55k
    }
445
446
    // short circuit for drawing an invisible polygon
447
2.82k
    if( (!mbFillColor && !mbLineColor) || (nTransparencePercent >= 100) )
448
895
        return; // tdf#84294: do not record it in metafile
449
450
    // handle metafile recording
451
1.92k
    if( mpMetaFile )
452
0
        mpMetaFile->AddAction( new MetaTransparentAction( rPolyPoly, nTransparencePercent ) );
453
454
1.92k
    bool bDrawn = !IsDeviceOutputNecessary() || ImplIsRecordLayout();
455
1.92k
    if( bDrawn )
456
0
        return;
457
458
    // get the device graphics as drawing target
459
1.92k
    if( !mpGraphics && !AcquireGraphics() )
460
0
        return;
461
1.92k
    assert(mpGraphics);
462
463
    // try hard to draw it directly, because the emulation layers are slower
464
1.92k
    bDrawn = DrawTransparentNatively( rPolyPoly, nTransparencePercent );
465
466
1.92k
    if (!bDrawn)
467
344
        EmulateDrawTransparent( rPolyPoly, nTransparencePercent );
468
1.92k
}
469
470
void OutputDevice::DrawTransparent( const GDIMetaFile& rMtf, const Point& rPos,
471
                                    const Size& rSize, const Gradient& rTransparenceGradient )
472
190
{
473
190
    DrawTransparent( rMtf, rPos, rSize, rPos, rSize, rTransparenceGradient );
474
190
}
475
476
void OutputDevice::DrawTransparent( const GDIMetaFile& rMtf, const Point& rPos, const Size& rSize,
477
                                    const Point& rMtfPos, const Size& rMtfSize,
478
                                    const Gradient& rTransparenceGradient )
479
190
{
480
190
    assert(!is_double_buffered_window());
481
482
190
    const Color aBlack( COL_BLACK );
483
484
190
    if( mpMetaFile )
485
6
    {
486
         // missing here is to map the data using the DeviceTransformation
487
6
        mpMetaFile->AddAction( new MetaFloatTransparentAction( rMtf, rPos, rSize, rTransparenceGradient ) );
488
6
    }
489
490
190
    if ( !IsDeviceOutputNecessary() )
491
6
        return;
492
493
184
    if( ( rTransparenceGradient.GetStartColor() == aBlack && rTransparenceGradient.GetEndColor() == aBlack ) ||
494
93
        ( mnDrawMode & DrawModeFlags::NoTransparency ) )
495
91
    {
496
91
        const_cast<GDIMetaFile&>(rMtf).WindStart();
497
91
        const_cast<GDIMetaFile&>(rMtf).Play(*this, rMtfPos, rMtfSize);
498
91
        const_cast<GDIMetaFile&>(rMtf).WindStart();
499
91
    }
500
93
    else
501
93
    {
502
93
        GDIMetaFile* pOldMetaFile = mpMetaFile;
503
93
        tools::Rectangle aOutRect( LogicToPixel( tools::Rectangle(rPos, rSize) ) );
504
93
        Point aPoint;
505
93
        tools::Rectangle aDstRect( aPoint, GetOutputSizePixel() );
506
507
93
        mpMetaFile = nullptr;
508
93
        aDstRect.Intersection( aOutRect );
509
510
93
        ClipToPaintRegion( aDstRect );
511
512
93
        if( !aDstRect.IsEmpty() )
513
57
        {
514
            // Create transparent buffer
515
57
            ScopedVclPtrInstance<VirtualDevice> xVDev(DeviceFormat::WITH_ALPHA);
516
517
57
            xVDev->SetDPIX(GetDPIX());
518
57
            xVDev->SetDPIY(GetDPIY());
519
520
57
            if( xVDev->SetOutputSizePixel( aDstRect.GetSize(), true, true ) )
521
57
            {
522
                // tdf#150610 fix broken rendering of text meta actions
523
                // Even when drawing to a VirtualDevice that has antialiasing
524
                // disabled, text will still be drawn with some antialiased
525
                // pixels on HiDPI displays. So, use the antialiasing enabled
526
                // code to render if there are any text meta actions in the
527
                // metafile.
528
57
                if(GetAntialiasing() != AntialiasingFlags::NONE || rPos != rMtfPos || rSize != rMtfSize)
529
0
                {
530
                    // #i102109#
531
                    // For MetaFile replay (see task) it may now be necessary to take
532
                    // into account that the content is AntiAlialiased and needs to be masked
533
                    // like that. Instead of masking, i will use a copy-modify-paste cycle
534
                    // here (as i already use in the VclPrimiziveRenderer with success)
535
0
                    xVDev->SetAntialiasing(GetAntialiasing());
536
537
                    // create MapMode for buffer (offset needed) and set
538
0
                    MapMode aMap(GetMapMode());
539
0
                    const Point aOutPos(PixelToLogic(aDstRect.TopLeft()));
540
0
                    aMap.SetOrigin(Point(-aOutPos.X(), -aOutPos.Y()));
541
0
                    xVDev->SetMapMode(aMap);
542
543
                    // copy MapMode state and disable for target
544
0
                    const bool bOrigMapModeEnabled(IsMapModeEnabled());
545
0
                    EnableMapMode(false);
546
547
                    // copy MapMode state and disable for buffer
548
0
                    const bool bBufferMapModeEnabled(xVDev->IsMapModeEnabled());
549
0
                    xVDev->EnableMapMode(false);
550
551
                    // copy content from original to buffer
552
0
                    xVDev->DrawOutDev( aPoint, xVDev->GetOutputSizePixel(), // dest
553
0
                                       aDstRect.TopLeft(), xVDev->GetOutputSizePixel(), // source
554
0
                                       *this);
555
556
                    // draw MetaFile to buffer
557
0
                    xVDev->EnableMapMode(bBufferMapModeEnabled);
558
0
                    const_cast<GDIMetaFile&>(rMtf).WindStart();
559
0
                    const_cast<GDIMetaFile&>(rMtf).Play(*xVDev, rMtfPos, rMtfSize);
560
0
                    const_cast<GDIMetaFile&>(rMtf).WindStart();
561
562
                    // get content bitmap from buffer
563
0
                    xVDev->EnableMapMode(false);
564
565
0
                    const Bitmap aPaint(xVDev->GetBitmap(aPoint, xVDev->GetOutputSizePixel()));
566
567
                    // create alpha mask from gradient and get as Bitmap
568
0
                    xVDev->EnableMapMode(bBufferMapModeEnabled);
569
0
                    xVDev->SetDrawMode(DrawModeFlags::GrayGradient);
570
                    // Related tdf#150610 draw gradient to VirtualDevice bounds
571
                    // If we are here and the metafile bounds differs from the
572
                    // VirtualDevice bounds so that we apply the transparency
573
                    // gradient to any pixels drawn outside of the metafile
574
                    // bounds.
575
0
                    xVDev->DrawGradient(tools::Rectangle(rPos, rSize), rTransparenceGradient);
576
0
                    xVDev->SetDrawMode(DrawModeFlags::Default);
577
0
                    xVDev->EnableMapMode(false);
578
579
0
                    AlphaMask aAlpha(xVDev->GetBitmap(aPoint, xVDev->GetOutputSizePixel()));
580
0
                    const AlphaMask aPaintAlpha(aPaint.CreateAlphaMask());
581
                    // The alpha mask is inverted from what
582
                    // is expected so invert it again
583
0
                    aAlpha.Invert(); // convert to alpha
584
0
                    aAlpha.BlendWith(aPaintAlpha);
585
586
0
                    xVDev.disposeAndClear();
587
588
                    // draw masked content to target and restore MapMode
589
0
                    DrawBitmap(aDstRect.TopLeft(), Bitmap(aPaint.CreateColorBitmap(), aAlpha));
590
0
                    EnableMapMode(bOrigMapModeEnabled);
591
0
                }
592
57
                else
593
57
                {
594
57
                    MapMode aMap( GetMapMode() );
595
57
                    Point aOutPos( PixelToLogic( aDstRect.TopLeft() ) );
596
57
                    const bool bOldMap = mbMap;
597
598
57
                    aMap.SetOrigin( Point( -aOutPos.X(), -aOutPos.Y() ) );
599
57
                    xVDev->SetMapMode( aMap );
600
57
                    const bool bVDevOldMap = xVDev->IsMapModeEnabled();
601
602
                    // create paint bitmap
603
57
                    const_cast<GDIMetaFile&>(rMtf).WindStart();
604
57
                    const_cast<GDIMetaFile&>(rMtf).Play(*xVDev, rMtfPos, rMtfSize);
605
57
                    const_cast<GDIMetaFile&>(rMtf).WindStart();
606
57
                    xVDev->EnableMapMode( false );
607
57
                    Bitmap aPaint(xVDev->GetBitmap(Point(), xVDev->GetOutputSizePixel()));
608
57
                    xVDev->EnableMapMode( bVDevOldMap ); // #i35331#: MUST NOT use EnableMapMode( sal_True ) here!
609
610
                    // create alpha mask from gradient
611
57
                    xVDev->SetDrawMode( DrawModeFlags::GrayGradient );
612
57
                    xVDev->DrawGradient( tools::Rectangle( rMtfPos, rMtfSize ), rTransparenceGradient );
613
57
                    xVDev->SetDrawMode( DrawModeFlags::Default );
614
57
                    xVDev->EnableMapMode( false );
615
616
57
                    AlphaMask aAlpha(xVDev->GetBitmap(Point(), xVDev->GetOutputSizePixel()));
617
57
                    const AlphaMask aPaintAlpha(aPaint.CreateAlphaMask());
618
                    // The alpha mask is inverted from what
619
                    // is expected so invert it again
620
57
                    aAlpha.Invert(); // convert to alpha
621
57
                    aAlpha.BlendWith(aPaintAlpha);
622
623
57
                    xVDev.disposeAndClear();
624
625
57
                    EnableMapMode( false );
626
57
                    DrawBitmap(aDstRect.TopLeft(), Bitmap(aPaint.CreateColorBitmap(), aAlpha));
627
57
                    EnableMapMode( bOldMap );
628
57
                }
629
57
            }
630
57
        }
631
632
93
        mpMetaFile = pOldMetaFile;
633
93
    }
634
184
}
635
636
typedef ::std::pair< MetaAction*, int > Component; // MetaAction plus index in metafile
637
638
namespace {
639
640
// List of (intersecting) actions, plus overall bounds
641
struct ConnectedComponents
642
{
643
    ConnectedComponents() :
644
0
        aComponentList(),
645
0
        aBounds(),
646
0
        aBgColor(COL_WHITE),
647
0
        bIsSpecial(false),
648
0
        bIsFullyTransparent(false)
649
0
    {}
650
651
    ::std::list< Component > aComponentList;
652
    tools::Rectangle       aBounds;
653
    Color           aBgColor;
654
    bool            bIsSpecial;
655
    bool            bIsFullyTransparent;
656
};
657
658
}
659
660
namespace {
661
662
/** Determines whether the action can handle transparency correctly
663
  (i.e. when painted on white background, does the action still look
664
  correct)?
665
 */
666
bool DoesActionHandleTransparency( const MetaAction& rAct )
667
0
{
668
    // MetaActionType::FLOATTRANSPARENT can contain a whole metafile,
669
    // which is to be rendered with the given transparent gradient. We
670
    // currently cannot emulate transparent painting on a white
671
    // background reliably.
672
673
    // the remainder can handle printing itself correctly on a uniform
674
    // white background.
675
0
    switch( rAct.GetType() )
676
0
    {
677
0
        case MetaActionType::Transparent:
678
0
        case MetaActionType::BMPEX:
679
0
        case MetaActionType::BMPEXSCALE:
680
0
        case MetaActionType::BMPEXSCALEPART:
681
0
            return true;
682
683
0
        default:
684
0
            return false;
685
0
    }
686
0
}
687
688
bool doesRectCoverWithUniformColor(
689
        tools::Rectangle const & rPrevRect,
690
        tools::Rectangle const & rCurrRect,
691
        OutputDevice const & rMapModeVDev)
692
0
{
693
    // shape needs to fully cover previous content, and have uniform
694
    // color
695
0
    return (rMapModeVDev.LogicToPixel(rCurrRect).Contains(rPrevRect) &&
696
0
        rMapModeVDev.IsFillColor());
697
0
}
698
699
/** Check whether rCurrRect rectangle fully covers io_rPrevRect - if
700
    yes, return true and update o_rBgColor
701
 */
702
bool checkRect( tools::Rectangle&       io_rPrevRect,
703
                       Color&           o_rBgColor,
704
                       const tools::Rectangle& rCurrRect,
705
                       OutputDevice const &    rMapModeVDev )
706
0
{
707
0
    bool bRet = doesRectCoverWithUniformColor(io_rPrevRect, rCurrRect, rMapModeVDev);
708
709
0
    if( bRet )
710
0
    {
711
0
        io_rPrevRect = rCurrRect;
712
0
        o_rBgColor = rMapModeVDev.GetFillColor();
713
0
    }
714
715
0
    return bRet;
716
0
}
717
718
/** #107169# Convert to Bitmap with appropriately blended
719
    color. Convert MetaTransparentAction to plain polygon,
720
    appropriately colored
721
722
    @param o_rMtf
723
    Add converted actions to this metafile
724
*/
725
void ImplConvertTransparentAction( GDIMetaFile&        o_rMtf,
726
                                   const MetaAction&   rAct,
727
                                   const OutputDevice& rStateOutDev,
728
                                   Color               aBgColor )
729
0
{
730
0
    if (rAct.GetType() == MetaActionType::Transparent)
731
0
    {
732
0
        const MetaTransparentAction* pTransAct = static_cast<const MetaTransparentAction*>(&rAct);
733
0
        sal_uInt16 nTransparency( pTransAct->GetTransparence() );
734
735
        // #i10613# Respect transparency for draw color
736
0
        if (nTransparency)
737
0
        {
738
0
            o_rMtf.AddAction(new MetaPushAction(vcl::PushFlags::LINECOLOR|vcl::PushFlags::FILLCOLOR));
739
740
            // assume white background for alpha blending
741
0
            Color aLineColor(rStateOutDev.GetLineColor());
742
0
            aLineColor.SetRed(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency) * aLineColor.GetRed()) / 100));
743
0
            aLineColor.SetGreen(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency) * aLineColor.GetGreen()) / 100));
744
0
            aLineColor.SetBlue(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency) * aLineColor.GetBlue()) / 100));
745
0
            o_rMtf.AddAction(new MetaLineColorAction(aLineColor, true));
746
747
0
            Color aFillColor(rStateOutDev.GetFillColor());
748
0
            aFillColor.SetRed(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency)*aFillColor.GetRed()) / 100));
749
0
            aFillColor.SetGreen(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency)*aFillColor.GetGreen()) / 100));
750
0
            aFillColor.SetBlue(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency)*aFillColor.GetBlue()) / 100));
751
0
            o_rMtf.AddAction(new MetaFillColorAction(aFillColor, true));
752
0
        }
753
754
0
        o_rMtf.AddAction(new MetaPolyPolygonAction(pTransAct->GetPolyPolygon()));
755
756
0
        if(nTransparency)
757
0
            o_rMtf.AddAction(new MetaPopAction());
758
0
    }
759
0
    else
760
0
    {
761
0
        Bitmap aBmp;
762
763
0
        switch (rAct.GetType())
764
0
        {
765
0
            case MetaActionType::BMPEX:
766
0
                aBmp = static_cast<const MetaBmpExAction&>(rAct).GetBitmap();
767
0
                break;
768
769
0
            case MetaActionType::BMPEXSCALE:
770
0
            case MetaActionType::BMPEXSCALEPART:
771
0
                aBmp = static_cast<const MetaBmpExScaleAction&>(rAct).GetBitmap();
772
0
                break;
773
774
0
            case MetaActionType::Transparent:
775
776
0
            default:
777
0
                OSL_FAIL("Printer::GetPreparedMetafile impossible state reached");
778
0
                break;
779
0
        }
780
781
0
        if (aBmp.HasAlpha())
782
0
        {
783
0
            AlphaMask aMask = aBmp.CreateAlphaMask();
784
            // blend with alpha channel
785
0
            aBmp.Convert(BmpConversion::N24Bit);
786
0
            aBmp.Blend(aMask, aBgColor);
787
0
        }
788
789
        // add corresponding action
790
0
        switch (rAct.GetType())
791
0
        {
792
0
            case MetaActionType::BMPEX:
793
0
                o_rMtf.AddAction(new MetaBmpAction(
794
0
                                       static_cast<const MetaBmpExAction&>(rAct).GetPoint(),
795
0
                                       aBmp));
796
0
                break;
797
0
            case MetaActionType::BMPEXSCALE:
798
0
                o_rMtf.AddAction(new MetaBmpScaleAction(
799
0
                                       static_cast<const MetaBmpExScaleAction&>(rAct).GetPoint(),
800
0
                                       static_cast<const MetaBmpExScaleAction&>(rAct).GetSize(),
801
0
                                       aBmp));
802
0
                break;
803
0
            case MetaActionType::BMPEXSCALEPART:
804
0
                o_rMtf.AddAction(new MetaBmpScalePartAction(
805
0
                                       static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestPoint(),
806
0
                                       static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestSize(),
807
0
                                       static_cast<const MetaBmpExScalePartAction&>(rAct).GetSrcPoint(),
808
0
                                       static_cast<const MetaBmpExScalePartAction&>(rAct).GetSrcSize(),
809
0
                                       aBmp));
810
0
                break;
811
0
            default:
812
0
                OSL_FAIL("Unexpected case");
813
0
                break;
814
0
        }
815
0
    }
816
0
}
817
818
// #i10613# Extracted from ImplCheckRect::ImplCreate
819
// Returns true, if given action creates visible (i.e. non-transparent) output
820
bool ImplIsNotTransparent( const MetaAction& rAct, const OutputDevice& rOut )
821
0
{
822
0
    const bool  bLineTransparency( !rOut.IsLineColor() || rOut.GetLineColor().IsFullyTransparent() );
823
0
    const bool  bFillTransparency( !rOut.IsFillColor() || rOut.GetFillColor().IsFullyTransparent() );
824
0
    bool        bRet( false );
825
826
0
    switch( rAct.GetType() )
827
0
    {
828
0
        case MetaActionType::POINT:
829
0
        case MetaActionType::LINE:
830
0
        case MetaActionType::POLYLINE:
831
0
            if( !bLineTransparency )
832
0
                bRet = true;
833
0
            break;
834
835
0
        case MetaActionType::RECT:
836
0
            if( !bLineTransparency || !bFillTransparency )
837
0
                bRet = true;
838
0
            break;
839
840
0
        case MetaActionType::ROUNDRECT:
841
0
            if( !bLineTransparency || !bFillTransparency )
842
0
                bRet = true;
843
0
            break;
844
845
0
        case MetaActionType::ELLIPSE:
846
0
            if( !bLineTransparency || !bFillTransparency )
847
0
                bRet = true;
848
0
            break;
849
850
0
        case MetaActionType::ARC:
851
0
            if( !bLineTransparency || !bFillTransparency )
852
0
                bRet = true;
853
0
            break;
854
855
0
        case MetaActionType::PIE:
856
0
            if( !bLineTransparency || !bFillTransparency )
857
0
                bRet = true;
858
0
            break;
859
860
0
        case MetaActionType::CHORD:
861
0
            if( !bLineTransparency || !bFillTransparency )
862
0
                bRet = true;
863
0
            break;
864
865
0
        case MetaActionType::POLYGON:
866
0
            if( !bLineTransparency || !bFillTransparency )
867
0
                bRet = true;
868
0
            break;
869
870
0
        case MetaActionType::POLYPOLYGON:
871
0
            if( !bLineTransparency || !bFillTransparency )
872
0
                bRet = true;
873
0
            break;
874
875
0
        case MetaActionType::TEXT:
876
0
        {
877
0
            const MetaTextAction& rTextAct = static_cast<const MetaTextAction&>(rAct);
878
0
            const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
879
0
            if (!aString.isEmpty())
880
0
                bRet = true;
881
0
        }
882
0
        break;
883
884
0
        case MetaActionType::TEXTARRAY:
885
0
        {
886
0
            const MetaTextArrayAction& rTextAct = static_cast<const MetaTextArrayAction&>(rAct);
887
0
            const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
888
0
            if (!aString.isEmpty())
889
0
                bRet = true;
890
0
        }
891
0
        break;
892
893
0
        case MetaActionType::PIXEL:
894
0
        case MetaActionType::BMP:
895
0
        case MetaActionType::BMPSCALE:
896
0
        case MetaActionType::BMPSCALEPART:
897
0
        case MetaActionType::BMPEX:
898
0
        case MetaActionType::BMPEXSCALE:
899
0
        case MetaActionType::BMPEXSCALEPART:
900
0
        case MetaActionType::MASK:
901
0
        case MetaActionType::MASKSCALE:
902
0
        case MetaActionType::MASKSCALEPART:
903
0
        case MetaActionType::GRADIENT:
904
0
        case MetaActionType::GRADIENTEX:
905
0
        case MetaActionType::HATCH:
906
0
        case MetaActionType::WALLPAPER:
907
0
        case MetaActionType::Transparent:
908
0
        case MetaActionType::FLOATTRANSPARENT:
909
0
        case MetaActionType::EPS:
910
0
        case MetaActionType::TEXTRECT:
911
0
        case MetaActionType::STRETCHTEXT:
912
0
        case MetaActionType::TEXTLINE:
913
            // all other actions: generate non-transparent output
914
0
            bRet = true;
915
0
            break;
916
917
0
        default:
918
0
            break;
919
0
    }
920
921
0
    return bRet;
922
0
}
923
924
// #i10613# Extracted from ImplCheckRect::ImplCreate
925
tools::Rectangle ImplCalcActionBounds( const MetaAction& rAct, const OutputDevice& rOut )
926
0
{
927
0
    tools::Rectangle aActionBounds;
928
929
0
    switch( rAct.GetType() )
930
0
    {
931
0
        case MetaActionType::PIXEL:
932
0
            aActionBounds = tools::Rectangle( static_cast<const MetaPixelAction&>(rAct).GetPoint(), Size( 1, 1 ) );
933
0
            break;
934
935
0
        case MetaActionType::POINT:
936
0
            aActionBounds = tools::Rectangle( static_cast<const MetaPointAction&>(rAct).GetPoint(), Size( 1, 1 ) );
937
0
            break;
938
939
0
        case MetaActionType::LINE:
940
0
        {
941
0
            const MetaLineAction& rMetaLineAction = static_cast<const MetaLineAction&>(rAct);
942
0
            aActionBounds = tools::Rectangle( rMetaLineAction.GetStartPoint(),  rMetaLineAction.GetEndPoint() );
943
0
            aActionBounds.Normalize();
944
0
            const tools::Long nLineWidth(rMetaLineAction.GetLineInfo().GetWidth());
945
0
            if(nLineWidth)
946
0
            {
947
0
                const tools::Long nHalfLineWidth((nLineWidth + 1) / 2);
948
0
                aActionBounds.AdjustLeft( -nHalfLineWidth );
949
0
                aActionBounds.AdjustTop( -nHalfLineWidth );
950
0
                aActionBounds.AdjustRight(nHalfLineWidth );
951
0
                aActionBounds.AdjustBottom(nHalfLineWidth );
952
0
            }
953
0
            break;
954
0
        }
955
956
0
        case MetaActionType::RECT:
957
0
            aActionBounds = static_cast<const MetaRectAction&>(rAct).GetRect();
958
0
            break;
959
960
0
        case MetaActionType::ROUNDRECT:
961
0
            aActionBounds = tools::Polygon( static_cast<const MetaRoundRectAction&>(rAct).GetRect(),
962
0
                                            static_cast<const MetaRoundRectAction&>(rAct).GetHorzRound(),
963
0
                                            static_cast<const MetaRoundRectAction&>(rAct).GetVertRound() ).GetBoundRect();
964
0
            break;
965
966
0
        case MetaActionType::ELLIPSE:
967
0
        {
968
0
            const tools::Rectangle& rRect = static_cast<const MetaEllipseAction&>(rAct).GetRect();
969
0
            aActionBounds = tools::Polygon( rRect.Center(),
970
0
                                            rRect.GetWidth() >> 1,
971
0
                                            rRect.GetHeight() >> 1 ).GetBoundRect();
972
0
            break;
973
0
        }
974
975
0
        case MetaActionType::ARC:
976
0
            aActionBounds = tools::Polygon( static_cast<const MetaArcAction&>(rAct).GetRect(),
977
0
                                            static_cast<const MetaArcAction&>(rAct).GetStartPoint(),
978
0
                                            static_cast<const MetaArcAction&>(rAct).GetEndPoint(), PolyStyle::Arc ).GetBoundRect();
979
0
            break;
980
981
0
        case MetaActionType::PIE:
982
0
            aActionBounds = tools::Polygon( static_cast<const MetaPieAction&>(rAct).GetRect(),
983
0
                                            static_cast<const MetaPieAction&>(rAct).GetStartPoint(),
984
0
                                            static_cast<const MetaPieAction&>(rAct).GetEndPoint(), PolyStyle::Pie ).GetBoundRect();
985
0
            break;
986
987
0
        case MetaActionType::CHORD:
988
0
            aActionBounds = tools::Polygon( static_cast<const MetaChordAction&>(rAct).GetRect(),
989
0
                                            static_cast<const MetaChordAction&>(rAct).GetStartPoint(),
990
0
                                            static_cast<const MetaChordAction&>(rAct).GetEndPoint(), PolyStyle::Chord ).GetBoundRect();
991
0
            break;
992
993
0
        case MetaActionType::POLYLINE:
994
0
        {
995
0
            const MetaPolyLineAction& rMetaPolyLineAction = static_cast<const MetaPolyLineAction&>(rAct);
996
0
            aActionBounds = rMetaPolyLineAction.GetPolygon().GetBoundRect();
997
0
            const tools::Long nLineWidth(rMetaPolyLineAction.GetLineInfo().GetWidth());
998
0
            if(nLineWidth)
999
0
            {
1000
0
                const tools::Long nHalfLineWidth((nLineWidth + 1) / 2);
1001
0
                aActionBounds.AdjustLeft( -nHalfLineWidth );
1002
0
                aActionBounds.AdjustTop( -nHalfLineWidth );
1003
0
                aActionBounds.AdjustRight(nHalfLineWidth );
1004
0
                aActionBounds.AdjustBottom(nHalfLineWidth );
1005
0
            }
1006
0
            break;
1007
0
        }
1008
1009
0
        case MetaActionType::POLYGON:
1010
0
            aActionBounds = static_cast<const MetaPolygonAction&>(rAct).GetPolygon().GetBoundRect();
1011
0
            break;
1012
1013
0
        case MetaActionType::POLYPOLYGON:
1014
0
            aActionBounds = static_cast<const MetaPolyPolygonAction&>(rAct).GetPolyPolygon().GetBoundRect();
1015
0
            break;
1016
1017
0
        case MetaActionType::BMP:
1018
0
            aActionBounds = tools::Rectangle( static_cast<const MetaBmpAction&>(rAct).GetPoint(),
1019
0
                                       rOut.PixelToLogic( static_cast<const MetaBmpAction&>(rAct).GetBitmap().GetSizePixel() ) );
1020
0
            break;
1021
1022
0
        case MetaActionType::BMPSCALE:
1023
0
            aActionBounds = tools::Rectangle( static_cast<const MetaBmpScaleAction&>(rAct).GetPoint(),
1024
0
                                       static_cast<const MetaBmpScaleAction&>(rAct).GetSize() );
1025
0
            break;
1026
1027
0
        case MetaActionType::BMPSCALEPART:
1028
0
            aActionBounds = tools::Rectangle( static_cast<const MetaBmpScalePartAction&>(rAct).GetDestPoint(),
1029
0
                                       static_cast<const MetaBmpScalePartAction&>(rAct).GetDestSize() );
1030
0
            break;
1031
1032
0
        case MetaActionType::BMPEX:
1033
0
            aActionBounds = tools::Rectangle( static_cast<const MetaBmpExAction&>(rAct).GetPoint(),
1034
0
                                       rOut.PixelToLogic( static_cast<const MetaBmpExAction&>(rAct).GetBitmap().GetSizePixel() ) );
1035
0
            break;
1036
1037
0
        case MetaActionType::BMPEXSCALE:
1038
0
            aActionBounds = tools::Rectangle( static_cast<const MetaBmpExScaleAction&>(rAct).GetPoint(),
1039
0
                                       static_cast<const MetaBmpExScaleAction&>(rAct).GetSize() );
1040
0
            break;
1041
1042
0
        case MetaActionType::BMPEXSCALEPART:
1043
0
            aActionBounds = tools::Rectangle( static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestPoint(),
1044
0
                                       static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestSize() );
1045
0
            break;
1046
1047
0
        case MetaActionType::MASK:
1048
0
            aActionBounds = tools::Rectangle( static_cast<const MetaMaskAction&>(rAct).GetPoint(),
1049
0
                                       rOut.PixelToLogic( static_cast<const MetaMaskAction&>(rAct).GetBitmap().GetSizePixel() ) );
1050
0
            break;
1051
1052
0
        case MetaActionType::MASKSCALE:
1053
0
            aActionBounds = tools::Rectangle( static_cast<const MetaMaskScaleAction&>(rAct).GetPoint(),
1054
0
                                       static_cast<const MetaMaskScaleAction&>(rAct).GetSize() );
1055
0
            break;
1056
1057
0
        case MetaActionType::MASKSCALEPART:
1058
0
            aActionBounds = tools::Rectangle( static_cast<const MetaMaskScalePartAction&>(rAct).GetDestPoint(),
1059
0
                                       static_cast<const MetaMaskScalePartAction&>(rAct).GetDestSize() );
1060
0
            break;
1061
1062
0
        case MetaActionType::GRADIENT:
1063
0
            aActionBounds = static_cast<const MetaGradientAction&>(rAct).GetRect();
1064
0
            break;
1065
1066
0
        case MetaActionType::GRADIENTEX:
1067
0
            aActionBounds = static_cast<const MetaGradientExAction&>(rAct).GetPolyPolygon().GetBoundRect();
1068
0
            break;
1069
1070
0
        case MetaActionType::HATCH:
1071
0
            aActionBounds = static_cast<const MetaHatchAction&>(rAct).GetPolyPolygon().GetBoundRect();
1072
0
            break;
1073
1074
0
        case MetaActionType::WALLPAPER:
1075
0
            aActionBounds = static_cast<const MetaWallpaperAction&>(rAct).GetRect();
1076
0
            break;
1077
1078
0
        case MetaActionType::Transparent:
1079
0
            aActionBounds = static_cast<const MetaTransparentAction&>(rAct).GetPolyPolygon().GetBoundRect();
1080
0
            break;
1081
1082
0
        case MetaActionType::FLOATTRANSPARENT:
1083
0
            aActionBounds = tools::Rectangle( static_cast<const MetaFloatTransparentAction&>(rAct).GetPoint(),
1084
0
                                       static_cast<const MetaFloatTransparentAction&>(rAct).GetSize() );
1085
0
            break;
1086
1087
0
        case MetaActionType::EPS:
1088
0
            aActionBounds = tools::Rectangle( static_cast<const MetaEPSAction&>(rAct).GetPoint(),
1089
0
                                       static_cast<const MetaEPSAction&>(rAct).GetSize() );
1090
0
            break;
1091
1092
0
        case MetaActionType::TEXT:
1093
0
        {
1094
0
            const MetaTextAction& rTextAct = static_cast<const MetaTextAction&>(rAct);
1095
0
            const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
1096
1097
0
            if (!aString.isEmpty())
1098
0
            {
1099
0
                const Point aPtLog( rTextAct.GetPoint() );
1100
1101
                // #105987# Use API method instead of Impl* methods
1102
                // #107490# Set base parameter equal to index parameter
1103
0
                rOut.GetTextBoundRect( aActionBounds, rTextAct.GetText(), rTextAct.GetIndex(),
1104
0
                                       rTextAct.GetIndex(), rTextAct.GetLen() );
1105
0
                aActionBounds.Move( aPtLog.X(), aPtLog.Y() );
1106
0
            }
1107
0
        }
1108
0
        break;
1109
1110
0
        case MetaActionType::TEXTARRAY:
1111
0
        {
1112
0
            const MetaTextArrayAction&  rTextAct = static_cast<const MetaTextArrayAction&>(rAct);
1113
0
            const OUString              aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
1114
1115
0
            if( !aString.isEmpty() )
1116
0
            {
1117
                // #105987# ImplLayout takes everything in logical coordinates
1118
0
                std::unique_ptr<SalLayout> pSalLayout;
1119
0
                if (rTextAct.GetLayoutContextIndex() >= 0)
1120
0
                {
1121
0
                    pSalLayout = rOut.ImplLayout(
1122
0
                        rTextAct.GetText(), rTextAct.GetLayoutContextIndex(),
1123
0
                        rTextAct.GetLayoutContextLen(), rTextAct.GetPoint(), 0,
1124
0
                        rTextAct.GetDXArray(), rTextAct.GetKashidaArray(), SalLayoutFlags::NONE,
1125
0
                        /*pTextLayoutCache=*/nullptr,
1126
0
                        /*pGlyphs=*/nullptr,
1127
0
                        /*nDrawOriginCluster=*/rTextAct.GetIndex(),
1128
0
                        /*nDrawMinCharPos=*/rTextAct.GetIndex(),
1129
0
                        /*nDrawEndCharPos=*/rTextAct.GetIndex() + rTextAct.GetLen());
1130
0
                }
1131
0
                else
1132
0
                {
1133
0
                    pSalLayout = rOut.ImplLayout(rTextAct.GetText(), rTextAct.GetIndex(),
1134
0
                                                 rTextAct.GetLen(), rTextAct.GetPoint(), 0,
1135
0
                                                 rTextAct.GetDXArray(), rTextAct.GetKashidaArray());
1136
0
                }
1137
1138
0
                if( pSalLayout )
1139
0
                {
1140
0
                    tools::Rectangle aBoundRect( rOut.ImplGetTextBoundRect( *pSalLayout ) );
1141
0
                    aActionBounds = rOut.PixelToLogic( aBoundRect );
1142
0
                }
1143
0
            }
1144
0
        }
1145
0
        break;
1146
1147
0
        case MetaActionType::TEXTRECT:
1148
0
            aActionBounds = static_cast<const MetaTextRectAction&>(rAct).GetRect();
1149
0
            break;
1150
1151
0
        case MetaActionType::STRETCHTEXT:
1152
0
        {
1153
0
            const MetaStretchTextAction& rTextAct = static_cast<const MetaStretchTextAction&>(rAct);
1154
0
            const OUString               aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
1155
1156
            // #i16195# Literate copy from TextArray action, the
1157
            // semantics for the ImplLayout call are copied from the
1158
            // OutDev::DrawStretchText() code. Unfortunately, also in
1159
            // this case, public outdev methods such as GetTextWidth()
1160
            // don't provide enough info.
1161
0
            if( !aString.isEmpty() )
1162
0
            {
1163
                // #105987# ImplLayout takes everything in logical coordinates
1164
0
                std::unique_ptr<SalLayout> pSalLayout = rOut.ImplLayout( rTextAct.GetText(), rTextAct.GetIndex(),
1165
0
                                                         rTextAct.GetLen(), rTextAct.GetPoint(),
1166
0
                                                         rTextAct.GetWidth() );
1167
0
                if( pSalLayout )
1168
0
                {
1169
0
                    tools::Rectangle aBoundRect( rOut.ImplGetTextBoundRect( *pSalLayout ) );
1170
0
                    aActionBounds = rOut.PixelToLogic( aBoundRect );
1171
0
                }
1172
0
            }
1173
0
        }
1174
0
        break;
1175
1176
0
        case MetaActionType::TEXTLINE:
1177
0
            OSL_FAIL("MetaActionType::TEXTLINE not supported");
1178
0
        break;
1179
1180
0
        default:
1181
0
            break;
1182
0
    }
1183
1184
0
    if( !aActionBounds.IsEmpty() )
1185
0
    {
1186
        // fdo#40421 limit current action's output to clipped area
1187
0
        if( rOut.IsClipRegion() )
1188
0
            return rOut.LogicToPixel(
1189
0
                rOut.GetClipRegion().GetBoundRect().Intersection( aActionBounds ) );
1190
0
        else
1191
0
            return rOut.LogicToPixel( aActionBounds );
1192
0
    }
1193
0
    else
1194
0
        return tools::Rectangle();
1195
0
}
1196
1197
} // end anon namespace
1198
1199
// TODO: this massive function operates on metafiles, so eventually it should probably
1200
// be shifted to the GDIMetaFile class
1201
bool OutputDevice::RemoveTransparenciesFromMetaFile( const GDIMetaFile& rInMtf, GDIMetaFile& rOutMtf,
1202
                                                     tools::Long nMaxBmpDPIX, tools::Long nMaxBmpDPIY,
1203
                                                     bool bReduceTransparency, bool bTransparencyAutoMode,
1204
                                                     bool bDownsampleBitmaps,
1205
                                                     const Color& rBackground
1206
                                                     )
1207
0
{
1208
0
    MetaAction*             pCurrAct;
1209
0
    bool                    bTransparent( false );
1210
1211
0
    rOutMtf.Clear();
1212
1213
#ifdef MACOSX
1214
    // tdf#164354 skip unnecessary transparency removal
1215
    // On macOS, there are no known problems drawing semi-transparent
1216
    // shapes, fill, text, and bitmaps so only do this sometimes very
1217
    // expensive step when both reduce transparency and no
1218
    // transparency are true.
1219
    if(bReduceTransparency && !bTransparencyAutoMode)
1220
#else
1221
0
    if(!bReduceTransparency || bTransparencyAutoMode)
1222
0
#endif
1223
0
        bTransparent = rInMtf.HasTransparentActions();
1224
1225
    // #i10613# Determine set of connected components containing transparent objects. These are
1226
    // then processed as bitmaps, the original actions are removed from the metafile.
1227
0
    if( !bTransparent )
1228
0
    {
1229
        // nothing transparent -> just copy
1230
0
        rOutMtf = rInMtf;
1231
0
    }
1232
0
    else
1233
0
    {
1234
        // #i10613#
1235
        // This works as follows: we want a number of distinct sets of
1236
        // connected components, where each set contains metafile
1237
        // actions that are intersecting (note: there are possibly
1238
        // more actions contained as are directly intersecting,
1239
        // because we can only produce rectangular bitmaps later
1240
        // on. Thus, each set of connected components is the smallest
1241
        // enclosing, axis-aligned rectangle that completely bounds a
1242
        // number of intersecting metafile actions, plus any action
1243
        // that would otherwise be cut in two). Therefore, we
1244
        // iteratively add metafile actions from the original metafile
1245
        // to this connected components list (aCCList), by checking
1246
        // each element's bounding box against intersection with the
1247
        // metaaction at hand.
1248
        // All those intersecting elements are removed from aCCList
1249
        // and collected in a temporary list (aCCMergeList). After all
1250
        // elements have been checked, the aCCMergeList elements are
1251
        // merged with the metaaction at hand into one resulting
1252
        // connected component, with one big bounding box, and
1253
        // inserted into aCCList again.
1254
        // The time complexity of this algorithm is O(n^3), where n is
1255
        // the number of metafile actions, and it finds all distinct
1256
        // regions of rectangle-bounded connected components. This
1257
        // algorithm was designed by AF.
1258
1259
        //  STAGE 1: Detect background
1260
1261
        // Receives uniform background content, and is _not_ merged
1262
        // nor checked for intersection against other aCCList elements
1263
0
        ConnectedComponents aBackgroundComponent;
1264
1265
        // Read the configuration value of minimal object area where transparency will be removed
1266
0
        double fReduceTransparencyMinArea = officecfg::Office::Common::VCL::ReduceTransparencyMinArea::get() / 100.0;
1267
0
        SAL_WARN_IF(fReduceTransparencyMinArea > 1.0, "vcl",
1268
0
            "Value of ReduceTransparencyMinArea config option is too high");
1269
0
        SAL_WARN_IF(fReduceTransparencyMinArea < 0.0, "vcl",
1270
0
            "Value of ReduceTransparencyMinArea config option is too low");
1271
0
        fReduceTransparencyMinArea = std::clamp(fReduceTransparencyMinArea, 0.0, 1.0);
1272
1273
        // create an OutputDevice to record mapmode changes and the like
1274
0
        ScopedVclPtrInstance< VirtualDevice > aMapModeVDev;
1275
1276
0
        aMapModeVDev->SetDPIX(GetDPIX());
1277
0
        aMapModeVDev->SetDPIY(GetDPIY());
1278
1279
0
        aMapModeVDev->EnableOutput(false);
1280
1281
        // weed out page-filling background objects (if they are
1282
        // uniformly coloured). Keeping them outside the other
1283
        // connected components often prevents whole-page bitmap
1284
        // generation.
1285
0
        bool bStillBackground=true; // true until first non-bg action
1286
0
        int nActionNum = 0, nLastBgAction = -1;
1287
0
        pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction();
1288
0
        if( rBackground != COL_TRANSPARENT )
1289
0
        {
1290
0
            aBackgroundComponent.aBgColor = rBackground;
1291
0
            aBackgroundComponent.aBounds = GetBackgroundComponentBounds();
1292
0
        }
1293
0
        while( pCurrAct && bStillBackground )
1294
0
        {
1295
0
            switch( pCurrAct->GetType() )
1296
0
            {
1297
0
                case MetaActionType::RECT:
1298
0
                {
1299
0
                    if( !checkRect(
1300
0
                            aBackgroundComponent.aBounds,
1301
0
                            aBackgroundComponent.aBgColor,
1302
0
                            static_cast<const MetaRectAction*>(pCurrAct)->GetRect(),
1303
0
                            *aMapModeVDev) )
1304
0
                        bStillBackground=false; // incomplete occlusion of background
1305
0
                    else
1306
0
                        nLastBgAction=nActionNum; // this _is_ background
1307
0
                    break;
1308
0
                }
1309
0
                case MetaActionType::POLYGON:
1310
0
                {
1311
0
                    const tools::Polygon aPoly(
1312
0
                        static_cast<const MetaPolygonAction*>(pCurrAct)->GetPolygon());
1313
0
                    if( !basegfx::utils::isRectangle(
1314
0
                            aPoly.getB2DPolygon()) ||
1315
0
                        !checkRect(
1316
0
                            aBackgroundComponent.aBounds,
1317
0
                            aBackgroundComponent.aBgColor,
1318
0
                            aPoly.GetBoundRect(),
1319
0
                            *aMapModeVDev) )
1320
0
                        bStillBackground=false; // incomplete occlusion of background
1321
0
                    else
1322
0
                        nLastBgAction=nActionNum; // this _is_ background
1323
0
                    break;
1324
0
                }
1325
0
                case MetaActionType::POLYPOLYGON:
1326
0
                {
1327
0
                    const tools::PolyPolygon aPoly(
1328
0
                        static_cast<const MetaPolyPolygonAction*>(pCurrAct)->GetPolyPolygon());
1329
0
                    if( aPoly.Count() != 1 ||
1330
0
                        !basegfx::utils::isRectangle(
1331
0
                            aPoly[0].getB2DPolygon()) ||
1332
0
                        !checkRect(
1333
0
                            aBackgroundComponent.aBounds,
1334
0
                            aBackgroundComponent.aBgColor,
1335
0
                            aPoly.GetBoundRect(),
1336
0
                            *aMapModeVDev) )
1337
0
                        bStillBackground=false; // incomplete occlusion of background
1338
0
                    else
1339
0
                        nLastBgAction=nActionNum; // this _is_ background
1340
0
                    break;
1341
0
                }
1342
0
                case MetaActionType::WALLPAPER:
1343
0
                {
1344
0
                    if( !checkRect(
1345
0
                            aBackgroundComponent.aBounds,
1346
0
                            aBackgroundComponent.aBgColor,
1347
0
                            static_cast<const MetaWallpaperAction*>(pCurrAct)->GetRect(),
1348
0
                            *aMapModeVDev) )
1349
0
                        bStillBackground=false; // incomplete occlusion of background
1350
0
                    else
1351
0
                        nLastBgAction=nActionNum; // this _is_ background
1352
0
                    break;
1353
0
                }
1354
0
                default:
1355
0
                {
1356
0
                    if( ImplIsNotTransparent( *pCurrAct,
1357
0
                                              *aMapModeVDev ) )
1358
0
                        bStillBackground=false; // non-transparent action, possibly
1359
                                                // not uniform
1360
0
                    else
1361
                        // extend current bounds (next uniform action
1362
                        // needs to fully cover this area)
1363
0
                        aBackgroundComponent.aBounds.Union(
1364
0
                            ImplCalcActionBounds(*pCurrAct, *aMapModeVDev) );
1365
0
                    break;
1366
0
                }
1367
0
            }
1368
1369
            // execute action to get correct MapModes etc.
1370
0
            pCurrAct->Execute( aMapModeVDev.get() );
1371
1372
0
            pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction();
1373
0
            ++nActionNum;
1374
0
        }
1375
1376
0
        if (nLastBgAction != -1)
1377
0
        {
1378
0
            size_t nActionSize = rInMtf.GetActionSize();
1379
            // tdf#134736 move nLastBgAction to also include any trailing pops
1380
0
            for (size_t nPostLastBgAction = nLastBgAction + 1; nPostLastBgAction < nActionSize; ++nPostLastBgAction)
1381
0
            {
1382
0
                if (rInMtf.GetAction(nPostLastBgAction)->GetType() != MetaActionType::POP)
1383
0
                    break;
1384
0
                nLastBgAction = nPostLastBgAction;
1385
0
            }
1386
0
        }
1387
1388
0
        aMapModeVDev->ClearStack(); // clean up aMapModeVDev
1389
1390
        // fast-forward until one after the last background action
1391
        // (need to reconstruct map mode vdev state)
1392
0
        nActionNum=0;
1393
0
        pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction();
1394
0
        while( pCurrAct && nActionNum<=nLastBgAction )
1395
0
        {
1396
            // up to and including last ink-generating background
1397
            // action go to background component
1398
0
            aBackgroundComponent.aComponentList.emplace_back(
1399
0
                    pCurrAct, nActionNum );
1400
1401
            // execute action to get correct MapModes etc.
1402
0
            pCurrAct->Execute( aMapModeVDev.get() );
1403
0
            pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction();
1404
0
            ++nActionNum;
1405
0
        }
1406
1407
        //  STAGE 2: Generate connected components list
1408
1409
0
        ::std::vector<ConnectedComponents> aCCList; // contains distinct sets of connected components as elements.
1410
1411
        // iterate over all actions (start where background action
1412
        // search left off)
1413
0
        for( ;
1414
0
             pCurrAct;
1415
0
             pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(), ++nActionNum )
1416
0
        {
1417
            // execute action to get correct MapModes etc.
1418
0
            pCurrAct->Execute( aMapModeVDev.get() );
1419
1420
            // cache bounds of current action
1421
0
            const tools::Rectangle aBBCurrAct( ImplCalcActionBounds(*pCurrAct, *aMapModeVDev) );
1422
1423
            // accumulate collected bounds here, initialize with current action
1424
0
            tools::Rectangle aTotalBounds( aBBCurrAct ); // thus, aTotalComponents.aBounds is empty
1425
                                                         // for non-output-generating actions
1426
0
            bool bTreatSpecial( false );
1427
0
            ConnectedComponents aTotalComponents;
1428
1429
            //  STAGE 2.1: Search for intersecting cc entries
1430
1431
            // if aBBCurrAct is empty, it will intersect with no
1432
            // aCCList member. Thus, we can save the check.
1433
            // Furthermore, this ensures that non-output-generating
1434
            // actions get their own aCCList entry, which is necessary
1435
            // when copying them to the output metafile (see stage 4
1436
            // below).
1437
1438
            // #107169# Wholly transparent objects need
1439
            // not be considered for connected components,
1440
            // too. Just put each of them into a separate
1441
            // component.
1442
0
            aTotalComponents.bIsFullyTransparent = !ImplIsNotTransparent(*pCurrAct, *aMapModeVDev);
1443
1444
0
            if( !aBBCurrAct.IsEmpty() &&
1445
0
                !aTotalComponents.bIsFullyTransparent )
1446
0
            {
1447
0
                if( !aBackgroundComponent.aComponentList.empty() &&
1448
0
                    !aBackgroundComponent.aBounds.Contains(aTotalBounds) )
1449
0
                {
1450
                    // it seems the background is not large enough. to
1451
                    // be on the safe side, combine with this component.
1452
0
                    aTotalBounds.Union( aBackgroundComponent.aBounds );
1453
1454
                    // extract all aCurr actions to aTotalComponents
1455
0
                    aTotalComponents.aComponentList.splice( aTotalComponents.aComponentList.end(),
1456
0
                                                            aBackgroundComponent.aComponentList );
1457
1458
0
                    if( aBackgroundComponent.bIsSpecial )
1459
0
                        bTreatSpecial = true;
1460
0
                }
1461
1462
0
                bool                                    bSomeComponentsChanged;
1463
1464
                // now, this is unfortunate: since changing anyone of
1465
                // the aCCList elements (e.g. by merging or addition
1466
                // of an action) might generate new intersection with
1467
                // other aCCList elements, have to repeat the whole
1468
                // element scanning, until nothing changes anymore.
1469
                // Thus, this loop here makes us O(n^3) in the worst
1470
                // case.
1471
0
                do
1472
0
                {
1473
                    // only loop here if 'intersects' branch below was hit
1474
0
                    bSomeComponentsChanged = false;
1475
1476
                    // iterate over all current members of aCCList
1477
0
                    for( auto aCurrCC=aCCList.begin(); aCurrCC != aCCList.end(); )
1478
0
                    {
1479
                        // first check if current element's bounds are
1480
                        // empty. This ensures that empty actions are not
1481
                        // merged into one component, as a matter of fact,
1482
                        // they have no position.
1483
1484
                        // #107169# Wholly transparent objects need
1485
                        // not be considered for connected components,
1486
                        // too. Just put each of them into a separate
1487
                        // component.
1488
0
                        if( !aCurrCC->aBounds.IsEmpty() &&
1489
0
                            !aCurrCC->bIsFullyTransparent &&
1490
0
                            aCurrCC->aBounds.Overlaps( aTotalBounds ) )
1491
0
                        {
1492
                            // union the intersecting aCCList element into aTotalComponents
1493
1494
                            // calc union bounding box
1495
0
                            aTotalBounds.Union( aCurrCC->aBounds );
1496
1497
                            // extract all aCurr actions to aTotalComponents
1498
0
                            aTotalComponents.aComponentList.splice( aTotalComponents.aComponentList.end(),
1499
0
                                                                    aCurrCC->aComponentList );
1500
1501
0
                            if( aCurrCC->bIsSpecial )
1502
0
                                bTreatSpecial = true;
1503
1504
                            // remove and delete aCurrCC element from list (we've now merged its content)
1505
0
                            aCurrCC = aCCList.erase( aCurrCC );
1506
1507
                            // at least one component changed, need to rescan everything
1508
0
                            bSomeComponentsChanged = true;
1509
0
                        }
1510
0
                        else
1511
0
                        {
1512
0
                            ++aCurrCC;
1513
0
                        }
1514
0
                    }
1515
0
                }
1516
0
                while( bSomeComponentsChanged );
1517
0
            }
1518
1519
            //  STAGE 2.2: Determine special state for cc element
1520
1521
            // now test whether the whole connected component must be
1522
            // treated specially (i.e. rendered as a bitmap): if the
1523
            // added action is the very first action, or all actions
1524
            // before it are completely transparent, the connected
1525
            // component need not be treated specially, not even if
1526
            // the added action contains transparency. This is because
1527
            // painting of transparent objects on _white background_
1528
            // works without alpha compositing (you just calculate the
1529
            // color). Note that for the test "all objects before me
1530
            // are transparent" no sorting is necessary, since the
1531
            // added metaaction pCurrAct is always in the order the
1532
            // metafile is painted. Generally, the order of the
1533
            // metaactions in the ConnectedComponents are not
1534
            // guaranteed to be the same as in the metafile.
1535
0
            if( bTreatSpecial )
1536
0
            {
1537
                // prev component(s) special -> this one, too
1538
0
                aTotalComponents.bIsSpecial = true;
1539
0
            }
1540
0
            else if(!pCurrAct->IsTransparent())
1541
0
            {
1542
                // added action and none of prev components special ->
1543
                // this one normal, too
1544
0
                aTotalComponents.bIsSpecial = false;
1545
0
            }
1546
0
            else
1547
0
            {
1548
                // added action is special and none of prev components
1549
                // special -> do the detailed tests
1550
1551
                // can the action handle transparency correctly
1552
                // (i.e. when painted on white background, does the
1553
                // action still look correct)?
1554
0
                if( !DoesActionHandleTransparency( *pCurrAct ) )
1555
0
                {
1556
                    // no, action cannot handle its transparency on
1557
                    // a printer device, render to bitmap
1558
0
                    aTotalComponents.bIsSpecial = true;
1559
0
                }
1560
0
                else
1561
0
                {
1562
                    // yes, action can handle its transparency, so
1563
                    // check whether we're on white background
1564
0
                    if( aTotalComponents.aComponentList.empty() )
1565
0
                    {
1566
                        // nothing between pCurrAct and page
1567
                        // background -> don't be special
1568
0
                        aTotalComponents.bIsSpecial = false;
1569
0
                    }
1570
0
                    else
1571
0
                    {
1572
                        // #107169# Fixes above now ensure that _no_
1573
                        // object in the list is fully transparent. Thus,
1574
                        // if the component list is not empty above, we
1575
                        // must assume that we have to treat this
1576
                        // component special.
1577
1578
                        // there are non-transparent objects between
1579
                        // pCurrAct and the empty sheet of paper -> be
1580
                        // special, then
1581
0
                        aTotalComponents.bIsSpecial = true;
1582
0
                    }
1583
0
                }
1584
0
            }
1585
1586
            //  STAGE 2.3: Add newly generated CC list element
1587
1588
            // set new bounds and add action to list
1589
0
            aTotalComponents.aBounds = aTotalBounds;
1590
0
            aTotalComponents.aComponentList.emplace_back(
1591
0
                    pCurrAct, nActionNum );
1592
1593
            // add aTotalComponents as a new entry to aCCList
1594
0
            aCCList.push_back(std::move(aTotalComponents));
1595
1596
0
            SAL_WARN_IF( aTotalComponents.aComponentList.empty(), "vcl",
1597
0
                        "Printer::GetPreparedMetaFile empty component" );
1598
0
            SAL_WARN_IF( aTotalComponents.aBounds.IsEmpty() && (aTotalComponents.aComponentList.size() != 1), "vcl",
1599
0
                        "Printer::GetPreparedMetaFile non-output generating actions must be solitary");
1600
0
            SAL_WARN_IF( aTotalComponents.bIsFullyTransparent && (aTotalComponents.aComponentList.size() != 1), "vcl",
1601
0
                        "Printer::GetPreparedMetaFile fully transparent actions must be solitary");
1602
0
        }
1603
1604
        // well now, we've got the list of disjunct connected
1605
        // components. Now we've got to create a map, which contains
1606
        // the corresponding aCCList element for every
1607
        // metaaction. Later on, we always process the complete
1608
        // metafile for each bitmap to be generated, but switch on
1609
        // output only for actions contained in the then current
1610
        // aCCList element. This ensures correct mapmode and attribute
1611
        // settings for all cases.
1612
1613
        // maps mtf actions to CC list entries
1614
0
        ::std::vector< const ConnectedComponents* > aCCList_MemberMap( rInMtf.GetActionSize() );
1615
1616
        // iterate over all aCCList members and their contained metaactions
1617
0
        for (auto const& currentItem : aCCList)
1618
0
        {
1619
0
            for (auto const& currentAction : currentItem.aComponentList)
1620
0
            {
1621
                // set pointer to aCCList element for corresponding index
1622
0
                aCCList_MemberMap[ currentAction.second ] = &currentItem;
1623
0
            }
1624
0
        }
1625
1626
        //  STAGE 3.1: Output background mtf actions (if there are any)
1627
1628
0
        for (auto & component : aBackgroundComponent.aComponentList)
1629
0
        {
1630
            // simply add this action (above, we inserted the actions
1631
            // starting at index 0 up to and including nLastBgAction)
1632
0
            rOutMtf.AddAction( component.first );
1633
0
        }
1634
1635
        //  STAGE 3.2: Generate banded bitmaps for special regions
1636
1637
0
        Point aPageOffset;
1638
0
        Size aTmpSize( GetOutputSizePixel() );
1639
0
        if( meOutDevType == OUTDEV_PDF )
1640
0
        {
1641
0
            auto pPdfWriter = static_cast<vcl::PDFWriterImpl*>(this);
1642
0
            aTmpSize = LogicToPixel(pPdfWriter->getCurPageSize(), MapMode(MapUnit::MapPoint));
1643
1644
            // also add error code to PDFWriter
1645
0
            pPdfWriter->insertError(vcl::PDFWriter::Warning_Transparency_Converted);
1646
0
        }
1647
0
        else if( meOutDevType == OUTDEV_PRINTER )
1648
0
        {
1649
0
            Printer* pThis = dynamic_cast<Printer*>(this);
1650
0
            assert(pThis);
1651
0
            aPageOffset = pThis->GetPageOffsetPixel();
1652
0
            aPageOffset = Point( 0, 0 ) - aPageOffset;
1653
0
            aTmpSize  = pThis->GetPaperSizePixel();
1654
0
        }
1655
0
        const tools::Rectangle aOutputRect( aPageOffset, aTmpSize );
1656
0
        bool bTiling = dynamic_cast<Printer*>(this) != nullptr;
1657
1658
        // iterate over all aCCList members and generate bitmaps for the special ones
1659
0
        for (auto & currentItem : aCCList)
1660
0
        {
1661
0
            if( currentItem.bIsSpecial )
1662
0
            {
1663
0
                tools::Rectangle aBoundRect( currentItem.aBounds );
1664
0
                aBoundRect.Intersection( aOutputRect );
1665
1666
0
                const double fBmpArea( static_cast<double>(aBoundRect.GetWidth()) * aBoundRect.GetHeight() );
1667
0
                const double fOutArea( static_cast<double>(aOutputRect.GetWidth()) * aOutputRect.GetHeight() );
1668
1669
                // check if output doesn't exceed given size
1670
0
                if( bReduceTransparency && bTransparencyAutoMode && ( fBmpArea > ( fReduceTransparencyMinArea * fOutArea ) ) )
1671
0
                {
1672
                    // output normally. Therefore, we simply clear the
1673
                    // special attribute, as everything non-special is
1674
                    // copied to rOutMtf further below.
1675
0
                    currentItem.bIsSpecial = false;
1676
0
                }
1677
0
                else
1678
0
                {
1679
                    // create new bitmap action first
1680
0
                    if( aBoundRect.GetWidth() && aBoundRect.GetHeight() )
1681
0
                    {
1682
0
                        Point           aDstPtPix( aBoundRect.TopLeft() );
1683
0
                        Size            aDstSzPix;
1684
1685
0
                        ScopedVclPtrInstance<VirtualDevice> aMapVDev;   // here, we record only mapmode information
1686
0
                        aMapVDev->EnableOutput(false);
1687
1688
0
                        ScopedVclPtrInstance<VirtualDevice> aPaintVDev; // into this one, we render.
1689
0
                        aPaintVDev->SetBackground( aBackgroundComponent.aBgColor );
1690
1691
0
                        rOutMtf.AddAction( new MetaPushAction( vcl::PushFlags::MAPMODE ) );
1692
0
                        rOutMtf.AddAction( new MetaMapModeAction() );
1693
1694
0
                        aPaintVDev->SetDrawMode( GetDrawMode() );
1695
1696
0
                        while( aDstPtPix.Y() <= aBoundRect.Bottom() )
1697
0
                        {
1698
0
                            aDstPtPix.setX( aBoundRect.Left() );
1699
0
                            aDstSzPix = bTiling ? Size( MAX_TILE_WIDTH, MAX_TILE_HEIGHT ) : aBoundRect.GetSize();
1700
1701
0
                            if( ( aDstPtPix.Y() + aDstSzPix.Height() - 1 ) > aBoundRect.Bottom() )
1702
0
                                aDstSzPix.setHeight( aBoundRect.Bottom() - aDstPtPix.Y() + 1 );
1703
1704
0
                            while( aDstPtPix.X() <= aBoundRect.Right() )
1705
0
                            {
1706
0
                                if( ( aDstPtPix.X() + aDstSzPix.Width() - 1 ) > aBoundRect.Right() )
1707
0
                                    aDstSzPix.setWidth( aBoundRect.Right() - aDstPtPix.X() + 1 );
1708
1709
0
                                if( !tools::Rectangle( aDstPtPix, aDstSzPix ).Intersection( aBoundRect ).IsEmpty() &&
1710
0
                                    aPaintVDev->SetOutputSizePixel( aDstSzPix ) )
1711
0
                                {
1712
0
                                    auto popIt1 = aPaintVDev->ScopedPush();
1713
0
                                    auto popIt2 = aMapVDev->ScopedPush();
1714
1715
0
                                    aMapVDev->SetDPIX(GetDPIX());
1716
0
                                    aPaintVDev->SetDPIX(GetDPIX());
1717
0
                                    aMapVDev->SetDPIY(GetDPIY());
1718
0
                                    aPaintVDev->SetDPIY(GetDPIY());
1719
1720
0
                                    aPaintVDev->EnableOutput(false);
1721
1722
                                    // iterate over all actions
1723
0
                                    for( pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction(), nActionNum=0;
1724
0
                                         pCurrAct;
1725
0
                                         pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(), ++nActionNum )
1726
0
                                    {
1727
                                        // enable output only for
1728
                                        // actions that are members of
1729
                                        // the current aCCList element
1730
                                        // (currentItem)
1731
0
                                        if( aCCList_MemberMap[nActionNum] == &currentItem )
1732
0
                                            aPaintVDev->EnableOutput();
1733
1734
                                        // but process every action
1735
0
                                        const MetaActionType nType( pCurrAct->GetType() );
1736
1737
0
                                        if( MetaActionType::MAPMODE == nType )
1738
0
                                        {
1739
0
                                            pCurrAct->Execute( aMapVDev.get() );
1740
1741
0
                                            MapMode     aMtfMap( aMapVDev->GetMapMode() );
1742
0
                                            const Point aNewOrg( aMapVDev->PixelToLogic( aDstPtPix ) );
1743
1744
0
                                            aMtfMap.SetOrigin( Point( -aNewOrg.X(), -aNewOrg.Y() ) );
1745
0
                                            aPaintVDev->SetMapMode( aMtfMap );
1746
0
                                        }
1747
0
                                        else if( ( MetaActionType::PUSH == nType ) || MetaActionType::POP == nType )
1748
0
                                        {
1749
0
                                            pCurrAct->Execute( aMapVDev.get() );
1750
0
                                            pCurrAct->Execute( aPaintVDev.get() );
1751
0
                                        }
1752
0
                                        else if( MetaActionType::GRADIENT == nType )
1753
0
                                        {
1754
0
                                            MetaGradientAction* pGradientAction = static_cast<MetaGradientAction*>(pCurrAct);
1755
0
                                            Printer* pPrinter = dynamic_cast< Printer* >(this);
1756
0
                                            if( pPrinter )
1757
0
                                                pPrinter->DrawGradientEx( aPaintVDev.get(), pGradientAction->GetRect(), pGradientAction->GetGradient() );
1758
0
                                            else
1759
0
                                                DrawGradient( pGradientAction->GetRect(), pGradientAction->GetGradient() );
1760
0
                                        }
1761
0
                                        else
1762
0
                                        {
1763
0
                                            pCurrAct->Execute( aPaintVDev.get() );
1764
0
                                        }
1765
1766
0
                                        Application::Reschedule( true );
1767
0
                                    }
1768
1769
0
                                    const bool bOldMap = mbMap;
1770
0
                                    mbMap = aPaintVDev->mbMap = false;
1771
1772
0
                                    Bitmap aBandBmp( aPaintVDev->GetBitmap( Point(), aDstSzPix ) );
1773
1774
                                    // scale down bitmap, if requested
1775
0
                                    if( bDownsampleBitmaps )
1776
0
                                        aBandBmp = vcl::bitmap::GetDownsampledBitmap(PixelToLogic(LogicToPixel(aDstSzPix), MapMode(MapUnit::MapTwip)),
1777
0
                                                                         Point(), aBandBmp.GetSizePixel(),
1778
0
                                                                         aBandBmp, nMaxBmpDPIX, nMaxBmpDPIY);
1779
1780
0
                                    rOutMtf.AddAction( new MetaCommentAction( "PRNSPOOL_TRANSPARENTBITMAP_BEGIN"_ostr ) );
1781
0
                                    rOutMtf.AddAction( new MetaBmpScaleAction( aDstPtPix, aDstSzPix, aBandBmp ) );
1782
0
                                    rOutMtf.AddAction( new MetaCommentAction( "PRNSPOOL_TRANSPARENTBITMAP_END"_ostr ) );
1783
1784
0
                                    aPaintVDev->mbMap = true;
1785
0
                                    mbMap = bOldMap;
1786
0
                                }
1787
1788
                                // overlapping bands to avoid missing lines (e.g. PostScript)
1789
0
                                aDstPtPix.AdjustX(aDstSzPix.Width() );
1790
0
                            }
1791
1792
                            // overlapping bands to avoid missing lines (e.g. PostScript)
1793
0
                            aDstPtPix.AdjustY(aDstSzPix.Height() );
1794
0
                        }
1795
1796
0
                        rOutMtf.AddAction( new MetaPopAction() );
1797
0
                    }
1798
0
                }
1799
0
            }
1800
0
        }
1801
1802
0
        aMapModeVDev->ClearStack(); // clean up aMapModeVDev
1803
1804
        //  STAGE 4: Copy actions to output metafile
1805
1806
        // iterate over all actions and duplicate the ones not in a
1807
        // special aCCList member into rOutMtf
1808
0
        for( pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction(), nActionNum=0;
1809
0
             pCurrAct;
1810
0
             pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(), ++nActionNum )
1811
0
        {
1812
0
            const ConnectedComponents* pCurrAssociatedComponent = aCCList_MemberMap[nActionNum];
1813
1814
            // NOTE: This relies on the fact that map-mode or draw
1815
            // mode changing actions are solitary aCCList elements and
1816
            // have empty bounding boxes, see comment on stage 2.1
1817
            // above
1818
0
            if( pCurrAssociatedComponent &&
1819
0
                (pCurrAssociatedComponent->aBounds.IsEmpty() ||
1820
0
                 !pCurrAssociatedComponent->bIsSpecial) )
1821
0
            {
1822
                // #107169# Treat transparent bitmaps special, if they
1823
                // are the first (or sole) action in their bounds
1824
                // list. Note that we previously ensured that no
1825
                // fully-transparent objects are before us here.
1826
0
                if( DoesActionHandleTransparency( *pCurrAct ) &&
1827
0
                    pCurrAssociatedComponent->aComponentList.begin()->first == pCurrAct )
1828
0
                {
1829
                    // convert actions, where masked-out parts are of
1830
                    // given background color
1831
0
                    ImplConvertTransparentAction(rOutMtf,
1832
0
                                                 *pCurrAct,
1833
0
                                                 *aMapModeVDev,
1834
0
                                                 aBackgroundComponent.aBgColor);
1835
0
                }
1836
0
                else
1837
0
                {
1838
                    // simply add this action
1839
0
                    rOutMtf.AddAction( pCurrAct );
1840
0
                }
1841
1842
0
                pCurrAct->Execute(aMapModeVDev.get());
1843
0
            }
1844
0
        }
1845
1846
0
        rOutMtf.SetPrefMapMode( rInMtf.GetPrefMapMode() );
1847
0
        rOutMtf.SetPrefSize( rInMtf.GetPrefSize() );
1848
1849
#if OSL_DEBUG_LEVEL > 1
1850
        // iterate over all aCCList members and generate rectangles for the bounding boxes
1851
        rOutMtf.AddAction( new MetaFillColorAction( COL_WHITE, false ) );
1852
        for(auto const& aCurr:aCCList)
1853
        {
1854
            if( aCurr.bIsSpecial )
1855
                rOutMtf.AddAction( new MetaLineColorAction( COL_RED, true) );
1856
            else
1857
                rOutMtf.AddAction( new MetaLineColorAction( COL_BLUE, true) );
1858
1859
            rOutMtf.AddAction( new MetaRectAction( aMapModeVDev->PixelToLogic( aCurr.aBounds ) ) );
1860
        }
1861
#endif
1862
0
    }
1863
0
    return bTransparent;
1864
0
}
1865
1866
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */