glyphline

HTML text

Highlight ordinary HTML with domSource; it follows reflow and inline markup.

Inline markup, a heading and a list: one text for glyphline.

How the cursor moves

Along a line the cursor stretches toward the next word and then contracts onto it. Between lines it travels with the same motion, so going down a line reads like moving along it.

  • A sentence is one rounded shape, even across lines.
  • Justified line ends are snapped, so the edge stays straight.

Drag the corner of this box to change its width while it reads.

domSource(root) walks the text nodes under root and joins them into one string. Inline markup (em, a, strong, spans) is transparent; a block boundary (paragraph, heading, list item) ends a sentence even without punctuation, so a heading never runs into the paragraph after it.

import { domSource } from "glyphline/dom";

const source = domSource(article, {
  locale: "pl", // default: the nearest lang attribute, then "en"
  skip: "pre, code, [data-glyphline-skip]", // default skips script, style, textarea…
});

In React, useDomSource builds the source and rebuilds it when the text changes; in Svelte, domText does the same.

Reflow

HTML moves: a resize, a font load, an expanded section. An HTML source is live, so the highlighter watches the overlay with a ResizeObserver and redraws when its size changes. For changes that do not resize the overlay (a web font swapping in), call hl.refresh().

When the content changes, build a new source and pass it to hl.setSource(). Indices and ranges refer to the text the source was built from.

Clicking a word

import { wordAtPoint } from "glyphline";

article.addEventListener("click", (e) => {
  const word = wordAtPoint(source, svg, e.clientX, e.clientY);
  if (word >= 0) hl.update({ word });
});

wordAtPoint uses the highlighter's own boxes, so it works the same for PDF sources. An HTML source also has charAtPoint(x, y) for the caret position under a point.

Scrolling into view

const w = source.words[word];
source.toRange(w.start, w.end)?.startContainer.parentElement?.scrollIntoView({ block: "nearest" });

Made by Lucas Piera

On this page