diff --git a/lib/vtt.js b/lib/vtt.js index 48ce4504..97f3636f 100644 --- a/lib/vtt.js +++ b/lib/vtt.js @@ -276,6 +276,54 @@ var TAG_NAME = { lang: "span" }; +// 5.1 default text color +// 5.2 default text background color is equivalent to text color with bg_ prefix +var DEFAULT_COLOR_CLASS = { + white: 'rgba(255,255,255,1)', + lime: 'rgba(0,255,0,1)', + cyan: 'rgba(0,255,255,1)', + red: 'rgba(255,0,0,1)', + yellow: 'rgba(255,255,0,1)', + magenta: 'rgba(255,0,255,1)', + blue: 'rgba(0,0,255,1)', + black: 'rgba(0,0,0,1)' +}; + +var ALLOWED_CSS_PROPS = { + 'color': 1, + 'opacity': 1, + 'visibility': 1, + 'text-decoration': 1, + 'text-decoration-color': 1, + 'text-decoration-style': 1, + 'text-decoration-line': 1, + 'text-shadow': 1, + 'background': 1, + 'background-image': 1, + 'background-position': 1, + 'background-size': 1, + 'background-repeat': 1, + 'background-origin': 1, + 'background-clip': 1, + 'background-attachment': 1, + 'background-color': 1, + 'outline': 1, + 'outline-color': 1, + 'outline-style': 1, + 'outline-width': 1, + 'font': 1, + 'font-style': 1, + 'font-variant': 1, + 'font-weight': 1, + 'font-stretch': 1, + 'font-size': 1, + 'line-height': 1, + 'font-family': 1, + 'white-space': 1, + 'text-combine-upright': 1, + 'ruby-position': 1 +}; + var TAG_ANNOTATION = { v: "title", lang: "lang" @@ -286,7 +334,11 @@ var NEEDS_PARENT = { }; // Parse content into a document fragment. -function parseContent(window, input) { +function parseContent(window, input, styles) { + if (!styles) { + styles = []; + } + function nextToken() { // Check for end-of-string. if (!input) { @@ -341,6 +393,23 @@ function parseContent(window, input) { t, tagStack = []; + styles.forEach(function(style) { + var s = css.parse(style.css); + s.stylesheet.rules.forEach(function(rule) { + if (rule.type === 'comment') { + return; + } + if (rule.selectors.indexOf('::cue') !== -1) { + rule.declarations.forEach(function(decl) { + if (ALLOWED_CSS_PROPS.hasOwnProperty(decl.property)) { + rootDiv.style[decl.property] = decl.value; + } + }); + } + }); + }); + + while ((t = nextToken()) !== null) { if (t[0] === '<') { if (t[1] === "/") { @@ -378,7 +447,22 @@ function parseContent(window, input) { } // Set the class list (as a list of classes, separated by space). if (m[2]) { - node.className = m[2].substr(1).replace('.', ' '); + var classes = m[2].split('.'); + + classes.forEach(function(cl) { + var bgColor = /^bg_/.test(cl); + // slice out `bg_` if it's a background color + var colorName = bgColor ? cl.slice(3) : cl; + + if (DEFAULT_COLOR_CLASS.hasOwnProperty(colorName)) { + var propName = bgColor ? 'background-color' : 'color'; + var propValue = DEFAULT_COLOR_CLASS[colorName]; + + node.style[propName] = propValue; + } + }); + + node.className = classes.join(' '); } // Append the node to the current node, and enter the scope of the new // node. @@ -530,13 +614,13 @@ StyleBox.prototype.formatStyle = function(val, unit) { // Constructs the computed display state of the cue (a div). Places the div // into the overlay which should be a block level element (usually a div). -function CueStyleBox(window, cue, styleOptions) { +function CueStyleBox(window, cue, styleOptions, styles) { StyleBox.call(this); this.cue = cue; // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will // have inline positioning and will function as the cue background box. - this.cueDiv = parseContent(window, cue.text); + this.cueDiv = parseContent(window, cue.text, styles); var styles = { color: "rgba(255, 255, 255, 1)", backgroundColor: "rgba(0, 0, 0, 0.8)", @@ -930,7 +1014,7 @@ var CUE_BACKGROUND_PADDING = "1.5%"; // Runs the processing model over the cues and regions passed to it. // @param overlay A block level element (usually a div) that the computed cues // and regions will be placed into. -WebVTT.processCues = function(window, cues, overlay) { +WebVTT.processCues = function(window, cues, overlay, styles) { if (!window || !cues || !overlay) { return null; } @@ -983,7 +1067,7 @@ WebVTT.processCues = function(window, cues, overlay) { cue = cues[i]; // Compute the intial position and styles of the cue div. - styleBox = new CueStyleBox(window, cue, styleOptions); + styleBox = new CueStyleBox(window, cue, styleOptions, styles); paddedOverlay.appendChild(styleBox.div); // Move the cue div to it's correct line position. @@ -1028,6 +1112,8 @@ WebVTT.Parser.prototype = { parse: function (data) { var self = this; + self.styles = []; + // If there is no data then we won't decode it, but will just try to parse // whatever is in buffer already. This may occur in circumstances, for // example when flush() is called. @@ -1036,6 +1122,10 @@ WebVTT.Parser.prototype = { self.buffer += self.decoder.decode(data, {stream: true}); } + function skipWhitespace() { + self.buffer = self.buffer.replace(/^\s+/, ''); + } + function collectNextLine() { var buffer = self.buffer; var pos = 0; @@ -1181,6 +1271,7 @@ WebVTT.Parser.prototype = { } var alreadyCollectedLine = false; + var sawCue = false; while (self.buffer) { // We can't parse a line until we have the full line. if (!/\r\n|\n/.test(self.buffer)) { @@ -1200,15 +1291,58 @@ WebVTT.Parser.prototype = { parseHeader(line); } else if (!line) { // An empty line terminates the header and starts the body (cues). - self.state = "ID"; + self.state = "BLOCKS"; } continue; + case "REGION": + if (!line) { + self.state = "BLOCKS"; + break; + } + continue; + case "STYLE": + if (!line) { + self.state = "BLOCKS"; + break; + } + self.style.css += line + "\n"; + continue; case "NOTE": // Ignore NOTE blocks. if (!line) { self.state = "ID"; } continue; + case "BLOCKS": + console.log('blocks', line); + + if (!line) { + continue; + } + + // Check for the start of NOTE blocks. + if (/^NOTE($|[ \t])/.test(line)) { + self.state = "NOTE"; + break; + } + + // Check for the start of the REGION blocks + if (/^REGION/.test(line) && !sawCue) { + self.state = "REGION"; + break; + } + + // Check for the start of the STYLE blocks + if (/^STYLE/.test(line) && !sawCue) { + self.state = "STYLE"; + self.style = {css: ''}; + self.styles.push(self.style); + break; + } + + self.state = "ID"; + // Process line as an ID. + /*falls through*/ case "ID": // Check for the start of NOTE blocks. if (/^NOTE($|[ \t])/.test(line)) { @@ -1219,6 +1353,8 @@ WebVTT.Parser.prototype = { if (!line) { continue; } + console.log(line); + sawCue = true; self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, ""); self.state = "CUE"; // 30-39 - Check if self line contains an optional identifier or timing data.