@vectojs/core 1.13.0 → 1.15.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.
@@ -1,137 +1,9 @@
1
- import {
2
- ArabicShaper,
3
- BidiResolver,
4
- LayoutWorkerManager
5
- } from "./chunk-IESDTEJ4.mjs";
6
-
7
- // src/math/SpringPhysics.ts
8
- var MAX_FRAME_DT = 0.25;
9
- var MAX_STEP_DT = 1 / 120;
10
- var SpringPhysics = class {
11
- value;
12
- target;
13
- velocity = 0;
14
- stiffness = 180;
15
- damping = 12;
16
- mass = 1;
17
- valEpsilon = 5e-3;
18
- velEpsilon = 5e-3;
19
- constructor(initial) {
20
- this.value = initial;
21
- this.target = initial;
22
- }
23
- update(dt) {
24
- if (this.isAtRest()) {
25
- this.value = this.target;
26
- this.velocity = 0;
27
- return;
28
- }
29
- if (!(dt > 0)) return;
30
- let remaining = dt < MAX_FRAME_DT ? dt : MAX_FRAME_DT;
31
- while (remaining > 0) {
32
- const step = remaining < MAX_STEP_DT ? remaining : MAX_STEP_DT;
33
- const forceSpring = -this.stiffness * (this.value - this.target);
34
- const forceDamping = -this.damping * this.velocity;
35
- const acceleration = (forceSpring + forceDamping) / this.mass;
36
- this.velocity += acceleration * step;
37
- this.value += this.velocity * step;
38
- remaining -= step;
39
- if (this.isAtRest()) {
40
- this.value = this.target;
41
- this.velocity = 0;
42
- return;
43
- }
44
- }
45
- }
46
- isAtRest() {
47
- return Math.abs(this.value - this.target) < this.valEpsilon && Math.abs(this.velocity) < this.velEpsilon;
48
- }
49
- };
50
-
51
- // src/animation/easing.ts
52
- var c1 = 1.70158;
53
- var c3 = c1 + 1;
54
- var Easing = {
55
- linear: (t) => t,
56
- easeInQuad: (t) => t * t,
57
- easeOutQuad: (t) => t * (2 - t),
58
- easeInOutQuad: (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2,
59
- easeInCubic: (t) => t * t * t,
60
- easeOutCubic: (t) => 1 - Math.pow(1 - t, 3),
61
- easeInOutCubic: (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2,
62
- easeOutBack: (t) => 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2),
63
- easeInOutBack: (t) => {
64
- const c2 = c1 * 1.525;
65
- return t < 0.5 ? Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2) / 2 : (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
66
- }
67
- };
68
-
69
- // src/animation/drivers.ts
70
- function isTweenConfig(c) {
71
- return typeof c === "object" && "duration" in c;
72
- }
73
- var TweenDriver = class {
74
- value;
75
- from;
76
- to;
77
- elapsed = 0;
78
- duration;
79
- delay;
80
- ease;
81
- constructor(from, to, cfg) {
82
- this.value = from;
83
- this.from = from;
84
- this.to = to;
85
- this.duration = Math.max(1, cfg.duration);
86
- this.delay = cfg.delay ?? 0;
87
- this.ease = typeof cfg.easing === "function" ? cfg.easing : Easing[cfg.easing ?? "easeOutQuad"];
88
- }
89
- get target() {
90
- return this.to;
91
- }
92
- retarget(to) {
93
- this.from = this.value;
94
- this.to = to;
95
- this.elapsed = 0;
96
- }
97
- tick(dtMs) {
98
- this.elapsed += dtMs;
99
- const active = this.elapsed - this.delay;
100
- if (active <= 0) return;
101
- const p = Math.min(active / this.duration, 1);
102
- this.value = this.from + (this.to - this.from) * this.ease(p);
103
- }
104
- isDone() {
105
- return this.elapsed - this.delay >= this.duration;
106
- }
107
- };
108
- var SpringDriver = class {
109
- spring;
110
- constructor(from, to, cfg) {
111
- this.spring = new SpringPhysics(from);
112
- if (cfg.stiffness !== void 0) this.spring.stiffness = cfg.stiffness;
113
- if (cfg.damping !== void 0) this.spring.damping = cfg.damping;
114
- if (cfg.mass !== void 0) this.spring.mass = cfg.mass;
115
- this.spring.target = to;
116
- }
117
- get value() {
118
- return this.spring.value;
119
- }
120
- get target() {
121
- return this.spring.target;
122
- }
123
- retarget(to) {
124
- this.spring.target = to;
125
- }
126
- tick(dtMs) {
127
- this.spring.update(dtMs / 1e3);
128
- }
129
- isDone() {
130
- return this.spring.isAtRest();
131
- }
132
- };
133
-
134
1
  // src/tree/Entity.ts
2
+ import {
3
+ TweenDriver,
4
+ SpringDriver,
5
+ isTweenConfig
6
+ } from "@vectojs/animation";
135
7
  var ANIMATABLE_PROPS = /* @__PURE__ */ new Set([
136
8
  "x",
137
9
  "y",
@@ -985,129 +857,8 @@ var Entity = class {
985
857
  }
986
858
  };
987
859
 
988
- // src/text/Typography.ts
989
- var typographyContext;
990
- var baselineCache = /* @__PURE__ */ new Map();
991
- function cssLineBoxBaseline(font, lineHeight) {
992
- if (typeof document === "undefined") return lineHeight * 0.8;
993
- const key = `${font}\0${lineHeight}`;
994
- const cached = baselineCache.get(key);
995
- if (cached !== void 0) return cached;
996
- if (typographyContext === void 0) {
997
- typographyContext = document.createElement("canvas").getContext("2d");
998
- }
999
- if (!typographyContext) return lineHeight * 0.8;
1000
- typographyContext.font = font;
1001
- const metrics = typographyContext.measureText("Mg");
1002
- const ascent = metrics.fontBoundingBoxAscent || metrics.actualBoundingBoxAscent;
1003
- const descent = metrics.fontBoundingBoxDescent || metrics.actualBoundingBoxDescent;
1004
- if (!(ascent > 0) || !(descent >= 0)) return lineHeight * 0.8;
1005
- const baseline = (lineHeight - ascent - descent) / 2 + ascent;
1006
- baselineCache.set(key, baseline);
1007
- return baseline;
1008
- }
1009
- function clearCssLineBoxMetrics() {
1010
- baselineCache.clear();
1011
- }
1012
-
1013
- // src/text/MSDFFont.ts
1014
- function kernKey(a, b) {
1015
- return a * 1114112 + b;
1016
- }
1017
- var MSDFFont = class _MSDFFont {
1018
- static idCounter = 0;
1019
- id;
1020
- data;
1021
- byCode = /* @__PURE__ */ new Map();
1022
- kern = /* @__PURE__ */ new Map();
1023
- constructor(data) {
1024
- this.id = `font-${_MSDFFont.idCounter++}`;
1025
- this.data = data;
1026
- for (const g of data.glyphs) this.byCode.set(g.unicode, g);
1027
- for (const k of data.kerning ?? []) this.kern.set(kernKey(k.unicode1, k.unicode2), k.advance);
1028
- }
1029
- /** Parse the `msdf-atlas-gen` JSON (string or already-parsed object). */
1030
- static parse(json) {
1031
- return new _MSDFFont(typeof json === "string" ? JSON.parse(json) : json);
1032
- }
1033
- /** Get a glyph's definition by its unicode value in O(1) time. */
1034
- getGlyph(unicode) {
1035
- return this.byCode.get(unicode);
1036
- }
1037
- /** Distance field range in atlas pixels (for the shader's `u_distanceRange`). */
1038
- get distanceRange() {
1039
- return this.data.atlas.distanceRange;
1040
- }
1041
- get atlasWidth() {
1042
- return this.data.atlas.width;
1043
- }
1044
- get atlasHeight() {
1045
- return this.data.atlas.height;
1046
- }
1047
- /**
1048
- * Lay `text` out at `fontSizePx`. Returns positioned quads (skipping glyphs the
1049
- * font doesn't contain), the widest line's advance, and the total block height.
1050
- * Honors `\n`, kerning pairs, and `letterSpacing`.
1051
- */
1052
- layout(text, fontSizePx, opts = {}) {
1053
- const { x = 0, y = 0, letterSpacing = 0 } = opts;
1054
- const { width: aw, height: ah, yOrigin } = this.data.atlas;
1055
- const { lineHeight, ascender } = this.data.metrics;
1056
- const glyphs = [];
1057
- let penX = x;
1058
- let line = 0;
1059
- let maxAdvance = 0;
1060
- let prevCode = -1;
1061
- const chars = Array.from(text);
1062
- for (const char of chars) {
1063
- if (char === "\n") {
1064
- maxAdvance = Math.max(maxAdvance, penX - x);
1065
- penX = x;
1066
- line++;
1067
- prevCode = -1;
1068
- continue;
1069
- }
1070
- const code = char.codePointAt(0);
1071
- const def = this.byCode.get(code);
1072
- if (!def) {
1073
- prevCode = -1;
1074
- continue;
1075
- }
1076
- if (prevCode >= 0) {
1077
- const k = this.kern.get(kernKey(prevCode, code));
1078
- if (k) penX += k * fontSizePx;
1079
- }
1080
- const baseline = y + (ascender + line * lineHeight) * fontSizePx;
1081
- const pb = def.planeBounds;
1082
- const ab = def.atlasBounds;
1083
- if (pb && ab) {
1084
- const v0 = yOrigin === "bottom" ? 1 - ab.top / ah : ab.top / ah;
1085
- const v1 = yOrigin === "bottom" ? 1 - ab.bottom / ah : ab.bottom / ah;
1086
- glyphs.push({
1087
- char,
1088
- x: penX + pb.left * fontSizePx,
1089
- y: baseline - pb.top * fontSizePx,
1090
- w: (pb.right - pb.left) * fontSizePx,
1091
- h: (pb.top - pb.bottom) * fontSizePx,
1092
- u0: ab.left / aw,
1093
- v0,
1094
- u1: ab.right / aw,
1095
- v1
1096
- });
1097
- }
1098
- penX += def.advance * fontSizePx + letterSpacing;
1099
- prevCode = code;
1100
- }
1101
- maxAdvance = Math.max(maxAdvance, penX - x);
1102
- return {
1103
- glyphs,
1104
- width: maxAdvance,
1105
- height: (line + 1) * lineHeight * fontSizePx
1106
- };
1107
- }
1108
- };
1109
-
1110
860
  // src/text/MSDFTextEntity.ts
861
+ import { LayoutWorkerManager } from "@vectojs/layout";
1111
862
  var MSDFTextEntity = class extends Entity {
1112
863
  font;
1113
864
  texture;
@@ -1505,217 +1256,9 @@ var SVGEntity = class extends Entity {
1505
1256
  }
1506
1257
  };
1507
1258
 
1508
- // src/text/PreparedContentGrid.ts
1509
- var nextRevision = 1;
1510
- var graphemeSegmenter = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
1511
- var MARK = /\p{Mark}/u;
1512
- var EXTENDED_PICTOGRAPHIC = /\p{Extended_Pictographic}/u;
1513
- var REGIONAL_INDICATOR = /\p{Regional_Indicator}/u;
1514
- var BIDI_CONTROL = /\p{Bidi_Control}/u;
1515
- var EAST_ASIAN_WIDE = /[ᄀ-ᅟ⌚-⌛⏩-⏬⏰⏳◽-◾⺀-〾ぁ-㏿㐀-䶿一-鿿ꀀ-꓏가-힣豈-﫿︰-﹏＀-⦆¢-₩]/u;
1516
- function codePointAt(text, index) {
1517
- const point = text.codePointAt(index);
1518
- if (point === void 0) return { value: "", next: index };
1519
- const value = String.fromCodePoint(point);
1520
- return { value, next: index + value.length };
1521
- }
1522
- function fallbackGraphemes(text) {
1523
- const parts = [];
1524
- let index = 0;
1525
- while (index < text.length) {
1526
- const start = index;
1527
- let current = codePointAt(text, index);
1528
- let segment = current.value;
1529
- index = current.next;
1530
- let regionalCount = REGIONAL_INDICATOR.test(segment) ? 1 : 0;
1531
- while (index < text.length) {
1532
- current = codePointAt(text, index);
1533
- const point = current.value.codePointAt(0) ?? 0;
1534
- const isVariation = point >= 65024 && point <= 65039;
1535
- const isEmojiModifier = point >= 127995 && point <= 127999;
1536
- const isKeycap = point === 8419;
1537
- if (MARK.test(current.value) || isVariation || isEmojiModifier || isKeycap) {
1538
- segment += current.value;
1539
- index = current.next;
1540
- continue;
1541
- }
1542
- if (REGIONAL_INDICATOR.test(current.value) && regionalCount === 1) {
1543
- segment += current.value;
1544
- index = current.next;
1545
- regionalCount++;
1546
- continue;
1547
- }
1548
- if (point === 8205) {
1549
- segment += current.value;
1550
- index = current.next;
1551
- if (index < text.length) {
1552
- current = codePointAt(text, index);
1553
- segment += current.value;
1554
- index = current.next;
1555
- }
1556
- continue;
1557
- }
1558
- break;
1559
- }
1560
- parts.push({ segment, index: start });
1561
- }
1562
- return parts;
1563
- }
1564
- function graphemes(text) {
1565
- if (!graphemeSegmenter) return fallbackGraphemes(text);
1566
- return Array.from(graphemeSegmenter.segment(text), (part) => ({
1567
- segment: part.segment,
1568
- index: part.index
1569
- }));
1570
- }
1571
- function lowerBound(values, target) {
1572
- let low = 0;
1573
- let high = values.length;
1574
- while (low < high) {
1575
- const middle = low + high >>> 1;
1576
- if (values[middle] < target) low = middle + 1;
1577
- else high = middle;
1578
- }
1579
- return low;
1580
- }
1581
- function isWideCluster(cluster) {
1582
- if (EXTENDED_PICTOGRAPHIC.test(cluster) || REGIONAL_INDICATOR.test(cluster)) return true;
1583
- if (cluster.includes("\u20E3")) return true;
1584
- if (EAST_ASIAN_WIDE.test(cluster)) return true;
1585
- const point = cluster.codePointAt(0) ?? 0;
1586
- return point >= 131072 && point <= 262141;
1587
- }
1588
- function sourceLines(source) {
1589
- const lines = [];
1590
- let start = 0;
1591
- while (true) {
1592
- let end = start;
1593
- while (end < source.length && source[end] !== "\r" && source[end] !== "\n") end++;
1594
- if (end === source.length) {
1595
- lines.push({
1596
- sourceStart: start,
1597
- sourceEnd: end,
1598
- nextSourceStart: end,
1599
- text: source.slice(start)
1600
- });
1601
- break;
1602
- }
1603
- const next = source[end] === "\r" && source[end + 1] === "\n" ? end + 2 : end + 1;
1604
- lines.push({
1605
- sourceStart: start,
1606
- sourceEnd: end,
1607
- nextSourceStart: next,
1608
- text: source.slice(start, end)
1609
- });
1610
- start = next;
1611
- if (start === source.length) {
1612
- lines.push({ sourceStart: start, sourceEnd: start, nextSourceStart: start, text: "" });
1613
- break;
1614
- }
1615
- }
1616
- return lines;
1617
- }
1618
- function assertPositiveFinite(value, name) {
1619
- if (!Number.isFinite(value) || value <= 0) {
1620
- throw new RangeError(`${name} must be a positive finite number`);
1621
- }
1622
- }
1623
- function prepareContentGrid(source, options) {
1624
- assertPositiveFinite(options.cellWidth, "cellWidth");
1625
- assertPositiveFinite(options.lineHeight, "lineHeight");
1626
- if (!Number.isFinite(options.baseline)) throw new RangeError("baseline must be finite");
1627
- const tabSize = options.tabSize ?? 4;
1628
- if (!Number.isInteger(tabSize) || tabSize <= 0) {
1629
- throw new RangeError("tabSize must be a positive integer");
1630
- }
1631
- const rawLines = sourceLines(source);
1632
- const lines = [];
1633
- for (let lineIndex = 0; lineIndex < rawLines.length; lineIndex++) {
1634
- const sourceLine = rawLines[lineIndex];
1635
- const rawLine = sourceLine.text;
1636
- const { sourceStart: lineStart, sourceEnd, nextSourceStart } = sourceLine;
1637
- const rawCaretBoundaries = [
1638
- 0,
1639
- ...graphemes(rawLine).map((grapheme) => grapheme.index + grapheme.segment.length)
1640
- ];
1641
- const shaped = ArabicShaper.shapeArabic(rawLine);
1642
- const shapedParts = graphemes(shaped.shapedText);
1643
- const levels = BidiResolver.resolveLevels(shaped.shapedText);
1644
- const cells = [];
1645
- let column = 0;
1646
- for (let index = 0; index < shapedParts.length; index++) {
1647
- const part = shapedParts[index];
1648
- const sourceOffset = shaped.indexMap[part.index] ?? part.index;
1649
- const nextPart = shapedParts[index + 1];
1650
- const sourceOffsetEnd = nextPart ? shaped.indexMap[nextPart.index] ?? nextPart.index : rawLine.length;
1651
- const raw = rawLine.slice(sourceOffset, sourceOffsetEnd);
1652
- const sourceCaretOffsets = [0];
1653
- for (let caretIndex = lowerBound(rawCaretBoundaries, sourceOffset + 1); caretIndex < rawCaretBoundaries.length && rawCaretBoundaries[caretIndex] < sourceOffsetEnd; caretIndex++) {
1654
- sourceCaretOffsets.push(rawCaretBoundaries[caretIndex] - sourceOffset);
1655
- }
1656
- if (sourceCaretOffsets.at(-1) !== sourceOffsetEnd - sourceOffset) {
1657
- sourceCaretOffsets.push(sourceOffsetEnd - sourceOffset);
1658
- }
1659
- let columns;
1660
- if (BIDI_CONTROL.test(raw)) columns = 0;
1661
- else if (raw === " ") columns = tabSize - column % tabSize;
1662
- else columns = isWideCluster(raw) ? 2 : 1;
1663
- const advance = columns * options.cellWidth;
1664
- cells.push({
1665
- sourceStart: lineStart + sourceOffset,
1666
- sourceEnd: lineStart + sourceOffsetEnd,
1667
- sourceCaretOffsets: Object.freeze(sourceCaretOffsets),
1668
- glyph: part.segment,
1669
- x: 0,
1670
- advance,
1671
- level: levels[part.index] ?? 0,
1672
- char: part.segment
1673
- });
1674
- column += columns;
1675
- }
1676
- const visualCells = [...cells];
1677
- BidiResolver.reorderVisual(visualCells, BidiResolver.getBaseLevel(shaped.shapedText));
1678
- let visualX = 0;
1679
- for (const cell of visualCells) {
1680
- cell.x = visualX;
1681
- visualX += cell.advance;
1682
- }
1683
- const frozenCells = cells.map(({ char: _char, ...cell }) => Object.freeze(cell));
1684
- lines.push(
1685
- Object.freeze({
1686
- sourceStart: lineStart,
1687
- sourceEnd,
1688
- nextSourceStart,
1689
- width: visualX,
1690
- cells: Object.freeze(frozenCells)
1691
- })
1692
- );
1693
- }
1694
- return Object.freeze({
1695
- kind: "content-grid",
1696
- revision: nextRevision++,
1697
- source,
1698
- font: options.font,
1699
- cellWidth: options.cellWidth,
1700
- lineHeight: options.lineHeight,
1701
- baseline: options.baseline,
1702
- tabSize,
1703
- lines: Object.freeze(lines)
1704
- });
1705
- }
1706
-
1707
1259
  export {
1708
- SpringPhysics,
1709
- Easing,
1710
- isTweenConfig,
1711
- TweenDriver,
1712
- SpringDriver,
1713
1260
  VectoJSEvent,
1714
1261
  Entity,
1715
- cssLineBoxBaseline,
1716
- clearCssLineBoxMetrics,
1717
- MSDFFont,
1718
1262
  MSDFTextEntity,
1719
- SVGEntity,
1720
- prepareContentGrid
1263
+ SVGEntity
1721
1264
  };