Coverage Report

Created: 2026-10-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/poppler/poppler/Rendition.cc
Line
Count
Source
1
//*********************************************************************************
2
//                               Rendition.cc
3
//---------------------------------------------------------------------------------
4
//
5
//---------------------------------------------------------------------------------
6
// Hugo Mercier <hmercier31[at]gmail.com> (c) 2008
7
// Pino Toscano <pino@kde.org> (c) 2008
8
// Carlos Garcia Campos <carlosgc@gnome.org> (c) 2010
9
// Tobias Koenig <tobias.koenig@kdab.com> (c) 2012
10
// Albert Astals Cid <aacid@kde.org> (C) 2017, 2018, 2024-2026
11
// g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk> (C) 2025, 2026
12
// 2025 Arnav V <arnav0872@gmail.com> (C) 2025
13
// Stefan BrĂ¼ns <stefan.bruens@rwth-aachen.de> (C) 2026
14
//
15
// This program is free software; you can redistribute it and/or modify
16
// it under the terms of the GNU General Public License as published by
17
// the Free Software Foundation; either version 2 of the License, or
18
// (at your option) any later version.
19
//
20
// This program is distributed in the hope that it will be useful,
21
// but WITHOUT ANY WARRANTY; without even the implied warranty of
22
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
// GNU General Public License for more details.
24
//
25
// You should have received a copy of the GNU General Public License
26
// along with this program; if not, write to the Free Software
27
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
28
//*********************************************************************************
29
30
#include "Rendition.h"
31
#include "Stream.h"
32
33
MediaWindowParameters::MediaWindowParameters()
34
0
{
35
    // default values
36
0
    type = windowEmbedded;
37
0
    width = -1;
38
0
    height = -1;
39
0
    relativeTo = windowRelativeToDocument;
40
0
    XPosition = 0.5;
41
0
    YPosition = 0.5;
42
0
    hasTitleBar = true;
43
0
    hasCloseButton = true;
44
0
    isResizeable = true;
45
0
}
46
47
void MediaWindowParameters::parseFWParams(const Dict &params)
48
0
{
49
0
    Object tmp = params.lookup("D");
50
0
    if (tmp.isArray()) {
51
0
        Array *dim = tmp.getArray();
52
53
0
        if (dim->getLength() >= 2) {
54
0
            Object dd = dim->get(0);
55
0
            if (dd.isInt()) {
56
0
                width = dd.getInt();
57
0
            }
58
59
0
            dd = dim->get(1);
60
0
            if (dd.isInt()) {
61
0
                height = dd.getInt();
62
0
            }
63
0
        }
64
0
    }
65
66
0
    tmp = params.lookup("RT");
67
0
    if (tmp.isInt()) {
68
0
        int t = tmp.getInt();
69
0
        switch (t) {
70
0
        case 0:
71
0
            relativeTo = windowRelativeToDocument;
72
0
            break;
73
0
        case 1:
74
0
            relativeTo = windowRelativeToApplication;
75
0
            break;
76
0
        case 2:
77
0
            relativeTo = windowRelativeToDesktop;
78
0
            break;
79
0
        }
80
0
    }
81
82
0
    tmp = params.lookup("P");
83
0
    if (tmp.isInt()) {
84
0
        int t = tmp.getInt();
85
86
0
        switch (t) {
87
0
        case 0: // Upper left
88
0
            XPosition = 0.0;
89
0
            YPosition = 0.0;
90
0
            break;
91
0
        case 1: // Upper Center
92
0
            XPosition = 0.5;
93
0
            YPosition = 0.0;
94
0
            break;
95
0
        case 2: // Upper Right
96
0
            XPosition = 1.0;
97
0
            YPosition = 0.0;
98
0
            break;
99
0
        case 3: // Center Left
100
0
            XPosition = 0.0;
101
0
            YPosition = 0.5;
102
0
            break;
103
0
        case 4: // Center
104
0
            XPosition = 0.5;
105
0
            YPosition = 0.5;
106
0
            break;
107
0
        case 5: // Center Right
108
0
            XPosition = 1.0;
109
0
            YPosition = 0.5;
110
0
            break;
111
0
        case 6: // Lower Left
112
0
            XPosition = 0.0;
113
0
            YPosition = 1.0;
114
0
            break;
115
0
        case 7: // Lower Center
116
0
            XPosition = 0.5;
117
0
            YPosition = 1.0;
118
0
            break;
119
0
        case 8: // Lower Right
120
0
            XPosition = 1.0;
121
0
            YPosition = 1.0;
122
0
            break;
123
0
        }
124
0
    }
125
126
0
    tmp = params.lookup("T");
127
0
    if (tmp.isBool()) {
128
0
        hasTitleBar = tmp.getBool();
129
0
    }
130
0
    tmp = params.lookup("UC");
131
0
    if (tmp.isBool()) {
132
0
        hasCloseButton = tmp.getBool();
133
0
    }
134
0
    tmp = params.lookup("R");
135
0
    if (tmp.isInt()) {
136
0
        isResizeable = (tmp.getInt() != 0);
137
0
    }
138
0
}
139
140
MediaParameters::MediaParameters()
141
0
{
142
    // instanciate to default values
143
144
0
    volume = 100;
145
0
    fittingPolicy = fittingUndefined;
146
0
    autoPlay = true;
147
0
    repeatCount = 1.0;
148
0
    opacity = 1.0;
149
0
    showControls = false;
150
0
    duration = 0;
151
0
}
152
153
void MediaParameters::parseMediaPlayParameters(const Dict &playDict)
154
0
{
155
0
    Object tmp = playDict.lookup("V");
156
0
    if (tmp.isInt()) {
157
0
        volume = tmp.getInt();
158
0
    }
159
160
0
    tmp = playDict.lookup("C");
161
0
    if (tmp.isBool()) {
162
0
        showControls = tmp.getBool();
163
0
    }
164
165
0
    tmp = playDict.lookup("F");
166
0
    if (tmp.isInt()) {
167
0
        int t = tmp.getInt();
168
169
0
        switch (t) {
170
0
        case 0:
171
0
            fittingPolicy = fittingMeet;
172
0
            break;
173
0
        case 1:
174
0
            fittingPolicy = fittingSlice;
175
0
            break;
176
0
        case 2:
177
0
            fittingPolicy = fittingFill;
178
0
            break;
179
0
        case 3:
180
0
            fittingPolicy = fittingScroll;
181
0
            break;
182
0
        case 4:
183
0
            fittingPolicy = fittingHidden;
184
0
            break;
185
0
        case 5:
186
0
            fittingPolicy = fittingUndefined;
187
0
            break;
188
0
        }
189
0
    }
190
191
    // duration parsing
192
    // duration's default value is set to 0, which means : intrinsinc media duration
193
0
    tmp = playDict.lookup("D");
194
0
    if (tmp.isDict()) {
195
0
        Object oname = tmp.dictLookup("S");
196
0
        if (oname.isName()) {
197
0
            const std::string &name = oname.getNameString();
198
0
            if (name == "F") {
199
0
                duration = -1; // infinity
200
0
            } else if (name == "T") {
201
0
                Object ddict = tmp.dictLookup("T");
202
0
                if (ddict.isDict()) {
203
0
                    Object tmp2 = ddict.dictLookup("V");
204
0
                    if (tmp2.isNum()) {
205
0
                        duration = static_cast<unsigned long>(tmp2.getNum());
206
0
                    }
207
0
                }
208
0
            }
209
0
        }
210
0
    }
211
212
0
    tmp = playDict.lookup("A");
213
0
    if (tmp.isBool()) {
214
0
        autoPlay = tmp.getBool();
215
0
    }
216
217
0
    tmp = playDict.lookup("RC");
218
0
    if (tmp.isNum()) {
219
0
        repeatCount = tmp.getNum();
220
0
    }
221
0
}
222
223
void MediaParameters::parseMediaScreenParameters(const Dict &screenDict)
224
0
{
225
0
    Object tmp = screenDict.lookup("W");
226
0
    if (tmp.isInt()) {
227
0
        int t = tmp.getInt();
228
229
0
        switch (t) {
230
0
        case 0:
231
0
            windowParams.type = MediaWindowParameters::windowFloating;
232
0
            break;
233
0
        case 1:
234
0
            windowParams.type = MediaWindowParameters::windowFullscreen;
235
0
            break;
236
0
        case 2:
237
0
            windowParams.type = MediaWindowParameters::windowHidden;
238
0
            break;
239
0
        case 3:
240
0
            windowParams.type = MediaWindowParameters::windowEmbedded;
241
0
            break;
242
0
        }
243
0
    }
244
245
    // background color
246
0
    tmp = screenDict.lookup("B");
247
0
    if (tmp.isArray()) {
248
0
        Array *color = tmp.getArray();
249
250
0
        Object component = color->get(0);
251
0
        bgColor.r = component.getNum();
252
253
0
        component = color->get(1);
254
0
        bgColor.g = component.getNum();
255
256
0
        component = color->get(2);
257
0
        bgColor.b = component.getNum();
258
0
    }
259
260
    // opacity
261
0
    tmp = screenDict.lookup("O");
262
0
    if (tmp.isNum()) {
263
0
        opacity = tmp.getNum();
264
0
    }
265
266
0
    if (windowParams.type == MediaWindowParameters::windowFloating) {
267
0
        const Object winDict = screenDict.lookup("F");
268
0
        if (winDict.isDict()) {
269
0
            windowParams.parseFWParams(*winDict.getDict());
270
0
        }
271
0
    }
272
0
}
273
274
0
MediaRendition::~MediaRendition() = default;
275
276
MediaRendition::MediaRendition(const Dict &dict)
277
0
{
278
0
    bool hasClip = false;
279
280
0
    ok = true;
281
0
    isEmbedded = false;
282
283
    //
284
    // Parse media clip data
285
    //
286
0
    Object tmp2 = dict.lookup("C");
287
0
    if (tmp2.isDict()) { // media clip
288
0
        hasClip = true;
289
0
        Object tmp = tmp2.dictLookup("S");
290
0
        if (tmp.isName()) {
291
0
            if (tmp.getNameString() == "MCD") { // media clip data
292
0
                Object obj1 = tmp2.dictLookup("D");
293
0
                if (obj1.isDict()) {
294
0
                    Object obj2 = obj1.dictLookup("F");
295
0
                    if (obj2.isString()) {
296
0
                        fileName = obj2.takeString();
297
0
                    }
298
0
                    obj2 = obj1.dictLookup("EF");
299
0
                    if (obj2.isDict()) {
300
0
                        Object embedded = obj2.dictLookup("F");
301
0
                        if (embedded.isStream()) {
302
0
                            isEmbedded = true;
303
0
                            embeddedStreamObject = embedded.copy();
304
0
                        }
305
0
                    }
306
307
                    // TODO: D might be a form XObject too
308
0
                } else {
309
0
                    error(errSyntaxError, -1, "Invalid Media Clip Data");
310
0
                    ok = false;
311
0
                }
312
313
                // FIXME: ignore CT if D is a form XObject
314
0
                obj1 = tmp2.dictLookup("CT");
315
0
                if (obj1.isString()) {
316
0
                    contentType = obj1.takeString();
317
0
                }
318
0
            } else if (tmp.getNameString() == "MCS") { // media clip data
319
                // TODO
320
0
            }
321
0
        } else {
322
0
            error(errSyntaxError, -1, "Invalid Media Clip");
323
0
            ok = false;
324
0
        }
325
0
    }
326
327
0
    if (!ok) {
328
0
        return;
329
0
    }
330
331
    //
332
    // parse Media Play Parameters
333
0
    tmp2 = dict.lookup("P");
334
0
    if (tmp2.isDict()) { // media play parameters
335
0
        Object params = tmp2.dictLookup("MH");
336
0
        if (params.isDict()) {
337
0
            MH = MediaParameters {};
338
0
            MH->parseMediaPlayParameters(*params.getDict());
339
0
        }
340
0
        params = tmp2.dictLookup("BE");
341
0
        if (params.isDict()) {
342
0
            BE = MediaParameters {};
343
0
            BE->parseMediaPlayParameters(*params.getDict());
344
0
        }
345
0
    } else if (!hasClip) {
346
0
        error(errSyntaxError, -1, "Invalid Media Rendition");
347
0
        ok = false;
348
0
    }
349
350
    //
351
    // parse Media Screen Parameters
352
0
    tmp2 = dict.lookup("SP");
353
0
    if (tmp2.isDict()) { // media screen parameters
354
0
        Object params = tmp2.dictLookup("MH");
355
0
        if (params.isDict()) {
356
0
            if (!MH) {
357
0
                MH = MediaParameters {};
358
0
            }
359
0
            MH->parseMediaScreenParameters(*params.getDict());
360
0
        }
361
0
        params = tmp2.dictLookup("BE");
362
0
        if (params.isDict()) {
363
0
            if (!BE) {
364
0
                BE = MediaParameters {};
365
0
            }
366
0
            BE->parseMediaScreenParameters(*params.getDict());
367
0
        }
368
0
    }
369
0
}
370
371
MediaRendition::MediaRendition(const MediaRendition &other)
372
0
{
373
0
    ok = other.ok;
374
0
    MH = other.MH;
375
0
    BE = other.BE;
376
0
    isEmbedded = other.isEmbedded;
377
0
    embeddedStreamObject = other.embeddedStreamObject.copy();
378
379
0
    if (other.contentType) {
380
0
        contentType = other.contentType->copy();
381
0
    }
382
383
0
    if (other.fileName) {
384
0
        fileName = other.fileName->copy();
385
0
    }
386
0
}
387
388
void MediaRendition::outputToFile(FILE *fp)
389
0
{
390
0
    if (!isEmbedded) {
391
0
        return;
392
0
    }
393
394
0
    Stream *embeddedStream = embeddedStreamObject.getStream();
395
0
    if (!embeddedStream->rewind()) {
396
0
        return;
397
0
    }
398
399
0
    while (true) {
400
0
        int c = embeddedStream->getChar();
401
0
        if (c == EOF) {
402
0
            break;
403
0
        }
404
405
0
        fwrite(&c, 1, 1, fp);
406
0
    }
407
0
}
408
409
std::unique_ptr<MediaRendition> MediaRendition::copy() const
410
0
{
411
0
    return std::make_unique<MediaRendition>(*this);
412
0
}
413
414
// TODO: SelectorRendition