Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 this.EXPORTED_SYMBOLS = ["WebVTT"];
7 /**
8 * Code below is vtt.js the JS WebVTT implementation.
9 * Current source code can be found at http://github.com/mozilla/vtt.js
10 *
11 * Code taken from commit 65ae2daaf6ec7e710f591214893bb03d8b7a94b5
12 */
13 /**
14 * Copyright 2013 vtt.js Contributors
15 *
16 * Licensed under the Apache License, Version 2.0 (the "License");
17 * you may not use this file except in compliance with the License.
18 * You may obtain a copy of the License at
19 *
20 * http://www.apache.org/licenses/LICENSE-2.0
21 *
22 * Unless required by applicable law or agreed to in writing, software
23 * distributed under the License is distributed on an "AS IS" BASIS,
24 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 * See the License for the specific language governing permissions and
26 * limitations under the License.
27 */
30 (function(global) {
32 _objCreate = Object.create || (function() {
33 function F() {}
34 return function(o) {
35 if (arguments.length !== 1) {
36 throw new Error('Object.create shim only accepts one parameter.');
37 }
38 F.prototype = o;
39 return new F();
40 };
41 })();
43 // Creates a new ParserError object from an errorData object. The errorData
44 // object should have default code and message properties. The default message
45 // property can be overriden by passing in a message parameter.
46 // See ParsingError.Errors below for acceptable errors.
47 function ParsingError(errorData, message) {
48 this.name = "ParsingError";
49 this.code = errorData.code;
50 this.message = message || errorData.message;
51 }
52 ParsingError.prototype = _objCreate(Error.prototype);
53 ParsingError.prototype.constructor = ParsingError;
55 // ParsingError metadata for acceptable ParsingErrors.
56 ParsingError.Errors = {
57 BadSignature: {
58 code: 0,
59 message: "Malformed WebVTT signature."
60 },
61 BadTimeStamp: {
62 code: 1,
63 message: "Malformed time stamp."
64 }
65 };
67 // Try to parse input as a time stamp.
68 function parseTimeStamp(input) {
70 function computeSeconds(h, m, s, f) {
71 return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
72 }
74 var m = input.match(/^(\d+):(\d{2})(:\d{2})?\.(\d{3})/);
75 if (!m) {
76 return null;
77 }
79 if (m[3]) {
80 // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds]
81 return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]);
82 } else if (m[1] > 59) {
83 // Timestamp takes the form of [hours]:[minutes].[milliseconds]
84 // First position is hours as it's over 59.
85 return computeSeconds(m[1], m[2], 0, m[4]);
86 } else {
87 // Timestamp takes the form of [minutes]:[seconds].[milliseconds]
88 return computeSeconds(0, m[1], m[2], m[4]);
89 }
90 }
92 // A settings object holds key/value pairs and will ignore anything but the first
93 // assignment to a specific key.
94 function Settings() {
95 this.values = _objCreate(null);
96 }
98 Settings.prototype = {
99 // Only accept the first assignment to any key.
100 set: function(k, v) {
101 if (!this.get(k) && v !== "") {
102 this.values[k] = v;
103 }
104 },
105 // Return the value for a key, or a default value.
106 // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
107 // a number of possible default values as properties where 'defaultKey' is
108 // the key of the property that will be chosen; otherwise it's assumed to be
109 // a single value.
110 get: function(k, dflt, defaultKey) {
111 if (defaultKey) {
112 return this.has(k) ? this.values[k] : dflt[defaultKey];
113 }
114 return this.has(k) ? this.values[k] : dflt;
115 },
116 // Check whether we have a value for a key.
117 has: function(k) {
118 return k in this.values;
119 },
120 // Accept a setting if its one of the given alternatives.
121 alt: function(k, v, a) {
122 for (var n = 0; n < a.length; ++n) {
123 if (v === a[n]) {
124 this.set(k, v);
125 break;
126 }
127 }
128 },
129 // Accept a setting if its a valid (signed) integer.
130 integer: function(k, v) {
131 if (/^-?\d+$/.test(v)) { // integer
132 this.set(k, parseInt(v, 10));
133 }
134 },
135 // Accept a setting if its a valid percentage.
136 percent: function(k, v) {
137 var m;
138 if ((m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/))) {
139 v = parseFloat(v);
140 if (v >= 0 && v <= 100) {
141 this.set(k, v);
142 return true;
143 }
144 }
145 return false;
146 }
147 };
149 // Helper function to parse input into groups separated by 'groupDelim', and
150 // interprete each group as a key/value pair separated by 'keyValueDelim'.
151 function parseOptions(input, callback, keyValueDelim, groupDelim) {
152 var groups = groupDelim ? input.split(groupDelim) : [input];
153 for (var i in groups) {
154 if (typeof groups[i] !== "string") {
155 continue;
156 }
157 var kv = groups[i].split(keyValueDelim);
158 if (kv.length !== 2) {
159 continue;
160 }
161 var k = kv[0];
162 var v = kv[1];
163 callback(k, v);
164 }
165 }
167 function parseCue(input, cue, regionList) {
168 // 4.1 WebVTT timestamp
169 function consumeTimeStamp() {
170 var ts = parseTimeStamp(input);
171 if (ts === null) {
172 throw new ParsingError(ParsingError.Errors.BadTimeStamp);
173 }
174 // Remove time stamp from input.
175 input = input.replace(/^[^\sa-zA-Z-]+/, "");
176 return ts;
177 }
179 // 4.4.2 WebVTT cue settings
180 function consumeCueSettings(input, cue) {
181 var settings = new Settings();
183 parseOptions(input, function (k, v) {
184 switch (k) {
185 case "region":
186 // Find the last region we parsed with the same region id.
187 for (var i = regionList.length - 1; i >= 0; i--) {
188 if (regionList[i].id === v) {
189 settings.set(k, regionList[i].region);
190 break;
191 }
192 }
193 break;
194 case "vertical":
195 settings.alt(k, v, ["rl", "lr"]);
196 break;
197 case "line":
198 var vals = v.split(","),
199 vals0 = vals[0];
200 settings.integer(k, vals0);
201 settings.percent(k, vals0) ? settings.set("snapToLines", false) : null;
202 settings.alt(k, vals0, ["auto"]);
203 if (vals.length === 2) {
204 settings.alt("lineAlign", vals[1], ["start", "middle", "end"]);
205 }
206 break;
207 case "position":
208 vals = v.split(",");
209 settings.percent(k, vals[0]);
210 if (vals.length === 2) {
211 settings.alt("positionAlign", vals[1], ["start", "middle", "end"]);
212 }
213 break;
214 case "size":
215 settings.percent(k, v);
216 break;
217 case "align":
218 settings.alt(k, v, ["start", "middle", "end", "left", "right"]);
219 break;
220 }
221 }, /:/, /\s/);
223 // Apply default values for any missing fields.
224 cue.region = settings.get("region", null);
225 cue.vertical = settings.get("vertical", "");
226 cue.line = settings.get("line", "auto");
227 cue.lineAlign = settings.get("lineAlign", "start");
228 cue.snapToLines = settings.get("snapToLines", true);
229 cue.size = settings.get("size", 100);
230 cue.align = settings.get("align", "middle");
231 cue.position = settings.get("position", {
232 start: 0,
233 left: 0,
234 middle: 50,
235 end: 100,
236 right: 100
237 }, cue.align);
238 cue.positionAlign = settings.get("positionAlign", {
239 start: "start",
240 left: "start",
241 middle: "middle",
242 end: "end",
243 right: "end"
244 }, cue.align);
245 }
247 function skipWhitespace() {
248 input = input.replace(/^\s+/, "");
249 }
251 // 4.1 WebVTT cue timings.
252 skipWhitespace();
253 cue.startTime = consumeTimeStamp(); // (1) collect cue start time
254 skipWhitespace();
255 if (input.substr(0, 3) !== "-->") { // (3) next characters must match "-->"
256 throw new ParsingError(ParsingError.Errors.BadTimeStamp,
257 "Malformed time stamp (time stamps must be separated by '-->').");
258 }
259 input = input.substr(3);
260 skipWhitespace();
261 cue.endTime = consumeTimeStamp(); // (5) collect cue end time
263 // 4.1 WebVTT cue settings list.
264 skipWhitespace();
265 consumeCueSettings(input, cue);
266 }
268 var ESCAPE = {
269 "&": "&",
270 "<": "<",
271 ">": ">",
272 "‎": "\u200e",
273 "‏": "\u200f",
274 " ": "\u00a0"
275 };
277 var TAG_NAME = {
278 c: "span",
279 i: "i",
280 b: "b",
281 u: "u",
282 ruby: "ruby",
283 rt: "rt",
284 v: "span",
285 lang: "span"
286 };
288 var TAG_ANNOTATION = {
289 v: "title",
290 lang: "lang"
291 };
293 var NEEDS_PARENT = {
294 rt: "ruby"
295 };
297 // Parse content into a document fragment.
298 function parseContent(window, input) {
299 function nextToken() {
300 // Check for end-of-string.
301 if (!input) {
302 return null;
303 }
305 // Consume 'n' characters from the input.
306 function consume(result) {
307 input = input.substr(result.length);
308 return result;
309 }
311 var m = input.match(/^([^<]*)(<[^>]+>?)?/);
312 // If there is some text before the next tag, return it, otherwise return
313 // the tag.
314 return consume(m[1] ? m[1] : m[2]);
315 }
317 // Unescape a string 's'.
318 function unescape1(e) {
319 return ESCAPE[e];
320 }
321 function unescape(s) {
322 while ((m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/))) {
323 s = s.replace(m[0], unescape1);
324 }
325 return s;
326 }
328 function shouldAdd(current, element) {
329 return !NEEDS_PARENT[element.localName] ||
330 NEEDS_PARENT[element.localName] === current.localName;
331 }
333 // Create an element for this tag.
334 function createElement(type, annotation) {
335 var tagName = TAG_NAME[type];
336 if (!tagName) {
337 return null;
338 }
339 var element = window.document.createElement(tagName);
340 element.localName = tagName;
341 var name = TAG_ANNOTATION[type];
342 if (name && annotation) {
343 element[name] = annotation.trim();
344 }
345 return element;
346 }
348 var rootDiv = window.document.createElement("div"),
349 current = rootDiv,
350 t,
351 tagStack = [];
353 while ((t = nextToken()) !== null) {
354 if (t[0] === '<') {
355 if (t[1] === "/") {
356 // If the closing tag matches, move back up to the parent node.
357 if (tagStack.length &&
358 tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) {
359 tagStack.pop();
360 current = current.parentNode;
361 }
362 // Otherwise just ignore the end tag.
363 continue;
364 }
365 var ts = parseTimeStamp(t.substr(1, t.length - 2));
366 var node;
367 if (ts) {
368 // Timestamps are lead nodes as well.
369 node = window.document.createProcessingInstruction("timestamp", ts);
370 current.appendChild(node);
371 continue;
372 }
373 var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/);
374 // If we can't parse the tag, skip to the next tag.
375 if (!m) {
376 continue;
377 }
378 // Try to construct an element, and ignore the tag if we couldn't.
379 node = createElement(m[1], m[3]);
380 if (!node) {
381 continue;
382 }
383 // Determine if the tag should be added based on the context of where it
384 // is placed in the cuetext.
385 if (!shouldAdd(current, node)) {
386 continue;
387 }
388 // Set the class list (as a list of classes, separated by space).
389 if (m[2]) {
390 node.className = m[2].substr(1).replace('.', ' ');
391 }
392 // Append the node to the current node, and enter the scope of the new
393 // node.
394 tagStack.push(m[1]);
395 current.appendChild(node);
396 current = node;
397 continue;
398 }
400 // Text nodes are leaf nodes.
401 current.appendChild(window.document.createTextNode(unescape(t)));
402 }
404 return rootDiv;
405 }
407 // This is a list of all the Unicode characters that have a strong
408 // right-to-left category. What this means is that these characters are
409 // written right-to-left for sure. It was generated by pulling all the strong
410 // right-to-left characters out of the Unicode data table. That table can
411 // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
412 var strongRTLChars = [0x05BE, 0x05C0, 0x05C3, 0x05C6, 0x05D0, 0x05D1,
413 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8, 0x05D9, 0x05DA,
414 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF, 0x05E0, 0x05E1, 0x05E2, 0x05E3,
415 0x05E4, 0x05E5, 0x05E6, 0x05E7, 0x05E8, 0x05E9, 0x05EA, 0x05F0, 0x05F1,
416 0x05F2, 0x05F3, 0x05F4, 0x0608, 0x060B, 0x060D, 0x061B, 0x061E, 0x061F,
417 0x0620, 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627, 0x0628,
418 0x0629, 0x062A, 0x062B, 0x062C, 0x062D, 0x062E, 0x062F, 0x0630, 0x0631,
419 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x0637, 0x0638, 0x0639, 0x063A,
420 0x063B, 0x063C, 0x063D, 0x063E, 0x063F, 0x0640, 0x0641, 0x0642, 0x0643,
421 0x0644, 0x0645, 0x0646, 0x0647, 0x0648, 0x0649, 0x064A, 0x066D, 0x066E,
422 0x066F, 0x0671, 0x0672, 0x0673, 0x0674, 0x0675, 0x0676, 0x0677, 0x0678,
423 0x0679, 0x067A, 0x067B, 0x067C, 0x067D, 0x067E, 0x067F, 0x0680, 0x0681,
424 0x0682, 0x0683, 0x0684, 0x0685, 0x0686, 0x0687, 0x0688, 0x0689, 0x068A,
425 0x068B, 0x068C, 0x068D, 0x068E, 0x068F, 0x0690, 0x0691, 0x0692, 0x0693,
426 0x0694, 0x0695, 0x0696, 0x0697, 0x0698, 0x0699, 0x069A, 0x069B, 0x069C,
427 0x069D, 0x069E, 0x069F, 0x06A0, 0x06A1, 0x06A2, 0x06A3, 0x06A4, 0x06A5,
428 0x06A6, 0x06A7, 0x06A8, 0x06A9, 0x06AA, 0x06AB, 0x06AC, 0x06AD, 0x06AE,
429 0x06AF, 0x06B0, 0x06B1, 0x06B2, 0x06B3, 0x06B4, 0x06B5, 0x06B6, 0x06B7,
430 0x06B8, 0x06B9, 0x06BA, 0x06BB, 0x06BC, 0x06BD, 0x06BE, 0x06BF, 0x06C0,
431 0x06C1, 0x06C2, 0x06C3, 0x06C4, 0x06C5, 0x06C6, 0x06C7, 0x06C8, 0x06C9,
432 0x06CA, 0x06CB, 0x06CC, 0x06CD, 0x06CE, 0x06CF, 0x06D0, 0x06D1, 0x06D2,
433 0x06D3, 0x06D4, 0x06D5, 0x06E5, 0x06E6, 0x06EE, 0x06EF, 0x06FA, 0x06FB,
434 0x06FC, 0x06FD, 0x06FE, 0x06FF, 0x0700, 0x0701, 0x0702, 0x0703, 0x0704,
435 0x0705, 0x0706, 0x0707, 0x0708, 0x0709, 0x070A, 0x070B, 0x070C, 0x070D,
436 0x070F, 0x0710, 0x0712, 0x0713, 0x0714, 0x0715, 0x0716, 0x0717, 0x0718,
437 0x0719, 0x071A, 0x071B, 0x071C, 0x071D, 0x071E, 0x071F, 0x0720, 0x0721,
438 0x0722, 0x0723, 0x0724, 0x0725, 0x0726, 0x0727, 0x0728, 0x0729, 0x072A,
439 0x072B, 0x072C, 0x072D, 0x072E, 0x072F, 0x074D, 0x074E, 0x074F, 0x0750,
440 0x0751, 0x0752, 0x0753, 0x0754, 0x0755, 0x0756, 0x0757, 0x0758, 0x0759,
441 0x075A, 0x075B, 0x075C, 0x075D, 0x075E, 0x075F, 0x0760, 0x0761, 0x0762,
442 0x0763, 0x0764, 0x0765, 0x0766, 0x0767, 0x0768, 0x0769, 0x076A, 0x076B,
443 0x076C, 0x076D, 0x076E, 0x076F, 0x0770, 0x0771, 0x0772, 0x0773, 0x0774,
444 0x0775, 0x0776, 0x0777, 0x0778, 0x0779, 0x077A, 0x077B, 0x077C, 0x077D,
445 0x077E, 0x077F, 0x0780, 0x0781, 0x0782, 0x0783, 0x0784, 0x0785, 0x0786,
446 0x0787, 0x0788, 0x0789, 0x078A, 0x078B, 0x078C, 0x078D, 0x078E, 0x078F,
447 0x0790, 0x0791, 0x0792, 0x0793, 0x0794, 0x0795, 0x0796, 0x0797, 0x0798,
448 0x0799, 0x079A, 0x079B, 0x079C, 0x079D, 0x079E, 0x079F, 0x07A0, 0x07A1,
449 0x07A2, 0x07A3, 0x07A4, 0x07A5, 0x07B1, 0x07C0, 0x07C1, 0x07C2, 0x07C3,
450 0x07C4, 0x07C5, 0x07C6, 0x07C7, 0x07C8, 0x07C9, 0x07CA, 0x07CB, 0x07CC,
451 0x07CD, 0x07CE, 0x07CF, 0x07D0, 0x07D1, 0x07D2, 0x07D3, 0x07D4, 0x07D5,
452 0x07D6, 0x07D7, 0x07D8, 0x07D9, 0x07DA, 0x07DB, 0x07DC, 0x07DD, 0x07DE,
453 0x07DF, 0x07E0, 0x07E1, 0x07E2, 0x07E3, 0x07E4, 0x07E5, 0x07E6, 0x07E7,
454 0x07E8, 0x07E9, 0x07EA, 0x07F4, 0x07F5, 0x07FA, 0x0800, 0x0801, 0x0802,
455 0x0803, 0x0804, 0x0805, 0x0806, 0x0807, 0x0808, 0x0809, 0x080A, 0x080B,
456 0x080C, 0x080D, 0x080E, 0x080F, 0x0810, 0x0811, 0x0812, 0x0813, 0x0814,
457 0x0815, 0x081A, 0x0824, 0x0828, 0x0830, 0x0831, 0x0832, 0x0833, 0x0834,
458 0x0835, 0x0836, 0x0837, 0x0838, 0x0839, 0x083A, 0x083B, 0x083C, 0x083D,
459 0x083E, 0x0840, 0x0841, 0x0842, 0x0843, 0x0844, 0x0845, 0x0846, 0x0847,
460 0x0848, 0x0849, 0x084A, 0x084B, 0x084C, 0x084D, 0x084E, 0x084F, 0x0850,
461 0x0851, 0x0852, 0x0853, 0x0854, 0x0855, 0x0856, 0x0857, 0x0858, 0x085E,
462 0x08A0, 0x08A2, 0x08A3, 0x08A4, 0x08A5, 0x08A6, 0x08A7, 0x08A8, 0x08A9,
463 0x08AA, 0x08AB, 0x08AC, 0x200F, 0xFB1D, 0xFB1F, 0xFB20, 0xFB21, 0xFB22,
464 0xFB23, 0xFB24, 0xFB25, 0xFB26, 0xFB27, 0xFB28, 0xFB2A, 0xFB2B, 0xFB2C,
465 0xFB2D, 0xFB2E, 0xFB2F, 0xFB30, 0xFB31, 0xFB32, 0xFB33, 0xFB34, 0xFB35,
466 0xFB36, 0xFB38, 0xFB39, 0xFB3A, 0xFB3B, 0xFB3C, 0xFB3E, 0xFB40, 0xFB41,
467 0xFB43, 0xFB44, 0xFB46, 0xFB47, 0xFB48, 0xFB49, 0xFB4A, 0xFB4B, 0xFB4C,
468 0xFB4D, 0xFB4E, 0xFB4F, 0xFB50, 0xFB51, 0xFB52, 0xFB53, 0xFB54, 0xFB55,
469 0xFB56, 0xFB57, 0xFB58, 0xFB59, 0xFB5A, 0xFB5B, 0xFB5C, 0xFB5D, 0xFB5E,
470 0xFB5F, 0xFB60, 0xFB61, 0xFB62, 0xFB63, 0xFB64, 0xFB65, 0xFB66, 0xFB67,
471 0xFB68, 0xFB69, 0xFB6A, 0xFB6B, 0xFB6C, 0xFB6D, 0xFB6E, 0xFB6F, 0xFB70,
472 0xFB71, 0xFB72, 0xFB73, 0xFB74, 0xFB75, 0xFB76, 0xFB77, 0xFB78, 0xFB79,
473 0xFB7A, 0xFB7B, 0xFB7C, 0xFB7D, 0xFB7E, 0xFB7F, 0xFB80, 0xFB81, 0xFB82,
474 0xFB83, 0xFB84, 0xFB85, 0xFB86, 0xFB87, 0xFB88, 0xFB89, 0xFB8A, 0xFB8B,
475 0xFB8C, 0xFB8D, 0xFB8E, 0xFB8F, 0xFB90, 0xFB91, 0xFB92, 0xFB93, 0xFB94,
476 0xFB95, 0xFB96, 0xFB97, 0xFB98, 0xFB99, 0xFB9A, 0xFB9B, 0xFB9C, 0xFB9D,
477 0xFB9E, 0xFB9F, 0xFBA0, 0xFBA1, 0xFBA2, 0xFBA3, 0xFBA4, 0xFBA5, 0xFBA6,
478 0xFBA7, 0xFBA8, 0xFBA9, 0xFBAA, 0xFBAB, 0xFBAC, 0xFBAD, 0xFBAE, 0xFBAF,
479 0xFBB0, 0xFBB1, 0xFBB2, 0xFBB3, 0xFBB4, 0xFBB5, 0xFBB6, 0xFBB7, 0xFBB8,
480 0xFBB9, 0xFBBA, 0xFBBB, 0xFBBC, 0xFBBD, 0xFBBE, 0xFBBF, 0xFBC0, 0xFBC1,
481 0xFBD3, 0xFBD4, 0xFBD5, 0xFBD6, 0xFBD7, 0xFBD8, 0xFBD9, 0xFBDA, 0xFBDB,
482 0xFBDC, 0xFBDD, 0xFBDE, 0xFBDF, 0xFBE0, 0xFBE1, 0xFBE2, 0xFBE3, 0xFBE4,
483 0xFBE5, 0xFBE6, 0xFBE7, 0xFBE8, 0xFBE9, 0xFBEA, 0xFBEB, 0xFBEC, 0xFBED,
484 0xFBEE, 0xFBEF, 0xFBF0, 0xFBF1, 0xFBF2, 0xFBF3, 0xFBF4, 0xFBF5, 0xFBF6,
485 0xFBF7, 0xFBF8, 0xFBF9, 0xFBFA, 0xFBFB, 0xFBFC, 0xFBFD, 0xFBFE, 0xFBFF,
486 0xFC00, 0xFC01, 0xFC02, 0xFC03, 0xFC04, 0xFC05, 0xFC06, 0xFC07, 0xFC08,
487 0xFC09, 0xFC0A, 0xFC0B, 0xFC0C, 0xFC0D, 0xFC0E, 0xFC0F, 0xFC10, 0xFC11,
488 0xFC12, 0xFC13, 0xFC14, 0xFC15, 0xFC16, 0xFC17, 0xFC18, 0xFC19, 0xFC1A,
489 0xFC1B, 0xFC1C, 0xFC1D, 0xFC1E, 0xFC1F, 0xFC20, 0xFC21, 0xFC22, 0xFC23,
490 0xFC24, 0xFC25, 0xFC26, 0xFC27, 0xFC28, 0xFC29, 0xFC2A, 0xFC2B, 0xFC2C,
491 0xFC2D, 0xFC2E, 0xFC2F, 0xFC30, 0xFC31, 0xFC32, 0xFC33, 0xFC34, 0xFC35,
492 0xFC36, 0xFC37, 0xFC38, 0xFC39, 0xFC3A, 0xFC3B, 0xFC3C, 0xFC3D, 0xFC3E,
493 0xFC3F, 0xFC40, 0xFC41, 0xFC42, 0xFC43, 0xFC44, 0xFC45, 0xFC46, 0xFC47,
494 0xFC48, 0xFC49, 0xFC4A, 0xFC4B, 0xFC4C, 0xFC4D, 0xFC4E, 0xFC4F, 0xFC50,
495 0xFC51, 0xFC52, 0xFC53, 0xFC54, 0xFC55, 0xFC56, 0xFC57, 0xFC58, 0xFC59,
496 0xFC5A, 0xFC5B, 0xFC5C, 0xFC5D, 0xFC5E, 0xFC5F, 0xFC60, 0xFC61, 0xFC62,
497 0xFC63, 0xFC64, 0xFC65, 0xFC66, 0xFC67, 0xFC68, 0xFC69, 0xFC6A, 0xFC6B,
498 0xFC6C, 0xFC6D, 0xFC6E, 0xFC6F, 0xFC70, 0xFC71, 0xFC72, 0xFC73, 0xFC74,
499 0xFC75, 0xFC76, 0xFC77, 0xFC78, 0xFC79, 0xFC7A, 0xFC7B, 0xFC7C, 0xFC7D,
500 0xFC7E, 0xFC7F, 0xFC80, 0xFC81, 0xFC82, 0xFC83, 0xFC84, 0xFC85, 0xFC86,
501 0xFC87, 0xFC88, 0xFC89, 0xFC8A, 0xFC8B, 0xFC8C, 0xFC8D, 0xFC8E, 0xFC8F,
502 0xFC90, 0xFC91, 0xFC92, 0xFC93, 0xFC94, 0xFC95, 0xFC96, 0xFC97, 0xFC98,
503 0xFC99, 0xFC9A, 0xFC9B, 0xFC9C, 0xFC9D, 0xFC9E, 0xFC9F, 0xFCA0, 0xFCA1,
504 0xFCA2, 0xFCA3, 0xFCA4, 0xFCA5, 0xFCA6, 0xFCA7, 0xFCA8, 0xFCA9, 0xFCAA,
505 0xFCAB, 0xFCAC, 0xFCAD, 0xFCAE, 0xFCAF, 0xFCB0, 0xFCB1, 0xFCB2, 0xFCB3,
506 0xFCB4, 0xFCB5, 0xFCB6, 0xFCB7, 0xFCB8, 0xFCB9, 0xFCBA, 0xFCBB, 0xFCBC,
507 0xFCBD, 0xFCBE, 0xFCBF, 0xFCC0, 0xFCC1, 0xFCC2, 0xFCC3, 0xFCC4, 0xFCC5,
508 0xFCC6, 0xFCC7, 0xFCC8, 0xFCC9, 0xFCCA, 0xFCCB, 0xFCCC, 0xFCCD, 0xFCCE,
509 0xFCCF, 0xFCD0, 0xFCD1, 0xFCD2, 0xFCD3, 0xFCD4, 0xFCD5, 0xFCD6, 0xFCD7,
510 0xFCD8, 0xFCD9, 0xFCDA, 0xFCDB, 0xFCDC, 0xFCDD, 0xFCDE, 0xFCDF, 0xFCE0,
511 0xFCE1, 0xFCE2, 0xFCE3, 0xFCE4, 0xFCE5, 0xFCE6, 0xFCE7, 0xFCE8, 0xFCE9,
512 0xFCEA, 0xFCEB, 0xFCEC, 0xFCED, 0xFCEE, 0xFCEF, 0xFCF0, 0xFCF1, 0xFCF2,
513 0xFCF3, 0xFCF4, 0xFCF5, 0xFCF6, 0xFCF7, 0xFCF8, 0xFCF9, 0xFCFA, 0xFCFB,
514 0xFCFC, 0xFCFD, 0xFCFE, 0xFCFF, 0xFD00, 0xFD01, 0xFD02, 0xFD03, 0xFD04,
515 0xFD05, 0xFD06, 0xFD07, 0xFD08, 0xFD09, 0xFD0A, 0xFD0B, 0xFD0C, 0xFD0D,
516 0xFD0E, 0xFD0F, 0xFD10, 0xFD11, 0xFD12, 0xFD13, 0xFD14, 0xFD15, 0xFD16,
517 0xFD17, 0xFD18, 0xFD19, 0xFD1A, 0xFD1B, 0xFD1C, 0xFD1D, 0xFD1E, 0xFD1F,
518 0xFD20, 0xFD21, 0xFD22, 0xFD23, 0xFD24, 0xFD25, 0xFD26, 0xFD27, 0xFD28,
519 0xFD29, 0xFD2A, 0xFD2B, 0xFD2C, 0xFD2D, 0xFD2E, 0xFD2F, 0xFD30, 0xFD31,
520 0xFD32, 0xFD33, 0xFD34, 0xFD35, 0xFD36, 0xFD37, 0xFD38, 0xFD39, 0xFD3A,
521 0xFD3B, 0xFD3C, 0xFD3D, 0xFD50, 0xFD51, 0xFD52, 0xFD53, 0xFD54, 0xFD55,
522 0xFD56, 0xFD57, 0xFD58, 0xFD59, 0xFD5A, 0xFD5B, 0xFD5C, 0xFD5D, 0xFD5E,
523 0xFD5F, 0xFD60, 0xFD61, 0xFD62, 0xFD63, 0xFD64, 0xFD65, 0xFD66, 0xFD67,
524 0xFD68, 0xFD69, 0xFD6A, 0xFD6B, 0xFD6C, 0xFD6D, 0xFD6E, 0xFD6F, 0xFD70,
525 0xFD71, 0xFD72, 0xFD73, 0xFD74, 0xFD75, 0xFD76, 0xFD77, 0xFD78, 0xFD79,
526 0xFD7A, 0xFD7B, 0xFD7C, 0xFD7D, 0xFD7E, 0xFD7F, 0xFD80, 0xFD81, 0xFD82,
527 0xFD83, 0xFD84, 0xFD85, 0xFD86, 0xFD87, 0xFD88, 0xFD89, 0xFD8A, 0xFD8B,
528 0xFD8C, 0xFD8D, 0xFD8E, 0xFD8F, 0xFD92, 0xFD93, 0xFD94, 0xFD95, 0xFD96,
529 0xFD97, 0xFD98, 0xFD99, 0xFD9A, 0xFD9B, 0xFD9C, 0xFD9D, 0xFD9E, 0xFD9F,
530 0xFDA0, 0xFDA1, 0xFDA2, 0xFDA3, 0xFDA4, 0xFDA5, 0xFDA6, 0xFDA7, 0xFDA8,
531 0xFDA9, 0xFDAA, 0xFDAB, 0xFDAC, 0xFDAD, 0xFDAE, 0xFDAF, 0xFDB0, 0xFDB1,
532 0xFDB2, 0xFDB3, 0xFDB4, 0xFDB5, 0xFDB6, 0xFDB7, 0xFDB8, 0xFDB9, 0xFDBA,
533 0xFDBB, 0xFDBC, 0xFDBD, 0xFDBE, 0xFDBF, 0xFDC0, 0xFDC1, 0xFDC2, 0xFDC3,
534 0xFDC4, 0xFDC5, 0xFDC6, 0xFDC7, 0xFDF0, 0xFDF1, 0xFDF2, 0xFDF3, 0xFDF4,
535 0xFDF5, 0xFDF6, 0xFDF7, 0xFDF8, 0xFDF9, 0xFDFA, 0xFDFB, 0xFDFC, 0xFE70,
536 0xFE71, 0xFE72, 0xFE73, 0xFE74, 0xFE76, 0xFE77, 0xFE78, 0xFE79, 0xFE7A,
537 0xFE7B, 0xFE7C, 0xFE7D, 0xFE7E, 0xFE7F, 0xFE80, 0xFE81, 0xFE82, 0xFE83,
538 0xFE84, 0xFE85, 0xFE86, 0xFE87, 0xFE88, 0xFE89, 0xFE8A, 0xFE8B, 0xFE8C,
539 0xFE8D, 0xFE8E, 0xFE8F, 0xFE90, 0xFE91, 0xFE92, 0xFE93, 0xFE94, 0xFE95,
540 0xFE96, 0xFE97, 0xFE98, 0xFE99, 0xFE9A, 0xFE9B, 0xFE9C, 0xFE9D, 0xFE9E,
541 0xFE9F, 0xFEA0, 0xFEA1, 0xFEA2, 0xFEA3, 0xFEA4, 0xFEA5, 0xFEA6, 0xFEA7,
542 0xFEA8, 0xFEA9, 0xFEAA, 0xFEAB, 0xFEAC, 0xFEAD, 0xFEAE, 0xFEAF, 0xFEB0,
543 0xFEB1, 0xFEB2, 0xFEB3, 0xFEB4, 0xFEB5, 0xFEB6, 0xFEB7, 0xFEB8, 0xFEB9,
544 0xFEBA, 0xFEBB, 0xFEBC, 0xFEBD, 0xFEBE, 0xFEBF, 0xFEC0, 0xFEC1, 0xFEC2,
545 0xFEC3, 0xFEC4, 0xFEC5, 0xFEC6, 0xFEC7, 0xFEC8, 0xFEC9, 0xFECA, 0xFECB,
546 0xFECC, 0xFECD, 0xFECE, 0xFECF, 0xFED0, 0xFED1, 0xFED2, 0xFED3, 0xFED4,
547 0xFED5, 0xFED6, 0xFED7, 0xFED8, 0xFED9, 0xFEDA, 0xFEDB, 0xFEDC, 0xFEDD,
548 0xFEDE, 0xFEDF, 0xFEE0, 0xFEE1, 0xFEE2, 0xFEE3, 0xFEE4, 0xFEE5, 0xFEE6,
549 0xFEE7, 0xFEE8, 0xFEE9, 0xFEEA, 0xFEEB, 0xFEEC, 0xFEED, 0xFEEE, 0xFEEF,
550 0xFEF0, 0xFEF1, 0xFEF2, 0xFEF3, 0xFEF4, 0xFEF5, 0xFEF6, 0xFEF7, 0xFEF8,
551 0xFEF9, 0xFEFA, 0xFEFB, 0xFEFC, 0x10800, 0x10801, 0x10802, 0x10803,
552 0x10804, 0x10805, 0x10808, 0x1080A, 0x1080B, 0x1080C, 0x1080D, 0x1080E,
553 0x1080F, 0x10810, 0x10811, 0x10812, 0x10813, 0x10814, 0x10815, 0x10816,
554 0x10817, 0x10818, 0x10819, 0x1081A, 0x1081B, 0x1081C, 0x1081D, 0x1081E,
555 0x1081F, 0x10820, 0x10821, 0x10822, 0x10823, 0x10824, 0x10825, 0x10826,
556 0x10827, 0x10828, 0x10829, 0x1082A, 0x1082B, 0x1082C, 0x1082D, 0x1082E,
557 0x1082F, 0x10830, 0x10831, 0x10832, 0x10833, 0x10834, 0x10835, 0x10837,
558 0x10838, 0x1083C, 0x1083F, 0x10840, 0x10841, 0x10842, 0x10843, 0x10844,
559 0x10845, 0x10846, 0x10847, 0x10848, 0x10849, 0x1084A, 0x1084B, 0x1084C,
560 0x1084D, 0x1084E, 0x1084F, 0x10850, 0x10851, 0x10852, 0x10853, 0x10854,
561 0x10855, 0x10857, 0x10858, 0x10859, 0x1085A, 0x1085B, 0x1085C, 0x1085D,
562 0x1085E, 0x1085F, 0x10900, 0x10901, 0x10902, 0x10903, 0x10904, 0x10905,
563 0x10906, 0x10907, 0x10908, 0x10909, 0x1090A, 0x1090B, 0x1090C, 0x1090D,
564 0x1090E, 0x1090F, 0x10910, 0x10911, 0x10912, 0x10913, 0x10914, 0x10915,
565 0x10916, 0x10917, 0x10918, 0x10919, 0x1091A, 0x1091B, 0x10920, 0x10921,
566 0x10922, 0x10923, 0x10924, 0x10925, 0x10926, 0x10927, 0x10928, 0x10929,
567 0x1092A, 0x1092B, 0x1092C, 0x1092D, 0x1092E, 0x1092F, 0x10930, 0x10931,
568 0x10932, 0x10933, 0x10934, 0x10935, 0x10936, 0x10937, 0x10938, 0x10939,
569 0x1093F, 0x10980, 0x10981, 0x10982, 0x10983, 0x10984, 0x10985, 0x10986,
570 0x10987, 0x10988, 0x10989, 0x1098A, 0x1098B, 0x1098C, 0x1098D, 0x1098E,
571 0x1098F, 0x10990, 0x10991, 0x10992, 0x10993, 0x10994, 0x10995, 0x10996,
572 0x10997, 0x10998, 0x10999, 0x1099A, 0x1099B, 0x1099C, 0x1099D, 0x1099E,
573 0x1099F, 0x109A0, 0x109A1, 0x109A2, 0x109A3, 0x109A4, 0x109A5, 0x109A6,
574 0x109A7, 0x109A8, 0x109A9, 0x109AA, 0x109AB, 0x109AC, 0x109AD, 0x109AE,
575 0x109AF, 0x109B0, 0x109B1, 0x109B2, 0x109B3, 0x109B4, 0x109B5, 0x109B6,
576 0x109B7, 0x109BE, 0x109BF, 0x10A00, 0x10A10, 0x10A11, 0x10A12, 0x10A13,
577 0x10A15, 0x10A16, 0x10A17, 0x10A19, 0x10A1A, 0x10A1B, 0x10A1C, 0x10A1D,
578 0x10A1E, 0x10A1F, 0x10A20, 0x10A21, 0x10A22, 0x10A23, 0x10A24, 0x10A25,
579 0x10A26, 0x10A27, 0x10A28, 0x10A29, 0x10A2A, 0x10A2B, 0x10A2C, 0x10A2D,
580 0x10A2E, 0x10A2F, 0x10A30, 0x10A31, 0x10A32, 0x10A33, 0x10A40, 0x10A41,
581 0x10A42, 0x10A43, 0x10A44, 0x10A45, 0x10A46, 0x10A47, 0x10A50, 0x10A51,
582 0x10A52, 0x10A53, 0x10A54, 0x10A55, 0x10A56, 0x10A57, 0x10A58, 0x10A60,
583 0x10A61, 0x10A62, 0x10A63, 0x10A64, 0x10A65, 0x10A66, 0x10A67, 0x10A68,
584 0x10A69, 0x10A6A, 0x10A6B, 0x10A6C, 0x10A6D, 0x10A6E, 0x10A6F, 0x10A70,
585 0x10A71, 0x10A72, 0x10A73, 0x10A74, 0x10A75, 0x10A76, 0x10A77, 0x10A78,
586 0x10A79, 0x10A7A, 0x10A7B, 0x10A7C, 0x10A7D, 0x10A7E, 0x10A7F, 0x10B00,
587 0x10B01, 0x10B02, 0x10B03, 0x10B04, 0x10B05, 0x10B06, 0x10B07, 0x10B08,
588 0x10B09, 0x10B0A, 0x10B0B, 0x10B0C, 0x10B0D, 0x10B0E, 0x10B0F, 0x10B10,
589 0x10B11, 0x10B12, 0x10B13, 0x10B14, 0x10B15, 0x10B16, 0x10B17, 0x10B18,
590 0x10B19, 0x10B1A, 0x10B1B, 0x10B1C, 0x10B1D, 0x10B1E, 0x10B1F, 0x10B20,
591 0x10B21, 0x10B22, 0x10B23, 0x10B24, 0x10B25, 0x10B26, 0x10B27, 0x10B28,
592 0x10B29, 0x10B2A, 0x10B2B, 0x10B2C, 0x10B2D, 0x10B2E, 0x10B2F, 0x10B30,
593 0x10B31, 0x10B32, 0x10B33, 0x10B34, 0x10B35, 0x10B40, 0x10B41, 0x10B42,
594 0x10B43, 0x10B44, 0x10B45, 0x10B46, 0x10B47, 0x10B48, 0x10B49, 0x10B4A,
595 0x10B4B, 0x10B4C, 0x10B4D, 0x10B4E, 0x10B4F, 0x10B50, 0x10B51, 0x10B52,
596 0x10B53, 0x10B54, 0x10B55, 0x10B58, 0x10B59, 0x10B5A, 0x10B5B, 0x10B5C,
597 0x10B5D, 0x10B5E, 0x10B5F, 0x10B60, 0x10B61, 0x10B62, 0x10B63, 0x10B64,
598 0x10B65, 0x10B66, 0x10B67, 0x10B68, 0x10B69, 0x10B6A, 0x10B6B, 0x10B6C,
599 0x10B6D, 0x10B6E, 0x10B6F, 0x10B70, 0x10B71, 0x10B72, 0x10B78, 0x10B79,
600 0x10B7A, 0x10B7B, 0x10B7C, 0x10B7D, 0x10B7E, 0x10B7F, 0x10C00, 0x10C01,
601 0x10C02, 0x10C03, 0x10C04, 0x10C05, 0x10C06, 0x10C07, 0x10C08, 0x10C09,
602 0x10C0A, 0x10C0B, 0x10C0C, 0x10C0D, 0x10C0E, 0x10C0F, 0x10C10, 0x10C11,
603 0x10C12, 0x10C13, 0x10C14, 0x10C15, 0x10C16, 0x10C17, 0x10C18, 0x10C19,
604 0x10C1A, 0x10C1B, 0x10C1C, 0x10C1D, 0x10C1E, 0x10C1F, 0x10C20, 0x10C21,
605 0x10C22, 0x10C23, 0x10C24, 0x10C25, 0x10C26, 0x10C27, 0x10C28, 0x10C29,
606 0x10C2A, 0x10C2B, 0x10C2C, 0x10C2D, 0x10C2E, 0x10C2F, 0x10C30, 0x10C31,
607 0x10C32, 0x10C33, 0x10C34, 0x10C35, 0x10C36, 0x10C37, 0x10C38, 0x10C39,
608 0x10C3A, 0x10C3B, 0x10C3C, 0x10C3D, 0x10C3E, 0x10C3F, 0x10C40, 0x10C41,
609 0x10C42, 0x10C43, 0x10C44, 0x10C45, 0x10C46, 0x10C47, 0x10C48, 0x1EE00,
610 0x1EE01, 0x1EE02, 0x1EE03, 0x1EE05, 0x1EE06, 0x1EE07, 0x1EE08, 0x1EE09,
611 0x1EE0A, 0x1EE0B, 0x1EE0C, 0x1EE0D, 0x1EE0E, 0x1EE0F, 0x1EE10, 0x1EE11,
612 0x1EE12, 0x1EE13, 0x1EE14, 0x1EE15, 0x1EE16, 0x1EE17, 0x1EE18, 0x1EE19,
613 0x1EE1A, 0x1EE1B, 0x1EE1C, 0x1EE1D, 0x1EE1E, 0x1EE1F, 0x1EE21, 0x1EE22,
614 0x1EE24, 0x1EE27, 0x1EE29, 0x1EE2A, 0x1EE2B, 0x1EE2C, 0x1EE2D, 0x1EE2E,
615 0x1EE2F, 0x1EE30, 0x1EE31, 0x1EE32, 0x1EE34, 0x1EE35, 0x1EE36, 0x1EE37,
616 0x1EE39, 0x1EE3B, 0x1EE42, 0x1EE47, 0x1EE49, 0x1EE4B, 0x1EE4D, 0x1EE4E,
617 0x1EE4F, 0x1EE51, 0x1EE52, 0x1EE54, 0x1EE57, 0x1EE59, 0x1EE5B, 0x1EE5D,
618 0x1EE5F, 0x1EE61, 0x1EE62, 0x1EE64, 0x1EE67, 0x1EE68, 0x1EE69, 0x1EE6A,
619 0x1EE6C, 0x1EE6D, 0x1EE6E, 0x1EE6F, 0x1EE70, 0x1EE71, 0x1EE72, 0x1EE74,
620 0x1EE75, 0x1EE76, 0x1EE77, 0x1EE79, 0x1EE7A, 0x1EE7B, 0x1EE7C, 0x1EE7E,
621 0x1EE80, 0x1EE81, 0x1EE82, 0x1EE83, 0x1EE84, 0x1EE85, 0x1EE86, 0x1EE87,
622 0x1EE88, 0x1EE89, 0x1EE8B, 0x1EE8C, 0x1EE8D, 0x1EE8E, 0x1EE8F, 0x1EE90,
623 0x1EE91, 0x1EE92, 0x1EE93, 0x1EE94, 0x1EE95, 0x1EE96, 0x1EE97, 0x1EE98,
624 0x1EE99, 0x1EE9A, 0x1EE9B, 0x1EEA1, 0x1EEA2, 0x1EEA3, 0x1EEA5, 0x1EEA6,
625 0x1EEA7, 0x1EEA8, 0x1EEA9, 0x1EEAB, 0x1EEAC, 0x1EEAD, 0x1EEAE, 0x1EEAF,
626 0x1EEB0, 0x1EEB1, 0x1EEB2, 0x1EEB3, 0x1EEB4, 0x1EEB5, 0x1EEB6, 0x1EEB7,
627 0x1EEB8, 0x1EEB9, 0x1EEBA, 0x1EEBB, 0x10FFFD];
629 function determineBidi(cueDiv) {
630 var nodeStack = [],
631 text = "",
632 charCode;
634 if (!cueDiv || !cueDiv.childNodes) {
635 return "ltr";
636 }
638 function pushNodes(nodeStack, node) {
639 for (var i = node.childNodes.length - 1; i >= 0; i--) {
640 nodeStack.push(node.childNodes[i]);
641 }
642 }
644 function nextTextNode(nodeStack) {
645 if (!nodeStack || !nodeStack.length) {
646 return null;
647 }
649 var node = nodeStack.pop(),
650 text = node.textContent || node.innerText;
651 if (text) {
652 // TODO: This should match all unicode type B characters (paragraph
653 // separator characters). See issue #115.
654 var m = text.match(/^.*(\n|\r)/);
655 if (m) {
656 nodeStack.length = 0;
657 return m[0];
658 }
659 return text;
660 }
661 if (node.tagName === "ruby") {
662 return nextTextNode(nodeStack);
663 }
664 if (node.childNodes) {
665 pushNodes(nodeStack, node);
666 return nextTextNode(nodeStack);
667 }
668 }
670 pushNodes(nodeStack, cueDiv);
671 while ((text = nextTextNode(nodeStack))) {
672 for (var i = 0; i < text.length; i++) {
673 charCode = text.charCodeAt(i);
674 for (var j = 0; j < strongRTLChars.length; j++) {
675 if (strongRTLChars[j] === charCode) {
676 return "rtl";
677 }
678 }
679 }
680 }
681 return "ltr";
682 }
684 function computeLinePos(cue) {
685 if (typeof cue.line === "number" &&
686 (cue.snapToLines || (cue.line >= 0 && cue.line <= 100))) {
687 return cue.line;
688 }
689 if (!cue.track || !cue.track.textTrackList ||
690 !cue.track.textTrackList.mediaElement) {
691 return -1;
692 }
693 var track = cue.track,
694 trackList = track.textTrackList,
695 count = 0;
696 for (var i = 0; i < trackList.length && trackList[i] !== track; i++) {
697 if (trackList[i].mode === "showing") {
698 count++;
699 }
700 }
701 return ++count * -1;
702 }
704 function StyleBox() {
705 }
707 // Apply styles to a div. If there is no div passed then it defaults to the
708 // div on 'this'.
709 StyleBox.prototype.applyStyles = function(styles, div) {
710 div = div || this.div;
711 for (var prop in styles) {
712 if (styles.hasOwnProperty(prop)) {
713 div.style[prop] = styles[prop];
714 }
715 }
716 };
718 StyleBox.prototype.formatStyle = function(val, unit) {
719 return val === 0 ? 0 : val + unit;
720 };
722 // Constructs the computed display state of the cue (a div). Places the div
723 // into the overlay which should be a block level element (usually a div).
724 function CueStyleBox(window, cue, styleOptions) {
725 StyleBox.call(this);
726 this.cue = cue;
728 // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
729 // have inline positioning and will function as the cue background box.
730 this.cueDiv = parseContent(window, cue.text);
731 this.applyStyles({
732 color: "rgba(255, 255, 255, 1)",
733 backgroundColor: "rgba(0, 0, 0, 0.8)",
734 position: "relative",
735 left: 0,
736 right: 0,
737 top: 0,
738 bottom: 0,
739 display: "inline"
740 }, this.cueDiv);
742 // Create an absolutely positioned div that will be used to position the cue
743 // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS
744 // mirrors of them except "middle" which is "center" in CSS.
745 this.div = window.document.createElement("div");
746 this.applyStyles({
747 textAlign: cue.align === "middle" ? "center" : cue.align,
748 direction: determineBidi(this.cueDiv),
749 writingMode: cue.vertical === "" ? "horizontal-tb"
750 : cue.vertical === "lr" ? "vertical-lr"
751 : "vertical-rl",
752 unicodeBidi: "plaintext",
753 font: styleOptions.font,
754 whiteSpace: "pre-line",
755 position: "absolute"
756 });
758 this.div.appendChild(this.cueDiv);
760 // Calculate the distance from the reference edge of the viewport to the text
761 // position of the cue box. The reference edge will be resolved later when
762 // the box orientation styles are applied.
763 var textPos = 0;
764 switch (cue.positionAlign) {
765 case "start":
766 textPos = cue.position;
767 break;
768 case "middle":
769 textPos = cue.position - (cue.size / 2);
770 break;
771 case "end":
772 textPos = cue.position - cue.size;
773 break;
774 }
776 // Horizontal box orientation; textPos is the distance from the left edge of the
777 // area to the left edge of the box and cue.size is the distance extending to
778 // the right from there.
779 if (cue.vertical === "") {
780 this.applyStyles({
781 left: this.formatStyle(textPos, "%"),
782 width: this.formatStyle(cue.size, "%"),
783 });
784 // Vertical box orientation; textPos is the distance from the top edge of the
785 // area to the top edge of the box and cue.size is the height extending
786 // downwards from there.
787 } else {
788 this.applyStyles({
789 top: this.formatStyle(textPos, "%"),
790 height: this.formatStyle(cue.size, "%")
791 });
792 }
794 this.move = function(box) {
795 this.applyStyles({
796 top: this.formatStyle(box.top, "px"),
797 bottom: this.formatStyle(box.bottom, "px"),
798 left: this.formatStyle(box.left, "px"),
799 right: this.formatStyle(box.right, "px"),
800 height: this.formatStyle(box.height, "px"),
801 width: this.formatStyle(box.width, "px"),
802 });
803 };
804 }
805 CueStyleBox.prototype = _objCreate(StyleBox.prototype);
806 CueStyleBox.prototype.constructor = CueStyleBox;
808 // Represents the co-ordinates of an Element in a way that we can easily
809 // compute things with such as if it overlaps or intersects with another Element.
810 // Can initialize it with either a StyleBox or another BoxPosition.
811 function BoxPosition(obj) {
812 // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
813 // was passed in and we need to copy the results of 'getBoundingClientRect'
814 // as the object returned is readonly. All co-ordinate values are in reference
815 // to the viewport origin (top left).
816 var lh;
817 if (obj.div) {
818 var rects = (rects = obj.div.childNodes) && (rects = rects[0]) &&
819 rects.getClientRects && rects.getClientRects();
820 obj = obj.div.getBoundingClientRect();
821 // In certain cases the outter div will be slightly larger then the sum of
822 // the inner div's lines. This could be due to bold text, etc, on some platforms.
823 // In this case we should get the average line height and use that. This will
824 // result in the desired behaviour.
825 lh = rects ? Math.max((rects[0] && rects[0].height) || 0, obj.height / rects.length)
826 : 0;
827 }
828 this.left = obj.left;
829 this.right = obj.right;
830 this.top = obj.top;
831 this.height = obj.height;
832 this.bottom = obj.bottom;
833 this.width = obj.width;
834 this.lineHeight = lh !== undefined ? lh : obj.lineHeight;
835 }
837 // Move the box along a particular axis. Optionally pass in an amount to move
838 // the box. If no amount is passed then the default is the line height of the
839 // box.
840 BoxPosition.prototype.move = function(axis, toMove) {
841 toMove = toMove !== undefined ? toMove : this.lineHeight;
842 switch (axis) {
843 case "+x":
844 this.left += toMove;
845 this.right += toMove;
846 break;
847 case "-x":
848 this.left -= toMove;
849 this.right -= toMove;
850 break;
851 case "+y":
852 this.top += toMove;
853 this.bottom += toMove;
854 break;
855 case "-y":
856 this.top -= toMove;
857 this.bottom -= toMove;
858 break;
859 }
860 };
862 // Check if this box overlaps another box, b2.
863 BoxPosition.prototype.overlaps = function(b2) {
864 return this.left < b2.right &&
865 this.right > b2.left &&
866 this.top < b2.bottom &&
867 this.bottom > b2.top;
868 };
870 // Check if this box overlaps any other boxes in boxes.
871 BoxPosition.prototype.overlapsAny = function(boxes) {
872 for (var i = 0; i < boxes.length; i++) {
873 if (this.overlaps(boxes[i])) {
874 return true;
875 }
876 }
877 return false;
878 };
880 // Check if this box is within another box.
881 BoxPosition.prototype.within = function(container) {
882 return this.top >= container.top &&
883 this.bottom <= container.bottom &&
884 this.left >= container.left &&
885 this.right <= container.right;
886 };
888 // Check if this box is entirely within the container or it is overlapping
889 // on the edge opposite of the axis direction passed. For example, if "+x" is
890 // passed and the box is overlapping on the left edge of the container, then
891 // return true.
892 BoxPosition.prototype.overlapsOppositeAxis = function(container, axis) {
893 switch (axis) {
894 case "+x":
895 return this.left < container.left;
896 case "-x":
897 return this.right > container.right;
898 case "+y":
899 return this.top < container.top;
900 case "-y":
901 return this.bottom > container.bottom;
902 }
903 };
905 // Find the percentage of the area that this box is overlapping with another
906 // box.
907 BoxPosition.prototype.intersectPercentage = function(b2) {
908 var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
909 y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
910 intersectArea = x * y;
911 return intersectArea / (this.height * this.width);
912 };
914 // Convert the positions from this box to CSS compatible positions using
915 // the reference container's positions. This has to be done because this
916 // box's positions are in reference to the viewport origin, whereas, CSS
917 // values are in referecne to their respective edges.
918 BoxPosition.prototype.toCSSCompatValues = function(reference) {
919 return {
920 top: this.top - reference.top,
921 bottom: reference.bottom - this.bottom,
922 left: this.left - reference.left,
923 right: reference.right - this.right,
924 height: this.height,
925 width: this.width
926 };
927 };
929 // Get an object that represents the box's position without anything extra.
930 // Can pass a StyleBox, HTMLElement, or another BoxPositon.
931 BoxPosition.getSimpleBoxPosition = function(obj) {
932 obj = obj.div ? obj.div.getBoundingClientRect() :
933 obj.tagName ? obj.getBoundingClientRect() : obj;
934 return {
935 left: obj.left,
936 right: obj.right,
937 top: obj.top,
938 height: obj.height,
939 bottom: obj.bottom,
940 width: obj.width
941 };
942 };
944 // Move a StyleBox to its specified, or next best, position. The containerBox
945 // is the box that contains the StyleBox, such as a div. boxPositions are
946 // a list of other boxes that the styleBox can't overlap with.
947 function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
949 // Find the best position for a cue box, b, on the video. The axis parameter
950 // is a list of axis, the order of which, it will move the box along. For example:
951 // Passing ["+x", "-x"] will move the box first along the x axis in the positive
952 // direction. If it doesn't find a good position for it there it will then move
953 // it along the x axis in the negative direction.
954 function findBestPosition(b, axis) {
955 var bestPosition,
956 specifiedPosition = new BoxPosition(b),
957 percentage = 1; // Highest possible so the first thing we get is better.
959 for (var i = 0; i < axis.length; i++) {
960 while (b.overlapsOppositeAxis(containerBox, axis[i]) ||
961 (b.within(containerBox) && b.overlapsAny(boxPositions))) {
962 b.move(axis[i]);
963 }
964 // We found a spot where we aren't overlapping anything. This is our
965 // best position.
966 if (b.within(containerBox)) {
967 return b;
968 }
969 var p = b.intersectPercentage(containerBox);
970 // If we're outside the container box less then we were on our last try
971 // then remember this position as the best position.
972 if (percentage > p) {
973 bestPosition = new BoxPosition(b);
974 percentage = p;
975 }
976 // Reset the box position to the specified position.
977 b = new BoxPosition(specifiedPosition);
978 }
979 return bestPosition || specifiedPosition;
980 }
982 var boxPosition = new BoxPosition(styleBox),
983 cue = styleBox.cue,
984 linePos = computeLinePos(cue),
985 axis = [];
987 // If we have a line number to align the cue to.
988 if (cue.snapToLines) {
989 var size;
990 switch (cue.vertical) {
991 case "":
992 axis = [ "+y", "-y" ];
993 size = "height";
994 break;
995 case "rl":
996 axis = [ "+x", "-x" ];
997 size = "width";
998 break;
999 case "lr":
1000 axis = [ "-x", "+x" ];
1001 size = "width";
1002 break;
1003 }
1005 var step = boxPosition.lineHeight,
1006 position = step * Math.round(linePos),
1007 maxPosition = containerBox[size] + step,
1008 initialAxis = axis[0];
1010 // If the specified intial position is greater then the max position then
1011 // clamp the box to the amount of steps it would take for the box to
1012 // reach the max position.
1013 if (Math.abs(position) > maxPosition) {
1014 position = position < 0 ? -1 : 1;
1015 position *= Math.ceil(maxPosition / step) * step;
1016 }
1018 // If computed line position returns negative then line numbers are
1019 // relative to the bottom of the video instead of the top. Therefore, we
1020 // need to increase our initial position by the length or width of the
1021 // video, depending on the writing direction, and reverse our axis directions.
1022 if (linePos < 0) {
1023 position += cue.vertical === "" ? containerBox.height : containerBox.width;
1024 axis = axis.reverse();
1025 }
1027 // Move the box to the specified position. This may not be its best
1028 // position.
1029 boxPosition.move(initialAxis, position);
1031 } else {
1032 // If we have a percentage line value for the cue.
1033 var calculatedPercentage = (boxPosition.lineHeight / containerBox.height) * 100;
1035 switch (cue.lineAlign) {
1036 case "middle":
1037 linePos -= (calculatedPercentage / 2);
1038 break;
1039 case "end":
1040 linePos -= calculatedPercentage;
1041 break;
1042 }
1044 // Apply initial line position to the cue box.
1045 switch (cue.vertical) {
1046 case "":
1047 styleBox.applyStyles({
1048 top: styleBox.formatStyle(linePos, "%")
1049 });
1050 break;
1051 case "rl":
1052 styleBox.applyStyles({
1053 left: styleBox.formatStyle(linePos, "%")
1054 });
1055 break;
1056 case "lr":
1057 styleBox.applyStyles({
1058 right: styleBox.formatStyle(linePos, "%")
1059 });
1060 break;
1061 }
1063 axis = [ "+y", "-x", "+x", "-y" ];
1065 // Get the box position again after we've applied the specified positioning
1066 // to it.
1067 boxPosition = new BoxPosition(styleBox);
1068 }
1070 var bestPosition = findBestPosition(boxPosition, axis);
1071 styleBox.move(bestPosition.toCSSCompatValues(containerBox));
1072 }
1074 function WebVTT() {
1075 // Nothing
1076 }
1078 // Helper to allow strings to be decoded instead of the default binary utf8 data.
1079 WebVTT.StringDecoder = function() {
1080 return {
1081 decode: function(data) {
1082 if (!data) {
1083 return "";
1084 }
1085 if (typeof data !== "string") {
1086 throw new Error("Error - expected string data.");
1087 }
1088 return decodeURIComponent(encodeURIComponent(data));
1089 }
1090 };
1091 };
1093 WebVTT.convertCueToDOMTree = function(window, cuetext) {
1094 if (!window || !cuetext) {
1095 return null;
1096 }
1097 return parseContent(window, cuetext);
1098 };
1100 var FONT_SIZE_PERCENT = 0.05;
1101 var FONT_STYLE = "sans-serif";
1102 var CUE_BACKGROUND_PADDING = "1.5%";
1104 // Runs the processing model over the cues and regions passed to it.
1105 // @param overlay A block level element (usually a div) that the computed cues
1106 // and regions will be placed into.
1107 WebVTT.processCues = function(window, cues, overlay) {
1108 if (!window || !cues || !overlay) {
1109 return null;
1110 }
1112 // Remove all previous children.
1113 while (overlay.firstChild) {
1114 overlay.removeChild(overlay.firstChild);
1115 }
1117 var paddedOverlay = window.document.createElement("div");
1118 paddedOverlay.style.position = "absolute";
1119 paddedOverlay.style.left = "0";
1120 paddedOverlay.style.right = "0";
1121 paddedOverlay.style.top = "0";
1122 paddedOverlay.style.bottom = "0";
1123 paddedOverlay.style.margin = CUE_BACKGROUND_PADDING;
1124 overlay.appendChild(paddedOverlay);
1126 // Determine if we need to compute the display states of the cues. This could
1127 // be the case if a cue's state has been changed since the last computation or
1128 // if it has not been computed yet.
1129 function shouldCompute(cues) {
1130 for (var i = 0; i < cues.length; i++) {
1131 if (cues[i].hasBeenReset || !cues[i].displayState) {
1132 return true;
1133 }
1134 }
1135 return false;
1136 }
1138 // We don't need to recompute the cues' display states. Just reuse them.
1139 if (!shouldCompute(cues)) {
1140 cues.forEach(function(cue) {
1141 paddedOverlay.appendChild(cue.displayState);
1142 });
1143 return;
1144 }
1146 var boxPositions = [],
1147 containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay),
1148 fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
1149 var styleOptions = {
1150 font: fontSize + "px " + FONT_STYLE
1151 };
1153 cues.forEach(function(cue) {
1154 // Compute the intial position and styles of the cue div.
1155 var styleBox = new CueStyleBox(window, cue, styleOptions);
1156 paddedOverlay.appendChild(styleBox.div);
1158 // Move the cue div to it's correct line position.
1159 moveBoxToLinePosition(window, styleBox, containerBox, boxPositions);
1161 // Remember the computed div so that we don't have to recompute it later
1162 // if we don't have too.
1163 cue.displayState = styleBox.div;
1165 boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
1166 });
1167 };
1169 WebVTT.Parser = function(window, decoder) {
1170 this.window = window;
1171 this.state = "INITIAL";
1172 this.buffer = "";
1173 this.decoder = decoder || new TextDecoder("utf8");
1174 this.regionList = [];
1175 };
1177 WebVTT.Parser.prototype = {
1178 // If the error is a ParsingError then report it to the consumer if
1179 // possible. If it's not a ParsingError then throw it like normal.
1180 reportOrThrowError: function(e) {
1181 if (e instanceof ParsingError) {
1182 this.onparsingerror && this.onparsingerror(e);
1183 } else {
1184 throw e;
1185 }
1186 },
1187 parse: function (data) {
1188 var self = this;
1190 // If there is no data then we won't decode it, but will just try to parse
1191 // whatever is in buffer already. This may occur in circumstances, for
1192 // example when flush() is called.
1193 if (data) {
1194 // Try to decode the data that we received.
1195 self.buffer += self.decoder.decode(data, {stream: true});
1196 }
1198 function collectNextLine() {
1199 var buffer = self.buffer;
1200 var pos = 0;
1201 while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
1202 ++pos;
1203 }
1204 var line = buffer.substr(0, pos);
1205 // Advance the buffer early in case we fail below.
1206 if (buffer[pos] === '\r') {
1207 ++pos;
1208 }
1209 if (buffer[pos] === '\n') {
1210 ++pos;
1211 }
1212 self.buffer = buffer.substr(pos);
1213 return line;
1214 }
1216 // 3.4 WebVTT region and WebVTT region settings syntax
1217 function parseRegion(input) {
1218 var settings = new Settings();
1220 parseOptions(input, function (k, v) {
1221 switch (k) {
1222 case "id":
1223 settings.set(k, v);
1224 break;
1225 case "width":
1226 settings.percent(k, v);
1227 break;
1228 case "lines":
1229 settings.integer(k, v);
1230 break;
1231 case "regionanchor":
1232 case "viewportanchor":
1233 var xy = v.split(',');
1234 if (xy.length !== 2) {
1235 break;
1236 }
1237 // We have to make sure both x and y parse, so use a temporary
1238 // settings object here.
1239 var anchor = new Settings();
1240 anchor.percent("x", xy[0]);
1241 anchor.percent("y", xy[1]);
1242 if (!anchor.has("x") || !anchor.has("y")) {
1243 break;
1244 }
1245 settings.set(k + "X", anchor.get("x"));
1246 settings.set(k + "Y", anchor.get("y"));
1247 break;
1248 case "scroll":
1249 settings.alt(k, v, ["up"]);
1250 break;
1251 }
1252 }, /=/, /\s/);
1254 // Create the region, using default values for any values that were not
1255 // specified.
1256 if (settings.has("id")) {
1257 var region = new self.window.VTTRegion();
1258 region.width = settings.get("width", 100);
1259 region.lines = settings.get("lines", 3);
1260 region.regionAnchorX = settings.get("regionanchorX", 0);
1261 region.regionAnchorY = settings.get("regionanchorY", 100);
1262 region.viewportAnchorX = settings.get("viewportanchorX", 0);
1263 region.viewportAnchorY = settings.get("viewportanchorY", 100);
1264 region.scroll = settings.get("scroll", "");
1265 // Register the region.
1266 self.onregion && self.onregion(region);
1267 // Remember the VTTRegion for later in case we parse any VTTCues that
1268 // reference it.
1269 self.regionList.push({
1270 id: settings.get("id"),
1271 region: region
1272 });
1273 }
1274 }
1276 // 3.2 WebVTT metadata header syntax
1277 function parseHeader(input) {
1278 parseOptions(input, function (k, v) {
1279 switch (k) {
1280 case "Region":
1281 // 3.3 WebVTT region metadata header syntax
1282 parseRegion(v);
1283 break;
1284 }
1285 }, /:/);
1286 }
1288 // 5.1 WebVTT file parsing.
1289 try {
1290 var line;
1291 if (self.state === "INITIAL") {
1292 // We can't start parsing until we have the first line.
1293 if (!/\r\n|\n/.test(self.buffer)) {
1294 return this;
1295 }
1297 line = collectNextLine();
1299 var m = line.match(/^WEBVTT([ \t].*)?$/);
1300 if (!m || !m[0]) {
1301 throw new ParsingError(ParsingError.Errors.BadSignature);
1302 }
1304 self.state = "HEADER";
1305 }
1307 while (self.buffer) {
1308 // We can't parse a line until we have the full line.
1309 if (!/\r\n|\n/.test(self.buffer)) {
1310 return this;
1311 }
1313 line = collectNextLine();
1315 switch (self.state) {
1316 case "HEADER":
1317 // 13-18 - Allow a header (metadata) under the WEBVTT line.
1318 if (/:/.test(line)) {
1319 parseHeader(line);
1320 } else if (!line) {
1321 // An empty line terminates the header and starts the body (cues).
1322 self.state = "ID";
1323 }
1324 continue;
1325 case "NOTE":
1326 // Ignore NOTE blocks.
1327 if (!line) {
1328 self.state = "ID";
1329 }
1330 continue;
1331 case "ID":
1332 // Check for the start of NOTE blocks.
1333 if (/^NOTE($|[ \t])/.test(line)) {
1334 self.state = "NOTE";
1335 break;
1336 }
1337 // 19-29 - Allow any number of line terminators, then initialize new cue values.
1338 if (!line) {
1339 continue;
1340 }
1341 self.cue = new self.window.VTTCue(0, 0, "");
1342 self.state = "CUE";
1343 // 30-39 - Check if self line contains an optional identifier or timing data.
1344 if (line.indexOf("-->") === -1) {
1345 self.cue.id = line;
1346 continue;
1347 }
1348 // Process line as start of a cue.
1349 /*falls through*/
1350 case "CUE":
1351 // 40 - Collect cue timings and settings.
1352 try {
1353 parseCue(line, self.cue, self.regionList);
1354 } catch (e) {
1355 self.reportOrThrowError(e);
1356 // In case of an error ignore rest of the cue.
1357 self.cue = null;
1358 self.state = "BADCUE";
1359 continue;
1360 }
1361 self.state = "CUETEXT";
1362 continue;
1363 case "CUETEXT":
1364 // 41-53 - Collect the cue text, create a cue, and add it to the output.
1365 if (!line) {
1366 // We are done parsing self cue.
1367 self.oncue && self.oncue(self.cue);
1368 self.cue = null;
1369 self.state = "ID";
1370 continue;
1371 }
1372 if (self.cue.text) {
1373 self.cue.text += "\n";
1374 }
1375 self.cue.text += line;
1376 continue;
1377 case "BADCUE": // BADCUE
1378 // 54-62 - Collect and discard the remaining cue.
1379 if (!line) {
1380 self.state = "ID";
1381 }
1382 continue;
1383 }
1384 }
1385 } catch (e) {
1386 self.reportOrThrowError(e);
1388 // If we are currently parsing a cue, report what we have.
1389 if (self.state === "CUETEXT" && self.cue && self.oncue) {
1390 self.oncue(self.cue);
1391 }
1392 self.cue = null;
1393 // Enter BADWEBVTT state if header was not parsed correctly otherwise
1394 // another exception occurred so enter BADCUE state.
1395 self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
1396 }
1397 return this;
1398 },
1399 flush: function () {
1400 var self = this;
1401 try {
1402 // Finish decoding the stream.
1403 self.buffer += self.decoder.decode();
1404 // Synthesize the end of the current cue or region.
1405 if (self.cue || self.state === "HEADER") {
1406 self.buffer += "\n\n";
1407 self.parse();
1408 }
1409 // If we've flushed, parsed, and we're still on the INITIAL state then
1410 // that means we don't have enough of the stream to parse the first
1411 // line.
1412 if (self.state === "INITIAL") {
1413 throw new ParsingError(ParsingError.Errors.BadSignature);
1414 }
1415 } catch(e) {
1416 self.reportOrThrowError(e);
1417 }
1418 self.onflush && self.onflush();
1419 return this;
1420 }
1421 };
1423 global.WebVTT = WebVTT;
1425 }(this));