@zzclub/pipeline 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +559 -0
- package/dist/assets/imgx/assets/browser/obstacle-flow.d.ts +27 -0
- package/dist/assets/imgx/assets/browser/obstacle-flow.js +326 -0
- package/dist/assets/imgx/assets/icons/avatar_jinx_cartoon.jpg +0 -0
- package/dist/assets/imgx/assets/icons/clover.svg +15 -0
- package/dist/assets/imgx/assets/icons/fishbone-logo-square.png +0 -0
- package/dist/assets/imgx/assets/icons/fishbone-logo.jpg +0 -0
- package/dist/assets/imgx/assets/icons/fishbone-logo.png +0 -0
- package/dist/assets/imgx/assets/icons/openclaw-logo.svg +22 -0
- package/dist/assets/imgx/assets/icons/zzclub-logo-black.jpg +0 -0
- package/dist/assets/imgx/assets/icons/zzclub-logo-gray.svg +25 -0
- package/dist/assets/imgx/assets/templates/ascii-portrait-3-4.html +342 -0
- package/dist/assets/imgx/assets/templates/ascii-portrait-tile.html +144 -0
- package/dist/assets/imgx/assets/templates/longform-3-4.html +179 -0
- package/dist/assets/imgx/assets/templates/poster-3-4.html +359 -0
- package/dist/assets/imgx/assets/templates/tips-3-4.html +300 -0
- package/dist/assets/imgx/assets/templates/wechat-cover-split.html +307 -0
- package/dist/assets/imgx/assets/templates/x-like-posts.html +246 -0
- package/dist/assets/wechat-preview/assets/browser-dist/.vite/manifest.json +314 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/SweiCurveLegCJKsc-Regular-B3Ut5bwH.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/style-BYNXnUvc.css +1 -0
- package/dist/assets/wechat-preview/assets/browser-dist/editor-export.js +969 -0
- package/dist/assets/wechat-preview/assets/templates/export-shell.html +38 -0
- package/dist/cli.js +404 -0
- package/dist/node_modules/@chenglou/pretext/dist/analysis.d.ts +33 -0
- package/dist/node_modules/@chenglou/pretext/dist/analysis.js +1063 -0
- package/dist/node_modules/@chenglou/pretext/dist/bidi.d.ts +1 -0
- package/dist/node_modules/@chenglou/pretext/dist/bidi.js +175 -0
- package/dist/node_modules/@chenglou/pretext/dist/generated/bidi-data.d.ts +4 -0
- package/dist/node_modules/@chenglou/pretext/dist/generated/bidi-data.js +979 -0
- package/dist/node_modules/@chenglou/pretext/dist/layout.d.ts +70 -0
- package/dist/node_modules/@chenglou/pretext/dist/layout.js +496 -0
- package/dist/node_modules/@chenglou/pretext/dist/line-break.d.ts +36 -0
- package/dist/node_modules/@chenglou/pretext/dist/line-break.js +820 -0
- package/dist/node_modules/@chenglou/pretext/dist/measurement.d.ts +28 -0
- package/dist/node_modules/@chenglou/pretext/dist/measurement.js +219 -0
- package/dist/node_modules/@chenglou/pretext/dist/rich-inline.d.ts +51 -0
- package/dist/node_modules/@chenglou/pretext/dist/rich-inline.js +401 -0
- package/package.json +42 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type SegmentBreakKind, type WhiteSpaceMode, type WordBreakMode as AnalysisWordBreakMode } from './analysis.js';
|
|
2
|
+
declare const preparedTextBrand: unique symbol;
|
|
3
|
+
type PreparedCore = {
|
|
4
|
+
widths: number[];
|
|
5
|
+
lineEndFitAdvances: number[];
|
|
6
|
+
lineEndPaintAdvances: number[];
|
|
7
|
+
kinds: SegmentBreakKind[];
|
|
8
|
+
simpleLineWalkFastPath: boolean;
|
|
9
|
+
segLevels: Int8Array | null;
|
|
10
|
+
breakableFitAdvances: (number[] | null)[];
|
|
11
|
+
discretionaryHyphenWidth: number;
|
|
12
|
+
tabStopAdvance: number;
|
|
13
|
+
chunks: PreparedLineChunk[];
|
|
14
|
+
};
|
|
15
|
+
export type PreparedText = {
|
|
16
|
+
readonly [preparedTextBrand]: true;
|
|
17
|
+
};
|
|
18
|
+
type InternalPreparedText = PreparedText & PreparedCore;
|
|
19
|
+
export type PreparedTextWithSegments = InternalPreparedText & {
|
|
20
|
+
segments: string[];
|
|
21
|
+
};
|
|
22
|
+
export type LayoutCursor = {
|
|
23
|
+
segmentIndex: number;
|
|
24
|
+
graphemeIndex: number;
|
|
25
|
+
};
|
|
26
|
+
export type LayoutResult = {
|
|
27
|
+
lineCount: number;
|
|
28
|
+
height: number;
|
|
29
|
+
};
|
|
30
|
+
export type LineStats = {
|
|
31
|
+
lineCount: number;
|
|
32
|
+
maxLineWidth: number;
|
|
33
|
+
};
|
|
34
|
+
export type LayoutLine = {
|
|
35
|
+
text: string;
|
|
36
|
+
width: number;
|
|
37
|
+
start: LayoutCursor;
|
|
38
|
+
end: LayoutCursor;
|
|
39
|
+
};
|
|
40
|
+
export type LayoutLineRange = {
|
|
41
|
+
width: number;
|
|
42
|
+
start: LayoutCursor;
|
|
43
|
+
end: LayoutCursor;
|
|
44
|
+
};
|
|
45
|
+
export type LayoutLinesResult = LayoutResult & {
|
|
46
|
+
lines: LayoutLine[];
|
|
47
|
+
};
|
|
48
|
+
export type WordBreakMode = AnalysisWordBreakMode;
|
|
49
|
+
export type PrepareOptions = {
|
|
50
|
+
whiteSpace?: WhiteSpaceMode;
|
|
51
|
+
wordBreak?: WordBreakMode;
|
|
52
|
+
};
|
|
53
|
+
type PreparedLineChunk = {
|
|
54
|
+
startSegmentIndex: number;
|
|
55
|
+
endSegmentIndex: number;
|
|
56
|
+
consumedEndSegmentIndex: number;
|
|
57
|
+
};
|
|
58
|
+
export declare function prepare(text: string, font: string, options?: PrepareOptions): PreparedText;
|
|
59
|
+
export declare function prepareWithSegments(text: string, font: string, options?: PrepareOptions): PreparedTextWithSegments;
|
|
60
|
+
export declare function layout(prepared: PreparedText, maxWidth: number, lineHeight: number): LayoutResult;
|
|
61
|
+
export declare function materializeLineRange(prepared: PreparedTextWithSegments, line: LayoutLineRange): LayoutLine;
|
|
62
|
+
export declare function walkLineRanges(prepared: PreparedTextWithSegments, maxWidth: number, onLine: (line: LayoutLineRange) => void): number;
|
|
63
|
+
export declare function measureLineStats(prepared: PreparedTextWithSegments, maxWidth: number): LineStats;
|
|
64
|
+
export declare function measureNaturalWidth(prepared: PreparedTextWithSegments): number;
|
|
65
|
+
export declare function layoutNextLine(prepared: PreparedTextWithSegments, start: LayoutCursor, maxWidth: number): LayoutLine | null;
|
|
66
|
+
export declare function layoutNextLineRange(prepared: PreparedTextWithSegments, start: LayoutCursor, maxWidth: number): LayoutLineRange | null;
|
|
67
|
+
export declare function layoutWithLines(prepared: PreparedTextWithSegments, maxWidth: number, lineHeight: number): LayoutLinesResult;
|
|
68
|
+
export declare function clearCache(): void;
|
|
69
|
+
export declare function setLocale(locale?: string): void;
|
|
70
|
+
export {};
|
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
// Text measurement for browser environments using canvas measureText.
|
|
2
|
+
//
|
|
3
|
+
// Problem: DOM-based text measurement (getBoundingClientRect, offsetHeight)
|
|
4
|
+
// forces synchronous layout reflow. When components independently measure text,
|
|
5
|
+
// each measurement triggers a reflow of the entire document. This creates
|
|
6
|
+
// read/write interleaving that can cost 30ms+ per frame for 500 text blocks.
|
|
7
|
+
//
|
|
8
|
+
// Solution: two-phase measurement centered around canvas measureText.
|
|
9
|
+
// prepare(text, font) — segments text via Intl.Segmenter, measures each word
|
|
10
|
+
// via canvas, caches widths, and does one cached DOM calibration read per
|
|
11
|
+
// font when emoji correction is needed. Call once when text first appears.
|
|
12
|
+
// layout(prepared, maxWidth, lineHeight) — walks cached word widths with pure
|
|
13
|
+
// arithmetic to count lines and compute height. Call on every resize.
|
|
14
|
+
// ~0.0002ms per text.
|
|
15
|
+
//
|
|
16
|
+
// i18n: Intl.Segmenter handles CJK (per-character breaking), Thai, Arabic, etc.
|
|
17
|
+
// Bidi: simplified rich-path metadata for mixed LTR/RTL custom rendering.
|
|
18
|
+
// Punctuation merging: "better." measured as one unit (matches CSS behavior).
|
|
19
|
+
// Trailing whitespace: hangs past line edge without triggering breaks (CSS behavior).
|
|
20
|
+
// overflow-wrap: pre-measured grapheme widths enable character-level word breaking.
|
|
21
|
+
//
|
|
22
|
+
// Emoji correction: Chrome/Firefox canvas measures emoji wider than DOM at font
|
|
23
|
+
// sizes <24px on macOS (Apple Color Emoji). The inflation is constant per emoji
|
|
24
|
+
// grapheme at a given size, font-independent. Auto-detected by comparing canvas
|
|
25
|
+
// vs actual DOM emoji width (one cached DOM read per font). Safari canvas and
|
|
26
|
+
// DOM agree (both wider than fontSize), so correction = 0 there.
|
|
27
|
+
//
|
|
28
|
+
// Limitations:
|
|
29
|
+
// - system-ui font: canvas resolves to different optical variants than DOM on macOS.
|
|
30
|
+
// Use named fonts (Helvetica, Inter, etc.) for guaranteed accuracy.
|
|
31
|
+
// See RESEARCH.md "Discovery: system-ui font resolution mismatch".
|
|
32
|
+
//
|
|
33
|
+
// Based on Sebastian Markbage's text-layout research (github.com/chenglou/text-layout).
|
|
34
|
+
import { computeSegmentLevels } from './bidi.js';
|
|
35
|
+
import { analyzeText, canContinueKeepAllTextRun, clearAnalysisCaches, endsWithClosingQuote, isCJK, isNumericRunSegment, kinsokuEnd, kinsokuStart, leftStickyPunctuation, setAnalysisLocale, } from './analysis.js';
|
|
36
|
+
import { clearMeasurementCaches, getCorrectedSegmentWidth, getSegmentBreakableFitAdvances, getEngineProfile, getFontMeasurementState, getSegmentMetrics, textMayContainEmoji, } from './measurement.js';
|
|
37
|
+
import { countPreparedLines, layoutNextLineRange as stepPreparedLineRange, measurePreparedLineGeometry, walkPreparedLines, } from './line-break.js';
|
|
38
|
+
let sharedGraphemeSegmenter = null;
|
|
39
|
+
// Rich-path only. Reuses grapheme splits while materializing multiple lines
|
|
40
|
+
// from the same prepared handle, without pushing that cache into the API.
|
|
41
|
+
let sharedLineTextCaches = new WeakMap();
|
|
42
|
+
function getSharedGraphemeSegmenter() {
|
|
43
|
+
if (sharedGraphemeSegmenter === null) {
|
|
44
|
+
sharedGraphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
45
|
+
}
|
|
46
|
+
return sharedGraphemeSegmenter;
|
|
47
|
+
}
|
|
48
|
+
// --- Public API ---
|
|
49
|
+
function createEmptyPrepared(includeSegments) {
|
|
50
|
+
if (includeSegments) {
|
|
51
|
+
return {
|
|
52
|
+
widths: [],
|
|
53
|
+
lineEndFitAdvances: [],
|
|
54
|
+
lineEndPaintAdvances: [],
|
|
55
|
+
kinds: [],
|
|
56
|
+
simpleLineWalkFastPath: true,
|
|
57
|
+
segLevels: null,
|
|
58
|
+
breakableFitAdvances: [],
|
|
59
|
+
discretionaryHyphenWidth: 0,
|
|
60
|
+
tabStopAdvance: 0,
|
|
61
|
+
chunks: [],
|
|
62
|
+
segments: [],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
widths: [],
|
|
67
|
+
lineEndFitAdvances: [],
|
|
68
|
+
lineEndPaintAdvances: [],
|
|
69
|
+
kinds: [],
|
|
70
|
+
simpleLineWalkFastPath: true,
|
|
71
|
+
segLevels: null,
|
|
72
|
+
breakableFitAdvances: [],
|
|
73
|
+
discretionaryHyphenWidth: 0,
|
|
74
|
+
tabStopAdvance: 0,
|
|
75
|
+
chunks: [],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function buildBaseCjkUnits(segText, engineProfile) {
|
|
79
|
+
const units = [];
|
|
80
|
+
let unitParts = [];
|
|
81
|
+
let unitStart = 0;
|
|
82
|
+
let unitContainsCJK = false;
|
|
83
|
+
let unitEndsWithClosingQuote = false;
|
|
84
|
+
let unitIsSingleKinsokuEnd = false;
|
|
85
|
+
function pushUnit() {
|
|
86
|
+
if (unitParts.length === 0)
|
|
87
|
+
return;
|
|
88
|
+
units.push({
|
|
89
|
+
text: unitParts.length === 1 ? unitParts[0] : unitParts.join(''),
|
|
90
|
+
start: unitStart,
|
|
91
|
+
});
|
|
92
|
+
unitParts = [];
|
|
93
|
+
unitContainsCJK = false;
|
|
94
|
+
unitEndsWithClosingQuote = false;
|
|
95
|
+
unitIsSingleKinsokuEnd = false;
|
|
96
|
+
}
|
|
97
|
+
function startUnit(grapheme, start, graphemeContainsCJK) {
|
|
98
|
+
unitParts = [grapheme];
|
|
99
|
+
unitStart = start;
|
|
100
|
+
unitContainsCJK = graphemeContainsCJK;
|
|
101
|
+
unitEndsWithClosingQuote = endsWithClosingQuote(grapheme);
|
|
102
|
+
unitIsSingleKinsokuEnd = kinsokuEnd.has(grapheme);
|
|
103
|
+
}
|
|
104
|
+
function appendToUnit(grapheme, graphemeContainsCJK) {
|
|
105
|
+
unitParts.push(grapheme);
|
|
106
|
+
unitContainsCJK = unitContainsCJK || graphemeContainsCJK;
|
|
107
|
+
const graphemeEndsWithClosingQuote = endsWithClosingQuote(grapheme);
|
|
108
|
+
if (grapheme.length === 1 && leftStickyPunctuation.has(grapheme)) {
|
|
109
|
+
unitEndsWithClosingQuote = unitEndsWithClosingQuote || graphemeEndsWithClosingQuote;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
unitEndsWithClosingQuote = graphemeEndsWithClosingQuote;
|
|
113
|
+
}
|
|
114
|
+
unitIsSingleKinsokuEnd = false;
|
|
115
|
+
}
|
|
116
|
+
for (const gs of getSharedGraphemeSegmenter().segment(segText)) {
|
|
117
|
+
const grapheme = gs.segment;
|
|
118
|
+
const graphemeContainsCJK = isCJK(grapheme);
|
|
119
|
+
if (unitParts.length === 0) {
|
|
120
|
+
startUnit(grapheme, gs.index, graphemeContainsCJK);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (unitIsSingleKinsokuEnd ||
|
|
124
|
+
kinsokuStart.has(grapheme) ||
|
|
125
|
+
leftStickyPunctuation.has(grapheme) ||
|
|
126
|
+
(engineProfile.carryCJKAfterClosingQuote &&
|
|
127
|
+
graphemeContainsCJK &&
|
|
128
|
+
unitEndsWithClosingQuote)) {
|
|
129
|
+
appendToUnit(grapheme, graphemeContainsCJK);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (!unitContainsCJK && !graphemeContainsCJK) {
|
|
133
|
+
appendToUnit(grapheme, graphemeContainsCJK);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
pushUnit();
|
|
137
|
+
startUnit(grapheme, gs.index, graphemeContainsCJK);
|
|
138
|
+
}
|
|
139
|
+
pushUnit();
|
|
140
|
+
return units;
|
|
141
|
+
}
|
|
142
|
+
function mergeKeepAllTextUnits(units) {
|
|
143
|
+
if (units.length <= 1)
|
|
144
|
+
return units;
|
|
145
|
+
const merged = [];
|
|
146
|
+
let currentTextParts = [units[0].text];
|
|
147
|
+
let currentStart = units[0].start;
|
|
148
|
+
let currentContainsCJK = isCJK(units[0].text);
|
|
149
|
+
let currentCanContinue = canContinueKeepAllTextRun(units[0].text);
|
|
150
|
+
function flushCurrent() {
|
|
151
|
+
merged.push({
|
|
152
|
+
text: currentTextParts.length === 1 ? currentTextParts[0] : currentTextParts.join(''),
|
|
153
|
+
start: currentStart,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
for (let i = 1; i < units.length; i++) {
|
|
157
|
+
const next = units[i];
|
|
158
|
+
const nextContainsCJK = isCJK(next.text);
|
|
159
|
+
const nextCanContinue = canContinueKeepAllTextRun(next.text);
|
|
160
|
+
if (currentContainsCJK && currentCanContinue) {
|
|
161
|
+
currentTextParts.push(next.text);
|
|
162
|
+
currentContainsCJK = currentContainsCJK || nextContainsCJK;
|
|
163
|
+
currentCanContinue = nextCanContinue;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
flushCurrent();
|
|
167
|
+
currentTextParts = [next.text];
|
|
168
|
+
currentStart = next.start;
|
|
169
|
+
currentContainsCJK = nextContainsCJK;
|
|
170
|
+
currentCanContinue = nextCanContinue;
|
|
171
|
+
}
|
|
172
|
+
flushCurrent();
|
|
173
|
+
return merged;
|
|
174
|
+
}
|
|
175
|
+
function measureAnalysis(analysis, font, includeSegments, wordBreak) {
|
|
176
|
+
const engineProfile = getEngineProfile();
|
|
177
|
+
const { cache, emojiCorrection } = getFontMeasurementState(font, textMayContainEmoji(analysis.normalized));
|
|
178
|
+
const discretionaryHyphenWidth = getCorrectedSegmentWidth('-', getSegmentMetrics('-', cache), emojiCorrection);
|
|
179
|
+
const spaceWidth = getCorrectedSegmentWidth(' ', getSegmentMetrics(' ', cache), emojiCorrection);
|
|
180
|
+
const tabStopAdvance = spaceWidth * 8;
|
|
181
|
+
if (analysis.len === 0)
|
|
182
|
+
return createEmptyPrepared(includeSegments);
|
|
183
|
+
const widths = [];
|
|
184
|
+
const lineEndFitAdvances = [];
|
|
185
|
+
const lineEndPaintAdvances = [];
|
|
186
|
+
const kinds = [];
|
|
187
|
+
let simpleLineWalkFastPath = analysis.chunks.length <= 1;
|
|
188
|
+
const segStarts = includeSegments ? [] : null;
|
|
189
|
+
const breakableFitAdvances = [];
|
|
190
|
+
const segments = includeSegments ? [] : null;
|
|
191
|
+
const preparedStartByAnalysisIndex = Array.from({ length: analysis.len });
|
|
192
|
+
function pushMeasuredSegment(text, width, lineEndFitAdvance, lineEndPaintAdvance, kind, start, breakableFitAdvance) {
|
|
193
|
+
if (kind !== 'text' && kind !== 'space' && kind !== 'zero-width-break') {
|
|
194
|
+
simpleLineWalkFastPath = false;
|
|
195
|
+
}
|
|
196
|
+
widths.push(width);
|
|
197
|
+
lineEndFitAdvances.push(lineEndFitAdvance);
|
|
198
|
+
lineEndPaintAdvances.push(lineEndPaintAdvance);
|
|
199
|
+
kinds.push(kind);
|
|
200
|
+
segStarts?.push(start);
|
|
201
|
+
breakableFitAdvances.push(breakableFitAdvance);
|
|
202
|
+
if (segments !== null)
|
|
203
|
+
segments.push(text);
|
|
204
|
+
}
|
|
205
|
+
function pushMeasuredTextSegment(text, kind, start, wordLike, allowOverflowBreaks) {
|
|
206
|
+
const textMetrics = getSegmentMetrics(text, cache);
|
|
207
|
+
const width = getCorrectedSegmentWidth(text, textMetrics, emojiCorrection);
|
|
208
|
+
const lineEndFitAdvance = kind === 'space' || kind === 'preserved-space' || kind === 'zero-width-break'
|
|
209
|
+
? 0
|
|
210
|
+
: width;
|
|
211
|
+
const lineEndPaintAdvance = kind === 'space' || kind === 'zero-width-break'
|
|
212
|
+
? 0
|
|
213
|
+
: width;
|
|
214
|
+
if (allowOverflowBreaks && wordLike && text.length > 1) {
|
|
215
|
+
let fitMode = 'sum-graphemes';
|
|
216
|
+
if (isNumericRunSegment(text)) {
|
|
217
|
+
fitMode = 'pair-context';
|
|
218
|
+
}
|
|
219
|
+
else if (engineProfile.preferPrefixWidthsForBreakableRuns) {
|
|
220
|
+
fitMode = 'segment-prefixes';
|
|
221
|
+
}
|
|
222
|
+
const fitAdvances = getSegmentBreakableFitAdvances(text, textMetrics, cache, emojiCorrection, fitMode);
|
|
223
|
+
pushMeasuredSegment(text, width, lineEndFitAdvance, lineEndPaintAdvance, kind, start, fitAdvances);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
pushMeasuredSegment(text, width, lineEndFitAdvance, lineEndPaintAdvance, kind, start, null);
|
|
227
|
+
}
|
|
228
|
+
for (let mi = 0; mi < analysis.len; mi++) {
|
|
229
|
+
preparedStartByAnalysisIndex[mi] = widths.length;
|
|
230
|
+
const segText = analysis.texts[mi];
|
|
231
|
+
const segWordLike = analysis.isWordLike[mi];
|
|
232
|
+
const segKind = analysis.kinds[mi];
|
|
233
|
+
const segStart = analysis.starts[mi];
|
|
234
|
+
if (segKind === 'soft-hyphen') {
|
|
235
|
+
pushMeasuredSegment(segText, 0, discretionaryHyphenWidth, discretionaryHyphenWidth, segKind, segStart, null);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (segKind === 'hard-break') {
|
|
239
|
+
pushMeasuredSegment(segText, 0, 0, 0, segKind, segStart, null);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (segKind === 'tab') {
|
|
243
|
+
pushMeasuredSegment(segText, 0, 0, 0, segKind, segStart, null);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const segMetrics = getSegmentMetrics(segText, cache);
|
|
247
|
+
if (segKind === 'text' && segMetrics.containsCJK) {
|
|
248
|
+
const baseUnits = buildBaseCjkUnits(segText, engineProfile);
|
|
249
|
+
const measuredUnits = wordBreak === 'keep-all'
|
|
250
|
+
? mergeKeepAllTextUnits(baseUnits)
|
|
251
|
+
: baseUnits;
|
|
252
|
+
for (let i = 0; i < measuredUnits.length; i++) {
|
|
253
|
+
const unit = measuredUnits[i];
|
|
254
|
+
pushMeasuredTextSegment(unit.text, 'text', segStart + unit.start, segWordLike, wordBreak === 'keep-all' || !isCJK(unit.text));
|
|
255
|
+
}
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
pushMeasuredTextSegment(segText, segKind, segStart, segWordLike, true);
|
|
259
|
+
}
|
|
260
|
+
const chunks = mapAnalysisChunksToPreparedChunks(analysis.chunks, preparedStartByAnalysisIndex, widths.length);
|
|
261
|
+
const segLevels = segStarts === null ? null : computeSegmentLevels(analysis.normalized, segStarts);
|
|
262
|
+
if (segments !== null) {
|
|
263
|
+
return {
|
|
264
|
+
widths,
|
|
265
|
+
lineEndFitAdvances,
|
|
266
|
+
lineEndPaintAdvances,
|
|
267
|
+
kinds,
|
|
268
|
+
simpleLineWalkFastPath,
|
|
269
|
+
segLevels,
|
|
270
|
+
breakableFitAdvances,
|
|
271
|
+
discretionaryHyphenWidth,
|
|
272
|
+
tabStopAdvance,
|
|
273
|
+
chunks,
|
|
274
|
+
segments,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
widths,
|
|
279
|
+
lineEndFitAdvances,
|
|
280
|
+
lineEndPaintAdvances,
|
|
281
|
+
kinds,
|
|
282
|
+
simpleLineWalkFastPath,
|
|
283
|
+
segLevels,
|
|
284
|
+
breakableFitAdvances,
|
|
285
|
+
discretionaryHyphenWidth,
|
|
286
|
+
tabStopAdvance,
|
|
287
|
+
chunks,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function mapAnalysisChunksToPreparedChunks(chunks, preparedStartByAnalysisIndex, preparedEndSegmentIndex) {
|
|
291
|
+
const preparedChunks = [];
|
|
292
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
293
|
+
const chunk = chunks[i];
|
|
294
|
+
const startSegmentIndex = chunk.startSegmentIndex < preparedStartByAnalysisIndex.length
|
|
295
|
+
? preparedStartByAnalysisIndex[chunk.startSegmentIndex]
|
|
296
|
+
: preparedEndSegmentIndex;
|
|
297
|
+
const endSegmentIndex = chunk.endSegmentIndex < preparedStartByAnalysisIndex.length
|
|
298
|
+
? preparedStartByAnalysisIndex[chunk.endSegmentIndex]
|
|
299
|
+
: preparedEndSegmentIndex;
|
|
300
|
+
const consumedEndSegmentIndex = chunk.consumedEndSegmentIndex < preparedStartByAnalysisIndex.length
|
|
301
|
+
? preparedStartByAnalysisIndex[chunk.consumedEndSegmentIndex]
|
|
302
|
+
: preparedEndSegmentIndex;
|
|
303
|
+
preparedChunks.push({
|
|
304
|
+
startSegmentIndex,
|
|
305
|
+
endSegmentIndex,
|
|
306
|
+
consumedEndSegmentIndex,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return preparedChunks;
|
|
310
|
+
}
|
|
311
|
+
function prepareInternal(text, font, includeSegments, options) {
|
|
312
|
+
const wordBreak = options?.wordBreak ?? 'normal';
|
|
313
|
+
const analysis = analyzeText(text, getEngineProfile(), options?.whiteSpace, wordBreak);
|
|
314
|
+
return measureAnalysis(analysis, font, includeSegments, wordBreak);
|
|
315
|
+
}
|
|
316
|
+
// Prepare text for layout. Segments the text, measures each segment via canvas,
|
|
317
|
+
// and stores the widths for fast relayout at any width. Call once per text block
|
|
318
|
+
// (e.g. when a comment first appears). The result is width-independent — the
|
|
319
|
+
// same PreparedText can be laid out at any maxWidth and lineHeight via layout().
|
|
320
|
+
//
|
|
321
|
+
// Steps:
|
|
322
|
+
// 1. Normalize collapsible whitespace (CSS white-space: normal behavior)
|
|
323
|
+
// 2. Segment via Intl.Segmenter (handles CJK, Thai, etc.)
|
|
324
|
+
// 3. Merge punctuation into preceding word ("better." as one unit)
|
|
325
|
+
// 4. Split CJK words into individual graphemes (per-character line breaks)
|
|
326
|
+
// 5. Measure each segment via canvas measureText, cache by (segment, font)
|
|
327
|
+
// 6. Pre-measure graphemes of long words (for overflow-wrap: break-word)
|
|
328
|
+
// 7. Correct emoji canvas inflation (auto-detected per font size)
|
|
329
|
+
// 8. Optionally compute rich-path bidi metadata for custom renderers
|
|
330
|
+
export function prepare(text, font, options) {
|
|
331
|
+
return prepareInternal(text, font, false, options);
|
|
332
|
+
}
|
|
333
|
+
// Rich variant used by callers that need enough information to render the
|
|
334
|
+
// laid-out lines themselves.
|
|
335
|
+
export function prepareWithSegments(text, font, options) {
|
|
336
|
+
return prepareInternal(text, font, true, options);
|
|
337
|
+
}
|
|
338
|
+
function getInternalPrepared(prepared) {
|
|
339
|
+
return prepared;
|
|
340
|
+
}
|
|
341
|
+
// Layout prepared text at a given max width and caller-provided lineHeight.
|
|
342
|
+
// Pure arithmetic on cached widths — no canvas calls, no DOM reads, no string
|
|
343
|
+
// operations, no allocations.
|
|
344
|
+
// ~0.0002ms per text block. Call on every resize.
|
|
345
|
+
//
|
|
346
|
+
// Line breaking rules (matching CSS white-space: normal + overflow-wrap: break-word):
|
|
347
|
+
// - Break before any non-space segment that would overflow the line
|
|
348
|
+
// - Trailing whitespace hangs past the line edge (doesn't trigger breaks)
|
|
349
|
+
// - Segments wider than maxWidth are broken at grapheme boundaries
|
|
350
|
+
export function layout(prepared, maxWidth, lineHeight) {
|
|
351
|
+
// Keep the resize hot path specialized. `layoutWithLines()` shares the same
|
|
352
|
+
// break semantics but also tracks line ranges; the extra bookkeeping is too
|
|
353
|
+
// expensive to pay on every hot-path `layout()` call.
|
|
354
|
+
const lineCount = countPreparedLines(getInternalPrepared(prepared), maxWidth);
|
|
355
|
+
return { lineCount, height: lineCount * lineHeight };
|
|
356
|
+
}
|
|
357
|
+
function getSegmentGraphemes(segmentIndex, segments, cache) {
|
|
358
|
+
let graphemes = cache.get(segmentIndex);
|
|
359
|
+
if (graphemes !== undefined)
|
|
360
|
+
return graphemes;
|
|
361
|
+
graphemes = [];
|
|
362
|
+
const graphemeSegmenter = getSharedGraphemeSegmenter();
|
|
363
|
+
for (const gs of graphemeSegmenter.segment(segments[segmentIndex])) {
|
|
364
|
+
graphemes.push(gs.segment);
|
|
365
|
+
}
|
|
366
|
+
cache.set(segmentIndex, graphemes);
|
|
367
|
+
return graphemes;
|
|
368
|
+
}
|
|
369
|
+
function getLineTextCache(prepared) {
|
|
370
|
+
let cache = sharedLineTextCaches.get(prepared);
|
|
371
|
+
if (cache !== undefined)
|
|
372
|
+
return cache;
|
|
373
|
+
cache = new Map();
|
|
374
|
+
sharedLineTextCaches.set(prepared, cache);
|
|
375
|
+
return cache;
|
|
376
|
+
}
|
|
377
|
+
function lineHasDiscretionaryHyphen(kinds, startSegmentIndex, startGraphemeIndex, endSegmentIndex) {
|
|
378
|
+
return (endSegmentIndex > 0 &&
|
|
379
|
+
kinds[endSegmentIndex - 1] === 'soft-hyphen' &&
|
|
380
|
+
!(startSegmentIndex === endSegmentIndex && startGraphemeIndex > 0));
|
|
381
|
+
}
|
|
382
|
+
function buildLineTextFromRange(segments, kinds, cache, startSegmentIndex, startGraphemeIndex, endSegmentIndex, endGraphemeIndex) {
|
|
383
|
+
let text = '';
|
|
384
|
+
const endsWithDiscretionaryHyphen = lineHasDiscretionaryHyphen(kinds, startSegmentIndex, startGraphemeIndex, endSegmentIndex);
|
|
385
|
+
for (let i = startSegmentIndex; i < endSegmentIndex; i++) {
|
|
386
|
+
if (kinds[i] === 'soft-hyphen' || kinds[i] === 'hard-break')
|
|
387
|
+
continue;
|
|
388
|
+
if (i === startSegmentIndex && startGraphemeIndex > 0) {
|
|
389
|
+
text += getSegmentGraphemes(i, segments, cache).slice(startGraphemeIndex).join('');
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
text += segments[i];
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (endGraphemeIndex > 0) {
|
|
396
|
+
if (endsWithDiscretionaryHyphen)
|
|
397
|
+
text += '-';
|
|
398
|
+
text += getSegmentGraphemes(endSegmentIndex, segments, cache).slice(startSegmentIndex === endSegmentIndex ? startGraphemeIndex : 0, endGraphemeIndex).join('');
|
|
399
|
+
}
|
|
400
|
+
else if (endsWithDiscretionaryHyphen) {
|
|
401
|
+
text += '-';
|
|
402
|
+
}
|
|
403
|
+
return text;
|
|
404
|
+
}
|
|
405
|
+
function createLayoutLine(prepared, cache, width, startSegmentIndex, startGraphemeIndex, endSegmentIndex, endGraphemeIndex) {
|
|
406
|
+
return {
|
|
407
|
+
text: buildLineTextFromRange(prepared.segments, prepared.kinds, cache, startSegmentIndex, startGraphemeIndex, endSegmentIndex, endGraphemeIndex),
|
|
408
|
+
width,
|
|
409
|
+
start: {
|
|
410
|
+
segmentIndex: startSegmentIndex,
|
|
411
|
+
graphemeIndex: startGraphemeIndex,
|
|
412
|
+
},
|
|
413
|
+
end: {
|
|
414
|
+
segmentIndex: endSegmentIndex,
|
|
415
|
+
graphemeIndex: endGraphemeIndex,
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
function materializeLayoutLine(prepared, cache, line) {
|
|
420
|
+
return createLayoutLine(prepared, cache, line.width, line.startSegmentIndex, line.startGraphemeIndex, line.endSegmentIndex, line.endGraphemeIndex);
|
|
421
|
+
}
|
|
422
|
+
function toLayoutLineRange(line) {
|
|
423
|
+
return {
|
|
424
|
+
width: line.width,
|
|
425
|
+
start: {
|
|
426
|
+
segmentIndex: line.startSegmentIndex,
|
|
427
|
+
graphemeIndex: line.startGraphemeIndex,
|
|
428
|
+
},
|
|
429
|
+
end: {
|
|
430
|
+
segmentIndex: line.endSegmentIndex,
|
|
431
|
+
graphemeIndex: line.endGraphemeIndex,
|
|
432
|
+
},
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
export function materializeLineRange(prepared, line) {
|
|
436
|
+
return createLayoutLine(prepared, getLineTextCache(prepared), line.width, line.start.segmentIndex, line.start.graphemeIndex, line.end.segmentIndex, line.end.graphemeIndex);
|
|
437
|
+
}
|
|
438
|
+
// Batch low-level line-range pass. This is the non-materializing counterpart
|
|
439
|
+
// to layoutWithLines(), useful for shrinkwrap and other aggregate stats work.
|
|
440
|
+
export function walkLineRanges(prepared, maxWidth, onLine) {
|
|
441
|
+
if (prepared.widths.length === 0)
|
|
442
|
+
return 0;
|
|
443
|
+
return walkPreparedLines(getInternalPrepared(prepared), maxWidth, line => {
|
|
444
|
+
onLine(toLayoutLineRange(line));
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
export function measureLineStats(prepared, maxWidth) {
|
|
448
|
+
return measurePreparedLineGeometry(getInternalPrepared(prepared), maxWidth);
|
|
449
|
+
}
|
|
450
|
+
// Intrinsic-width helper for rich/userland layout work. This asks "how wide is
|
|
451
|
+
// the prepared text when container width is not the thing forcing wraps?".
|
|
452
|
+
// Explicit hard breaks still count, so this returns the widest forced line.
|
|
453
|
+
export function measureNaturalWidth(prepared) {
|
|
454
|
+
let maxWidth = 0;
|
|
455
|
+
walkLineRanges(prepared, Number.POSITIVE_INFINITY, line => {
|
|
456
|
+
if (line.width > maxWidth)
|
|
457
|
+
maxWidth = line.width;
|
|
458
|
+
});
|
|
459
|
+
return maxWidth;
|
|
460
|
+
}
|
|
461
|
+
export function layoutNextLine(prepared, start, maxWidth) {
|
|
462
|
+
const line = layoutNextLineRange(prepared, start, maxWidth);
|
|
463
|
+
if (line === null)
|
|
464
|
+
return null;
|
|
465
|
+
return materializeLineRange(prepared, line);
|
|
466
|
+
}
|
|
467
|
+
export function layoutNextLineRange(prepared, start, maxWidth) {
|
|
468
|
+
const line = stepPreparedLineRange(prepared, start, maxWidth);
|
|
469
|
+
if (line === null)
|
|
470
|
+
return null;
|
|
471
|
+
return toLayoutLineRange(line);
|
|
472
|
+
}
|
|
473
|
+
// Rich layout API for callers that want the actual line contents and widths.
|
|
474
|
+
// Caller still supplies lineHeight at layout time. Mirrors layout()'s break
|
|
475
|
+
// decisions, but keeps extra per-line bookkeeping so it should stay off the
|
|
476
|
+
// resize hot path.
|
|
477
|
+
export function layoutWithLines(prepared, maxWidth, lineHeight) {
|
|
478
|
+
const lines = [];
|
|
479
|
+
if (prepared.widths.length === 0)
|
|
480
|
+
return { lineCount: 0, height: 0, lines };
|
|
481
|
+
const graphemeCache = getLineTextCache(prepared);
|
|
482
|
+
const lineCount = walkPreparedLines(getInternalPrepared(prepared), maxWidth, line => {
|
|
483
|
+
lines.push(materializeLayoutLine(prepared, graphemeCache, line));
|
|
484
|
+
});
|
|
485
|
+
return { lineCount, height: lineCount * lineHeight, lines };
|
|
486
|
+
}
|
|
487
|
+
export function clearCache() {
|
|
488
|
+
clearAnalysisCaches();
|
|
489
|
+
sharedGraphemeSegmenter = null;
|
|
490
|
+
sharedLineTextCaches = new WeakMap();
|
|
491
|
+
clearMeasurementCaches();
|
|
492
|
+
}
|
|
493
|
+
export function setLocale(locale) {
|
|
494
|
+
setAnalysisLocale(locale);
|
|
495
|
+
clearCache();
|
|
496
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { SegmentBreakKind } from './analysis.js';
|
|
2
|
+
export type LineBreakCursor = {
|
|
3
|
+
segmentIndex: number;
|
|
4
|
+
graphemeIndex: number;
|
|
5
|
+
};
|
|
6
|
+
export type PreparedLineBreakData = {
|
|
7
|
+
widths: number[];
|
|
8
|
+
lineEndFitAdvances: number[];
|
|
9
|
+
lineEndPaintAdvances: number[];
|
|
10
|
+
kinds: SegmentBreakKind[];
|
|
11
|
+
simpleLineWalkFastPath: boolean;
|
|
12
|
+
breakableFitAdvances: (number[] | null)[];
|
|
13
|
+
discretionaryHyphenWidth: number;
|
|
14
|
+
tabStopAdvance: number;
|
|
15
|
+
chunks: {
|
|
16
|
+
startSegmentIndex: number;
|
|
17
|
+
endSegmentIndex: number;
|
|
18
|
+
consumedEndSegmentIndex: number;
|
|
19
|
+
}[];
|
|
20
|
+
};
|
|
21
|
+
export type InternalLayoutLine = {
|
|
22
|
+
startSegmentIndex: number;
|
|
23
|
+
startGraphemeIndex: number;
|
|
24
|
+
endSegmentIndex: number;
|
|
25
|
+
endGraphemeIndex: number;
|
|
26
|
+
width: number;
|
|
27
|
+
};
|
|
28
|
+
export declare function normalizeLineStart(prepared: PreparedLineBreakData, start: LineBreakCursor): LineBreakCursor | null;
|
|
29
|
+
export declare function countPreparedLines(prepared: PreparedLineBreakData, maxWidth: number): number;
|
|
30
|
+
export declare function walkPreparedLines(prepared: PreparedLineBreakData, maxWidth: number, onLine?: (line: InternalLayoutLine) => void): number;
|
|
31
|
+
export declare function layoutNextLineRange(prepared: PreparedLineBreakData, start: LineBreakCursor, maxWidth: number): InternalLayoutLine | null;
|
|
32
|
+
export declare function stepPreparedLineGeometry(prepared: PreparedLineBreakData, cursor: LineBreakCursor, maxWidth: number): number | null;
|
|
33
|
+
export declare function measurePreparedLineGeometry(prepared: PreparedLineBreakData, maxWidth: number): {
|
|
34
|
+
lineCount: number;
|
|
35
|
+
maxLineWidth: number;
|
|
36
|
+
};
|