@neta-art/cohub 3.1.0 → 4.0.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 (94) hide show
  1. package/README.md +33 -2
  2. package/dist/{board.d.ts → board/animation.d.ts} +4 -2
  3. package/dist/{chunks/board.js → board/animation.js} +5 -44
  4. package/dist/board/codec.d.ts +25 -0
  5. package/dist/board/codec.js +243 -0
  6. package/dist/board/core/bindings.d.ts +45 -0
  7. package/dist/board/core/bindings.js +162 -0
  8. package/dist/board/core/draw-geometry.d.ts +30 -0
  9. package/dist/board/core/draw-geometry.js +175 -0
  10. package/dist/board/core/export-assets.d.ts +14 -0
  11. package/dist/board/core/export-assets.js +22 -0
  12. package/dist/board/core/export-plan.d.ts +84 -0
  13. package/dist/board/core/export-plan.js +128 -0
  14. package/dist/board/core/file-preview.d.ts +178 -0
  15. package/dist/board/core/file-preview.js +282 -0
  16. package/dist/board/core/palette.d.ts +42 -0
  17. package/dist/board/core/palette.js +166 -0
  18. package/dist/board/core/shape-definition.d.ts +35 -0
  19. package/dist/board/core/shape-definition.js +64 -0
  20. package/dist/board/core/shape-types.d.ts +140 -0
  21. package/dist/board/core/shape-types.js +27 -0
  22. package/dist/board/core/text-metrics.d.ts +18 -0
  23. package/dist/board/core/text-metrics.js +43 -0
  24. package/dist/board/core/tool-styles.d.ts +52 -0
  25. package/dist/board/core/tool-styles.js +51 -0
  26. package/dist/board/export/index.d.ts +56 -0
  27. package/dist/board/export/index.js +86 -0
  28. package/dist/board/export/scene.d.ts +35 -0
  29. package/dist/board/export/scene.js +69 -0
  30. package/dist/board/geometry.d.ts +139 -0
  31. package/dist/board/geometry.js +492 -0
  32. package/dist/board/headless/index.d.ts +69 -0
  33. package/dist/board/headless/index.js +150 -0
  34. package/dist/board/image-key.d.ts +19 -0
  35. package/dist/board/image-key.js +34 -0
  36. package/dist/board/index.d.ts +17 -0
  37. package/dist/board/index.js +17 -0
  38. package/dist/board/render/index.d.ts +6 -0
  39. package/dist/board/render/index.js +6 -0
  40. package/dist/board/render/palette.d.ts +5 -0
  41. package/dist/board/render/palette.js +30 -0
  42. package/dist/board/render/renderers/arrow-card-renderer.d.ts +5 -0
  43. package/dist/board/render/renderers/arrow-card-renderer.js +157 -0
  44. package/dist/board/render/renderers/base-card-renderer.d.ts +46 -0
  45. package/dist/board/render/renderers/base-card-renderer.js +153 -0
  46. package/dist/board/render/renderers/board-renderer-registry.d.ts +85 -0
  47. package/dist/board/render/renderers/board-renderer-registry.js +41 -0
  48. package/dist/board/render/renderers/draw-card-renderer.d.ts +5 -0
  49. package/dist/board/render/renderers/draw-card-renderer.js +82 -0
  50. package/dist/board/render/renderers/far-plate.d.ts +39 -0
  51. package/dist/board/render/renderers/far-plate.js +92 -0
  52. package/dist/board/render/renderers/file-card-renderer.d.ts +40 -0
  53. package/dist/board/render/renderers/file-card-renderer.js +350 -0
  54. package/dist/board/render/renderers/frame-card-renderer.d.ts +5 -0
  55. package/dist/board/render/renderers/frame-card-renderer.js +99 -0
  56. package/dist/board/render/renderers/geo-card-renderer.d.ts +5 -0
  57. package/dist/board/render/renderers/geo-card-renderer.js +128 -0
  58. package/dist/board/render/renderers/image-card-renderer.d.ts +8 -0
  59. package/dist/board/render/renderers/image-card-renderer.js +138 -0
  60. package/dist/board/render/renderers/text-card-renderer.d.ts +7 -0
  61. package/dist/board/render/renderers/text-card-renderer.js +94 -0
  62. package/dist/board/render/renderers/unknown-card-renderer.d.ts +5 -0
  63. package/dist/board/render/renderers/unknown-card-renderer.js +102 -0
  64. package/dist/board/render/renderers/video-card-renderer.d.ts +7 -0
  65. package/dist/board/render/renderers/video-card-renderer.js +146 -0
  66. package/dist/board/render/text-measurement.d.ts +30 -0
  67. package/dist/board/render/text-measurement.js +57 -0
  68. package/dist/board/render/text-resolution.d.ts +16 -0
  69. package/dist/board/render/text-resolution.js +43 -0
  70. package/dist/board/render/themes/board-theme-registry.d.ts +22 -0
  71. package/dist/board/render/themes/board-theme-registry.js +13 -0
  72. package/dist/board/render/themes/clean-theme.d.ts +5 -0
  73. package/dist/board/render/themes/clean-theme.js +105 -0
  74. package/dist/chunks/http.d.ts +2 -3
  75. package/dist/chunks/http.js +4 -0
  76. package/dist/chunks/websocket.d.ts +851 -353
  77. package/dist/http.d.ts +1 -2
  78. package/dist/index.d.ts +100 -5
  79. package/dist/index.js +391 -1
  80. package/dist/protocol/dist/board-constants.d.ts +23 -0
  81. package/dist/protocol/dist/board-constants.js +61 -0
  82. package/dist/protocol/dist/board-document.d.ts +1047 -0
  83. package/dist/protocol/dist/board-document.js +321 -0
  84. package/dist/protocol/dist/board.d.ts +186 -0
  85. package/dist/protocol/dist/board.js +207 -0
  86. package/dist/protocol/dist/index.d.ts +5 -0
  87. package/dist/protocol/dist/index.js +5 -0
  88. package/dist/protocol/dist/provenance.js +12 -0
  89. package/dist/protocol/dist/realtime/board-awareness.d.ts +1 -0
  90. package/dist/protocol/dist/realtime/board-awareness.js +119 -0
  91. package/dist/protocol/dist/realtime/types.d.ts +2 -0
  92. package/package.json +40 -4
  93. package/dist/board.js +0 -2
  94. package/dist/chunks/board.d.ts +0 -2589
@@ -0,0 +1,153 @@
1
+ import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
+ import { getBoardResolution } from "../text-resolution.js";
3
+ import { Container, DOMAdapter, Graphics, Text } from "pixi.js";
4
+ //#region src/board/render/renderers/base-card-renderer.ts
5
+ const CARD_RADIUS = 10;
6
+ const CARD_PADDING = 12;
7
+ const FOOTER_HEIGHT = 34;
8
+ const TEXT_OPTIONS = {
9
+ resolution: getBoardResolution(),
10
+ roundPixels: true
11
+ };
12
+ function emphasisColor(item, palette) {
13
+ if (item.style?.accentColor) {
14
+ const normalized = parseCssColor(item.style.accentColor);
15
+ if (normalized != null) return normalized;
16
+ }
17
+ switch (item.style?.emphasis) {
18
+ case "rare": return palette.rare;
19
+ case "epic": return palette.epic;
20
+ case "legendary": return palette.legendary;
21
+ default: return palette.brand;
22
+ }
23
+ }
24
+ /**
25
+ * Normalise any CSS color to a number by letting a 2D context parse it.
26
+ *
27
+ * Goes through `DOMAdapter` rather than `document` so a headless export resolves
28
+ * accent colors exactly as the browser does; without it, custom accents would
29
+ * silently fall back to the emphasis palette only when exporting.
30
+ */
31
+ function parseCssColor(value) {
32
+ try {
33
+ const context = DOMAdapter.get().createCanvas(1, 1).getContext("2d");
34
+ if (!context) return null;
35
+ context.fillStyle = value;
36
+ const match = /^#([0-9a-f]{6})$/i.exec(String(context.fillStyle));
37
+ return match?.[1] ? Number.parseInt(match[1], 16) : null;
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+ function createLabel(text, style) {
43
+ return new Text({
44
+ ...TEXT_OPTIONS,
45
+ text,
46
+ style
47
+ });
48
+ }
49
+ /**
50
+ * Shared card chrome: rounded background, selection/hover border, optional
51
+ * footer with title, and a masked content region. Tracks the last rendered
52
+ * state so unchanged attributes are not redrawn.
53
+ */
54
+ function createCardShell() {
55
+ const root = new Container();
56
+ const background = new Graphics();
57
+ const content = new Container();
58
+ const mask = new Graphics();
59
+ const footerBg = new Graphics();
60
+ const title = createLabel("", {
61
+ fill: 16777215,
62
+ fontFamily: BOARD_FONT_STACK,
63
+ fontSize: 12,
64
+ fontWeight: "500",
65
+ wordWrap: true
66
+ });
67
+ content.mask = mask;
68
+ root.addChild(background, mask, content, footerBg, title);
69
+ let last = null;
70
+ function paletteKeyOf(palette) {
71
+ return `${palette.surface}|${palette.border}|${palette.hover}|${palette.text}|${palette.muted}`;
72
+ }
73
+ function contentRectFor(width, height, footer) {
74
+ const bottom = footer ? 34 : 12;
75
+ return {
76
+ x: 12,
77
+ y: 12,
78
+ width: Math.max(1, width - 24),
79
+ height: Math.max(1, height - 12 - bottom)
80
+ };
81
+ }
82
+ function draw(state, palette, footer) {
83
+ const { width, height, selected, hovered, accent } = state;
84
+ background.clear();
85
+ background.roundRect(0, 0, width, height, 10).fill({
86
+ color: palette.surface,
87
+ alpha: .98
88
+ });
89
+ const borderColor = selected ? accent : hovered ? palette.muted : palette.border;
90
+ background.roundRect(0, 0, width, height, 10).stroke({
91
+ color: borderColor,
92
+ width: selected ? 2 : 1,
93
+ alpha: selected ? .95 : hovered ? .9 : .8
94
+ });
95
+ mask.clear();
96
+ const rect = contentRectFor(width, height, footer);
97
+ mask.roundRect(rect.x, rect.y, rect.width, rect.height, 4).fill({ color: 16777215 });
98
+ footerBg.clear();
99
+ footerBg.visible = footer;
100
+ title.visible = footer;
101
+ if (footer) {
102
+ const fx = 1;
103
+ const fy = height - 34;
104
+ const fw = width - 2;
105
+ const fh = 33;
106
+ const r = 9;
107
+ footerBg.moveTo(fx, fy).lineTo(fx + fw, fy).lineTo(fx + fw, fy + fh - r).arcTo(fx + fw, fy + fh, fx + fw - r, fy + fh, r).lineTo(10, fy + fh).arcTo(fx, fy + fh, fx, fy + fh - r, r).lineTo(fx, fy).fill({
108
+ color: palette.hover,
109
+ alpha: .55
110
+ });
111
+ title.text = state.title;
112
+ title.style.fill = palette.text;
113
+ title.style.wordWrapWidth = width - 24;
114
+ title.x = 12;
115
+ title.y = height - 34 + (34 - title.height) / 2;
116
+ }
117
+ }
118
+ function update(state, palette, footer) {
119
+ const paletteKey = paletteKeyOf(palette);
120
+ if (!last || last.width !== state.width || last.height !== state.height || last.selected !== state.selected || last.hovered !== state.hovered || last.accent !== state.accent || last.title !== state.title || last.footer !== footer || last.paletteKey !== paletteKey) {
121
+ draw(state, palette, footer);
122
+ last = {
123
+ ...state,
124
+ footer,
125
+ paletteKey
126
+ };
127
+ }
128
+ }
129
+ function destroy() {
130
+ root.destroy({ children: true });
131
+ }
132
+ return {
133
+ root,
134
+ content,
135
+ contentRect: () => contentRectFor(last?.width ?? 0, last?.height ?? 0, last?.footer ?? true),
136
+ update,
137
+ destroy
138
+ };
139
+ }
140
+ /**
141
+ * Position a shell root at an item frame. The container pivots around its
142
+ * center so that Pixi's rotation matches the geometry model (which treats
143
+ * `frame.rotation` as a rotation about the frame center).
144
+ */
145
+ function positionShell(root, item) {
146
+ const { x, y, width, height, rotation } = item.frame;
147
+ root.pivot.set(width / 2, height / 2);
148
+ root.x = x + width / 2;
149
+ root.y = y + height / 2;
150
+ root.rotation = (rotation || 0) * Math.PI / 180;
151
+ }
152
+ //#endregion
153
+ export { CARD_PADDING, CARD_RADIUS, FOOTER_HEIGHT, createCardShell, createLabel, emphasisColor, positionShell };
@@ -0,0 +1,85 @@
1
+ import { BoardDocument, BoardItem } from "../../../protocol/dist/board-document.js";
2
+ import { BoardShapeColors } from "../../core/palette.js";
3
+ import { Container, Graphics, Texture } from "pixi.js";
4
+ //#region src/board/render/renderers/board-renderer-registry.d.ts
5
+ type BoardRenderPalette = {
6
+ bg: number;
7
+ surface: number;
8
+ hover: number;
9
+ border: number;
10
+ brand: number;
11
+ text: number;
12
+ muted: number;
13
+ rare: number;
14
+ epic: number;
15
+ legendary: number;
16
+ };
17
+ type BoardRenderContext = {
18
+ document: BoardDocument;
19
+ /**
20
+ * O(1) item lookup by id. Renderers that need to resolve references (arrow
21
+ * bindings) must use this rather than scanning `document.items`, which would
22
+ * be O(items) per card per frame.
23
+ */
24
+ getItem: (id: string) => BoardItem | null;
25
+ selectedIds: Set<string>;
26
+ hoveredId: string | null;
27
+ /** Nodes currently receiving a live resize preview. */
28
+ resizingIds: Set<string>;
29
+ palette: BoardRenderPalette;
30
+ /**
31
+ * Live shape colors resolved from CSS tokens (theme + space theme.css).
32
+ * Prefer this over hard-coded light/dark tables at render time.
33
+ */
34
+ colors: BoardShapeColors;
35
+ /** Resolved color mode, for fallback mapping when colors are unavailable. */
36
+ colorScheme: "dark" | "light";
37
+ /** Current camera zoom — used for text re-rasterisation buckets. */
38
+ zoom: number;
39
+ /** Stable preview texture key for an item, or null when it has no preview. */
40
+ assetKey: (item: BoardItem) => string | null;
41
+ /** Currently loaded texture for a key (null while loading). */
42
+ getTexture: (key: string) => Texture | null;
43
+ /** Whether the preview for a key failed to load (for a failure placeholder). */
44
+ hasError: (key: string) => boolean;
45
+ /**
46
+ * Whether a referenced workspace file could not be read, and why. Transient,
47
+ * client-local state — never part of the document — so one client's outage is
48
+ * not shown to everyone.
49
+ */
50
+ fileState: (path: string) => "ok" | "missing" | "unavailable";
51
+ /** Reference-counted texture acquisition and release. */
52
+ acquireTexture: (key: string) => void;
53
+ releaseTexture: (key: string) => void;
54
+ };
55
+ type BoardCardRenderer = {
56
+ id: string;
57
+ canRender: (item: BoardItem, context: BoardRenderContext) => boolean;
58
+ create: (item: BoardItem, context: BoardRenderContext) => Container;
59
+ /** Efficiently sync an existing container to a (possibly changed) item. */
60
+ update: (container: Container, item: BoardItem, context: BoardRenderContext) => void;
61
+ /**
62
+ * Draw the item as flat batched geometry into a shared `Graphics`, for the
63
+ * far LOD used when too many cards are on screen to afford a container each.
64
+ *
65
+ * Implementing this opts the shape into the far layer: past the scene's
66
+ * visible-card threshold it is drawn here instead of being materialised, so
67
+ * cost stops scaling with node count. Omit it for shapes whose whole point is
68
+ * their vector detail (strokes, arrows, frames) — those keep real containers.
69
+ *
70
+ * Must draw in world space and must not allocate per call beyond the path
71
+ * itself; this runs once per far-capable item on every far-layer rebuild.
72
+ */
73
+ renderFar?: (graphics: Graphics, item: BoardItem, context: BoardRenderContext) => void;
74
+ destroy?: (container: Container, context: BoardRenderContext) => void;
75
+ };
76
+ /**
77
+ * Every registered renderer, for invariants that must hold across all of them —
78
+ * notably that each can draw itself at far LOD, which is what keeps the far
79
+ * layer's z-order correct (see board-scene).
80
+ */
81
+ declare function boardCardRenderersForTest(): readonly BoardCardRenderer[];
82
+ declare function getBoardCardRenderer(item: BoardItem, context: BoardRenderContext): BoardCardRenderer;
83
+ declare function registerBoardCardRenderer(renderer: BoardCardRenderer): void;
84
+ //#endregion
85
+ export { BoardCardRenderer, BoardRenderContext, BoardRenderPalette, boardCardRenderersForTest, getBoardCardRenderer, registerBoardCardRenderer };
@@ -0,0 +1,41 @@
1
+ import { ensureBoardTextMeasurement } from "../text-measurement.js";
2
+ import { arrowCardRenderer } from "./arrow-card-renderer.js";
3
+ import { drawCardRenderer } from "./draw-card-renderer.js";
4
+ import { fileCardRenderer } from "./file-card-renderer.js";
5
+ import { frameCardRenderer } from "./frame-card-renderer.js";
6
+ import { geoCardRenderer } from "./geo-card-renderer.js";
7
+ import { imageCardRenderer } from "./image-card-renderer.js";
8
+ import { textCardRenderer } from "./text-card-renderer.js";
9
+ import { unknownCardRenderer } from "./unknown-card-renderer.js";
10
+ import { videoCardRenderer } from "./video-card-renderer.js";
11
+ //#region src/board/render/renderers/board-renderer-registry.ts
12
+ const boardCardRenderers = [
13
+ textCardRenderer,
14
+ imageCardRenderer,
15
+ videoCardRenderer,
16
+ fileCardRenderer,
17
+ geoCardRenderer,
18
+ drawCardRenderer,
19
+ arrowCardRenderer,
20
+ frameCardRenderer,
21
+ unknownCardRenderer
22
+ ];
23
+ /**
24
+ * Every registered renderer, for invariants that must hold across all of them —
25
+ * notably that each can draw itself at far LOD, which is what keeps the far
26
+ * layer's z-order correct (see board-scene).
27
+ */
28
+ function boardCardRenderersForTest() {
29
+ return boardCardRenderers;
30
+ }
31
+ function getBoardCardRenderer(item, context) {
32
+ ensureBoardTextMeasurement();
33
+ return boardCardRenderers.find((renderer) => renderer.canRender(item, context)) ?? unknownCardRenderer;
34
+ }
35
+ function registerBoardCardRenderer(renderer) {
36
+ const existingIndex = boardCardRenderers.findIndex((candidate) => candidate.id === renderer.id);
37
+ if (existingIndex >= 0) boardCardRenderers.splice(existingIndex, 1, renderer);
38
+ else boardCardRenderers.unshift(renderer);
39
+ }
40
+ //#endregion
41
+ export { boardCardRenderersForTest, getBoardCardRenderer, registerBoardCardRenderer };
@@ -0,0 +1,5 @@
1
+ import { BoardCardRenderer } from "./board-renderer-registry.js";
2
+ //#region src/board/render/renderers/draw-card-renderer.d.ts
3
+ declare const drawCardRenderer: BoardCardRenderer;
4
+ //#endregion
5
+ export { drawCardRenderer };
@@ -0,0 +1,82 @@
1
+ import { buildStrokeOutline, computeDrawBounds } from "../../core/draw-geometry.js";
2
+ import { pickBoardColor } from "../../core/palette.js";
3
+ import { drawFarStroke } from "./far-plate.js";
4
+ import { positionShell } from "./base-card-renderer.js";
5
+ import { Container, Graphics } from "pixi.js";
6
+ //#region src/board/render/renderers/draw-card-renderer.ts
7
+ const partsByContainer = /* @__PURE__ */ new WeakMap();
8
+ function sync(container, item, context) {
9
+ const parts = partsByContainer.get(container);
10
+ if (!parts) return;
11
+ positionShell(parts.root, item);
12
+ const selected = context.selectedIds.has(item.id);
13
+ const hovered = context.hoveredId === item.id;
14
+ const color = pickBoardColor(context.colors, item.color, context.colorScheme);
15
+ const sig = [
16
+ item.points.length,
17
+ item.size,
18
+ item.color,
19
+ selected,
20
+ hovered,
21
+ context.colorScheme,
22
+ color.stroke
23
+ ].join("|");
24
+ if (sig !== parts.sig || item.points !== parts.points) {
25
+ parts.sig = sig;
26
+ parts.points = item.points;
27
+ parts.baseWidth = computeDrawBounds(item.points, item.size).width;
28
+ parts.stroke.clear();
29
+ const outline = buildStrokeOutline(item.points, item.size);
30
+ const origin = outline[0];
31
+ if (origin && outline.length >= 3) {
32
+ parts.stroke.moveTo(origin.x, origin.y);
33
+ for (let i = 1; i < outline.length; i += 1) {
34
+ const point = outline[i];
35
+ if (point) parts.stroke.lineTo(point.x, point.y);
36
+ }
37
+ parts.stroke.closePath();
38
+ parts.stroke.fill({
39
+ color: color.stroke,
40
+ alpha: selected || hovered ? 1 : .92
41
+ });
42
+ }
43
+ }
44
+ const previewScale = item.frame.width / Math.max(1e-4, parts.baseWidth);
45
+ parts.stroke.scale.set(Number.isFinite(previewScale) ? previewScale : 1);
46
+ }
47
+ const drawCardRenderer = {
48
+ id: "draw-card",
49
+ canRender: (item) => item.type === "draw",
50
+ create: (item, context) => {
51
+ const root = new Container();
52
+ const stroke = new Graphics();
53
+ root.addChild(stroke);
54
+ partsByContainer.set(root, {
55
+ root,
56
+ stroke,
57
+ sig: "",
58
+ points: null,
59
+ baseWidth: 0
60
+ });
61
+ if (item.type === "draw") sync(root, item, context);
62
+ return root;
63
+ },
64
+ update: (container, item, context) => {
65
+ if (item.type === "draw") sync(container, item, context);
66
+ },
67
+ renderFar: (graphics, item, context) => {
68
+ if (item.type !== "draw") return;
69
+ const color = pickBoardColor(context.colors, item.color, context.colorScheme);
70
+ drawFarStroke(graphics, item.points, {
71
+ color: color.stroke,
72
+ width: item.size,
73
+ alpha: .92
74
+ });
75
+ },
76
+ destroy: (container) => {
77
+ partsByContainer.get(container)?.root.destroy({ children: true });
78
+ partsByContainer.delete(container);
79
+ }
80
+ };
81
+ //#endregion
82
+ export { drawCardRenderer };
@@ -0,0 +1,39 @@
1
+ import { BoardFrame } from "../../../protocol/dist/board-document.js";
2
+ import { Graphics } from "pixi.js";
3
+ //#region src/board/render/renderers/far-plate.d.ts
4
+ type FarPlateStyle = {
5
+ /** Plate body fill. */
6
+ fill: number;
7
+ fillAlpha?: number;
8
+ /** Optional accent band along the top edge, identifying the content kind. */
9
+ accent?: number;
10
+ accentAlpha?: number;
11
+ };
12
+ /**
13
+ * Draw one card as a far-LOD plate: a filled body plus an optional top accent
14
+ * band. Selection and hover are intentionally not drawn here — the interaction
15
+ * overlay already renders selection in world space, above this layer.
16
+ */
17
+ declare function drawFarPlate(graphics: Graphics, frame: BoardFrame, style: FarPlateStyle): void;
18
+ /**
19
+ * Draw one card as a far-LOD polyline.
20
+ *
21
+ * The counterpart to `drawFarPlate` for shapes whose meaning is a path rather than
22
+ * a region — strokes and arrows. It stays in the shared `Graphics`, so these keep
23
+ * their place in the batch and therefore in document order: an unbatched shape
24
+ * would be a live container, drawn above every plate no matter where the document
25
+ * puts it.
26
+ *
27
+ * Points are decimated to a bound, because a freehand stroke can hold thousands of
28
+ * samples that are sub-pixel apart at this zoom.
29
+ */
30
+ declare function drawFarStroke(graphics: Graphics, points: ReadonlyArray<{
31
+ x: number;
32
+ y: number;
33
+ }>, style: {
34
+ color: number;
35
+ width: number;
36
+ alpha?: number;
37
+ }): void;
38
+ //#endregion
39
+ export { FarPlateStyle, drawFarPlate, drawFarStroke };
@@ -0,0 +1,92 @@
1
+ import { degToRad } from "../../geometry.js";
2
+ //#region src/board/render/renderers/far-plate.ts
3
+ /** Upper bound on points kept when a stroke is drawn at far LOD. */
4
+ const FAR_STROKE_MAX_POINTS = 24;
5
+ /** Accent band height as a fraction of the plate, clamped to sane world units. */
6
+ const ACCENT_RATIO = .14;
7
+ const ACCENT_MIN = 2;
8
+ const ACCENT_MAX = 10;
9
+ /**
10
+ * Trace a frame's rectangle, honouring rotation. Unrotated frames take the
11
+ * fast `rect` path; rotated ones emit four explicit corners (still one path,
12
+ * no arcs) so the far layer never needs a per-card transform.
13
+ */
14
+ function traceFrame(graphics, frame, inset = 0, heightOverride) {
15
+ const x = frame.x + inset;
16
+ const y = frame.y + inset;
17
+ const width = Math.max(0, frame.width - inset * 2);
18
+ const height = Math.max(0, (heightOverride ?? frame.height) - inset * 2);
19
+ if (!frame.rotation) {
20
+ graphics.rect(x, y, width, height);
21
+ return;
22
+ }
23
+ const cx = frame.x + frame.width / 2;
24
+ const cy = frame.y + frame.height / 2;
25
+ const angle = degToRad(frame.rotation);
26
+ const cos = Math.cos(angle);
27
+ const sin = Math.sin(angle);
28
+ const corner = (px, py) => {
29
+ const dx = px - cx;
30
+ const dy = py - cy;
31
+ return {
32
+ x: cx + dx * cos - dy * sin,
33
+ y: cy + dx * sin + dy * cos
34
+ };
35
+ };
36
+ const a = corner(x, y);
37
+ const b = corner(x + width, y);
38
+ const c = corner(x + width, y + height);
39
+ const d = corner(x, y + height);
40
+ graphics.moveTo(a.x, a.y).lineTo(b.x, b.y).lineTo(c.x, c.y).lineTo(d.x, d.y).closePath();
41
+ }
42
+ /**
43
+ * Draw one card as a far-LOD plate: a filled body plus an optional top accent
44
+ * band. Selection and hover are intentionally not drawn here — the interaction
45
+ * overlay already renders selection in world space, above this layer.
46
+ */
47
+ function drawFarPlate(graphics, frame, style) {
48
+ if (frame.width <= 0 || frame.height <= 0) return;
49
+ traceFrame(graphics, frame);
50
+ graphics.fill({
51
+ color: style.fill,
52
+ alpha: style.fillAlpha ?? 1
53
+ });
54
+ if (style.accent === void 0) return;
55
+ traceFrame(graphics, frame, 0, Math.min(ACCENT_MAX, Math.max(ACCENT_MIN, frame.height * ACCENT_RATIO)));
56
+ graphics.fill({
57
+ color: style.accent,
58
+ alpha: style.accentAlpha ?? .9
59
+ });
60
+ }
61
+ /**
62
+ * Draw one card as a far-LOD polyline.
63
+ *
64
+ * The counterpart to `drawFarPlate` for shapes whose meaning is a path rather than
65
+ * a region — strokes and arrows. It stays in the shared `Graphics`, so these keep
66
+ * their place in the batch and therefore in document order: an unbatched shape
67
+ * would be a live container, drawn above every plate no matter where the document
68
+ * puts it.
69
+ *
70
+ * Points are decimated to a bound, because a freehand stroke can hold thousands of
71
+ * samples that are sub-pixel apart at this zoom.
72
+ */
73
+ function drawFarStroke(graphics, points, style) {
74
+ if (points.length < 2) return;
75
+ const step = Math.max(1, Math.ceil(points.length / FAR_STROKE_MAX_POINTS));
76
+ const first = points[0];
77
+ if (!first) return;
78
+ graphics.moveTo(first.x, first.y);
79
+ for (let i = step; i < points.length; i += step) {
80
+ const point = points[i];
81
+ if (point) graphics.lineTo(point.x, point.y);
82
+ }
83
+ const last = points[points.length - 1];
84
+ if (last) graphics.lineTo(last.x, last.y);
85
+ graphics.stroke({
86
+ color: style.color,
87
+ width: style.width,
88
+ alpha: style.alpha ?? .9
89
+ });
90
+ }
91
+ //#endregion
92
+ export { drawFarPlate, drawFarStroke };
@@ -0,0 +1,40 @@
1
+ import { BoardFileItem, BoardItem } from "../../../protocol/dist/board-document.js";
2
+ import { BoardCardRenderer } from "./board-renderer-registry.js";
3
+ import { Text } from "pixi.js";
4
+ //#region src/board/render/renderers/file-card-renderer.d.ts
5
+ /**
6
+ * Join already-wrapped lines and append an ellipsis to the last one.
7
+ * Pure helper so the clamp can be unit-tested without a Pixi canvas.
8
+ */
9
+ declare function ellipsizeWrappedLines(lines: string[], maxLines: number): string;
10
+ /**
11
+ * Shorten `line` so `line + "…"` fits in `wrapWidth`.
12
+ *
13
+ * Binary search over the prefix: O(log n) single-line measures. Callers pass a
14
+ * measure callback so this stays free of Pixi types and easy to unit-test.
15
+ */
16
+ declare function fitLineWithEllipsis(line: string, wrapWidth: number, measureWidth: (value: string) => number): string;
17
+ /**
18
+ * Fit a Pixi text node into a fixed line budget.
19
+ *
20
+ * Pixi Text has no built-in max-lines / ellipsis, so we measure with the same
21
+ * style the node will render, keep the leading wrapped lines, and mark a cut
22
+ * with an ellipsis. Returning early when everything fits avoids an extra
23
+ * texture rebuild on the common path.
24
+ *
25
+ * Cost is one wrap of a length-capped sample, plus at most a binary search on
26
+ * the last kept line. No character-by-character multi-line remeasure.
27
+ */
28
+ declare function fitTextToLines(text: Text, value: string, maxLines: number, wrapWidth: number): void;
29
+ /** Scale an image into a fixed cover band without cropping or distortion. */
30
+ declare function containCoverRect(width: number, height: number, imageWidth: number, imageHeight: number): {
31
+ x: number;
32
+ y: number;
33
+ width: number;
34
+ height: number;
35
+ };
36
+ declare const fileCardRenderer: BoardCardRenderer;
37
+ /** Helper kept for type narrowing in tests. */
38
+ declare function isFileItem(item: BoardItem): item is BoardFileItem;
39
+ //#endregion
40
+ export { containCoverRect, ellipsizeWrappedLines, fileCardRenderer, fitLineWithEllipsis, fitTextToLines, isFileItem };