This is a proposal for adding a text measurement and font metrics API.
In many ways it is similar to the Canvas TextMetrics API however it is designed to match the text rendering capabilities of the DOM and allows either in-document or out-of-document measurements.
An accurate and fast text measurement API is something that current generation web applications have been asking for and the alternatives aren’t great. Without CSS Custom Layout this is likely to become an even bigger problem.
Current alternatives:
- In-document measurements. Accurate but slow, requires mutating the DOM and forcing layout.
- Canvas TextMetrics API. Limited API and results aren’t guaranteed to match DOM measurements.
- Using opentype.js. Very low level, requiring a lot of extra work by author. Bypasses entire text stack in browser and as such measurements are not guaranteed to match.
The proposed API provides a high-level API without any of the drawbacks mentioned above.
Proposed API
partial interface Document { FontMetrics measureElement(Element element); FontMetrics measureText(DOMString text, StylePropertyMapReadOnly styleMap); };
Two methods are provided for measuring text, one for in-document measurements
and another for out-of-document measurements. Both return a FontMetrics
object.
measureElement()
takes an Element
and returns a FontMetrics
object. If the Element
is not in the document or isn’t rendered an empty FontMetrics
object is returned.
measureText()
takes a DOMString
and an optional
StylePropertyMapReadOnly
, returning a FontMetrics
object. Unless a font is specified as a part of the styleMap the user agents default will be used.
Note: The only styles that apply to the measureText()
method are
those that are passed in as a part of the styleMap. Document styles do not apply.
FontMetrics object
interface FontMetrics { readonly attribute double width; readonly attribute sequence<double> advances; readonly attribute double boundingBoxLeft; readonly attribute double boundingBoxRight; readonly attribute double height; readonly attribute double emHeightAscent; readonly attribute double emHeightDescent; readonly attribute double boundingBoxAscent; readonly attribute double boundingBoxDescent; readonly attribute double fontBoundingBoxAscent; readonly attribute double fontBoundingBoxDescent; readonly attribute Baseline dominantBaseline; readonly attribute sequence<Baseline> baselines; readonly attribute sequence<Font> fonts; };
Full Proposed API with further details.
Minimal Example
Example of out-of-document text measurement.
let metrics = document.measureText('Hello WICG'); let width = metrics.width;
Middle-truncation Example
Using the measurement API to implement a very simple middle-truncation method. Unlike text-overflow this truncates from the middle of a string which is often useful for things like URLs and phone numbers where the leading and trailing ends tends to be more important than the middle.
This example demonstrates the use of the advances field in the metrics object.
function truncateMiddle(textNode, maxWidth) { var string = textNode.textContent; let metrics = measureText(string); if (metrics.width < maxWidth) return; let ellipsis = '\u2026'; let ellipsisWidth = measureText(ellipsis).width; let availableWidth = maxWidth - ellipsisWidth; // Allow at maximum half the available width before the ellipsis. let availableLeadingWidth = availableWidth / 2; let leadingOffset = 0; for (let i = 0; i < metrics.advances.length; i++) { if (metrics.advances[i] > availableLeadingWidth) break; leadingOffset = i; } let leadingWidth = metrics.advances[leadingOffset]; // Allow all remaining width after the ellipsis. let availableTrailingWidth = availableWidth - leadingWidth; let trailingOffset = string.length - 1; for (let i = metrics.advances.length - 1; i > leadingOffset; i--) { let width = metrics.width - metrics.advances[i]; if (width > availableTrailingWidth) break; trailingOffset = i; } let trailingWidth = metrics.width - metrics.advances[trailingOffset]; // Replace text content with truncated string. let truncatedString = string.substr(0, leadingOffset) + ellipsis + string.substr(trailingOffset, string.length - trailingOffset); textNode.textContent = truncatedString; }
Note: This work started as a part of the Houdini Task Force before it was decided to move it to WICG for incubation.