@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.
- package/dist/code/index.d.ts.map +1 -1
- package/dist/code/index.js +25 -24
- package/dist/code/index.js.map +1 -1
- package/dist/code-language/index.d.ts +1 -1
- package/dist/code-language/index.d.ts.map +1 -1
- package/dist/code-language/index.js +1 -1
- package/dist/code-language/index.js.map +1 -1
- package/dist/code-language/runs.d.ts +31 -0
- package/dist/code-language/runs.d.ts.map +1 -1
- package/dist/code-language/runs.js +105 -0
- package/dist/code-language/runs.js.map +1 -1
- package/dist/codeblock/index.d.ts +13 -0
- package/dist/codeblock/index.d.ts.map +1 -1
- package/dist/codeblock/index.js +37 -1
- package/dist/codeblock/index.js.map +1 -1
- package/dist/html/css/cascade.d.ts +32 -10
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +72 -27
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/style.d.ts +7 -4
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +37 -6
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/ua.js +8 -5
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +127 -9
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +27 -0
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +30 -2
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/inline.d.ts +3 -0
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +236 -14
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +118 -57
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +203 -37
- package/dist/html/paint.js.map +1 -1
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +27 -20
- package/dist/html/svg.js.map +1 -1
- package/dist/internal/codelines.d.ts +42 -0
- package/dist/internal/codelines.d.ts.map +1 -0
- package/dist/internal/codelines.js +170 -0
- package/dist/internal/codelines.js.map +1 -0
- package/dist/markdown/index.d.ts.map +1 -1
- package/dist/markdown/index.js +98 -26
- package/dist/markdown/index.js.map +1 -1
- package/dist/rich-text-editor/index.d.ts.map +1 -1
- package/dist/rich-text-editor/index.js +3 -8
- package/dist/rich-text-editor/index.js.map +1 -1
- package/dist/rich-text-editor/markdown.d.ts.map +1 -1
- package/dist/rich-text-editor/markdown.js +30 -1
- package/dist/rich-text-editor/markdown.js.map +1 -1
- package/dist/rich-text-editor/replace.d.ts +13 -0
- package/dist/rich-text-editor/replace.d.ts.map +1 -0
- package/dist/rich-text-editor/replace.js +64 -0
- package/dist/rich-text-editor/replace.js.map +1 -0
- package/dist/terminal-output/index.d.ts.map +1 -1
- package/dist/terminal-output/index.js +100 -25
- package/dist/terminal-output/index.js.map +1 -1
- package/dist/terminal-output/runs.d.ts +4 -1
- package/dist/terminal-output/runs.d.ts.map +1 -1
- package/dist/terminal-output/runs.js +5 -1
- package/dist/terminal-output/runs.js.map +1 -1
- package/package.json +1 -1
- package/src/code/index.ts +34 -32
- package/src/code-language/index.ts +1 -1
- package/src/code-language/runs.ts +111 -1
- package/src/codeblock/index.ts +48 -1
- package/src/html/css/cascade.ts +117 -39
- package/src/html/css/style.ts +43 -15
- package/src/html/css/ua.ts +8 -5
- package/src/html/layout/block.ts +134 -8
- package/src/html/layout/boxes.ts +43 -2
- package/src/html/layout/inline.ts +257 -13
- package/src/html/node.ts +145 -79
- package/src/html/paint.ts +236 -45
- package/src/html/svg.ts +30 -19
- package/src/internal/codelines.ts +200 -0
- package/src/markdown/index.ts +145 -26
- package/src/rich-text-editor/index.ts +3 -11
- package/src/rich-text-editor/markdown.ts +36 -1
- package/src/rich-text-editor/replace.ts +85 -0
- package/src/terminal-output/index.ts +127 -34
- 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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
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
|
|
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
|