/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 ¶ms) |
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 |