@jsenv/terminal-recorder 1.5.8 → 1.5.9

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.
@@ -1,4 +1,99 @@
1
- import { stripAnsi, ansiRegex, stringWidth, __jsenv_default_import__ } from "/jsenv_terminal_recorder_node_modules.js";
1
+ import { emojiRegex, eastAsianWidth, stripAnsi, ansiRegex, __jsenv_default_import__ } from "./jsenv_terminal_recorder_node_modules.js";
2
+
3
+ const createMeasureTextWidth = ({ stripAnsi }) => {
4
+ const segmenter = new Intl.Segmenter();
5
+ const defaultIgnorableCodePointRegex = /^\p{Default_Ignorable_Code_Point}$/u;
6
+
7
+ const measureTextWidth = (
8
+ string,
9
+ {
10
+ ambiguousIsNarrow = true,
11
+ countAnsiEscapeCodes = false,
12
+ skipEmojis = false,
13
+ } = {},
14
+ ) => {
15
+ if (typeof string !== "string" || string.length === 0) {
16
+ return 0;
17
+ }
18
+
19
+ if (!countAnsiEscapeCodes) {
20
+ string = stripAnsi(string);
21
+ }
22
+
23
+ if (string.length === 0) {
24
+ return 0;
25
+ }
26
+
27
+ let width = 0;
28
+ const eastAsianWidthOptions = { ambiguousAsWide: !ambiguousIsNarrow };
29
+
30
+ for (const { segment: character } of segmenter.segment(string)) {
31
+ const codePoint = character.codePointAt(0);
32
+
33
+ // Ignore control characters
34
+ if (codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f)) {
35
+ continue;
36
+ }
37
+
38
+ // Ignore zero-width characters
39
+ if (
40
+ (codePoint >= 0x20_0b && codePoint <= 0x20_0f) || // Zero-width space, non-joiner, joiner, left-to-right mark, right-to-left mark
41
+ codePoint === 0xfe_ff // Zero-width no-break space
42
+ ) {
43
+ continue;
44
+ }
45
+
46
+ // Ignore combining characters
47
+ if (
48
+ (codePoint >= 0x3_00 && codePoint <= 0x3_6f) || // Combining diacritical marks
49
+ (codePoint >= 0x1a_b0 && codePoint <= 0x1a_ff) || // Combining diacritical marks extended
50
+ (codePoint >= 0x1d_c0 && codePoint <= 0x1d_ff) || // Combining diacritical marks supplement
51
+ (codePoint >= 0x20_d0 && codePoint <= 0x20_ff) || // Combining diacritical marks for symbols
52
+ (codePoint >= 0xfe_20 && codePoint <= 0xfe_2f) // Combining half marks
53
+ ) {
54
+ continue;
55
+ }
56
+
57
+ // Ignore surrogate pairs
58
+ if (codePoint >= 0xd8_00 && codePoint <= 0xdf_ff) {
59
+ continue;
60
+ }
61
+
62
+ // Ignore variation selectors
63
+ if (codePoint >= 0xfe_00 && codePoint <= 0xfe_0f) {
64
+ continue;
65
+ }
66
+
67
+ // This covers some of the above cases, but we still keep them for performance reasons.
68
+ if (defaultIgnorableCodePointRegex.test(character)) {
69
+ continue;
70
+ }
71
+
72
+ if (!skipEmojis && emojiRegex().test(character)) {
73
+ if (process.env.CAPTURING_SIDE_EFFECTS) {
74
+ if (character === "✔️") {
75
+ width += 2;
76
+ continue;
77
+ }
78
+ }
79
+ width += measureTextWidth(character, {
80
+ skipEmojis: true,
81
+ countAnsiEscapeCodes: true, // to skip call to stripAnsi
82
+ });
83
+ continue;
84
+ }
85
+
86
+ width += eastAsianWidth(codePoint, eastAsianWidthOptions);
87
+ }
88
+
89
+ return width;
90
+ };
91
+ return measureTextWidth;
92
+ };
93
+
94
+ const measureTextWidth = createMeasureTextWidth({
95
+ stripAnsi,
96
+ });
2
97
 
3
98
  // inspired by https://github.com/F1LT3R/parse-ansi/blob/master/index.js
4
99
 
