rx-player
Version:
Canal+ HTML5 Video Player
478 lines (477 loc) • 19.7 kB
JavaScript
;
/**
* Copyright 2015 CANAL+ Group
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = createElement;
var add_class_name_1 = require("../../../../compat/add_class_name");
var is_non_empty_string_1 = require("../../../../utils/is_non_empty_string");
var object_assign_1 = require("../../../../utils/object_assign");
var get_styling_1 = require("../get_styling");
var xml_utils_1 = require("../xml_utils");
var apply_extent_1 = require("./apply_extent");
var apply_font_size_1 = require("./apply_font_size");
var apply_line_height_1 = require("./apply_line_height");
var apply_origin_1 = require("./apply_origin");
var apply_padding_1 = require("./apply_padding");
var generate_css_test_outline_1 = require("./generate_css_test_outline");
var ttml_color_to_css_color_1 = require("./ttml_color_to_css_color");
// Styling which can be applied to <span> from any level upper.
// Added here as an optimization
var SPAN_LEVEL_ATTRIBUTES = [
"color",
"direction",
"display",
"fontFamily",
"fontSize",
"fontStyle",
"fontWeight",
"textDecoration",
"textOutline",
"unicodeBidi",
"visibility",
"wrapOption",
];
// TODO
// tts:showBackground (applies to region)
// tts:zIndex (applies to region)
/**
* Apply style set for a singular text span of the current cue.
* @param {HTMLElement} element - The text span
* @param {Object} style - The style to apply
*/
function applyTextStyle(element, style, shouldTrimWhiteSpace) {
// applies to span
var color = style.color;
if ((0, is_non_empty_string_1.default)(color)) {
element.style.color = (0, ttml_color_to_css_color_1.default)(color);
}
// applies to body, div, p, region, span
var backgroundColor = style.backgroundColor;
if ((0, is_non_empty_string_1.default)(backgroundColor)) {
element.style.backgroundColor = (0, ttml_color_to_css_color_1.default)(backgroundColor);
}
// applies to span
var textOutline = style.textOutline;
if ((0, is_non_empty_string_1.default)(textOutline)) {
var outlineData = textOutline.trim().replace(/\s+/g, " ").split(" ");
var len = outlineData.length;
if (len === 3) {
var outlineColor = (0, ttml_color_to_css_color_1.default)(outlineData[0]);
var thickness = outlineData[1];
element.style.textShadow = (0, generate_css_test_outline_1.default)(outlineColor, thickness);
}
else if ((0, is_non_empty_string_1.default)(color) && len === 1) {
var thickness = outlineData[0];
element.style.textShadow = (0, generate_css_test_outline_1.default)(color, thickness);
}
else if (len === 2) {
var isFirstArgAColor = /^[#A-Z]/i.test(outlineData[0]);
var isFirstArgANumber = /^[0-9]/.test(outlineData[0]);
// XOR-ing to be sure we get what we have
if (isFirstArgAColor !== isFirstArgANumber) {
if (isFirstArgAColor) {
var outlineColor = (0, ttml_color_to_css_color_1.default)(outlineData[0]);
var thickness = outlineData[1];
element.style.textShadow = (0, generate_css_test_outline_1.default)(outlineColor, thickness);
}
else if ((0, is_non_empty_string_1.default)(color)) {
var thickness = outlineData[0];
element.style.textShadow = (0, generate_css_test_outline_1.default)(color, thickness);
}
}
}
}
// applies to span
var textDecoration = style.textDecoration;
if ((0, is_non_empty_string_1.default)(textDecoration)) {
switch (textDecoration) {
case "noUnderline":
case "noLineThrough":
case "noOverline":
element.style.textDecoration = "none";
break;
case "lineThrough":
element.style.textDecoration = "line-through";
break;
default:
element.style.textDecoration = textDecoration;
break;
}
}
// applies to span
var fontFamily = style.fontFamily;
if ((0, is_non_empty_string_1.default)(fontFamily)) {
switch (fontFamily) {
case "proportionalSansSerif":
element.style.fontFamily = "Arial, Helvetica, Liberation Sans, sans-serif";
break;
// TODO monospace or sans-serif or font with both?
case "monospaceSansSerif":
case "sansSerif":
element.style.fontFamily = "sans-serif";
break;
case "monospaceSerif":
case "default":
element.style.fontFamily = "Courier New, Liberation Mono, monospace";
break;
// TODO font with both?
case "proportionalSerif":
element.style.fontFamily = "serif";
break;
default:
element.style.fontFamily = fontFamily;
}
}
// applies to span
var fontStyle = style.fontStyle;
if ((0, is_non_empty_string_1.default)(fontStyle)) {
element.style.fontStyle = fontStyle;
}
// applies to span
var fontWeight = style.fontWeight;
if ((0, is_non_empty_string_1.default)(fontWeight)) {
element.style.fontWeight = fontWeight;
}
// applies to span
var fontSize = style.fontSize;
if ((0, is_non_empty_string_1.default)(fontSize)) {
(0, apply_font_size_1.default)(element, fontSize);
}
else {
(0, add_class_name_1.default)(element, "proportional-style");
element.setAttribute("data-proportional-font-size", "1");
}
// applies to p, span
var direction = style.direction;
if ((0, is_non_empty_string_1.default)(direction)) {
element.style.direction = direction;
}
// applies to p, span
var unicodeBidi = style.unicodeBidi;
if ((0, is_non_empty_string_1.default)(unicodeBidi)) {
switch (unicodeBidi) {
case "bidiOverride":
element.style.unicodeBidi = "bidi-override";
break;
case "embed":
element.style.unicodeBidi = "embed";
break;
default:
element.style.unicodeBidi = "normal";
}
}
// applies to body, div, p, region, span
var visibility = style.visibility;
if ((0, is_non_empty_string_1.default)(visibility)) {
element.style.visibility = visibility;
}
// applies to body, div, p, region, span
var display = style.display;
if (display === "none") {
element.style.display = "none";
}
// applies to body, div, p, region, span
var wrapOption = style.wrapOption;
if (wrapOption === "noWrap") {
if (shouldTrimWhiteSpace) {
element.style.whiteSpace = "nowrap";
}
else {
element.style.whiteSpace = "pre";
}
}
else if (shouldTrimWhiteSpace) {
element.style.whiteSpace = "normal";
}
else {
element.style.whiteSpace = "pre-wrap";
}
}
/**
* Apply style for the general text track div.
* @param {HTMLElement} element - The <div> the style will be applied on.
* @param {Object} style - The general style object of the paragraph.
*/
function applyGeneralStyle(element, style) {
// Set default text color. It can be overrided by text element color.
element.style.color = "white";
element.style.position = "absolute";
// applies to tt, region
var extent = style.extent;
if ((0, is_non_empty_string_1.default)(extent)) {
(0, apply_extent_1.default)(element, extent);
}
// applies to region
var writingMode = style.writingMode;
if ((0, is_non_empty_string_1.default)(writingMode)) {
// TODO
}
// applies to region
var overflow = style.overflow;
element.style.overflow = (0, is_non_empty_string_1.default)(overflow) ? overflow : "hidden";
// applies to region
var padding = style.padding;
if ((0, is_non_empty_string_1.default)(padding)) {
(0, apply_padding_1.default)(element, padding);
}
// applies to region
var origin = style.origin;
if ((0, is_non_empty_string_1.default)(origin)) {
(0, apply_origin_1.default)(element, origin);
}
// applies to region
var displayAlign = style.displayAlign;
if ((0, is_non_empty_string_1.default)(displayAlign)) {
element.style.display = "flex";
element.style.flexDirection = "column";
switch (displayAlign) {
case "before":
element.style.justifyContent = "flex-start";
break;
case "center":
element.style.justifyContent = "center";
break;
case "after":
element.style.justifyContent = "flex-end";
break;
}
}
// applies to region
var opacity = style.opacity;
if ((0, is_non_empty_string_1.default)(opacity)) {
element.style.opacity = opacity;
}
// applies to body, div, p, region, span
var visibility = style.visibility;
if ((0, is_non_empty_string_1.default)(visibility)) {
element.style.visibility = visibility;
}
// applies to body, div, p, region, span
var display = style.display;
if (display === "none") {
element.style.display = "none";
}
}
/**
* Apply style set for a <p> element
* @param {HTMLElement} element - The <p> element
* @param {Object} style - The general style object of the paragraph.
*/
function applyPStyle(element, style) {
element.style.margin = "0px";
// Set on it the default font-size, more specific font sizes may then be set
// on children elements.
// Doing this on the parent <p> elements seems to fix some CSS issues we had
// with too large inner line breaks spacing when the text track element was
// too small, for some reasons.
(0, add_class_name_1.default)(element, "proportional-style");
element.setAttribute("data-proportional-font-size", "1");
// applies to body, div, p, region, span
var paragraphBackgroundColor = style.backgroundColor;
if ((0, is_non_empty_string_1.default)(paragraphBackgroundColor)) {
element.style.backgroundColor = (0, ttml_color_to_css_color_1.default)(paragraphBackgroundColor);
}
// applies to p
var lineHeight = style.lineHeight;
if ((0, is_non_empty_string_1.default)(lineHeight)) {
(0, apply_line_height_1.default)(element, lineHeight);
}
// applies to p
var textAlign = style.textAlign;
if ((0, is_non_empty_string_1.default)(textAlign)) {
switch (textAlign) {
case "center":
element.style.textAlign = "center";
break;
case "left":
case "start":
// TODO check what start means (difference with left, writing direction?)
element.style.textAlign = "left";
break;
case "right":
case "end":
// TODO check what end means (difference with right, writing direction?)
element.style.textAlign = "right";
break;
}
}
}
/**
* Creates span of text for the given #text element, with the right style.
*
* TODO create text elements as string? Might help performances.
* @param {Element} el - the #text element, which text content should be
* displayed
* @param {Object} style - the style object for the given text
* @param {Boolean} shouldTrimWhiteSpace - True if the space should be
* trimmed.
* @returns {HTMLElement}
*/
function createTextElement(el, style, shouldTrimWhiteSpace) {
var textElement = document.createElement("span");
var textContent = el.textContent === null ? "" : el.textContent;
if (shouldTrimWhiteSpace) {
// 1. Trim leading and trailing whitespace.
// 2. Collapse multiple spaces into one.
var trimmed = textContent.trim();
trimmed = trimmed.replace(/\s+/g, " ");
textContent = trimmed;
}
var textNode = document.createTextNode(textContent);
textElement.appendChild(textNode);
textElement.className = "rxp-texttrack-span";
applyTextStyle(textElement, style, shouldTrimWhiteSpace);
return textElement;
}
/**
* Generate every text elements to display in a given paragraph.
* @param {Element} paragraph - The <p> tag.
* @param {Array.<Object>} regions
* @param {Array.<Object>} styles
* @param {Object} paragraphStyle - The general style object of the paragraph.
* @param {Boolean} shouldTrimWhiteSpace
* @returns {Array.<HTMLElement>}
*/
function generateTextContent(paragraph, regions, styles, paragraphStyle, shouldTrimWhiteSpace) {
/**
* Recursive function, taking a node in argument and returning the
* corresponding array of HTMLElement in order.
* @param {Node} node - the node in question
* @param {Object} style - the current state of the style for the node.
* /!\ The style object can be mutated, provide a copy of it.
* @param {Array.<Element>} spans - The spans parent of this node.
* @param {Boolean} shouldTrimWhiteSpaceFromParent - True if the space should be
* trimmed by default. From the parent xml:space parameter.
* @returns {Array.<HTMLElement>}
*/
function loop(node, style, spans, shouldTrimWhiteSpaceFromParent) {
var childNodes = node.childNodes;
var elements = [];
for (var i = 0; i < childNodes.length; i++) {
var currentNode = childNodes[i];
if (currentNode.nodeName === "#text") {
var backgroundColor = (0, get_styling_1.getStylingAttributes)(["backgroundColor"], spans, styles, regions).backgroundColor;
if ((0, is_non_empty_string_1.default)(backgroundColor)) {
style.backgroundColor = backgroundColor;
}
else {
style.backgroundColor = "";
}
var el = createTextElement(currentNode, style, shouldTrimWhiteSpaceFromParent);
elements.push(el);
}
else if ((0, xml_utils_1.isLineBreakElement)(currentNode)) {
var br = document.createElement("BR");
elements.push(br);
}
else if ((0, xml_utils_1.isSpanElement)(currentNode) &&
currentNode.nodeType === Node.ELEMENT_NODE &&
currentNode.childNodes.length > 0) {
var spaceAttribute = currentNode.getAttribute("xml:space");
var shouldTrimWhiteSpaceOnSpan = (0, is_non_empty_string_1.default)(spaceAttribute)
? spaceAttribute === "default"
: shouldTrimWhiteSpaceFromParent;
// compute the new applyable style
var newStyle = (0, object_assign_1.default)({}, style, (0, get_styling_1.getStylingAttributes)(SPAN_LEVEL_ATTRIBUTES, [currentNode], styles, regions));
elements.push.apply(elements, __spreadArray([], __read(loop(currentNode, newStyle, __spreadArray([currentNode], __read(spans), false), shouldTrimWhiteSpaceOnSpan)), false));
}
}
return elements;
}
return loop(paragraph, (0, object_assign_1.default)({}, paragraphStyle), [], shouldTrimWhiteSpace);
}
/**
* @param {Element} paragraph
* @param {Element} body
* @param {Array.<Object>} regions
* @param {Array.<Object>} styles
* @param {Object} paragraphStyle
* @param {Object}
* @returns {HTMLElement}
*/
function createElement(paragraph, body, regions, styles, paragraphStyle, _a) {
var cellResolution = _a.cellResolution, shouldTrimWhiteSpace = _a.shouldTrimWhiteSpace;
var divs = (0, xml_utils_1.getParentDivElements)(paragraph);
var parentElement = document.createElement("DIV");
parentElement.className = "rxp-texttrack-region";
parentElement.setAttribute("data-resolution-columns", String(cellResolution.columns));
parentElement.setAttribute("data-resolution-rows", String(cellResolution.rows));
applyGeneralStyle(parentElement, paragraphStyle);
if (body !== null) {
// applies to body, div, p, region, span
var bodyBackgroundColor = (0, get_styling_1.getStylingAttributes)(["backgroundColor"], __spreadArray(__spreadArray([], __read(divs), false), [body], false), styles, regions).bodyBackgroundColor;
if ((0, is_non_empty_string_1.default)(bodyBackgroundColor)) {
parentElement.style.backgroundColor = (0, ttml_color_to_css_color_1.default)(bodyBackgroundColor);
}
}
var pElement = document.createElement("p");
pElement.className = "rxp-texttrack-p";
applyPStyle(pElement, paragraphStyle);
var textContent = generateTextContent(paragraph, regions, styles, paragraphStyle, shouldTrimWhiteSpace);
for (var i = 0; i < textContent.length; i++) {
pElement.appendChild(textContent[i]);
}
// NOTE:
// The following code is for the inclusion of div elements. This has no
// advantage for now, and might only with future evolutions.
// (This is only an indication of what the base of the code could look like).
// if (divs.length) {
// let container = parentElement;
// for (let i = divs.length - 1; i >= 0; i--) {
// // TODO manage style at div level?
// // They are: visibility, display and backgroundColor
// // All these do not have any difference if applied to the <p> element
// // instead of the div.
// // The advantage might only be for multiple <p> elements dispatched
// // in multiple div Which we do not manage anyway for now.
// const divEl = document.createElement("DIV");
// divEl.className = "rxp-texttrack-div";
// container.appendChild(divEl);
// container = divEl;
// }
// container.appendChild(pElement);
// parentElement.appendChild(container);
// } else {
// parentElement.appendChild(pElement);
// }
parentElement.appendChild(pElement);
return parentElement;
}