@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,28 @@
|
|
|
1
|
+
export type SegmentMetrics = {
|
|
2
|
+
width: number;
|
|
3
|
+
containsCJK: boolean;
|
|
4
|
+
emojiCount?: number;
|
|
5
|
+
breakableFitAdvances?: number[] | null;
|
|
6
|
+
};
|
|
7
|
+
export type EngineProfile = {
|
|
8
|
+
lineFitEpsilon: number;
|
|
9
|
+
carryCJKAfterClosingQuote: boolean;
|
|
10
|
+
preferPrefixWidthsForBreakableRuns: boolean;
|
|
11
|
+
preferEarlySoftHyphenBreak: boolean;
|
|
12
|
+
};
|
|
13
|
+
export type BreakableFitMode = 'sum-graphemes' | 'segment-prefixes' | 'pair-context';
|
|
14
|
+
export declare function getMeasureContext(): CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
|
|
15
|
+
export declare function getSegmentMetricCache(font: string): Map<string, SegmentMetrics>;
|
|
16
|
+
export declare function getSegmentMetrics(seg: string, cache: Map<string, SegmentMetrics>): SegmentMetrics;
|
|
17
|
+
export declare function getEngineProfile(): EngineProfile;
|
|
18
|
+
export declare function parseFontSize(font: string): number;
|
|
19
|
+
export declare function textMayContainEmoji(text: string): boolean;
|
|
20
|
+
export declare function getCorrectedSegmentWidth(seg: string, metrics: SegmentMetrics, emojiCorrection: number): number;
|
|
21
|
+
export declare function getSegmentGraphemeWidths(seg: string, cache: Map<string, SegmentMetrics>, emojiCorrection: number): number[] | null;
|
|
22
|
+
export declare function getSegmentBreakableFitAdvances(seg: string, metrics: SegmentMetrics, cache: Map<string, SegmentMetrics>, emojiCorrection: number, mode: BreakableFitMode): number[] | null;
|
|
23
|
+
export declare function getFontMeasurementState(font: string, needsEmojiCorrection: boolean): {
|
|
24
|
+
cache: Map<string, SegmentMetrics>;
|
|
25
|
+
fontSize: number;
|
|
26
|
+
emojiCorrection: number;
|
|
27
|
+
};
|
|
28
|
+
export declare function clearMeasurementCaches(): void;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { isCJK } from './analysis.js';
|
|
2
|
+
let measureContext = null;
|
|
3
|
+
const segmentMetricCaches = new Map();
|
|
4
|
+
let cachedEngineProfile = null;
|
|
5
|
+
// Safari's prefix-fit policy is useful for ordinary word-sized runs, but letting
|
|
6
|
+
// it measure every growing prefix of a giant segment recreates a pathological
|
|
7
|
+
// superlinear prepare-time path. Past this size, switch to the cheaper
|
|
8
|
+
// pair-context model and keep the public behavior linear.
|
|
9
|
+
const MAX_PREFIX_FIT_GRAPHEMES = 96;
|
|
10
|
+
const emojiPresentationRe = /\p{Emoji_Presentation}/u;
|
|
11
|
+
const maybeEmojiRe = /[\p{Emoji_Presentation}\p{Extended_Pictographic}\p{Regional_Indicator}\uFE0F\u20E3]/u;
|
|
12
|
+
let sharedGraphemeSegmenter = null;
|
|
13
|
+
const emojiCorrectionCache = new Map();
|
|
14
|
+
export function getMeasureContext() {
|
|
15
|
+
if (measureContext !== null)
|
|
16
|
+
return measureContext;
|
|
17
|
+
if (typeof OffscreenCanvas !== 'undefined') {
|
|
18
|
+
measureContext = new OffscreenCanvas(1, 1).getContext('2d');
|
|
19
|
+
return measureContext;
|
|
20
|
+
}
|
|
21
|
+
if (typeof document !== 'undefined') {
|
|
22
|
+
measureContext = document.createElement('canvas').getContext('2d');
|
|
23
|
+
return measureContext;
|
|
24
|
+
}
|
|
25
|
+
throw new Error('Text measurement requires OffscreenCanvas or a DOM canvas context.');
|
|
26
|
+
}
|
|
27
|
+
export function getSegmentMetricCache(font) {
|
|
28
|
+
let cache = segmentMetricCaches.get(font);
|
|
29
|
+
if (!cache) {
|
|
30
|
+
cache = new Map();
|
|
31
|
+
segmentMetricCaches.set(font, cache);
|
|
32
|
+
}
|
|
33
|
+
return cache;
|
|
34
|
+
}
|
|
35
|
+
export function getSegmentMetrics(seg, cache) {
|
|
36
|
+
let metrics = cache.get(seg);
|
|
37
|
+
if (metrics === undefined) {
|
|
38
|
+
const ctx = getMeasureContext();
|
|
39
|
+
metrics = {
|
|
40
|
+
width: ctx.measureText(seg).width,
|
|
41
|
+
containsCJK: isCJK(seg),
|
|
42
|
+
};
|
|
43
|
+
cache.set(seg, metrics);
|
|
44
|
+
}
|
|
45
|
+
return metrics;
|
|
46
|
+
}
|
|
47
|
+
export function getEngineProfile() {
|
|
48
|
+
if (cachedEngineProfile !== null)
|
|
49
|
+
return cachedEngineProfile;
|
|
50
|
+
if (typeof navigator === 'undefined') {
|
|
51
|
+
cachedEngineProfile = {
|
|
52
|
+
lineFitEpsilon: 0.005,
|
|
53
|
+
carryCJKAfterClosingQuote: false,
|
|
54
|
+
preferPrefixWidthsForBreakableRuns: false,
|
|
55
|
+
preferEarlySoftHyphenBreak: false,
|
|
56
|
+
};
|
|
57
|
+
return cachedEngineProfile;
|
|
58
|
+
}
|
|
59
|
+
const ua = navigator.userAgent;
|
|
60
|
+
const vendor = navigator.vendor;
|
|
61
|
+
const isSafari = vendor === 'Apple Computer, Inc.' &&
|
|
62
|
+
ua.includes('Safari/') &&
|
|
63
|
+
!ua.includes('Chrome/') &&
|
|
64
|
+
!ua.includes('Chromium/') &&
|
|
65
|
+
!ua.includes('CriOS/') &&
|
|
66
|
+
!ua.includes('FxiOS/') &&
|
|
67
|
+
!ua.includes('EdgiOS/');
|
|
68
|
+
const isChromium = ua.includes('Chrome/') ||
|
|
69
|
+
ua.includes('Chromium/') ||
|
|
70
|
+
ua.includes('CriOS/') ||
|
|
71
|
+
ua.includes('Edg/');
|
|
72
|
+
cachedEngineProfile = {
|
|
73
|
+
lineFitEpsilon: isSafari ? 1 / 64 : 0.005,
|
|
74
|
+
carryCJKAfterClosingQuote: isChromium,
|
|
75
|
+
preferPrefixWidthsForBreakableRuns: isSafari,
|
|
76
|
+
preferEarlySoftHyphenBreak: isSafari,
|
|
77
|
+
};
|
|
78
|
+
return cachedEngineProfile;
|
|
79
|
+
}
|
|
80
|
+
export function parseFontSize(font) {
|
|
81
|
+
const m = font.match(/(\d+(?:\.\d+)?)\s*px/);
|
|
82
|
+
return m ? parseFloat(m[1]) : 16;
|
|
83
|
+
}
|
|
84
|
+
function getSharedGraphemeSegmenter() {
|
|
85
|
+
if (sharedGraphemeSegmenter === null) {
|
|
86
|
+
sharedGraphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
87
|
+
}
|
|
88
|
+
return sharedGraphemeSegmenter;
|
|
89
|
+
}
|
|
90
|
+
function isEmojiGrapheme(g) {
|
|
91
|
+
return emojiPresentationRe.test(g) || g.includes('\uFE0F');
|
|
92
|
+
}
|
|
93
|
+
export function textMayContainEmoji(text) {
|
|
94
|
+
return maybeEmojiRe.test(text);
|
|
95
|
+
}
|
|
96
|
+
function getEmojiCorrection(font, fontSize) {
|
|
97
|
+
let correction = emojiCorrectionCache.get(font);
|
|
98
|
+
if (correction !== undefined)
|
|
99
|
+
return correction;
|
|
100
|
+
const ctx = getMeasureContext();
|
|
101
|
+
ctx.font = font;
|
|
102
|
+
const canvasW = ctx.measureText('\u{1F600}').width;
|
|
103
|
+
correction = 0;
|
|
104
|
+
if (canvasW > fontSize + 0.5 &&
|
|
105
|
+
typeof document !== 'undefined' &&
|
|
106
|
+
document.body !== null) {
|
|
107
|
+
const span = document.createElement('span');
|
|
108
|
+
span.style.font = font;
|
|
109
|
+
span.style.display = 'inline-block';
|
|
110
|
+
span.style.visibility = 'hidden';
|
|
111
|
+
span.style.position = 'absolute';
|
|
112
|
+
span.textContent = '\u{1F600}';
|
|
113
|
+
document.body.appendChild(span);
|
|
114
|
+
const domW = span.getBoundingClientRect().width;
|
|
115
|
+
document.body.removeChild(span);
|
|
116
|
+
if (canvasW - domW > 0.5) {
|
|
117
|
+
correction = canvasW - domW;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
emojiCorrectionCache.set(font, correction);
|
|
121
|
+
return correction;
|
|
122
|
+
}
|
|
123
|
+
function countEmojiGraphemes(text) {
|
|
124
|
+
let count = 0;
|
|
125
|
+
const graphemeSegmenter = getSharedGraphemeSegmenter();
|
|
126
|
+
for (const g of graphemeSegmenter.segment(text)) {
|
|
127
|
+
if (isEmojiGrapheme(g.segment))
|
|
128
|
+
count++;
|
|
129
|
+
}
|
|
130
|
+
return count;
|
|
131
|
+
}
|
|
132
|
+
function getEmojiCount(seg, metrics) {
|
|
133
|
+
if (metrics.emojiCount === undefined) {
|
|
134
|
+
metrics.emojiCount = countEmojiGraphemes(seg);
|
|
135
|
+
}
|
|
136
|
+
return metrics.emojiCount;
|
|
137
|
+
}
|
|
138
|
+
export function getCorrectedSegmentWidth(seg, metrics, emojiCorrection) {
|
|
139
|
+
if (emojiCorrection === 0)
|
|
140
|
+
return metrics.width;
|
|
141
|
+
return metrics.width - getEmojiCount(seg, metrics) * emojiCorrection;
|
|
142
|
+
}
|
|
143
|
+
export function getSegmentGraphemeWidths(seg, cache, emojiCorrection) {
|
|
144
|
+
const widths = [];
|
|
145
|
+
const graphemeSegmenter = getSharedGraphemeSegmenter();
|
|
146
|
+
for (const gs of graphemeSegmenter.segment(seg)) {
|
|
147
|
+
const graphemeMetrics = getSegmentMetrics(gs.segment, cache);
|
|
148
|
+
widths.push(getCorrectedSegmentWidth(gs.segment, graphemeMetrics, emojiCorrection));
|
|
149
|
+
}
|
|
150
|
+
return widths.length > 1 ? widths : null;
|
|
151
|
+
}
|
|
152
|
+
export function getSegmentBreakableFitAdvances(seg, metrics, cache, emojiCorrection, mode) {
|
|
153
|
+
if (metrics.breakableFitAdvances !== undefined)
|
|
154
|
+
return metrics.breakableFitAdvances;
|
|
155
|
+
const graphemeSegmenter = getSharedGraphemeSegmenter();
|
|
156
|
+
const graphemes = [];
|
|
157
|
+
for (const gs of graphemeSegmenter.segment(seg)) {
|
|
158
|
+
graphemes.push(gs.segment);
|
|
159
|
+
}
|
|
160
|
+
if (graphemes.length <= 1) {
|
|
161
|
+
metrics.breakableFitAdvances = null;
|
|
162
|
+
return metrics.breakableFitAdvances;
|
|
163
|
+
}
|
|
164
|
+
if (mode === 'sum-graphemes') {
|
|
165
|
+
const advances = [];
|
|
166
|
+
for (const grapheme of graphemes) {
|
|
167
|
+
const graphemeMetrics = getSegmentMetrics(grapheme, cache);
|
|
168
|
+
advances.push(getCorrectedSegmentWidth(grapheme, graphemeMetrics, emojiCorrection));
|
|
169
|
+
}
|
|
170
|
+
metrics.breakableFitAdvances = advances;
|
|
171
|
+
return metrics.breakableFitAdvances;
|
|
172
|
+
}
|
|
173
|
+
if (mode === 'pair-context' || graphemes.length > MAX_PREFIX_FIT_GRAPHEMES) {
|
|
174
|
+
const advances = [];
|
|
175
|
+
let previousGrapheme = null;
|
|
176
|
+
let previousWidth = 0;
|
|
177
|
+
for (const grapheme of graphemes) {
|
|
178
|
+
const graphemeMetrics = getSegmentMetrics(grapheme, cache);
|
|
179
|
+
const currentWidth = getCorrectedSegmentWidth(grapheme, graphemeMetrics, emojiCorrection);
|
|
180
|
+
if (previousGrapheme === null) {
|
|
181
|
+
advances.push(currentWidth);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
const pair = previousGrapheme + grapheme;
|
|
185
|
+
const pairMetrics = getSegmentMetrics(pair, cache);
|
|
186
|
+
advances.push(getCorrectedSegmentWidth(pair, pairMetrics, emojiCorrection) - previousWidth);
|
|
187
|
+
}
|
|
188
|
+
previousGrapheme = grapheme;
|
|
189
|
+
previousWidth = currentWidth;
|
|
190
|
+
}
|
|
191
|
+
metrics.breakableFitAdvances = advances;
|
|
192
|
+
return metrics.breakableFitAdvances;
|
|
193
|
+
}
|
|
194
|
+
const advances = [];
|
|
195
|
+
let prefix = '';
|
|
196
|
+
let prefixWidth = 0;
|
|
197
|
+
for (const grapheme of graphemes) {
|
|
198
|
+
prefix += grapheme;
|
|
199
|
+
const prefixMetrics = getSegmentMetrics(prefix, cache);
|
|
200
|
+
const nextPrefixWidth = getCorrectedSegmentWidth(prefix, prefixMetrics, emojiCorrection);
|
|
201
|
+
advances.push(nextPrefixWidth - prefixWidth);
|
|
202
|
+
prefixWidth = nextPrefixWidth;
|
|
203
|
+
}
|
|
204
|
+
metrics.breakableFitAdvances = advances;
|
|
205
|
+
return metrics.breakableFitAdvances;
|
|
206
|
+
}
|
|
207
|
+
export function getFontMeasurementState(font, needsEmojiCorrection) {
|
|
208
|
+
const ctx = getMeasureContext();
|
|
209
|
+
ctx.font = font;
|
|
210
|
+
const cache = getSegmentMetricCache(font);
|
|
211
|
+
const fontSize = parseFontSize(font);
|
|
212
|
+
const emojiCorrection = needsEmojiCorrection ? getEmojiCorrection(font, fontSize) : 0;
|
|
213
|
+
return { cache, fontSize, emojiCorrection };
|
|
214
|
+
}
|
|
215
|
+
export function clearMeasurementCaches() {
|
|
216
|
+
segmentMetricCaches.clear();
|
|
217
|
+
emojiCorrectionCache.clear();
|
|
218
|
+
sharedGraphemeSegmenter = null;
|
|
219
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type LayoutCursor } from './layout.js';
|
|
2
|
+
declare const preparedRichInlineBrand: unique symbol;
|
|
3
|
+
export type RichInlineItem = {
|
|
4
|
+
text: string;
|
|
5
|
+
font: string;
|
|
6
|
+
break?: 'normal' | 'never';
|
|
7
|
+
extraWidth?: number;
|
|
8
|
+
};
|
|
9
|
+
export type PreparedRichInline = {
|
|
10
|
+
readonly [preparedRichInlineBrand]: true;
|
|
11
|
+
};
|
|
12
|
+
export type RichInlineCursor = {
|
|
13
|
+
itemIndex: number;
|
|
14
|
+
segmentIndex: number;
|
|
15
|
+
graphemeIndex: number;
|
|
16
|
+
};
|
|
17
|
+
export type RichInlineFragment = {
|
|
18
|
+
itemIndex: number;
|
|
19
|
+
text: string;
|
|
20
|
+
gapBefore: number;
|
|
21
|
+
occupiedWidth: number;
|
|
22
|
+
start: LayoutCursor;
|
|
23
|
+
end: LayoutCursor;
|
|
24
|
+
};
|
|
25
|
+
export type RichInlineFragmentRange = {
|
|
26
|
+
itemIndex: number;
|
|
27
|
+
gapBefore: number;
|
|
28
|
+
occupiedWidth: number;
|
|
29
|
+
start: LayoutCursor;
|
|
30
|
+
end: LayoutCursor;
|
|
31
|
+
};
|
|
32
|
+
export type RichInlineLine = {
|
|
33
|
+
fragments: RichInlineFragment[];
|
|
34
|
+
width: number;
|
|
35
|
+
end: RichInlineCursor;
|
|
36
|
+
};
|
|
37
|
+
export type RichInlineLineRange = {
|
|
38
|
+
fragments: RichInlineFragmentRange[];
|
|
39
|
+
width: number;
|
|
40
|
+
end: RichInlineCursor;
|
|
41
|
+
};
|
|
42
|
+
export type RichInlineStats = {
|
|
43
|
+
lineCount: number;
|
|
44
|
+
maxLineWidth: number;
|
|
45
|
+
};
|
|
46
|
+
export declare function prepareRichInline(items: RichInlineItem[]): PreparedRichInline;
|
|
47
|
+
export declare function layoutNextRichInlineLineRange(prepared: PreparedRichInline, maxWidth: number, start?: RichInlineCursor): RichInlineLineRange | null;
|
|
48
|
+
export declare function materializeRichInlineLineRange(prepared: PreparedRichInline, line: RichInlineLineRange): RichInlineLine;
|
|
49
|
+
export declare function walkRichInlineLineRanges(prepared: PreparedRichInline, maxWidth: number, onLine: (line: RichInlineLineRange) => void): number;
|
|
50
|
+
export declare function measureRichInlineStats(prepared: PreparedRichInline, maxWidth: number): RichInlineStats;
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { materializeLineRange, measureNaturalWidth, prepareWithSegments, } from './layout.js';
|
|
2
|
+
import { layoutNextLineRange as stepPreparedLineRange, stepPreparedLineGeometry, } from './line-break.js';
|
|
3
|
+
const COLLAPSIBLE_BOUNDARY_RE = /[ \t\n\f\r]+/;
|
|
4
|
+
const LEADING_COLLAPSIBLE_BOUNDARY_RE = /^[ \t\n\f\r]+/;
|
|
5
|
+
const TRAILING_COLLAPSIBLE_BOUNDARY_RE = /[ \t\n\f\r]+$/;
|
|
6
|
+
const EMPTY_LAYOUT_CURSOR = { segmentIndex: 0, graphemeIndex: 0 };
|
|
7
|
+
const RICH_INLINE_START_CURSOR = {
|
|
8
|
+
itemIndex: 0,
|
|
9
|
+
segmentIndex: 0,
|
|
10
|
+
graphemeIndex: 0,
|
|
11
|
+
};
|
|
12
|
+
function getInternalPreparedRichInline(prepared) {
|
|
13
|
+
return prepared;
|
|
14
|
+
}
|
|
15
|
+
function cloneCursor(cursor) {
|
|
16
|
+
return {
|
|
17
|
+
segmentIndex: cursor.segmentIndex,
|
|
18
|
+
graphemeIndex: cursor.graphemeIndex,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function isLineStartCursor(cursor) {
|
|
22
|
+
return cursor.segmentIndex === 0 && cursor.graphemeIndex === 0;
|
|
23
|
+
}
|
|
24
|
+
function getCollapsedSpaceWidth(font, cache) {
|
|
25
|
+
const cached = cache.get(font);
|
|
26
|
+
if (cached !== undefined)
|
|
27
|
+
return cached;
|
|
28
|
+
const joinedWidth = measureNaturalWidth(prepareWithSegments('A A', font));
|
|
29
|
+
const compactWidth = measureNaturalWidth(prepareWithSegments('AA', font));
|
|
30
|
+
const collapsedWidth = Math.max(0, joinedWidth - compactWidth);
|
|
31
|
+
cache.set(font, collapsedWidth);
|
|
32
|
+
return collapsedWidth;
|
|
33
|
+
}
|
|
34
|
+
function prepareWholeItemLine(prepared) {
|
|
35
|
+
const line = stepPreparedLineRange(prepared, EMPTY_LAYOUT_CURSOR, Number.POSITIVE_INFINITY);
|
|
36
|
+
if (line === null)
|
|
37
|
+
return null;
|
|
38
|
+
return {
|
|
39
|
+
endGraphemeIndex: line.endGraphemeIndex,
|
|
40
|
+
endSegmentIndex: line.endSegmentIndex,
|
|
41
|
+
width: line.width,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function endsInsideFirstSegment(segmentIndex, graphemeIndex) {
|
|
45
|
+
return segmentIndex === 0 && graphemeIndex > 0;
|
|
46
|
+
}
|
|
47
|
+
export function prepareRichInline(items) {
|
|
48
|
+
const preparedItems = [];
|
|
49
|
+
const itemsBySourceItemIndex = Array.from({ length: items.length });
|
|
50
|
+
const collapsedSpaceWidthCache = new Map();
|
|
51
|
+
let pendingGapWidth = 0;
|
|
52
|
+
for (let index = 0; index < items.length; index++) {
|
|
53
|
+
const item = items[index];
|
|
54
|
+
const hasLeadingWhitespace = LEADING_COLLAPSIBLE_BOUNDARY_RE.test(item.text);
|
|
55
|
+
const hasTrailingWhitespace = TRAILING_COLLAPSIBLE_BOUNDARY_RE.test(item.text);
|
|
56
|
+
const trimmedText = item.text
|
|
57
|
+
.replace(LEADING_COLLAPSIBLE_BOUNDARY_RE, '')
|
|
58
|
+
.replace(TRAILING_COLLAPSIBLE_BOUNDARY_RE, '');
|
|
59
|
+
if (trimmedText.length === 0) {
|
|
60
|
+
if (COLLAPSIBLE_BOUNDARY_RE.test(item.text) && pendingGapWidth === 0) {
|
|
61
|
+
pendingGapWidth = getCollapsedSpaceWidth(item.font, collapsedSpaceWidthCache);
|
|
62
|
+
}
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const gapBefore = pendingGapWidth > 0
|
|
66
|
+
? pendingGapWidth
|
|
67
|
+
: hasLeadingWhitespace
|
|
68
|
+
? getCollapsedSpaceWidth(item.font, collapsedSpaceWidthCache)
|
|
69
|
+
: 0;
|
|
70
|
+
const prepared = prepareWithSegments(trimmedText, item.font);
|
|
71
|
+
const wholeLine = prepareWholeItemLine(prepared);
|
|
72
|
+
if (wholeLine === null) {
|
|
73
|
+
pendingGapWidth = hasTrailingWhitespace ? getCollapsedSpaceWidth(item.font, collapsedSpaceWidthCache) : 0;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const preparedItem = {
|
|
77
|
+
break: item.break ?? 'normal',
|
|
78
|
+
endGraphemeIndex: wholeLine.endGraphemeIndex,
|
|
79
|
+
endSegmentIndex: wholeLine.endSegmentIndex,
|
|
80
|
+
extraWidth: item.extraWidth ?? 0,
|
|
81
|
+
gapBefore,
|
|
82
|
+
naturalWidth: wholeLine.width,
|
|
83
|
+
prepared,
|
|
84
|
+
sourceItemIndex: index,
|
|
85
|
+
};
|
|
86
|
+
preparedItems.push(preparedItem);
|
|
87
|
+
itemsBySourceItemIndex[index] = preparedItem;
|
|
88
|
+
pendingGapWidth = hasTrailingWhitespace ? getCollapsedSpaceWidth(item.font, collapsedSpaceWidthCache) : 0;
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
items: preparedItems,
|
|
92
|
+
itemsBySourceItemIndex,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function stepRichInlineLine(flow, maxWidth, cursor, collectFragment) {
|
|
96
|
+
if (flow.items.length === 0 || cursor.itemIndex >= flow.items.length)
|
|
97
|
+
return null;
|
|
98
|
+
const safeWidth = Math.max(1, maxWidth);
|
|
99
|
+
let lineWidth = 0;
|
|
100
|
+
let remainingWidth = safeWidth;
|
|
101
|
+
let itemIndex = cursor.itemIndex;
|
|
102
|
+
const textCursor = {
|
|
103
|
+
segmentIndex: cursor.segmentIndex,
|
|
104
|
+
graphemeIndex: cursor.graphemeIndex,
|
|
105
|
+
};
|
|
106
|
+
lineLoop: while (itemIndex < flow.items.length) {
|
|
107
|
+
const item = flow.items[itemIndex];
|
|
108
|
+
if (!isLineStartCursor(textCursor) &&
|
|
109
|
+
textCursor.segmentIndex === item.endSegmentIndex &&
|
|
110
|
+
textCursor.graphemeIndex === item.endGraphemeIndex) {
|
|
111
|
+
itemIndex++;
|
|
112
|
+
textCursor.segmentIndex = 0;
|
|
113
|
+
textCursor.graphemeIndex = 0;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const gapBefore = lineWidth === 0 ? 0 : item.gapBefore;
|
|
117
|
+
const atItemStart = isLineStartCursor(textCursor);
|
|
118
|
+
if (item.break === 'never') {
|
|
119
|
+
if (!atItemStart) {
|
|
120
|
+
itemIndex++;
|
|
121
|
+
textCursor.segmentIndex = 0;
|
|
122
|
+
textCursor.graphemeIndex = 0;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const occupiedWidth = item.naturalWidth + item.extraWidth;
|
|
126
|
+
const totalWidth = gapBefore + occupiedWidth;
|
|
127
|
+
if (lineWidth > 0 && totalWidth > remainingWidth)
|
|
128
|
+
break lineLoop;
|
|
129
|
+
collectFragment?.(item, gapBefore, occupiedWidth, cloneCursor(EMPTY_LAYOUT_CURSOR), {
|
|
130
|
+
segmentIndex: item.endSegmentIndex,
|
|
131
|
+
graphemeIndex: item.endGraphemeIndex,
|
|
132
|
+
});
|
|
133
|
+
lineWidth += totalWidth;
|
|
134
|
+
remainingWidth = Math.max(0, safeWidth - lineWidth);
|
|
135
|
+
itemIndex++;
|
|
136
|
+
textCursor.segmentIndex = 0;
|
|
137
|
+
textCursor.graphemeIndex = 0;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const reservedWidth = gapBefore + item.extraWidth;
|
|
141
|
+
if (lineWidth > 0 && reservedWidth >= remainingWidth)
|
|
142
|
+
break lineLoop;
|
|
143
|
+
if (atItemStart) {
|
|
144
|
+
const totalWidth = reservedWidth + item.naturalWidth;
|
|
145
|
+
if (totalWidth <= remainingWidth) {
|
|
146
|
+
collectFragment?.(item, gapBefore, item.naturalWidth + item.extraWidth, cloneCursor(EMPTY_LAYOUT_CURSOR), {
|
|
147
|
+
segmentIndex: item.endSegmentIndex,
|
|
148
|
+
graphemeIndex: item.endGraphemeIndex,
|
|
149
|
+
});
|
|
150
|
+
lineWidth += totalWidth;
|
|
151
|
+
remainingWidth = Math.max(0, safeWidth - lineWidth);
|
|
152
|
+
itemIndex++;
|
|
153
|
+
textCursor.segmentIndex = 0;
|
|
154
|
+
textCursor.graphemeIndex = 0;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const availableWidth = Math.max(1, remainingWidth - reservedWidth);
|
|
159
|
+
const line = stepPreparedLineRange(item.prepared, textCursor, availableWidth);
|
|
160
|
+
if (line === null) {
|
|
161
|
+
itemIndex++;
|
|
162
|
+
textCursor.segmentIndex = 0;
|
|
163
|
+
textCursor.graphemeIndex = 0;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (textCursor.segmentIndex === line.endSegmentIndex &&
|
|
167
|
+
textCursor.graphemeIndex === line.endGraphemeIndex) {
|
|
168
|
+
itemIndex++;
|
|
169
|
+
textCursor.segmentIndex = 0;
|
|
170
|
+
textCursor.graphemeIndex = 0;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
// If the only thing we can fit after paying the boundary gap is a partial
|
|
174
|
+
// slice of the item's first segment, prefer wrapping before the item so we
|
|
175
|
+
// keep whole-word-style boundaries when they exist. But once the current
|
|
176
|
+
// line can consume a real breakable unit from the item, stay greedy and
|
|
177
|
+
// keep filling the line.
|
|
178
|
+
if (lineWidth > 0 &&
|
|
179
|
+
atItemStart &&
|
|
180
|
+
gapBefore > 0 &&
|
|
181
|
+
endsInsideFirstSegment(line.endSegmentIndex, line.endGraphemeIndex)) {
|
|
182
|
+
const freshLine = stepPreparedLineRange(item.prepared, EMPTY_LAYOUT_CURSOR, Math.max(1, safeWidth - item.extraWidth));
|
|
183
|
+
if (freshLine !== null &&
|
|
184
|
+
(freshLine.endSegmentIndex > line.endSegmentIndex ||
|
|
185
|
+
(freshLine.endSegmentIndex === line.endSegmentIndex &&
|
|
186
|
+
freshLine.endGraphemeIndex > line.endGraphemeIndex))) {
|
|
187
|
+
break lineLoop;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
collectFragment?.(item, gapBefore, line.width + item.extraWidth, cloneCursor(textCursor), {
|
|
191
|
+
segmentIndex: line.endSegmentIndex,
|
|
192
|
+
graphemeIndex: line.endGraphemeIndex,
|
|
193
|
+
});
|
|
194
|
+
lineWidth += gapBefore + line.width + item.extraWidth;
|
|
195
|
+
remainingWidth = Math.max(0, safeWidth - lineWidth);
|
|
196
|
+
if (line.endSegmentIndex === item.endSegmentIndex &&
|
|
197
|
+
line.endGraphemeIndex === item.endGraphemeIndex) {
|
|
198
|
+
itemIndex++;
|
|
199
|
+
textCursor.segmentIndex = 0;
|
|
200
|
+
textCursor.graphemeIndex = 0;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
textCursor.segmentIndex = line.endSegmentIndex;
|
|
204
|
+
textCursor.graphemeIndex = line.endGraphemeIndex;
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
if (lineWidth === 0)
|
|
208
|
+
return null;
|
|
209
|
+
cursor.itemIndex = itemIndex;
|
|
210
|
+
cursor.segmentIndex = textCursor.segmentIndex;
|
|
211
|
+
cursor.graphemeIndex = textCursor.graphemeIndex;
|
|
212
|
+
return lineWidth;
|
|
213
|
+
}
|
|
214
|
+
function stepRichInlineLineStats(flow, maxWidth, cursor) {
|
|
215
|
+
if (flow.items.length === 0 || cursor.itemIndex >= flow.items.length)
|
|
216
|
+
return null;
|
|
217
|
+
const safeWidth = Math.max(1, maxWidth);
|
|
218
|
+
let lineWidth = 0;
|
|
219
|
+
let remainingWidth = safeWidth;
|
|
220
|
+
let itemIndex = cursor.itemIndex;
|
|
221
|
+
lineLoop: while (itemIndex < flow.items.length) {
|
|
222
|
+
const item = flow.items[itemIndex];
|
|
223
|
+
if (!isLineStartCursor(cursor) &&
|
|
224
|
+
cursor.segmentIndex === item.endSegmentIndex &&
|
|
225
|
+
cursor.graphemeIndex === item.endGraphemeIndex) {
|
|
226
|
+
itemIndex++;
|
|
227
|
+
cursor.segmentIndex = 0;
|
|
228
|
+
cursor.graphemeIndex = 0;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const gapBefore = lineWidth === 0 ? 0 : item.gapBefore;
|
|
232
|
+
const atItemStart = isLineStartCursor(cursor);
|
|
233
|
+
if (item.break === 'never') {
|
|
234
|
+
if (!atItemStart) {
|
|
235
|
+
itemIndex++;
|
|
236
|
+
cursor.segmentIndex = 0;
|
|
237
|
+
cursor.graphemeIndex = 0;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const occupiedWidth = item.naturalWidth + item.extraWidth;
|
|
241
|
+
const totalWidth = gapBefore + occupiedWidth;
|
|
242
|
+
if (lineWidth > 0 && totalWidth > remainingWidth)
|
|
243
|
+
break lineLoop;
|
|
244
|
+
lineWidth += totalWidth;
|
|
245
|
+
remainingWidth = Math.max(0, safeWidth - lineWidth);
|
|
246
|
+
itemIndex++;
|
|
247
|
+
cursor.segmentIndex = 0;
|
|
248
|
+
cursor.graphemeIndex = 0;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const reservedWidth = gapBefore + item.extraWidth;
|
|
252
|
+
if (lineWidth > 0 && reservedWidth >= remainingWidth)
|
|
253
|
+
break lineLoop;
|
|
254
|
+
if (atItemStart) {
|
|
255
|
+
const totalWidth = reservedWidth + item.naturalWidth;
|
|
256
|
+
if (totalWidth <= remainingWidth) {
|
|
257
|
+
lineWidth += totalWidth;
|
|
258
|
+
remainingWidth = Math.max(0, safeWidth - lineWidth);
|
|
259
|
+
itemIndex++;
|
|
260
|
+
cursor.segmentIndex = 0;
|
|
261
|
+
cursor.graphemeIndex = 0;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const availableWidth = Math.max(1, remainingWidth - reservedWidth);
|
|
266
|
+
const lineEnd = {
|
|
267
|
+
segmentIndex: cursor.segmentIndex,
|
|
268
|
+
graphemeIndex: cursor.graphemeIndex,
|
|
269
|
+
};
|
|
270
|
+
const lineWidthForItem = stepPreparedLineGeometry(item.prepared, lineEnd, availableWidth);
|
|
271
|
+
if (lineWidthForItem === null) {
|
|
272
|
+
itemIndex++;
|
|
273
|
+
cursor.segmentIndex = 0;
|
|
274
|
+
cursor.graphemeIndex = 0;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (cursor.segmentIndex === lineEnd.segmentIndex && cursor.graphemeIndex === lineEnd.graphemeIndex) {
|
|
278
|
+
itemIndex++;
|
|
279
|
+
cursor.segmentIndex = 0;
|
|
280
|
+
cursor.graphemeIndex = 0;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (lineWidth > 0 &&
|
|
284
|
+
atItemStart &&
|
|
285
|
+
gapBefore > 0 &&
|
|
286
|
+
endsInsideFirstSegment(lineEnd.segmentIndex, lineEnd.graphemeIndex)) {
|
|
287
|
+
const freshLineEnd = {
|
|
288
|
+
segmentIndex: 0,
|
|
289
|
+
graphemeIndex: 0,
|
|
290
|
+
};
|
|
291
|
+
const freshLineWidth = stepPreparedLineGeometry(item.prepared, freshLineEnd, Math.max(1, safeWidth - item.extraWidth));
|
|
292
|
+
if (freshLineWidth !== null &&
|
|
293
|
+
(freshLineEnd.segmentIndex > lineEnd.segmentIndex ||
|
|
294
|
+
(freshLineEnd.segmentIndex === lineEnd.segmentIndex &&
|
|
295
|
+
freshLineEnd.graphemeIndex > lineEnd.graphemeIndex))) {
|
|
296
|
+
break lineLoop;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
lineWidth += gapBefore + lineWidthForItem + item.extraWidth;
|
|
300
|
+
remainingWidth = Math.max(0, safeWidth - lineWidth);
|
|
301
|
+
if (lineEnd.segmentIndex === item.endSegmentIndex && lineEnd.graphemeIndex === item.endGraphemeIndex) {
|
|
302
|
+
itemIndex++;
|
|
303
|
+
cursor.segmentIndex = 0;
|
|
304
|
+
cursor.graphemeIndex = 0;
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
cursor.segmentIndex = lineEnd.segmentIndex;
|
|
308
|
+
cursor.graphemeIndex = lineEnd.graphemeIndex;
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
if (lineWidth === 0)
|
|
312
|
+
return null;
|
|
313
|
+
cursor.itemIndex = itemIndex;
|
|
314
|
+
return lineWidth;
|
|
315
|
+
}
|
|
316
|
+
export function layoutNextRichInlineLineRange(prepared, maxWidth, start = RICH_INLINE_START_CURSOR) {
|
|
317
|
+
const flow = getInternalPreparedRichInline(prepared);
|
|
318
|
+
const end = {
|
|
319
|
+
itemIndex: start.itemIndex,
|
|
320
|
+
segmentIndex: start.segmentIndex,
|
|
321
|
+
graphemeIndex: start.graphemeIndex,
|
|
322
|
+
};
|
|
323
|
+
const fragments = [];
|
|
324
|
+
const width = stepRichInlineLine(flow, maxWidth, end, (item, gapBefore, occupiedWidth, fragmentStart, fragmentEnd) => {
|
|
325
|
+
fragments.push({
|
|
326
|
+
itemIndex: item.sourceItemIndex,
|
|
327
|
+
gapBefore,
|
|
328
|
+
occupiedWidth,
|
|
329
|
+
start: fragmentStart,
|
|
330
|
+
end: fragmentEnd,
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
if (width === null)
|
|
334
|
+
return null;
|
|
335
|
+
return {
|
|
336
|
+
fragments,
|
|
337
|
+
width,
|
|
338
|
+
end,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
function materializeFragmentText(item, fragment) {
|
|
342
|
+
const line = materializeLineRange(item.prepared, {
|
|
343
|
+
width: fragment.occupiedWidth - item.extraWidth,
|
|
344
|
+
start: fragment.start,
|
|
345
|
+
end: fragment.end,
|
|
346
|
+
});
|
|
347
|
+
return line.text;
|
|
348
|
+
}
|
|
349
|
+
// Bridge from cheap range walking to full fragment text. Lets callers do
|
|
350
|
+
// shrinkwrap/virtualization/probing work first, then only pay for text on the
|
|
351
|
+
// lines they actually render.
|
|
352
|
+
export function materializeRichInlineLineRange(prepared, line) {
|
|
353
|
+
const flow = getInternalPreparedRichInline(prepared);
|
|
354
|
+
return {
|
|
355
|
+
fragments: line.fragments.map(fragment => {
|
|
356
|
+
const item = flow.itemsBySourceItemIndex[fragment.itemIndex];
|
|
357
|
+
if (item === undefined)
|
|
358
|
+
throw new Error('Missing rich-text inline item for fragment');
|
|
359
|
+
return {
|
|
360
|
+
...fragment,
|
|
361
|
+
text: materializeFragmentText(item, fragment),
|
|
362
|
+
};
|
|
363
|
+
}),
|
|
364
|
+
width: line.width,
|
|
365
|
+
end: line.end,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
export function walkRichInlineLineRanges(prepared, maxWidth, onLine) {
|
|
369
|
+
let lineCount = 0;
|
|
370
|
+
let cursor = RICH_INLINE_START_CURSOR;
|
|
371
|
+
while (true) {
|
|
372
|
+
const line = layoutNextRichInlineLineRange(prepared, maxWidth, cursor);
|
|
373
|
+
if (line === null)
|
|
374
|
+
return lineCount;
|
|
375
|
+
onLine(line);
|
|
376
|
+
lineCount++;
|
|
377
|
+
cursor = line.end;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
export function measureRichInlineStats(prepared, maxWidth) {
|
|
381
|
+
const flow = getInternalPreparedRichInline(prepared);
|
|
382
|
+
let lineCount = 0;
|
|
383
|
+
let maxLineWidth = 0;
|
|
384
|
+
const cursor = {
|
|
385
|
+
itemIndex: 0,
|
|
386
|
+
segmentIndex: 0,
|
|
387
|
+
graphemeIndex: 0,
|
|
388
|
+
};
|
|
389
|
+
while (true) {
|
|
390
|
+
const lineWidth = stepRichInlineLineStats(flow, maxWidth, cursor);
|
|
391
|
+
if (lineWidth === null) {
|
|
392
|
+
return {
|
|
393
|
+
lineCount,
|
|
394
|
+
maxLineWidth,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
lineCount++;
|
|
398
|
+
if (lineWidth > maxLineWidth)
|
|
399
|
+
maxLineWidth = lineWidth;
|
|
400
|
+
}
|
|
401
|
+
}
|