@@ -10,12 +105,12 @@ const parseAnsi = (ansi) => {
10
105
  const lines = plainText.split("\n");
11
106
  const rows = lines.length;
12
107
  let columns = 0;
13
- lines.forEach((line) => {
108
+ for (const line of lines) {
14
109
  const len = line.length;
15
110
  if (len > columns) {
16
111
  columns = len;
17
112
  }
18
- });
113
+ }
19
114
 
20
115
  const result = {
21
116
  raw: ansi,
@@ -156,7 +251,7 @@ const parseAnsi = (ansi) => {
156
251
  let nAnsi = 0;
157
252
  let nPlain = 0;
158
253
 
159
- const bundle = (type, value) => {
254
+ const bundle = (type, value, { width = 0, height = 0 } = {}) => {
160
255
  const chunk = {
161
256
  type,
162
257
  value,
@@ -165,9 +260,10 @@ const parseAnsi = (ansi) => {
165
260
  y,
166
261
  n: nPlain,
167
262
  raw: nAnsi,
263
+ width,
264
+ height,
168
265
  },
169
266
  };
170
-
171
267
  if (type === "text" || type === "ansi") {
172
268
  const style = {};
173
269
 
@@ -217,7 +313,7 @@ const parseAnsi = (ansi) => {
217
313
  for (const word of words) {
218
314
  // Newline character
219
315
  if (word === "\n") {
220
- const chunk = bundle("newline", "\n");
316
+ const chunk = bundle("newline", "\n", { height: 1 });
221
317
  result.chunks.push(chunk);
222
318
  x = 0;
223
319
  y += 1;
@@ -227,9 +323,9 @@ const parseAnsi = (ansi) => {
227
323
  }
228
324
  // Text characters
229
325
  if (delimiters.includes(word) === false) {
230
- const chunk = bundle("text", word);
326
+ const width = measureTextWidth(word);
327
+ const chunk = bundle("text", word, { width });
231
328
  result.chunks.push(chunk);
232
- const width = stringWidth(word);
233
329
  x += width;
234
330
  nAnsi += width;
235
331
  nPlain += width;
@@ -747,11 +843,24 @@ const renderTerminalSvg = (
747
843
  continue;
748
844
  }
749
845
  const { position } = chunk;
750
- const x =
751
- offsetLeft + (position.x + leadingWhitespaceWidth(value)) * font.width;
752
- const y = offsetTop + position.y + font.lineHeight * position.y;
753
- const w = font.width * value.length;
754
846
  const attrs = {};
847
+
848
+ // Some SVG Implementations drop whitespaces
849
+ // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xml:space
850
+ // and https://codepen.io/dmail/pen/wvLvbbP
851
+ if (
852
+ // in the following case we need to preserve whitespaces:
853
+ // - one or more leading whitespace
854
+ // - one or more trailing whitespace
855
+ // - 2 or more whitespace in the middle of the string
856
+ /^\s+|\s\s|\s+$/.test(value)
857
+ ) {
858
+ attrs.style = "white-space:pre";
859
+ }
860
+ const x = offsetLeft + position.x * font.width;
861
+ const y = offsetTop + position.y + font.lineHeight * position.y;
862
+ const w = font.width * position.width;
863
+
755
864
  if (style.bold) {
756
865
  attrs["font-weight"] = "bold";
757
866
  }
@@ -823,15 +932,4 @@ const renderTerminalSvg = (
823
932
  return svgString;
824
933
  };
825
934
 
826
- // Some SVG Implementations drop whitespaces
827
- // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xml:space
828
- // and https://codepen.io/dmail/pen/wvLvbbP
829
- const leadingWhitespaceWidth = (text) => {
830
- if (text.trim() === "") {
831
- return 0;
832
- }
833
- const leadingSpace = text.match(/^\s*/g);
834
- return leadingSpace[0].length;
835
- };
836
-
837
935
  export { parseAnsi, renderTerminalSvg };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/terminal-recorder",
3
- "version": "1.5.8",
3
+ "version": "1.5.9",
4
4
  "description": "Record terminal output as .svg, .gif, .webm, .mp4",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -37,13 +37,12 @@
37
37
  "prepublishOnly": "npm run build"
38
38
  },
39
39
  "dependencies": {
40
- "@jsenv/humanize": "1.3.1",
41
- "@jsenv/server": "16.0.5",
40
+ "@jsenv/humanize": "1.4.0",
41
+ "@jsenv/server": "16.0.6",
42
42
  "@playwright/browser-chromium": "1.51.0",
43
43
  "ansi-regex": "6.1.0",
44
44
  "he": "1.2.0",
45
45
  "playwright": "1.51.0",
46
- "string-width": "5.1.2",
47
46
  "strip-ansi": "7.1.0",
48
47
  "eastasianwidth": "0.3.0",
49
48
  "webm-to-mp4": "1.0.0"
@@ -1,7 +1,7 @@
1
1
  // inspired by https://github.com/F1LT3R/parse-ansi/blob/master/index.js
2
2
 
3
+ import { measureTextWidth } from "@jsenv/terminal-text-size";
3
4
  import ansiRegex from "ansi-regex";
4
- import stringWidth from "string-width";
5
5
  import stripAnsi from "strip-ansi";
6
6
 
7
7
  export const parseAnsi = (ansi) => {
@@ -11,12 +11,12 @@ export const parseAnsi = (ansi) => {
11
11
  const lines = plainText.split("\n");
12
12
  const rows = lines.length;
13
13
  let columns = 0;
14
- lines.forEach((line) => {
14
+ for (const line of lines) {
15
15
  const len = line.length;
16
16
  if (len > columns) {
17
17
  columns = len;
18
18
  }
19
- });
19
+ }
20
20
 
21
21
  const result = {
22
22
  raw: ansi,
@@ -158,7 +158,7 @@ export const parseAnsi = (ansi) => {
158
158
  let nAnsi = 0;
159
159
  let nPlain = 0;
160
160
 
161
- const bundle = (type, value) => {
161
+ const bundle = (type, value, { width = 0, height = 0 } = {}) => {
162
162
  const chunk = {
163
163
  type,
164
164
  value,
@@ -167,9 +167,10 @@ export const parseAnsi = (ansi) => {
167
167
  y,
168
168
  n: nPlain,
169
169
  raw: nAnsi,
170
+ width,
171
+ height,
170
172
  },
171
173
  };
172
-
173
174
  if (type === "text" || type === "ansi") {
174
175
  const style = {};
175
176
 
@@ -219,7 +220,7 @@ export const parseAnsi = (ansi) => {
219
220
  for (const word of words) {
220
221
  // Newline character
221
222
  if (word === "\n") {
222
- const chunk = bundle("newline", "\n");
223
+ const chunk = bundle("newline", "\n", { height: 1 });
223
224
  result.chunks.push(chunk);
224
225
  x = 0;
225
226
  y += 1;
@@ -229,9 +230,9 @@ export const parseAnsi = (ansi) => {
229
230
  }
230
231
  // Text characters
231
232
  if (delimiters.includes(word) === false) {
232
- const chunk = bundle("text", word);
233
+ const width = measureTextWidth(word);
234
+ const chunk = bundle("text", word, { width });
233
235
  result.chunks.push(chunk);
234
- const width = stringWidth(word);
235
236
  x += width;
236
237
  nAnsi += width;
237
238
  nPlain += width;
@@ -221,11 +221,24 @@ export const renderTerminalSvg = (
221
221
  continue;
222
222
  }
223
223
  const { position } = chunk;
224
- const x =
225
- offsetLeft + (position.x + leadingWhitespaceWidth(value)) * font.width;
226
- const y = offsetTop + position.y + font.lineHeight * position.y;
227
- const w = font.width * value.length;
228
224
  const attrs = {};
225
+
226
+ // Some SVG Implementations drop whitespaces
227
+ // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xml:space
228
+ // and https://codepen.io/dmail/pen/wvLvbbP
229
+ if (
230
+ // in the following case we need to preserve whitespaces:
231
+ // - one or more leading whitespace
232
+ // - one or more trailing whitespace
233
+ // - 2 or more whitespace in the middle of the string
234
+ /^\s+|\s\s|\s+$/.test(value)
235
+ ) {
236
+ attrs.style = "white-space:pre";
237
+ }
238
+ const x = offsetLeft + position.x * font.width;
239
+ const y = offsetTop + position.y + font.lineHeight * position.y;
240
+ const w = font.width * position.width;
241
+
229
242
  if (style.bold) {
230
243
  attrs["font-weight"] = "bold";
231
244
  }
@@ -297,14 +310,3 @@ export const renderTerminalSvg = (
297
310
  const svgString = svg.renderAsString();
298
311
  return svgString;
299
312
  };
300
-
301
- // Some SVG Implementations drop whitespaces
302
- // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xml:space
303
- // and https://codepen.io/dmail/pen/wvLvbbP
304
- const leadingWhitespaceWidth = (text) => {
305
- if (text.trim() === "") {
306
- return 0;
307
- }
308
- const leadingSpace = text.match(/^\s*/g);
309
- return leadingSpace[0].length;
310
- };
File without changes