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