Source code

Revision control

Copy as Markdown

Other Tools

'use strict';
/**
* Returns an array of advances for all characters in the descendants
* of the specified element.
*
* Technically speaking, advances and glyph bounding boxes are different things,
* and advances are not exposed. This function computes approximate values from
* bounding boxes.
*/
function getCharAdvances(element) {
const style = getComputedStyle(element);
const is_vertical = style.writingMode.startsWith('vertical');
const range = document.createRange();
const all_bounds = []
function walk(element) {
for (const node of element.childNodes) {
const nodeType = node.nodeType;
if (nodeType === Node.TEXT_NODE) {
const text = node.nodeValue;
for (let i = 0; i < text.length; ++i) {
range.setStart(node, i);
range.setEnd(node, i + 1);
let bounds = range.getBoundingClientRect();
// Transpose if it's in vertical flow. Guarantee that top < bottom
// and left < right are always true.
if (is_vertical) {
bounds = {
left: bounds.top,
top: bounds.left,
right: bounds.bottom,
bottom: bounds.right
};
}
all_bounds.push(bounds);
}
} else if (nodeType === Node.ELEMENT_NODE) {
walk(node);
}
}
}
walk(element);
all_bounds.sort(function(bound_a, bound_b) {
if (bound_a.bottom <= bound_b.top) {
return -1;
}
if (bound_b.bottom <= bound_a.top) {
return 1;
}
return bound_a.left - bound_b.left;
});
let origin = undefined;
let blockEnd = -1;
const advances = [];
for (let bounds of all_bounds) {
// Check if this is on the same line.
if (bounds.top >= blockEnd) {
origin = undefined;
blockEnd = bounds.bottom;
}
// For the first character, the left bound is closest to the origin.
if (origin === undefined)
origin = bounds.left;
// The right bound is a good approximation of the next origin.
advances.push(bounds.right - origin);
origin = bounds.right;
}
return advances;
}