Coverage Report

Created: 2026-04-09 11:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/svx/source/svdraw/svdomedia.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column:100 -*- */
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 <config_features.h>
21
22
#include <svx/svdomedia.hxx>
23
24
#include <com/sun/star/text/GraphicCrop.hpp>
25
26
#include <rtl/ustring.hxx>
27
#include <sal/log.hxx>
28
29
#include <ucbhelper/content.hxx>
30
#include <comphelper/processfactory.hxx>
31
#include <comphelper/storagehelper.hxx>
32
33
#include <vcl/svapp.hxx>
34
35
#include <svx/svdmodel.hxx>
36
#include <svx/dialmgr.hxx>
37
#include <svx/strings.hrc>
38
#include <sdr/contact/viewcontactofsdrmediaobj.hxx>
39
#include <avmedia/mediawindow.hxx>
40
#include <comphelper/diagnose_ex.hxx>
41
42
using namespace ::com::sun::star;
43
44
45
struct SdrMediaObj::Impl
46
{
47
    ::avmedia::MediaItem                  m_MediaProperties;
48
#if HAVE_FEATURE_AVMEDIA
49
    // Note: the temp file is read only, until it is deleted!
50
    // It may be shared between multiple documents in case of copy/paste,
51
    // hence the shared_ptr.
52
    std::shared_ptr< ::avmedia::MediaTempFile >  m_pTempFile;
53
    rtl::Reference<avmedia::PlayerListener> m_xPlayerListener;
54
#endif
55
    uno::Reference< graphic::XGraphic >   m_xCachedSnapshot;
56
    OUString m_LastFailedPkgURL;
57
};
58
59
SdrMediaObj::SdrMediaObj(SdrModel& rSdrModel)
60
0
:   SdrRectObj(rSdrModel)
61
0
    ,m_xImpl( new Impl )
62
0
{
63
0
}
64
65
SdrMediaObj::SdrMediaObj(SdrModel& rSdrModel, SdrMediaObj const & rSource)
66
0
:   SdrRectObj(rSdrModel, rSource)
67
0
    ,m_xImpl( new Impl )
68
0
{
69
0
#if HAVE_FEATURE_AVMEDIA
70
0
    m_xImpl->m_pTempFile = rSource.m_xImpl->m_pTempFile; // before props
71
0
#endif
72
0
    setMediaProperties( rSource.getMediaProperties() );
73
0
    m_xImpl->m_xCachedSnapshot = rSource.m_xImpl->m_xCachedSnapshot;
74
0
}
75
76
SdrMediaObj::SdrMediaObj(
77
    SdrModel& rSdrModel,
78
    const tools::Rectangle& rRect)
79
0
:   SdrRectObj(rSdrModel, rRect)
80
0
    ,m_xImpl( new Impl )
