Select Git revision
App.test.tsx
html.js 40.76 KiB
/*
* 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.
*/
import * as imscStyles from './styles';
export function render(isd,
element,
imgResolver,
eheight,
ewidth,
displayForcedOnlyMode,
errorHandler,
previousISDState,
enableRollUp
) {
/* maintain aspect ratio if specified */
let height = eheight || element.clientHeight;
let width = ewidth || element.clientWidth;
if (isd.aspectRatio !== null) {
let twidth = height * isd.aspectRatio;
if (twidth > width) {
height = Math.round(width / isd.aspectRatio);
} else {
width = twidth;
}
}
let rootcontainer = document.createElement("div");
rootcontainer.style.position = "relative";
rootcontainer.style.width = width + "px";
rootcontainer.style.height = height + "px";
rootcontainer.style.margin = "auto";
rootcontainer.style.top = 0;
rootcontainer.style.bottom = 0;
rootcontainer.style.left = 0;
rootcontainer.style.right = 0;
rootcontainer.style.zIndex = 0;
let context = {
h: height,
w: width,
regionH: null,
regionW: null,
imgResolver: imgResolver,
displayForcedOnlyMode: displayForcedOnlyMode || false,
isd: isd,
errorHandler: errorHandler,
previousISDState: previousISDState,
enableRollUp: enableRollUp || false,
currentISDState: {},
flg: null, /* current fillLineGap value if active, null otherwise */
lp: null, /* current linePadding value if active, null otherwise */
mra: null, /* current multiRowAlign value if active, null otherwise */
ipd: null, /* inline progression direction (lr, rl, tb) */
bpd: null, /* block progression direction (lr, rl, tb) */
ruby: null, /* is ruby present in a <p> */
textEmphasis: null, /* is textEmphasis present in a <p> */
rubyReserve: null /* is rubyReserve applicable to a <p> */
};
element.appendChild(rootcontainer);
if (isd.contents) {
for (let content of isd.contents) {
processElement(context, rootcontainer, content);
}
}
return context.currentISDState;
}
function processElement(context, dom_parent, isd_element) {
let e;
if (isd_element.kind === 'region') {
e = document.createElement("div");
e.style.position = "absolute";
} else if (isd_element.kind === 'body') {
e = document.createElement("div");
} else if (isd_element.kind === 'div') {
e = document.createElement("div");
} else if (isd_element.kind === 'image') {
e = document.createElement("img");
if (context.imgResolver !== null && isd_element.src !== null) {
let uri = context.imgResolver(isd_element.src, e);
if (uri)
e.src = uri;
e.height = context.regionH;
e.width = context.regionW;
}
} else if (isd_element.kind === 'p') {
e = document.createElement("p");
} else if (isd_element.kind === 'span') {
if (isd_element.styleAttrs[imscStyles.byName.ruby.qname] === "container") {
e = document.createElement("ruby");
context.ruby = true;
} else if (isd_element.styleAttrs[imscStyles.byName.ruby.qname] === "base") {
e = document.createElement("rb");
} else if (isd_element.styleAttrs[imscStyles.byName.ruby.qname] === "text") {
e = document.createElement("rt");
} else if (isd_element.styleAttrs[imscStyles.byName.ruby.qname] === "baseContainer") {
e = document.createElement("rbc");
} else if (isd_element.styleAttrs[imscStyles.byName.ruby.qname] === "textContainer") {
e = document.createElement("rtc");
} else if (isd_element.styleAttrs[imscStyles.byName.ruby.qname] === "delimiter") {
/* ignore rp */
return;
} else {
e = document.createElement("span");
}
let te = isd_element.styleAttrs[imscStyles.byName.textEmphasis.qname];
if (te && te.style !== "none") {
context.textEmphasis = true;
}
//e.textContent = isd_element.text;
} else if (isd_element.kind === 'br') {
e = document.createElement("br");
}
if (!e) {
reportError(context.errorHandler, "Error processing ISD element kind: " + isd_element.kind);
return;
}
/* add to parent */
dom_parent.appendChild(e);
/* override UA default margin */
/* TODO: should apply to <p> only */
e.style.margin = "0";
/* tranform TTML styles to CSS styles */
for (let i in STYLING_MAP_DEFS) {
let sm = STYLING_MAP_DEFS[i];
let attr = isd_element.styleAttrs[sm.qname];
if (attr !== undefined && sm.map !== null) {
sm.map(context, e, isd_element, attr);
}
}
let proc_e = e;
/* remember writing direction */
if (isd_element.kind === "region") {
let wdir = isd_element.styleAttrs[imscStyles.byName.writingMode.qname];
if (wdir === "lrtb" || wdir === "lr") {
context.ipd = "lr";
context.bpd = "tb";
} else if (wdir === "rltb" || wdir === "rl") {
context.ipd = "rl";
context.bpd = "tb";
} else if (wdir === "tblr") {
context.ipd = "tb";
context.bpd = "lr";
} else if (wdir === "tbrl" || wdir === "tb") {
context.ipd = "tb";
context.bpd = "rl";
}
}
/* do we have linePadding ? */
let lp = isd_element.styleAttrs[imscStyles.byName.linePadding.qname];
if (lp && (!lp.isZero())) {
let plength = lp.toUsedLength(context.w, context.h);
if (plength > 0) {
/* apply padding to the <p> so that line padding does not cause line wraps */
let padmeasure = Math.ceil(plength) + "px";
if (context.bpd === "tb") {
proc_e.style.paddingLeft = padmeasure;
proc_e.style.paddingRight = padmeasure;
} else {
proc_e.style.paddingTop = padmeasure;
proc_e.style.paddingBottom = padmeasure;
}
context.lp = lp;
}
}
// do we have multiRowAlign?
let mra = isd_element.styleAttrs[imscStyles.byName.multiRowAlign.qname];
if (mra && mra !== "auto") {
/* create inline block to handle multirowAlign */
let s = document.createElement("span");
s.style.display = "inline-block";
s.style.textAlign = mra;
e.appendChild(s);
proc_e = s;
context.mra = mra;
}
/* do we have rubyReserve? */
let rr = isd_element.styleAttrs[imscStyles.byName.rubyReserve.qname];
if (rr && rr[0] !== "none") {
context.rubyReserve = rr;
}
/* remember we are filling line gaps */
if (isd_element.styleAttrs[imscStyles.byName.fillLineGap.qname]) {
context.flg = true;
}
if (isd_element.kind === "span" && isd_element.text) {
if (imscStyles.byName.textCombine.qname in isd_element.styleAttrs &&
isd_element.styleAttrs[imscStyles.byName.textCombine.qname][0] === "all") {
/* ignore tate-chu-yoku since line break cannot happen within */
e.textContent = isd_element.text;
} else {
// wrap characters in spans to find the line wrap locations
let cbuf = '';
for (let j = 0; j < isd_element.text.length; j++) {
cbuf += isd_element.text.charAt(j);
let cc = isd_element.text.charCodeAt(j);
if (cc < 0xD800 || cc > 0xDBFF || j === isd_element.text.length) {
/* wrap the character(s) in a span unless it is a high surrogate */
let span = document.createElement("span");
span.textContent = cbuf;
e.appendChild(span);
cbuf = '';
}
}
}
}
/* process the children of the ISD element */
if (isd_element.contents) {
for (let content of isd_element.contents) {
processElement(context, proc_e, content);
}
}
/* list of lines */
let linelist = [];
/* paragraph processing */
/* TODO: linePadding only supported for horizontal scripts */
if ((context.lp || context.mra || context.flg || context.ruby || context.textEmphasis || context.rubyReserve) &&
isd_element.kind === "p") {
constructLineList(context, proc_e, linelist, null);
/* apply rubyReserve */
if (context.rubyReserve) {
applyRubyReserve(linelist, context);
context.rubyReserve = null;
}
/* apply tts:rubyPosition="outside" */
if (context.ruby || context.rubyReserve) {
applyRubyPosition(linelist, context);
context.ruby = null;
}
/* apply text emphasis "outside" position */
if (context.textEmphasis) {
applyTextEmphasis(linelist, context);
context.textEmphasis = null;
}
/* insert line breaks for multirowalign */
if (context.mra) {
applyMultiRowAlign(linelist);
context.mra = null;
}
/* add linepadding */
if (context.lp) {
applyLinePadding(linelist, context.lp.toUsedLength(context.w, context.h), context);
context.lp = null;
}
/* fill line gaps linepadding */
if (context.flg) {
let par_edges = rect2edges(proc_e.getBoundingClientRect(), context);
applyFillLineGap(linelist, par_edges.before, par_edges.after, context);
context.flg = null;
}
}
/* region processing */
if (isd_element.kind === "region") {
/* build line list */
constructLineList(context, proc_e, linelist);
/* perform roll up if needed */
if ((context.bpd === "tb") &&
context.enableRollUp &&
isd_element.contents.length > 0 &&
isd_element.styleAttrs[imscStyles.byName.displayAlign.qname] === 'after') {
/* horrible hack, perhaps default region id should be underscore everywhere? */
let rid = isd_element.id === '' ? '_' : isd_element.id;
let rb = new RegionPBuffer(rid, linelist);
context.currentISDState[rb.id] = rb;
if (context.previousISDState &&
rb.id in context.previousISDState &&
context.previousISDState[rb.id].plist.length > 0 &&
rb.plist.length > 1 &&
rb.plist[rb.plist.length - 2].text ===
context.previousISDState[rb.id].plist[context.previousISDState[rb.id].plist.length - 1].text) {
let body_elem = e.firstElementChild;
let h = rb.plist[rb.plist.length - 1].after - rb.plist[rb.plist.length - 1].before;
body_elem.style.bottom = "-" + h + "px";
body_elem.style.transition = "transform 0.4s";
body_elem.style.position = "relative";
body_elem.style.transform = "translateY(-" + h + "px)";
}
}
/* TODO: clean-up the spans ? */
}
}
function applyLinePadding(lineList, lp, context) {
if (lineList) {
for (let line of lineList) {
let l = line.elements.length;
let se = line.elements[line.start_elem];
let ee = line.elements[line.end_elem];
let pospadpxlen = Math.ceil(lp) + "px";
let negpadpxlen = "-" + Math.ceil(lp) + "px";
if (l !== 0) {
if (context.ipd === "lr") {
se.node.style.borderLeftColor = se.bgcolor || "#00000000";
se.node.style.borderLeftStyle = "solid";
se.node.style.borderLeftWidth = pospadpxlen;
se.node.style.marginLeft = negpadpxlen;
} else if (context.ipd === "rl") {
se.node.style.borderRightColor = se.bgcolor || "#00000000";
se.node.style.borderRightStyle = "solid";
se.node.style.borderRightWidth = pospadpxlen;
se.node.style.marginRight = negpadpxlen;
} else if (context.ipd === "tb") {
se.node.style.borderTopColor = se.bgcolor || "#00000000";
se.node.style.borderTopStyle = "solid";
se.node.style.borderTopWidth = pospadpxlen;
se.node.style.marginTop = negpadpxlen;
}
if (context.ipd === "lr") {
ee.node.style.borderRightColor = ee.bgcolor || "#00000000";
ee.node.style.borderRightStyle = "solid";
ee.node.style.borderRightWidth = pospadpxlen;
ee.node.style.marginRight = negpadpxlen;
} else if (context.ipd === "rl") {
ee.node.style.borderLeftColor = ee.bgcolor || "#00000000";
ee.node.style.borderLeftStyle = "solid";
ee.node.style.borderLeftWidth = pospadpxlen;
ee.node.style.marginLeft = negpadpxlen;
} else if (context.ipd === "tb") {
ee.node.style.borderBottomColor = ee.bgcolor || "#00000000";
ee.node.style.borderBottomStyle = "solid";
ee.node.style.borderBottomWidth = pospadpxlen;
ee.node.style.marginBottom = negpadpxlen;
}
}
}
}
}
function applyMultiRowAlign(lineList) {
/* apply an explicit br to all but the last line */
for (let i = 0; i < lineList.length - 1; i++) {
let l = lineList[i].elements.length;
if (l !== 0 && lineList[i].br === false) {
let br = document.createElement("br");
let lastnode = lineList[i].elements[l - 1].node;
lastnode.parentElement.insertBefore(br, lastnode.nextSibling);
}
}
}
function applyTextEmphasis(lineList, context) {
/* supports "outside" only */
for (let i = 0; i < lineList.length; i++) {
for (let j = 0; j < lineList[i].te.length; j++) {
/* skip if position already set */
if (lineList[i].te[j].style.textEmphasisPosition &&
lineList[i].te[j].style.textEmphasisPosition !== "none")
continue;
let pos;
if (context.bpd === "tb") {
pos = (i === 0) ? "left over" : "left under";
} else {
if (context.bpd === "rl") {
pos = (i === 0) ? "right under" : "left under";
} else {
pos = (i === 0) ? "left under" : "right under";
}
}
lineList[i].te[j].style.textEmphasisPosition = pos;
}
}
}
function applyRubyPosition(lineList, context) {
for (let i = 0; i < lineList.length; i++) {
for (let j = 0; j < lineList[i].rbc.length; j++) {
/* skip if ruby-position already set */
if (lineList[i].rbc[j].style.rubyPosition)
continue;
let pos;
if (context.bpd === "tb") {
pos = (i === 0) ? "over" : "under";
} else {
if (context.bpd === "rl") {
pos = (i === 0) ? "over" : "under";
} else {
pos = (i === 0) ? "under" : "over";
}
}
lineList[i].rbc[j].style.rubyPosition = pos;
}
}
}
function applyRubyReserve(lineList, context) {
for (let i = 0; i < lineList.length; i++) {
let ruby = document.createElement("ruby");
let rb = document.createElement("rb");
rb.textContent = "\u200B";
ruby.appendChild(rb);
let rt1;
let rt2;
let fs = context.rubyReserve[1].toUsedLength(context.w, context.h) + "px";
if (context.rubyReserve[0] === "both") {
rt1 = document.createElement("rtc");
rt1.style.rubyPosition = "under";
rt1.textContent = "\u200B";
rt1.style.fontSize = fs;
rt2 = document.createElement("rtc");
rt2.style.rubyPosition = "over";
rt2.textContent = "\u200B";
rt2.style.fontSize = fs;
ruby.appendChild(rt1);
ruby.appendChild(rt2);
} else {
rt1 = document.createElement("rtc");
rt1.textContent = "\u200B";
rt1.style.fontSize = fs;
if (context.rubyReserve[0] === "after" || (context.rubyReserve[0] === "outside" && i > 0)) {
rt1.style.rubyPosition = (context.bpd === "tb" || context.bpd === "rl") ? "under" : "over";
} else {
rt1.style.rubyPosition = (context.bpd === "tb" || context.bpd === "rl") ? "over" : "under";
}
ruby.appendChild(rt1);
}
lineList[i].elements[0].node.parentElement.insertBefore(
ruby,
lineList[i].elements[0].node
);
}
}
function applyFillLineGap(lineList, par_before, par_after, context) {
/* positive for BPD = lr and tb, negative for BPD = rl */
let s = Math.sign(par_after - par_before);
for (let i = 0; i <= lineList.length; i++) {
/* compute frontier between lines */
let frontier;
if (i === 0) {
frontier = par_before;
} else if (i === lineList.length) {
frontier = par_after;
} else {
frontier = (lineList[i].before + lineList[i - 1].after) / 2;
}
/* padding amount */
let pad;
/* current element */
let e;
/* before line */
if (i > 0) {
for (let j = 0; j < lineList[i - 1].elements.length; j++) {
if (lineList[i - 1].elements[j].bgcolor === null)
continue;
e = lineList[i - 1].elements[j];
if (s * (e.after - frontier) < 0) {
pad = Math.ceil(Math.abs(frontier - e.after)) + "px";
e.node.style.backgroundColor = e.bgcolor;
if (context.bpd === "lr") {
e.node.style.paddingRight = pad;
} else if (context.bpd === "rl") {
e.node.style.paddingLeft = pad;
} else if (context.bpd === "tb") {
e.node.style.paddingBottom = pad;
}
}
}
}
/* after line */
if (i < lineList.length) {
for (let k = 0; k < lineList[i].elements.length; k++) {
e = lineList[i].elements[k];
if (e.bgcolor === null)
continue;
if (s * (e.before - frontier) > 0) {
pad = Math.ceil(Math.abs(e.before - frontier)) + "px";
e.node.style.backgroundColor = e.bgcolor;
if (context.bpd === "lr") {
e.node.style.paddingLeft = pad;
} else if (context.bpd === "rl") {
e.node.style.paddingRight = pad;
} else if (context.bpd === "tb") {
e.node.style.paddingTop = pad;
}
}
}
}
}
}
function RegionPBuffer(id, lineList) {
this.id = id;
this.plist = lineList;
}
function rect2edges(rect, context) {
let edges = {before: null, after: null, start: null, end: null};
if (context.bpd === "tb") {
edges.before = rect.top;
edges.after = rect.bottom;
if (context.ipd === "lr") {
edges.start = rect.left;
edges.end = rect.right;
} else {
edges.start = rect.right;
edges.end = rect.left;
}
} else if (context.bpd === "lr") {
edges.before = rect.left;
edges.after = rect.right;
edges.start = rect.top;
edges.end = rect.bottom;
} else if (context.bpd === "rl") {
edges.before = rect.right;
edges.after = rect.left;
edges.start = rect.top;
edges.end = rect.bottom;
}
return edges;
}
function constructLineList(context, element, llist, bgcolor) {
if (element.localName === "rt" || element.localName === "rtc") {
/* skip ruby annotations */
return;
}
let curbgcolor = element.style.backgroundColor || bgcolor;
if (element.childElementCount === 0) {
if (element.localName === 'span' || element.localName === 'rb') {
let r = element.getBoundingClientRect();
/* skip if span is not displayed */
if (r.height === 0 || r.width === 0)
return;
let edges = rect2edges(r, context);
if (llist.length === 0 ||
(!isSameLine(edges.before, edges.after, llist[llist.length - 1].before, llist[llist.length - 1].after))
) {
llist.push({
before: edges.before,
after: edges.after,
start: edges.start,
end: edges.end,
start_elem: 0,
end_elem: 0,
elements: [],
rbc: [],
te: [],
text: "",
br: false
});
} else {
/* positive for BPD = lr and tb, negative for BPD = rl */
let bpd_dir = Math.sign(edges.after - edges.before);
/* positive for IPD = lr and tb, negative for IPD = rl */
let ipd_dir = Math.sign(edges.end - edges.start);
/* check if the line height has increased */
if (bpd_dir * (edges.before - llist[llist.length - 1].before) < 0) {
llist[llist.length - 1].before = edges.before;
}
if (bpd_dir * (edges.after - llist[llist.length - 1].after) > 0) {
llist[llist.length - 1].after = edges.after;
}
if (ipd_dir * (edges.start - llist[llist.length - 1].start) < 0) {
llist[llist.length - 1].start = edges.start;
llist[llist.length - 1].start_elem = llist[llist.length - 1].elements.length;
}
if (ipd_dir * (edges.end - llist[llist.length - 1].end) > 0) {
llist[llist.length - 1].end = edges.end;
llist[llist.length - 1].end_elem = llist[llist.length - 1].elements.length;
}
}
llist[llist.length - 1].text += element.textContent;
llist[llist.length - 1].elements.push(
{
node: element,
bgcolor: curbgcolor,
before: edges.before,
after: edges.after
}
);
} else if (element.localName === 'br' && llist.length !== 0) {
llist[llist.length - 1].br = true;
}
} else {
let child = element.firstChild;
while (child) {
if (child.nodeType === Node.ELEMENT_NODE) {
constructLineList(context, child, llist, curbgcolor);
if (child.localName === 'ruby' || child.localName === 'rtc') {
/* remember non-empty ruby and rtc elements so that tts:rubyPosition can be applied */
if (llist.length > 0) {
llist[llist.length - 1].rbc.push(child);
}
} else if (child.localName === 'span' &&
child.style.textEmphasisStyle &&
child.style.textEmphasisStyle !== "none") {
/* remember non-empty span elements with textEmphasis */
if (llist.length > 0) {
llist[llist.length - 1].te.push(child);
}
}
}
child = child.nextSibling;
}
}
}
function isSameLine(before1, after1, before2, after2) {
return ((after1 < after2) && (before1 > before2)) || ((after2 <= after1) && (before2 >= before1));
}
function HTMLStylingMapDefintion(qName, mapFunc) {
this.qname = qName;
this.map = mapFunc;
}
let STYLING_MAP_DEFS = [
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling backgroundColor",
function (context, dom_element, isd_element, attr) {
/* skip if transparent */
if (attr[3] === 0)
return;
dom_element.style.backgroundColor = "rgba(" +
attr[0].toString() + "," +
attr[1].toString() + "," +
attr[2].toString() + "," +
(attr[3] / 255).toString() +
")";
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling color",
function (context, dom_element, isd_element, attr) {
dom_element.style.color = "rgba(" +
attr[0].toString() + "," +
attr[1].toString() + "," +
attr[2].toString() + "," +
(attr[3] / 255).toString() +
")";
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling direction",
function (context, dom_element, isd_element, attr) {
dom_element.style.direction = attr;
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling display",
function (context, dom_element, isd_element, attr) {
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling displayAlign",
function (context, dom_element, isd_element, attr) {
/* see https://css-tricks.com/snippets/css/a-guide-to-flexbox/ */
/* TODO: is this affected by writing direction? */
dom_element.style.display = "flex";
dom_element.style.flexDirection = "column";
if (attr === "before") {
dom_element.style.justifyContent = "flex-start";
} else if (attr === "center") {
dom_element.style.justifyContent = "center";
} else if (attr === "after") {
dom_element.style.justifyContent = "flex-end";
}
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling extent",
function (context, dom_element, isd_element, attr) {
/* TODO: this is super ugly */
context.regionH = attr.h.toUsedLength(context.w, context.h);
context.regionW = attr.w.toUsedLength(context.w, context.h);
/*
* CSS height/width are measured against the content rectangle,
* whereas TTML height/width include padding
*/
let hdelta = 0;
let wdelta = 0;
let p = isd_element.styleAttrs["http://www.w3.org/ns/ttml#styling padding"];
if (!p) {
/* error */
} else {
hdelta = p[0].toUsedLength(context.w, context.h) + p[2].toUsedLength(context.w, context.h);
wdelta = p[1].toUsedLength(context.w, context.h) + p[3].toUsedLength(context.w, context.h);
}
dom_element.style.height = (context.regionH - hdelta) + "px";
dom_element.style.width = (context.regionW - wdelta) + "px";
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling fontFamily",
function (context, dom_element, isd_element, attr) {
/* per IMSC1 */
const styleClasses = ["monospaceSerif", "proportionalSerif", "monospace", "sansSerif", "serif", "monospaceSansSerif", "proportionalSansSerif"];
for (let attribute of attr) {
if (styleClasses.includes(attribute)) {
dom_element.classList.add("ttml-" + attribute);
} else {
dom_element.style.fontFamily = attribute;
}
}
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling shear",
function (context, dom_element, isd_element, attr) {
/* return immediately if tts:shear is 0% since CSS transforms are not inherited*/
if (attr === 0)
return;
let angle = attr * -0.9;
/* context.writingMode is needed since writing mode is not inherited and sets the inline progression */
if (context.bpd === "tb") {
dom_element.style.transform = "skewX(" + angle + "deg)";
} else {
dom_element.style.transform = "skewY(" + angle + "deg)";
}
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling fontSize",
function (context, dom_element, isd_element, attr) {
dom_element.style.fontSize = attr.toUsedLength(context.w, context.h) + "px";
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling fontStyle",
function (context, dom_element, isd_element, attr) {
dom_element.style.fontStyle = attr;
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling fontWeight",
function (context, dom_element, isd_element, attr) {
dom_element.style.fontWeight = attr;
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling lineHeight",
function (context, dom_element, isd_element, attr) {
if (attr === "normal") {
dom_element.style.lineHeight = "normal";
} else {
dom_element.style.lineHeight = attr.toUsedLength(context.w, context.h) + "px";
}
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling opacity",
function (context, dom_element, isd_element, attr) {
dom_element.style.opacity = attr;
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling origin",
function (context, dom_element, isd_element, attr) {
dom_element.style.top = attr.h.toUsedLength(context.w, context.h) + "px";
dom_element.style.left = attr.w.toUsedLength(context.w, context.h) + "px";
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling overflow",
function (context, dom_element, isd_element, attr) {
dom_element.style.overflow = attr;
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling padding",
function (context, dom_element, isd_element, attr) {
/* attr: top,left,bottom,right*/
/* style: top right bottom left*/
let rslt = [];
rslt[0] = attr[0].toUsedLength(context.w, context.h) + "px";
rslt[1] = attr[3].toUsedLength(context.w, context.h) + "px";
rslt[2] = attr[2].toUsedLength(context.w, context.h) + "px";
rslt[3] = attr[1].toUsedLength(context.w, context.h) + "px";
dom_element.style.padding = rslt.join(" ");
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling position",
function (context, dom_element, isd_element, attr) {
dom_element.style.top = attr.h.toUsedLength(context.w, context.h) + "px";
dom_element.style.left = attr.w.toUsedLength(context.w, context.h) + "px";
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling rubyAlign",
function (context, dom_element, isd_element, attr) {
dom_element.style.rubyAlign = attr;
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling rubyPosition",
function (context, dom_element, isd_element, attr) {
/* skip if "outside", which is handled by applyRubyPosition() */
if (attr === "before" || attr === "after") {
let pos;
if (context.bpd === "tb") {
pos = (attr === "before") ? "over" : "under";
} else {
if (context.bpd === "rl") {
pos = (attr === "before") ? "over" : "under";
} else {
pos = (attr === "before") ? "under" : "over";
}
}
/* apply position to the parent dom_element, i.e. ruby or rtc */
dom_element.parentElement.style.rubyPosition = pos;
}
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling showBackground",
null
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling textAlign",
function (context, dom_element, isd_element, attr) {
let ta;
let dir = isd_element.styleAttrs[imscStyles.byName.direction.qname];
/* handle UAs that do not understand start or end */
if (attr === "start") {
ta = (dir === "rtl") ? "right" : "left";
} else if (attr === "end") {
ta = (dir === "rtl") ? "left" : "right";
} else {
ta = attr;
}
dom_element.style.textAlign = ta;
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling textDecoration",
function (context, dom_element, isd_element, attr) {
dom_element.style.textDecoration = attr.join(" ").replace("lineThrough", "line-through");
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling textOutline",
function (context, dom_element, isd_element, attr) {
/* defer to tts:textShadow */
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling textShadow",
function (context, dom_element, isd_element, attr) {
let txto = isd_element.styleAttrs[imscStyles.byName.textOutline.qname];
if (attr === "none" && txto === "none") {
dom_element.style.textShadow = "";
} else {
let s = [];
if (txto !== "none") {
/* emulate text outline */
s.push(
"rgba(" +
txto.color[0].toString() + "," +
txto.color[1].toString() + "," +
txto.color[2].toString() + "," +
(txto.color[3] / 255).toString() +
") 0px 0px " +
txto.thickness.toUsedLength(context.w, context.h) + "px"
);
}
/* add text shadow */
if (attr !== "none") {
for (let attribute of attr) {
s.push(attribute.x_off.toUsedLength(context.w, context.h) + "px " +
attribute.y_off.toUsedLength(context.w, context.h) + "px " +
attribute.b_radius.toUsedLength(context.w, context.h) + "px " +
"rgba(" +
attribute.color[0].toString() + "," +
attribute.color[1].toString() + "," +
attribute.color[2].toString() + "," +
(attribute.color[3] / 255).toString() +
")"
);
}
}
dom_element.style.textShadow = s.join(",");
}
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling textCombine",
function (context, dom_element, isd_element, attr) {
dom_element.style.textCombineUpright = attr.join(" ");
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling textEmphasis",
function (context, dom_element, isd_element, attr) {
/* ignore color (not used in IMSC 1.1) */
if (attr.style === "none") {
dom_element.style.textEmphasisStyle = "none";
/* no need to set position, so return */
return;
} else if (attr.style === "auto") {
dom_element.style.textEmphasisStyle = "filled";
} else {
dom_element.style.textEmphasisStyle = attr.style + " " + attr.symbol;
}
/* ignore "outside" position (set in postprocessing) */
if (attr.position === "before" || attr.position === "after") {
let pos;
if (context.bpd === "tb") {
pos = (attr.position === "before") ? "left over" : "left under";
} else {
if (context.bpd === "rl") {
pos = (attr.position === "before") ? "right under" : "left under";
} else {
pos = (attr.position === "before") ? "left under" : "right under";
}
}
dom_element.style.textEmphasisPosition = pos;
}
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling unicodeBidi",
function (context, dom_element, isd_element, attr) {
let ub;
if (attr === 'bidiOverride') {
ub = "bidi-override";
} else {
ub = attr;
}
dom_element.style.unicodeBidi = ub;
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling visibility",
function (context, dom_element, isd_element, attr) {
dom_element.style.visibility = attr;
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling wrapOption",
function (context, dom_element, isd_element, attr) {
if (attr === "wrap") {
if (isd_element.space === "preserve") {
dom_element.style.whiteSpace = "pre-wrap";
} else {
dom_element.style.whiteSpace = "normal";
}
} else {
if (isd_element.space === "preserve") {
dom_element.style.whiteSpace = "pre";
} else {
dom_element.style.whiteSpace = "noWrap";
}
}
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling writingMode",
function (context, dom_element, isd_element, attr) {
if (attr === "lrtb" || attr === "lr") {
context.writingMode = "horizontal-tb";
} else if (attr === "rltb" || attr === "rl") {
context.writingMode = "horizontal-tb";
} else if (attr === "tblr") {
context.writingMode = "vertical-lr";
} else if (attr === "tbrl" || attr === "tb") {
context.writingMode = "vertical-rl";
}
dom_element.style.writingMode = context.writingMode;
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml#styling zIndex",
function (context, dom_element, isd_element, attr) {
dom_element.style.zIndex = attr;
}
),
new HTMLStylingMapDefintion(
"http://www.w3.org/ns/ttml/profile/imsc1#styling forcedDisplay",
function (context, dom_element, isd_element, attr) {
if (context.displayForcedOnlyMode && attr === false) {
dom_element.style.visibility = "hidden";
}
}
)
];
let STYLMAP_BY_QNAME = {};
for (let i in STYLING_MAP_DEFS) {
STYLMAP_BY_QNAME[STYLING_MAP_DEFS[i].qname] = STYLING_MAP_DEFS[i];
}
function reportError(errorHandler, msg) {
if (errorHandler && errorHandler.error && errorHandler.error(msg))
throw msg;
}