content/media/webvtt/vtt.jsm

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

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

mercurial