Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 | "&": "&", |
michael@0 | 270 | "<": "<", |
michael@0 | 271 | ">": ">", |
michael@0 | 272 | "‎": "\u200e", |
michael@0 | 273 | "‏": "\u200f", |
michael@0 | 274 | " ": "\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)); |