From b1f337a625035275eb842ef040b173c684fc283a Mon Sep 17 00:00:00 2001 From: Sandra Lokshina Date: Tue, 18 Jul 2017 14:35:05 -0700 Subject: [PATCH] Add style attributes to shaka.text.Cue and TtmlParser. Closes #927. Change-Id: Ife2e241f8e91999ac0f4600766b0b0c04738c013 --- lib/text/cue.js | 49 ++++++++++- lib/text/ttml_text_parser.js | 137 ++++++++++++++++++++++++++++- lib/util/array_utils.js | 12 +++ test/text/ttml_text_parser_unit.js | 115 ++++++++++++++++-------- 4 files changed, 271 insertions(+), 42 deletions(-) diff --git a/lib/text/cue.js b/lib/text/cue.js index c88d418426..2ae48d38b0 100644 --- a/lib/text/cue.js +++ b/lib/text/cue.js @@ -99,6 +99,14 @@ shaka.text.Cue = function(startTime, endTime, payload) { */ this.line = null; + /** + * Separation between line areas inside the cue box in px or em + * (e.g. '100px'/'100em'). If not specified, should be no less than + * the largest font size applied to the text in the cue. + * @type {string}. + */ + this.lineHeight = ''; + /** * Line alignment of the cue box. * @type {shaka.text.Cue.lineAlign} @@ -128,10 +136,10 @@ shaka.text.Cue = function(startTime, endTime, payload) { this.backgroundColor = ''; /** - * Text font size in pixels. - * @type {?number} + * Text font size in px or em (e.g. '100px'/'100em'). + * @type {string} */ - this.fontSize = null; + this.fontSize = ''; /** * Text font weight. Either normal or bold. @@ -139,12 +147,25 @@ shaka.text.Cue = function(startTime, endTime, payload) { */ this.fontWeight = Cue.fontWeight.NORMAL; + /** + * Text font style. Normal, italic or oblique. + * @type {shaka.text.Cue.fontStyle} + */ + this.fontStyle = Cue.fontStyle.NORMAL; + /** * Text font family. * @type {!string} */ this.fontFamily = ''; + /** + * Text decoration. A combination of underline, overline + * and line through. Empty array means no decoration. + * @type {!Array.} + */ + this.textDecoration = []; + /** * Whether or not line wrapping should be applied * to the cue. @@ -239,3 +260,25 @@ shaka.text.Cue.fontWeight = { BOLD: 700 }; + +/** + * @enum {string} + * @export + */ +shaka.text.Cue.fontStyle = { + NORMAL: 'normal', + ITALIC: 'italic', + OBLIQUE: 'oblique' +}; + + +/** + * @enum {string} + * @export + */ +shaka.text.Cue.textDecoration = { + UNDERLINE: 'underline', + LINE_THROUGH: 'lineThrough', + OVERLINE: 'overline' +}; + diff --git a/lib/text/ttml_text_parser.js b/lib/text/ttml_text_parser.js index b6656a329f..1c68f2c7e7 100644 --- a/lib/text/ttml_text_parser.js +++ b/lib/text/ttml_text_parser.js @@ -20,6 +20,7 @@ goog.provide('shaka.text.TtmlTextParser'); goog.require('goog.asserts'); goog.require('shaka.text.Cue'); goog.require('shaka.text.TextEngine'); +goog.require('shaka.util.ArrayUtils'); goog.require('shaka.util.Error'); goog.require('shaka.util.StringUtils'); @@ -172,6 +173,14 @@ shaka.text.TtmlTextParser.timeHMSFormat_ = shaka.text.TtmlTextParser.percentValues_ = /^(\d{1,2}|100)% (\d{1,2}|100)%$/; +/** + * @const + * @private {!RegExp} + * @example 100px + */ +shaka.text.TtmlTextParser.unitValues_ = /^(\d+px|\d+em)$/; + + /** * @const * @private {!Object} @@ -422,6 +431,87 @@ shaka.text.TtmlTextParser.addStyle_ = function( cueElement, region, styles, 'tts:wrapOption'); if (wrapOption && wrapOption == 'noWrap') cue.wrapLine = false; + + var lineHeight = TtmlTextParser.getStyleAttribute_( + cueElement, region, styles, 'tts:lineHeight'); + if (lineHeight && lineHeight.match(TtmlTextParser.unitValues_)) + cue.lineHeight = lineHeight; + + var fontSize = TtmlTextParser.getStyleAttribute_( + cueElement, region, styles, 'tts:fontSize'); + if (fontSize && fontSize.match(TtmlTextParser.unitValues_)) + cue.fontSize = fontSize; + + var fontStyle = TtmlTextParser.getStyleAttribute_( + cueElement, region, styles, 'tts:fontStyle'); + if (fontStyle) { + goog.asserts.assert(fontStyle.toUpperCase() in Cue.fontStyle, + fontStyle.toUpperCase() + + ' Should be in Cue.fontStyle values!'); + cue.fontStyle = Cue.fontStyle[fontStyle.toUpperCase()]; + } + + // Text decoration is an array of values which can come both + // from the element's style or be inherited from elements' + // parent nodes. All of those values should be applied as long + // as they don't contradict each other. If they do, elements' + // own style gets preference. + var textDecorationRegion = TtmlTextParser.getStyleAttributeFromRegion_( + region, styles, 'tts:textDecoration'); + if (textDecorationRegion) + TtmlTextParser.addTextDecoration_(cue, textDecorationRegion); + + var textDecorationElement = TtmlTextParser.getStyleAttributeFromElement_( + cueElement, styles, 'tts:textDecoration'); + if (textDecorationElement) + TtmlTextParser.addTextDecoration_(cue, textDecorationElement); +}; + + +/** + * Parses text decoration values and adds/removes them to/from the cue. + * + * @param {!shaka.text.Cue} cue + * @param {string} decoration + * @private + */ +shaka.text.TtmlTextParser.addTextDecoration_ = function(cue, decoration) { + var Cue = shaka.text.Cue; + var values = decoration.split(' '); + for (var i = 0; i < values.length; i++) { + switch (values[i]) { + case 'underline': + if (!cue.textDecoration.includes(Cue.textDecoration.UNDERLINE)) + cue.textDecoration.push(Cue.textDecoration.UNDERLINE); + break; + case 'noUnderline': + if (cue.textDecoration.includes(Cue.textDecoration.UNDERLINE)) { + shaka.util.ArrayUtils.remove(cue.textDecoration, + Cue.textDecoration.UNDERLINE); + } + break; + case 'lineThrough': + if (!cue.textDecoration.includes(Cue.textDecoration.LINE_THROUGH)) + cue.textDecoration.push(Cue.textDecoration.LINE_THROUGH); + break; + case 'noLineThrough': + if (cue.textDecoration.includes(Cue.textDecoration.LINE_THROUGH)) { + shaka.util.ArrayUtils.remove(cue.textDecoration, + Cue.textDecoration.LINE_THROUGH); + } + break; + case 'overline': + if (!cue.textDecoration.includes(Cue.textDecoration.OVERLINE)) + cue.textDecoration.push(Cue.textDecoration.OVERLINE); + break; + case 'noOverline': + if (cue.textDecoration.includes(Cue.textDecoration.OVERLINE)) { + shaka.util.ArrayUtils.remove(cue.textDecoration, + Cue.textDecoration.OVERLINE); + } + break; + } + } }; @@ -438,9 +528,31 @@ shaka.text.TtmlTextParser.addStyle_ = function( */ shaka.text.TtmlTextParser.getStyleAttribute_ = function( cueElement, region, styles, attribute) { - // An attribute can be specified on region level or in a styling block // associated with the region or original element. + var TtmlTextParser = shaka.text.TtmlTextParser; + var attr = TtmlTextParser.getStyleAttributeFromElement_( + cueElement, styles, attribute); + if (attr) + return attr; + + return TtmlTextParser.getStyleAttributeFromRegion_( + region, styles, attribute); +}; + + +/** + * Finds a specified attribute on the element's associated region + * and returns the value if the attribute was found. + * + * @param {Element} region + * @param {!Array.} styles + * @param {string} attribute + * @return {?string} + * @private + */ +shaka.text.TtmlTextParser.getStyleAttributeFromRegion_ = function( + region, styles, attribute) { var regionChildren = shaka.text.TtmlTextParser.getLeafNodes_(region); for (var i = 0; i < regionChildren.length; i++) { var attr = regionChildren[i].getAttribute(attribute); @@ -448,10 +560,29 @@ shaka.text.TtmlTextParser.getStyleAttribute_ = function( return attr; } + var style = shaka.text.TtmlTextParser.getElementFromCollection_( + region, 'style', styles); + if (style) + return style.getAttribute(attribute); + return null; +}; + + +/** + * Finds a specified attribute on the cue element and returns the value + * if the attribute was found. + * + * @param {!Element} cueElement + * @param {!Array.} styles + * @param {string} attribute + * @return {?string} + * @private + */ +shaka.text.TtmlTextParser.getStyleAttributeFromElement_ = function( + cueElement, styles, attribute) { var getElementFromCollection_ = shaka.text.TtmlTextParser.getElementFromCollection_; - var style = getElementFromCollection_(region, 'style', styles) || - getElementFromCollection_(cueElement, 'style', styles); + var style = getElementFromCollection_(cueElement, 'style', styles); if (style) return style.getAttribute(attribute); return null; diff --git a/lib/util/array_utils.js b/lib/util/array_utils.js index f3c9bf71bc..6ecff1a911 100644 --- a/lib/util/array_utils.js +++ b/lib/util/array_utils.js @@ -68,3 +68,15 @@ shaka.util.ArrayUtils.indexOf = function(array, value, compareFn) { return -1; }; + +/** + * Remove given element from array (assumes no duplicates). + * @param {!Array.} array + * @param {T} element + * @template T + */ +shaka.util.ArrayUtils.remove = function(array, element) { + var index = array.indexOf(element); + if (index > -1) + array.splice(index, 1); +}; diff --git a/test/text/ttml_text_parser_unit.js b/test/text/ttml_text_parser_unit.js index f48b9ea8da..02fa90e8d8 100644 --- a/test/text/ttml_text_parser_unit.js +++ b/test/text/ttml_text_parser_unit.js @@ -558,7 +558,10 @@ describe('TtmlTextParser', function() { color: 'red', backgroundColor: 'blue', fontWeight: Cue.fontWeight.BOLD, - fontFamily: 'Times New Roman' + fontFamily: 'Times New Roman', + fontStyle: Cue.fontStyle.ITALIC, + lineHeight: '20px', + fontSize: '10em' } ], '' + @@ -566,7 +569,10 @@ describe('TtmlTextParser', function() { '