@mentra/sdk 2.1.28 → 2.1.29-beta.2
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/dist/app/session/events.d.ts +8 -3
- package/dist/app/session/events.d.ts.map +1 -1
- package/dist/app/session/index.d.ts +2 -1
- package/dist/app/session/index.d.ts.map +1 -1
- package/dist/app/session/modules/camera.d.ts +6 -5
- package/dist/app/session/modules/camera.d.ts.map +1 -1
- package/dist/app/session/modules/simple-storage.d.ts +22 -1
- package/dist/app/session/modules/simple-storage.d.ts.map +1 -1
- package/dist/display-utils/helpers/DisplayHelpers.d.ts +165 -0
- package/dist/display-utils/helpers/DisplayHelpers.d.ts.map +1 -0
- package/dist/display-utils/helpers/ScrollView.d.ts +183 -0
- package/dist/display-utils/helpers/ScrollView.d.ts.map +1 -0
- package/dist/display-utils/helpers/index.d.ts +11 -0
- package/dist/display-utils/helpers/index.d.ts.map +1 -0
- package/dist/display-utils/index.d.ts +108 -0
- package/dist/display-utils/index.d.ts.map +1 -0
- package/dist/display-utils/measurer/TextMeasurer.d.ts +160 -0
- package/dist/display-utils/measurer/TextMeasurer.d.ts.map +1 -0
- package/dist/display-utils/measurer/index.d.ts +10 -0
- package/dist/display-utils/measurer/index.d.ts.map +1 -0
- package/dist/display-utils/measurer/script-detection.d.ts +53 -0
- package/dist/display-utils/measurer/script-detection.d.ts.map +1 -0
- package/dist/display-utils/profiles/g1.d.ts +33 -0
- package/dist/display-utils/profiles/g1.d.ts.map +1 -0
- package/dist/display-utils/profiles/index.d.ts +9 -0
- package/dist/display-utils/profiles/index.d.ts.map +1 -0
- package/dist/display-utils/profiles/types.d.ts +95 -0
- package/dist/display-utils/profiles/types.d.ts.map +1 -0
- package/dist/display-utils/test/ScrollView.test.d.ts +2 -0
- package/dist/display-utils/test/ScrollView.test.d.ts.map +1 -0
- package/dist/display-utils/test/TextMeasurer.test.d.ts +2 -0
- package/dist/display-utils/test/TextMeasurer.test.d.ts.map +1 -0
- package/dist/display-utils/test/TextWrapper.test.d.ts +2 -0
- package/dist/display-utils/test/TextWrapper.test.d.ts.map +1 -0
- package/dist/display-utils/wrapper/TextWrapper.d.ts +94 -0
- package/dist/display-utils/wrapper/TextWrapper.d.ts.map +1 -0
- package/dist/display-utils/wrapper/index.d.ts +12 -0
- package/dist/display-utils/wrapper/index.d.ts.map +1 -0
- package/dist/display-utils/wrapper/types.d.ts +71 -0
- package/dist/display-utils/wrapper/types.d.ts.map +1 -0
- package/dist/display-utils.d.ts +30 -0
- package/dist/display-utils.d.ts.map +1 -0
- package/dist/display-utils.js +1197 -0
- package/dist/display-utils.js.map +17 -0
- package/dist/index.js +102 -22
- package/dist/index.js.map +10 -10
- package/dist/types/messages/app-to-cloud.d.ts +1 -1
- package/dist/types/messages/app-to-cloud.d.ts.map +1 -1
- package/dist/types/messages/cloud-to-app.d.ts +2 -0
- package/dist/types/messages/cloud-to-app.d.ts.map +1 -1
- package/dist/types/messages/cloud-to-glasses.d.ts +1 -1
- package/dist/types/messages/cloud-to-glasses.d.ts.map +1 -1
- package/dist/types/streams.d.ts +3 -1
- package/dist/types/streams.d.ts.map +1 -1
- package/package.json +24 -10
|
@@ -0,0 +1,1197 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
7
|
+
var __toCommonJS = (from) => {
|
|
8
|
+
var entry = __moduleCache.get(from), desc;
|
|
9
|
+
if (entry)
|
|
10
|
+
return entry;
|
|
11
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function")
|
|
13
|
+
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
14
|
+
get: () => from[key],
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
}));
|
|
17
|
+
__moduleCache.set(from, entry);
|
|
18
|
+
return entry;
|
|
19
|
+
};
|
|
20
|
+
var __export = (target, all) => {
|
|
21
|
+
for (var name in all)
|
|
22
|
+
__defProp(target, name, {
|
|
23
|
+
get: all[name],
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
set: (newValue) => all[name] = () => newValue
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
30
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
31
|
+
|
|
32
|
+
// src/display-utils/profiles/g1.ts
|
|
33
|
+
var G1_GLYPH_WIDTHS = {
|
|
34
|
+
" ": 2,
|
|
35
|
+
"!": 1,
|
|
36
|
+
'"': 2,
|
|
37
|
+
"#": 6,
|
|
38
|
+
$: 5,
|
|
39
|
+
"%": 6,
|
|
40
|
+
"&": 7,
|
|
41
|
+
"'": 1,
|
|
42
|
+
"(": 2,
|
|
43
|
+
")": 2,
|
|
44
|
+
"*": 3,
|
|
45
|
+
"+": 4,
|
|
46
|
+
",": 1,
|
|
47
|
+
"-": 4,
|
|
48
|
+
".": 1,
|
|
49
|
+
"/": 3,
|
|
50
|
+
"0": 5,
|
|
51
|
+
"1": 3,
|
|
52
|
+
"2": 5,
|
|
53
|
+
"3": 5,
|
|
54
|
+
"4": 5,
|
|
55
|
+
"5": 5,
|
|
56
|
+
"6": 5,
|
|
57
|
+
"7": 5,
|
|
58
|
+
"8": 5,
|
|
59
|
+
"9": 5,
|
|
60
|
+
":": 1,
|
|
61
|
+
";": 1,
|
|
62
|
+
"<": 4,
|
|
63
|
+
"=": 4,
|
|
64
|
+
">": 4,
|
|
65
|
+
"?": 5,
|
|
66
|
+
"@": 7,
|
|
67
|
+
A: 6,
|
|
68
|
+
B: 5,
|
|
69
|
+
C: 5,
|
|
70
|
+
D: 5,
|
|
71
|
+
E: 4,
|
|
72
|
+
F: 4,
|
|
73
|
+
G: 5,
|
|
74
|
+
H: 5,
|
|
75
|
+
I: 2,
|
|
76
|
+
J: 3,
|
|
77
|
+
K: 5,
|
|
78
|
+
L: 4,
|
|
79
|
+
M: 7,
|
|
80
|
+
N: 5,
|
|
81
|
+
O: 5,
|
|
82
|
+
P: 5,
|
|
83
|
+
Q: 5,
|
|
84
|
+
R: 5,
|
|
85
|
+
S: 5,
|
|
86
|
+
T: 5,
|
|
87
|
+
U: 5,
|
|
88
|
+
V: 6,
|
|
89
|
+
W: 7,
|
|
90
|
+
X: 6,
|
|
91
|
+
Y: 6,
|
|
92
|
+
Z: 5,
|
|
93
|
+
"[": 2,
|
|
94
|
+
"\\": 3,
|
|
95
|
+
"]": 2,
|
|
96
|
+
"^": 4,
|
|
97
|
+
_: 3,
|
|
98
|
+
"`": 2,
|
|
99
|
+
a: 5,
|
|
100
|
+
b: 4,
|
|
101
|
+
c: 4,
|
|
102
|
+
d: 4,
|
|
103
|
+
e: 4,
|
|
104
|
+
f: 4,
|
|
105
|
+
g: 4,
|
|
106
|
+
h: 4,
|
|
107
|
+
i: 1,
|
|
108
|
+
j: 2,
|
|
109
|
+
k: 4,
|
|
110
|
+
l: 1,
|
|
111
|
+
m: 7,
|
|
112
|
+
n: 4,
|
|
113
|
+
o: 4,
|
|
114
|
+
p: 4,
|
|
115
|
+
q: 4,
|
|
116
|
+
r: 3,
|
|
117
|
+
s: 4,
|
|
118
|
+
t: 3,
|
|
119
|
+
u: 5,
|
|
120
|
+
v: 5,
|
|
121
|
+
w: 7,
|
|
122
|
+
x: 5,
|
|
123
|
+
y: 5,
|
|
124
|
+
z: 4,
|
|
125
|
+
"{": 3,
|
|
126
|
+
"|": 1,
|
|
127
|
+
"}": 3,
|
|
128
|
+
"~": 7
|
|
129
|
+
};
|
|
130
|
+
var G1_PROFILE = {
|
|
131
|
+
id: "even-realities-g1",
|
|
132
|
+
name: "Even Realities G1",
|
|
133
|
+
displayWidthPx: 576,
|
|
134
|
+
maxLines: 5,
|
|
135
|
+
maxPayloadBytes: 390,
|
|
136
|
+
bleChunkSize: 176,
|
|
137
|
+
fontMetrics: {
|
|
138
|
+
glyphWidths: new Map(Object.entries(G1_GLYPH_WIDTHS)),
|
|
139
|
+
defaultGlyphWidth: 7,
|
|
140
|
+
renderFormula: (glyphWidth) => (glyphWidth + 1) * 2,
|
|
141
|
+
uniformScripts: {
|
|
142
|
+
cjk: 18,
|
|
143
|
+
hiragana: 18,
|
|
144
|
+
katakana: 18,
|
|
145
|
+
korean: 24,
|
|
146
|
+
cyrillic: 18
|
|
147
|
+
},
|
|
148
|
+
fallback: {
|
|
149
|
+
latinMaxWidth: 16,
|
|
150
|
+
unknownBehavior: "useLatinMax"
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
constraints: {
|
|
154
|
+
minCharsBeforeHyphen: 3,
|
|
155
|
+
noStartChars: [".", ",", "!", "?", ":", ";", ")", "]", "}", "。", ",", "!", "?", ":", ";", ")", "】", "」"],
|
|
156
|
+
noEndChars: ["(", "[", "{", "(", "【", "「"]
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
var G1_PROFILE_LEGACY = {
|
|
160
|
+
id: "even-realities-g1-legacy",
|
|
161
|
+
name: "Even Realities G1 (Legacy Client Compatibility)",
|
|
162
|
+
displayWidthPx: 420,
|
|
163
|
+
maxLines: 5,
|
|
164
|
+
maxPayloadBytes: 390,
|
|
165
|
+
bleChunkSize: 176,
|
|
166
|
+
fontMetrics: {
|
|
167
|
+
glyphWidths: new Map(Object.entries(G1_GLYPH_WIDTHS)),
|
|
168
|
+
defaultGlyphWidth: 7,
|
|
169
|
+
renderFormula: (glyphWidth) => (glyphWidth + 1) * 2,
|
|
170
|
+
uniformScripts: {
|
|
171
|
+
cjk: 18,
|
|
172
|
+
hiragana: 18,
|
|
173
|
+
katakana: 18,
|
|
174
|
+
korean: 24,
|
|
175
|
+
cyrillic: 18
|
|
176
|
+
},
|
|
177
|
+
fallback: {
|
|
178
|
+
latinMaxWidth: 16,
|
|
179
|
+
unknownBehavior: "useLatinMax"
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
constraints: {
|
|
183
|
+
minCharsBeforeHyphen: 3,
|
|
184
|
+
noStartChars: [".", ",", "!", "?", ":", ";", ")", "]", "}", "。", ",", "!", "?", ":", ";", ")", "】", "」"],
|
|
185
|
+
noEndChars: ["(", "[", "{", "(", "【", "「"]
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
var G1_HYPHEN_WIDTH_PX = 10;
|
|
189
|
+
var G1_SPACE_WIDTH_PX = 6;
|
|
190
|
+
// src/display-utils/measurer/script-detection.ts
|
|
191
|
+
var SCRIPT_RANGES = {
|
|
192
|
+
cjk: [
|
|
193
|
+
[19968, 40959],
|
|
194
|
+
[13312, 19903],
|
|
195
|
+
[131072, 173791],
|
|
196
|
+
[173824, 177983],
|
|
197
|
+
[177984, 178207],
|
|
198
|
+
[63744, 64255]
|
|
199
|
+
],
|
|
200
|
+
hiragana: [[12352, 12447]],
|
|
201
|
+
katakana: [
|
|
202
|
+
[12448, 12543],
|
|
203
|
+
[12784, 12799]
|
|
204
|
+
],
|
|
205
|
+
korean: [
|
|
206
|
+
[44032, 55215],
|
|
207
|
+
[4352, 4607],
|
|
208
|
+
[12592, 12687],
|
|
209
|
+
[43360, 43391],
|
|
210
|
+
[55216, 55295]
|
|
211
|
+
],
|
|
212
|
+
cyrillic: [
|
|
213
|
+
[1024, 1279],
|
|
214
|
+
[1280, 1327]
|
|
215
|
+
],
|
|
216
|
+
numbers: [[48, 57]],
|
|
217
|
+
punctuation: [
|
|
218
|
+
[32, 47],
|
|
219
|
+
[58, 64],
|
|
220
|
+
[91, 96],
|
|
221
|
+
[123, 126]
|
|
222
|
+
],
|
|
223
|
+
arabic: [[1536, 1791]],
|
|
224
|
+
hebrew: [[1424, 1535]],
|
|
225
|
+
thai: [[3584, 3711]],
|
|
226
|
+
emoji: [
|
|
227
|
+
[128512, 128591],
|
|
228
|
+
[127744, 128511],
|
|
229
|
+
[128640, 128767],
|
|
230
|
+
[127456, 127487],
|
|
231
|
+
[9728, 9983],
|
|
232
|
+
[9984, 10175],
|
|
233
|
+
[65024, 65039],
|
|
234
|
+
[129280, 129535]
|
|
235
|
+
]
|
|
236
|
+
};
|
|
237
|
+
function inRanges(codePoint, ranges) {
|
|
238
|
+
for (const [start, end] of ranges) {
|
|
239
|
+
if (codePoint >= start && codePoint <= end) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
function detectScript(char) {
|
|
246
|
+
if (!char || char.length === 0) {
|
|
247
|
+
return "latin";
|
|
248
|
+
}
|
|
249
|
+
const codePoint = char.codePointAt(0);
|
|
250
|
+
if (codePoint === undefined) {
|
|
251
|
+
return "latin";
|
|
252
|
+
}
|
|
253
|
+
if (inRanges(codePoint, SCRIPT_RANGES.cjk)) {
|
|
254
|
+
return "cjk";
|
|
255
|
+
}
|
|
256
|
+
if (inRanges(codePoint, SCRIPT_RANGES.hiragana)) {
|
|
257
|
+
return "hiragana";
|
|
258
|
+
}
|
|
259
|
+
if (inRanges(codePoint, SCRIPT_RANGES.katakana)) {
|
|
260
|
+
return "katakana";
|
|
261
|
+
}
|
|
262
|
+
if (inRanges(codePoint, SCRIPT_RANGES.korean)) {
|
|
263
|
+
return "korean";
|
|
264
|
+
}
|
|
265
|
+
if (inRanges(codePoint, SCRIPT_RANGES.cyrillic)) {
|
|
266
|
+
return "cyrillic";
|
|
267
|
+
}
|
|
268
|
+
if (inRanges(codePoint, SCRIPT_RANGES.numbers)) {
|
|
269
|
+
return "numbers";
|
|
270
|
+
}
|
|
271
|
+
if (inRanges(codePoint, SCRIPT_RANGES.punctuation)) {
|
|
272
|
+
return "punctuation";
|
|
273
|
+
}
|
|
274
|
+
if (inRanges(codePoint, SCRIPT_RANGES.arabic) || inRanges(codePoint, SCRIPT_RANGES.hebrew) || inRanges(codePoint, SCRIPT_RANGES.thai) || inRanges(codePoint, SCRIPT_RANGES.emoji)) {
|
|
275
|
+
return "unsupported";
|
|
276
|
+
}
|
|
277
|
+
return "latin";
|
|
278
|
+
}
|
|
279
|
+
function isCJKCharacter(char) {
|
|
280
|
+
const script = detectScript(char);
|
|
281
|
+
return script === "cjk" || script === "hiragana" || script === "katakana";
|
|
282
|
+
}
|
|
283
|
+
function isKoreanCharacter(char) {
|
|
284
|
+
return detectScript(char) === "korean";
|
|
285
|
+
}
|
|
286
|
+
function isUniformWidthScript(char) {
|
|
287
|
+
const script = detectScript(char);
|
|
288
|
+
return script === "cjk" || script === "hiragana" || script === "katakana" || script === "korean" || script === "cyrillic";
|
|
289
|
+
}
|
|
290
|
+
function isUnsupportedScript(char) {
|
|
291
|
+
return detectScript(char) === "unsupported";
|
|
292
|
+
}
|
|
293
|
+
function needsHyphenForBreak(charBefore, charAfter) {
|
|
294
|
+
if (isCJKCharacter(charBefore)) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
if (isCJKCharacter(charAfter)) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
if (charBefore === " " || charBefore === "\t") {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
if (charAfter === " " || charAfter === "\t") {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
const breakPunctuation = ["-", "–", "—", "/", "\\", "|"];
|
|
307
|
+
if (breakPunctuation.includes(charBefore)) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/display-utils/measurer/TextMeasurer.ts
|
|
314
|
+
class TextMeasurer {
|
|
315
|
+
profile;
|
|
316
|
+
charCache = new Map;
|
|
317
|
+
constructor(profile) {
|
|
318
|
+
this.profile = profile;
|
|
319
|
+
this.buildCharCache();
|
|
320
|
+
}
|
|
321
|
+
buildCharCache() {
|
|
322
|
+
const { glyphWidths, renderFormula } = this.profile.fontMetrics;
|
|
323
|
+
for (const [char, glyphWidth] of glyphWidths.entries()) {
|
|
324
|
+
const renderedWidth = renderFormula(glyphWidth);
|
|
325
|
+
this.charCache.set(char, renderedWidth);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
measureText(text) {
|
|
329
|
+
if (!text || text.length === 0) {
|
|
330
|
+
return 0;
|
|
331
|
+
}
|
|
332
|
+
let totalWidth = 0;
|
|
333
|
+
for (const char of text) {
|
|
334
|
+
totalWidth += this.measureChar(char);
|
|
335
|
+
}
|
|
336
|
+
return totalWidth;
|
|
337
|
+
}
|
|
338
|
+
measureTextDetailed(text) {
|
|
339
|
+
if (!text || text.length === 0) {
|
|
340
|
+
return {
|
|
341
|
+
text: "",
|
|
342
|
+
totalWidthPx: 0,
|
|
343
|
+
charCount: 0,
|
|
344
|
+
chars: []
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
const chars = [];
|
|
348
|
+
let totalWidth = 0;
|
|
349
|
+
for (const char of text) {
|
|
350
|
+
const script = detectScript(char);
|
|
351
|
+
const widthPx = this.measureChar(char);
|
|
352
|
+
const fromGlyphMap = this.profile.fontMetrics.glyphWidths.has(char);
|
|
353
|
+
chars.push({
|
|
354
|
+
char,
|
|
355
|
+
widthPx,
|
|
356
|
+
script,
|
|
357
|
+
fromGlyphMap
|
|
358
|
+
});
|
|
359
|
+
totalWidth += widthPx;
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
text,
|
|
363
|
+
totalWidthPx: totalWidth,
|
|
364
|
+
charCount: chars.length,
|
|
365
|
+
chars
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
measureChar(char) {
|
|
369
|
+
if (!char || char.length === 0) {
|
|
370
|
+
return 0;
|
|
371
|
+
}
|
|
372
|
+
const cached = this.charCache.get(char);
|
|
373
|
+
if (cached !== undefined) {
|
|
374
|
+
return cached;
|
|
375
|
+
}
|
|
376
|
+
const width = this.calculateCharWidth(char);
|
|
377
|
+
this.charCache.set(char, width);
|
|
378
|
+
return width;
|
|
379
|
+
}
|
|
380
|
+
calculateCharWidth(char) {
|
|
381
|
+
const { fontMetrics } = this.profile;
|
|
382
|
+
const { uniformScripts, fallback, renderFormula, glyphWidths } = fontMetrics;
|
|
383
|
+
const glyphWidth = glyphWidths.get(char);
|
|
384
|
+
if (glyphWidth !== undefined) {
|
|
385
|
+
return renderFormula(glyphWidth);
|
|
386
|
+
}
|
|
387
|
+
const script = detectScript(char);
|
|
388
|
+
switch (script) {
|
|
389
|
+
case "cjk":
|
|
390
|
+
return uniformScripts.cjk;
|
|
391
|
+
case "hiragana":
|
|
392
|
+
return uniformScripts.hiragana;
|
|
393
|
+
case "katakana":
|
|
394
|
+
return uniformScripts.katakana;
|
|
395
|
+
case "korean":
|
|
396
|
+
return uniformScripts.korean;
|
|
397
|
+
case "cyrillic":
|
|
398
|
+
return uniformScripts.cyrillic;
|
|
399
|
+
}
|
|
400
|
+
return fallback.latinMaxWidth;
|
|
401
|
+
}
|
|
402
|
+
getGlyphWidth(char) {
|
|
403
|
+
return this.profile.fontMetrics.glyphWidths.get(char);
|
|
404
|
+
}
|
|
405
|
+
fitsInWidth(text, maxWidthPx) {
|
|
406
|
+
return this.measureText(text) <= maxWidthPx;
|
|
407
|
+
}
|
|
408
|
+
charsThatFit(text, maxWidthPx, startIndex = 0) {
|
|
409
|
+
if (!text || startIndex >= text.length) {
|
|
410
|
+
return 0;
|
|
411
|
+
}
|
|
412
|
+
let currentWidth = 0;
|
|
413
|
+
let count = 0;
|
|
414
|
+
const chars = Array.from(text).slice(startIndex);
|
|
415
|
+
for (const char of chars) {
|
|
416
|
+
const charWidth = this.measureChar(char);
|
|
417
|
+
if (currentWidth + charWidth > maxWidthPx) {
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
currentWidth += charWidth;
|
|
421
|
+
count++;
|
|
422
|
+
}
|
|
423
|
+
return count;
|
|
424
|
+
}
|
|
425
|
+
getPixelOffset(text, index) {
|
|
426
|
+
if (!text || index <= 0) {
|
|
427
|
+
return 0;
|
|
428
|
+
}
|
|
429
|
+
const chars = Array.from(text).slice(0, index);
|
|
430
|
+
let offset = 0;
|
|
431
|
+
for (const char of chars) {
|
|
432
|
+
offset += this.measureChar(char);
|
|
433
|
+
}
|
|
434
|
+
return offset;
|
|
435
|
+
}
|
|
436
|
+
detectScript(char) {
|
|
437
|
+
return detectScript(char);
|
|
438
|
+
}
|
|
439
|
+
isUniformWidth(char) {
|
|
440
|
+
return isUniformWidthScript(char);
|
|
441
|
+
}
|
|
442
|
+
getProfile() {
|
|
443
|
+
return this.profile;
|
|
444
|
+
}
|
|
445
|
+
getDisplayWidthPx() {
|
|
446
|
+
return this.profile.displayWidthPx;
|
|
447
|
+
}
|
|
448
|
+
getMaxLines() {
|
|
449
|
+
return this.profile.maxLines;
|
|
450
|
+
}
|
|
451
|
+
getMaxPayloadBytes() {
|
|
452
|
+
return this.profile.maxPayloadBytes;
|
|
453
|
+
}
|
|
454
|
+
getByteSize(text) {
|
|
455
|
+
return new TextEncoder().encode(text).length;
|
|
456
|
+
}
|
|
457
|
+
getHyphenWidth() {
|
|
458
|
+
return this.measureChar("-");
|
|
459
|
+
}
|
|
460
|
+
getSpaceWidth() {
|
|
461
|
+
return this.measureChar(" ");
|
|
462
|
+
}
|
|
463
|
+
clearCache() {
|
|
464
|
+
this.charCache.clear();
|
|
465
|
+
this.buildCharCache();
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
// src/display-utils/wrapper/types.ts
|
|
469
|
+
var DEFAULT_WRAP_OPTIONS = {
|
|
470
|
+
breakMode: "character",
|
|
471
|
+
hyphenChar: "-",
|
|
472
|
+
minCharsBeforeHyphen: 3,
|
|
473
|
+
trimLines: true,
|
|
474
|
+
preserveNewlines: true
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
// src/display-utils/wrapper/TextWrapper.ts
|
|
478
|
+
class TextWrapper {
|
|
479
|
+
measurer;
|
|
480
|
+
defaultOptions;
|
|
481
|
+
constructor(measurer, defaultOptions) {
|
|
482
|
+
this.measurer = measurer;
|
|
483
|
+
const profile = measurer.getProfile();
|
|
484
|
+
this.defaultOptions = {
|
|
485
|
+
maxWidthPx: profile.displayWidthPx,
|
|
486
|
+
maxLines: profile.maxLines,
|
|
487
|
+
maxBytes: profile.maxPayloadBytes,
|
|
488
|
+
...DEFAULT_WRAP_OPTIONS,
|
|
489
|
+
...defaultOptions
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
wrap(text, options) {
|
|
493
|
+
const opts = this.mergeOptions(options);
|
|
494
|
+
const { maxWidthPx, maxLines, maxBytes, breakMode, preserveNewlines } = opts;
|
|
495
|
+
if (!text || text.length === 0) {
|
|
496
|
+
return this.createEmptyResult(opts);
|
|
497
|
+
}
|
|
498
|
+
const paragraphs = preserveNewlines ? text.split(`
|
|
499
|
+
`) : [text];
|
|
500
|
+
const allLines = [];
|
|
501
|
+
const allMetrics = [];
|
|
502
|
+
let totalBytes = 0;
|
|
503
|
+
let truncated = false;
|
|
504
|
+
for (let pIndex = 0;pIndex < paragraphs.length; pIndex++) {
|
|
505
|
+
const paragraph = paragraphs[pIndex];
|
|
506
|
+
const isFromNewline = pIndex > 0;
|
|
507
|
+
const paragraphLines = this.wrapParagraph(paragraph, maxWidthPx, breakMode, opts);
|
|
508
|
+
for (let lIndex = 0;lIndex < paragraphLines.length; lIndex++) {
|
|
509
|
+
const line = paragraphLines[lIndex];
|
|
510
|
+
const lineBytes = this.measurer.getByteSize(line);
|
|
511
|
+
if (allLines.length >= maxLines) {
|
|
512
|
+
truncated = true;
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
if (totalBytes + lineBytes > maxBytes) {
|
|
516
|
+
truncated = true;
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
const widthPx = this.measurer.measureText(line);
|
|
520
|
+
const metrics = {
|
|
521
|
+
text: line,
|
|
522
|
+
widthPx,
|
|
523
|
+
bytes: lineBytes,
|
|
524
|
+
utilizationPercent: Math.round(widthPx / maxWidthPx * 100),
|
|
525
|
+
endsWithHyphen: line.endsWith(opts.hyphenChar) && lIndex < paragraphLines.length - 1,
|
|
526
|
+
fromExplicitNewline: isFromNewline && lIndex === 0
|
|
527
|
+
};
|
|
528
|
+
allLines.push(line);
|
|
529
|
+
allMetrics.push(metrics);
|
|
530
|
+
totalBytes += lineBytes;
|
|
531
|
+
if (allLines.length < maxLines) {
|
|
532
|
+
totalBytes += 1;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
if (truncated)
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
const maxLineWidthPx = allMetrics.reduce((max, m) => Math.max(max, m.widthPx), 0);
|
|
539
|
+
return {
|
|
540
|
+
lines: allLines,
|
|
541
|
+
truncated,
|
|
542
|
+
maxLineWidthPx,
|
|
543
|
+
totalBytes,
|
|
544
|
+
lineMetrics: allMetrics,
|
|
545
|
+
originalText: text,
|
|
546
|
+
breakMode
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
wrapToLines(text, options) {
|
|
550
|
+
return this.wrap(text, options).lines;
|
|
551
|
+
}
|
|
552
|
+
needsWrap(text, maxWidthPx) {
|
|
553
|
+
const width = maxWidthPx ?? this.defaultOptions.maxWidthPx;
|
|
554
|
+
return this.measurer.measureText(text) > width || text.includes(`
|
|
555
|
+
`);
|
|
556
|
+
}
|
|
557
|
+
getOptions() {
|
|
558
|
+
return { ...this.defaultOptions };
|
|
559
|
+
}
|
|
560
|
+
getMeasurer() {
|
|
561
|
+
return this.measurer;
|
|
562
|
+
}
|
|
563
|
+
wrapParagraph(paragraph, maxWidthPx, breakMode, opts) {
|
|
564
|
+
const trimmed = opts.trimLines ? paragraph.trim() : paragraph;
|
|
565
|
+
if (!trimmed) {
|
|
566
|
+
return [""];
|
|
567
|
+
}
|
|
568
|
+
if (this.measurer.fitsInWidth(trimmed, maxWidthPx)) {
|
|
569
|
+
return [trimmed];
|
|
570
|
+
}
|
|
571
|
+
switch (breakMode) {
|
|
572
|
+
case "character":
|
|
573
|
+
return this.wrapCharacterMode(trimmed, maxWidthPx, opts);
|
|
574
|
+
case "word":
|
|
575
|
+
return this.wrapWordMode(trimmed, maxWidthPx, opts);
|
|
576
|
+
case "strict-word":
|
|
577
|
+
return this.wrapStrictWordMode(trimmed, maxWidthPx, opts);
|
|
578
|
+
default:
|
|
579
|
+
return this.wrapCharacterMode(trimmed, maxWidthPx, opts);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
wrapCharacterMode(text, maxWidthPx, opts) {
|
|
583
|
+
const lines = [];
|
|
584
|
+
const hyphenWidth = this.measurer.measureChar(opts.hyphenChar);
|
|
585
|
+
let currentLine = "";
|
|
586
|
+
let currentWidth = 0;
|
|
587
|
+
const chars = Array.from(text);
|
|
588
|
+
for (let i = 0;i < chars.length; i++) {
|
|
589
|
+
const char = chars[i];
|
|
590
|
+
const charWidth = this.measurer.measureChar(char);
|
|
591
|
+
if (currentWidth + charWidth <= maxWidthPx) {
|
|
592
|
+
currentLine += char;
|
|
593
|
+
currentWidth += charWidth;
|
|
594
|
+
} else {
|
|
595
|
+
const prevChar = currentLine.length > 0 ? currentLine[currentLine.length - 1] : "";
|
|
596
|
+
const needsHyphen = currentLine.length >= opts.minCharsBeforeHyphen && needsHyphenForBreak(prevChar, char);
|
|
597
|
+
if (needsHyphen) {
|
|
598
|
+
const result = this.backoffForHyphen(currentLine, currentWidth, maxWidthPx, hyphenWidth, opts);
|
|
599
|
+
if (result.skipHyphen) {
|
|
600
|
+
lines.push(result.line);
|
|
601
|
+
} else {
|
|
602
|
+
lines.push(result.line + opts.hyphenChar);
|
|
603
|
+
}
|
|
604
|
+
currentLine = result.remainder + char;
|
|
605
|
+
currentWidth = this.measurer.measureText(currentLine);
|
|
606
|
+
} else {
|
|
607
|
+
const trimmedLine = opts.trimLines ? currentLine.trimEnd() : currentLine;
|
|
608
|
+
if (trimmedLine) {
|
|
609
|
+
lines.push(trimmedLine);
|
|
610
|
+
}
|
|
611
|
+
currentLine = char === " " ? "" : char;
|
|
612
|
+
currentWidth = char === " " ? 0 : charWidth;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
if (currentLine) {
|
|
617
|
+
const trimmedLine = opts.trimLines ? currentLine.trim() : currentLine;
|
|
618
|
+
if (trimmedLine) {
|
|
619
|
+
lines.push(trimmedLine);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return lines.length > 0 ? lines : [""];
|
|
623
|
+
}
|
|
624
|
+
wrapWordMode(text, maxWidthPx, opts) {
|
|
625
|
+
const lines = [];
|
|
626
|
+
const words = this.splitIntoWords(text);
|
|
627
|
+
const spaceWidth = this.measurer.getSpaceWidth();
|
|
628
|
+
let currentLine = "";
|
|
629
|
+
let currentWidth = 0;
|
|
630
|
+
for (const word of words) {
|
|
631
|
+
const wordWidth = this.measurer.measureText(word);
|
|
632
|
+
const needsSpace = currentLine.length > 0;
|
|
633
|
+
const totalWidth = currentWidth + (needsSpace ? spaceWidth : 0) + wordWidth;
|
|
634
|
+
if (totalWidth <= maxWidthPx) {
|
|
635
|
+
if (needsSpace) {
|
|
636
|
+
currentLine += " ";
|
|
637
|
+
currentWidth += spaceWidth;
|
|
638
|
+
}
|
|
639
|
+
currentLine += word;
|
|
640
|
+
currentWidth += wordWidth;
|
|
641
|
+
} else {
|
|
642
|
+
if (currentLine.length > 0) {
|
|
643
|
+
lines.push(opts.trimLines ? currentLine.trim() : currentLine);
|
|
644
|
+
currentLine = "";
|
|
645
|
+
currentWidth = 0;
|
|
646
|
+
}
|
|
647
|
+
if (wordWidth > maxWidthPx) {
|
|
648
|
+
const hyphenatedLines = this.hyphenateLongWord(word, maxWidthPx, opts);
|
|
649
|
+
for (let i = 0;i < hyphenatedLines.length - 1; i++) {
|
|
650
|
+
lines.push(hyphenatedLines[i]);
|
|
651
|
+
}
|
|
652
|
+
currentLine = hyphenatedLines[hyphenatedLines.length - 1];
|
|
653
|
+
currentWidth = this.measurer.measureText(currentLine);
|
|
654
|
+
} else {
|
|
655
|
+
currentLine = word;
|
|
656
|
+
currentWidth = wordWidth;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
if (currentLine) {
|
|
661
|
+
const trimmedLine = opts.trimLines ? currentLine.trim() : currentLine;
|
|
662
|
+
if (trimmedLine) {
|
|
663
|
+
lines.push(trimmedLine);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return lines.length > 0 ? lines : [""];
|
|
667
|
+
}
|
|
668
|
+
wrapStrictWordMode(text, maxWidthPx, opts) {
|
|
669
|
+
const lines = [];
|
|
670
|
+
const words = this.splitIntoWords(text);
|
|
671
|
+
const spaceWidth = this.measurer.getSpaceWidth();
|
|
672
|
+
let currentLine = "";
|
|
673
|
+
let currentWidth = 0;
|
|
674
|
+
for (const word of words) {
|
|
675
|
+
const wordWidth = this.measurer.measureText(word);
|
|
676
|
+
const needsSpace = currentLine.length > 0;
|
|
677
|
+
const totalWidth = currentWidth + (needsSpace ? spaceWidth : 0) + wordWidth;
|
|
678
|
+
if (totalWidth <= maxWidthPx) {
|
|
679
|
+
if (needsSpace) {
|
|
680
|
+
currentLine += " ";
|
|
681
|
+
currentWidth += spaceWidth;
|
|
682
|
+
}
|
|
683
|
+
currentLine += word;
|
|
684
|
+
currentWidth += wordWidth;
|
|
685
|
+
} else {
|
|
686
|
+
if (currentLine.length > 0) {
|
|
687
|
+
lines.push(opts.trimLines ? currentLine.trim() : currentLine);
|
|
688
|
+
}
|
|
689
|
+
currentLine = word;
|
|
690
|
+
currentWidth = wordWidth;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
if (currentLine) {
|
|
694
|
+
const trimmedLine = opts.trimLines ? currentLine.trim() : currentLine;
|
|
695
|
+
if (trimmedLine) {
|
|
696
|
+
lines.push(trimmedLine);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return lines.length > 0 ? lines : [""];
|
|
700
|
+
}
|
|
701
|
+
splitIntoWords(text) {
|
|
702
|
+
const words = [];
|
|
703
|
+
let currentWord = "";
|
|
704
|
+
for (const char of text) {
|
|
705
|
+
if (char === " " || char === "\t") {
|
|
706
|
+
if (currentWord) {
|
|
707
|
+
words.push(currentWord);
|
|
708
|
+
currentWord = "";
|
|
709
|
+
}
|
|
710
|
+
} else if (isCJKCharacter(char)) {
|
|
711
|
+
if (currentWord) {
|
|
712
|
+
words.push(currentWord);
|
|
713
|
+
currentWord = "";
|
|
714
|
+
}
|
|
715
|
+
words.push(char);
|
|
716
|
+
} else {
|
|
717
|
+
currentWord += char;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (currentWord) {
|
|
721
|
+
words.push(currentWord);
|
|
722
|
+
}
|
|
723
|
+
return words;
|
|
724
|
+
}
|
|
725
|
+
hyphenateLongWord(word, maxWidthPx, opts) {
|
|
726
|
+
const lines = [];
|
|
727
|
+
const hyphenWidth = this.measurer.measureChar(opts.hyphenChar);
|
|
728
|
+
const chars = Array.from(word);
|
|
729
|
+
let currentLine = "";
|
|
730
|
+
let currentWidth = 0;
|
|
731
|
+
for (let i = 0;i < chars.length; i++) {
|
|
732
|
+
const char = chars[i];
|
|
733
|
+
const charWidth = this.measurer.measureChar(char);
|
|
734
|
+
const isLastChar = i === chars.length - 1;
|
|
735
|
+
const widthNeeded = isLastChar ? charWidth : charWidth + hyphenWidth;
|
|
736
|
+
if (currentWidth + widthNeeded <= maxWidthPx) {
|
|
737
|
+
currentLine += char;
|
|
738
|
+
currentWidth += charWidth;
|
|
739
|
+
} else {
|
|
740
|
+
if (currentLine.length >= opts.minCharsBeforeHyphen) {
|
|
741
|
+
const result = this.backoffForHyphen(currentLine, currentWidth, maxWidthPx, hyphenWidth, opts);
|
|
742
|
+
if (result.skipHyphen) {
|
|
743
|
+
lines.push(result.line);
|
|
744
|
+
} else {
|
|
745
|
+
lines.push(result.line + opts.hyphenChar);
|
|
746
|
+
}
|
|
747
|
+
currentLine = result.remainder + char;
|
|
748
|
+
currentWidth = this.measurer.measureText(currentLine);
|
|
749
|
+
} else {
|
|
750
|
+
if (currentLine) {
|
|
751
|
+
lines.push(currentLine);
|
|
752
|
+
}
|
|
753
|
+
currentLine = char;
|
|
754
|
+
currentWidth = charWidth;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
if (currentLine) {
|
|
759
|
+
lines.push(currentLine);
|
|
760
|
+
}
|
|
761
|
+
return lines.length > 0 ? lines : [word];
|
|
762
|
+
}
|
|
763
|
+
backoffForHyphen(line, lineWidth, maxWidthPx, hyphenWidth, opts) {
|
|
764
|
+
let adjustedLine = line;
|
|
765
|
+
let adjustedWidth = lineWidth;
|
|
766
|
+
let remainder = "";
|
|
767
|
+
while (adjustedWidth + hyphenWidth > maxWidthPx && adjustedLine.length > opts.minCharsBeforeHyphen) {
|
|
768
|
+
const lastChar = adjustedLine[adjustedLine.length - 1];
|
|
769
|
+
const lastCharWidth = this.measurer.measureChar(lastChar);
|
|
770
|
+
adjustedLine = adjustedLine.slice(0, -1);
|
|
771
|
+
adjustedWidth -= lastCharWidth;
|
|
772
|
+
remainder = lastChar + remainder;
|
|
773
|
+
if (adjustedLine.length > 0 && adjustedLine[adjustedLine.length - 1] === " ") {
|
|
774
|
+
adjustedLine = adjustedLine.trimEnd();
|
|
775
|
+
return { line: adjustedLine, remainder, skipHyphen: true };
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return { line: adjustedLine, remainder, skipHyphen: false };
|
|
779
|
+
}
|
|
780
|
+
mergeOptions(options) {
|
|
781
|
+
return {
|
|
782
|
+
...this.defaultOptions,
|
|
783
|
+
...options
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
createEmptyResult(opts) {
|
|
787
|
+
return {
|
|
788
|
+
lines: [""],
|
|
789
|
+
truncated: false,
|
|
790
|
+
maxLineWidthPx: 0,
|
|
791
|
+
totalBytes: 0,
|
|
792
|
+
lineMetrics: [
|
|
793
|
+
{
|
|
794
|
+
text: "",
|
|
795
|
+
widthPx: 0,
|
|
796
|
+
bytes: 0,
|
|
797
|
+
utilizationPercent: 0,
|
|
798
|
+
endsWithHyphen: false,
|
|
799
|
+
fromExplicitNewline: false
|
|
800
|
+
}
|
|
801
|
+
],
|
|
802
|
+
originalText: "",
|
|
803
|
+
breakMode: opts.breakMode
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
// src/display-utils/helpers/DisplayHelpers.ts
|
|
808
|
+
class DisplayHelpers {
|
|
809
|
+
measurer;
|
|
810
|
+
wrapper;
|
|
811
|
+
profile;
|
|
812
|
+
constructor(measurer, wrapper) {
|
|
813
|
+
this.measurer = measurer;
|
|
814
|
+
this.wrapper = wrapper;
|
|
815
|
+
this.profile = measurer.getProfile();
|
|
816
|
+
}
|
|
817
|
+
truncateToLines(lines, maxLines, fromEnd = false) {
|
|
818
|
+
if (lines.length <= maxLines) {
|
|
819
|
+
return lines;
|
|
820
|
+
}
|
|
821
|
+
if (fromEnd) {
|
|
822
|
+
return lines.slice(-maxLines);
|
|
823
|
+
} else {
|
|
824
|
+
return lines.slice(0, maxLines);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
truncateWithEllipsis(text, maxWidthPx, ellipsis = "...") {
|
|
828
|
+
const width = maxWidthPx ?? this.profile.displayWidthPx;
|
|
829
|
+
const textWidth = this.measurer.measureText(text);
|
|
830
|
+
if (textWidth <= width) {
|
|
831
|
+
return {
|
|
832
|
+
text,
|
|
833
|
+
wasTruncated: false,
|
|
834
|
+
widthPx: textWidth,
|
|
835
|
+
originalLength: text.length,
|
|
836
|
+
truncatedLength: text.length
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
const ellipsisWidth = this.measurer.measureText(ellipsis);
|
|
840
|
+
const targetWidth = width - ellipsisWidth;
|
|
841
|
+
let truncated = "";
|
|
842
|
+
let currentWidth = 0;
|
|
843
|
+
for (const char of text) {
|
|
844
|
+
const charWidth = this.measurer.measureChar(char);
|
|
845
|
+
if (currentWidth + charWidth > targetWidth) {
|
|
846
|
+
break;
|
|
847
|
+
}
|
|
848
|
+
truncated += char;
|
|
849
|
+
currentWidth += charWidth;
|
|
850
|
+
}
|
|
851
|
+
truncated = truncated.trimEnd();
|
|
852
|
+
const finalText = truncated + ellipsis;
|
|
853
|
+
return {
|
|
854
|
+
text: finalText,
|
|
855
|
+
wasTruncated: true,
|
|
856
|
+
widthPx: this.measurer.measureText(finalText),
|
|
857
|
+
originalLength: text.length,
|
|
858
|
+
truncatedLength: truncated.length
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
estimateLineCount(text, maxWidthPx) {
|
|
862
|
+
if (!text)
|
|
863
|
+
return 1;
|
|
864
|
+
const width = maxWidthPx ?? this.profile.displayWidthPx;
|
|
865
|
+
const textWidth = this.measurer.measureText(text);
|
|
866
|
+
const newlineCount = (text.match(/\n/g) || []).length;
|
|
867
|
+
const wrappedLines = Math.ceil(textWidth / width);
|
|
868
|
+
return wrappedLines + newlineCount;
|
|
869
|
+
}
|
|
870
|
+
fitToScreen(text, options) {
|
|
871
|
+
const result = this.wrapper.wrap(text, options);
|
|
872
|
+
return result.lines.slice(0, this.profile.maxLines);
|
|
873
|
+
}
|
|
874
|
+
paginate(text, options) {
|
|
875
|
+
const wrapResult = this.wrapper.wrap(text, {
|
|
876
|
+
...options,
|
|
877
|
+
maxLines: Infinity,
|
|
878
|
+
maxBytes: Infinity
|
|
879
|
+
});
|
|
880
|
+
const linesPerPage = options?.maxLines ?? this.profile.maxLines;
|
|
881
|
+
const allLines = wrapResult.lines;
|
|
882
|
+
const pages = [];
|
|
883
|
+
for (let i = 0;i < allLines.length; i += linesPerPage) {
|
|
884
|
+
const pageLines = allLines.slice(i, i + linesPerPage);
|
|
885
|
+
const pageNumber = Math.floor(i / linesPerPage) + 1;
|
|
886
|
+
const totalPages = Math.ceil(allLines.length / linesPerPage);
|
|
887
|
+
pages.push({
|
|
888
|
+
lines: pageLines,
|
|
889
|
+
pageNumber,
|
|
890
|
+
totalPages,
|
|
891
|
+
isFirst: pageNumber === 1,
|
|
892
|
+
isLast: pageNumber === totalPages
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
return pages.length > 0 ? pages : [
|
|
896
|
+
{
|
|
897
|
+
lines: [""],
|
|
898
|
+
pageNumber: 1,
|
|
899
|
+
totalPages: 1,
|
|
900
|
+
isFirst: true,
|
|
901
|
+
isLast: true
|
|
902
|
+
}
|
|
903
|
+
];
|
|
904
|
+
}
|
|
905
|
+
calculateByteSize(text) {
|
|
906
|
+
return this.measurer.getByteSize(text);
|
|
907
|
+
}
|
|
908
|
+
exceedsByteLimit(text, maxBytes) {
|
|
909
|
+
const limit = maxBytes ?? this.profile.maxPayloadBytes;
|
|
910
|
+
return this.calculateByteSize(text) > limit;
|
|
911
|
+
}
|
|
912
|
+
splitIntoChunks(text, chunkSize) {
|
|
913
|
+
const size = chunkSize ?? this.profile.bleChunkSize;
|
|
914
|
+
const encoder = new TextEncoder;
|
|
915
|
+
const bytes = encoder.encode(text);
|
|
916
|
+
if (bytes.length <= size) {
|
|
917
|
+
return [
|
|
918
|
+
{
|
|
919
|
+
text,
|
|
920
|
+
index: 0,
|
|
921
|
+
totalChunks: 1,
|
|
922
|
+
bytes: bytes.length
|
|
923
|
+
}
|
|
924
|
+
];
|
|
925
|
+
}
|
|
926
|
+
const chunks = [];
|
|
927
|
+
let offset = 0;
|
|
928
|
+
while (offset < bytes.length) {
|
|
929
|
+
let endOffset = Math.min(offset + size, bytes.length);
|
|
930
|
+
if (endOffset < bytes.length) {
|
|
931
|
+
while (endOffset > offset && (bytes[endOffset] & 192) === 128) {
|
|
932
|
+
endOffset--;
|
|
933
|
+
}
|
|
934
|
+
let breakPoint = endOffset;
|
|
935
|
+
for (let i = endOffset - 1;i > offset + size / 2; i--) {
|
|
936
|
+
if (bytes[i] === 32 || bytes[i] === 10) {
|
|
937
|
+
breakPoint = i + 1;
|
|
938
|
+
break;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
if (breakPoint > offset) {
|
|
942
|
+
endOffset = breakPoint;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
const chunkBytes = bytes.slice(offset, endOffset);
|
|
946
|
+
const chunkText = new TextDecoder().decode(chunkBytes);
|
|
947
|
+
chunks.push({
|
|
948
|
+
text: chunkText,
|
|
949
|
+
index: chunks.length,
|
|
950
|
+
totalChunks: 0,
|
|
951
|
+
bytes: chunkBytes.length
|
|
952
|
+
});
|
|
953
|
+
offset = endOffset;
|
|
954
|
+
}
|
|
955
|
+
for (const chunk of chunks) {
|
|
956
|
+
chunk.totalChunks = chunks.length;
|
|
957
|
+
}
|
|
958
|
+
return chunks;
|
|
959
|
+
}
|
|
960
|
+
calculateUtilization(result) {
|
|
961
|
+
if (result.lines.length === 0 || result.lineMetrics.length === 0) {
|
|
962
|
+
return {
|
|
963
|
+
averageUtilization: 0,
|
|
964
|
+
minUtilization: 0,
|
|
965
|
+
maxUtilization: 0,
|
|
966
|
+
totalWastedPx: 0
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
const maxWidthPx = this.profile.displayWidthPx;
|
|
970
|
+
let totalUtilization = 0;
|
|
971
|
+
let minUtilization = 100;
|
|
972
|
+
let maxUtilization = 0;
|
|
973
|
+
let totalWastedPx = 0;
|
|
974
|
+
for (const metric of result.lineMetrics) {
|
|
975
|
+
totalUtilization += metric.utilizationPercent;
|
|
976
|
+
minUtilization = Math.min(minUtilization, metric.utilizationPercent);
|
|
977
|
+
maxUtilization = Math.max(maxUtilization, metric.utilizationPercent);
|
|
978
|
+
totalWastedPx += maxWidthPx - metric.widthPx;
|
|
979
|
+
}
|
|
980
|
+
return {
|
|
981
|
+
averageUtilization: Math.round(totalUtilization / result.lineMetrics.length),
|
|
982
|
+
minUtilization,
|
|
983
|
+
maxUtilization,
|
|
984
|
+
totalWastedPx
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
padToLineCount(lines, targetCount, padAtEnd = true) {
|
|
988
|
+
if (lines.length >= targetCount) {
|
|
989
|
+
return lines.slice(0, targetCount);
|
|
990
|
+
}
|
|
991
|
+
const padding = Array(targetCount - lines.length).fill("");
|
|
992
|
+
if (padAtEnd) {
|
|
993
|
+
return [...lines, ...padding];
|
|
994
|
+
} else {
|
|
995
|
+
return [...padding, ...lines];
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
joinLines(lines) {
|
|
999
|
+
return lines.join(`
|
|
1000
|
+
`);
|
|
1001
|
+
}
|
|
1002
|
+
getMeasurer() {
|
|
1003
|
+
return this.measurer;
|
|
1004
|
+
}
|
|
1005
|
+
getWrapper() {
|
|
1006
|
+
return this.wrapper;
|
|
1007
|
+
}
|
|
1008
|
+
getProfile() {
|
|
1009
|
+
return this.profile;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
// src/display-utils/helpers/ScrollView.ts
|
|
1013
|
+
class ScrollView {
|
|
1014
|
+
measurer;
|
|
1015
|
+
wrapper;
|
|
1016
|
+
profile;
|
|
1017
|
+
viewportSize;
|
|
1018
|
+
allLines = [];
|
|
1019
|
+
wrapResult = null;
|
|
1020
|
+
scrollOffset = 0;
|
|
1021
|
+
constructor(measurer, wrapper, viewportSize) {
|
|
1022
|
+
this.measurer = measurer;
|
|
1023
|
+
this.wrapper = wrapper;
|
|
1024
|
+
this.profile = measurer.getProfile();
|
|
1025
|
+
this.viewportSize = viewportSize ?? this.profile.maxLines;
|
|
1026
|
+
}
|
|
1027
|
+
setContent(text, options) {
|
|
1028
|
+
this.wrapResult = this.wrapper.wrap(text, {
|
|
1029
|
+
...options,
|
|
1030
|
+
maxLines: Infinity,
|
|
1031
|
+
maxBytes: Infinity
|
|
1032
|
+
});
|
|
1033
|
+
this.allLines = this.wrapResult.lines;
|
|
1034
|
+
this.scrollOffset = 0;
|
|
1035
|
+
}
|
|
1036
|
+
appendContent(text, options, autoScroll = true) {
|
|
1037
|
+
const wasAtBottom = this.isAtBottom();
|
|
1038
|
+
const newResult = this.wrapper.wrap(text, {
|
|
1039
|
+
...options,
|
|
1040
|
+
maxLines: Infinity,
|
|
1041
|
+
maxBytes: Infinity
|
|
1042
|
+
});
|
|
1043
|
+
this.allLines = [...this.allLines, ...newResult.lines];
|
|
1044
|
+
if (autoScroll && wasAtBottom) {
|
|
1045
|
+
this.scrollToBottom();
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
getViewport() {
|
|
1049
|
+
const visibleLines = this.allLines.slice(this.scrollOffset, this.scrollOffset + this.viewportSize);
|
|
1050
|
+
while (visibleLines.length < this.viewportSize) {
|
|
1051
|
+
visibleLines.push("");
|
|
1052
|
+
}
|
|
1053
|
+
return {
|
|
1054
|
+
lines: visibleLines,
|
|
1055
|
+
position: this.getPosition(),
|
|
1056
|
+
contentTruncated: this.wrapResult?.truncated ?? false
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
getPosition() {
|
|
1060
|
+
const totalLines = this.allLines.length;
|
|
1061
|
+
const maxOffset = Math.max(0, totalLines - this.viewportSize);
|
|
1062
|
+
return {
|
|
1063
|
+
offset: this.scrollOffset,
|
|
1064
|
+
totalLines,
|
|
1065
|
+
visibleLines: this.viewportSize,
|
|
1066
|
+
maxOffset,
|
|
1067
|
+
atTop: this.scrollOffset === 0,
|
|
1068
|
+
atBottom: this.scrollOffset >= maxOffset,
|
|
1069
|
+
scrollPercent: maxOffset > 0 ? Math.round(this.scrollOffset / maxOffset * 100) : 100
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
scrollTo(offset) {
|
|
1073
|
+
const maxOffset = Math.max(0, this.allLines.length - this.viewportSize);
|
|
1074
|
+
this.scrollOffset = Math.max(0, Math.min(offset, maxOffset));
|
|
1075
|
+
}
|
|
1076
|
+
scrollDown(lines = 1) {
|
|
1077
|
+
this.scrollTo(this.scrollOffset + lines);
|
|
1078
|
+
}
|
|
1079
|
+
scrollUp(lines = 1) {
|
|
1080
|
+
this.scrollTo(this.scrollOffset - lines);
|
|
1081
|
+
}
|
|
1082
|
+
pageDown() {
|
|
1083
|
+
this.scrollDown(this.viewportSize);
|
|
1084
|
+
}
|
|
1085
|
+
pageUp() {
|
|
1086
|
+
this.scrollUp(this.viewportSize);
|
|
1087
|
+
}
|
|
1088
|
+
scrollToTop() {
|
|
1089
|
+
this.scrollOffset = 0;
|
|
1090
|
+
}
|
|
1091
|
+
scrollToBottom() {
|
|
1092
|
+
const maxOffset = Math.max(0, this.allLines.length - this.viewportSize);
|
|
1093
|
+
this.scrollOffset = maxOffset;
|
|
1094
|
+
}
|
|
1095
|
+
scrollToLine(lineIndex, position = "top") {
|
|
1096
|
+
let targetOffset;
|
|
1097
|
+
switch (position) {
|
|
1098
|
+
case "top":
|
|
1099
|
+
targetOffset = lineIndex;
|
|
1100
|
+
break;
|
|
1101
|
+
case "center":
|
|
1102
|
+
targetOffset = lineIndex - Math.floor(this.viewportSize / 2);
|
|
1103
|
+
break;
|
|
1104
|
+
case "bottom":
|
|
1105
|
+
targetOffset = lineIndex - this.viewportSize + 1;
|
|
1106
|
+
break;
|
|
1107
|
+
}
|
|
1108
|
+
this.scrollTo(targetOffset);
|
|
1109
|
+
}
|
|
1110
|
+
scrollToPercent(percent) {
|
|
1111
|
+
const maxOffset = Math.max(0, this.allLines.length - this.viewportSize);
|
|
1112
|
+
const targetOffset = Math.round(percent / 100 * maxOffset);
|
|
1113
|
+
this.scrollTo(targetOffset);
|
|
1114
|
+
}
|
|
1115
|
+
isAtTop() {
|
|
1116
|
+
return this.scrollOffset === 0;
|
|
1117
|
+
}
|
|
1118
|
+
isAtBottom() {
|
|
1119
|
+
const maxOffset = Math.max(0, this.allLines.length - this.viewportSize);
|
|
1120
|
+
return this.scrollOffset >= maxOffset;
|
|
1121
|
+
}
|
|
1122
|
+
isScrollable() {
|
|
1123
|
+
return this.allLines.length > this.viewportSize;
|
|
1124
|
+
}
|
|
1125
|
+
getAllLines() {
|
|
1126
|
+
return [...this.allLines];
|
|
1127
|
+
}
|
|
1128
|
+
getTotalLines() {
|
|
1129
|
+
return this.allLines.length;
|
|
1130
|
+
}
|
|
1131
|
+
getViewportSize() {
|
|
1132
|
+
return this.viewportSize;
|
|
1133
|
+
}
|
|
1134
|
+
clear() {
|
|
1135
|
+
this.allLines = [];
|
|
1136
|
+
this.wrapResult = null;
|
|
1137
|
+
this.scrollOffset = 0;
|
|
1138
|
+
}
|
|
1139
|
+
getMeasurer() {
|
|
1140
|
+
return this.measurer;
|
|
1141
|
+
}
|
|
1142
|
+
getWrapper() {
|
|
1143
|
+
return this.wrapper;
|
|
1144
|
+
}
|
|
1145
|
+
getProfile() {
|
|
1146
|
+
return this.profile;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
// src/display-utils/index.ts
|
|
1150
|
+
function createDisplayToolkit(profile = G1_PROFILE, wrapOptions) {
|
|
1151
|
+
const measurer = new TextMeasurer(profile);
|
|
1152
|
+
const wrapper = new TextWrapper(measurer, wrapOptions);
|
|
1153
|
+
const helpers = new DisplayHelpers(measurer, wrapper);
|
|
1154
|
+
return {
|
|
1155
|
+
measurer,
|
|
1156
|
+
wrapper,
|
|
1157
|
+
helpers,
|
|
1158
|
+
profile
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
function createG1Toolkit() {
|
|
1162
|
+
return createDisplayToolkit(G1_PROFILE, {
|
|
1163
|
+
breakMode: "character",
|
|
1164
|
+
hyphenChar: "-",
|
|
1165
|
+
minCharsBeforeHyphen: 3
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
function createG1LegacyToolkit() {
|
|
1169
|
+
return createDisplayToolkit(G1_PROFILE_LEGACY, {
|
|
1170
|
+
breakMode: "character",
|
|
1171
|
+
hyphenChar: "-",
|
|
1172
|
+
minCharsBeforeHyphen: 3
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
export {
|
|
1176
|
+
needsHyphenForBreak,
|
|
1177
|
+
isUnsupportedScript,
|
|
1178
|
+
isUniformWidthScript,
|
|
1179
|
+
isKoreanCharacter,
|
|
1180
|
+
isCJKCharacter,
|
|
1181
|
+
detectScript,
|
|
1182
|
+
createG1Toolkit,
|
|
1183
|
+
createG1LegacyToolkit,
|
|
1184
|
+
createDisplayToolkit,
|
|
1185
|
+
TextWrapper,
|
|
1186
|
+
TextMeasurer,
|
|
1187
|
+
ScrollView,
|
|
1188
|
+
SCRIPT_RANGES,
|
|
1189
|
+
G1_SPACE_WIDTH_PX,
|
|
1190
|
+
G1_PROFILE_LEGACY,
|
|
1191
|
+
G1_PROFILE,
|
|
1192
|
+
G1_HYPHEN_WIDTH_PX,
|
|
1193
|
+
DisplayHelpers,
|
|
1194
|
+
DEFAULT_WRAP_OPTIONS
|
|
1195
|
+
};
|
|
1196
|
+
|
|
1197
|
+
//# debugId=7A9C99D0B00FC7CF64756E2164756E21
|