Coverage Report

Created: 2026-02-14 09:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/svx/source/sdr/contact/viewobjectcontact.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 <svx/sdr/contact/viewobjectcontact.hxx>
21
#include <svx/sdr/contact/viewcontact.hxx>
22
#include <svx/sdr/contact/objectcontact.hxx>
23
#include <svx/sdr/contact/displayinfo.hxx>
24
#include <svx/sdr/animation/animationstate.hxx>
25
#include <svx/sdr/contact/viewobjectcontactredirector.hxx>
26
#include <basegfx/color/bcolor.hxx>
27
#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
28
#include <drawinglayer/primitive2d/animatedprimitive2d.hxx>
29
#include <drawinglayer/processor2d/baseprocessor2d.hxx>
30
#include <svx/sdr/primitive2d/svx_primitivetypes2d.hxx>
31
#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
32
#include <drawinglayer/primitive2d/structuretagprimitive2d.hxx>
33
#include <svx/svdobj.hxx>
34
#include <svx/svdomedia.hxx>
35
#include <svx/svdmodel.hxx>
36
#include <svx/svdpage.hxx>
37
#include <svx/svdotext.hxx>
38
#include <vcl/pdfwriter.hxx>
39
#include <vcl/pdfextoutdevdata.hxx>
40
41
using namespace com::sun::star;
42
43
namespace {
44
45
// animated extractor
46
47
// Necessary to filter a sequence of animated primitives from
48
// a sequence of primitives to find out if animated or not. The decision for
49
// what to decompose is hard-coded and only done for knowingly animated primitives
50
// to not decompose too deeply and unnecessarily. This implies that the list
51
// which is view-specific needs to be expanded by hand when new animated objects
52
// are added. This may eventually be changed to a dynamically configurable approach
53
// if necessary.
54
class AnimatedExtractingProcessor2D : public drawinglayer::processor2d::BaseProcessor2D
55
{
56
protected:
57
    // the found animated primitives
58
    drawinglayer::primitive2d::Primitive2DContainer  maPrimitive2DSequence;
59
60
    // text animation allowed?
61
    bool                                            mbTextAnimationAllowed : 1;
62
63
    // graphic animation allowed?
64
    bool                                            mbGraphicAnimationAllowed : 1;
65
66
    // as tooling, the process() implementation takes over API handling and calls this
67
    // virtual render method when the primitive implementation is BasePrimitive2D-based.
68
    virtual void processBasePrimitive2D(const drawinglayer::primitive2d::BasePrimitive2D& rCandidate) override;
69
70
public:
71
    AnimatedExtractingProcessor2D(
72
        const drawinglayer::geometry::ViewInformation2D& rViewInformation,
73
        bool bTextAnimationAllowed,
74
        bool bGraphicAnimationAllowed);
75
76
    // data access
77
1.35k
    const drawinglayer::primitive2d::Primitive2DContainer& getPrimitive2DSequence() const { return maPrimitive2DSequence; }
78
0
    drawinglayer::primitive2d::Primitive2DContainer extractPrimitive2DSequence() { return std::move(maPrimitive2DSequence); }
79
};
80
81
AnimatedExtractingProcessor2D::AnimatedExtractingProcessor2D(
82
    const drawinglayer::geometry::ViewInformation2D& rViewInformation,
83
    bool bTextAnimationAllowed,
84
    bool bGraphicAnimationAllowed)
85
1.35k
:   drawinglayer::processor2d::BaseProcessor2D(rViewInformation),
86
1.35k
    mbTextAnimationAllowed(bTextAnimationAllowed),
87
1.35k
    mbGraphicAnimationAllowed(bGraphicAnimationAllowed)
88
1.35k
{
89
1.35k
}
90
91
void AnimatedExtractingProcessor2D::processBasePrimitive2D(const drawinglayer::primitive2d::BasePrimitive2D& rCandidate)
92
2.04k
{
93
    // known implementation, access directly
94
2.04k
    switch(rCandidate.getPrimitive2DID())
95
2.04k
    {
96
        // add and accept animated primitives directly, no need to decompose
97
0
        case PRIMITIVE2D_ID_ANIMATEDSWITCHPRIMITIVE2D :
98
0
        case PRIMITIVE2D_ID_ANIMATEDBLINKPRIMITIVE2D :
99
0
        case PRIMITIVE2D_ID_ANIMATEDINTERPOLATEPRIMITIVE2D :
100
0
        case PRIMITIVE2D_ID_ANIMATEDGRAPHICPRIMITIVE2D :
101
0
        {
102
0
            const drawinglayer::primitive2d::AnimatedSwitchPrimitive2D& rSwitchPrimitive = static_cast< const drawinglayer::primitive2d::AnimatedSwitchPrimitive2D& >(rCandidate);
103
104
0
            if((rSwitchPrimitive.isTextAnimation() && mbTextAnimationAllowed)
105
0
                || (rSwitchPrimitive.isGraphicAnimation() && mbGraphicAnimationAllowed))
106
0
            {
107
0
                const drawinglayer::primitive2d::Primitive2DReference xReference(const_cast< drawinglayer::primitive2d::BasePrimitive2D* >(&rCandidate));
108
0
                maPrimitive2DSequence.push_back(xReference);
109
0
            }
110
0
            break;
111
0
        }
112
113
        // decompose animated gifs where SdrGrafPrimitive2D produces a GraphicPrimitive2D
114
        // which then produces the animation infos (all when used/needed)
115
0
        case PRIMITIVE2D_ID_SDRGRAFPRIMITIVE2D :
116
97
        case PRIMITIVE2D_ID_GRAPHICPRIMITIVE2D :
117
118
        // decompose SdrObjects with evtl. animated text
119
97
        case PRIMITIVE2D_ID_SDRCAPTIONPRIMITIVE2D :
120
97
        case PRIMITIVE2D_ID_SDRCONNECTORPRIMITIVE2D :
121
369
        case PRIMITIVE2D_ID_SDRCUSTOMSHAPEPRIMITIVE2D :
122
369
        case PRIMITIVE2D_ID_SDRELLIPSEPRIMITIVE2D :
123
369
        case PRIMITIVE2D_ID_SDRELLIPSESEGMENTPRIMITIVE2D :
124
369
        case PRIMITIVE2D_ID_SDRMEASUREPRIMITIVE2D :
125
439
        case PRIMITIVE2D_ID_SDRPATHPRIMITIVE2D :
126
439
        case PRIMITIVE2D_ID_SDRRECTANGLEPRIMITIVE2D :
127
128
        // #121194# With Graphic as Bitmap FillStyle, also check
129
        // for primitives filled with animated graphics
130
439
        case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D:
131
590
        case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D:
132
133
        // decompose evtl. animated text contained in MaskPrimitive2D
134
        // or group primitives
135
590
        case PRIMITIVE2D_ID_MASKPRIMITIVE2D :
136
935
        case PRIMITIVE2D_ID_GROUPPRIMITIVE2D :
137
935
        {
138
935
            process(rCandidate);
139
935
            break;
140
590
        }
141
142
1.11k
        default :
143
1.11k
        {
144
            // nothing to do for the rest
145
1.11k
            break;
146
590
        }
147
2.04k
    }
148
2.04k
}
149
150
} // end of anonymous namespace
151
152
namespace sdr::contact {
153
154
ViewObjectContact::ViewObjectContact(ObjectContact& rObjectContact, ViewContact& rViewContact)
155
76.9k
:   mrObjectContact(rObjectContact),
156
76.9k
    mrViewContact(rViewContact),
157
76.9k
    mnActionChangedCount(0),
158
76.9k
    mbLazyInvalidate(false)
159
76.9k
{
160
    // make the ViewContact remember me
161
76.9k
    mrViewContact.AddViewObjectContact(*this);
162
163
    // make the ObjectContact remember me
164
76.9k
    mrObjectContact.AddViewObjectContact(*this);
165
76.9k
}
166
167
ViewObjectContact::~ViewObjectContact()
168
76.9k
{
169
    // if the object range is empty, then we have never had the primitive range change, so nothing to invalidate
170
76.9k
    if (!maObjectRange.isEmpty())
171
1.35k
    {
172
        // invalidate in view
173
1.35k
        GetObjectContact().InvalidatePartOfView(maObjectRange);
174
1.35k
    }
175
176
    // delete PrimitiveAnimation
177
76.9k
    mpPrimitiveAnimation.reset();
178
179
    // take care of remembered ObjectContact. Remove from
180
    // OC first. The VC removal (below) CAN trigger a StopGettingViewed()
181
    // which (depending of its implementation) may destroy other OCs. This
182
    // can trigger the deletion of the helper OC of a page visualising object
183
    // which IS the OC of this object. Eventually StopGettingViewed() needs
184
    // to get asynchron later
185
76.9k
    GetObjectContact().RemoveViewObjectContact(*this);
186
187
    // take care of remembered ViewContact
188
76.9k
    GetViewContact().RemoveViewObjectContact(*this);
189
76.9k
}
190
191
const basegfx::B2DRange& ViewObjectContact::getObjectRange() const
192
0
{
193
0
    if(maObjectRange.isEmpty())
194
0
    {
195
0
        const drawinglayer::geometry::ViewInformation2D& rViewInfo2D = GetObjectContact().getViewInformation2D();
196
0
        basegfx::B2DRange aTempRange = GetViewContact().getRange(rViewInfo2D);
197
0
        if (!aTempRange.isEmpty())
198
0
        {
199
0
            const_cast< ViewObjectContact* >(this)->maObjectRange = aTempRange;
200
0
        }
201
0
        else
202
0
        {
203
            // if range is not computed (new or LazyInvalidate objects), force it
204
0
            const DisplayInfo aDisplayInfo;
205
0
            const drawinglayer::primitive2d::Primitive2DContainer& xSequence(getPrimitive2DSequence(aDisplayInfo));
206
207
0
            if(!xSequence.empty())
208
0
            {
209
0
                const_cast< ViewObjectContact* >(this)->maObjectRange =
210
0
                    xSequence.getB2DRange(rViewInfo2D);
211
0
            }
212
0
        }
213
0
    }
214
215
0
    return maObjectRange;
216
0
}
217
218
void ViewObjectContact::ActionChanged()
219
0
{
220
    // clear cached primitives
221
0
    mxPrimitive2DSequence.clear();
222
0
    ++mnActionChangedCount;
223
224
0
    if(mbLazyInvalidate)
225
0
        return;
226
227
    // set local flag
228
0
    mbLazyInvalidate = true;
229
230
    // force ObjectRange
231
0
    getObjectRange();
232
233
0
    if(!getObjectRange().isEmpty())
234
0
    {
235
        // invalidate current valid range
236
0
        GetObjectContact().InvalidatePartOfView(maObjectRange);
237
238
        // reset gridOffset, it needs to be recalculated
239
0
        if (GetObjectContact().supportsGridOffsets())
240
0
            resetGridOffset();
241
0
        else
242
0
            maObjectRange.reset();
243
0
    }
244
245
    // register at OC for lazy invalidate
246
0
    GetObjectContact().setLazyInvalidate(*this);
247
0
}
248
249
// IASS: helper for IASS invalidates
250
void ViewObjectContact::ActionChangedIfDifferentPageView(const SdrPageView& rSdrPageView)
251
0
{
252
0
    SdrPageView* pSdrPageView(GetObjectContact().TryToGetSdrPageView());
253
254
    // if there is no SdrPageView or different from given one, force
255
    // invalidate/repaint
256
0
    if (nullptr == pSdrPageView || pSdrPageView != &rSdrPageView)
257
0
        ActionChanged();
258
0
}
259
260
void ViewObjectContact::triggerLazyInvalidate()
261
0
{
262
0
    if(!mbLazyInvalidate)
263
0
        return;
264
265
    // reset flag
266
0
    mbLazyInvalidate = false;
267
268
    // force ObjectRange
269
0
    getObjectRange();
270
271
0
    if(!getObjectRange().isEmpty())
272
0
    {
273
        // invalidate current valid range
274
0
        GetObjectContact().InvalidatePartOfView(maObjectRange);
275
0
    }
276
0
}
277
278
// Take some action when new objects are inserted
279
void ViewObjectContact::ActionChildInserted(ViewContact& rChild)
280
0
{
281
    // force creation of the new VOC and trigger it's refresh, so it
282
    // will take part in LazyInvalidate immediately
283
0
    rChild.GetViewObjectContact(GetObjectContact()).ActionChanged();
284
285
    // forward action to ObjectContact
286
    // const ViewObjectContact& rChildVOC = rChild.GetViewObjectContact(GetObjectContact());
287
    // GetObjectContact().InvalidatePartOfView(rChildVOC.getObjectRange());
288
0
}
289
290
// Check for animated primitives.
291
void ViewObjectContact::checkForPrimitive2DAnimations()
292
1.35k
{
293
    // Somewhat odd structure here, because we are trying to avoid deleting and re-allocating the mpPrimitiveAnimation
294
    // object, because we might be inside a call from the Scheduler, in which case we will be deleting an object
295
    // on the call stack.
296
    // Yes, this does still leave a possible situation where the user could turn animations off while we are
297
    // animating and we could crash. I don't have a better solution right now.
298
    //
299
1.35k
    drawinglayer::primitive2d::Primitive2DContainer aNewAnimatedPrimitives;
300
1.35k
    if(!mxPrimitive2DSequence.empty())
301
1.35k
    {
302
1.35k
        const bool bTextAnimationAllowed(GetObjectContact().IsTextAnimationAllowed());
303
1.35k
        const bool bGraphicAnimationAllowed(GetObjectContact().IsGraphicAnimationAllowed());
304
305
1.35k
        if(bTextAnimationAllowed || bGraphicAnimationAllowed)
306
1.35k
        {
307
1.35k
            AnimatedExtractingProcessor2D aAnimatedExtractor(GetObjectContact().getViewInformation2D(),
308
1.35k
                bTextAnimationAllowed, bGraphicAnimationAllowed);
309
1.35k
            aAnimatedExtractor.process(mxPrimitive2DSequence);
310
311
1.35k
            if(!aAnimatedExtractor.getPrimitive2DSequence().empty())
312
0
            {
313
                // derived primitiveList is animated, setup new PrimitiveAnimation
314
0
                aNewAnimatedPrimitives = aAnimatedExtractor.extractPrimitive2DSequence();
315
0
            }
316
1.35k
        }
317
1.35k
    }
318
1.35k
    if (!aNewAnimatedPrimitives.empty())
319
0
    {
320
        // derived primitiveList is animated, setup new PrimitiveAnimation
321
0
        if (mpPrimitiveAnimation)
322
0
            mpPrimitiveAnimation->SetPrimitives(std::move(aNewAnimatedPrimitives));
323
0
        else
324
0
            mpPrimitiveAnimation.reset( new sdr::animation::PrimitiveAnimation(*this, std::move(aNewAnimatedPrimitives)) );
325
0
    }
326
1.35k
    else
327
        // remove old one
328
1.35k
        mpPrimitiveAnimation.reset();
329
1.35k
}
330
331
void ViewObjectContact::createPrimitive2DSequence(const DisplayInfo& rDisplayInfo, drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) const
332
1.41k
{
333
    // get the view-independent Primitive from the viewContact
334
1.41k
    drawinglayer::primitive2d::Primitive2DContainer xRetval;
335
1.41k
    GetViewContact().getViewIndependentPrimitive2DContainer(xRetval);
336
337
1.41k
    if(!xRetval.empty())
338
1.11k
    {
339
        // handle GluePoint
340
1.11k
        if(!GetObjectContact().isOutputToPrinter() && GetObjectContact().AreGluePointsVisible())
341
0
        {
342
0
            drawinglayer::primitive2d::Primitive2DContainer xGlue(GetViewContact().createGluePointPrimitive2DSequence());
343
344
0
            if(!xGlue.empty())
345
0
            {
346
0
                xRetval.append(std::move(xGlue));
347
0
            }
348
0
        }
349
350
        // handle ghosted
351
1.11k
        if(isPrimitiveGhosted(rDisplayInfo))
352
0
        {
353
0
            basegfx::BColor aRGBWhite(1.0, 1.0, 1.0);
354
0
            basegfx::BColorModifierSharedPtr aBColorModifier =
355
0
                std::make_shared<basegfx::BColorModifier_interpolate>(
356
0
                    aRGBWhite,
357
0
                    0.5);
358
0
            xRetval = drawinglayer::primitive2d::Primitive2DContainer{
359
0
                    new drawinglayer::primitive2d::ModifiedColorPrimitive2D(
360
0
                        std::move(xRetval),
361
0
                        std::move(aBColorModifier))
362
0
            };
363
0
        }
364
1.11k
    }
365
366
1.41k
    rVisitor.visit(xRetval);
367
1.41k
}
368
369
bool ViewObjectContact::isExportPDFTags() const
370
1.58k
{
371
1.58k
    return GetObjectContact().isExportTaggedPDF();
372
1.58k
}
373
374
/** Check if we need to embed to a StructureTagPrimitive2D, too. This
375
    was done at ImplRenderPaintProc::createRedirectedPrimitive2DSequence before
376
*/
377
void ViewObjectContact::createStructureTag(drawinglayer::primitive2d::Primitive2DContainer & rNewPrimitiveSequence) const
378
2.71k
{
379
2.71k
    SdrObject *const pSdrObj(mrViewContact.TryToGetSdrObject());
380
381
    // Check if we need to embed to a StructureTagPrimitive2D, too. This
382
    // was done at ImplRenderPaintProc::createRedirectedPrimitive2DSequence before
383
2.71k
    if (!rNewPrimitiveSequence.empty() && isExportPDFTags()
384
        // ISO 14289-1:2014, Clause: 7.3
385
0
        && (!pSdrObj || pSdrObj->getParentSdrObjectFromSdrObject() == nullptr))
386
0
    {
387
0
        if (nullptr != pSdrObj && !pSdrObj->IsDecorative())
388
0
        {
389
0
            vcl::pdf::StructElement eElement(vcl::pdf::StructElement::NonStructElement);
390
0
            const SdrInventor nInventor(pSdrObj->GetObjInventor());
391
0
            const SdrObjKind nIdentifier(pSdrObj->GetObjIdentifier());
392
0
            const bool bIsTextObj(nullptr != DynCastSdrTextObj(pSdrObj));
393
394
            // Note: SwFlyDrawObj/SwVirtFlyDrawObj have SdrInventor::Swg - these
395
            // are *not* handled here because not all of them are painted
396
            // completely with primitives, so a tag here does not encapsulate them.
397
            // The tag must be created by SwTaggedPDFHelper until this is fixed.
398
0
            if ( nInventor == SdrInventor::Default )
399
0
            {
400
0
                if ( nIdentifier == SdrObjKind::Group )
401
0
                    eElement = vcl::pdf::StructElement::Figure;
402
0
                else if (nIdentifier == SdrObjKind::Table)
403
0
                    eElement = vcl::pdf::StructElement::Table;
404
0
                else if (nIdentifier == SdrObjKind::Media)
405
0
                    eElement = vcl::pdf::StructElement::Annot;
406
0
                else if ( nIdentifier == SdrObjKind::TitleText )
407
0
                    eElement = vcl::pdf::StructElement::Heading;
408
0
                else if ( nIdentifier == SdrObjKind::OutlineText )
409
0
                    eElement = vcl::pdf::StructElement::Division;
410
0
                else if ( !bIsTextObj || !static_cast<const SdrTextObj&>(*pSdrObj).HasText() )
411
0
                    eElement = vcl::pdf::StructElement::Figure;
412
0
                else
413
0
                    eElement = vcl::pdf::StructElement::Division;
414
0
            }
415
416
0
            if(vcl::pdf::StructElement::NonStructElement != eElement)
417
0
            {
418
0
                SdrPage* pSdrPage(pSdrObj->getSdrPageFromSdrObject());
419
420
0
                if(pSdrPage)
421
0
                {
422
0
                    const bool bBackground(pSdrPage->IsMasterPage());
423
0
                    const bool bImage(SdrObjKind::Graphic == pSdrObj->GetObjIdentifier());
424
                    // note: there must be output device here, in PDF export
425
0
                    void const* pAnchorKey(nullptr);
426
0
                    if (auto const pUserCall = pSdrObj->GetUserCall())
427
0
                    {
428
0
                        pAnchorKey = pUserCall->GetPDFAnchorStructureElementKey(*pSdrObj);
429
0
                    }
430
431
0
                    ::std::vector<sal_Int32> annotIds;
432
0
                    if (eElement == vcl::pdf::StructElement::Annot
433
0
                        && !static_cast<SdrMediaObj*>(pSdrObj)->getURL().isEmpty())
434
0
                    {
435
0
                        auto const pPDFExtOutDevData(GetObjectContact().GetPDFExtOutDevData());
436
0
                        assert(pPDFExtOutDevData);
437
0
                        annotIds = pPDFExtOutDevData->GetScreenAnnotIds(pSdrObj);
438
0
                    }
439
440
0
                    rNewPrimitiveSequence = drawinglayer::primitive2d::Primitive2DContainer {
441
0
                            new drawinglayer::primitive2d::StructureTagPrimitive2D(
442
0
                                eElement,
443
0
                                bBackground,
444
0
                                bImage,
445
0
                                false, // Decorative
446
0
                                std::move(rNewPrimitiveSequence),
447
0
                                pAnchorKey,
448
0
                                &annotIds)
449
0
                    };
450
0
                }
451
0
            }
452
0
        }
453
0
        else
454
0
        {
455
            // page backgrounds etc should be tagged as artifacts:
456
0
            rNewPrimitiveSequence = drawinglayer::primitive2d::Primitive2DContainer {
457
0
                    new drawinglayer::primitive2d::StructureTagPrimitive2D(
458
                        // lies to force silly VclMetafileProcessor2D to emit NonStructElement
459
0
                        vcl::pdf::StructElement::Division,
460
0
                        true,
461
0
                        true,
462
0
                        true, // Decorative
463
0
                        std::move(rNewPrimitiveSequence))
464
0
                };
465
0
        }
466
0
    }
467
2.71k
}
468
469
drawinglayer::primitive2d::Primitive2DContainer const & ViewObjectContact::getPrimitive2DSequence(const DisplayInfo& rDisplayInfo) const
470
2.38k
{
471
    // only some of the top-level apps are any good at reliably invalidating us (e.g. writer is not)
472
2.38k
    SdrObject* pSdrObj(mrViewContact.TryToGetSdrObject());
473
474
2.38k
    if (nullptr != pSdrObj && pSdrObj->getSdrModelFromSdrObject().IsVOCInvalidationIsReliable())
475
773
    {
476
773
        if (!mxPrimitive2DSequence.empty())
477
0
            return mxPrimitive2DSequence;
478
773
    }
479
480
    // prepare new representation
481
2.38k
    drawinglayer::primitive2d::Primitive2DContainer xNewPrimitiveSequence;
482
483
    // take care of redirectors and create new list
484
2.38k
    ViewObjectContactRedirector* pRedirector = GetObjectContact().GetViewObjectContactRedirector();
485
486
2.38k
    if(pRedirector)
487
2.29k
    {
488
2.29k
        pRedirector->createRedirectedPrimitive2DSequence(*this, rDisplayInfo, xNewPrimitiveSequence);
489
2.29k
    }
490
86
    else
491
86
    {
492
86
        createPrimitive2DSequence(rDisplayInfo, xNewPrimitiveSequence);
493
86
    }
494
495
    // check and eventually embed to GridOffset transform primitive (calc only)
496
2.38k
    if(!xNewPrimitiveSequence.empty() && GetObjectContact().supportsGridOffsets())
497
0
    {
498
0
        const basegfx::B2DVector& rGridOffset(getGridOffset());
499
500
0
        if(0.0 != rGridOffset.getX() || 0.0 != rGridOffset.getY())
501
0
        {
502
0
            const basegfx::B2DHomMatrix aTranslateGridOffset(
503
0
                basegfx::utils::createTranslateB2DHomMatrix(
504
0
                    rGridOffset));
505
0
            xNewPrimitiveSequence = drawinglayer::primitive2d::Primitive2DContainer {
506
0
                    new drawinglayer::primitive2d::TransformPrimitive2D(
507
0
                        aTranslateGridOffset,
508
0
                        std::move(xNewPrimitiveSequence))
509
0
            };
510
0
        }
511
0
    }
512
513
2.38k
    createStructureTag(xNewPrimitiveSequence);
514
515
    // Local up-to-date checks. New list different from local one?
516
    // This is the important point where it gets decided if the current or the new
517
    // representation gets used. This is important for performance, since the
518
    // current representation contains possible precious decompositions. That
519
    // comparisons triggers exactly if something in the object visualization
520
    // has changed.
521
    // Note: That is the main reason for BasePrimitive2D::operator== at all. I
522
    // have alternatively tried to invalidate the local representation on object
523
    // change, but that is simply not reliable.
524
    // Note2: I did that once in aw080, the lost CWS, and it worked well enough
525
    // so that I could remove *all* operator== from all derivations of
526
    // BasePrimitive2D, so it can be done again (with the needed resources)
527
2.38k
    if(mxPrimitive2DSequence != xNewPrimitiveSequence)
528
1.35k
    {
529
        // has changed, copy content
530
1.35k
        const_cast< ViewObjectContact* >(this)->mxPrimitive2DSequence = std::move(xNewPrimitiveSequence);
531
532
        // check for animated stuff
533
1.35k
        const_cast< ViewObjectContact* >(this)->checkForPrimitive2DAnimations();
534
535
        // always update object range when PrimitiveSequence changes
536
1.35k
        const drawinglayer::geometry::ViewInformation2D& rViewInformation2D(GetObjectContact().getViewInformation2D());
537
1.35k
        const_cast< ViewObjectContact* >(this)->maObjectRange = mxPrimitive2DSequence.getB2DRange(rViewInformation2D);
538
1.35k
    }
539
540
    // return current Primitive2DContainer
541
2.38k
    return mxPrimitive2DSequence;
542
2.38k
}
543
544
bool ViewObjectContact::isPrimitiveVisible(const DisplayInfo& /*rDisplayInfo*/) const
545
0
{
546
    // default: always visible
547
0
    return true;
548
0
}
549
550
bool ViewObjectContact::isPrimitiveGhosted(const DisplayInfo& rDisplayInfo) const
551
1.11k
{
552
    // default: standard check
553
1.11k
    return (GetObjectContact().DoVisualizeEnteredGroup() && !GetObjectContact().isOutputToPrinter() && rDisplayInfo.IsGhostedDrawModeActive());
554
1.11k
}
555
556
void ViewObjectContact::getPrimitive2DSequenceHierarchy(DisplayInfo& rDisplayInfo, drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) const
557
60.7k
{
558
    // check model-view visibility
559
60.7k
    if(!isPrimitiveVisible(rDisplayInfo))
560
58.4k
        return;
561
562
2.28k
    getPrimitive2DSequence(rDisplayInfo);
563
2.28k
    if(mxPrimitive2DSequence.empty())
564
1.02k
        return;
565
566
    // get ranges
567
1.26k
    const drawinglayer::geometry::ViewInformation2D& rViewInformation2D(GetObjectContact().getViewInformation2D());
568
    // tdf#147164 cannot use maObjectRange here, it is unreliable
569
1.26k
    const basegfx::B2DRange aObjectRange(mxPrimitive2DSequence.getB2DRange(rViewInformation2D));
570
1.26k
    const basegfx::B2DRange& aViewRange(rViewInformation2D.getViewport());
571
572
    // check geometrical visibility
573
1.26k
    bool bVisible = aViewRange.isEmpty() || aViewRange.overlaps(aObjectRange);
574
1.26k
    if(!bVisible)
575
123
        return;
576
577
    // temporarily take over the mxPrimitive2DSequence, in case it gets invalidated while we want to iterate over it
578
1.13k
    auto tmp = std::move(const_cast<ViewObjectContact*>(this)->mxPrimitive2DSequence);
579
1.13k
    int nPrevCount = mnActionChangedCount;
580
581
1.13k
    rVisitor.visit(tmp);
582
583
    // if we received ActionChanged() calls while walking the primitives, then leave it empty, otherwise move it back
584
1.13k
    if (mnActionChangedCount == nPrevCount)
585
1.13k
        const_cast<ViewObjectContact*>(this)->mxPrimitive2DSequence = std::move(tmp);
586
1.13k
}
587
588
void ViewObjectContact::getPrimitive2DSequenceSubHierarchy(DisplayInfo& rDisplayInfo, drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) const
589
2.52k
{
590
2.52k
    ViewContact& rViewContact = GetViewContact();
591
2.52k
    const sal_uInt32 nSubHierarchyCount(rViewContact.GetObjectCount());
592
593
64.3k
    for(sal_uInt32 a(0); a < nSubHierarchyCount; a++)
594
61.8k
        rViewContact.getPrimitive2DSequenceHierarchyOfIndex(a, rDisplayInfo, GetObjectContact(), rVisitor);
595
2.52k
}
596
597
// Support getting a GridOffset per object and view for non-linear ViewToDevice
598
// transformation (calc). On-demand created by delegating to the ObjectContact
599
// (->View) that has then all needed information
600
const basegfx::B2DVector& ViewObjectContact::getGridOffset() const
601
0
{
602
0
    static const basegfx::B2DVector EMPTY(0.0, 0.0);
603
0
    if (!GetObjectContact().supportsGridOffsets())
604
0
        return EMPTY;
605
606
0
    if (moGridOffset.has_value() && fabs(moGridOffset->getX()) > 1000.0)
607
0
    {
608
        // Huge offsets are a hint for error -> usually the conditions for
609
        // calculation have changed. E.g. - I saw errors with +/-5740, that
610
        // was in the environment of massive external UNO API using LO as
611
        // target.
612
        // If conditions for this calculation change, it is usually required to call
613
        // - ViewObjectContact::resetGridOffset(), or
614
        // - ObjectContact::resetAllGridOffsets() or
615
        // - ScDrawView::resetGridOffsetsForAllSdrPageViews()
616
        // as it is done e.g. when zoom changes (see ScDrawView::RecalcScale()).
617
        // Theoretically these resets have to be done for any precondition
618
        // changed that is used in the calculation of that value (see
619
        // ScDrawView::calculateGridOffsetForSdrObject).
620
        // This is not complete and would be hard to do so.
621
        // Since it is just a buffered value and re-calculation is not
622
        // expensive (linear O(n)) we can just reset suspicious values here.
623
        // Hopefully - when that non-linear ViewTransformation problem for
624
        // the calc-view gets solved one day - all this can be removed
625
        // again. For now, let's just reset here and force re-calculation.
626
        // Add a SAL_WARN to inform about this.
627
0
        SAL_WARN("svx", "Suspicious GridOffset value resetted (!)");
628
0
        moGridOffset.reset();
629
0
    }
630
631
0
    if(!moGridOffset)
632
0
    {
633
        // create on-demand
634
0
        moGridOffset.emplace();
635
0
        GetObjectContact().calculateGridOffsetForViewObjectContact(*moGridOffset, *this);
636
0
    }
637
638
0
    return *moGridOffset;
639
0
}
640
641
void ViewObjectContact::resetGridOffset()
642
0
{
643
    // reset buffered GridOffset itself
644
0
    moGridOffset.reset();
645
646
    // also reset sequence to get a re-calculation when GridOffset changes
647
0
    mxPrimitive2DSequence.clear();
648
0
    maObjectRange.reset();
649
0
}
650
651
}
652
653
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */