@react-x11/components 0.10.0 → 0.11.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.
Files changed (90) hide show
  1. package/dist/code/index.d.ts.map +1 -1
  2. package/dist/code/index.js +25 -24
  3. package/dist/code/index.js.map +1 -1
  4. package/dist/code-language/index.d.ts +1 -1
  5. package/dist/code-language/index.d.ts.map +1 -1
  6. package/dist/code-language/index.js +1 -1
  7. package/dist/code-language/index.js.map +1 -1
  8. package/dist/code-language/runs.d.ts +31 -0
  9. package/dist/code-language/runs.d.ts.map +1 -1
  10. package/dist/code-language/runs.js +105 -0
  11. package/dist/code-language/runs.js.map +1 -1
  12. package/dist/codeblock/index.d.ts +13 -0
  13. package/dist/codeblock/index.d.ts.map +1 -1
  14. package/dist/codeblock/index.js +37 -1
  15. package/dist/codeblock/index.js.map +1 -1
  16. package/dist/html/css/cascade.d.ts +32 -10
  17. package/dist/html/css/cascade.d.ts.map +1 -1
  18. package/dist/html/css/cascade.js +72 -27
  19. package/dist/html/css/cascade.js.map +1 -1
  20. package/dist/html/css/style.d.ts +7 -4
  21. package/dist/html/css/style.d.ts.map +1 -1
  22. package/dist/html/css/style.js +37 -6
  23. package/dist/html/css/style.js.map +1 -1
  24. package/dist/html/css/ua.js +8 -5
  25. package/dist/html/css/ua.js.map +1 -1
  26. package/dist/html/layout/block.d.ts.map +1 -1
  27. package/dist/html/layout/block.js +127 -9
  28. package/dist/html/layout/block.js.map +1 -1
  29. package/dist/html/layout/boxes.d.ts +27 -0
  30. package/dist/html/layout/boxes.d.ts.map +1 -1
  31. package/dist/html/layout/boxes.js +30 -2
  32. package/dist/html/layout/boxes.js.map +1 -1
  33. package/dist/html/layout/inline.d.ts +3 -0
  34. package/dist/html/layout/inline.d.ts.map +1 -1
  35. package/dist/html/layout/inline.js +236 -14
  36. package/dist/html/layout/inline.js.map +1 -1
  37. package/dist/html/node.d.ts.map +1 -1
  38. package/dist/html/node.js +118 -57
  39. package/dist/html/node.js.map +1 -1
  40. package/dist/html/paint.d.ts.map +1 -1
  41. package/dist/html/paint.js +203 -37
  42. package/dist/html/paint.js.map +1 -1
  43. package/dist/html/svg.d.ts.map +1 -1
  44. package/dist/html/svg.js +27 -20
  45. package/dist/html/svg.js.map +1 -1
  46. package/dist/internal/codelines.d.ts +42 -0
  47. package/dist/internal/codelines.d.ts.map +1 -0
  48. package/dist/internal/codelines.js +170 -0
  49. package/dist/internal/codelines.js.map +1 -0
  50. package/dist/markdown/index.d.ts.map +1 -1
  51. package/dist/markdown/index.js +98 -26
  52. package/dist/markdown/index.js.map +1 -1
  53. package/dist/rich-text-editor/index.d.ts.map +1 -1
  54. package/dist/rich-text-editor/index.js +3 -8
  55. package/dist/rich-text-editor/index.js.map +1 -1
  56. package/dist/rich-text-editor/markdown.d.ts.map +1 -1
  57. package/dist/rich-text-editor/markdown.js +30 -1
  58. package/dist/rich-text-editor/markdown.js.map +1 -1
  59. package/dist/rich-text-editor/replace.d.ts +13 -0
  60. package/dist/rich-text-editor/replace.d.ts.map +1 -0
  61. package/dist/rich-text-editor/replace.js +64 -0
  62. package/dist/rich-text-editor/replace.js.map +1 -0
  63. package/dist/terminal-output/index.d.ts.map +1 -1
  64. package/dist/terminal-output/index.js +100 -25
  65. package/dist/terminal-output/index.js.map +1 -1
  66. package/dist/terminal-output/runs.d.ts +4 -1
  67. package/dist/terminal-output/runs.d.ts.map +1 -1
  68. package/dist/terminal-output/runs.js +5 -1
  69. package/dist/terminal-output/runs.js.map +1 -1
  70. package/package.json +1 -1
  71. package/src/code/index.ts +34 -32
  72. package/src/code-language/index.ts +1 -1
  73. package/src/code-language/runs.ts +111 -1
  74. package/src/codeblock/index.ts +48 -1
  75. package/src/html/css/cascade.ts +117 -39
  76. package/src/html/css/style.ts +43 -15
  77. package/src/html/css/ua.ts +8 -5
  78. package/src/html/layout/block.ts +134 -8
  79. package/src/html/layout/boxes.ts +43 -2
  80. package/src/html/layout/inline.ts +257 -13
  81. package/src/html/node.ts +145 -79
  82. package/src/html/paint.ts +236 -45
  83. package/src/html/svg.ts +30 -19
  84. package/src/internal/codelines.ts +200 -0
  85. package/src/markdown/index.ts +145 -26
  86. package/src/rich-text-editor/index.ts +3 -11
  87. package/src/rich-text-editor/markdown.ts +36 -1
  88. package/src/rich-text-editor/replace.ts +85 -0
  89. package/src/terminal-output/index.ts +127 -34
  90. package/src/terminal-output/runs.ts +16 -2
