content/media/webvtt/vtt.jsm

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial