Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,13 @@ pipenv install
pipenv run python3 src/test/script/compare_renders.py <unzipped renders-imsc1> <unzipped renders-imsc1_1>
```

[src/test/script/gen-compare-site.mjs](src/test/script/gen-compare-site.mjs) generates, under `build/compare`, a static website that compares imscJS renders with the reference PNGs of the [W3C IMSC test suite](https://github.com/w3c/imsc-tests).

```sh
npm run build
npm run gen-compare-site
```

### Unit tests

Unit tests run using Node's built-in [test runner](https://nodejs.org/api/test.html) and are located at [src/test/js](src/test/js). They can be run with:
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"gen-imsc1_1": "node src/test/script/gen-render-package.mjs imsc-tests/imsc1_1 build/renders-imsc1_1.zip",
"gen-imsc1_3": "node src/test/script/gen-render-package.mjs imsc-tests/imsc1_3 build/renders-imsc1_3.zip",
"gen-reference-files": "node src/test/script/gen-reference-files.mjs",
"gen-compare-site": "node src/test/script/gen-compare-site.mjs",
"install:firefox": "puppeteer browsers install firefox",
"lint": "eslint .",
"fix": "eslint --fix src",
Expand Down
222 changes: 155 additions & 67 deletions src/main/js/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*/

import { reportError } from "./error.js";
import { reportError, reportWarning } from "./error.js";
import { byName } from "./styles.js";

/**
Expand Down Expand Up @@ -211,15 +211,6 @@ function processElement(context, dom_parent, isd_element, isd_parent) {

e = document.createElement("rt");

} else if (isd_element.styleAttrs[byName.ruby.qname] === "baseContainer") {

e = document.createElement("span"); // rbc element is deprecated in HTML

} else if (isd_element.styleAttrs[byName.ruby.qname] === "textContainer") {

e = document.createElement("span"); // rtc element is deprecated in HTML
e.style.display = "ruby-text-container";

} else if (isd_element.styleAttrs[byName.ruby.qname] === "delimiter") {

/* ignore rp */
Expand Down Expand Up @@ -397,10 +388,11 @@ function processElement(context, dom_parent, isd_element, isd_parent) {

}

if (byName.textCombine.qname in isd_element.styleAttrs &&
isd_element.styleAttrs[byName.textCombine.qname] === "all") {
if ((byName.textCombine.qname in isd_element.styleAttrs &&
isd_element.styleAttrs[byName.textCombine.qname] === "all") ||
e.closest("rt") !== null) {

/* ignore tate-chu-yoku since line break cannot happen within */
/* ignore tate-chu-yoku and ruby text since line break cannot happen within */
e.textContent = isd_element.text;
e._isd_element = isd_element;

Expand Down Expand Up @@ -455,9 +447,19 @@ function processElement(context, dom_parent, isd_element, isd_parent) {

if ("contents" in isd_element) {

for (let k = 0; k < isd_element.contents.length; k++) {
if (isd_element.kind === "span" &&
isd_element.styleAttrs[byName.ruby.qname] === "container" &&
isd_element.contents.some((c) => c.styleAttrs[byName.ruby.qname] === "baseContainer")) {

processRubyContainer(context, e, isd_element);

} else {

for (let k = 0; k < isd_element.contents.length; k++) {

processElement(context, proc_e, isd_element.contents[k], isd_element);

processElement(context, proc_e, isd_element.contents[k], isd_element);
}

}

Expand Down Expand Up @@ -583,6 +585,104 @@ function processElement(context, dom_parent, isd_element, isd_parent) {
}
}

/*
* Maps a TTML ruby container that contains a ruby base container to N <ruby>
* elements, one for each ruby base of the base container, since <rtc> and <rbc>
* are deprecated.
*
* The ith <ruby> element contains:
* - the ith ruby base, or, if there are two ruby text containers, a nested
* <ruby> element that contains the ith ruby base and the ith ruby text of the
* second text container; followed by
* - the ith ruby text of the first text container.
*
* If a text container has more ruby texts than the base container has ruby
* bases, the extra ruby texts are kept and annotate an empty base.
*/
function processRubyContainer(context, ruby, isd_ruby) {

let bases = [];

const textContainers = [];

for (const c of isd_ruby.contents) {

if (c.styleAttrs[byName.ruby.qname] === "baseContainer") {

bases = c.contents.filter((b) => b.styleAttrs[byName.ruby.qname] === "base");

} else if (c.styleAttrs[byName.ruby.qname] === "textContainer") {

textContainers.push(c.contents.filter((t) => t.styleAttrs[byName.ruby.qname] === "text"));

}

}

/* number of <ruby> elements, i.e. the greater of the number of ruby bases and the number of ruby text in the first two text containers */

let ruby_count = bases.length;

for (let k = 0; k < textContainers.length && k < 2; k++) {

ruby_count = Math.max(ruby_count, textContainers[k].length);

}

if (ruby_count > bases.length) {

reportWarning(context.errorHandler, "Ruby text container has more ruby text than the ruby base container has ruby bases.");

}

const template = ruby.cloneNode(false);

let outer = ruby;

for (let i = 0; i < ruby_count; i++) {

if (i > 0) {

const next = template.cloneNode(false);

outer.parentElement.insertBefore(next, outer.nextSibling);

outer = next;

}

let base_parent = outer;

if (textContainers.length > 1) {

base_parent = template.cloneNode(false);

outer.appendChild(base_parent);

}

if (i < bases.length) {

processElement(context, base_parent, bases[i], isd_ruby);

}

if (textContainers.length > 1 && i < textContainers[1].length) {

processElement(context, base_parent, textContainers[1][i], isd_ruby);

}

if (textContainers.length > 0 && i < textContainers[0].length) {

processElement(context, outer, textContainers[0][i], isd_ruby);

}

}

}

function mergeSpans(lineList, context) {

for (let i = 0; i < lineList.length; i++) {
Expand Down Expand Up @@ -886,88 +986,76 @@ function applyRubyReserve(lineList, context) {

for (let i = 0; i < lineList.length; i++) {

const ruby = document.createElement("ruby");

const rb = document.createElement("span"); // rb element is deprecated in HTML
rb.textContent = "\u200B";

ruby.appendChild(rb);
const fs = context.rubyReserve[1].toUsedLength(context.w, context.h) + "px";

let rt1;
let rt2;
/* We cannot use the deprecated <rtc> to reserve space, so instead we use nested rubies */

const fs = context.rubyReserve[1].toUsedLength(context.w, context.h) + "px";
const reserved_pos = [];

if (context.rubyReserve[0] === "both" || (context.rubyReserve[0] === "outside" && lineList.length == 1)) {

rt1 = document.createElement("span"); // rtc element is deprecated in HTML
rt1.style.display = "ruby-text-container";
rt1.style[RUBYPOSITION_PROP] = RUBYPOSITION_ISWK ? "after" : "under";
rt1.textContent = "\u200B";
rt1.style.fontSize = fs;
reserved_pos.push(RUBYPOSITION_ISWK ? "after" : "under");
reserved_pos.push(RUBYPOSITION_ISWK ? "before" : "over");

rt2 = document.createElement("span"); // rtc element is deprecated in HTML
rt2.style.display = "ruby-text-container";
rt2.style[RUBYPOSITION_PROP] = RUBYPOSITION_ISWK ? "before" : "over";
rt2.textContent = "\u200B";
rt2.style.fontSize = fs;
} else if (context.rubyReserve[0] === "after" || (context.rubyReserve[0] === "outside" && i > 0)) {

ruby.appendChild(rt1);
ruby.appendChild(rt2);
reserved_pos.push(RUBYPOSITION_ISWK ? "after" : ((context.bpd === "tb" || context.bpd === "rl") ? "under" : "over"));

} else {

rt1 = document.createElement("span"); // rtc element is deprecated in HTML
rt1.style.display = "ruby-text-container";
rt1.textContent = "\u200B";
rt1.style.fontSize = fs;
reserved_pos.push(RUBYPOSITION_ISWK ? "before" : ((context.bpd === "tb" || context.bpd === "rl") ? "over" : "under"));

let pos;
}

if (context.rubyReserve[0] === "after" || (context.rubyReserve[0] === "outside" && i > 0)) {
/* add in front of the first ruby element of the line, if it exists */

pos = RUBYPOSITION_ISWK ? "after" : ((context.bpd === "tb" || context.bpd === "rl") ? "under" : "over");
let ruby_sibling = null;

} else {

pos = RUBYPOSITION_ISWK ? "before" : ((context.bpd === "tb" || context.bpd === "rl") ? "over" : "under");
for (let j = 0; j < lineList[i].rbc.length; j++) {

}
if (lineList[i].rbc[j].localName === "ruby") {

rt1.style[RUBYPOSITION_PROP] = pos;
ruby_sibling = lineList[i].rbc[j];

ruby.appendChild(rt1);
break;
}

}

/* add in front of the first ruby element of the line, if it exists */
let base = document.createElement("span"); // rb element is deprecated in HTML
base.textContent = "\u200B";

let sib = null;
for (const pos of reserved_pos) {

for (let j = 0; j < lineList[i].rbc.length; j++) {
const ruby = document.createElement("ruby");

if (lineList[i].rbc[j].localName === "ruby") {
ruby.appendChild(base);

sib = lineList[i].rbc[j];
const rt = document.createElement("rt");
rt.style[RUBYPOSITION_PROP] = pos;
rt.textContent = "\u200B";
rt.style.fontSize = fs;

/* copy specified style properties from the sibling ruby container */
ruby.appendChild(rt);

for (let k = 0; k < sib.style.length; k++) {
/* copy specified style properties from the sibling ruby container */

ruby.style.setProperty(sib.style.item(k), sib.style.getPropertyValue(sib.style.item(k)));
if (ruby_sibling !== null) {

for (let k = 0; k < ruby_sibling.style.length; k++) {
ruby.style.setProperty(ruby_sibling.style.item(k), ruby_sibling.style.getPropertyValue(ruby_sibling.style.item(k)));
}

break;
}

}
ruby.style[RUBYPOSITION_PROP] = pos; // for Chrome

/* otherwise add before first span */

sib = sib || lineList[i].elements[0].node;
base = ruby;
}

sib.parentElement.insertBefore(ruby, sib);
/* add before first span if there is no ruby sibling */
const actual_sibling = ruby_sibling || lineList[i].elements[0].node;
actual_sibling.parentElement.insertBefore(base, actual_sibling);

}

Expand Down Expand Up @@ -1113,7 +1201,7 @@ function rect2edges(rect, context) {

function constructLineList(context, element, llist, bgcolor) {

if (element.localName === "rt" || element.style.display === "ruby-text-container") {
if (element.localName === "rt") {

/* skip ruby annotations */

Expand Down Expand Up @@ -1205,9 +1293,9 @@ function constructLineList(context, element, llist, bgcolor) {

constructLineList(context, child, llist, curbgcolor);

if (child.localName === "ruby" || child.style.display === "ruby-text-container") {
if (child.localName === "ruby") {

/* remember non-empty ruby and rtc elements so that tts:rubyPosition can be applied */
/* remember non-empty ruby elements so that tts:rubyPosition can be applied */

if (llist.length > 0) {

Expand Down Expand Up @@ -1640,7 +1728,7 @@ const STYLING_MAP_DEFS = [

}

/* apply position to the parent dom_element, i.e. ruby or rtc */
/* apply position to the parent dom_element, i.e. ruby */

dom_element.parentElement.style[RUBYPOSITION_PROP] = pos;
}
Expand Down
Loading
Loading