@@ -32,7 +32,7 @@
32
32
  import { codePointAtOffset, codeUnitOffsets } from '../../internal/text.js';
33
33
  import { CONTAIN_LAYOUT, INLINE_BEFORE_ABSOLUTE, scrolls, } from '../css/style.js';
34
34
  import { inkColor, isTransparent, resolve } from '../css/values.js';
35
- import { BOX_RAISES, LINE_BOX_RAISES, isOffset, SHADOWED_TEXT, SHIFTED_LINES, TEXT_RAISES, } from './boxes.js';
35
+ import { BOX_RAISES, LINE_BOX_RAISES, isOffset, letteredAfter, SHADOWED_TEXT, SHIFTED_LINES, TEXT_RAISES, transformInPlace, } from './boxes.js';
36
36
  import { tableGrid } from './grid.js';
37
37
  /**
38
38
  * The text behind a layout, for the one caller that needs it: a selection
@@ -246,6 +246,8 @@ function linesOf(block, options, items, floatCount) {
246
246
  CLIPPED_TEXT.add(block);
247
247
  fonts = recording(fonts);
248
248
  }
249
+ // small capitals the face does not have, made of its capitals
250
+ synthesizeSmallCaps(items, fonts);
249
251
  if (wraps(block.style))
250
252
  holdNoWrap(items, block);
251
253
  // one walk for what few paragraphs have: text that casts a shadow, and a
@@ -353,23 +355,33 @@ function linesOf(block, options, items, floatCount) {
353
355
  const spaced = hasEdges && !hasAtomics && !hasOffset && spacersHold(style, items);
354
356
  // a first line in fonts of its own is found a piece at a time, in them
355
357
  const restyled = options.firstLineStyle !== undefined;
358
+ // and where one layout makes a line shorter than the strut, its text all
359
+ // smaller than the block's, the lines are made a piece at a time too,
360
+ // where every line holds it (`shortOfStrut`)
361
+ const strut = fonts ? strutOf(fonts, style) : null;
362
+ // and the first line holds what it is handed besides: a marker's room
363
+ const firstStrut = withRoom(strut, options.firstStrut);
364
+ let strutted = false;
356
365
  // Inline boxes' edges as spacers in one layout, where the engine left
357
366
  // every edge on the line of the content it belongs to (`layoutSpaced`);
358
367
  // the lines a piece at a time, below, where it did not.
359
368
  if (spaced && !floated && !indent && !alignedApart && !restyled) {
360
369
  const laid = layoutSpaced(items, base, style, options.width, lineHeightMul, align, fonts);
361
- if (laid)
370
+ if (laid && !shortOfStrut(laid.lines, strut, firstStrut))
362
371
  return laid;
372
+ if (laid)
373
+ strutted = true;
363
374
  }
364
375
  // The text-only, float-free, unindented case: one call, every line — or
365
376
  // one call per *chunk*, when the text is long and carries hard breaks.
366
- if (!hasAtomics &&
377
+ fast: if (!hasAtomics &&
367
378
  !hasEdges &&
368
379
  !raised &&
369
380
  !floated &&
370
381
  !indent &&
371
382
  !alignedApart &&
372
- !restyled) {
383
+ !restyled &&
384
+ !strutted) {
373
385
  // no atomics and no edges, so everything is text
374
386
  const textItems = items;
375
387
  let total = 0;
@@ -395,7 +407,11 @@ function linesOf(block, options, items, floatCount) {
395
407
  if (perLine)
396
408
  cut = null;
397
409
  if (total > CHUNK_TRIGGER_CHARS && hasNewline && !hasControls(items)) {
398
- return layoutChunked(textItems, base, style, options.width, lineHeightMul, align, fonts);
410
+ const chunked = layoutChunked(textItems, base, style, options.width, lineHeightMul, align, fonts);
411
+ if (!shortOfStrut(chunked.lines, strut, firstStrut))
412
+ return chunked;
413
+ strutted = true;
414
+ break fast;
399
415
  }
400
416
  let runs = [];
401
417
  let spans = new SpanMap();
@@ -464,6 +480,11 @@ function linesOf(block, options, items, floatCount) {
464
480
  : balanced
465
481
  ? unwrappedPlacer(style, options.width, false)
466
482
  : null);
483
+ // a clamp and a cut are the one layout's to make
484
+ if (!clamp && !cut && shortOfStrut(lines, strut, firstStrut)) {
485
+ strutted = true;
486
+ break fast;
487
+ }
467
488
  return clampCut
468
489
  ? { lines, height: layout.height, width: widest, cut: true }
469
490
  : { lines, height: layout.height, width: widest };
@@ -509,7 +530,6 @@ function linesOf(block, options, items, floatCount) {
509
530
  }
510
531
  deferred = [];
511
532
  };
512
- const strut = fonts ? strutOf(fonts, style) : null;
513
533
  // How tall a line is taken to be before it is made, for its room beside
514
534
  // the floats: the paragraph's strut, a line of its text. An item taller
515
535
  // asks over its own height, and the line made over its whole (`close`).
@@ -544,7 +564,9 @@ function linesOf(block, options, items, floatCount) {
544
564
  const line = finishLine(open, y,
545
565
  // the room beside the floats over the whole line box, which an
546
566
  // inline-block can make taller than its text (CSS 2.1 9.5)
547
- (height) => bandAt(options, y, Math.max(height, guess)), last || !wraps(style) ? lastShift : restShift, rtl, onFirst ? lineOne.strut : strut, onFirst ? lineOne.lifts : lifts, (last || !wraps(style) ? justify.last : justify.rest)
567
+ (height) => bandAt(options, y, Math.max(height, guess)), last || !wraps(style) ? lastShift : restShift, rtl, lines.length
568
+ ? strut
569
+ : withRoom(onFirst ? lineOne.strut : strut, options.firstStrut), onFirst ? lineOne.lifts : lifts, (last || !wraps(style) ? justify.last : justify.rest)
548
570
  ? onFirst
549
571
  ? lineOne.justify
550
572
  : lineJustify
@@ -702,6 +724,7 @@ function linesOf(block, options, items, floatCount) {
702
724
  // `text-align-last` sets it apart)
703
725
  const tailIsPlain = !alignedApart &&
704
726
  !onFirst &&
727
+ !strutted &&
705
728
  segment.nextIndex >= items.length &&
706
729
  open.x === 0 &&
707
730
  !open.atomics.length &&
@@ -722,6 +745,7 @@ function linesOf(block, options, items, floatCount) {
722
745
  // the lines past the floats are justified as the lines beside them
723
746
  // were (`finishLine`), in the room they have
724
747
  layout = justifiedLayout(fonts, segment.runs, base, tailOptions, layout, Math.max(1, available), justify).layout;
748
+ const tail = [];
725
749
  for (let i = 0; i < layout.lines.length; i += 1) {
726
750
  const natural = layout.lines[i];
727
751
  const text = {
@@ -735,7 +759,7 @@ function linesOf(block, options, items, floatCount) {
735
759
  spans: segment.spans,
736
760
  };
737
761
  segment.spans.giveGaps(text, natural.start, natural.end);
738
- lines.push({
762
+ tail.push({
739
763
  x: band.left + natural.x,
740
764
  y: y + natural.y,
741
765
  width: natural.width,
@@ -746,12 +770,19 @@ function linesOf(block, options, items, floatCount) {
746
770
  textEnd: text.textEnd,
747
771
  atomics: [],
748
772
  });
749
- widest = Math.max(widest, natural.width);
750
773
  }
751
- y += layout.height;
752
- index = segment.nextIndex;
753
- offset = 0;
754
- continue;
774
+ // or a line at a time, where one of them is shorter than the strut
775
+ if (!shortOfStrut(tail, strut, lines.length ? null : firstStrut)) {
776
+ for (const line of tail) {
777
+ lines.push(line);
778
+ widest = Math.max(widest, line.width);
779
+ }
780
+ y += layout.height;
781
+ index = segment.nextIndex;
782
+ offset = 0;
783
+ continue;
784
+ }
785
+ strutted = true;
755
786
  }
756
787
  // One line: `maxLines: 1` cuts the layout at the first break and its
757
788
  // `truncated` flag answers "did the segment wrap" — so a line costs one
@@ -2365,7 +2396,12 @@ function inlineEdges(box, width, fonts) {
2365
2396
  * a pointer move that gives a link a background (`HtmlViewNode`).
2366
2397
  */
