@hebcal/icalendar 7.0.1 → 7.0.3

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/foldLine.js CHANGED
@@ -1,6 +1,28 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  let foldSegmenter;
3
- const char74re = /.{1,74}/g;
3
+ /**
4
+ * Number of UTF-8 octets used to encode the code point starting at `i`.
5
+ * Returns 4 for a surrogate pair (which occupies two UTF-16 units).
6
+ */
7
+ function octetsAt(line, i) {
8
+ const c = line.charCodeAt(i);
9
+ if (c < 0x80) {
10
+ return 1;
11
+ }
12
+ if (c < 0x800) {
13
+ return 2;
14
+ }
15
+ // A high surrogate starts a 4-octet code point; an unpaired surrogate
16
+ // still encodes as 3 octets, but WHATWG replaces it with U+FFFD, which
17
+ // is also 3 octets, so the count stays correct either way.
18
+ if (c >= 0xd800 && c <= 0xdbff && i + 1 < line.length) {
19
+ const lo = line.charCodeAt(i + 1);
20
+ if (lo >= 0xdc00 && lo <= 0xdfff) {
21
+ return 4;
22
+ }
23
+ }
24
+ return 3;
25
+ }
4
26
  export function foldLine(line) {
5
27
  const lineBytes = Buffer.byteLength(line);
6
28
  if (lineBytes <= 74) {
@@ -9,38 +31,58 @@ export function foldLine(line) {
9
31
  if (lineBytes === line.length) {
10
32
  // All-ASCII fast path: every UTF-16 unit encodes to one UTF-8 byte
11
33
  // iff the string is pure ASCII, so we can split by JS chars.
12
- return line.match(char74re).join('\r\n ');
34
+ // A /.{1,74}/g regex looks tempting here but is wrong: `.` does not
35
+ // match \r or \n, so it silently drops those characters and returns
36
+ // null outright for a line built only from them.
37
+ const parts = [];
38
+ for (let i = 0; i < line.length; i += 74) {
39
+ parts.push(line.slice(i, i + 74));
40
+ }
41
+ return parts.join('\r\n ');
13
42
  }
14
- // Walk grapheme clusters so we never split a multi-octet UTF-8
15
- // sequence (or a combining/ZWJ/regional-indicator cluster) across
16
- // a fold boundary. Reuse a single Segmenter to avoid per-call
17
- // construction cost.
43
+ // Fold on code-point boundaries so a multi-octet UTF-8 sequence is never
44
+ // split, then pull each break back to the start of its grapheme cluster
45
+ // so combining marks, ZWJ sequences and regional-indicator pairs stay
46
+ // with their base character.
47
+ //
48
+ // Only the break positions need the Segmenter, and there are at most
49
+ // lineBytes/74 of them, so we ask it about those offsets via
50
+ // `containing()` rather than iterating every cluster in the line --
51
+ // the same fold points for a fraction of the work.
18
52
  if (!foldSegmenter) {
19
53
  foldSegmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
20
54
  }
55
+ const segments = foldSegmenter.segment(line);
21
56
  const parts = [];
57
+ const n = line.length;
22
58
  let chunkStart = 0;
23
59
  let len = 0;
24
- for (const { segment, index } of foldSegmenter.segment(line)) {
25
- const cp = segment.codePointAt(0);
26
- // Most segments are a single code point; compute UTF-8 length
27
- // arithmetically and skip the Buffer.byteLength call.
28
- const cpUnits = cp >= 0x10000 ? 2 : 1;
29
- let octets;
30
- if (segment.length === cpUnits) {
31
- octets = cp < 0x80 ? 1 : cp < 0x800 ? 2 : cp < 0x10000 ? 3 : 4;
32
- }
33
- else {
34
- octets = Buffer.byteLength(segment);
35
- }
60
+ let i = 0;
61
+ while (i < n) {
62
+ const octets = octetsAt(line, i);
63
+ const units = octets === 4 ? 2 : 1;
36
64
  if (len + octets >= 75) {
37
- parts.push(line.slice(chunkStart, index));
38
- chunkStart = index;
39
- len = octets;
65
+ let brk = i;
66
+ if (i > chunkStart) {
67
+ const cluster = segments.containing(i);
68
+ // Back up to the cluster start, unless that would consume the whole
69
+ // chunk -- a single cluster wider than the limit has to be split.
70
+ if (cluster && cluster.index > chunkStart && cluster.index < i) {
71
+ brk = cluster.index;
72
+ }
73
+ }
74
+ parts.push(line.slice(chunkStart, brk));
75
+ chunkStart = brk;
76
+ // Anything we backed up over carries into the next chunk.
77
+ len = 0;
78
+ for (let k = brk; k < i + units; k += octetsAt(line, k) === 4 ? 2 : 1) {
79
+ len += octetsAt(line, k);
80
+ }
40
81
  }
41
82
  else {
42
83
  len += octets;
43
84
  }
85
+ i += units;
44
86
  }
45
87
  parts.push(line.slice(chunkStart));
46
88
  return parts.join('\r\n ');
@@ -1 +1 @@
1
- export declare const version = "7.0.1";
1
+ export declare const version = "7.0.3";
@@ -1,2 +1,2 @@
1
1
  // DO NOT EDIT THIS AUTO-GENERATED FILE!
2
- export const version = '7.0.1';
2
+ export const version = '7.0.3';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hebcal/icalendar",
3
- "version": "7.0.1",
3
+ "version": "7.0.3",
4
4
  "author": "Michael J. Radwin (https://github.com/mjradwin)",
5
5
  "keywords": [
6
6
  "ical",
@@ -30,10 +30,10 @@
30
30
  },
31
31
  "homepage": "https://hebcal.github.io/api/icalendar/",
32
32
  "dependencies": {
33
- "@hebcal/core": "^6.8.2",
34
- "@hebcal/hdate": "^0.22.5",
33
+ "@hebcal/core": "^6.9.1",
34
+ "@hebcal/hdate": "^0.22.7",
35
35
  "@hebcal/murmurhash3": "^1.0.0",
36
- "@hebcal/rest-api": "^7.0.0"
36
+ "@hebcal/rest-api": "^7.1.3"
37
37
  },
38
38
  "scripts": {
39
39
  "build:version": "node ./version.js package.json src/pkgVersion.ts",
@@ -48,9 +48,9 @@
48
48
  },
49
49
  "license": "BSD-2-Clause",
50
50
  "devDependencies": {
51
- "@hebcal/learning": "^6.9.5",
52
- "@types/node": "26.1.1",
53
- "oxlint": "^1.76.0",
51
+ "@hebcal/learning": "^6.9.7",
52
+ "@types/node": "26.2.0",
53
+ "oxlint": "^1.77.0",
54
54
  "prettier": "^3.9.6",
55
55
  "typescript": "^7.0.2",
56
56
  "vitest": "^4.1.10"