1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/webvtt/vtt.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1425 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +this.EXPORTED_SYMBOLS = ["WebVTT"]; 1.9 + 1.10 +/** 1.11 + * Code below is vtt.js the JS WebVTT implementation. 1.12 + * Current source code can be found at http://github.com/mozilla/vtt.js 1.13 + * 1.14 + * Code taken from commit 65ae2daaf6ec7e710f591214893bb03d8b7a94b5 1.15 + */ 1.16 +/** 1.17 + * Copyright 2013 vtt.js Contributors 1.18 + * 1.19 + * Licensed under the Apache License, Version 2.0 (the "License"); 1.20 + * you may not use this file except in compliance with the License. 1.21 + * You may obtain a copy of the License at 1.22 + * 1.23 + * http://www.apache.org/licenses/LICENSE-2.0 1.24 + * 1.25 + * Unless required by applicable law or agreed to in writing, software 1.26 + * distributed under the License is distributed on an "AS IS" BASIS, 1.27 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1.28 + * See the License for the specific language governing permissions and 1.29 + * limitations under the License. 1.30 + */ 1.31 + 1.32 + 1.33 +(function(global) { 1.34 + 1.35 + _objCreate = Object.create || (function() { 1.36 + function F() {} 1.37 + return function(o) { 1.38 + if (arguments.length !== 1) { 1.39 + throw new Error('Object.create shim only accepts one parameter.'); 1.40 + } 1.41 + F.prototype = o; 1.42 + return new F(); 1.43 + }; 1.44 + })(); 1.45 + 1.46 + // Creates a new ParserError object from an errorData object. The errorData 1.47 + // object should have default code and message properties. The default message 1.48 + // property can be overriden by passing in a message parameter. 1.49 + // See ParsingError.Errors below for acceptable errors. 1.50 + function ParsingError(errorData, message) { 1.51 + this.name = "ParsingError"; 1.52 + this.code = errorData.code; 1.53 + this.message = message || errorData.message; 1.54 + } 1.55 + ParsingError.prototype = _objCreate(Error.prototype); 1.56 + ParsingError.prototype.constructor = ParsingError; 1.57 + 1.58 + // ParsingError metadata for acceptable ParsingErrors. 1.59 + ParsingError.Errors = { 1.60 + BadSignature: { 1.61 + code: 0, 1.62 + message: "Malformed WebVTT signature." 1.63 + }, 1.64 + BadTimeStamp: { 1.65 + code: 1, 1.66 + message: "Malformed time stamp." 1.67 + } 1.68 + }; 1.69 + 1.70 + // Try to parse input as a time stamp. 1.71 + function parseTimeStamp(input) { 1.72 + 1.73 + function computeSeconds(h, m, s, f) { 1.74 + return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000; 1.75 + } 1.76 + 1.77 + var m = input.match(/^(\d+):(\d{2})(:\d{2})?\.(\d{3})/); 1.78 + if (!m) { 1.79 + return null; 1.80 + } 1.81 + 1.82 + if (m[3]) { 1.83 + // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds] 1.84 + return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]); 1.85 + } else if (m[1] > 59) { 1.86 + // Timestamp takes the form of [hours]:[minutes].[milliseconds] 1.87 + // First position is hours as it's over 59. 1.88 + return computeSeconds(m[1], m[2], 0, m[4]); 1.89 + } else { 1.90 + // Timestamp takes the form of [minutes]:[seconds].[milliseconds] 1.91 + return computeSeconds(0, m[1], m[2], m[4]); 1.92 + } 1.93 + } 1.94 + 1.95 + // A settings object holds key/value pairs and will ignore anything but the first 1.96 + // assignment to a specific key. 1.97 + function Settings() { 1.98 + this.values = _objCreate(null); 1.99 + } 1.100 + 1.101 + Settings.prototype = { 1.102 + // Only accept the first assignment to any key. 1.103 + set: function(k, v) { 1.104 + if (!this.get(k) && v !== "") { 1.105 + this.values[k] = v; 1.106 + } 1.107 + }, 1.108 + // Return the value for a key, or a default value. 1.109 + // If 'defaultKey' is passed then 'dflt' is assumed to be an object with 1.110 + // a number of possible default values as properties where 'defaultKey' is 1.111 + // the key of the property that will be chosen; otherwise it's assumed to be 1.112 + // a single value. 1.113 + get: function(k, dflt, defaultKey) { 1.114 + if (defaultKey) { 1.115 + return this.has(k) ? this.values[k] : dflt[defaultKey]; 1.116 + } 1.117 + return this.has(k) ? this.values[k] : dflt; 1.118 + }, 1.119 + // Check whether we have a value for a key. 1.120 + has: function(k) { 1.121 + return k in this.values; 1.122 + }, 1.123 + // Accept a setting if its one of the given alternatives. 1.124 + alt: function(k, v, a) { 1.125 + for (var n = 0; n < a.length; ++n) { 1.126 + if (v === a[n]) { 1.127 + this.set(k, v); 1.128 + break; 1.129 + } 1.130 + } 1.131 + }, 1.132 + // Accept a setting if its a valid (signed) integer. 1.133 + integer: function(k, v) { 1.134 + if (/^-?\d+$/.test(v)) { // integer 1.135 + this.set(k, parseInt(v, 10)); 1.136 + } 1.137 + }, 1.138 + // Accept a setting if its a valid percentage. 1.139 + percent: function(k, v) { 1.140 + var m; 1.141 + if ((m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/))) { 1.142 + v = parseFloat(v); 1.143 + if (v >= 0 && v <= 100) { 1.144 + this.set(k, v); 1.145 + return true; 1.146 + } 1.147 + } 1.148 + return false; 1.149 + } 1.150 + }; 1.151 + 1.152 + // Helper function to parse input into groups separated by 'groupDelim', and 1.153 + // interprete each group as a key/value pair separated by 'keyValueDelim'. 1.154 + function parseOptions(input, callback, keyValueDelim, groupDelim) { 1.155 + var groups = groupDelim ? input.split(groupDelim) : [input]; 1.156 + for (var i in groups) { 1.157 + if (typeof groups[i] !== "string") { 1.158 + continue; 1.159 + } 1.160 + var kv = groups[i].split(keyValueDelim); 1.161 + if (kv.length !== 2) { 1.162 + continue; 1.163 + } 1.164 + var k = kv[0]; 1.165 + var v = kv[1]; 1.166 + callback(k, v); 1.167 + } 1.168 + } 1.169 + 1.170 + function parseCue(input, cue, regionList) { 1.171 + // 4.1 WebVTT timestamp 1.172 + function consumeTimeStamp() { 1.173 + var ts = parseTimeStamp(input); 1.174 + if (ts === null) { 1.175 + throw new ParsingError(ParsingError.Errors.BadTimeStamp); 1.176 + } 1.177 + // Remove time stamp from input. 1.178 + input = input.replace(/^[^\sa-zA-Z-]+/, ""); 1.179 + return ts; 1.180 + } 1.181 + 1.182 + // 4.4.2 WebVTT cue settings 1.183 + function consumeCueSettings(input, cue) { 1.184 + var settings = new Settings(); 1.185 + 1.186 + parseOptions(input, function (k, v) { 1.187 + switch (k) { 1.188 + case "region": 1.189 + // Find the last region we parsed with the same region id. 1.190 + for (var i = regionList.length - 1; i >= 0; i--) { 1.191 + if (regionList[i].id === v) { 1.192 + settings.set(k, regionList[i].region); 1.193 + break; 1.194 + } 1.195 + } 1.196 + break; 1.197 + case "vertical": 1.198 + settings.alt(k, v, ["rl", "lr"]); 1.199 + break; 1.200 + case "line": 1.201 + var vals = v.split(","), 1.202 + vals0 = vals[0]; 1.203 + settings.integer(k, vals0); 1.204 + settings.percent(k, vals0) ? settings.set("snapToLines", false) : null; 1.205 + settings.alt(k, vals0, ["auto"]); 1.206 + if (vals.length === 2) { 1.207 + settings.alt("lineAlign", vals[1], ["start", "middle", "end"]); 1.208 + } 1.209 + break; 1.210 + case "position": 1.211 + vals = v.split(","); 1.212 + settings.percent(k, vals[0]); 1.213 + if (vals.length === 2) { 1.214 + settings.alt("positionAlign", vals[1], ["start", "middle", "end"]); 1.215 + } 1.216 + break; 1.217 + case "size": 1.218 + settings.percent(k, v); 1.219 + break; 1.220 + case "align": 1.221 + settings.alt(k, v, ["start", "middle", "end", "left", "right"]); 1.222 + break; 1.223 + } 1.224 + }, /:/, /\s/); 1.225 + 1.226 + // Apply default values for any missing fields. 1.227 + cue.region = settings.get("region", null); 1.228 + cue.vertical = settings.get("vertical", ""); 1.229 + cue.line = settings.get("line", "auto"); 1.230 + cue.lineAlign = settings.get("lineAlign", "start"); 1.231 + cue.snapToLines = settings.get("snapToLines", true); 1.232 + cue.size = settings.get("size", 100); 1.233 + cue.align = settings.get("align", "middle"); 1.234 + cue.position = settings.get("position", { 1.235 + start: 0, 1.236 + left: 0, 1.237 + middle: 50, 1.238 + end: 100, 1.239 + right: 100 1.240 + }, cue.align); 1.241 + cue.positionAlign = settings.get("positionAlign", { 1.242 + start: "start", 1.243 + left: "start", 1.244 + middle: "middle", 1.245 + end: "end", 1.246 + right: "end" 1.247 + }, cue.align); 1.248 + } 1.249 + 1.250 + function skipWhitespace() { 1.251 + input = input.replace(/^\s+/, ""); 1.252 + } 1.253 + 1.254 + // 4.1 WebVTT cue timings. 1.255 + skipWhitespace(); 1.256 + cue.startTime = consumeTimeStamp(); // (1) collect cue start time 1.257 + skipWhitespace(); 1.258 + if (input.substr(0, 3) !== "-->") { // (3) next characters must match "-->" 1.259 + throw new ParsingError(ParsingError.Errors.BadTimeStamp, 1.260 + "Malformed time stamp (time stamps must be separated by '-->')."); 1.261 + } 1.262 + input = input.substr(3); 1.263 + skipWhitespace(); 1.264 + cue.endTime = consumeTimeStamp(); // (5) collect cue end time 1.265 + 1.266 + // 4.1 WebVTT cue settings list. 1.267 + skipWhitespace(); 1.268 + consumeCueSettings(input, cue); 1.269 + } 1.270 + 1.271 + var ESCAPE = { 1.272 + "&": "&", 1.273 + "<": "<", 1.274 + ">": ">", 1.275 + "‎": "\u200e", 1.276 + "‏": "\u200f", 1.277 + " ": "\u00a0" 1.278 + }; 1.279 + 1.280 + var TAG_NAME = { 1.281 + c: "span", 1.282 + i: "i", 1.283 + b: "b", 1.284 + u: "u", 1.285 + ruby: "ruby", 1.286 + rt: "rt", 1.287 + v: "span", 1.288 + lang: "span" 1.289 + }; 1.290 + 1.291 + var TAG_ANNOTATION = { 1.292 + v: "title", 1.293 + lang: "lang" 1.294 + }; 1.295 + 1.296 + var NEEDS_PARENT = { 1.297 + rt: "ruby" 1.298 + }; 1.299 + 1.300 + // Parse content into a document fragment. 1.301 + function parseContent(window, input) { 1.302 + function nextToken() { 1.303 + // Check for end-of-string. 1.304 + if (!input) { 1.305 + return null; 1.306 + } 1.307 + 1.308 + // Consume 'n' characters from the input. 1.309 + function consume(result) { 1.310 + input = input.substr(result.length); 1.311 + return result; 1.312 + } 1.313 + 1.314 + var m = input.match(/^([^<]*)(<[^>]+>?)?/); 1.315 + // If there is some text before the next tag, return it, otherwise return 1.316 + // the tag. 1.317 + return consume(m[1] ? m[1] : m[2]); 1.318 + } 1.319 + 1.320 + // Unescape a string 's'. 1.321 + function unescape1(e) { 1.322 + return ESCAPE[e]; 1.323 + } 1.324 + function unescape(s) { 1.325 + while ((m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/))) { 1.326 + s = s.replace(m[0], unescape1); 1.327 + } 1.328 + return s; 1.329 + } 1.330 + 1.331 + function shouldAdd(current, element) { 1.332 + return !NEEDS_PARENT[element.localName] || 1.333 + NEEDS_PARENT[element.localName] === current.localName; 1.334 + } 1.335 + 1.336 + // Create an element for this tag. 1.337 + function createElement(type, annotation) { 1.338 + var tagName = TAG_NAME[type]; 1.339 + if (!tagName) { 1.340 + return null; 1.341 + } 1.342 + var element = window.document.createElement(tagName); 1.343 + element.localName = tagName; 1.344 + var name = TAG_ANNOTATION[type]; 1.345 + if (name && annotation) { 1.346 + element[name] = annotation.trim(); 1.347 + } 1.348 + return element; 1.349 + } 1.350 + 1.351 + var rootDiv = window.document.createElement("div"), 1.352 + current = rootDiv, 1.353 + t, 1.354 + tagStack = []; 1.355 + 1.356 + while ((t = nextToken()) !== null) { 1.357 + if (t[0] === '<') { 1.358 + if (t[1] === "/") { 1.359 + // If the closing tag matches, move back up to the parent node. 1.360 + if (tagStack.length && 1.361 + tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) { 1.362 + tagStack.pop(); 1.363 + current = current.parentNode; 1.364 + } 1.365 + // Otherwise just ignore the end tag. 1.366 + continue; 1.367 + } 1.368 + var ts = parseTimeStamp(t.substr(1, t.length - 2)); 1.369 + var node; 1.370 + if (ts) { 1.371 + // Timestamps are lead nodes as well. 1.372 + node = window.document.createProcessingInstruction("timestamp", ts); 1.373 + current.appendChild(node); 1.374 + continue; 1.375 + } 1.376 + var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/); 1.377 + // If we can't parse the tag, skip to the next tag. 1.378 + if (!m) { 1.379 + continue; 1.380 + } 1.381 + // Try to construct an element, and ignore the tag if we couldn't. 1.382 + node = createElement(m[1], m[3]); 1.383 + if (!node) { 1.384 + continue; 1.385 + } 1.386 + // Determine if the tag should be added based on the context of where it 1.387 + // is placed in the cuetext. 1.388 + if (!shouldAdd(current, node)) { 1.389 + continue; 1.390 + } 1.391 + // Set the class list (as a list of classes, separated by space). 1.392 + if (m[2]) { 1.393 + node.className = m[2].substr(1).replace('.', ' '); 1.394 + } 1.395 + // Append the node to the current node, and enter the scope of the new 1.396 + // node. 1.397 + tagStack.push(m[1]); 1.398 + current.appendChild(node); 1.399 + current = node; 1.400 + continue; 1.401 + } 1.402 + 1.403 + // Text nodes are leaf nodes. 1.404 + current.appendChild(window.document.createTextNode(unescape(t))); 1.405 + } 1.406 + 1.407 + return rootDiv; 1.408 + } 1.409 + 1.410 + // This is a list of all the Unicode characters that have a strong 1.411 + // right-to-left category. What this means is that these characters are 1.412 + // written right-to-left for sure. It was generated by pulling all the strong 1.413 + // right-to-left characters out of the Unicode data table. That table can 1.414 + // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt 1.415 + var strongRTLChars = [0x05BE, 0x05C0, 0x05C3, 0x05C6, 0x05D0, 0x05D1, 1.416 + 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8, 0x05D9, 0x05DA, 1.417 + 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF, 0x05E0, 0x05E1, 0x05E2, 0x05E3, 1.418 + 0x05E4, 0x05E5, 0x05E6, 0x05E7, 0x05E8, 0x05E9, 0x05EA, 0x05F0, 0x05F1, 1.419 + 0x05F2, 0x05F3, 0x05F4, 0x0608, 0x060B, 0x060D, 0x061B, 0x061E, 0x061F, 1.420 + 0x0620, 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627, 0x0628, 1.421 + 0x0629, 0x062A, 0x062B, 0x062C, 0x062D, 0x062E, 0x062F, 0x0630, 0x0631, 1.422 + 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x0637, 0x0638, 0x0639, 0x063A, 1.423 + 0x063B, 0x063C, 0x063D, 0x063E, 0x063F, 0x0640, 0x0641, 0x0642, 0x0643, 1.424 + 0x0644, 0x0645, 0x0646, 0x0647, 0x0648, 0x0649, 0x064A, 0x066D, 0x066E, 1.425 + 0x066F, 0x0671, 0x0672, 0x0673, 0x0674, 0x0675, 0x0676, 0x0677, 0x0678, 1.426 + 0x0679, 0x067A, 0x067B, 0x067C, 0x067D, 0x067E, 0x067F, 0x0680, 0x0681, 1.427 + 0x0682, 0x0683, 0x0684, 0x0685, 0x0686, 0x0687, 0x0688, 0x0689, 0x068A, 1.428 + 0x068B, 0x068C, 0x068D, 0x068E, 0x068F, 0x0690, 0x0691, 0x0692, 0x0693, 1.429 + 0x0694, 0x0695, 0x0696, 0x0697, 0x0698, 0x0699, 0x069A, 0x069B, 0x069C, 1.430 + 0x069D, 0x069E, 0x069F, 0x06A0, 0x06A1, 0x06A2, 0x06A3, 0x06A4, 0x06A5, 1.431 + 0x06A6, 0x06A7, 0x06A8, 0x06A9, 0x06AA, 0x06AB, 0x06AC, 0x06AD, 0x06AE, 1.432 + 0x06AF, 0x06B0, 0x06B1, 0x06B2, 0x06B3, 0x06B4, 0x06B5, 0x06B6, 0x06B7, 1.433 + 0x06B8, 0x06B9, 0x06BA, 0x06BB, 0x06BC, 0x06BD, 0x06BE, 0x06BF, 0x06C0, 1.434 + 0x06C1, 0x06C2, 0x06C3, 0x06C4, 0x06C5, 0x06C6, 0x06C7, 0x06C8, 0x06C9, 1.435 + 0x06CA, 0x06CB, 0x06CC, 0x06CD, 0x06CE, 0x06CF, 0x06D0, 0x06D1, 0x06D2, 1.436 + 0x06D3, 0x06D4, 0x06D5, 0x06E5, 0x06E6, 0x06EE, 0x06EF, 0x06FA, 0x06FB, 1.437 + 0x06FC, 0x06FD, 0x06FE, 0x06FF, 0x0700, 0x0701, 0x0702, 0x0703, 0x0704, 1.438 + 0x0705, 0x0706, 0x0707, 0x0708, 0x0709, 0x070A, 0x070B, 0x070C, 0x070D, 1.439 + 0x070F, 0x0710, 0x0712, 0x0713, 0x0714, 0x0715, 0x0716, 0x0717, 0x0718, 1.440 + 0x0719, 0x071A, 0x071B, 0x071C, 0x071D, 0x071E, 0x071F, 0x0720, 0x0721, 1.441 + 0x0722, 0x0723, 0x0724, 0x0725, 0x0726, 0x0727, 0x0728, 0x0729, 0x072A, 1.442 + 0x072B, 0x072C, 0x072D, 0x072E, 0x072F, 0x074D, 0x074E, 0x074F, 0x0750, 1.443 + 0x0751, 0x0752, 0x0753, 0x0754, 0x0755, 0x0756, 0x0757, 0x0758, 0x0759, 1.444 + 0x075A, 0x075B, 0x075C, 0x075D, 0x075E, 0x075F, 0x0760, 0x0761, 0x0762, 1.445 + 0x0763, 0x0764, 0x0765, 0x0766, 0x0767, 0x0768, 0x0769, 0x076A, 0x076B, 1.446 + 0x076C, 0x076D, 0x076E, 0x076F, 0x0770, 0x0771, 0x0772, 0x0773, 0x0774, 1.447 + 0x0775, 0x0776, 0x0777, 0x0778, 0x0779, 0x077A, 0x077B, 0x077C, 0x077D, 1.448 + 0x077E, 0x077F, 0x0780, 0x0781, 0x0782, 0x0783, 0x0784, 0x0785, 0x0786, 1.449 + 0x0787, 0x0788, 0x0789, 0x078A, 0x078B, 0x078C, 0x078D, 0x078E, 0x078F, 1.450 + 0x0790, 0x0791, 0x0792, 0x0793, 0x0794, 0x0795, 0x0796, 0x0797, 0x0798, 1.451 + 0x0799, 0x079A, 0x079B, 0x079C, 0x079D, 0x079E, 0x079F, 0x07A0, 0x07A1, 1.452 + 0x07A2, 0x07A3, 0x07A4, 0x07A5, 0x07B1, 0x07C0, 0x07C1, 0x07C2, 0x07C3, 1.453 + 0x07C4, 0x07C5, 0x07C6, 0x07C7, 0x07C8, 0x07C9, 0x07CA, 0x07CB, 0x07CC, 1.454 + 0x07CD, 0x07CE, 0x07CF, 0x07D0, 0x07D1, 0x07D2, 0x07D3, 0x07D4, 0x07D5, 1.455 + 0x07D6, 0x07D7, 0x07D8, 0x07D9, 0x07DA, 0x07DB, 0x07DC, 0x07DD, 0x07DE, 1.456 + 0x07DF, 0x07E0, 0x07E1, 0x07E2, 0x07E3, 0x07E4, 0x07E5, 0x07E6, 0x07E7, 1.457 + 0x07E8, 0x07E9, 0x07EA, 0x07F4, 0x07F5, 0x07FA, 0x0800, 0x0801, 0x0802, 1.458 + 0x0803, 0x0804, 0x0805, 0x0806, 0x0807, 0x0808, 0x0809, 0x080A, 0x080B, 1.459 + 0x080C, 0x080D, 0x080E, 0x080F, 0x0810, 0x0811, 0x0812, 0x0813, 0x0814, 1.460 + 0x0815, 0x081A, 0x0824, 0x0828, 0x0830, 0x0831, 0x0832, 0x0833, 0x0834, 1.461 + 0x0835, 0x0836, 0x0837, 0x0838, 0x0839, 0x083A, 0x083B, 0x083C, 0x083D, 1.462 + 0x083E, 0x0840, 0x0841, 0x0842, 0x0843, 0x0844, 0x0845, 0x0846, 0x0847, 1.463 + 0x0848, 0x0849, 0x084A, 0x084B, 0x084C, 0x084D, 0x084E, 0x084F, 0x0850, 1.464 + 0x0851, 0x0852, 0x0853, 0x0854, 0x0855, 0x0856, 0x0857, 0x0858, 0x085E, 1.465 + 0x08A0, 0x08A2, 0x08A3, 0x08A4, 0x08A5, 0x08A6, 0x08A7, 0x08A8, 0x08A9, 1.466 + 0x08AA, 0x08AB, 0x08AC, 0x200F, 0xFB1D, 0xFB1F, 0xFB20, 0xFB21, 0xFB22, 1.467 + 0xFB23, 0xFB24, 0xFB25, 0xFB26, 0xFB27, 0xFB28, 0xFB2A, 0xFB2B, 0xFB2C, 1.468 + 0xFB2D, 0xFB2E, 0xFB2F, 0xFB30, 0xFB31, 0xFB32, 0xFB33, 0xFB34, 0xFB35, 1.469 + 0xFB36, 0xFB38, 0xFB39, 0xFB3A, 0xFB3B, 0xFB3C, 0xFB3E, 0xFB40, 0xFB41, 1.470 + 0xFB43, 0xFB44, 0xFB46, 0xFB47, 0xFB48, 0xFB49, 0xFB4A, 0xFB4B, 0xFB4C, 1.471 + 0xFB4D, 0xFB4E, 0xFB4F, 0xFB50, 0xFB51, 0xFB52, 0xFB53, 0xFB54, 0xFB55, 1.472 + 0xFB56, 0xFB57, 0xFB58, 0xFB59, 0xFB5A, 0xFB5B, 0xFB5C, 0xFB5D, 0xFB5E, 1.473 + 0xFB5F, 0xFB60, 0xFB61, 0xFB62, 0xFB63, 0xFB64, 0xFB65, 0xFB66, 0xFB67, 1.474 + 0xFB68, 0xFB69, 0xFB6A, 0xFB6B, 0xFB6C, 0xFB6D, 0xFB6E, 0xFB6F, 0xFB70, 1.475 + 0xFB71, 0xFB72, 0xFB73, 0xFB74, 0xFB75, 0xFB76, 0xFB77, 0xFB78, 0xFB79, 1.476 + 0xFB7A, 0xFB7B, 0xFB7C, 0xFB7D, 0xFB7E, 0xFB7F, 0xFB80, 0xFB81, 0xFB82, 1.477 + 0xFB83, 0xFB84, 0xFB85, 0xFB86, 0xFB87, 0xFB88, 0xFB89, 0xFB8A, 0xFB8B, 1.478 + 0xFB8C, 0xFB8D, 0xFB8E, 0xFB8F, 0xFB90, 0xFB91, 0xFB92, 0xFB93, 0xFB94, 1.479 + 0xFB95, 0xFB96, 0xFB97, 0xFB98, 0xFB99, 0xFB9A, 0xFB9B, 0xFB9C, 0xFB9D, 1.480 + 0xFB9E, 0xFB9F, 0xFBA0, 0xFBA1, 0xFBA2, 0xFBA3, 0xFBA4, 0xFBA5, 0xFBA6, 1.481 + 0xFBA7, 0xFBA8, 0xFBA9, 0xFBAA, 0xFBAB, 0xFBAC, 0xFBAD, 0xFBAE, 0xFBAF, 1.482 + 0xFBB0, 0xFBB1, 0xFBB2, 0xFBB3, 0xFBB4, 0xFBB5, 0xFBB6, 0xFBB7, 0xFBB8, 1.483 + 0xFBB9, 0xFBBA, 0xFBBB, 0xFBBC, 0xFBBD, 0xFBBE, 0xFBBF, 0xFBC0, 0xFBC1, 1.484 + 0xFBD3, 0xFBD4, 0xFBD5, 0xFBD6, 0xFBD7, 0xFBD8, 0xFBD9, 0xFBDA, 0xFBDB, 1.485 + 0xFBDC, 0xFBDD, 0xFBDE, 0xFBDF, 0xFBE0, 0xFBE1, 0xFBE2, 0xFBE3, 0xFBE4, 1.486 + 0xFBE5, 0xFBE6, 0xFBE7, 0xFBE8, 0xFBE9, 0xFBEA, 0xFBEB, 0xFBEC, 0xFBED, 1.487 + 0xFBEE, 0xFBEF, 0xFBF0, 0xFBF1, 0xFBF2, 0xFBF3, 0xFBF4, 0xFBF5, 0xFBF6, 1.488 + 0xFBF7, 0xFBF8, 0xFBF9, 0xFBFA, 0xFBFB, 0xFBFC, 0xFBFD, 0xFBFE, 0xFBFF, 1.489 + 0xFC00, 0xFC01, 0xFC02, 0xFC03, 0xFC04, 0xFC05, 0xFC06, 0xFC07, 0xFC08, 1.490 + 0xFC09, 0xFC0A, 0xFC0B, 0xFC0C, 0xFC0D, 0xFC0E, 0xFC0F, 0xFC10, 0xFC11, 1.491 + 0xFC12, 0xFC13, 0xFC14, 0xFC15, 0xFC16, 0xFC17, 0xFC18, 0xFC19, 0xFC1A, 1.492 + 0xFC1B, 0xFC1C, 0xFC1D, 0xFC1E, 0xFC1F, 0xFC20, 0xFC21, 0xFC22, 0xFC23, 1.493 + 0xFC24, 0xFC25, 0xFC26, 0xFC27, 0xFC28, 0xFC29, 0xFC2A, 0xFC2B, 0xFC2C, 1.494 + 0xFC2D, 0xFC2E, 0xFC2F, 0xFC30, 0xFC31, 0xFC32, 0xFC33, 0xFC34, 0xFC35, 1.495 + 0xFC36, 0xFC37, 0xFC38, 0xFC39, 0xFC3A, 0xFC3B, 0xFC3C, 0xFC3D, 0xFC3E, 1.496 + 0xFC3F, 0xFC40, 0xFC41, 0xFC42, 0xFC43, 0xFC44, 0xFC45, 0xFC46, 0xFC47, 1.497 + 0xFC48, 0xFC49, 0xFC4A, 0xFC4B, 0xFC4C, 0xFC4D, 0xFC4E, 0xFC4F, 0xFC50, 1.498 + 0xFC51, 0xFC52, 0xFC53, 0xFC54, 0xFC55, 0xFC56, 0xFC57, 0xFC58, 0xFC59, 1.499 + 0xFC5A, 0xFC5B, 0xFC5C, 0xFC5D, 0xFC5E, 0xFC5F, 0xFC60, 0xFC61, 0xFC62, 1.500 + 0xFC63, 0xFC64, 0xFC65, 0xFC66, 0xFC67, 0xFC68, 0xFC69, 0xFC6A, 0xFC6B, 1.501 + 0xFC6C, 0xFC6D, 0xFC6E, 0xFC6F, 0xFC70, 0xFC71, 0xFC72, 0xFC73, 0xFC74, 1.502 + 0xFC75, 0xFC76, 0xFC77, 0xFC78, 0xFC79, 0xFC7A, 0xFC7B, 0xFC7C, 0xFC7D, 1.503 + 0xFC7E, 0xFC7F, 0xFC80, 0xFC81, 0xFC82, 0xFC83, 0xFC84, 0xFC85, 0xFC86, 1.504 + 0xFC87, 0xFC88, 0xFC89, 0xFC8A, 0xFC8B, 0xFC8C, 0xFC8D, 0xFC8E, 0xFC8F, 1.505 + 0xFC90, 0xFC91, 0xFC92, 0xFC93, 0xFC94, 0xFC95, 0xFC96, 0xFC97, 0xFC98, 1.506 + 0xFC99, 0xFC9A, 0xFC9B, 0xFC9C, 0xFC9D, 0xFC9E, 0xFC9F, 0xFCA0, 0xFCA1, 1.507 + 0xFCA2, 0xFCA3, 0xFCA4, 0xFCA5, 0xFCA6, 0xFCA7, 0xFCA8, 0xFCA9, 0xFCAA, 1.508 + 0xFCAB, 0xFCAC, 0xFCAD, 0xFCAE, 0xFCAF, 0xFCB0, 0xFCB1, 0xFCB2, 0xFCB3, 1.509 + 0xFCB4, 0xFCB5, 0xFCB6, 0xFCB7, 0xFCB8, 0xFCB9, 0xFCBA, 0xFCBB, 0xFCBC, 1.510 + 0xFCBD, 0xFCBE, 0xFCBF, 0xFCC0, 0xFCC1, 0xFCC2, 0xFCC3, 0xFCC4, 0xFCC5, 1.511 + 0xFCC6, 0xFCC7, 0xFCC8, 0xFCC9, 0xFCCA, 0xFCCB, 0xFCCC, 0xFCCD, 0xFCCE, 1.512 + 0xFCCF, 0xFCD0, 0xFCD1, 0xFCD2, 0xFCD3, 0xFCD4, 0xFCD5, 0xFCD6, 0xFCD7, 1.513 + 0xFCD8, 0xFCD9, 0xFCDA, 0xFCDB, 0xFCDC, 0xFCDD, 0xFCDE, 0xFCDF, 0xFCE0, 1.514 + 0xFCE1, 0xFCE2, 0xFCE3, 0xFCE4, 0xFCE5, 0xFCE6, 0xFCE7, 0xFCE8, 0xFCE9, 1.515 + 0xFCEA, 0xFCEB, 0xFCEC, 0xFCED, 0xFCEE, 0xFCEF, 0xFCF0, 0xFCF1, 0xFCF2, 1.516 + 0xFCF3, 0xFCF4, 0xFCF5, 0xFCF6, 0xFCF7, 0xFCF8, 0xFCF9, 0xFCFA, 0xFCFB, 1.517 + 0xFCFC, 0xFCFD, 0xFCFE, 0xFCFF, 0xFD00, 0xFD01, 0xFD02, 0xFD03, 0xFD04, 1.518 + 0xFD05, 0xFD06, 0xFD07, 0xFD08, 0xFD09, 0xFD0A, 0xFD0B, 0xFD0C, 0xFD0D, 1.519 + 0xFD0E, 0xFD0F, 0xFD10, 0xFD11, 0xFD12, 0xFD13, 0xFD14, 0xFD15, 0xFD16, 1.520 + 0xFD17, 0xFD18, 0xFD19, 0xFD1A, 0xFD1B, 0xFD1C, 0xFD1D, 0xFD1E, 0xFD1F, 1.521 + 0xFD20, 0xFD21, 0xFD22, 0xFD23, 0xFD24, 0xFD25, 0xFD26, 0xFD27, 0xFD28, 1.522 + 0xFD29, 0xFD2A, 0xFD2B, 0xFD2C, 0xFD2D, 0xFD2E, 0xFD2F, 0xFD30, 0xFD31, 1.523 + 0xFD32, 0xFD33, 0xFD34, 0xFD35, 0xFD36, 0xFD37, 0xFD38, 0xFD39, 0xFD3A, 1.524 + 0xFD3B, 0xFD3C, 0xFD3D, 0xFD50, 0xFD51, 0xFD52, 0xFD53, 0xFD54, 0xFD55, 1.525 + 0xFD56, 0xFD57, 0xFD58, 0xFD59, 0xFD5A, 0xFD5B, 0xFD5C, 0xFD5D, 0xFD5E, 1.526 + 0xFD5F, 0xFD60, 0xFD61, 0xFD62, 0xFD63, 0xFD64, 0xFD65, 0xFD66, 0xFD67, 1.527 + 0xFD68, 0xFD69, 0xFD6A, 0xFD6B, 0xFD6C, 0xFD6D, 0xFD6E, 0xFD6F, 0xFD70, 1.528 + 0xFD71, 0xFD72, 0xFD73, 0xFD74, 0xFD75, 0xFD76, 0xFD77, 0xFD78, 0xFD79, 1.529 + 0xFD7A, 0xFD7B, 0xFD7C, 0xFD7D, 0xFD7E, 0xFD7F, 0xFD80, 0xFD81, 0xFD82, 1.530 + 0xFD83, 0xFD84, 0xFD85, 0xFD86, 0xFD87, 0xFD88, 0xFD89, 0xFD8A, 0xFD8B, 1.531 + 0xFD8C, 0xFD8D, 0xFD8E, 0xFD8F, 0xFD92, 0xFD93, 0xFD94, 0xFD95, 0xFD96, 1.532 + 0xFD97, 0xFD98, 0xFD99, 0xFD9A, 0xFD9B, 0xFD9C, 0xFD9D, 0xFD9E, 0xFD9F, 1.533 + 0xFDA0, 0xFDA1, 0xFDA2, 0xFDA3, 0xFDA4, 0xFDA5, 0xFDA6, 0xFDA7, 0xFDA8, 1.534 + 0xFDA9, 0xFDAA, 0xFDAB, 0xFDAC, 0xFDAD, 0xFDAE, 0xFDAF, 0xFDB0, 0xFDB1, 1.535 + 0xFDB2, 0xFDB3, 0xFDB4, 0xFDB5, 0xFDB6, 0xFDB7, 0xFDB8, 0xFDB9, 0xFDBA, 1.536 + 0xFDBB, 0xFDBC, 0xFDBD, 0xFDBE, 0xFDBF, 0xFDC0, 0xFDC1, 0xFDC2, 0xFDC3, 1.537 + 0xFDC4, 0xFDC5, 0xFDC6, 0xFDC7, 0xFDF0, 0xFDF1, 0xFDF2, 0xFDF3, 0xFDF4, 1.538 + 0xFDF5, 0xFDF6, 0xFDF7, 0xFDF8, 0xFDF9, 0xFDFA, 0xFDFB, 0xFDFC, 0xFE70, 1.539 + 0xFE71, 0xFE72, 0xFE73, 0xFE74, 0xFE76, 0xFE77, 0xFE78, 0xFE79, 0xFE7A, 1.540 + 0xFE7B, 0xFE7C, 0xFE7D, 0xFE7E, 0xFE7F, 0xFE80, 0xFE81, 0xFE82, 0xFE83, 1.541 + 0xFE84, 0xFE85, 0xFE86, 0xFE87, 0xFE88, 0xFE89, 0xFE8A, 0xFE8B, 0xFE8C, 1.542 + 0xFE8D, 0xFE8E, 0xFE8F, 0xFE90, 0xFE91, 0xFE92, 0xFE93, 0xFE94, 0xFE95, 1.543 + 0xFE96, 0xFE97, 0xFE98, 0xFE99, 0xFE9A, 0xFE9B, 0xFE9C, 0xFE9D, 0xFE9E, 1.544 + 0xFE9F, 0xFEA0, 0xFEA1, 0xFEA2, 0xFEA3, 0xFEA4, 0xFEA5, 0xFEA6, 0xFEA7, 1.545 + 0xFEA8, 0xFEA9, 0xFEAA, 0xFEAB, 0xFEAC, 0xFEAD, 0xFEAE, 0xFEAF, 0xFEB0, 1.546 + 0xFEB1, 0xFEB2, 0xFEB3, 0xFEB4, 0xFEB5, 0xFEB6, 0xFEB7, 0xFEB8, 0xFEB9, 1.547 + 0xFEBA, 0xFEBB, 0xFEBC, 0xFEBD, 0xFEBE, 0xFEBF, 0xFEC0, 0xFEC1, 0xFEC2, 1.548 + 0xFEC3, 0xFEC4, 0xFEC5, 0xFEC6, 0xFEC7, 0xFEC8, 0xFEC9, 0xFECA, 0xFECB, 1.549 + 0xFECC, 0xFECD, 0xFECE, 0xFECF, 0xFED0, 0xFED1, 0xFED2, 0xFED3, 0xFED4, 1.550 + 0xFED5, 0xFED6, 0xFED7, 0xFED8, 0xFED9, 0xFEDA, 0xFEDB, 0xFEDC, 0xFEDD, 1.551 + 0xFEDE, 0xFEDF, 0xFEE0, 0xFEE1, 0xFEE2, 0xFEE3, 0xFEE4, 0xFEE5, 0xFEE6, 1.552 + 0xFEE7, 0xFEE8, 0xFEE9, 0xFEEA, 0xFEEB, 0xFEEC, 0xFEED, 0xFEEE, 0xFEEF, 1.553 + 0xFEF0, 0xFEF1, 0xFEF2, 0xFEF3, 0xFEF4, 0xFEF5, 0xFEF6, 0xFEF7, 0xFEF8, 1.554 + 0xFEF9, 0xFEFA, 0xFEFB, 0xFEFC, 0x10800, 0x10801, 0x10802, 0x10803, 1.555 + 0x10804, 0x10805, 0x10808, 0x1080A, 0x1080B, 0x1080C, 0x1080D, 0x1080E, 1.556 + 0x1080F, 0x10810, 0x10811, 0x10812, 0x10813, 0x10814, 0x10815, 0x10816, 1.557 + 0x10817, 0x10818, 0x10819, 0x1081A, 0x1081B, 0x1081C, 0x1081D, 0x1081E, 1.558 + 0x1081F, 0x10820, 0x10821, 0x10822, 0x10823, 0x10824, 0x10825, 0x10826, 1.559 + 0x10827, 0x10828, 0x10829, 0x1082A, 0x1082B, 0x1082C, 0x1082D, 0x1082E, 1.560 + 0x1082F, 0x10830, 0x10831, 0x10832, 0x10833, 0x10834, 0x10835, 0x10837, 1.561 + 0x10838, 0x1083C, 0x1083F, 0x10840, 0x10841, 0x10842, 0x10843, 0x10844, 1.562 + 0x10845, 0x10846, 0x10847, 0x10848, 0x10849, 0x1084A, 0x1084B, 0x1084C, 1.563 + 0x1084D, 0x1084E, 0x1084F, 0x10850, 0x10851, 0x10852, 0x10853, 0x10854, 1.564 + 0x10855, 0x10857, 0x10858, 0x10859, 0x1085A, 0x1085B, 0x1085C, 0x1085D, 1.565 + 0x1085E, 0x1085F, 0x10900, 0x10901, 0x10902, 0x10903, 0x10904, 0x10905, 1.566 + 0x10906, 0x10907, 0x10908, 0x10909, 0x1090A, 0x1090B, 0x1090C, 0x1090D, 1.567 + 0x1090E, 0x1090F, 0x10910, 0x10911, 0x10912, 0x10913, 0x10914, 0x10915, 1.568 + 0x10916, 0x10917, 0x10918, 0x10919, 0x1091A, 0x1091B, 0x10920, 0x10921, 1.569 + 0x10922, 0x10923, 0x10924, 0x10925, 0x10926, 0x10927, 0x10928, 0x10929, 1.570 + 0x1092A, 0x1092B, 0x1092C, 0x1092D, 0x1092E, 0x1092F, 0x10930, 0x10931, 1.571 + 0x10932, 0x10933, 0x10934, 0x10935, 0x10936, 0x10937, 0x10938, 0x10939, 1.572 + 0x1093F, 0x10980, 0x10981, 0x10982, 0x10983, 0x10984, 0x10985, 0x10986, 1.573 + 0x10987, 0x10988, 0x10989, 0x1098A, 0x1098B, 0x1098C, 0x1098D, 0x1098E, 1.574 + 0x1098F, 0x10990, 0x10991, 0x10992, 0x10993, 0x10994, 0x10995, 0x10996, 1.575 + 0x10997, 0x10998, 0x10999, 0x1099A, 0x1099B, 0x1099C, 0x1099D, 0x1099E, 1.576 + 0x1099F, 0x109A0, 0x109A1, 0x109A2, 0x109A3, 0x109A4, 0x109A5, 0x109A6, 1.577 + 0x109A7, 0x109A8, 0x109A9, 0x109AA, 0x109AB, 0x109AC, 0x109AD, 0x109AE, 1.578 + 0x109AF, 0x109B0, 0x109B1, 0x109B2, 0x109B3, 0x109B4, 0x109B5, 0x109B6, 1.579 + 0x109B7, 0x109BE, 0x109BF, 0x10A00, 0x10A10, 0x10A11, 0x10A12, 0x10A13, 1.580 + 0x10A15, 0x10A16, 0x10A17, 0x10A19, 0x10A1A, 0x10A1B, 0x10A1C, 0x10A1D, 1.581 + 0x10A1E, 0x10A1F, 0x10A20, 0x10A21, 0x10A22, 0x10A23, 0x10A24, 0x10A25, 1.582 + 0x10A26, 0x10A27, 0x10A28, 0x10A29, 0x10A2A, 0x10A2B, 0x10A2C, 0x10A2D, 1.583 + 0x10A2E, 0x10A2F, 0x10A30, 0x10A31, 0x10A32, 0x10A33, 0x10A40, 0x10A41, 1.584 + 0x10A42, 0x10A43, 0x10A44, 0x10A45, 0x10A46, 0x10A47, 0x10A50, 0x10A51, 1.585 + 0x10A52, 0x10A53, 0x10A54, 0x10A55, 0x10A56, 0x10A57, 0x10A58, 0x10A60, 1.586 + 0x10A61, 0x10A62, 0x10A63, 0x10A64, 0x10A65, 0x10A66, 0x10A67, 0x10A68, 1.587 + 0x10A69, 0x10A6A, 0x10A6B, 0x10A6C, 0x10A6D, 0x10A6E, 0x10A6F, 0x10A70, 1.588 + 0x10A71, 0x10A72, 0x10A73, 0x10A74, 0x10A75, 0x10A76, 0x10A77, 0x10A78, 1.589 + 0x10A79, 0x10A7A, 0x10A7B, 0x10A7C, 0x10A7D, 0x10A7E, 0x10A7F, 0x10B00, 1.590 + 0x10B01, 0x10B02, 0x10B03, 0x10B04, 0x10B05, 0x10B06, 0x10B07, 0x10B08, 1.591 + 0x10B09, 0x10B0A, 0x10B0B, 0x10B0C, 0x10B0D, 0x10B0E, 0x10B0F, 0x10B10, 1.592 + 0x10B11, 0x10B12, 0x10B13, 0x10B14, 0x10B15, 0x10B16, 0x10B17, 0x10B18, 1.593 + 0x10B19, 0x10B1A, 0x10B1B, 0x10B1C, 0x10B1D, 0x10B1E, 0x10B1F, 0x10B20, 1.594 + 0x10B21, 0x10B22, 0x10B23, 0x10B24, 0x10B25, 0x10B26, 0x10B27, 0x10B28, 1.595 + 0x10B29, 0x10B2A, 0x10B2B, 0x10B2C, 0x10B2D, 0x10B2E, 0x10B2F, 0x10B30, 1.596 + 0x10B31, 0x10B32, 0x10B33, 0x10B34, 0x10B35, 0x10B40, 0x10B41, 0x10B42, 1.597 + 0x10B43, 0x10B44, 0x10B45, 0x10B46, 0x10B47, 0x10B48, 0x10B49, 0x10B4A, 1.598 + 0x10B4B, 0x10B4C, 0x10B4D, 0x10B4E, 0x10B4F, 0x10B50, 0x10B51, 0x10B52, 1.599 + 0x10B53, 0x10B54, 0x10B55, 0x10B58, 0x10B59, 0x10B5A, 0x10B5B, 0x10B5C, 1.600 + 0x10B5D, 0x10B5E, 0x10B5F, 0x10B60, 0x10B61, 0x10B62, 0x10B63, 0x10B64, 1.601 + 0x10B65, 0x10B66, 0x10B67, 0x10B68, 0x10B69, 0x10B6A, 0x10B6B, 0x10B6C, 1.602 + 0x10B6D, 0x10B6E, 0x10B6F, 0x10B70, 0x10B71, 0x10B72, 0x10B78, 0x10B79, 1.603 + 0x10B7A, 0x10B7B, 0x10B7C, 0x10B7D, 0x10B7E, 0x10B7F, 0x10C00, 0x10C01, 1.604 + 0x10C02, 0x10C03, 0x10C04, 0x10C05, 0x10C06, 0x10C07, 0x10C08, 0x10C09, 1.605 + 0x10C0A, 0x10C0B, 0x10C0C, 0x10C0D, 0x10C0E, 0x10C0F, 0x10C10, 0x10C11, 1.606 + 0x10C12, 0x10C13, 0x10C14, 0x10C15, 0x10C16, 0x10C17, 0x10C18, 0x10C19, 1.607 + 0x10C1A, 0x10C1B, 0x10C1C, 0x10C1D, 0x10C1E, 0x10C1F, 0x10C20, 0x10C21, 1.608 + 0x10C22, 0x10C23, 0x10C24, 0x10C25, 0x10C26, 0x10C27, 0x10C28, 0x10C29, 1.609 + 0x10C2A, 0x10C2B, 0x10C2C, 0x10C2D, 0x10C2E, 0x10C2F, 0x10C30, 0x10C31, 1.610 + 0x10C32, 0x10C33, 0x10C34, 0x10C35, 0x10C36, 0x10C37, 0x10C38, 0x10C39, 1.611 + 0x10C3A, 0x10C3B, 0x10C3C, 0x10C3D, 0x10C3E, 0x10C3F, 0x10C40, 0x10C41, 1.612 + 0x10C42, 0x10C43, 0x10C44, 0x10C45, 0x10C46, 0x10C47, 0x10C48, 0x1EE00, 1.613 + 0x1EE01, 0x1EE02, 0x1EE03, 0x1EE05, 0x1EE06, 0x1EE07, 0x1EE08, 0x1EE09, 1.614 + 0x1EE0A, 0x1EE0B, 0x1EE0C, 0x1EE0D, 0x1EE0E, 0x1EE0F, 0x1EE10, 0x1EE11, 1.615 + 0x1EE12, 0x1EE13, 0x1EE14, 0x1EE15, 0x1EE16, 0x1EE17, 0x1EE18, 0x1EE19, 1.616 + 0x1EE1A, 0x1EE1B, 0x1EE1C, 0x1EE1D, 0x1EE1E, 0x1EE1F, 0x1EE21, 0x1EE22, 1.617 + 0x1EE24, 0x1EE27, 0x1EE29, 0x1EE2A, 0x1EE2B, 0x1EE2C, 0x1EE2D, 0x1EE2E, 1.618 + 0x1EE2F, 0x1EE30, 0x1EE31, 0x1EE32, 0x1EE34, 0x1EE35, 0x1EE36, 0x1EE37, 1.619 + 0x1EE39, 0x1EE3B, 0x1EE42, 0x1EE47, 0x1EE49, 0x1EE4B, 0x1EE4D, 0x1EE4E, 1.620 + 0x1EE4F, 0x1EE51, 0x1EE52, 0x1EE54, 0x1EE57, 0x1EE59, 0x1EE5B, 0x1EE5D, 1.621 + 0x1EE5F, 0x1EE61, 0x1EE62, 0x1EE64, 0x1EE67, 0x1EE68, 0x1EE69, 0x1EE6A, 1.622 + 0x1EE6C, 0x1EE6D, 0x1EE6E, 0x1EE6F, 0x1EE70, 0x1EE71, 0x1EE72, 0x1EE74, 1.623 + 0x1EE75, 0x1EE76, 0x1EE77, 0x1EE79, 0x1EE7A, 0x1EE7B, 0x1EE7C, 0x1EE7E, 1.624 + 0x1EE80, 0x1EE81, 0x1EE82, 0x1EE83, 0x1EE84, 0x1EE85, 0x1EE86, 0x1EE87, 1.625 + 0x1EE88, 0x1EE89, 0x1EE8B, 0x1EE8C, 0x1EE8D, 0x1EE8E, 0x1EE8F, 0x1EE90, 1.626 + 0x1EE91, 0x1EE92, 0x1EE93, 0x1EE94, 0x1EE95, 0x1EE96, 0x1EE97, 0x1EE98, 1.627 + 0x1EE99, 0x1EE9A, 0x1EE9B, 0x1EEA1, 0x1EEA2, 0x1EEA3, 0x1EEA5, 0x1EEA6, 1.628 + 0x1EEA7, 0x1EEA8, 0x1EEA9, 0x1EEAB, 0x1EEAC, 0x1EEAD, 0x1EEAE, 0x1EEAF, 1.629 + 0x1EEB0, 0x1EEB1, 0x1EEB2, 0x1EEB3, 0x1EEB4, 0x1EEB5, 0x1EEB6, 0x1EEB7, 1.630 + 0x1EEB8, 0x1EEB9, 0x1EEBA, 0x1EEBB, 0x10FFFD]; 1.631 + 1.632 + function determineBidi(cueDiv) { 1.633 + var nodeStack = [], 1.634 + text = "", 1.635 + charCode; 1.636 + 1.637 + if (!cueDiv || !cueDiv.childNodes) { 1.638 + return "ltr"; 1.639 + } 1.640 + 1.641 + function pushNodes(nodeStack, node) { 1.642 + for (var i = node.childNodes.length - 1; i >= 0; i--) { 1.643 + nodeStack.push(node.childNodes[i]); 1.644 + } 1.645 + } 1.646 + 1.647 + function nextTextNode(nodeStack) { 1.648 + if (!nodeStack || !nodeStack.length) { 1.649 + return null; 1.650 + } 1.651 + 1.652 + var node = nodeStack.pop(), 1.653 + text = node.textContent || node.innerText; 1.654 + if (text) { 1.655 + // TODO: This should match all unicode type B characters (paragraph 1.656 + // separator characters). See issue #115. 1.657 + var m = text.match(/^.*(\n|\r)/); 1.658 + if (m) { 1.659 + nodeStack.length = 0; 1.660 + return m[0]; 1.661 + } 1.662 + return text; 1.663 + } 1.664 + if (node.tagName === "ruby") { 1.665 + return nextTextNode(nodeStack); 1.666 + } 1.667 + if (node.childNodes) { 1.668 + pushNodes(nodeStack, node); 1.669 + return nextTextNode(nodeStack); 1.670 + } 1.671 + } 1.672 + 1.673 + pushNodes(nodeStack, cueDiv); 1.674 + while ((text = nextTextNode(nodeStack))) { 1.675 + for (var i = 0; i < text.length; i++) { 1.676 + charCode = text.charCodeAt(i); 1.677 + for (var j = 0; j < strongRTLChars.length; j++) { 1.678 + if (strongRTLChars[j] === charCode) { 1.679 + return "rtl"; 1.680 + } 1.681 + } 1.682 + } 1.683 + } 1.684 + return "ltr"; 1.685 + } 1.686 + 1.687 + function computeLinePos(cue) { 1.688 + if (typeof cue.line === "number" && 1.689 + (cue.snapToLines || (cue.line >= 0 && cue.line <= 100))) { 1.690 + return cue.line; 1.691 + } 1.692 + if (!cue.track || !cue.track.textTrackList || 1.693 + !cue.track.textTrackList.mediaElement) { 1.694 + return -1; 1.695 + } 1.696 + var track = cue.track, 1.697 + trackList = track.textTrackList, 1.698 + count = 0; 1.699 + for (var i = 0; i < trackList.length && trackList[i] !== track; i++) { 1.700 + if (trackList[i].mode === "showing") { 1.701 + count++; 1.702 + } 1.703 + } 1.704 + return ++count * -1; 1.705 + } 1.706 + 1.707 + function StyleBox() { 1.708 + } 1.709 + 1.710 + // Apply styles to a div. If there is no div passed then it defaults to the 1.711 + // div on 'this'. 1.712 + StyleBox.prototype.applyStyles = function(styles, div) { 1.713 + div = div || this.div; 1.714 + for (var prop in styles) { 1.715 + if (styles.hasOwnProperty(prop)) { 1.716 + div.style[prop] = styles[prop]; 1.717 + } 1.718 + } 1.719 + }; 1.720 + 1.721 + StyleBox.prototype.formatStyle = function(val, unit) { 1.722 + return val === 0 ? 0 : val + unit; 1.723 + }; 1.724 + 1.725 + // Constructs the computed display state of the cue (a div). Places the div 1.726 + // into the overlay which should be a block level element (usually a div). 1.727 + function CueStyleBox(window, cue, styleOptions) { 1.728 + StyleBox.call(this); 1.729 + this.cue = cue; 1.730 + 1.731 + // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will 1.732 + // have inline positioning and will function as the cue background box. 1.733 + this.cueDiv = parseContent(window, cue.text); 1.734 + this.applyStyles({ 1.735 + color: "rgba(255, 255, 255, 1)", 1.736 + backgroundColor: "rgba(0, 0, 0, 0.8)", 1.737 + position: "relative", 1.738 + left: 0, 1.739 + right: 0, 1.740 + top: 0, 1.741 + bottom: 0, 1.742 + display: "inline" 1.743 + }, this.cueDiv); 1.744 + 1.745 + // Create an absolutely positioned div that will be used to position the cue 1.746 + // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS 1.747 + // mirrors of them except "middle" which is "center" in CSS. 1.748 + this.div = window.document.createElement("div"); 1.749 + this.applyStyles({ 1.750 + textAlign: cue.align === "middle" ? "center" : cue.align, 1.751 + direction: determineBidi(this.cueDiv), 1.752 + writingMode: cue.vertical === "" ? "horizontal-tb" 1.753 + : cue.vertical === "lr" ? "vertical-lr" 1.754 + : "vertical-rl", 1.755 + unicodeBidi: "plaintext", 1.756 + font: styleOptions.font, 1.757 + whiteSpace: "pre-line", 1.758 + position: "absolute" 1.759 + }); 1.760 + 1.761 + this.div.appendChild(this.cueDiv); 1.762 + 1.763 + // Calculate the distance from the reference edge of the viewport to the text 1.764 + // position of the cue box. The reference edge will be resolved later when 1.765 + // the box orientation styles are applied. 1.766 + var textPos = 0; 1.767 + switch (cue.positionAlign) { 1.768 + case "start": 1.769 + textPos = cue.position; 1.770 + break; 1.771 + case "middle": 1.772 + textPos = cue.position - (cue.size / 2); 1.773 + break; 1.774 + case "end": 1.775 + textPos = cue.position - cue.size; 1.776 + break; 1.777 + } 1.778 + 1.779 + // Horizontal box orientation; textPos is the distance from the left edge of the 1.780 + // area to the left edge of the box and cue.size is the distance extending to 1.781 + // the right from there. 1.782 + if (cue.vertical === "") { 1.783 + this.applyStyles({ 1.784 + left: this.formatStyle(textPos, "%"), 1.785 + width: this.formatStyle(cue.size, "%"), 1.786 + }); 1.787 + // Vertical box orientation; textPos is the distance from the top edge of the 1.788 + // area to the top edge of the box and cue.size is the height extending 1.789 + // downwards from there. 1.790 + } else { 1.791 + this.applyStyles({ 1.792 + top: this.formatStyle(textPos, "%"), 1.793 + height: this.formatStyle(cue.size, "%") 1.794 + }); 1.795 + } 1.796 + 1.797 + this.move = function(box) { 1.798 + this.applyStyles({ 1.799 + top: this.formatStyle(box.top, "px"), 1.800 + bottom: this.formatStyle(box.bottom, "px"), 1.801 + left: this.formatStyle(box.left, "px"), 1.802 + right: this.formatStyle(box.right, "px"), 1.803 + height: this.formatStyle(box.height, "px"), 1.804 + width: this.formatStyle(box.width, "px"), 1.805 + }); 1.806 + }; 1.807 + } 1.808 + CueStyleBox.prototype = _objCreate(StyleBox.prototype); 1.809 + CueStyleBox.prototype.constructor = CueStyleBox; 1.810 + 1.811 + // Represents the co-ordinates of an Element in a way that we can easily 1.812 + // compute things with such as if it overlaps or intersects with another Element. 1.813 + // Can initialize it with either a StyleBox or another BoxPosition. 1.814 + function BoxPosition(obj) { 1.815 + // Either a BoxPosition was passed in and we need to copy it, or a StyleBox 1.816 + // was passed in and we need to copy the results of 'getBoundingClientRect' 1.817 + // as the object returned is readonly. All co-ordinate values are in reference 1.818 + // to the viewport origin (top left). 1.819 + var lh; 1.820 + if (obj.div) { 1.821 + var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && 1.822 + rects.getClientRects && rects.getClientRects(); 1.823 + obj = obj.div.getBoundingClientRect(); 1.824 + // In certain cases the outter div will be slightly larger then the sum of 1.825 + // the inner div's lines. This could be due to bold text, etc, on some platforms. 1.826 + // In this case we should get the average line height and use that. This will 1.827 + // result in the desired behaviour. 1.828 + lh = rects ? Math.max((rects[0] && rects[0].height) || 0, obj.height / rects.length) 1.829 + : 0; 1.830 + } 1.831 + this.left = obj.left; 1.832 + this.right = obj.right; 1.833 + this.top = obj.top; 1.834 + this.height = obj.height; 1.835 + this.bottom = obj.bottom; 1.836 + this.width = obj.width; 1.837 + this.lineHeight = lh !== undefined ? lh : obj.lineHeight; 1.838 + } 1.839 + 1.840 + // Move the box along a particular axis. Optionally pass in an amount to move 1.841 + // the box. If no amount is passed then the default is the line height of the 1.842 + // box. 1.843 + BoxPosition.prototype.move = function(axis, toMove) { 1.844 + toMove = toMove !== undefined ? toMove : this.lineHeight; 1.845 + switch (axis) { 1.846 + case "+x": 1.847 + this.left += toMove; 1.848 + this.right += toMove; 1.849 + break; 1.850 + case "-x": 1.851 + this.left -= toMove; 1.852 + this.right -= toMove; 1.853 + break; 1.854 + case "+y": 1.855 + this.top += toMove; 1.856 + this.bottom += toMove; 1.857 + break; 1.858 + case "-y": 1.859 + this.top -= toMove; 1.860 + this.bottom -= toMove; 1.861 + break; 1.862 + } 1.863 + }; 1.864 + 1.865 + // Check if this box overlaps another box, b2. 1.866 + BoxPosition.prototype.overlaps = function(b2) { 1.867 + return this.left < b2.right && 1.868 + this.right > b2.left && 1.869 + this.top < b2.bottom && 1.870 + this.bottom > b2.top; 1.871 + }; 1.872 + 1.873 + // Check if this box overlaps any other boxes in boxes. 1.874 + BoxPosition.prototype.overlapsAny = function(boxes) { 1.875 + for (var i = 0; i < boxes.length; i++) { 1.876 + if (this.overlaps(boxes[i])) { 1.877 + return true; 1.878 + } 1.879 + } 1.880 + return false; 1.881 + }; 1.882 + 1.883 + // Check if this box is within another box. 1.884 + BoxPosition.prototype.within = function(container) { 1.885 + return this.top >= container.top && 1.886 + this.bottom <= container.bottom && 1.887 + this.left >= container.left && 1.888 + this.right <= container.right; 1.889 + }; 1.890 + 1.891 + // Check if this box is entirely within the container or it is overlapping 1.892 + // on the edge opposite of the axis direction passed. For example, if "+x" is 1.893 + // passed and the box is overlapping on the left edge of the container, then 1.894 + // return true. 1.895 + BoxPosition.prototype.overlapsOppositeAxis = function(container, axis) { 1.896 + switch (axis) { 1.897 + case "+x": 1.898 + return this.left < container.left; 1.899 + case "-x": 1.900 + return this.right > container.right; 1.901 + case "+y": 1.902 + return this.top < container.top; 1.903 + case "-y": 1.904 + return this.bottom > container.bottom; 1.905 + } 1.906 + }; 1.907 + 1.908 + // Find the percentage of the area that this box is overlapping with another 1.909 + // box. 1.910 + BoxPosition.prototype.intersectPercentage = function(b2) { 1.911 + var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)), 1.912 + y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)), 1.913 + intersectArea = x * y; 1.914 + return intersectArea / (this.height * this.width); 1.915 + }; 1.916 + 1.917 + // Convert the positions from this box to CSS compatible positions using 1.918 + // the reference container's positions. This has to be done because this 1.919 + // box's positions are in reference to the viewport origin, whereas, CSS 1.920 + // values are in referecne to their respective edges. 1.921 + BoxPosition.prototype.toCSSCompatValues = function(reference) { 1.922 + return { 1.923 + top: this.top - reference.top, 1.924 + bottom: reference.bottom - this.bottom, 1.925 + left: this.left - reference.left, 1.926 + right: reference.right - this.right, 1.927 + height: this.height, 1.928 + width: this.width 1.929 + }; 1.930 + }; 1.931 + 1.932 + // Get an object that represents the box's position without anything extra. 1.933 + // Can pass a StyleBox, HTMLElement, or another BoxPositon. 1.934 + BoxPosition.getSimpleBoxPosition = function(obj) { 1.935 + obj = obj.div ? obj.div.getBoundingClientRect() : 1.936 + obj.tagName ? obj.getBoundingClientRect() : obj; 1.937 + return { 1.938 + left: obj.left, 1.939 + right: obj.right, 1.940 + top: obj.top, 1.941 + height: obj.height, 1.942 + bottom: obj.bottom, 1.943 + width: obj.width 1.944 + }; 1.945 + }; 1.946 + 1.947 + // Move a StyleBox to its specified, or next best, position. The containerBox 1.948 + // is the box that contains the StyleBox, such as a div. boxPositions are 1.949 + // a list of other boxes that the styleBox can't overlap with. 1.950 + function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) { 1.951 + 1.952 + // Find the best position for a cue box, b, on the video. The axis parameter 1.953 + // is a list of axis, the order of which, it will move the box along. For example: 1.954 + // Passing ["+x", "-x"] will move the box first along the x axis in the positive 1.955 + // direction. If it doesn't find a good position for it there it will then move 1.956 + // it along the x axis in the negative direction. 1.957 + function findBestPosition(b, axis) { 1.958 + var bestPosition, 1.959 + specifiedPosition = new BoxPosition(b), 1.960 + percentage = 1; // Highest possible so the first thing we get is better. 1.961 + 1.962 + for (var i = 0; i < axis.length; i++) { 1.963 + while (b.overlapsOppositeAxis(containerBox, axis[i]) || 1.964 + (b.within(containerBox) && b.overlapsAny(boxPositions))) { 1.965 + b.move(axis[i]); 1.966 + } 1.967 + // We found a spot where we aren't overlapping anything. This is our 1.968 + // best position. 1.969 + if (b.within(containerBox)) { 1.970 + return b; 1.971 + } 1.972 + var p = b.intersectPercentage(containerBox); 1.973 + // If we're outside the container box less then we were on our last try 1.974 + // then remember this position as the best position. 1.975 + if (percentage > p) { 1.976 + bestPosition = new BoxPosition(b); 1.977 + percentage = p; 1.978 + } 1.979 + // Reset the box position to the specified position. 1.980 + b = new BoxPosition(specifiedPosition); 1.981 + } 1.982 + return bestPosition || specifiedPosition; 1.983 + } 1.984 + 1.985 + var boxPosition = new BoxPosition(styleBox), 1.986 + cue = styleBox.cue, 1.987 + linePos = computeLinePos(cue), 1.988 + axis = []; 1.989 + 1.990 + // If we have a line number to align the cue to. 1.991 + if (cue.snapToLines) { 1.992 + var size; 1.993 + switch (cue.vertical) { 1.994 + case "": 1.995 + axis = [ "+y", "-y" ]; 1.996 + size = "height"; 1.997 + break; 1.998 + case "rl": 1.999 + axis = [ "+x", "-x" ]; 1.1000 + size = "width"; 1.1001 + break; 1.1002 + case "lr": 1.1003 + axis = [ "-x", "+x" ]; 1.1004 + size = "width"; 1.1005 + break; 1.1006 + } 1.1007 + 1.1008 + var step = boxPosition.lineHeight, 1.1009 + position = step * Math.round(linePos), 1.1010 + maxPosition = containerBox[size] + step, 1.1011 + initialAxis = axis[0]; 1.1012 + 1.1013 + // If the specified intial position is greater then the max position then 1.1014 + // clamp the box to the amount of steps it would take for the box to 1.1015 + // reach the max position. 1.1016 + if (Math.abs(position) > maxPosition) { 1.1017 + position = position < 0 ? -1 : 1; 1.1018 + position *= Math.ceil(maxPosition / step) * step; 1.1019 + } 1.1020 + 1.1021 + // If computed line position returns negative then line numbers are 1.1022 + // relative to the bottom of the video instead of the top. Therefore, we 1.1023 + // need to increase our initial position by the length or width of the 1.1024 + // video, depending on the writing direction, and reverse our axis directions. 1.1025 + if (linePos < 0) { 1.1026 + position += cue.vertical === "" ? containerBox.height : containerBox.width; 1.1027 + axis = axis.reverse(); 1.1028 + } 1.1029 + 1.1030 + // Move the box to the specified position. This may not be its best 1.1031 + // position. 1.1032 + boxPosition.move(initialAxis, position); 1.1033 + 1.1034 + } else { 1.1035 + // If we have a percentage line value for the cue. 1.1036 + var calculatedPercentage = (boxPosition.lineHeight / containerBox.height) * 100; 1.1037 + 1.1038 + switch (cue.lineAlign) { 1.1039 + case "middle": 1.1040 + linePos -= (calculatedPercentage / 2); 1.1041 + break; 1.1042 + case "end": 1.1043 + linePos -= calculatedPercentage; 1.1044 + break; 1.1045 + } 1.1046 + 1.1047 + // Apply initial line position to the cue box. 1.1048 + switch (cue.vertical) { 1.1049 + case "": 1.1050 + styleBox.applyStyles({ 1.1051 + top: styleBox.formatStyle(linePos, "%") 1.1052 + }); 1.1053 + break; 1.1054 + case "rl": 1.1055 + styleBox.applyStyles({ 1.1056 + left: styleBox.formatStyle(linePos, "%") 1.1057 + }); 1.1058 + break; 1.1059 + case "lr": 1.1060 + styleBox.applyStyles({ 1.1061 + right: styleBox.formatStyle(linePos, "%") 1.1062 + }); 1.1063 + break; 1.1064 + } 1.1065 + 1.1066 + axis = [ "+y", "-x", "+x", "-y" ]; 1.1067 + 1.1068 + // Get the box position again after we've applied the specified positioning 1.1069 + // to it. 1.1070 + boxPosition = new BoxPosition(styleBox); 1.1071 + } 1.1072 + 1.1073 + var bestPosition = findBestPosition(boxPosition, axis); 1.1074 + styleBox.move(bestPosition.toCSSCompatValues(containerBox)); 1.1075 + } 1.1076 + 1.1077 + function WebVTT() { 1.1078 + // Nothing 1.1079 + } 1.1080 + 1.1081 + // Helper to allow strings to be decoded instead of the default binary utf8 data. 1.1082 + WebVTT.StringDecoder = function() { 1.1083 + return { 1.1084 + decode: function(data) { 1.1085 + if (!data) { 1.1086 + return ""; 1.1087 + } 1.1088 + if (typeof data !== "string") { 1.1089 + throw new Error("Error - expected string data."); 1.1090 + } 1.1091 + return decodeURIComponent(encodeURIComponent(data)); 1.1092 + } 1.1093 + }; 1.1094 + }; 1.1095 + 1.1096 + WebVTT.convertCueToDOMTree = function(window, cuetext) { 1.1097 + if (!window || !cuetext) { 1.1098 + return null; 1.1099 + } 1.1100 + return parseContent(window, cuetext); 1.1101 + }; 1.1102 + 1.1103 + var FONT_SIZE_PERCENT = 0.05; 1.1104 + var FONT_STYLE = "sans-serif"; 1.1105 + var CUE_BACKGROUND_PADDING = "1.5%"; 1.1106 + 1.1107 + // Runs the processing model over the cues and regions passed to it. 1.1108 + // @param overlay A block level element (usually a div) that the computed cues 1.1109 + // and regions will be placed into. 1.1110 + WebVTT.processCues = function(window, cues, overlay) { 1.1111 + if (!window || !cues || !overlay) { 1.1112 + return null; 1.1113 + } 1.1114 + 1.1115 + // Remove all previous children. 1.1116 + while (overlay.firstChild) { 1.1117 + overlay.removeChild(overlay.firstChild); 1.1118 + } 1.1119 + 1.1120 + var paddedOverlay = window.document.createElement("div"); 1.1121 + paddedOverlay.style.position = "absolute"; 1.1122 + paddedOverlay.style.left = "0"; 1.1123 + paddedOverlay.style.right = "0"; 1.1124 + paddedOverlay.style.top = "0"; 1.1125 + paddedOverlay.style.bottom = "0"; 1.1126 + paddedOverlay.style.margin = CUE_BACKGROUND_PADDING; 1.1127 + overlay.appendChild(paddedOverlay); 1.1128 + 1.1129 + // Determine if we need to compute the display states of the cues. This could 1.1130 + // be the case if a cue's state has been changed since the last computation or 1.1131 + // if it has not been computed yet. 1.1132 + function shouldCompute(cues) { 1.1133 + for (var i = 0; i < cues.length; i++) { 1.1134 + if (cues[i].hasBeenReset || !cues[i].displayState) { 1.1135 + return true; 1.1136 + } 1.1137 + } 1.1138 + return false; 1.1139 + } 1.1140 + 1.1141 + // We don't need to recompute the cues' display states. Just reuse them. 1.1142 + if (!shouldCompute(cues)) { 1.1143 + cues.forEach(function(cue) { 1.1144 + paddedOverlay.appendChild(cue.displayState); 1.1145 + }); 1.1146 + return; 1.1147 + } 1.1148 + 1.1149 + var boxPositions = [], 1.1150 + containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay), 1.1151 + fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100; 1.1152 + var styleOptions = { 1.1153 + font: fontSize + "px " + FONT_STYLE 1.1154 + }; 1.1155 + 1.1156 + cues.forEach(function(cue) { 1.1157 + // Compute the intial position and styles of the cue div. 1.1158 + var styleBox = new CueStyleBox(window, cue, styleOptions); 1.1159 + paddedOverlay.appendChild(styleBox.div); 1.1160 + 1.1161 + // Move the cue div to it's correct line position. 1.1162 + moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); 1.1163 + 1.1164 + // Remember the computed div so that we don't have to recompute it later 1.1165 + // if we don't have too. 1.1166 + cue.displayState = styleBox.div; 1.1167 + 1.1168 + boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox)); 1.1169 + }); 1.1170 + }; 1.1171 + 1.1172 + WebVTT.Parser = function(window, decoder) { 1.1173 + this.window = window; 1.1174 + this.state = "INITIAL"; 1.1175 + this.buffer = ""; 1.1176 + this.decoder = decoder || new TextDecoder("utf8"); 1.1177 + this.regionList = []; 1.1178 + }; 1.1179 + 1.1180 + WebVTT.Parser.prototype = { 1.1181 + // If the error is a ParsingError then report it to the consumer if 1.1182 + // possible. If it's not a ParsingError then throw it like normal. 1.1183 + reportOrThrowError: function(e) { 1.1184 + if (e instanceof ParsingError) { 1.1185 + this.onparsingerror && this.onparsingerror(e); 1.1186 + } else { 1.1187 + throw e; 1.1188 + } 1.1189 + }, 1.1190 + parse: function (data) { 1.1191 + var self = this; 1.1192 + 1.1193 + // If there is no data then we won't decode it, but will just try to parse 1.1194 + // whatever is in buffer already. This may occur in circumstances, for 1.1195 + // example when flush() is called. 1.1196 + if (data) { 1.1197 + // Try to decode the data that we received. 1.1198 + self.buffer += self.decoder.decode(data, {stream: true}); 1.1199 + } 1.1200 + 1.1201 + function collectNextLine() { 1.1202 + var buffer = self.buffer; 1.1203 + var pos = 0; 1.1204 + while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') { 1.1205 + ++pos; 1.1206 + } 1.1207 + var line = buffer.substr(0, pos); 1.1208 + // Advance the buffer early in case we fail below. 1.1209 + if (buffer[pos] === '\r') { 1.1210 + ++pos; 1.1211 + } 1.1212 + if (buffer[pos] === '\n') { 1.1213 + ++pos; 1.1214 + } 1.1215 + self.buffer = buffer.substr(pos); 1.1216 + return line; 1.1217 + } 1.1218 + 1.1219 + // 3.4 WebVTT region and WebVTT region settings syntax 1.1220 + function parseRegion(input) { 1.1221 + var settings = new Settings(); 1.1222 + 1.1223 + parseOptions(input, function (k, v) { 1.1224 + switch (k) { 1.1225 + case "id": 1.1226 + settings.set(k, v); 1.1227 + break; 1.1228 + case "width": 1.1229 + settings.percent(k, v); 1.1230 + break; 1.1231 + case "lines": 1.1232 + settings.integer(k, v); 1.1233 + break; 1.1234 + case "regionanchor": 1.1235 + case "viewportanchor": 1.1236 + var xy = v.split(','); 1.1237 + if (xy.length !== 2) { 1.1238 + break; 1.1239 + } 1.1240 + // We have to make sure both x and y parse, so use a temporary 1.1241 + // settings object here. 1.1242 + var anchor = new Settings(); 1.1243 + anchor.percent("x", xy[0]); 1.1244 + anchor.percent("y", xy[1]); 1.1245 + if (!anchor.has("x") || !anchor.has("y")) { 1.1246 + break; 1.1247 + } 1.1248 + settings.set(k + "X", anchor.get("x")); 1.1249 + settings.set(k + "Y", anchor.get("y")); 1.1250 + break; 1.1251 + case "scroll": 1.1252 + settings.alt(k, v, ["up"]); 1.1253 + break; 1.1254 + } 1.1255 + }, /=/, /\s/); 1.1256 + 1.1257 + // Create the region, using default values for any values that were not 1.1258 + // specified. 1.1259 + if (settings.has("id")) { 1.1260 + var region = new self.window.VTTRegion(); 1.1261 + region.width = settings.get("width", 100); 1.1262 + region.lines = settings.get("lines", 3); 1.1263 + region.regionAnchorX = settings.get("regionanchorX", 0); 1.1264 + region.regionAnchorY = settings.get("regionanchorY", 100); 1.1265 + region.viewportAnchorX = settings.get("viewportanchorX", 0); 1.1266 + region.viewportAnchorY = settings.get("viewportanchorY", 100); 1.1267 + region.scroll = settings.get("scroll", ""); 1.1268 + // Register the region. 1.1269 + self.onregion && self.onregion(region); 1.1270 + // Remember the VTTRegion for later in case we parse any VTTCues that 1.1271 + // reference it. 1.1272 + self.regionList.push({ 1.1273 + id: settings.get("id"), 1.1274 + region: region 1.1275 + }); 1.1276 + } 1.1277 + } 1.1278 + 1.1279 + // 3.2 WebVTT metadata header syntax 1.1280 + function parseHeader(input) { 1.1281 + parseOptions(input, function (k, v) { 1.1282 + switch (k) { 1.1283 + case "Region": 1.1284 + // 3.3 WebVTT region metadata header syntax 1.1285 + parseRegion(v); 1.1286 + break; 1.1287 + } 1.1288 + }, /:/); 1.1289 + } 1.1290 + 1.1291 + // 5.1 WebVTT file parsing. 1.1292 + try { 1.1293 + var line; 1.1294 + if (self.state === "INITIAL") { 1.1295 + // We can't start parsing until we have the first line. 1.1296 + if (!/\r\n|\n/.test(self.buffer)) { 1.1297 + return this; 1.1298 + } 1.1299 + 1.1300 + line = collectNextLine(); 1.1301 + 1.1302 + var m = line.match(/^WEBVTT([ \t].*)?$/); 1.1303 + if (!m || !m[0]) { 1.1304 + throw new ParsingError(ParsingError.Errors.BadSignature); 1.1305 + } 1.1306 + 1.1307 + self.state = "HEADER"; 1.1308 + } 1.1309 + 1.1310 + while (self.buffer) { 1.1311 + // We can't parse a line until we have the full line. 1.1312 + if (!/\r\n|\n/.test(self.buffer)) { 1.1313 + return this; 1.1314 + } 1.1315 + 1.1316 + line = collectNextLine(); 1.1317 + 1.1318 + switch (self.state) { 1.1319 + case "HEADER": 1.1320 + // 13-18 - Allow a header (metadata) under the WEBVTT line. 1.1321 + if (/:/.test(line)) { 1.1322 + parseHeader(line); 1.1323 + } else if (!line) { 1.1324 + // An empty line terminates the header and starts the body (cues). 1.1325 + self.state = "ID"; 1.1326 + } 1.1327 + continue; 1.1328 + case "NOTE": 1.1329 + // Ignore NOTE blocks. 1.1330 + if (!line) { 1.1331 + self.state = "ID"; 1.1332 + } 1.1333 + continue; 1.1334 + case "ID": 1.1335 + // Check for the start of NOTE blocks. 1.1336 + if (/^NOTE($|[ \t])/.test(line)) { 1.1337 + self.state = "NOTE"; 1.1338 + break; 1.1339 + } 1.1340 + // 19-29 - Allow any number of line terminators, then initialize new cue values. 1.1341 + if (!line) { 1.1342 + continue; 1.1343 + } 1.1344 + self.cue = new self.window.VTTCue(0, 0, ""); 1.1345 + self.state = "CUE"; 1.1346 + // 30-39 - Check if self line contains an optional identifier or timing data. 1.1347 + if (line.indexOf("-->") === -1) { 1.1348 + self.cue.id = line; 1.1349 + continue; 1.1350 + } 1.1351 + // Process line as start of a cue. 1.1352 + /*falls through*/ 1.1353 + case "CUE": 1.1354 + // 40 - Collect cue timings and settings. 1.1355 + try { 1.1356 + parseCue(line, self.cue, self.regionList); 1.1357 + } catch (e) { 1.1358 + self.reportOrThrowError(e); 1.1359 + // In case of an error ignore rest of the cue. 1.1360 + self.cue = null; 1.1361 + self.state = "BADCUE"; 1.1362 + continue; 1.1363 + } 1.1364 + self.state = "CUETEXT"; 1.1365 + continue; 1.1366 + case "CUETEXT": 1.1367 + // 41-53 - Collect the cue text, create a cue, and add it to the output. 1.1368 + if (!line) { 1.1369 + // We are done parsing self cue. 1.1370 + self.oncue && self.oncue(self.cue); 1.1371 + self.cue = null; 1.1372 + self.state = "ID"; 1.1373 + continue; 1.1374 + } 1.1375 + if (self.cue.text) { 1.1376 + self.cue.text += "\n"; 1.1377 + } 1.1378 + self.cue.text += line; 1.1379 + continue; 1.1380 + case "BADCUE": // BADCUE 1.1381 + // 54-62 - Collect and discard the remaining cue. 1.1382 + if (!line) { 1.1383 + self.state = "ID"; 1.1384 + } 1.1385 + continue; 1.1386 + } 1.1387 + } 1.1388 + } catch (e) { 1.1389 + self.reportOrThrowError(e); 1.1390 + 1.1391 + // If we are currently parsing a cue, report what we have. 1.1392 + if (self.state === "CUETEXT" && self.cue && self.oncue) { 1.1393 + self.oncue(self.cue); 1.1394 + } 1.1395 + self.cue = null; 1.1396 + // Enter BADWEBVTT state if header was not parsed correctly otherwise 1.1397 + // another exception occurred so enter BADCUE state. 1.1398 + self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE"; 1.1399 + } 1.1400 + return this; 1.1401 + }, 1.1402 + flush: function () { 1.1403 + var self = this; 1.1404 + try { 1.1405 + // Finish decoding the stream. 1.1406 + self.buffer += self.decoder.decode(); 1.1407 + // Synthesize the end of the current cue or region. 1.1408 + if (self.cue || self.state === "HEADER") { 1.1409 + self.buffer += "\n\n"; 1.1410 + self.parse(); 1.1411 + } 1.1412 + // If we've flushed, parsed, and we're still on the INITIAL state then 1.1413 + // that means we don't have enough of the stream to parse the first 1.1414 + // line. 1.1415 + if (self.state === "INITIAL") { 1.1416 + throw new ParsingError(ParsingError.Errors.BadSignature); 1.1417 + } 1.1418 + } catch(e) { 1.1419 + self.reportOrThrowError(e); 1.1420 + } 1.1421 + self.onflush && self.onflush(); 1.1422 + return this; 1.1423 + } 1.1424 + }; 1.1425 + 1.1426 + global.WebVTT = WebVTT; 1.1427 + 1.1428 +}(this));