restty 0.1.20 → 0.1.21

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 CHANGED
@@ -5,6 +5,7 @@
5
5
  [![CI](https://img.shields.io/github/actions/workflow/status/wiedymi/restty/ci.yml?branch=main&style=flat-square)](https://github.com/wiedymi/restty/actions/workflows/ci.yml)
6
6
  [![Publish](https://img.shields.io/github/actions/workflow/status/wiedymi/restty/publish.yml?style=flat-square&label=publish)](https://github.com/wiedymi/restty/actions/workflows/publish.yml)
7
7
  [![Demo](https://img.shields.io/badge/demo-restty.pages.dev-0ea5e9?style=flat-square)](https://restty.pages.dev/)
8
+
8
9
  [![GitHub](https://img.shields.io/badge/-GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/wiedymi)
9
10
  [![Twitter](https://img.shields.io/badge/-Twitter-1DA1F2?style=flat-square&logo=twitter&logoColor=white)](https://x.com/wiedymi)
10
11
  [![Email](https://img.shields.io/badge/-Email-EA4335?style=flat-square&logo=gmail&logoColor=white)](mailto:contact@wiedymi.com)
@@ -9095,6 +9095,37 @@ function isLikelyEmojiCodepoint(cp) {
9095
9095
  return true;
9096
9096
  return false;
9097
9097
  }
9098
+ function isVariationSelectorCodepoint(cp) {
9099
+ if (cp >= 65024 && cp <= 65039)
9100
+ return true;
9101
+ if (cp >= 917760 && cp <= 917999)
9102
+ return true;
9103
+ return false;
9104
+ }
9105
+ function isCombiningMarkCodepoint(cp) {
9106
+ if (cp >= 768 && cp <= 879)
9107
+ return true;
9108
+ if (cp >= 6832 && cp <= 6911)
9109
+ return true;
9110
+ if (cp >= 7616 && cp <= 7679)
9111
+ return true;
9112
+ if (cp >= 8400 && cp <= 8447)
9113
+ return true;
9114
+ if (cp >= 65056 && cp <= 65071)
9115
+ return true;
9116
+ return false;
9117
+ }
9118
+ function isCoverageIgnorableCodepoint(cp) {
9119
+ if (cp === 8204 || cp === 8205)
9120
+ return true;
9121
+ if (isVariationSelectorCodepoint(cp))
9122
+ return true;
9123
+ if (isCombiningMarkCodepoint(cp))
9124
+ return true;
9125
+ if (cp >= 917536 && cp <= 917631)
9126
+ return true;
9127
+ return false;
9128
+ }
9098
9129
  function resolvePresentationPreference(text, chars) {
9099
9130
  if (text.includes("️"))
9100
9131
  return "emoji";
@@ -9109,7 +9140,7 @@ function resolvePresentationPreference(text, chars) {
9109
9140
  }
9110
9141
  return "auto";
9111
9142
  }
9112
- function pickFontIndexForText(state, text, expectedSpan, shapeClusterWithFont) {
9143
+ function pickFontIndexForText(state, text, expectedSpan) {
9113
9144
  if (!state.fonts.length)
9114
9145
  return 0;
9115
9146
  const cacheKey = `${expectedSpan}:${text}`;
@@ -9117,6 +9148,10 @@ function pickFontIndexForText(state, text, expectedSpan, shapeClusterWithFont) {
9117
9148
  if (cached !== undefined)
9118
9149
  return cached;
9119
9150
  const chars = Array.from(text);
9151
+ const requiredChars = chars.filter((ch) => {
9152
+ const cp = ch.codePointAt(0) ?? 0;
9153
+ return !isCoverageIgnorableCodepoint(cp);
9154
+ });
9120
9155
  const firstCp = text.codePointAt(0) ?? 0;
9121
9156
  const nerdSymbol = isNerdSymbolCodepoint(firstCp);
9122
9157
  const presentation = resolvePresentationPreference(text, chars);
@@ -9128,7 +9163,7 @@ function pickFontIndexForText(state, text, expectedSpan, shapeClusterWithFont) {
9128
9163
  if (predicate && !predicate(entry))
9129
9164
  continue;
9130
9165
  let ok = true;
9131
- for (const ch of chars) {
9166
+ for (const ch of requiredChars) {
9132
9167
  if (!fontHasGlyph(entry.font, ch)) {
9133
9168
  ok = false;
9134
9169
  break;
@@ -53715,6 +53750,51 @@ function createResttyApp(options) {
53715
53750
  return true;
53716
53751
  return false;
53717
53752
  }
53753
+ function isVariationSelectorCodepoint2(cp) {
53754
+ if (cp >= 65024 && cp <= 65039)
53755
+ return true;
53756
+ if (cp >= 917760 && cp <= 917999)
53757
+ return true;
53758
+ return false;
53759
+ }
53760
+ function isCombiningMarkCodepoint2(cp) {
53761
+ if (cp >= 768 && cp <= 879)
53762
+ return true;
53763
+ if (cp >= 6832 && cp <= 6911)
53764
+ return true;
53765
+ if (cp >= 7616 && cp <= 7679)
53766
+ return true;
53767
+ if (cp >= 8400 && cp <= 8447)
53768
+ return true;
53769
+ if (cp >= 65056 && cp <= 65071)
53770
+ return true;
53771
+ return false;
53772
+ }
53773
+ function isEmojiModifierCodepoint(cp) {
53774
+ return cp >= 127995 && cp <= 127999;
53775
+ }
53776
+ function isCoverageIgnorableCodepoint2(cp) {
53777
+ if (cp === 8204 || cp === 8205)
53778
+ return true;
53779
+ if (isVariationSelectorCodepoint2(cp))
53780
+ return true;
53781
+ if (isCombiningMarkCodepoint2(cp))
53782
+ return true;
53783
+ if (cp >= 917536 && cp <= 917631)
53784
+ return true;
53785
+ return false;
53786
+ }
53787
+ function shouldMergeTrailingClusterCodepoint(cp) {
53788
+ if (cp === 8204 || cp === 8205)
53789
+ return true;
53790
+ if (isVariationSelectorCodepoint2(cp))
53791
+ return true;
53792
+ if (isCombiningMarkCodepoint2(cp))
53793
+ return true;
53794
+ if (isEmojiModifierCodepoint(cp))
53795
+ return true;
53796
+ return false;
53797
+ }
53718
53798
  function resolvePresentationPreference2(text, chars) {
53719
53799
  if (text.includes("️"))
53720
53800
  return "emoji";
@@ -53737,6 +53817,10 @@ function createResttyApp(options) {
53737
53817
  if (cached !== undefined)
53738
53818
  return cached;
53739
53819
  const chars = Array.from(text);
53820
+ const requiredChars = chars.filter((ch) => {
53821
+ const cp = ch.codePointAt(0) ?? 0;
53822
+ return !isCoverageIgnorableCodepoint2(cp);
53823
+ });
53740
53824
  const firstCp = text.codePointAt(0) ?? 0;
53741
53825
  const nerdSymbol = isNerdSymbolCodepoint(firstCp);
53742
53826
  const presentation = resolvePresentationPreference2(text, chars);
@@ -53751,7 +53835,7 @@ function createResttyApp(options) {
53751
53835
  (entry) => hasItalicHint(entry) && !hasBoldHint(entry),
53752
53836
  (entry) => hasItalicHint(entry)
53753
53837
  ] : [];
53754
- const pickFirstMatch = (predicate) => {
53838
+ const pickFirstMatch = (predicate, allowSequenceShapingFallback = false) => {
53755
53839
  for (let i3 = 0;i3 < fontState.fonts.length; i3 += 1) {
53756
53840
  const entry = fontState.fonts[i3];
53757
53841
  if (!entry?.font)
@@ -53759,18 +53843,22 @@ function createResttyApp(options) {
53759
53843
  if (predicate && !predicate(entry))
53760
53844
  continue;
53761
53845
  let ok = true;
53762
- for (const ch of chars) {
53846
+ for (const ch of requiredChars) {
53763
53847
  if (!fontHasGlyph2(entry.font, ch)) {
53764
53848
  ok = false;
53765
53849
  break;
53766
53850
  }
53767
53851
  }
53852
+ if (!ok && allowSequenceShapingFallback) {
53853
+ const shaped = shapeClusterWithFont(entry, text);
53854
+ ok = shaped.glyphs.some((glyph) => (glyph.glyphId ?? 0) !== 0);
53855
+ }
53768
53856
  if (ok)
53769
53857
  return i3;
53770
53858
  }
53771
53859
  return -1;
53772
53860
  };
53773
- const pickWithStyle = (predicate) => {
53861
+ const pickWithStyle = (predicate, allowSequenceShapingFallback = false) => {
53774
53862
  if (styleHintsEnabled) {
53775
53863
  for (let i3 = 0;i3 < stylePredicates.length; i3 += 1) {
53776
53864
  const stylePredicate = stylePredicates[i3];
@@ -53778,12 +53866,12 @@ function createResttyApp(options) {
53778
53866
  if (!stylePredicate(entry))
53779
53867
  return false;
53780
53868
  return predicate ? !!predicate(entry) : true;
53781
- });
53869
+ }, allowSequenceShapingFallback);
53782
53870
  if (styledIndex >= 0)
53783
53871
  return styledIndex;
53784
53872
  }
53785
53873
  }
53786
- return pickFirstMatch(predicate);
53874
+ return pickFirstMatch(predicate, allowSequenceShapingFallback);
53787
53875
  };
53788
53876
  const tryIndex = (index) => {
53789
53877
  if (index < 0)
@@ -53798,7 +53886,7 @@ function createResttyApp(options) {
53798
53886
  return result;
53799
53887
  }
53800
53888
  if (presentation === "emoji") {
53801
- const emojiIndex = pickFirstMatch((entry) => isColorEmojiFont(entry));
53889
+ const emojiIndex = pickFirstMatch((entry) => isColorEmojiFont(entry), true);
53802
53890
  const result = tryIndex(emojiIndex);
53803
53891
  if (result !== null)
53804
53892
  return result;
@@ -54508,10 +54596,13 @@ function createResttyApp(options) {
54508
54596
  }
54509
54597
  let nextSeqIdx = idx + baseSpan;
54510
54598
  let guard = 0;
54511
- while ((text.codePointAt(text.length - 1) ?? 0) === 8205 && nextSeqIdx < rowEnd && guard < 8) {
54599
+ while (nextSeqIdx < rowEnd && guard < 12) {
54512
54600
  const next = readCellCluster(nextSeqIdx);
54513
54601
  if (!next || !next.cp || isSpaceCp(next.cp))
54514
54602
  break;
54603
+ const shouldMerge = text.endsWith("‍") || shouldMergeTrailingClusterCodepoint(next.cp);
54604
+ if (!shouldMerge)
54605
+ break;
54515
54606
  text += next.text;
54516
54607
  baseSpan += next.span;
54517
54608
  mergedEmojiSkip[nextSeqIdx] = 1;
@@ -55374,10 +55465,13 @@ function createResttyApp(options) {
55374
55465
  }
55375
55466
  let nextSeqIdx = idx + baseSpan;
55376
55467
  let guard = 0;
55377
- while ((text.codePointAt(text.length - 1) ?? 0) === 8205 && nextSeqIdx < rowEnd && guard < 8) {
55468
+ while (nextSeqIdx < rowEnd && guard < 12) {
55378
55469
  const next = readCellCluster(nextSeqIdx);
55379
55470
  if (!next || !next.cp || isSpaceCp(next.cp))
55380
55471
  break;
55472
+ const shouldMerge = text.endsWith("‍") || shouldMergeTrailingClusterCodepoint(next.cp);
55473
+ if (!shouldMerge)
55474
+ break;
55381
55475
  text += next.text;
55382
55476
  baseSpan += next.span;
55383
55477
  mergedEmojiSkip[nextSeqIdx] = 1;
@@ -27,7 +27,7 @@ export declare function glyphWidthUnits(entry: FontEntry, glyphId: number | unde
27
27
  * Select the best font index from the manager's font list for rendering the
28
28
  * given text cluster, searching in fallback order similar to Ghostty.
29
29
  */
30
- export declare function pickFontIndexForText(state: FontManagerState, text: string, expectedSpan: number, shapeClusterWithFont: (entry: FontEntry, text: string) => ShapedCluster): number;
30
+ export declare function pickFontIndexForText(state: FontManagerState, text: string, expectedSpan: number): number;
31
31
  /** Fetch a font file from a URL and return its ArrayBuffer, or null on failure. */
32
32
  export declare function tryFetchFontBuffer(url: string): Promise<ArrayBuffer | null>;
33
33
  /** Query locally installed fonts via the Local Font Access API and return the first match, or null. */
package/dist/internal.js CHANGED
@@ -89,7 +89,7 @@ import {
89
89
  updateComposition,
90
90
  updateGridState,
91
91
  updateImePosition
92
- } from "./chunk-ef12eja6.js";
92
+ } from "./chunk-ym658zhj.js";
93
93
  // src/selection/selection.ts
94
94
  function createSelectionState() {
95
95
  return {
package/dist/restty.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  isBuiltinThemeName,
8
8
  listBuiltinThemeNames,
9
9
  parseGhosttyTheme
10
- } from "./chunk-ef12eja6.js";
10
+ } from "./chunk-ym658zhj.js";
11
11
  export {
12
12
  parseGhosttyTheme,
13
13
  listBuiltinThemeNames,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "restty",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "description": "Browser terminal rendering library powered by WASM, WebGPU/WebGL2, and TypeScript text shaping.",
5
5
  "keywords": [
6
6
  "terminal",