81
0
{
82
0
    osl_atomic_increment(&m_refCount);
83
84
0
    const bool bUndo(rSdrModel.IsUndoEnabled());
85
0
    rSdrModel.EnableUndo(false);
86
0
    MakeNameUnique();
87
0
    rSdrModel.EnableUndo(bUndo);
88
89
0
    osl_atomic_decrement(&m_refCount);
90
0
}
91
92
SdrMediaObj::~SdrMediaObj()
93
0
{
94
0
}
95
96
bool SdrMediaObj::HasTextEdit() const
97
0
{
98
0
    return false;
99
0
}
100
101
std::unique_ptr<sdr::contact::ViewContact> SdrMediaObj::CreateObjectSpecificViewContact()
102
0
{
103
0
    return std::make_unique<sdr::contact::ViewContactOfSdrMediaObj>( *this );
104
0
}
105
106
void SdrMediaObj::TakeObjInfo( SdrObjTransformInfoRec& rInfo ) const
107
0
{
108
0
    rInfo.bMoveAllowed = true;
109
0
    rInfo.bResizeFreeAllowed = true;
110
0
    rInfo.bResizePropAllowed = true;
111
0
    rInfo.bRotateFreeAllowed = false;
112
0
    rInfo.bRotate90Allowed = false;
113
0
    rInfo.bMirrorFreeAllowed = false;
114
0
    rInfo.bMirror45Allowed = false;
115
0
    rInfo.bMirror90Allowed = false;
116
0
    rInfo.bTransparenceAllowed = false;
117
0
    rInfo.bShearAllowed = false;
118
0
    rInfo.bEdgeRadiusAllowed = false;
119
0
    rInfo.bNoOrthoDesired = false;
120
0
    rInfo.bNoContortion = false;
121
0
    rInfo.bCanConvToPath = false;
122
0
    rInfo.bCanConvToPoly = false;
123
0
    rInfo.bCanConvToContour = false;
124
0
    rInfo.bCanConvToPathLineToArea = false;
125
0
    rInfo.bCanConvToPolyLineToArea = false;
126
0
}
127
128
SdrObjKind SdrMediaObj::GetObjIdentifier() const
129
0
{
130
0
    return SdrObjKind::Media;
131
0
}
132
133
OUString SdrMediaObj::TakeObjNameSingul() const
134
0
{
135
0
    OUString sName(SvxResId(STR_ObjNameSingulMEDIA));
136
137
0
    OUString aName(GetName());
138
139
0
    if (!aName.isEmpty())
140
0
        sName += " '" + aName + "'";
141
142
0
    return sName;
143
0
}
144
145
OUString SdrMediaObj::TakeObjNamePlural() const
146
0
{
147
0
    return SvxResId(STR_ObjNamePluralMEDIA);
148
0
}
149
150
rtl::Reference<SdrObject> SdrMediaObj::CloneSdrObject(SdrModel& rTargetModel) const
151
0
{
152
0
    return new SdrMediaObj(rTargetModel, *this);
153
0
}
154
155
uno::Reference< graphic::XGraphic > const & SdrMediaObj::getSnapshot() const
156
0
{
157
0
#if HAVE_FEATURE_AVMEDIA
158
0
    if( !m_xImpl->m_xCachedSnapshot.is() )
159
0
    {
160
0
        Graphic aGraphic = m_xImpl->m_MediaProperties.getGraphic();
161
0
        if (!aGraphic.IsNone())
162
0
        {
163
0
            Size aPref = aGraphic.GetPrefSize();
164
0
            Size aPixel = aGraphic.GetSizePixel();
165
0
            const text::GraphicCrop& rCrop = m_xImpl->m_MediaProperties.getCrop();
166
0
            if (rCrop.Bottom > 0 || rCrop.Left > 0 || rCrop.Right > 0 || rCrop.Top > 0)
167
0
            {
168
0
                tools::Long nLeft = aPixel.getWidth() * rCrop.Left / aPref.getWidth();
169
0
                tools::Long nTop = aPixel.getHeight() * rCrop.Top / aPref.getHeight();
170
0
                tools::Long nRight = aPixel.getWidth() * rCrop.Right / aPref.getWidth();
171
0
                tools::Long nBottom = aPixel.getHeight() * rCrop.Bottom / aPref.getHeight();
172
0
                Bitmap aBitmap = aGraphic.GetBitmap();
173
0
                aBitmap.Crop({nLeft, nTop, aPixel.getWidth() - nRight, aPixel.getHeight() - nBottom});
174
0
                aGraphic = aBitmap;
175
0
            }
176
177
            // We have an explicit graphic for this media object, then go with that instead of
178
            // generating our own one.
179
0
            m_xImpl->m_xCachedSnapshot = aGraphic.GetXGraphic();
180
0
            return m_xImpl->m_xCachedSnapshot;
181
0
        }
182
183
0
        OUString aRealURL = m_xImpl->m_MediaProperties.getTempURL();
184
0
        if( aRealURL.isEmpty() )
185
0
            aRealURL = m_xImpl->m_MediaProperties.getURL();
186
0
        OUString sReferer = m_xImpl->m_MediaProperties.getReferer();
187
0
        OUString sMimeType = m_xImpl->m_MediaProperties.getMimeType();
188
0
        uno::Reference<graphic::XGraphic> xCachedSnapshot = m_xImpl->m_xCachedSnapshot;
189
190
0
        m_xImpl->m_xPlayerListener.set(new avmedia::PlayerListener(
191
0
            [this, xCachedSnapshot, aRealURL, sReferer, sMimeType](const css::uno::Reference<css::media::XPlayer>& rPlayer){
192
0
                SolarMutexGuard g;
193
0
                uno::Reference<graphic::XGraphic> xGraphic
194
0
                    = m_xImpl->m_MediaProperties.getGraphic().GetXGraphic();
195
0
                m_xImpl->m_xCachedSnapshot = avmedia::MediaWindow::grabFrame(rPlayer, xGraphic);
196
0
                ActionChanged();
197
0
            }));
198
199
0
        avmedia::MediaWindow::grabFrame(aRealURL, sReferer, sMimeType, m_xImpl->m_xPlayerListener);
200
0
    }
201
0
#endif
202
0
    return m_xImpl->m_xCachedSnapshot;
203
0
}
204
205
void SdrMediaObj::AdjustToMaxRect( const tools::Rectangle& rMaxRect, bool bShrinkOnly /* = false */ )
206
0
{
207
0
    Size aSize( Application::GetDefaultDevice()->PixelToLogic(
208
0
                    static_cast< sdr::contact::ViewContactOfSdrMediaObj& >( GetViewContact() ).getPreferredSize(),
209
0
                    MapMode(MapUnit::Map100thMM)) );
210
0
    Size aMaxSize( rMaxRect.GetSize() );
211
212
0
    if( aSize.IsEmpty() )
213
0
        return;
214
215
0
    Point aPos( rMaxRect.TopLeft() );
216
217
    // if graphic is too large, fit it to the page
218
0
    if ( (!bShrinkOnly                          ||
219
0
         ( aSize.Height() > aMaxSize.Height() ) ||
220
0
         ( aSize.Width()  > aMaxSize.Width()  ) )&&
221
0
         aSize.Height() && aMaxSize.Height() )
222
0
    {
223
0
        float fGrfWH =  static_cast<float>(aSize.Width()) /
224
0
                        static_cast<float>(aSize.Height());
225
0
        float fWinWH =  static_cast<float>(aMaxSize.Width()) /
226
0
                        static_cast<float>(aMaxSize.Height());
227
228
        // scale graphic to page size
229
0
        if ( fGrfWH < fWinWH )
230
0
        {
231
0
            aSize.setWidth( static_cast<tools::Long>(aMaxSize.Height() * fGrfWH) );
232
0
            aSize.setHeight( aMaxSize.Height() );
233
0
        }
234
0
        else if ( fGrfWH > 0.F )
235
0
        {
236
0
            aSize.setWidth( aMaxSize.Width() );
237
0
            aSize.setHeight( static_cast<tools::Long>(aMaxSize.Width() / fGrfWH) );
238
0
        }
239
240
0
        aPos = rMaxRect.Center();
241
0
    }
242
243
0
    if( bShrinkOnly )
244
0
        aPos = getRectangle().TopLeft();
245
246
0
    aPos.AdjustX( -(aSize.Width() / 2) );
247
0
    aPos.AdjustY( -(aSize.Height() / 2) );
248
0
    SetLogicRect( tools::Rectangle( aPos, aSize ) );
249
0
}
250
251
void SdrMediaObj::setURL(const OUString& rURL, const OUString& rReferer)
252
0
{
253
0
    ::avmedia::MediaItem aURLItem;
254
0
#if HAVE_FEATURE_AVMEDIA
255
0
    aURLItem.setURL( rURL, u""_ustr, rReferer );
256
#else
257
    (void) rURL;
258
    (void) rReferer;
259
#endif
260
0
    setMediaProperties( aURLItem );
261
0
}
262
263
const OUString& SdrMediaObj::getURL() const
264
0
{
265
0
#if HAVE_FEATURE_AVMEDIA
266
0
    return m_xImpl->m_MediaProperties.getURL();
267
#else
268
    static OUString ret;
269
    return ret;
270
#endif
271
0
}
272
273
const OUString& SdrMediaObj::getTempURL() const
274
0
{
275
0
#if HAVE_FEATURE_AVMEDIA
276
0
    return m_xImpl->m_MediaProperties.getTempURL();
277
#else
278
static OUString ret;
279
    return ret;
280
#endif
281
0
}
282
283
void SdrMediaObj::setMediaProperties( const ::avmedia::MediaItem& rState )
284
0
{
285
0
    mediaPropertiesChanged( rState );
286
0
    static_cast< sdr::contact::ViewContactOfSdrMediaObj& >( GetViewContact() ).executeMediaItem( getMediaProperties() );
287
0
}
288
289
const ::avmedia::MediaItem& SdrMediaObj::getMediaProperties() const
290
0
{
291
0
    return m_xImpl->m_MediaProperties;
292
0
}
293
294
uno::Reference<io::XInputStream> SdrMediaObj::GetInputStream() const
295
0
{
296
0
#if HAVE_FEATURE_AVMEDIA
297
0
    if (!m_xImpl->m_pTempFile)
298
0
    {
299
0
        SAL_WARN("svx", "this is only intended for embedded media");
300
0
        return nullptr;
301
0
    }
302
0
    ucbhelper::Content tempFile(m_xImpl->m_pTempFile->m_TempFileURL,
303
0
                uno::Reference<ucb::XCommandEnvironment>(),
304
0
                comphelper::getProcessComponentContext());
305
0
    return tempFile.openStream();
306
#else
307
    return nullptr;
308
#endif
309
0
}
310
311
void SdrMediaObj::SetInputStream(uno::Reference<io::XInputStream> const& xStream)
312
0
{
313
#if !HAVE_FEATURE_AVMEDIA
314
    (void) xStream;
315
#else
316
0
    if (m_xImpl->m_pTempFile || m_xImpl->m_LastFailedPkgURL.isEmpty())
317
0
    {
318
0
        SAL_WARN("svx", "this is only intended for embedded media");
319
0
        return;
320
0
    }
321
322
0
    OUString tempFileURL;
323
0
    const bool bSuccess(
324
0
        ::avmedia::CreateMediaTempFile(
325
0
            xStream,
326
0
            tempFileURL,
327
0
            u""));
328
329
0
    if (bSuccess)
330
0
    {
331
0
        m_xImpl->m_pTempFile = std::make_shared<::avmedia::MediaTempFile>(tempFileURL);
332
0
        m_xImpl->m_MediaProperties.setURL(
333
0
            m_xImpl->m_LastFailedPkgURL, tempFileURL, u""_ustr);
334
0
    }
335
0
    m_xImpl->m_LastFailedPkgURL.clear(); // once only
336
0
#endif
337
0
}
338
339
/// copy a stream from XStorage to temp file
340
#if HAVE_FEATURE_AVMEDIA
341
static bool lcl_HandlePackageURL(
342
    OUString const & rURL,
343
    const SdrModel& rModel,
344
    OUString & o_rTempFileURL)
