'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; }