imsc
Version:
Renders IMSC 1.1 documents to HTML5 fragments
1,230 lines (952 loc) • 33.7 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 imscStyles
*/
;
(function (imscStyles, imscNames, imscUtils) { // wrapper for non-node envs
function StylingAttributeDefinition(ns, name, initialValue, appliesTo, isInherit, isAnimatable, parseFunc, computeFunc) {
this.name = name;
this.ns = ns;
this.qname = ns + " " + name;
this.inherit = isInherit;
this.animatable = isAnimatable;
this.initial = initialValue;
this.applies = appliesTo;
this.parse = parseFunc;
this.compute = computeFunc;
}
imscStyles.all = [
new StylingAttributeDefinition(
imscNames.ns_tts,
"backgroundColor",
"transparent",
['body', 'div', 'p', 'region', 'span'],
false,
true,
imscUtils.parseColor,
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"color",
"white",
['span'],
true,
true,
imscUtils.parseColor,
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"direction",
"ltr",
['p', 'span'],
true,
true,
function (str) {
return str;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"display",
"auto",
['body', 'div', 'p', 'region', 'span'],
false,
true,
function (str) {
return str;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"displayAlign",
"before",
['region'],
false,
true,
function (str) {
return str;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"extent",
"auto",
['tt', 'region'],
false,
true,
function (str) {
if (str === "auto") {
return str;
} else {
var s = str.split(" ");
if (s.length !== 2)
return null;
var w = imscUtils.parseLength(s[0]);
var h = imscUtils.parseLength(s[1]);
if (!h || !w)
return null;
return {'h': h, 'w': w};
}
},
function (doc, parent, element, attr, context) {
var h;
var w;
if (attr === "auto") {
h = new imscUtils.ComputedLength(0, 1);
} else {
h = imscUtils.toComputedLength(
attr.h.value,
attr.h.unit,
null,
doc.dimensions.h,
null,
doc.pxLength.h
);
if (h === null) {
return null;
}
}
if (attr === "auto") {
w = new imscUtils.ComputedLength(1, 0);
} else {
w = imscUtils.toComputedLength(
attr.w.value,
attr.w.unit,
null,
doc.dimensions.w,
null,
doc.pxLength.w
);
if (w === null) {
return null;
}
}
return {'h': h, 'w': w};
}
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"fontFamily",
"default",
['span'],
true,
true,
function (str) {
var ffs = str.split(",");
var rslt = [];
for (var i in ffs) {
if (ffs[i].charAt(0) !== "'" && ffs[i].charAt(0) !== '"') {
if (ffs[i] === "default") {
/* per IMSC1 */
rslt.push("monospaceSerif");
} else {
rslt.push(ffs[i]);
}
} else {
rslt.push(ffs[i]);
}
}
return rslt;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"shear",
"0%",
['p'],
true,
true,
imscUtils.parseLength,
function (doc, parent, element, attr) {
var fs;
if (attr.unit === "%") {
fs = Math.abs(attr.value) > 100 ? Math.sign(attr.value) * 100 : attr.value;
} else {
return null;
}
return fs;
}
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"fontSize",
"1c",
['span'],
true,
true,
imscUtils.parseLength,
function (doc, parent, element, attr, context) {
var fs;
fs = imscUtils.toComputedLength(
attr.value,
attr.unit,
parent !== null ? parent.styleAttrs[imscStyles.byName.fontSize.qname] : doc.cellLength.h,
parent !== null ? parent.styleAttrs[imscStyles.byName.fontSize.qname] : doc.cellLength.h,
doc.cellLength.h,
doc.pxLength.h
);
return fs;
}
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"fontStyle",
"normal",
['span'],
true,
true,
function (str) {
/* TODO: handle font style */
return str;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"fontWeight",
"normal",
['span'],
true,
true,
function (str) {
/* TODO: handle font weight */
return str;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"lineHeight",
"normal",
['p'],
true,
true,
function (str) {
if (str === "normal") {
return str;
} else {
return imscUtils.parseLength(str);
}
},
function (doc, parent, element, attr, context) {
var lh;
if (attr === "normal") {
/* inherit normal per https://github.com/w3c/ttml1/issues/220 */
lh = attr;
} else {
lh = imscUtils.toComputedLength(
attr.value,
attr.unit,
element.styleAttrs[imscStyles.byName.fontSize.qname],
element.styleAttrs[imscStyles.byName.fontSize.qname],
doc.cellLength.h,
doc.pxLength.h
);
if (lh === null) {
return null;
}
}
/* TODO: create a Length constructor */
return lh;
}
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"opacity",
1.0,
['region'],
false,
true,
parseFloat,
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"origin",
"auto",
['region'],
false,
true,
function (str) {
if (str === "auto") {
return str;
} else {
var s = str.split(" ");
if (s.length !== 2)
return null;
var w = imscUtils.parseLength(s[0]);
var h = imscUtils.parseLength(s[1]);
if (!h || !w)
return null;
return {'h': h, 'w': w};
}
},
function (doc, parent, element, attr, context) {
var h;
var w;
if (attr === "auto") {
h = new imscUtils.ComputedLength(0,0);
} else {
h = imscUtils.toComputedLength(
attr.h.value,
attr.h.unit,
null,
doc.dimensions.h,
null,
doc.pxLength.h
);
if (h === null) {
return null;
}
}
if (attr === "auto") {
w = new imscUtils.ComputedLength(0,0);
} else {
w = imscUtils.toComputedLength(
attr.w.value,
attr.w.unit,
null,
doc.dimensions.w,
null,
doc.pxLength.w
);
if (w === null) {
return null;
}
}
return {'h': h, 'w': w};
}
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"overflow",
"hidden",
['region'],
false,
true,
function (str) {
return str;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"padding",
"0px",
['region'],
false,
true,
function (str) {
var s = str.split(" ");
if (s.length > 4)
return null;
var r = [];
for (var i in s) {
var l = imscUtils.parseLength(s[i]);
if (!l)
return null;
r.push(l);
}
return r;
},
function (doc, parent, element, attr, context) {
var padding;
/* TODO: make sure we are in region */
/*
* expand padding shortcuts to
* [before, end, after, start]
*
*/
if (attr.length === 1) {
padding = [attr[0], attr[0], attr[0], attr[0]];
} else if (attr.length === 2) {
padding = [attr[0], attr[1], attr[0], attr[1]];
} else if (attr.length === 3) {
padding = [attr[0], attr[1], attr[2], attr[1]];
} else if (attr.length === 4) {
padding = [attr[0], attr[1], attr[2], attr[3]];
} else {
return null;
}
/* TODO: take into account tts:direction */
/*
* transform [before, end, after, start] according to writingMode to
* [top,left,bottom,right]
*
*/
var dir = element.styleAttrs[imscStyles.byName.writingMode.qname];
if (dir === "lrtb" || dir === "lr") {
padding = [padding[0], padding[3], padding[2], padding[1]];
} else if (dir === "rltb" || dir === "rl") {
padding = [padding[0], padding[1], padding[2], padding[3]];
} else if (dir === "tblr") {
padding = [padding[3], padding[0], padding[1], padding[2]];
} else if (dir === "tbrl" || dir === "tb") {
padding = [padding[3], padding[2], padding[1], padding[0]];
} else {
return null;
}
var out = [];
for (var i in padding) {
if (padding[i].value === 0) {
out[i] = new imscUtils.ComputedLength(0,0);
} else {
out[i] = imscUtils.toComputedLength(
padding[i].value,
padding[i].unit,
element.styleAttrs[imscStyles.byName.fontSize.qname],
i === "0" || i === "2" ? element.styleAttrs[imscStyles.byName.extent.qname].h : element.styleAttrs[imscStyles.byName.extent.qname].w,
i === "0" || i === "2" ? doc.cellLength.h : doc.cellLength.w,
i === "0" || i === "2" ? doc.pxLength.h: doc.pxLength.w
);
if (out[i] === null) return null;
}
}
return out;
}
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"position",
"top left",
['region'],
false,
true,
function (str) {
return imscUtils.parsePosition(str);
},
function (doc, parent, element, attr) {
var h;
var w;
h = imscUtils.toComputedLength(
attr.v.offset.value,
attr.v.offset.unit,
null,
new imscUtils.ComputedLength(
- element.styleAttrs[imscStyles.byName.extent.qname].h.rw,
doc.dimensions.h.rh - element.styleAttrs[imscStyles.byName.extent.qname].h.rh
),
null,
doc.pxLength.h
);
if (h === null) return null;
if (attr.v.edge === "bottom") {
h = new imscUtils.ComputedLength(
- h.rw - element.styleAttrs[imscStyles.byName.extent.qname].h.rw,
doc.dimensions.h.rh - h.rh - element.styleAttrs[imscStyles.byName.extent.qname].h.rh
);
}
w = imscUtils.toComputedLength(
attr.h.offset.value,
attr.h.offset.unit,
null,
new imscUtils.ComputedLength(
doc.dimensions.w.rw - element.styleAttrs[imscStyles.byName.extent.qname].w.rw,
- element.styleAttrs[imscStyles.byName.extent.qname].w.rh
),
null,
doc.pxLength.w
);
if (h === null) return null;
if (attr.h.edge === "right") {
w = new imscUtils.ComputedLength(
doc.dimensions.w.rw - w.rw - element.styleAttrs[imscStyles.byName.extent.qname].w.rw,
- w.rh - element.styleAttrs[imscStyles.byName.extent.qname].w.rh
);
}
return {'h': h, 'w': w};
}
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"ruby",
"none",
['span'],
false,
true,
function (str) {
return str;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"rubyAlign",
"center",
['span'],
true,
true,
function (str) {
if (! (str === "center" || str === "spaceAround")) {
return null;
}
return str;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"rubyPosition",
"outside",
['span'],
true,
true,
function (str) {
return str;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"rubyReserve",
"none",
['p'],
true,
true,
function (str) {
var s = str.split(" ");
var r = [null, null];
if (s.length === 0 || s.length > 2)
return null;
if (s[0] === "none" ||
s[0] === "both" ||
s[0] === "after" ||
s[0] === "before" ||
s[0] === "outside") {
r[0] = s[0];
} else {
return null;
}
if (s.length === 2 && s[0] !== "none") {
var l = imscUtils.parseLength(s[1]);
if (l) {
r[1] = l;
} else {
return null;
}
}
return r;
},
function (doc, parent, element, attr, context) {
if (attr[0] === "none") {
return attr;
}
var fs = null;
if (attr[1] === null) {
fs = new imscUtils.ComputedLength(
element.styleAttrs[imscStyles.byName.fontSize.qname].rw * 0.5,
element.styleAttrs[imscStyles.byName.fontSize.qname].rh * 0.5
);
} else {
fs = imscUtils.toComputedLength(attr[1].value,
attr[1].unit,
element.styleAttrs[imscStyles.byName.fontSize.qname],
element.styleAttrs[imscStyles.byName.fontSize.qname],
doc.cellLength.h,
doc.pxLength.h
);
}
if (fs === null) return null;
return [attr[0], fs];
}
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"showBackground",
"always",
['region'],
false,
true,
function (str) {
return str;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"textAlign",
"start",
['p'],
true,
true,
function (str) {
return str;
},
function (doc, parent, element, attr, context) {
/* Section 7.16.9 of XSL */
if (attr === "left") {
return "start";
} else if (attr === "right") {
return "end";
} else {
return attr;
}
}
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"textCombine",
"none",
['span'],
true,
true,
function (str) {
var s = str.split(" ");
if (s.length === 1) {
if (s[0] === "none" || s[0] === "all") {
return [s[0]];
} else if (s[0] === "digits") {
return [s[0], 2];
}
} else if (s.length === 2) {
if (s[0] === "digits") {
var num = parseInt(s[1], 10);
if (!isNaN(num)) {
return [s[0], num];
}
}
}
return null;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"textDecoration",
"none",
['span'],
true,
true,
function (str) {
return str.split(" ");
},
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"textEmphasis",
"none",
['span'],
false,
true,
function (str) {
var e = str.split(" ");
var rslt = {style: "filled", symbol: "circle", color: null, position: null};
for (var i in e) {
if (e[i] === "none" || e[i] === "auto") {
rslt.style = e[i];
} else if (e[i] === "filled" ||
e[i] === "open") {
rslt.style = e[i];
} else if (e[i] === "circle" ||
e[i] === "dot" ||
e[i] === "sesame") {
rslt.symbol = e[i];
} else if (e[i] === "current") {
rslt.color = e[i];
} else if (e[i] === "outside") {
rslt.position = "outside";
} else if (e[i] === "before" || e[i] === "after") {
return null;
} else {
rslt.color = imscUtils.parseColor(e[i]);
if (rslt.color === null)
return null;
}
}
return rslt;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"textOutline",
"none",
['span'],
true,
true,
function (str) {
/*
* returns {c: <color>?, thichness: <length>} | "none"
*
*/
if (str === "none") {
return str;
} else {
var r = {};
var s = str.split(" ");
if (s.length === 0 || s.length > 2)
return null;
var c = imscUtils.parseColor(s[0]);
r.color = c;
if (c !== null)
s.shift();
if (s.length !== 1)
return null;
var l = imscUtils.parseLength(s[0]);
if (!l)
return null;
r.thickness = l;
return r;
}
},
function (doc, parent, element, attr, context) {
/*
* returns {color: <color>, thickness: <norm length>}
*
*/
if (attr === "none")
return attr;
var rslt = {};
if (attr.color === null) {
rslt.color = element.styleAttrs[imscStyles.byName.color.qname];
} else {
rslt.color = attr.color;
}
rslt.thickness = imscUtils.toComputedLength(
attr.thickness.value,
attr.thickness.unit,
element.styleAttrs[imscStyles.byName.fontSize.qname],
element.styleAttrs[imscStyles.byName.fontSize.qname],
doc.cellLength.h,
doc.pxLength.h
);
if (rslt.thickness === null)
return null;
return rslt;
}
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"textShadow",
"none",
['span'],
true,
true,
imscUtils.parseTextShadow,
function (doc, parent, element, attr) {
/*
* returns [{x_off: <length>, y_off: <length>, b_radius: <length>, color: <color>}*] or "none"
*
*/
if (attr === "none")
return attr;
var r = [];
for (var i in attr) {
var shadow = {};
shadow.x_off = imscUtils.toComputedLength(
attr[i][0].value,
attr[i][0].unit,
null,
element.styleAttrs[imscStyles.byName.fontSize.qname],
null,
doc.pxLength.w
);
if (shadow.x_off === null)
return null;
shadow.y_off = imscUtils.toComputedLength(
attr[i][1].value,
attr[i][1].unit,
null,
element.styleAttrs[imscStyles.byName.fontSize.qname],
null,
doc.pxLength.h
);
if (shadow.y_off === null)
return null;
if (attr[i][2] === null) {
shadow.b_radius = 0;
} else {
shadow.b_radius = imscUtils.toComputedLength(
attr[i][2].value,
attr[i][2].unit,
null,
element.styleAttrs[imscStyles.byName.fontSize.qname],
null,
doc.pxLength.h
);
if (shadow.b_radius === null)
return null;
}
if (attr[i][3] === null) {
shadow.color = element.styleAttrs[imscStyles.byName.color.qname];
} else {
shadow.color = attr[i][3];
}
r.push(shadow);
}
return r;
}
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"unicodeBidi",
"normal",
['span', 'p'],
false,
true,
function (str) {
return str;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"visibility",
"visible",
['body', 'div', 'p', 'region', 'span'],
true,
true,
function (str) {
return str;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"wrapOption",
"wrap",
['span'],
true,
true,
function (str) {
return str;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"writingMode",
"lrtb",
['region'],
false,
true,
function (str) {
return str;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_tts,
"zIndex",
"auto",
['region'],
false,
true,
function (str) {
var rslt;
if (str === 'auto') {
rslt = str;
} else {
rslt = parseInt(str);
if (isNaN(rslt)) {
rslt = null;
}
}
return rslt;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_ebutts,
"linePadding",
"0c",
['p'],
true,
false,
imscUtils.parseLength,
function (doc, parent, element, attr, context) {
return imscUtils.toComputedLength(attr.value, attr.unit, null, null, doc.cellLength.w, null);
}
),
new StylingAttributeDefinition(
imscNames.ns_ebutts,
"multiRowAlign",
"auto",
['p'],
true,
false,
function (str) {
return str;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_smpte,
"backgroundImage",
null,
['div'],
false,
false,
function (str) {
return str;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_itts,
"forcedDisplay",
"false",
['body', 'div', 'p', 'region', 'span'],
true,
true,
function (str) {
return str === 'true' ? true : false;
},
null
),
new StylingAttributeDefinition(
imscNames.ns_itts,
"fillLineGap",
"false",
['p'],
true,
true,
function (str) {
return str === 'true' ? true : false;
},
null
)
];
/* TODO: allow null parse function */
imscStyles.byQName = {};
for (var i in imscStyles.all) {
imscStyles.byQName[imscStyles.all[i].qname] = imscStyles.all[i];
}
imscStyles.byName = {};
for (var j in imscStyles.all) {
imscStyles.byName[imscStyles.all[j].name] = imscStyles.all[j];
}
})(typeof exports === 'undefined' ? this.imscStyles = {} : exports,
typeof imscNames === 'undefined' ? require("./names") : imscNames,
typeof imscUtils === 'undefined' ? require("./utils") : imscUtils);