345
0
{
346
0
    ::comphelper::LifecycleProxy sourceProxy;
347
0
    uno::Reference<io::XInputStream> xInStream;
348
0
    try {
349
0
        xInStream = rModel.GetDocumentStream(rURL, sourceProxy);
350
0
    }
351
0
    catch (container::NoSuchElementException const&)
352
0
    {
353
0
        SAL_INFO("svx", "not found: '" << rURL << "'");
354
0
        return false;
355
0
    }
356
0
    catch (uno::Exception const&)
357
0
    {
358
0
        TOOLS_WARN_EXCEPTION("svx", "");
359
0
        return false;
360
0
    }
361
0
    if (!xInStream.is())
362
0
    {
363
0
        SAL_WARN("svx", "no stream?");
364
0
        return false;
365
0
    }
366
    // Make sure the temporary copy has the same file name extension as the original media file
367
    // (like .mp4). That seems to be important for some AVFoundation APIs. For random extension-less
368
    // file names, they don't seem to even bother looking inside the file.
369
0
    sal_Int32 nLastDot = rURL.lastIndexOf('.');
370
0
    sal_Int32 nLastSlash = rURL.lastIndexOf('/');
371
0
    OUString sDesiredExtension;
372
0
    if (nLastDot > nLastSlash && nLastDot+1 < rURL.getLength())
373
0
        sDesiredExtension = rURL.copy(nLastDot);
374
0
    return ::avmedia::CreateMediaTempFile(xInStream, o_rTempFileURL, sDesiredExtension);
375
0
}
376
#endif
377
378
void SdrMediaObj::mediaPropertiesChanged( const ::avmedia::MediaItem& rNewProperties )
379
0
{
380
0
    bool bBroadcastChanged = false;
381
0
#if HAVE_FEATURE_AVMEDIA
382
0
    const AVMediaSetMask nMaskSet = rNewProperties.getMaskSet();
383
384
    // use only a subset of MediaItem properties for own properties
385
0
    if( AVMediaSetMask::MIME_TYPE & nMaskSet )
386
0
        m_xImpl->m_MediaProperties.setMimeType( rNewProperties.getMimeType() );
387
388
0
    if (nMaskSet & AVMediaSetMask::GRAPHIC)
389
0
    {
390
0
        m_xImpl->m_MediaProperties.setGraphic(rNewProperties.getGraphic());
391
0
    }
392
393
0
    if (nMaskSet & AVMediaSetMask::CROP)
394
0
    {
395
0
        m_xImpl->m_MediaProperties.setCrop(rNewProperties.getCrop());
396
0
    }
397
398
0
    if( ( AVMediaSetMask::URL & nMaskSet ) &&
399
0
        ( rNewProperties.getURL() != getURL() ))
400
0
    {
401
0
        m_xImpl->m_xCachedSnapshot.clear();
402
0
        m_xImpl->m_xPlayerListener.clear();
403
0
        m_xImpl->m_MediaProperties.setFallbackURL( rNewProperties.getFallbackURL() );
404
0
        OUString const& url(rNewProperties.getURL());
405
0
        if (url.startsWithIgnoreAsciiCase("vnd.sun.star.Package:"))
406
0
        {
407
0
            if (   !m_xImpl->m_pTempFile
408
0
                || (m_xImpl->m_pTempFile->m_TempFileURL !=
409
0
                                rNewProperties.getTempURL()))
410
0
            {
411
0
                OUString tempFileURL;
412
0
                const bool bSuccess(
413
0
                    lcl_HandlePackageURL(
414
0
                        url,
415
0
                        getSdrModelFromSdrObject(),
416
0
                        tempFileURL));
417
418
0
                if (bSuccess)
419
0
                {
420
0
                    m_xImpl->m_pTempFile =
421
0
                            std::make_shared<::avmedia::MediaTempFile>(tempFileURL);
422
0
                    m_xImpl->m_MediaProperties.setURL(url, tempFileURL, u""_ustr);
423
0
                }
424
0
                else // this case is for Clone via operator=
425
0
                {
426
0
                    m_xImpl->m_pTempFile.reset();
427
0
                    m_xImpl->m_MediaProperties.setURL(u""_ustr, u""_ustr, u""_ustr);
428
                    // UGLY: oox import also gets here, because unlike ODF
429
                    // getDocumentStorage() is not the imported file...
430
0
                    m_xImpl->m_LastFailedPkgURL = url;
431
0
                }
432
0
            }
433
0
            else
434
0
            {
435
0
                m_xImpl->m_MediaProperties.setURL(url,
436
0
                        rNewProperties.getTempURL(), u""_ustr);
437
0
            }
438
0
        }
439
0
        else
440
0
        {
441
0
            m_xImpl->m_pTempFile.reset();
442
0
            m_xImpl->m_MediaProperties.setURL(url, u""_ustr, rNewProperties.getReferer());
443
0
        }
444
0
        bBroadcastChanged = true;
445
0
    }
446
447
0
    if( AVMediaSetMask::LOOP & nMaskSet )
448
0
        m_xImpl->m_MediaProperties.setLoop( rNewProperties.isLoop() );
449
450
0
    if( AVMediaSetMask::MUTE & nMaskSet )
451
0
        m_xImpl->m_MediaProperties.setMute( rNewProperties.isMute() );
452
453
0
    if( AVMediaSetMask::VOLUMEDB & nMaskSet )
454
0
        m_xImpl->m_MediaProperties.setVolumeDB( rNewProperties.getVolumeDB() );
455
456
0
    if( AVMediaSetMask::ZOOM & nMaskSet )
457
0
        m_xImpl->m_MediaProperties.setZoom( rNewProperties.getZoom() );
458
#else
459
    (void) rNewProperties;
460
#endif
461
462
0
    if( bBroadcastChanged )
463
0
    {
464
0
        SetChanged();
465
0
        BroadcastObjectChange();
466
0
    }
467
0
}
468
469
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */