@neta-art/cohub 12.1.0 → 12.2.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 (85) hide show
  1. package/README.md +8 -5
  2. package/dist/board/editor/editor.d.ts +1 -0
  3. package/dist/board/editor/editor.js +4 -0
  4. package/dist/board/editor/snapshots.js +5 -57
  5. package/dist/board/export/index.d.ts +2 -2
  6. package/dist/board/export/node/fonts.js +4 -0
  7. package/dist/board/export/node/index.d.ts +3 -0
  8. package/dist/board/export/node/index.js +13 -1
  9. package/dist/board/export/scene.d.ts +2 -2
  10. package/dist/board/export/scene.js +2 -2
  11. package/dist/board/model/asset-key.d.ts +16 -0
  12. package/dist/board/model/asset-key.js +92 -0
  13. package/dist/board/model/export-assets.d.ts +7 -3
  14. package/dist/board/model/export-assets.js +34 -13
  15. package/dist/board/model/index.d.ts +7 -5
  16. package/dist/board/model/index.js +9 -7
  17. package/dist/board/model/items/create.js +1 -1
  18. package/dist/board/model/media-sizes.d.ts +28 -0
  19. package/dist/board/model/media-sizes.js +64 -0
  20. package/dist/board/model/palette.d.ts +1 -0
  21. package/dist/board/model/palette.js +20 -130
  22. package/dist/board/model/text-metrics.d.ts +8 -3
  23. package/dist/board/model/text-metrics.js +13 -2
  24. package/dist/board/model/themes.d.ts +488 -0
  25. package/dist/board/model/themes.js +495 -0
  26. package/dist/board/render/card-tag.js +2 -2
  27. package/dist/board/render/index.d.ts +6 -4
  28. package/dist/board/render/index.js +5 -3
  29. package/dist/board/render/lod.d.ts +22 -0
  30. package/dist/board/render/lod.js +27 -0
  31. package/dist/board/render/media-interaction.js +1 -1
  32. package/dist/board/render/palette.js +2 -25
  33. package/dist/board/render/renderers/audio-card-renderer.js +2 -1
  34. package/dist/board/render/renderers/board-renderer-registry.d.ts +10 -0
  35. package/dist/board/render/renderers/custom-card-renderer.js +5 -5
  36. package/dist/board/render/renderers/far-plate.d.ts +11 -1
  37. package/dist/board/render/renderers/far-plate.js +27 -8
  38. package/dist/board/render/renderers/file-card-renderer.js +3 -5
  39. package/dist/board/render/renderers/image-card-renderer.js +32 -6
  40. package/dist/board/render/renderers/model-card-renderer.js +1 -1
  41. package/dist/board/render/renderers/shape-card-renderer.js +25 -24
  42. package/dist/board/render/renderers/task-card-renderer.js +7 -8
  43. package/dist/board/render/renderers/text-card-renderer.js +86 -41
  44. package/dist/board/render/renderers/video-card-renderer.js +2 -1
  45. package/dist/board/render/style.d.ts +7 -2
  46. package/dist/board/render/style.js +5 -1
  47. package/dist/board/render/surface.d.ts +5 -0
  48. package/dist/board/render/surface.js +13 -3
  49. package/dist/board/render/text-fit.js +8 -12
  50. package/dist/board/render/text-measurement.d.ts +15 -1
  51. package/dist/board/render/text-measurement.js +113 -20
  52. package/dist/board/render/text-resolution.d.ts +1 -6
  53. package/dist/board/render/text-resolution.js +1 -10
  54. package/dist/board/render/theme.d.ts +15 -0
  55. package/dist/board/render/theme.js +15 -0
  56. package/dist/board/stage/assets.d.ts +15 -4
  57. package/dist/board/stage/assets.js +155 -89
  58. package/dist/board/stage/index.d.ts +5 -5
  59. package/dist/board/stage/index.js +4 -4
  60. package/dist/board/stage/lru.d.ts +13 -9
  61. package/dist/board/stage/lru.js +38 -20
  62. package/dist/board/stage/scene.d.ts +11 -1
  63. package/dist/board/stage/scene.js +213 -138
  64. package/dist/board/stage/stage.d.ts +12 -1
  65. package/dist/board/stage/stage.js +97 -24
  66. package/dist/board/stage/texture-pool.d.ts +8 -1
  67. package/dist/board/stage/texture-pool.js +37 -1
  68. package/dist/chunks/app-realtime.js +1 -1
  69. package/dist/chunks/{transport.js → http-error.js} +37 -1
  70. package/dist/chunks/http.js +30 -10
  71. package/dist/http.js +1 -1
  72. package/dist/index.js +139 -131
  73. package/dist/media.d.ts +3 -27
  74. package/dist/media.js +3 -82
  75. package/dist/protocol/dist/board-constants.js +3 -3
  76. package/dist/protocol/dist/board-layout.d.ts +17 -3
  77. package/dist/protocol/dist/board-layout.js +68 -11
  78. package/dist/protocol/dist/core/media.d.ts +35 -1
  79. package/dist/protocol/dist/core/media.js +123 -1
  80. package/dist/protocol/dist/index.d.ts +2 -2
  81. package/dist/websocket.js +951 -1
  82. package/package.json +3 -2
  83. package/dist/board/model/image-key.d.ts +0 -9
  84. package/dist/board/model/image-key.js +0 -33
  85. package/dist/chunks/websocket.js +0 -988
@@ -13,23 +13,33 @@ function traceCardOutline(graphics, rect, style, grow = 0) {
13
13
  const radius = Math.min(Math.max(0, style.radius + grow), width / 2, height / 2);
14
14
  return radius > 0 ? graphics.roundRect(x, y, width, height, radius) : graphics.rect(x, y, width, height);
15
15
  }
16
+ /**
17
+ * The canvas renderer fills with a repeating pattern, so bilinear samples at the
18
+ * edge would blend in the opposite side. Mapping the picture half a texel inside
19
+ * its source keeps every sample within it on both renderers.
20
+ */
16
21
  function fillCardPicture(graphics, texture, picture, style, crop) {
17
22
  const sampling = texture.source.style;
18
23
  if (sampling.addressMode !== "mirror-repeat") {
19
24
  sampling.addressMode = "mirror-repeat";
20
25
  sampling.update();
21
26
  }
22
- const matrix = crop ? new Matrix(1 / crop.w, 0, 0, 1 / crop.h, -crop.x / crop.w, -crop.y / crop.h) : void 0;
27
+ const { x = 0, y = 0, w = 1, h = 1 } = crop ?? {};
28
+ const insetX = Math.min(.5 / texture.source.pixelWidth, w / 4);
29
+ const insetY = Math.min(.5 / texture.source.pixelHeight, h / 4);
30
+ const sw = w - insetX * 2;
31
+ const sh = h - insetY * 2;
32
+ const matrix = new Matrix(1 / sw, 0, 0, 1 / sh, -(x + insetX) / sw, -(y + insetY) / sh);
23
33
  return traceCardOutline(graphics, picture, style).fill({
24
34
  texture,
25
35
  textureSpace: "local",
26
- ...matrix ? { matrix } : {}
36
+ matrix
27
37
  });
28
38
  }
29
39
  function tracePixelRect(graphics, x, y, width, height, step) {
30
40
  const s = Math.min(step, width / 4, height / 4);
31
41
  if (s <= 0) return graphics.rect(x, y, width, height);
32
- return graphics.poly(pixelRectPoints(x, y, width, height, s).flatMap((point) => [point.x, point.y]));
42
+ return graphics.poly(pixelRectPoints(x, y, width, height, s).flatMap((point) => [point.x, point.y]), true);
33
43
  }