2367
2398
  export function inlineDecoration(fonts, box, s) {
2399
+ // a background is a colour, a gradient or an image: a span a gradient
2400
+ // underlines, a highlighter's, was painted only where it had a border
2368
2401
  const decorated = !isTransparent(s.backgroundColor) ||
2402
+ s.backgroundGradient !== null ||
2403
+ s.backgroundImage !== null ||
2404
+ s.backgroundImages !== null ||
2369
2405
  box.borderTop + box.borderRight + box.borderBottom + box.borderLeft > 0 ||
2370
2406
  s.outlineStyle !== 'none';
2371
2407
  return !decorated ? null : fonts ? faceExtent(fonts, s) : NO_EXTENT;
@@ -2434,10 +2470,27 @@ function firstLineSetting(block, items, how, fonts, strut, justify) {
2434
2470
  const of = (box) => box === block || !box.parent
2435
2471
  ? style
2436
2472
  : styleOnFirstLine(box, of(box.parent), how.styler);
2473
+ // and in its `text-transform` where the line has one of its own, which
2474
+ // the boxes' text was not made with: a word carried from one run into
2475
+ // the next, as the box builder carries it (`letteredAfter`)
2476
+ let lettered = false;
2437
2477
  const restyled = items.map((item) => {
2478
+ if (item.kind === 'atomic')
2479
+ lettered = false;
2438
2480
  if (item.kind !== 'text')
2439
2481
  return item;
2440
- const run = restyledRun(item.run, item.box.style, of(item.box));
2482
+ const own = item.box.style;
2483
+ const on = of(item.box);
2484
+ let run = restyledRun(item.run, own, on);
2485
+ const transform = on.textTransform;
2486
+ if (!item.control &&
2487
+ transform !== own.textTransform &&
2488
+ transform !== 'none') {
2489
+ const text = transformInPlace(run.text, transform, lettered);
2490
+ if (text !== run.text)
2491
+ run = { ...run, text };
2492
+ }
2493
+ lettered = letteredAfter(run.text, lettered);
2441
2494
  return run === item.run ? item : { ...item, run };
2442
2495
  });
2443
2496
  const lineHeight = lineHeightMultiplier(fonts, style);
@@ -2761,6 +2814,175 @@ function heldText(box) {
2761
2814
  return held;
2762
2815
  }
2763
2816
  const HELD = new WeakMap();
2817
+ /**
2818
+ * Whether a line one layout made is shorter about its baseline than the
2819
+ * block's strut. Every line box starts with the strut, the block's font at
2820
+ * its line height (CSS 2.1 10.8.1), and a layout has none: it makes a line
2821
+ * as tall as the text on it, at the block's line height as a multiple of
2822
+ * each face's natural one. So a line of `<small>` text alone came out as
2823
+ * short as the small text, and overlapped what followed. A paragraph with
2824
+ * such a line is made a line at a time instead, where `finishLine` puts
2825
+ * the strut on every line; one whose lines all hold text of the block's
2826
+ * own face is kept as it was laid out.
2827
+ */
2828
+ function shortOfStrut(lines, strut,
2829
+ /** The first line's, where it holds more: a marker's room. */
2830
+ first = null) {
2831
+ for (let i = 0; i < lines.length; i += 1) {
2832
+ const line = lines[i];
2833
+ const holds = i === 0 && first ? first : strut;
2834
+ if (!holds)
2835
+ continue;
2836
+ if (line.baseline < holds.ascent - 0.5 ||
2837
+ line.height - line.baseline < holds.descent - 0.5) {
2838
+ return true;
2839
+ }
2840
+ }
2841
+ return false;
2842
+ }
2843
+ /** A strut with room besides it about the same baseline, or the strut
2844
+ * where there is none. */
2845
+ function withRoom(strut, room) {
2846
+ if (!room)
2847
+ return strut;
2848
+ if (!strut)
2849
+ return room;
2850
+ return {
2851
+ ascent: Math.max(strut.ascent, room.ascent),
2852
+ descent: Math.max(strut.descent, room.descent),
2853
+ };
2854
+ }
2855
+ /** The share of the size a synthesized small capital is set at, rounded to
2856
+ * a whole pixel as Blink rounds it (`SimpleFontData`'s scaled font data):
2857
+ * CSS Fonts 4 (6.2) leaves the size to the user agent. */
2858
+ const SMALL_CAPS_SCALE = 0.7;
2859
+ /**
2860
+ * Small capitals a face does not have, made of its capitals set smaller
2861
+ * (CSS Fonts 4, 6.2): where `font-variant-caps` asks for `smcp` — or
2862
+ * `c2sc` too, for `all-small-caps` — and the face does not answer it,
2863
+ * each letter that would be a small capital is its capital at 70% of the
2864
+ * size, in a run of its own: a lower-case letter, and for `all-small-caps`
2865
+ * the rest of the text too. Under `small-caps` what has no case — a space,
2866
+ * a digit, a mark of punctuation — keeps its size, as Blink keeps it. Only
2867
+ * a letter whose capital is one character is made one, so every offset
2868
+ * holds.
2869
+ */
2870
+ function synthesizeSmallCaps(items, fonts) {
2871
+ for (let i = 0; i < items.length; i++) {
2872
+ const item = items[i];
2873
+ if (item.kind !== 'text' || item.control)
2874
+ continue;
2875
+ // `font-variant-caps`'s, which is synthesized where the face has none;
2876
+ // not `font-feature-settings`', which asks for the feature as it is
2877
+ const caps = item.box.style.fontVariantCaps;
2878
+ const features = item.run.features;
2879
+ if (!caps.includes('smcp') || !features?.smcp)
2880
+ continue;
2881
+ const all = caps.includes('c2sc');
2882
+ if (hasSmallCaps(fonts, item.run, all))
2883
+ continue;
2884
+ const pieces = smallCapsPieces(item, all, withoutCaps(features));
2885
+ if (!pieces)
2886
+ continue;
2887
+ items.splice(i, 1, ...pieces);
2888
+ i += pieces.length - 1;
2889
+ }
2890
+ }
2891
+ /** A text item cut where its letters turn to synthesized small capitals
2892
+ * and back, or null where none does. */
2893
+ function smallCapsPieces(item, all, features) {
2894
+ const text = item.run.text;
2895
+ const pieces = [];
2896
+ let from = 0;
2897
+ let small = false;
2898
+ let capitals = '';
2899
+ const cut = (to) => {
2900
+ if (to === from)
2901
+ return;
2902
+ const run = small
2903
+ ? {
2904
+ ...item.run,
2905
+ text: capitals,
2906
+ size: Math.round((item.run.size ?? 16) * SMALL_CAPS_SCALE),
2907
+ }
2908
+ : { ...item.run, text: text.slice(from, to) };
2909
+ if (small) {
2910
+ if (features)
2911
+ run.features = features;
2912
+ else
2913
+ delete run.features;
2914
+ }
2915
+ pieces.push({ ...item, run, start: item.start + from, length: to - from });
2916
+ from = to;
2917
+ capitals = '';
2918
+ };
2919
+ let any = false;
2920
+ for (let at = 0; at < text.length;) {
2921
+ const ch = String.fromCodePoint(text.codePointAt(at));
2922
+ const upper = ch.toUpperCase();
2923
+ // everything, for `all-small-caps`: its spaces are small too
2924
+ const becomes = (all || upper !== ch) && upper.length === ch.length;
2925
+ if (becomes !== small) {
2926
+ cut(at);
2927
+ small = becomes;
2928
+ }
2929
+ if (becomes) {
2930
+ capitals += upper;
2931
+ any = true;
2932
+ }
2933
+ at += ch.length;
2934
+ }
2935
+ cut(text.length);
2936
+ return any ? pieces : null;
2937
+ }
2938
+ /** Features without the small capitals a synthesis stands in for, one
2939
+ * object for each set, as `featuresOf` keeps them. */
2940
+ function withoutCaps(features) {
2941
+ let kept = WITHOUT_CAPS.get(features);
2942
+ if (kept === undefined) {
2943
+ const out = {};
2944
+ for (const [tag, value] of Object.entries(features)) {
2945
+ if (tag !== 'smcp' && tag !== 'c2sc')
2946
+ out[tag] = value;
2947
+ }
2948
+ kept = Object.keys(out).length ? out : null;
2949
+ WITHOUT_CAPS.set(features, kept);
2950
+ }
2951
+ return kept;
2952
+ }
2953
+ const WITHOUT_CAPS = new WeakMap();
2954
+ /**
2955
+ * Whether a run's face has small capitals of its own — and capitals to
2956
+ * small capitals, for `all-small-caps`: the alphabet set with the feature
2957
+ * and without it comes out another width where it does. Asked of the
2958
+ * engine's layout rather than of the font's tables, which a CoreText face
2959
+ * does not hand over. Kept per face.
2960
+ */
2961
+ function hasSmallCaps(fonts, run, all) {
2962
+ let known = SMALL_CAPS.get(fonts);
2963
+ if (!known)
2964
+ SMALL_CAPS.set(fonts, (known = new Map()));
2965
+ const key = `${run.family}|${run.weight}|${run.style}|${all}`;
2966
+ let has = known.get(key);
2967
+ if (has === undefined) {
2968
+ const face = {
2969
+ family: run.family,
2970
+ weight: run.weight,
2971
+ style: run.style,
2972
+ size: 16,
2973
+ };
2974
+ const width = (text, features) => fonts.layout([{ text, ...face, ...(features ? { features } : null) }], face, {}).width;
2975
+ const lower = 'abcdefghijklmnopqrstuvwxyz';
2976
+ has = Math.abs(width(lower, { smcp: 1 }) - width(lower, null)) > 0.01;
2977
+ if (has && all) {
2978
+ const upper = lower.toUpperCase();
2979
+ has = Math.abs(width(upper, { c2sc: 1 }) - width(upper, null)) > 0.01;
2980
+ }
2981
+ known.set(key, has);
2982
+ }
2983
+ return has;
2984
+ }
2985
+ const SMALL_CAPS = new WeakMap();
2764
2986
  /**
2765
2987
  * The OpenType features a style's text is shaped with: the `font-variant`
2766
2988
  * longhands' and `font-kerning`'s, then `font-feature-settings`, which has