imsc
Version:
Renders IMSC 1.1 documents to HTML5 fragments
408 lines (269 loc) • 11.3 kB
JavaScript
/*
* Copyright (c) 2016, Pierre-Anthony Lemieux <pal@sandflow.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @module imscUtils
*/
;
(function (imscUtils) { // wrapper for non-node envs
/* Documents the error handler interface */
/**
* @classdesc Generic interface for handling events. The interface exposes four
* methods:
* * <pre>info</pre>: unusual event that does not result in an inconsistent state
* * <pre>warn</pre>: unexpected event that should not result in an inconsistent state
* * <pre>error</pre>: unexpected event that may result in an inconsistent state
* * <pre>fatal</pre>: unexpected event that results in an inconsistent state
* and termination of processing
* Each method takes a single <pre>string</pre> describing the event as argument,
* and returns a single <pre>boolean</pre>, which terminates processing if <pre>true</pre>.
*
* @name ErrorHandler
* @class
*/
/*
* Parses a TTML color expression
*
*/
var HEX_COLOR_RE = /#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})?/;
var DEC_COLOR_RE = /rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/;
var DEC_COLORA_RE = /rgba\(\s*(\d+),\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/;
var NAMED_COLOR = {
transparent: [0, 0, 0, 0],
black: [0, 0, 0, 255],
silver: [192, 192, 192, 255],
gray: [128, 128, 128, 255],
white: [255, 255, 255, 255],
maroon: [128, 0, 0, 255],
red: [255, 0, 0, 255],
purple: [128, 0, 128, 255],
fuchsia: [255, 0, 255, 255],
magenta: [255, 0, 255, 255],
green: [0, 128, 0, 255],
lime: [0, 255, 0, 255],
olive: [128, 128, 0, 255],
yellow: [255, 255, 0, 255],
navy: [0, 0, 128, 255],
blue: [0, 0, 255, 255],
teal: [0, 128, 128, 255],
aqua: [0, 255, 255, 255],
cyan: [0, 255, 255, 255]
};
imscUtils.parseColor = function (str) {
var m;
var r = null;
if (str in NAMED_COLOR) {
r = NAMED_COLOR[str];
} else if ((m = HEX_COLOR_RE.exec(str)) !== null) {
r = [parseInt(m[1], 16),
parseInt(m[2], 16),
parseInt(m[3], 16),
(m[4] !== undefined ? parseInt(m[4], 16) : 255)];
} else if ((m = DEC_COLOR_RE.exec(str)) !== null) {
r = [parseInt(m[1]),
parseInt(m[2]),
parseInt(m[3]),
255];
} else if ((m = DEC_COLORA_RE.exec(str)) !== null) {
r = [parseInt(m[1]),
parseInt(m[2]),
parseInt(m[3]),
parseInt(m[4])];
}
return r;
};
var LENGTH_RE = /^((?:\+|\-)?\d*(?:\.\d+)?)(px|em|c|%|rh|rw)$/;
imscUtils.parseLength = function (str) {
var m;
var r = null;
if ((m = LENGTH_RE.exec(str)) !== null) {
r = {value: parseFloat(m[1]), unit: m[2]};
}
return r;
};
imscUtils.parseTextShadow = function (str) {
var shadows = str.split(",");
var r = [];
for (var i in shadows) {
var shadow = shadows[i].split(" ");
if (shadow.length === 1 && shadow[0] === "none") {
return "none";
} else if (shadow.length > 1 && shadow.length < 5) {
var out_shadow = [null, null, null, null];
/* x offset */
var l = imscUtils.parseLength(shadow.shift());
if (l === null)
return null;
out_shadow[0] = l;
/* y offset */
l = imscUtils.parseLength(shadow.shift());
if (l === null)
return null;
out_shadow[1] = l;
/* is there a third component */
if (shadow.length === 0) {
r.push(out_shadow);
continue;
}
l = imscUtils.parseLength(shadow[0]);
if (l !== null) {
out_shadow[2] = l;
shadow.shift();
}
if (shadow.length === 0) {
r.push(out_shadow);
continue;
}
var c = imscUtils.parseColor(shadow[0]);
if (c === null)
return null;
out_shadow[3] = c;
r.push(out_shadow);
}
}
return r;
};
imscUtils.parsePosition = function (str) {
/* see https://www.w3.org/TR/ttml2/#style-value-position */
var s = str.split(" ");
var isKeyword = function (str) {
return str === "center" ||
str === "left" ||
str === "top" ||
str === "bottom" ||
str === "right";
};
if (s.length > 4) {
return null;
}
/* initial clean-up pass */
for (var j in s) {
if (!isKeyword(s[j])) {
var l = imscUtils.parseLength(s[j]);
if (l === null)
return null;
s[j] = l;
}
}
/* position default */
var pos = {
h: {edge: "left", offset: {value: 50, unit: "%"}},
v: {edge: "top", offset: {value: 50, unit: "%"}}
};
/* update position */
for (var i = 0; i < s.length; ) {
/* extract the current component */
var comp = s[i++];
if (isKeyword(comp)) {
/* we have a keyword */
var offset = {value: 0, unit: "%"};
/* peek at the next component */
if (s.length !== 2 && i < s.length && (!isKeyword(s[i]))) {
/* followed by an offset */
offset = s[i++];
}
/* skip if center */
if (comp === "right") {
pos.h.edge = comp;
pos.h.offset = offset;
} else if (comp === "bottom") {
pos.v.edge = comp;
pos.v.offset = offset;
} else if (comp === "left") {
pos.h.offset = offset;
} else if (comp === "top") {
pos.v.offset = offset;
}
} else if (s.length === 1 || s.length === 2) {
/* we have a bare value */
if (i === 1) {
/* assign it to left edge if first bare value */
pos.h.offset = comp;
} else {
/* assign it to top edge if second bare value */
pos.v.offset = comp;
}
} else {
/* error condition */
return null;
}
}
return pos;
};
imscUtils.ComputedLength = function (rw, rh) {
this.rw = rw;
this.rh = rh;
};
imscUtils.ComputedLength.prototype.toUsedLength = function (width, height) {
return width * this.rw + height * this.rh;
};
/**
* Computes a specified length to a root container relative length
*
* @param {number} lengthVal Length value to be computed
* @param {string} lengthUnit Units of the length value
* @param {number} emScale length of 1em, or null if em is not allowed
* @param {number} percentScale length to which , or null if perecentage is not allowed
* @param {number} cellScale length of 1c, or null if c is not allowed
* @param {number} pxScale length of 1px, or null if px is not allowed
* @param {number} direction 0 if the length is computed in the horizontal direction, 1 if the length is computed in the vertical direction
* @return {number} Computed length
*/
imscUtils.toComputedLength = function(lengthVal, lengthUnit, emLength, percentLength, cellLength, pxLength) {
if (lengthUnit === "%" && percentLength) {
return new imscUtils.ComputedLength(
percentLength.rw * lengthVal / 100,
percentLength.rh * lengthVal / 100
);
} else if (lengthUnit === "em" && emLength) {
return new imscUtils.ComputedLength(
emLength.rw * lengthVal,
emLength.rh * lengthVal
);
} else if (lengthUnit === "c" && cellLength) {
return new imscUtils.ComputedLength(
lengthVal * cellLength.rw,
lengthVal * cellLength.rh
);
} else if (lengthUnit === "px" && pxLength) {
return new imscUtils.ComputedLength(
lengthVal * pxLength.rw,
lengthVal * pxLength.rh
);
} else if (lengthUnit === "rh") {
return new imscUtils.ComputedLength(
0,
lengthVal / 100
);
} else if (lengthUnit === "rw") {
return new imscUtils.ComputedLength(
lengthVal / 100,
0
);
} else {
return null;
}
};
})(typeof exports === 'undefined' ? this.imscUtils = {} : exports);