34
44
  function pixelRectPoints(x, y, width, height, s) {
35
45
  const r = x + width;
@@ -1,4 +1,5 @@
1
- import { CanvasTextMetrics } from "pixi.js";
1
+ import { layoutTextLines } from "./text-measurement.js";
2
+ import { CanvasTextMetrics, fontStringFromTextStyle } from "pixi.js";
2
3
  //#region src/board/render/text-fit.ts
3
4
  function ellipsizeWrappedLines(lines, maxLines) {
4
5
  if (maxLines <= 0 || lines.length === 0) return "";
@@ -29,24 +30,19 @@ function fitLineWithEllipsis(line, wrapWidth, measureWidth) {
29
30
  }
30
31
  function fitTextToLines(text, value, maxLines, wrapWidth) {
31
32
  const width = Math.max(1, wrapWidth);
32
- text.style.wordWrapWidth = width;
33
+ text.style.wordWrap = false;
33
34
  if (!value || maxLines <= 0) {
34
35
  if (text.text !== "") text.text = "";
35
36
  return;
36
37
  }
37
38
  const charCap = Math.max(maxLines * Math.ceil(width), maxLines * 4);
38
39
  const sample = value.length > charCap ? value.slice(0, charCap) : value;
39
- const metrics = CanvasTextMetrics.measureText(sample, text.style);
40
- if (!(metrics.lines.length > maxLines || sample !== value)) {
41
- if (text.text !== value) text.text = value;
42
- return;
40
+ const { lines } = layoutTextLines(sample, fontStringFromTextStyle(text.style), width);
41
+ const kept = lines.slice(0, maxLines);
42
+ if (lines.length > maxLines || sample !== value) {
43
+ const lastIndex = kept.length - 1;
44
+ kept[lastIndex] = fitLineWithEllipsis(kept[lastIndex] ?? "", width, (candidate) => CanvasTextMetrics.measureText(candidate, text.style).width);
43
45
  }
44
- const keptCount = Math.min(maxLines, metrics.lines.length);
45
- const kept = metrics.lines.slice(0, keptCount);
46
- const lastIndex = kept.length - 1;
47
- const probe = text.style.clone();
48
- probe.wordWrap = false;
49
- kept[lastIndex] = fitLineWithEllipsis(kept[lastIndex] ?? "", width, (candidate) => CanvasTextMetrics.measureText(candidate, probe).width);
50
46
  const fitted = kept.join("\n");
51
47
  if (text.text !== fitted) text.text = fitted;
52
48
  }
@@ -1,10 +1,24 @@
1
+ import { BoardTextFont, BoardTextLines } from "../../protocol/dist/board-layout.js";
2
+ import "../../protocol/dist/index.js";
3
+ import { TextStyle } from "pixi.js";
1
4
  //#region src/board/render/text-measurement.d.ts
2
5
  declare const BOARD_FONT_STACKS: {
3
6
  sans: string;
4
7
  serif: string;
5
8
  mono: string;
6
9
  };
10
+ type BoardTextStyle = {
11
+ fontFamily: string;
12
+ fontSize: number;
13
+ fontWeight: string;
14
+ };
15
+ declare function canvasFontFamily(families: string): string;
16
+ declare function boardTextStyle({ font, fontSize, fontWeight }: BoardTextFont): BoardTextStyle;
17
+ declare function canvasFont({ fontFamily, fontSize, fontWeight }: BoardTextStyle): string;
18
+ declare function layoutTextLines(text: string, font: string, maxWidth?: number): BoardTextLines;
19
+ declare function cssBaselineShift(style: TextStyle): number;
7
20
  declare function installBoardTextMeasurement(): void;
8
21
  declare function ensureBoardTextMeasurement(): void;
22
+ declare function onBoardTextFontsChange(listener: () => void): () => void;
9
23
  //#endregion
10
- export { BOARD_FONT_STACKS, ensureBoardTextMeasurement, installBoardTextMeasurement };
24
+ export { BOARD_FONT_STACKS, BoardTextStyle, boardTextStyle, canvasFont, canvasFontFamily, cssBaselineShift, ensureBoardTextMeasurement, installBoardTextMeasurement, layoutTextLines, onBoardTextFontsChange };
@@ -1,38 +1,131 @@
1
1
  import { BOARD_FONT_STACK, BOARD_MONO_FONT_STACK } from "../../protocol/dist/board-constants.js";
2
- import { setBoardTextMeasurer } from "../../protocol/dist/board-layout.js";
2
+ import { clampBoardFontSize, setBoardTextLayouter } from "../../protocol/dist/board-layout.js";
3
3
  import { boardFontFamily } from "../model/text-metrics.js";
4
- import { DOMAdapter } from "pixi.js";
4
+ import { CanvasTextMetrics, DOMAdapter, fontStringFromTextStyle } from "pixi.js";
5
+ import { clearCache, layoutWithLines, prepareWithSegments } from "@chenglou/pretext";
5
6
  //#region src/board/render/text-measurement.ts
6
7
  const BOARD_FONT_STACKS = {
7
8
  sans: BOARD_FONT_STACK,
8
- serif: "ui-serif, Georgia, \"Noto Serif CJK SC\", \"Songti SC\", \"Noto Sans Math\", \"DejaVu Sans\", \"Noto Sans CJK SC\", \"FreeSans\", \"FreeSerif\", sans-serif",
9
+ serif: "ui-serif, Georgia, \"Noto Serif CJK SC\", \"Songti SC\", \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Noto Color Emoji\", \"Noto Sans Math\", \"DejaVu Sans\", \"Noto Sans CJK SC\", \"FreeSans\", \"FreeSerif\", sans-serif",
9
10
  mono: BOARD_MONO_FONT_STACK
10
11
  };
11
- let measureContext;
12
- let installed = false;
13
- function getMeasureContext() {
14
- if (measureContext !== void 0) return measureContext;
15
- try {
16
- measureContext = DOMAdapter.get().createCanvas(1, 1).getContext("2d") ?? null;
17
- } catch {
18
- measureContext = null;
12
+ const GENERIC_FAMILIES = /* @__PURE__ */ new Set([
13
+ "serif",
14
+ "sans-serif",
15
+ "monospace",
16
+ "cursive",
17
+ "fantasy",
18
+ "system-ui"
19
+ ]);
20
+ const QUOTED = /^(["']).+\1$/;
21
+ const familyLists = /* @__PURE__ */ new Map();
22
+ function canvasFontFamily(families) {
23
+ let list = familyLists.get(families);
24
+ if (list === void 0) {
25
+ list = families.split(",").map((name) => name.trim()).filter(Boolean).map((name) => QUOTED.test(name) || GENERIC_FAMILIES.has(name) ? name : `"${name}"`).join(", ");
26
+ familyLists.set(families, list);
19
27
  }
20
- return measureContext;
28
+ return list;
29
+ }
30
+ function boardTextStyle({ font, fontSize, fontWeight }) {
31
+ return {
32
+ fontFamily: canvasFontFamily(boardFontFamily(font, BOARD_FONT_STACKS)),
33
+ fontSize: clampBoardFontSize(fontSize),
34
+ fontWeight: String(Math.round(fontWeight / 100) * 100)
35
+ };
21
36
  }
22
- function measureText(text, fontSize, fontWeight, font) {
23
- const context = getMeasureContext();
24
- if (!context) return null;
25
- context.font = `${fontWeight} ${fontSize}px ${boardFontFamily(font, BOARD_FONT_STACKS)}`;
26
- return context.measureText(text).width;
37
+ function canvasFont({ fontFamily, fontSize, fontWeight }) {
38
+ return `${fontWeight} ${fontSize}px ${fontFamily}`;
39
+ }
40
+ const PREPARED_LIMIT = 256;
41
+ const prepared = /* @__PURE__ */ new Map();
42
+ const measuredFonts = /* @__PURE__ */ new Set();
43
+ const measuredFamilies = /* @__PURE__ */ new Set();
44
+ const TRAILING_SPACE = /[ \t]+$/u;
45
+ const unquote = (name) => name.trim().replace(/^["']|["']$/gu, "");
46
+ function prepareText(text, font) {
47
+ const key = `${font}\n${text}`;
48
+ let value = prepared.get(key);
49
+ if (value) {
50
+ prepared.delete(key);
51
+ prepared.set(key, value);
52
+ return value;
53
+ }
54
+ if (!measuredFonts.has(font)) {
55
+ measuredFonts.add(font);
56
+ for (const name of /px\s+(.+)$/u.exec(font)?.[1]?.split(",") ?? []) measuredFamilies.add(unquote(name));
57
+ }
58
+ value = prepareWithSegments(text, font, { whiteSpace: "pre-wrap" });
59
+ if (prepared.size >= PREPARED_LIMIT) prepared.delete(prepared.keys().next().value);
60
+ prepared.set(key, value);
61
+ return value;
62
+ }
63
+ function layoutTextLines(text, font, maxWidth = Number.POSITIVE_INFINITY) {
64
+ const lines = [];
65
+ let width = 0;
66
+ for (const paragraph of text.replace(/\r\n?/gu, "\n").replaceAll(" ", " ").split("\n")) {
67
+ if (!paragraph) {
68
+ lines.push("");
69
+ continue;
70
+ }
71
+ const wrapped = layoutWithLines(prepareText(paragraph, font), maxWidth, 1).lines;
72
+ for (const [index, line] of wrapped.entries()) {
73
+ lines.push(index === wrapped.length - 1 ? line.text : line.text.replace(TRAILING_SPACE, ""));
74
+ width = Math.max(width, line.width);
75
+ }
76
+ }
77
+ return {
78
+ lines,
79
+ width
80
+ };
81
+ }
82
+ const baselineShifts = /* @__PURE__ */ new Map();
83
+ function cssBaselineShift(style) {
84
+ const font = fontStringFromTextStyle(style);
85
+ let shift = baselineShifts.get(font);
86
+ if (shift === void 0) {
87
+ const ink = CanvasTextMetrics.measureFont(font);
88
+ const context = DOMAdapter.get().createCanvas(1, 1).getContext("2d");
89
+ shift = 0;
90
+ if (context) {
91
+ context.font = font;
92
+ const { fontBoundingBoxAscent: ascent, fontBoundingBoxDescent: descent } = context.measureText("M");
93
+ if (Number.isFinite(ascent) && Number.isFinite(descent)) shift = (ascent - descent - (ink.ascent - ink.descent)) / 2;
94
+ }
95
+ baselineShifts.set(font, shift);
96
+ }
97
+ return shift;
98
+ }
99
+ const layoutBoardLines = (text, font, maxWidth) => layoutTextLines(text, canvasFont(boardTextStyle(font)), maxWidth);
100
+ const canMeasure = () => typeof OffscreenCanvas !== "undefined" || typeof document !== "undefined";
101
+ let installed = false;
102
+ let fontsChanged = false;
103
+ const fontListeners = /* @__PURE__ */ new Set();
104
+ function notifyFontsChanged() {
105
+ fontsChanged = false;
106
+ installBoardTextMeasurement();
107
+ for (const listener of fontListeners) listener();
27
108
  }
28
109
  function installBoardTextMeasurement() {
29
- measureContext = void 0;
30
110
  installed = true;
31
- setBoardTextMeasurer(measureText);
111
+ prepared.clear();
112
+ baselineShifts.clear();
113
+ clearCache();
114
+ CanvasTextMetrics.clearMetrics();
115
+ setBoardTextLayouter(canMeasure() ? layoutBoardLines : void 0);
32
116
  }
33
117
  function ensureBoardTextMeasurement() {
34
118
  if (installed) return;
35
119
  installBoardTextMeasurement();
120
+ globalThis.document?.fonts?.addEventListener("loadingdone", (event) => {
121
+ if (fontsChanged || !event.fontfaces.some((face) => measuredFamilies.has(unquote(face.family)))) return;
122
+ fontsChanged = true;
123
+ requestAnimationFrame(notifyFontsChanged);
124
+ });
125
+ }
126
+ function onBoardTextFontsChange(listener) {
127
+ fontListeners.add(listener);
128
+ return () => fontListeners.delete(listener);
36
129
  }
37
130
  //#endregion
38
- export { BOARD_FONT_STACKS, ensureBoardTextMeasurement, installBoardTextMeasurement };
131
+ export { BOARD_FONT_STACKS, boardTextStyle, canvasFont, canvasFontFamily, cssBaselineShift, ensureBoardTextMeasurement, installBoardTextMeasurement, layoutTextLines, onBoardTextFontsChange };
@@ -5,13 +5,8 @@ declare function getBoardResolution(size?: {
5
5
  height: number;
6
6
  }, devicePixelRatio?: number): number;
7
7
  declare function textZoomBucket(zoom: number): number;
8
- declare function boardCardDetailed(zoom: number): boolean;
9
8
  /** Exact screen density at rest; reuse or step the raster while moving. */
10
9
  declare function boardTextResolution(target: number, current: number, settled: boolean): number;
11
10
  declare function syncTextResolution(text: Text, resolution: number): void;
12
- /** Defer expensive Pixi word-wrap rasterisation during a live resize. */
13
- declare function syncTextWrapWidth(text: Text, state: {
14
- wrapWidth: number;
15
- }, width: number, defer: boolean): void;
16
11
  //#endregion
17
- export { boardCardDetailed, boardTextResolution, getBoardResolution, syncTextResolution, syncTextWrapWidth, textZoomBucket };
12
+ export { boardTextResolution, getBoardResolution, syncTextResolution, textZoomBucket };
@@ -23,9 +23,6 @@ function textZoomBucket(zoom) {
23
23
  for (const bucket of ZOOM_BUCKETS) if (clamped <= bucket) return bucket;
24
24
  return ZOOM_BUCKETS[ZOOM_BUCKETS.length - 1] ?? 4;
25
25
  }
26
- function boardCardDetailed(zoom) {
27
- return textZoomBucket(zoom) >= 1;
28
- }
29
26
  /** Exact screen density at rest; reuse or step the raster while moving. */
30
27
  function boardTextResolution(target, current, settled) {
31
28
  if (!Number.isFinite(target) || target <= 0) return current > 0 ? current : 1;
@@ -39,11 +36,5 @@ function syncTextResolution(text, resolution) {
39
36
  const next = Math.min(limit, Math.max(MIN_TEXT_RESOLUTION, resolution));
40
37
  if (text.resolution !== next) text.resolution = next;
41
38
  }
42
- /** Defer expensive Pixi word-wrap rasterisation during a live resize. */
43
- function syncTextWrapWidth(text, state, width, defer) {
44
- if (defer || width === state.wrapWidth) return;
45
- text.style.wordWrapWidth = width;
46
- state.wrapWidth = width;
47
- }
48
39
  //#endregion
49
- export { boardCardDetailed, boardTextResolution, getBoardResolution, syncTextResolution, syncTextWrapWidth, textZoomBucket };
40
+ export { boardTextResolution, getBoardResolution, syncTextResolution, textZoomBucket };
@@ -0,0 +1,15 @@
1
+ import { BoardShapeColors } from "../model/palette.js";
2
+ import { BoardThemeId } from "../model/themes.js";
3
+ import { BoardRenderStyle } from "./style.js";
4
+ import { BoardRenderPalette } from "./renderers/board-renderer-registry.js";
5
+ //#region src/board/render/theme.d.ts
6
+ type BoardRenderTheme = {
7
+ colorScheme: "dark" | "light";
8
+ palette: BoardRenderPalette;
9
+ colors: BoardShapeColors;
10
+ style: BoardRenderStyle;
11
+ };
12
+ /** A built-in Cohub theme as the app draws it; spread it into export or sampler options. */
13
+ declare function boardRenderTheme(id: BoardThemeId): BoardRenderTheme;
14
+ //#endregion
15
+ export { BoardRenderTheme, boardRenderTheme };
@@ -0,0 +1,15 @@
1
+ import { BOARD_THEME_DATA } from "../model/themes.js";
2
+ import { resolveBoardRenderStyle } from "./style.js";
3
+ //#region src/board/render/theme.ts
4
+ /** A built-in Cohub theme as the app draws it; spread it into export or sampler options. */
5
+ function boardRenderTheme(id) {
6
+ const { colorScheme, palette, colors, style } = BOARD_THEME_DATA[id];
7
+ return {
8
+ colorScheme,
9
+ palette: { ...palette },
10
+ colors: Object.fromEntries(Object.entries(colors).map(([key, value]) => [key, { ...value }])),
11
+ style: resolveBoardRenderStyle(colorScheme, style)
12
+ };
13
+ }
14
+ //#endregion
15
+ export { boardRenderTheme };
@@ -1,5 +1,6 @@
1
1
  import { ModelFormat } from "../../protocol/dist/fs/mime.js";
2
2
  import { BoardSceneItem } from "../model/scene.js";
3
+ import { BoardAssetMedia } from "../model/asset-key.js";
3
4
  import { BoardAssetUrlOptions } from "../model/media-playback.js";
4
5
  import "../model/index.js";
5
6
  import { LruBudget } from "./lru.js";
@@ -7,9 +8,6 @@ import { BoardImageTexturePool } from "./texture-pool.js";
7
8
  import { Texture } from "pixi.js";
8
9
  //#region src/board/stage/assets.d.ts
9
10
  type BoardUrlMedia = "image" | "video";
10
- type BoardAssetMedia = BoardUrlMedia | "model";
11
- declare function boardTextureEdge(item: BoardSceneItem, scale: number): number;
12
- declare function boardAssetKey(item: BoardSceneItem): string | null;
13
11
  type Size = {
14
12
  width: number;
15
13
  height: number;
@@ -28,6 +26,11 @@ type BoardAssetManager = {
28
26
  assetKey: (item: BoardSceneItem) => string | null;
29
27
  invalidatePath: (path: string) => void;
30
28
  requestItem: (item: BoardSceneItem, edge?: number) => void;
29
+ /** World point used to prioritize queued loads. */
30
+ focus: (point: {
31
+ x: number;
32
+ y: number;
33
+ } | null) => void;
31
34
  getTexture: (key: string) => Texture | null;
32
35
  getNaturalSize: (key: string) => Size | null;
33
36
  hasError: (key: string) => boolean;
@@ -38,6 +41,12 @@ type BoardAssetManager = {
38
41
  edge?: (item: BoardSceneItem) => number;
39
42
  }) => Promise<T>;
40
43
  subscribe: (listener: (key: string) => void) => () => void;
44
+ /** Current texture and cooling-cache usage. */
45
+ stats: () => {
46
+ textures: number;
47
+ cooling: number;
48
+ bytes: number;
49
+ };
41
50
  destroy: () => void;
42
51
  };
43
52
  type BoardAssetManagerOptions = {
@@ -51,8 +60,10 @@ type BoardAssetManagerOptions = {
51
60
  imageTexturePool?: BoardImageTexturePool;
52
61
  loadTexture?: (url: string, media: BoardUrlMedia) => Promise<Texture | null>;
53
62
  unloadTexture?: (url: string, texture: Texture | null, media: BoardAssetMedia) => Promise<void>;
63
+ /** An unshared copy of an image texture at `edge`; null keeps evicting outright. */
64
+ shrinkTexture?: (texture: Texture, edge: number) => Promise<Texture | null>;
54
65
  now?: () => number;
55
66
  };
56
67
  declare function createBoardAssetManager(options: BoardAssetManagerOptions): BoardAssetManager;
57
68
  //#endregion
58
- export { BoardAssetManager, BoardAssetManagerOptions, BoardModelPreviewRequest, BoardModelPreviews, boardAssetKey, boardTextureEdge, createBoardAssetManager };
69
+ export { BoardAssetManager, BoardAssetManagerOptions, BoardModelPreviewRequest, BoardModelPreviews, createBoardAssetManager };