Export template source from 488f7e229afed8f03fc3bfa4abe707da0c613460:examples/local-first/packages/language-reader-presentation
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
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"];
|
||||
}
|
||||
Reference in New Issue
Block a user