Files
package-language-reader-pre…/src/canvas/line-breaks.ts
T

37 lines
1.5 KiB
TypeScript

import { PASS, Rules } from "@cto.af/linebreak";
import type { Range } from "./types.js";
import { graphemes } from "./text.js";
const normal = new Rules();
// Emergency wrapping may split an oversized word or number, but still obeys
// the punctuation, bracket, combining-mark and nonbreaking-space rules. This
// is UAX #14 tailoring, not a list of special cases for a particular language.
const emergency = new Rules();
for (const rule of ["LB23", "LB25", "LB28", "LB29"]) emergency.replaceRule(rule, () => PASS);
const cached = new Map<string, { normal: Range[]; emergency: Range[] }>();
/** Legal UTF-16 source spans for line wrapping. Intl.Segmenter still owns word
* targeting; line breaking is a separate Unicode operation. */
export function lineSpans(text: string, emergencyWrap = false): Range[] {
let entry = cached.get(text);
if (!entry) {
const spans = (rules: Rules): Range[] => {
let start = 0;
const boundaries = new Set(graphemes(text).map((unit) => unit.end));
return Array.from(rules.breaks(text))
.filter(({ position }) => boundaries.has(position))
.map(({ position }) => {
const range = { start, end: position };
start = position;
return range;
});
};
entry = { normal: spans(normal), emergency: spans(emergency) };
if (text.length >= 256 && text.length <= 120000) {
if (cached.size >= 4) cached.delete(cached.keys().next().value!);
cached.set(text, entry);
}
}
return entry[emergencyWrap ? "emergency" : "normal"];
}