@swifttui/web 0.4.1 → 0.4.3
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/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/src/CanvasSurfacePainter.d.ts +16 -0
- package/dist/src/CanvasSurfacePainter.js +160 -21
- package/dist/src/CanvasSurfacePainter.js.map +1 -1
- package/dist/src/DomSurfacePainter.d.ts +17 -3
- package/dist/src/DomSurfacePainter.js +50 -5
- package/dist/src/DomSurfacePainter.js.map +1 -1
- package/dist/src/SurfacePainterConformanceControl.js +13 -0
- package/dist/src/SurfacePainterConformanceControl.js.map +1 -0
- package/dist/src/SurfaceRenderer.d.ts +3 -2
- package/dist/src/SurfaceRenderer.js.map +1 -1
- package/dist/src/WebHostSceneRuntime.d.ts +3 -1
- package/dist/src/WebHostSceneRuntime.js +10 -7
- package/dist/src/WebHostSceneRuntime.js.map +1 -1
- package/dist/src/WebHostSurfaceTransport.d.ts +61 -4
- package/dist/src/WebHostSurfaceTransport.js +150 -12
- package/dist/src/WebHostSurfaceTransport.js.map +1 -1
- package/dist/src/WebSocketSceneBridge.d.ts +2 -1
- package/dist/src/WebSocketSceneBridge.js +26 -11
- package/dist/src/WebSocketSceneBridge.js.map +1 -1
- package/dist/src/normalizeWireTokens.d.ts +5 -0
- package/dist/src/wasi/BrowserWASIBridge.d.ts +8 -0
- package/dist/src/wasi/BrowserWASIBridge.js +29 -7
- package/dist/src/wasi/BrowserWASIBridge.js.map +1 -1
- package/dist/src/wasi/SharedInputQueue.d.ts +61 -1
- package/dist/src/wasi/SharedInputQueue.js +108 -0
- package/dist/src/wasi/SharedInputQueue.js.map +1 -1
- package/dist/src/wasi/WasmSceneRuntime.js +38 -8
- package/dist/src/wasi/WasmSceneRuntime.js.map +1 -1
- package/dist/src/wasi/WasmSceneWorker.js +1 -1
- package/dist/testing.d.ts +2 -1
- package/dist/wasi.d.ts +2 -2
- package/dist/wasi.js +1 -1
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { ResolvedWebHostTerminalPalette, ResolvedWebHostTerminalStyle, WebHostANSIColors, WebHostTerminalAppearance, WebHostTerminalCursorStyle, WebHostTerminalPalette, WebHostTerminalRenderStyle, WebHostTerminalStyle, WebHostTerminalTheme, applyWebHostTerminalStyle, decodeWebHostTerminalRenderStyleBase64, encodeWebHostTerminalRenderStyleBase64, mergeWebHostTerminalStyle, normalizeWebHostTerminalStyle, resolveWebHostTerminalRenderStyle, webTUITerminalBackgroundColor } from "./src/WebHostTerminalStyle.js";
|
|
2
|
-
import { SUPPORTED_SURFACE_VERSION, WebHostAccessibilityAnnouncement, WebHostAccessibilityLiveRegion, WebHostAccessibilityNode, WebHostAccessibilityPoint, WebHostFocusPresentation, WebHostFocusSemantics, WebHostFrameDiagnosticRecord, WebHostKeyInput, WebHostMouseInput, WebHostOutputDecoder, WebHostOutputRecord, WebHostOutputSink, WebHostResyncRequest, WebHostRuntimeIssue, WebHostScrollRegion, WebHostSurfaceCell, WebHostSurfaceDamage, WebHostSurfaceDamageRange, WebHostSurfaceDamageTextRow, WebHostSurfaceDeltaFrame, WebHostSurfaceDeltaRow, WebHostSurfaceFrame, WebHostSurfaceImage, WebHostSurfaceImageFormat, WebHostSurfaceLineStyle, WebHostSurfaceLinkRow, WebHostSurfaceLinkRun, WebHostSurfaceRect, WebHostSurfaceSize, WebHostSurfaceStyle, encodeCapabilitiesControlMessage, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage } from "./src/WebHostSurfaceTransport.js";
|
|
2
|
+
import { MAX_IMAGE_RECOVERY_ID_BYTES, MAX_OUTSTANDING_IMAGE_RECOVERY_IDS, SUPPORTED_SURFACE_VERSION, WebHostAccessibilityAnnouncement, WebHostAccessibilityLiveRegion, WebHostAccessibilityNode, WebHostAccessibilityPoint, WebHostFocusPresentation, WebHostFocusSemantics, WebHostFrameDiagnosticRecord, WebHostImagePayloadRequestHandler, WebHostKeyInput, WebHostMouseInput, WebHostOutputDecoder, WebHostOutputRecord, WebHostOutputSink, WebHostResyncRequest, WebHostRuntimeIssue, WebHostScrollRegion, WebHostSurfaceCell, WebHostSurfaceDamage, WebHostSurfaceDamageRange, WebHostSurfaceDamageTextRow, WebHostSurfaceDeltaFrame, WebHostSurfaceDeltaRow, WebHostSurfaceFrame, WebHostSurfaceImage, WebHostSurfaceImageFormat, WebHostSurfaceLineStyle, WebHostSurfaceLinkRow, WebHostSurfaceLinkRun, WebHostSurfaceRect, WebHostSurfaceSize, WebHostSurfaceStyle, encodeCapabilitiesControlMessage, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage, isWebHostImageRecoveryId } from "./src/WebHostSurfaceTransport.js";
|
|
3
3
|
import { SurfaceMetrics, WebHostSurfacePainter, WebHostSurfaceRendererKind, resolvedSurfaceBackground, resolvedSurfaceForeground } from "./src/SurfaceRenderer.js";
|
|
4
4
|
import { WebHostSceneDescriptor, WebHostSceneManifest, WebHostSceneManifestSource, loadWebHostSceneManifest, normalizeWebHostSceneManifest, webTUISceneManifestFromDescriptors, webTUISceneManifestToJSON } from "./src/WebHostSceneManifest.js";
|
|
5
5
|
import { WebHostSceneBridge, WebHostSceneRuntime, WebHostSceneRuntimeOptions, WheelMode } from "./src/WebHostSceneRuntime.js";
|
|
6
6
|
import { WebSocketSceneBridge, WebSocketSceneBridgeFactory, WebSocketSceneBridgeOptions, WebSocketSceneSocket, webSocketSceneURL } from "./src/WebSocketSceneBridge.js";
|
|
7
7
|
import { WebHostAppController, WebHostAppOptions, WebHostBridgeFactory, WebHostBridgeFactoryOptions, WebHostEmbeddedHostConfig, WebHostVisibilityDocument, createWebHostApp } from "./src/WebHostApp.js";
|
|
8
|
-
import { DomSurfacePainter } from "./src/DomSurfacePainter.js";
|
|
8
|
+
import { DomSurfacePainter, DomSurfacePainterOptions } from "./src/DomSurfacePainter.js";
|
|
9
9
|
import { StdIOPipe } from "./src/wasi/StdIOPipe.js";
|
|
10
10
|
import { JSPIConstructors, WasmEngineCapabilities, WasmEngineFamily, WasmEngineProbeSignals, classifyWasmEngineFamily, collectWasmEngineProbeSignals, jspiConstructors, mainThreadStackProfileEnvironmentDefaults, resolveWasmEngineCapabilities, stackProfileEnvironmentDefaults } from "./src/wasi/WasmEngineCapabilities.js";
|
|
11
11
|
import { BrowserWASIBridge, BrowserWASIBridgeOptions, BrowserWASIOutputSink } from "./src/wasi/BrowserWASIBridge.js";
|
|
12
|
-
export { BrowserWASIBridge, BrowserWASIBridgeOptions, BrowserWASIOutputSink, DomSurfacePainter, JSPIConstructors, ResolvedWebHostTerminalPalette, ResolvedWebHostTerminalStyle, SUPPORTED_SURFACE_VERSION, StdIOPipe, SurfaceMetrics, WasmEngineCapabilities, WasmEngineFamily, WasmEngineProbeSignals, WebHostANSIColors, WebHostAccessibilityAnnouncement, WebHostAccessibilityLiveRegion, WebHostAccessibilityNode, WebHostAccessibilityPoint, WebHostAppController, WebHostAppOptions, WebHostBridgeFactory, WebHostBridgeFactoryOptions, WebHostEmbeddedHostConfig, WebHostFocusPresentation, WebHostFocusSemantics, WebHostFrameDiagnosticRecord, WebHostKeyInput, WebHostMouseInput, WebHostOutputDecoder, WebHostOutputRecord, WebHostOutputSink, WebHostResyncRequest, WebHostRuntimeIssue, WebHostSceneBridge, WebHostSceneDescriptor, WebHostSceneManifest, WebHostSceneManifestSource, WebHostSceneRuntime, WebHostSceneRuntimeOptions, WebHostScrollRegion, WebHostSurfaceCell, WebHostSurfaceDamage, WebHostSurfaceDamageRange, WebHostSurfaceDamageTextRow, WebHostSurfaceDeltaFrame, WebHostSurfaceDeltaRow, WebHostSurfaceFrame, WebHostSurfaceImage, WebHostSurfaceImageFormat, WebHostSurfaceLineStyle, WebHostSurfaceLinkRow, WebHostSurfaceLinkRun, WebHostSurfacePainter, WebHostSurfaceRect, WebHostSurfaceRendererKind, WebHostSurfaceSize, WebHostSurfaceStyle, WebHostTerminalAppearance, WebHostTerminalCursorStyle, WebHostTerminalPalette, WebHostTerminalRenderStyle, WebHostTerminalStyle, WebHostTerminalTheme, WebHostVisibilityDocument, WebSocketSceneBridge, WebSocketSceneBridgeFactory, WebSocketSceneBridgeOptions, WebSocketSceneSocket, WheelMode, applyWebHostTerminalStyle, classifyWasmEngineFamily, collectWasmEngineProbeSignals, createWebHostApp, decodeWebHostTerminalRenderStyleBase64, encodeCapabilitiesControlMessage, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage, encodeWebHostTerminalRenderStyleBase64, jspiConstructors, loadWebHostSceneManifest, mainThreadStackProfileEnvironmentDefaults, mergeWebHostTerminalStyle, normalizeWebHostSceneManifest, normalizeWebHostTerminalStyle, resolveWasmEngineCapabilities, resolveWebHostTerminalRenderStyle, resolvedSurfaceBackground, resolvedSurfaceForeground, stackProfileEnvironmentDefaults, webSocketSceneURL, webTUISceneManifestFromDescriptors, webTUISceneManifestToJSON, webTUITerminalBackgroundColor };
|
|
12
|
+
export { BrowserWASIBridge, BrowserWASIBridgeOptions, BrowserWASIOutputSink, DomSurfacePainter, DomSurfacePainterOptions, JSPIConstructors, MAX_IMAGE_RECOVERY_ID_BYTES, MAX_OUTSTANDING_IMAGE_RECOVERY_IDS, ResolvedWebHostTerminalPalette, ResolvedWebHostTerminalStyle, SUPPORTED_SURFACE_VERSION, StdIOPipe, SurfaceMetrics, WasmEngineCapabilities, WasmEngineFamily, WasmEngineProbeSignals, WebHostANSIColors, WebHostAccessibilityAnnouncement, WebHostAccessibilityLiveRegion, WebHostAccessibilityNode, WebHostAccessibilityPoint, WebHostAppController, WebHostAppOptions, WebHostBridgeFactory, WebHostBridgeFactoryOptions, WebHostEmbeddedHostConfig, WebHostFocusPresentation, WebHostFocusSemantics, WebHostFrameDiagnosticRecord, WebHostImagePayloadRequestHandler, WebHostKeyInput, WebHostMouseInput, WebHostOutputDecoder, WebHostOutputRecord, WebHostOutputSink, WebHostResyncRequest, WebHostRuntimeIssue, WebHostSceneBridge, WebHostSceneDescriptor, WebHostSceneManifest, WebHostSceneManifestSource, WebHostSceneRuntime, WebHostSceneRuntimeOptions, WebHostScrollRegion, WebHostSurfaceCell, WebHostSurfaceDamage, WebHostSurfaceDamageRange, WebHostSurfaceDamageTextRow, WebHostSurfaceDeltaFrame, WebHostSurfaceDeltaRow, WebHostSurfaceFrame, WebHostSurfaceImage, WebHostSurfaceImageFormat, WebHostSurfaceLineStyle, WebHostSurfaceLinkRow, WebHostSurfaceLinkRun, WebHostSurfacePainter, WebHostSurfaceRect, WebHostSurfaceRendererKind, WebHostSurfaceSize, WebHostSurfaceStyle, WebHostTerminalAppearance, WebHostTerminalCursorStyle, WebHostTerminalPalette, WebHostTerminalRenderStyle, WebHostTerminalStyle, WebHostTerminalTheme, WebHostVisibilityDocument, WebSocketSceneBridge, WebSocketSceneBridgeFactory, WebSocketSceneBridgeOptions, WebSocketSceneSocket, WheelMode, applyWebHostTerminalStyle, classifyWasmEngineFamily, collectWasmEngineProbeSignals, createWebHostApp, decodeWebHostTerminalRenderStyleBase64, encodeCapabilitiesControlMessage, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage, encodeWebHostTerminalRenderStyleBase64, isWebHostImageRecoveryId, jspiConstructors, loadWebHostSceneManifest, mainThreadStackProfileEnvironmentDefaults, mergeWebHostTerminalStyle, normalizeWebHostSceneManifest, normalizeWebHostTerminalStyle, resolveWasmEngineCapabilities, resolveWebHostTerminalRenderStyle, resolvedSurfaceBackground, resolvedSurfaceForeground, stackProfileEnvironmentDefaults, webSocketSceneURL, webTUISceneManifestFromDescriptors, webTUISceneManifestToJSON, webTUITerminalBackgroundColor };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { StdIOPipe } from "./src/wasi/StdIOPipe.js";
|
|
2
2
|
import { classifyWasmEngineFamily, collectWasmEngineProbeSignals, jspiConstructors, mainThreadStackProfileEnvironmentDefaults, resolveWasmEngineCapabilities, stackProfileEnvironmentDefaults } from "./src/wasi/WasmEngineCapabilities.js";
|
|
3
3
|
import { applyWebHostTerminalStyle, decodeWebHostTerminalRenderStyleBase64, encodeWebHostTerminalRenderStyleBase64, mergeWebHostTerminalStyle, normalizeWebHostTerminalStyle, resolveWebHostTerminalRenderStyle, webTUITerminalBackgroundColor } from "./src/WebHostTerminalStyle.js";
|
|
4
|
-
import { SUPPORTED_SURFACE_VERSION, WebHostOutputDecoder, encodeCapabilitiesControlMessage, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage } from "./src/WebHostSurfaceTransport.js";
|
|
4
|
+
import { MAX_IMAGE_RECOVERY_ID_BYTES, MAX_OUTSTANDING_IMAGE_RECOVERY_IDS, SUPPORTED_SURFACE_VERSION, WebHostOutputDecoder, encodeCapabilitiesControlMessage, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage, isWebHostImageRecoveryId } from "./src/WebHostSurfaceTransport.js";
|
|
5
5
|
import { BrowserWASIBridge } from "./src/wasi/BrowserWASIBridge.js";
|
|
6
6
|
import { WebSocketSceneBridge, webSocketSceneURL } from "./src/WebSocketSceneBridge.js";
|
|
7
7
|
import { loadWebHostSceneManifest, normalizeWebHostSceneManifest, webTUISceneManifestFromDescriptors, webTUISceneManifestToJSON } from "./src/WebHostSceneManifest.js";
|
|
@@ -9,4 +9,4 @@ import { resolvedSurfaceBackground, resolvedSurfaceForeground } from "./src/Surf
|
|
|
9
9
|
import { DomSurfacePainter } from "./src/DomSurfacePainter.js";
|
|
10
10
|
import { WebHostSceneRuntime } from "./src/WebHostSceneRuntime.js";
|
|
11
11
|
import { createWebHostApp } from "./src/WebHostApp.js";
|
|
12
|
-
export { BrowserWASIBridge, DomSurfacePainter, SUPPORTED_SURFACE_VERSION, StdIOPipe, WebHostOutputDecoder, WebHostSceneRuntime, WebSocketSceneBridge, applyWebHostTerminalStyle, classifyWasmEngineFamily, collectWasmEngineProbeSignals, createWebHostApp, decodeWebHostTerminalRenderStyleBase64, encodeCapabilitiesControlMessage, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage, encodeWebHostTerminalRenderStyleBase64, jspiConstructors, loadWebHostSceneManifest, mainThreadStackProfileEnvironmentDefaults, mergeWebHostTerminalStyle, normalizeWebHostSceneManifest, normalizeWebHostTerminalStyle, resolveWasmEngineCapabilities, resolveWebHostTerminalRenderStyle, resolvedSurfaceBackground, resolvedSurfaceForeground, stackProfileEnvironmentDefaults, webSocketSceneURL, webTUISceneManifestFromDescriptors, webTUISceneManifestToJSON, webTUITerminalBackgroundColor };
|
|
12
|
+
export { BrowserWASIBridge, DomSurfacePainter, MAX_IMAGE_RECOVERY_ID_BYTES, MAX_OUTSTANDING_IMAGE_RECOVERY_IDS, SUPPORTED_SURFACE_VERSION, StdIOPipe, WebHostOutputDecoder, WebHostSceneRuntime, WebSocketSceneBridge, applyWebHostTerminalStyle, classifyWasmEngineFamily, collectWasmEngineProbeSignals, createWebHostApp, decodeWebHostTerminalRenderStyleBase64, encodeCapabilitiesControlMessage, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage, encodeWebHostTerminalRenderStyleBase64, isWebHostImageRecoveryId, jspiConstructors, loadWebHostSceneManifest, mainThreadStackProfileEnvironmentDefaults, mergeWebHostTerminalStyle, normalizeWebHostSceneManifest, normalizeWebHostTerminalStyle, resolveWasmEngineCapabilities, resolveWebHostTerminalRenderStyle, resolvedSurfaceBackground, resolvedSurfaceForeground, stackProfileEnvironmentDefaults, webSocketSceneURL, webTUISceneManifestFromDescriptors, webTUISceneManifestToJSON, webTUITerminalBackgroundColor };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { WebHostImagePayloadRequestHandler } from "./WebHostSurfaceTransport.js";
|
|
2
|
+
import { NormalizedSurfaceImageFormat } from "./normalizeWireTokens.js";
|
|
3
|
+
//#region src/CanvasSurfacePainter.d.ts
|
|
4
|
+
interface CanvasSurfacePainterOptions {
|
|
5
|
+
/** Injectable decode seam for browser hosts and deterministic failure tests. */
|
|
6
|
+
decodeImage?: (dataBase64: string, format: NormalizedSurfaceImageFormat, imageID: string) => Promise<CanvasImageSource>;
|
|
7
|
+
/**
|
|
8
|
+
* Reports supported, positive-area images whose payload cannot be resolved
|
|
9
|
+
* locally. Returning the admitted subset keeps rejected IDs eligible for a
|
|
10
|
+
* later paint; `void` preserves legacy all-accepted behavior.
|
|
11
|
+
*/
|
|
12
|
+
onImagePayloadMiss?: WebHostImagePayloadRequestHandler;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { CanvasSurfacePainterOptions };
|
|
16
|
+
//# sourceMappingURL=CanvasSurfacePainter.d.ts.map
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { webTUITerminalBackgroundColor } from "./WebHostTerminalStyle.js";
|
|
2
|
+
import { isWebHostImageRecoveryId } from "./WebHostSurfaceTransport.js";
|
|
2
3
|
import { canRenderBoxDrawing, drawBoxDrawing } from "./BoxDrawingRenderer.js";
|
|
3
4
|
import { resolvedSurfaceBackground, resolvedSurfaceForeground } from "./SurfaceRenderer.js";
|
|
4
|
-
import { isSupportedImageFormat
|
|
5
|
-
|
|
5
|
+
import { isSupportedImageFormat } from "./normalizeWireTokens.js";
|
|
6
|
+
import { registerCanvasSurfacePainterConformanceControl } from "./SurfacePainterConformanceControl.js";
|
|
7
|
+
const MAX_UNRESOLVED_IMAGE_PAYLOAD_CHARACTERS = 64 * 1024 * 1024;
|
|
6
8
|
/**
|
|
7
9
|
* Draws SwiftTUI surface frames onto a 2D canvas: background fills, per-cell
|
|
8
10
|
* text/box-drawing/decorations, surface images, and damage-scoped dirty-region
|
|
@@ -15,8 +17,29 @@ import { isSupportedImageFormat, normalizeScalingMode } from "./normalizeWireTok
|
|
|
15
17
|
*/
|
|
16
18
|
var CanvasSurfacePainter = class {
|
|
17
19
|
imageCache = /* @__PURE__ */ new Map();
|
|
20
|
+
unresolvedImageIds = /* @__PURE__ */ new Set();
|
|
21
|
+
unresolvedImagePayloadCharacters = 0;
|
|
22
|
+
imageDecoder;
|
|
23
|
+
onImagePayloadMiss;
|
|
18
24
|
canvas;
|
|
19
25
|
requestRedraw = () => {};
|
|
26
|
+
lastEpoch;
|
|
27
|
+
pendingImagePayloadMissIds = /* @__PURE__ */ new Set();
|
|
28
|
+
imagePayloadMissScheduled = false;
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
this.imageDecoder = options.decodeImage ?? decodeImage;
|
|
31
|
+
this.onImagePayloadMiss = options.onImagePayloadMiss ?? (() => {});
|
|
32
|
+
registerCanvasSurfacePainterConformanceControl(this, {
|
|
33
|
+
evictImages: (ids) => {
|
|
34
|
+
for (const id of ids) {
|
|
35
|
+
this.removeUnresolvedImage(id);
|
|
36
|
+
this.imageCache.delete(id);
|
|
37
|
+
this.pendingImagePayloadMissIds.delete(id);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
visibleImageIDs: (images) => [...new Set(images.filter(isPaintableSurfaceImage).filter((image) => this.imageCache.get(image.id)?.image !== void 0).map((image) => image.id))].sort()
|
|
41
|
+
});
|
|
42
|
+
}
|
|
20
43
|
/**
|
|
21
44
|
* Binds the canvas the painter draws into and the callback used to request a
|
|
22
45
|
* full repaint after an asynchronous image decode completes.
|
|
@@ -25,12 +48,21 @@ var CanvasSurfacePainter = class {
|
|
|
25
48
|
this.canvas = canvas;
|
|
26
49
|
this.requestRedraw = requestRedraw;
|
|
27
50
|
}
|
|
28
|
-
paint(metrics, frame, damage) {
|
|
51
|
+
paint(metrics, frame, damage, recoveredImagePayloadIds = []) {
|
|
29
52
|
const canvas = this.canvas;
|
|
30
53
|
const context = canvas?.getContext("2d");
|
|
31
54
|
if (!canvas || !context) return;
|
|
55
|
+
if (frame?.epoch !== void 0 && frame.epoch !== this.lastEpoch) {
|
|
56
|
+
this.lastEpoch = frame.epoch;
|
|
57
|
+
for (const cached of this.imageCache.values()) if (!cached.image) cached.missReported = false;
|
|
58
|
+
}
|
|
59
|
+
this.sweepUnresolvedImages(frame?.images);
|
|
32
60
|
const dirtyRegion = frame ? this.dirtyRegionForDamage(damage, frame, metrics) : void 0;
|
|
33
|
-
|
|
61
|
+
const recoveredPayloadIds = new Set(recoveredImagePayloadIds);
|
|
62
|
+
if (dirtyRegion?.rects.length === 0) {
|
|
63
|
+
this.prepareImages(frame?.images ?? [], recoveredPayloadIds);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
34
66
|
const scale = globalThis.window?.devicePixelRatio || 1;
|
|
35
67
|
context.setTransform(scale, 0, 0, scale, 0, 0);
|
|
36
68
|
context.textBaseline = "alphabetic";
|
|
@@ -45,7 +77,7 @@ var CanvasSurfacePainter = class {
|
|
|
45
77
|
}
|
|
46
78
|
if (!frame) return;
|
|
47
79
|
this.drawRows(context, frame, metrics, dirtyRegion);
|
|
48
|
-
this.drawImages(context, frame.images ?? [], metrics, dirtyRegion);
|
|
80
|
+
this.drawImages(context, frame.images ?? [], metrics, dirtyRegion, recoveredPayloadIds);
|
|
49
81
|
}
|
|
50
82
|
drawRows(context, frame, metrics, dirtyRegion) {
|
|
51
83
|
if (dirtyRegion) {
|
|
@@ -68,21 +100,28 @@ var CanvasSurfacePainter = class {
|
|
|
68
100
|
this.drawCell(context, metrics, x, y, text, span, style);
|
|
69
101
|
}
|
|
70
102
|
}
|
|
71
|
-
drawImages(context, images, metrics, dirtyRegion) {
|
|
103
|
+
drawImages(context, images, metrics, dirtyRegion, recoveredPayloadIds) {
|
|
104
|
+
const missingPayloadIds = /* @__PURE__ */ new Set();
|
|
72
105
|
for (const image of images) {
|
|
73
|
-
if (!
|
|
74
|
-
this.drawImage(context,
|
|
75
|
-
...image,
|
|
76
|
-
scalingMode: normalizeScalingMode(image.scalingMode)
|
|
77
|
-
}, metrics, dirtyRegion);
|
|
106
|
+
if (!isPaintableSurfaceImage(image)) continue;
|
|
107
|
+
this.drawImage(context, image, metrics, dirtyRegion, missingPayloadIds, recoveredPayloadIds);
|
|
78
108
|
}
|
|
109
|
+
this.reportImagePayloadMisses(missingPayloadIds);
|
|
79
110
|
}
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
111
|
+
prepareImages(images, recoveredPayloadIds) {
|
|
112
|
+
const missingPayloadIds = /* @__PURE__ */ new Set();
|
|
113
|
+
for (const image of images) {
|
|
114
|
+
if (!isPaintableSurfaceImage(image)) continue;
|
|
115
|
+
this.cachedImage(image, missingPayloadIds, recoveredPayloadIds);
|
|
116
|
+
}
|
|
117
|
+
this.reportImagePayloadMisses(missingPayloadIds);
|
|
118
|
+
}
|
|
119
|
+
drawImage(context, image, metrics, dirtyRegion, missingPayloadIds, recoveredPayloadIds) {
|
|
83
120
|
const [boundsX, boundsY, boundsWidth, boundsHeight] = image.bounds;
|
|
84
121
|
const [clipX, clipY, clipWidth, clipHeight] = image.visibleBounds;
|
|
85
122
|
if (boundsWidth <= 0 || boundsHeight <= 0 || clipWidth <= 0 || clipHeight <= 0) return;
|
|
123
|
+
const decodedImage = this.cachedImage(image, missingPayloadIds, recoveredPayloadIds);
|
|
124
|
+
if (!decodedImage) return;
|
|
86
125
|
if (dirtyRegion && !dirtyRegionIntersectsCellRect(dirtyRegion, clipX, clipY, clipWidth, clipHeight)) return;
|
|
87
126
|
context.save();
|
|
88
127
|
context.beginPath();
|
|
@@ -91,21 +130,116 @@ var CanvasSurfacePainter = class {
|
|
|
91
130
|
context.drawImage(decodedImage, boundsX * metrics.cellWidth, boundsY * metrics.cellHeight, boundsWidth * metrics.cellWidth, boundsHeight * metrics.cellHeight);
|
|
92
131
|
context.restore();
|
|
93
132
|
}
|
|
94
|
-
cachedImage(image) {
|
|
95
|
-
|
|
133
|
+
cachedImage(image, missingPayloadIds, recoveredPayloadIds) {
|
|
134
|
+
let cached = this.imageCache.get(image.id);
|
|
96
135
|
if (cached?.image) return cached.image;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
this.
|
|
136
|
+
const beginsRecoveredGeneration = image.dataBase64 !== void 0 && recoveredPayloadIds.delete(image.id);
|
|
137
|
+
if (image.dataBase64 !== void 0 && (cached?.payload === void 0 || beginsRecoveredGeneration)) {
|
|
138
|
+
if (!this.canTrackUnresolvedImage(image.id, image.dataBase64)) return;
|
|
139
|
+
cached = {
|
|
140
|
+
payload: image.dataBase64,
|
|
141
|
+
retries: 0
|
|
142
|
+
};
|
|
143
|
+
this.setUnresolvedImage(image.id, cached);
|
|
144
|
+
} else if (!cached) {
|
|
145
|
+
if (!isWebHostImageRecoveryId(image.id)) return;
|
|
146
|
+
if (!this.canTrackUnresolvedImage(image.id)) return;
|
|
147
|
+
cached = { missReported: false };
|
|
148
|
+
this.setUnresolvedImage(image.id, cached);
|
|
149
|
+
missingPayloadIds.add(image.id);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const attempts = cached.retries ?? 0;
|
|
153
|
+
if (cached.payload === void 0) {
|
|
154
|
+
if (!cached.missReported) {
|
|
155
|
+
cached.missReported = true;
|
|
156
|
+
missingPayloadIds.add(image.id);
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (attempts >= 3) {
|
|
161
|
+
if (!cached.missReported) {
|
|
162
|
+
cached.missReported = true;
|
|
163
|
+
missingPayloadIds.add(image.id);
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (!cached.promise) {
|
|
168
|
+
const nextAttempts = attempts + 1;
|
|
169
|
+
const promise = this.imageDecoder(cached.payload, image.format, image.id);
|
|
170
|
+
cached.promise = promise;
|
|
171
|
+
cached.retries = nextAttempts;
|
|
100
172
|
promise.then((decodedImage) => {
|
|
101
173
|
if (this.imageCache.get(image.id)?.promise !== promise) return;
|
|
174
|
+
this.removeUnresolvedImage(image.id);
|
|
102
175
|
this.imageCache.set(image.id, { image: decodedImage });
|
|
103
176
|
this.requestRedraw();
|
|
104
177
|
}).catch(() => {
|
|
105
|
-
this.imageCache.
|
|
178
|
+
const latest = this.imageCache.get(image.id);
|
|
179
|
+
if (latest?.promise !== promise) return;
|
|
180
|
+
latest.promise = void 0;
|
|
181
|
+
if ((latest.retries ?? 0) >= 3 && !latest.missReported) {
|
|
182
|
+
latest.missReported = true;
|
|
183
|
+
if (isWebHostImageRecoveryId(image.id)) this.scheduleImagePayloadMiss(image.id);
|
|
184
|
+
}
|
|
185
|
+
this.requestRedraw();
|
|
106
186
|
});
|
|
107
187
|
}
|
|
108
188
|
}
|
|
189
|
+
canTrackUnresolvedImage(id, payload) {
|
|
190
|
+
const existing = this.imageCache.get(id);
|
|
191
|
+
const existingPayloadCharacters = existing?.image ? 0 : existing?.payload?.length ?? 0;
|
|
192
|
+
const nextPayloadCharacters = this.unresolvedImagePayloadCharacters - existingPayloadCharacters + (payload?.length ?? 0);
|
|
193
|
+
return (this.unresolvedImageIds.has(id) || this.unresolvedImageIds.size < 256) && nextPayloadCharacters <= 67108864;
|
|
194
|
+
}
|
|
195
|
+
setUnresolvedImage(id, cached) {
|
|
196
|
+
const existing = this.imageCache.get(id);
|
|
197
|
+
if (this.unresolvedImageIds.has(id)) this.unresolvedImagePayloadCharacters -= existing?.payload?.length ?? 0;
|
|
198
|
+
this.imageCache.set(id, cached);
|
|
199
|
+
this.unresolvedImageIds.add(id);
|
|
200
|
+
this.unresolvedImagePayloadCharacters += cached.payload?.length ?? 0;
|
|
201
|
+
this.pendingImagePayloadMissIds.delete(id);
|
|
202
|
+
}
|
|
203
|
+
removeUnresolvedImage(id) {
|
|
204
|
+
if (!this.unresolvedImageIds.delete(id)) return;
|
|
205
|
+
this.unresolvedImagePayloadCharacters -= this.imageCache.get(id)?.payload?.length ?? 0;
|
|
206
|
+
this.pendingImagePayloadMissIds.delete(id);
|
|
207
|
+
}
|
|
208
|
+
sweepUnresolvedImages(images) {
|
|
209
|
+
const presentedIds = /* @__PURE__ */ new Set();
|
|
210
|
+
for (const image of images ?? []) {
|
|
211
|
+
if (!this.unresolvedImageIds.has(image.id)) continue;
|
|
212
|
+
if (isPaintableSurfaceImage(image)) presentedIds.add(image.id);
|
|
213
|
+
}
|
|
214
|
+
for (const id of this.unresolvedImageIds) {
|
|
215
|
+
if (presentedIds.has(id)) continue;
|
|
216
|
+
this.removeUnresolvedImage(id);
|
|
217
|
+
this.imageCache.delete(id);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
reportImagePayloadMisses(ids) {
|
|
221
|
+
if (ids.size === 0) return;
|
|
222
|
+
const candidateIds = [...ids].sort();
|
|
223
|
+
this.applyImagePayloadMissAdmission(candidateIds, this.onImagePayloadMiss(candidateIds));
|
|
224
|
+
}
|
|
225
|
+
scheduleImagePayloadMiss(id) {
|
|
226
|
+
this.pendingImagePayloadMissIds.add(id);
|
|
227
|
+
if (this.imagePayloadMissScheduled) return;
|
|
228
|
+
this.imagePayloadMissScheduled = true;
|
|
229
|
+
queueMicrotask(() => {
|
|
230
|
+
this.imagePayloadMissScheduled = false;
|
|
231
|
+
const ids = [...this.pendingImagePayloadMissIds].sort();
|
|
232
|
+
this.pendingImagePayloadMissIds.clear();
|
|
233
|
+
if (ids.length > 0) this.applyImagePayloadMissAdmission(ids, this.onImagePayloadMiss(ids));
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
applyImagePayloadMissAdmission(candidateIds, admittedIds) {
|
|
237
|
+
const acceptedIds = new Set(Array.isArray(admittedIds) ? admittedIds : candidateIds);
|
|
238
|
+
for (const id of candidateIds) {
|
|
239
|
+
const cached = this.imageCache.get(id);
|
|
240
|
+
if (cached && !cached.image) cached.missReported = acceptedIds.has(id);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
109
243
|
drawCell(context, metrics, x, y, text, span, style) {
|
|
110
244
|
const rectX = x * metrics.cellWidth;
|
|
111
245
|
const rectY = y * metrics.cellHeight;
|
|
@@ -180,6 +314,11 @@ var CanvasSurfacePainter = class {
|
|
|
180
314
|
context.setLineDash([]);
|
|
181
315
|
}
|
|
182
316
|
};
|
|
317
|
+
function isPaintableSurfaceImage(image) {
|
|
318
|
+
const [, , boundsWidth, boundsHeight] = image.bounds;
|
|
319
|
+
const [, , clipWidth, clipHeight] = image.visibleBounds;
|
|
320
|
+
return isSupportedImageFormat(image.format) && boundsWidth > 0 && boundsHeight > 0 && clipWidth > 0 && clipHeight > 0;
|
|
321
|
+
}
|
|
183
322
|
/**
|
|
184
323
|
* The CSS font string for a cell, folding the surface emphasis bits (bold,
|
|
185
324
|
* italic) over the host terminal's font size/family. Exposed so the host can
|
|
@@ -258,6 +397,6 @@ function dirtyRegionIntersectsCellRect(region, x, y, width, height) {
|
|
|
258
397
|
return false;
|
|
259
398
|
}
|
|
260
399
|
//#endregion
|
|
261
|
-
export { CanvasSurfacePainter, fontForStyle };
|
|
400
|
+
export { CanvasSurfacePainter, MAX_UNRESOLVED_IMAGE_PAYLOAD_CHARACTERS, fontForStyle };
|
|
262
401
|
|
|
263
402
|
//# sourceMappingURL=CanvasSurfacePainter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CanvasSurfacePainter.js","names":[],"sources":["../../src/CanvasSurfacePainter.ts"],"sourcesContent":["import {\n canRenderBoxDrawing,\n drawBoxDrawing,\n} from \"./BoxDrawingRenderer.ts\";\nimport {\n resolvedSurfaceBackground,\n resolvedSurfaceForeground,\n type SurfaceMetrics,\n type WebHostSurfacePainter,\n} from \"./SurfaceRenderer.ts\";\nimport {\n isSupportedImageFormat,\n normalizeScalingMode,\n type NormalizedSurfaceImageFormat,\n} from \"./normalizeWireTokens.ts\";\nimport {\n type ResolvedWebHostTerminalStyle,\n webTUITerminalBackgroundColor,\n} from \"./WebHostTerminalStyle.ts\";\nimport type {\n WebHostSurfaceDamage,\n WebHostSurfaceFrame,\n WebHostSurfaceImage,\n WebHostSurfaceStyle,\n} from \"./WebHostSurfaceTransport.ts\";\n\n/**\n * A read-only snapshot of the cell grid geometry and active style the painter\n * needs for a single paint pass — see {@link SurfaceMetrics}, shared with the\n * DOM painter. The alias keeps this painter's original public name stable.\n */\nexport type CanvasSurfaceMetrics = SurfaceMetrics;\n\ninterface CachedWebHostImage {\n image?: CanvasImageSource;\n promise?: Promise<CanvasImageSource>;\n}\n\ninterface DirtyRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\ninterface DirtyCellRange {\n start: number;\n end: number;\n}\n\ntype DirtyRowRanges = \"full\" | DirtyCellRange[];\n\ninterface DirtyRegion {\n rects: DirtyRect[];\n rows: Map<number, DirtyRowRanges>;\n}\n\n/**\n * Draws SwiftTUI surface frames onto a 2D canvas: background fills, per-cell\n * text/box-drawing/decorations, surface images, and damage-scoped dirty-region\n * painting. The painter caches decoded images and asks the host to repaint once\n * an image finishes decoding (via the `requestRedraw` callback).\n *\n * Geometry and style are supplied per paint via {@link CanvasSurfaceMetrics};\n * the painter holds only the canvas handle, the image cache, and the redraw\n * callback as durable state.\n */\nexport class CanvasSurfacePainter implements WebHostSurfacePainter {\n private readonly imageCache = new Map<string, CachedWebHostImage>();\n private canvas?: HTMLCanvasElement;\n private requestRedraw: () => void = () => {};\n\n /**\n * Binds the canvas the painter draws into and the callback used to request a\n * full repaint after an asynchronous image decode completes.\n */\n attach(\n canvas: HTMLCanvasElement,\n requestRedraw: () => void\n ): void {\n this.canvas = canvas;\n this.requestRedraw = requestRedraw;\n }\n\n paint(\n metrics: CanvasSurfaceMetrics,\n frame: WebHostSurfaceFrame | undefined,\n damage?: WebHostSurfaceDamage\n ): void {\n const canvas = this.canvas;\n const context = canvas?.getContext(\"2d\");\n if (!canvas || !context) {\n return;\n }\n\n const dirtyRegion = frame\n ? this.dirtyRegionForDamage(damage, frame, metrics)\n : undefined;\n if (dirtyRegion?.rects.length === 0) {\n return;\n }\n\n const scale = globalThis.window?.devicePixelRatio || 1;\n context.setTransform(scale, 0, 0, scale, 0, 0);\n context.textBaseline = \"alphabetic\";\n\n context.fillStyle = webTUITerminalBackgroundColor(metrics.style);\n if (dirtyRegion) {\n for (const rect of dirtyRegion.rects) {\n context.clearRect(rect.x, rect.y, rect.width, rect.height);\n context.fillRect(rect.x, rect.y, rect.width, rect.height);\n }\n } else {\n context.clearRect(0, 0, canvas.width / scale, canvas.height / scale);\n context.fillRect(0, 0, metrics.columns * metrics.cellWidth, metrics.rows * metrics.cellHeight);\n }\n\n if (!frame) {\n return;\n }\n\n this.drawRows(context, frame, metrics, dirtyRegion);\n this.drawImages(context, frame.images ?? [], metrics, dirtyRegion);\n }\n\n private drawRows(\n context: CanvasRenderingContext2D,\n frame: WebHostSurfaceFrame,\n metrics: CanvasSurfaceMetrics,\n dirtyRegion?: DirtyRegion\n ): void {\n if (dirtyRegion) {\n for (const [y, ranges] of dirtyRegion.rows) {\n const row = frame.rows[y] ?? [];\n this.drawRow(context, frame, metrics, row, y, ranges);\n }\n return;\n }\n\n for (let y = 0; y < frame.rows.length; y += 1) {\n const row = frame.rows[y] ?? [];\n this.drawRow(context, frame, metrics, row, y);\n }\n }\n\n private drawRow(\n context: CanvasRenderingContext2D,\n frame: WebHostSurfaceFrame,\n metrics: CanvasSurfaceMetrics,\n row: WebHostSurfaceFrame[\"rows\"][number],\n y: number,\n ranges?: DirtyRowRanges\n ): void {\n for (const cell of row) {\n const [x, text, span, styleIndex] = cell;\n if (ranges !== undefined && !cellIntersectsRanges(x, span, ranges)) {\n continue;\n }\n const style = frame.styles[styleIndex] ?? undefined;\n this.drawCell(context, metrics, x, y, text, span, style);\n }\n }\n\n private drawImages(\n context: CanvasRenderingContext2D,\n images: WebHostSurfaceImage[],\n metrics: CanvasSurfaceMetrics,\n dirtyRegion?: DirtyRegion\n ): void {\n for (const image of images) {\n if (!isSupportedImageFormat(image.format)) {\n continue;\n }\n this.drawImage(\n context,\n {\n ...image,\n scalingMode: normalizeScalingMode(image.scalingMode),\n },\n metrics,\n dirtyRegion\n );\n }\n }\n\n private drawImage(\n context: CanvasRenderingContext2D,\n image: WebHostSurfaceImage,\n metrics: CanvasSurfaceMetrics,\n dirtyRegion?: DirtyRegion\n ): void {\n const decodedImage = this.cachedImage(image);\n if (!decodedImage) {\n return;\n }\n\n const [boundsX, boundsY, boundsWidth, boundsHeight] = image.bounds;\n const [clipX, clipY, clipWidth, clipHeight] = image.visibleBounds;\n if (boundsWidth <= 0 || boundsHeight <= 0 || clipWidth <= 0 || clipHeight <= 0) {\n return;\n }\n if (\n dirtyRegion\n && !dirtyRegionIntersectsCellRect(dirtyRegion, clipX, clipY, clipWidth, clipHeight)\n ) {\n return;\n }\n\n context.save();\n context.beginPath();\n context.rect(\n clipX * metrics.cellWidth,\n clipY * metrics.cellHeight,\n clipWidth * metrics.cellWidth,\n clipHeight * metrics.cellHeight\n );\n context.clip();\n context.drawImage(\n decodedImage,\n boundsX * metrics.cellWidth,\n boundsY * metrics.cellHeight,\n boundsWidth * metrics.cellWidth,\n boundsHeight * metrics.cellHeight\n );\n context.restore();\n }\n\n private cachedImage(\n image: WebHostSurfaceImage\n ): CanvasImageSource | undefined {\n const cached = this.imageCache.get(image.id);\n if (cached?.image) {\n return cached.image;\n }\n\n if (!cached?.promise && image.dataBase64) {\n const promise = decodeImage(image.dataBase64, image.format);\n this.imageCache.set(image.id, { promise });\n void promise.then((decodedImage) => {\n const latest = this.imageCache.get(image.id);\n if (latest?.promise !== promise) {\n return;\n }\n this.imageCache.set(image.id, { image: decodedImage });\n this.requestRedraw();\n }).catch(() => {\n this.imageCache.delete(image.id);\n });\n }\n\n return undefined;\n }\n\n private drawCell(\n context: CanvasRenderingContext2D,\n metrics: CanvasSurfaceMetrics,\n x: number,\n y: number,\n text: string,\n span: number,\n style?: WebHostSurfaceStyle | null\n ): void {\n const rectX = x * metrics.cellWidth;\n const rectY = y * metrics.cellHeight;\n const width = Math.max(1, span) * metrics.cellWidth;\n const background = resolvedSurfaceBackground(style, metrics.style);\n const foreground = resolvedSurfaceForeground(style, metrics.style);\n const opacity = style?.opacity ?? 1;\n\n if (background) {\n context.globalAlpha = opacity;\n context.fillStyle = background;\n context.fillRect(rectX, rectY, width, metrics.cellHeight);\n }\n\n if (text !== \" \") {\n context.globalAlpha = opacity;\n context.fillStyle = foreground;\n context.strokeStyle = foreground;\n if (!canRenderBoxDrawing(text) || !drawBoxDrawing(context, text, {\n x: rectX,\n y: rectY,\n width,\n height: metrics.cellHeight,\n })) {\n context.font = fontForStyle(metrics.style, style);\n context.fillText(\n text,\n rectX,\n rectY + Math.floor((metrics.cellHeight + metrics.style.fontSize) / 2) - 2\n );\n }\n }\n\n this.drawTextLine(context, metrics, rectX, rectY, width, style?.underline, \"underline\", foreground);\n this.drawTextLine(context, metrics, rectX, rectY, width, style?.strikethrough, \"strike\", foreground);\n context.globalAlpha = 1;\n }\n\n private dirtyRegionForDamage(\n damage: WebHostSurfaceDamage | undefined,\n frame: WebHostSurfaceFrame,\n metrics: CanvasSurfaceMetrics\n ): DirtyRegion | undefined {\n if (!damage || damage.requiresFullTextRepaint || damage.requiresFullGraphicsReplay) {\n return undefined;\n }\n\n const rects: DirtyRect[] = [];\n const rows = new Map<number, DirtyRowRanges>();\n for (const [row, ranges] of damage.textRows) {\n if (row < 0 || row >= frame.height) {\n continue;\n }\n if (ranges.length === 0) {\n rects.push(cellRect(metrics, 0, row, frame.width));\n rows.set(row, \"full\");\n continue;\n }\n const rowRanges: DirtyCellRange[] = rows.get(row) === \"full\"\n ? []\n : [...(rows.get(row) as DirtyCellRange[] | undefined ?? [])];\n for (const [start, end] of ranges) {\n const lowerBound = Math.max(0, Math.min(frame.width, Math.floor(start)));\n const upperBound = Math.max(lowerBound, Math.min(frame.width, Math.ceil(end)));\n if (lowerBound >= upperBound) {\n continue;\n }\n rects.push(cellRect(metrics, lowerBound, row, upperBound - lowerBound));\n rowRanges.push({ start: lowerBound, end: upperBound });\n }\n if (rows.get(row) !== \"full\" && rowRanges.length > 0) {\n rows.set(row, normalizeCellRanges(rowRanges));\n }\n }\n return { rects, rows };\n }\n\n private drawTextLine(\n context: CanvasRenderingContext2D,\n metrics: CanvasSurfaceMetrics,\n x: number,\n y: number,\n width: number,\n line: WebHostSurfaceStyle[\"underline\"],\n placement: \"underline\" | \"strike\",\n fallbackColor: string\n ): void {\n if (!line) {\n return;\n }\n context.strokeStyle = line.color ?? fallbackColor;\n context.lineWidth = line.pattern === \"double\" ? 2 : 1;\n if (line.pattern === \"dot\") {\n context.setLineDash([1, 3]);\n } else if (line.pattern === \"dash\") {\n context.setLineDash([4, 3]);\n } else {\n context.setLineDash([]);\n }\n\n const lineY = placement === \"underline\"\n ? y + metrics.cellHeight - 2\n : y + Math.floor(metrics.cellHeight / 2);\n context.beginPath();\n context.moveTo(x, lineY);\n context.lineTo(x + width, lineY);\n context.stroke();\n context.setLineDash([]);\n }\n}\n\n/**\n * The CSS font string for a cell, folding the surface emphasis bits (bold,\n * italic) over the host terminal's font size/family. Exposed so the host can\n * reuse the exact same metric when measuring cell dimensions.\n */\nexport function fontForStyle(\n terminalStyle: ResolvedWebHostTerminalStyle,\n style?: WebHostSurfaceStyle | null\n): string {\n const emphasis = style?.em ?? 0;\n const italic = (emphasis & 2) !== 0 ? \"italic \" : \"\";\n const weight = (emphasis & 1) !== 0 ? \"700 \" : \"\";\n return `${italic}${weight}${terminalStyle.fontSize}px ${terminalStyle.fontFamily}`;\n}\n\nfunction cellRect(\n metrics: CanvasSurfaceMetrics,\n x: number,\n y: number,\n span: number\n): DirtyRect {\n return {\n x: x * metrics.cellWidth,\n y: y * metrics.cellHeight,\n width: Math.max(1, span) * metrics.cellWidth,\n height: metrics.cellHeight,\n };\n}\n\nasync function decodeImage(\n dataBase64: string,\n format: NormalizedSurfaceImageFormat\n): Promise<CanvasImageSource> {\n const bytes = decodeBase64Bytes(dataBase64);\n const blob = new Blob([bytes], { type: `image/${format}` });\n\n if (typeof createImageBitmap === \"function\") {\n // Animated GIFs collapse to their first frame in createImageBitmap\n // — that matches the Kitty path's first-frame composite. Phase 7\n // will replace this with a frame ticker.\n return createImageBitmap(blob);\n }\n\n return new Promise((resolve, reject) => {\n const image = new Image();\n const url = URL.createObjectURL(blob);\n image.onload = () => {\n URL.revokeObjectURL(url);\n resolve(image);\n };\n image.onerror = () => {\n URL.revokeObjectURL(url);\n reject(new Error(`Failed to decode ${format} image`));\n };\n image.src = url;\n });\n}\n\nfunction decodeBase64Bytes(\n value: string\n): Uint8Array {\n if (typeof atob === \"function\") {\n const binary = atob(value);\n const bytes = new Uint8Array(binary.length);\n for (let index = 0; index < binary.length; index += 1) {\n bytes[index] = binary.charCodeAt(index);\n }\n return bytes;\n }\n\n return new Uint8Array(Buffer.from(value, \"base64\"));\n}\n\nfunction normalizeCellRanges(\n ranges: DirtyCellRange[]\n): DirtyCellRange[] {\n const sorted = ranges\n .filter((range) => range.end > range.start)\n .sort((lhs, rhs) => lhs.start - rhs.start || lhs.end - rhs.end);\n const normalized: DirtyCellRange[] = [];\n for (const range of sorted) {\n const previous = normalized[normalized.length - 1];\n if (previous && range.start <= previous.end) {\n previous.end = Math.max(previous.end, range.end);\n continue;\n }\n normalized.push({ ...range });\n }\n return normalized;\n}\n\nfunction cellIntersectsRanges(\n x: number,\n span: number,\n ranges: DirtyRowRanges\n): boolean {\n if (ranges === \"full\") {\n return true;\n }\n const start = Math.floor(x);\n const end = start + Math.max(1, Math.ceil(span));\n return ranges.some((range) => start < range.end && end > range.start);\n}\n\nfunction dirtyRegionIntersectsCellRect(\n region: DirtyRegion,\n x: number,\n y: number,\n width: number,\n height: number\n): boolean {\n const startRow = Math.max(0, Math.floor(y));\n const endRow = Math.max(startRow, Math.ceil(y + height));\n const rectRange = {\n start: Math.floor(x),\n end: Math.floor(x) + Math.max(1, Math.ceil(width)),\n };\n for (let row = startRow; row < endRow; row += 1) {\n const ranges = region.rows.get(row);\n if (!ranges) {\n continue;\n }\n if (cellIntersectsRanges(rectRange.start, rectRange.end - rectRange.start, ranges)) {\n return true;\n }\n }\n return false;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CanvasSurfacePainter.js","names":[],"sources":["../../src/CanvasSurfacePainter.ts"],"sourcesContent":["import {\n canRenderBoxDrawing,\n drawBoxDrawing,\n} from \"./BoxDrawingRenderer.ts\";\nimport {\n resolvedSurfaceBackground,\n resolvedSurfaceForeground,\n type SurfaceMetrics,\n type WebHostSurfacePainter,\n} from \"./SurfaceRenderer.ts\";\nimport {\n isSupportedImageFormat,\n type NormalizedSurfaceImageFormat,\n} from \"./normalizeWireTokens.ts\";\nimport {\n type ResolvedWebHostTerminalStyle,\n webTUITerminalBackgroundColor,\n} from \"./WebHostTerminalStyle.ts\";\nimport {\n isWebHostImageRecoveryId,\n type WebHostImagePayloadRequestHandler,\n type WebHostSurfaceDamage,\n type WebHostSurfaceFrame,\n type WebHostSurfaceImage,\n type WebHostSurfaceStyle,\n} from \"./WebHostSurfaceTransport.ts\";\nimport {\n registerCanvasSurfacePainterConformanceControl,\n} from \"./SurfacePainterConformanceControl.ts\";\n\n/**\n * A read-only snapshot of the cell grid geometry and active style the painter\n * needs for a single paint pass — see {@link SurfaceMetrics}, shared with the\n * DOM painter. The alias keeps this painter's original public name stable.\n */\nexport type CanvasSurfaceMetrics = SurfaceMetrics;\n\ninterface CachedWebHostImage {\n image?: CanvasImageSource;\n promise?: Promise<CanvasImageSource>;\n payload?: string;\n /** Total decode attempts already started; bounded by MAX_IMAGE_DECODE_ATTEMPTS. */\n retries?: number;\n missReported?: boolean;\n}\n\nexport const MAX_IMAGE_DECODE_ATTEMPTS = 3;\nexport const MAX_UNRESOLVED_IMAGE_CACHE_ENTRIES = 256;\nexport const MAX_UNRESOLVED_IMAGE_PAYLOAD_CHARACTERS = 64 * 1024 * 1024;\n\nexport interface CanvasSurfacePainterOptions {\n /** Injectable decode seam for browser hosts and deterministic failure tests. */\n decodeImage?: (\n dataBase64: string,\n format: NormalizedSurfaceImageFormat,\n imageID: string\n ) => Promise<CanvasImageSource>;\n /**\n * Reports supported, positive-area images whose payload cannot be resolved\n * locally. Returning the admitted subset keeps rejected IDs eligible for a\n * later paint; `void` preserves legacy all-accepted behavior.\n */\n onImagePayloadMiss?: WebHostImagePayloadRequestHandler;\n}\n\ninterface DirtyRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\ninterface DirtyCellRange {\n start: number;\n end: number;\n}\n\ntype DirtyRowRanges = \"full\" | DirtyCellRange[];\n\ninterface DirtyRegion {\n rects: DirtyRect[];\n rows: Map<number, DirtyRowRanges>;\n}\n\n/**\n * Draws SwiftTUI surface frames onto a 2D canvas: background fills, per-cell\n * text/box-drawing/decorations, surface images, and damage-scoped dirty-region\n * painting. The painter caches decoded images and asks the host to repaint once\n * an image finishes decoding (via the `requestRedraw` callback).\n *\n * Geometry and style are supplied per paint via {@link CanvasSurfaceMetrics};\n * the painter holds only the canvas handle, the image cache, and the redraw\n * callback as durable state.\n */\nexport class CanvasSurfacePainter implements WebHostSurfacePainter {\n private readonly imageCache = new Map<string, CachedWebHostImage>();\n private readonly unresolvedImageIds = new Set<string>();\n private unresolvedImagePayloadCharacters = 0;\n private readonly imageDecoder: NonNullable<CanvasSurfacePainterOptions[\"decodeImage\"]>;\n private readonly onImagePayloadMiss: WebHostImagePayloadRequestHandler;\n private canvas?: HTMLCanvasElement;\n private requestRedraw: () => void = () => {};\n private lastEpoch?: number;\n private readonly pendingImagePayloadMissIds = new Set<string>();\n private imagePayloadMissScheduled = false;\n\n constructor(options: CanvasSurfacePainterOptions = {}) {\n this.imageDecoder = options.decodeImage ?? decodeImage;\n this.onImagePayloadMiss = options.onImagePayloadMiss ?? (() => {});\n registerCanvasSurfacePainterConformanceControl(this, {\n evictImages: (ids) => {\n for (const id of ids) {\n this.removeUnresolvedImage(id);\n this.imageCache.delete(id);\n this.pendingImagePayloadMissIds.delete(id);\n }\n },\n visibleImageIDs: (images) => [...new Set(\n images\n .filter(isPaintableSurfaceImage)\n .filter((image) => this.imageCache.get(image.id)?.image !== undefined)\n .map((image) => image.id)\n )].sort(),\n });\n }\n\n /**\n * Binds the canvas the painter draws into and the callback used to request a\n * full repaint after an asynchronous image decode completes.\n */\n attach(\n canvas: HTMLCanvasElement,\n requestRedraw: () => void\n ): void {\n this.canvas = canvas;\n this.requestRedraw = requestRedraw;\n }\n\n paint(\n metrics: CanvasSurfaceMetrics,\n frame: WebHostSurfaceFrame | undefined,\n damage?: WebHostSurfaceDamage,\n recoveredImagePayloadIds: readonly string[] = []\n ): void {\n const canvas = this.canvas;\n const context = canvas?.getContext(\"2d\");\n if (!canvas || !context) {\n return;\n }\n\n if (frame?.epoch !== undefined && frame.epoch !== this.lastEpoch) {\n this.lastEpoch = frame.epoch;\n for (const cached of this.imageCache.values()) {\n if (!cached.image) {\n cached.missReported = false;\n }\n }\n }\n this.sweepUnresolvedImages(frame?.images);\n\n const dirtyRegion = frame\n ? this.dirtyRegionForDamage(damage, frame, metrics)\n : undefined;\n const recoveredPayloadIds = new Set(recoveredImagePayloadIds);\n if (dirtyRegion?.rects.length === 0) {\n this.prepareImages(frame?.images ?? [], recoveredPayloadIds);\n return;\n }\n\n const scale = globalThis.window?.devicePixelRatio || 1;\n context.setTransform(scale, 0, 0, scale, 0, 0);\n context.textBaseline = \"alphabetic\";\n\n context.fillStyle = webTUITerminalBackgroundColor(metrics.style);\n if (dirtyRegion) {\n for (const rect of dirtyRegion.rects) {\n context.clearRect(rect.x, rect.y, rect.width, rect.height);\n context.fillRect(rect.x, rect.y, rect.width, rect.height);\n }\n } else {\n context.clearRect(0, 0, canvas.width / scale, canvas.height / scale);\n context.fillRect(0, 0, metrics.columns * metrics.cellWidth, metrics.rows * metrics.cellHeight);\n }\n\n if (!frame) {\n return;\n }\n\n this.drawRows(context, frame, metrics, dirtyRegion);\n this.drawImages(\n context,\n frame.images ?? [],\n metrics,\n dirtyRegion,\n recoveredPayloadIds\n );\n }\n\n private drawRows(\n context: CanvasRenderingContext2D,\n frame: WebHostSurfaceFrame,\n metrics: CanvasSurfaceMetrics,\n dirtyRegion?: DirtyRegion\n ): void {\n if (dirtyRegion) {\n for (const [y, ranges] of dirtyRegion.rows) {\n const row = frame.rows[y] ?? [];\n this.drawRow(context, frame, metrics, row, y, ranges);\n }\n return;\n }\n\n for (let y = 0; y < frame.rows.length; y += 1) {\n const row = frame.rows[y] ?? [];\n this.drawRow(context, frame, metrics, row, y);\n }\n }\n\n private drawRow(\n context: CanvasRenderingContext2D,\n frame: WebHostSurfaceFrame,\n metrics: CanvasSurfaceMetrics,\n row: WebHostSurfaceFrame[\"rows\"][number],\n y: number,\n ranges?: DirtyRowRanges\n ): void {\n for (const cell of row) {\n const [x, text, span, styleIndex] = cell;\n if (ranges !== undefined && !cellIntersectsRanges(x, span, ranges)) {\n continue;\n }\n const style = frame.styles[styleIndex] ?? undefined;\n this.drawCell(context, metrics, x, y, text, span, style);\n }\n }\n\n private drawImages(\n context: CanvasRenderingContext2D,\n images: WebHostSurfaceImage[],\n metrics: CanvasSurfaceMetrics,\n dirtyRegion: DirtyRegion | undefined,\n recoveredPayloadIds: Set<string>\n ): void {\n const missingPayloadIds = new Set<string>();\n for (const image of images) {\n if (!isPaintableSurfaceImage(image)) {\n continue;\n }\n this.drawImage(\n context,\n image,\n metrics,\n dirtyRegion,\n missingPayloadIds,\n recoveredPayloadIds\n );\n }\n this.reportImagePayloadMisses(missingPayloadIds);\n }\n\n private prepareImages(\n images: WebHostSurfaceImage[],\n recoveredPayloadIds: Set<string>\n ): void {\n const missingPayloadIds = new Set<string>();\n for (const image of images) {\n if (!isPaintableSurfaceImage(image)) {\n continue;\n }\n this.cachedImage(image, missingPayloadIds, recoveredPayloadIds);\n }\n this.reportImagePayloadMisses(missingPayloadIds);\n }\n\n private drawImage(\n context: CanvasRenderingContext2D,\n image: WebHostSurfaceImage,\n metrics: CanvasSurfaceMetrics,\n dirtyRegion: DirtyRegion | undefined,\n missingPayloadIds: Set<string>,\n recoveredPayloadIds: Set<string>\n ): void {\n const [boundsX, boundsY, boundsWidth, boundsHeight] = image.bounds;\n const [clipX, clipY, clipWidth, clipHeight] = image.visibleBounds;\n if (boundsWidth <= 0 || boundsHeight <= 0 || clipWidth <= 0 || clipHeight <= 0) {\n return;\n }\n\n const decodedImage = this.cachedImage(\n image,\n missingPayloadIds,\n recoveredPayloadIds\n );\n if (!decodedImage) {\n return;\n }\n\n if (\n dirtyRegion\n && !dirtyRegionIntersectsCellRect(dirtyRegion, clipX, clipY, clipWidth, clipHeight)\n ) {\n return;\n }\n\n context.save();\n context.beginPath();\n context.rect(\n clipX * metrics.cellWidth,\n clipY * metrics.cellHeight,\n clipWidth * metrics.cellWidth,\n clipHeight * metrics.cellHeight\n );\n context.clip();\n context.drawImage(\n decodedImage,\n boundsX * metrics.cellWidth,\n boundsY * metrics.cellHeight,\n boundsWidth * metrics.cellWidth,\n boundsHeight * metrics.cellHeight\n );\n context.restore();\n }\n\n private cachedImage(\n image: WebHostSurfaceImage,\n missingPayloadIds: Set<string>,\n recoveredPayloadIds: Set<string>\n ): CanvasImageSource | undefined {\n let cached = this.imageCache.get(image.id);\n if (cached?.image) {\n return cached.image;\n }\n\n const beginsRecoveredGeneration = image.dataBase64 !== undefined\n && recoveredPayloadIds.delete(image.id);\n if (\n image.dataBase64 !== undefined\n && (cached?.payload === undefined || beginsRecoveredGeneration)\n ) {\n if (!this.canTrackUnresolvedImage(image.id, image.dataBase64)) {\n return undefined;\n }\n cached = {\n payload: image.dataBase64,\n retries: 0,\n };\n this.setUnresolvedImage(image.id, cached);\n } else if (!cached) {\n if (!isWebHostImageRecoveryId(image.id)) {\n return undefined;\n }\n if (!this.canTrackUnresolvedImage(image.id)) {\n return undefined;\n }\n cached = { missReported: false };\n this.setUnresolvedImage(image.id, cached);\n missingPayloadIds.add(image.id);\n return undefined;\n }\n\n const attempts = cached.retries ?? 0;\n if (cached.payload === undefined) {\n if (!cached.missReported) {\n cached.missReported = true;\n missingPayloadIds.add(image.id);\n }\n return undefined;\n }\n if (attempts >= MAX_IMAGE_DECODE_ATTEMPTS) {\n if (!cached.missReported) {\n cached.missReported = true;\n missingPayloadIds.add(image.id);\n }\n return undefined;\n }\n\n if (!cached.promise) {\n const nextAttempts = attempts + 1;\n const promise = this.imageDecoder(cached.payload, image.format, image.id);\n cached.promise = promise;\n cached.retries = nextAttempts;\n void promise.then((decodedImage) => {\n const latest = this.imageCache.get(image.id);\n if (latest?.promise !== promise) {\n return;\n }\n this.removeUnresolvedImage(image.id);\n this.imageCache.set(image.id, { image: decodedImage });\n this.requestRedraw();\n }).catch(() => {\n const latest = this.imageCache.get(image.id);\n if (latest?.promise !== promise) {\n return;\n }\n latest.promise = undefined;\n if (\n (latest.retries ?? 0) >= MAX_IMAGE_DECODE_ATTEMPTS\n && !latest.missReported\n ) {\n latest.missReported = true;\n if (isWebHostImageRecoveryId(image.id)) {\n this.scheduleImagePayloadMiss(image.id);\n }\n }\n this.requestRedraw();\n });\n }\n\n return undefined;\n }\n\n private canTrackUnresolvedImage(\n id: string,\n payload?: string\n ): boolean {\n const existing = this.imageCache.get(id);\n const existingPayloadCharacters = existing?.image\n ? 0\n : existing?.payload?.length ?? 0;\n const nextPayloadCharacters = this.unresolvedImagePayloadCharacters\n - existingPayloadCharacters\n + (payload?.length ?? 0);\n return (\n (this.unresolvedImageIds.has(id)\n || this.unresolvedImageIds.size < MAX_UNRESOLVED_IMAGE_CACHE_ENTRIES)\n && nextPayloadCharacters <= MAX_UNRESOLVED_IMAGE_PAYLOAD_CHARACTERS\n );\n }\n\n private setUnresolvedImage(\n id: string,\n cached: CachedWebHostImage\n ): void {\n const existing = this.imageCache.get(id);\n if (this.unresolvedImageIds.has(id)) {\n this.unresolvedImagePayloadCharacters -= existing?.payload?.length ?? 0;\n }\n this.imageCache.set(id, cached);\n this.unresolvedImageIds.add(id);\n this.unresolvedImagePayloadCharacters += cached.payload?.length ?? 0;\n this.pendingImagePayloadMissIds.delete(id);\n }\n\n private removeUnresolvedImage(\n id: string\n ): void {\n if (!this.unresolvedImageIds.delete(id)) {\n return;\n }\n this.unresolvedImagePayloadCharacters -=\n this.imageCache.get(id)?.payload?.length ?? 0;\n this.pendingImagePayloadMissIds.delete(id);\n }\n\n private sweepUnresolvedImages(\n images: WebHostSurfaceImage[] | undefined\n ): void {\n const presentedIds = new Set<string>();\n for (const image of images ?? []) {\n if (!this.unresolvedImageIds.has(image.id)) {\n continue;\n }\n if (isPaintableSurfaceImage(image)) {\n presentedIds.add(image.id);\n }\n }\n for (const id of this.unresolvedImageIds) {\n if (presentedIds.has(id)) {\n continue;\n }\n this.removeUnresolvedImage(id);\n this.imageCache.delete(id);\n }\n }\n\n private reportImagePayloadMisses(\n ids: Set<string>\n ): void {\n if (ids.size === 0) {\n return;\n }\n const candidateIds = [...ids].sort();\n this.applyImagePayloadMissAdmission(\n candidateIds,\n this.onImagePayloadMiss(candidateIds)\n );\n }\n\n private scheduleImagePayloadMiss(\n id: string\n ): void {\n this.pendingImagePayloadMissIds.add(id);\n if (this.imagePayloadMissScheduled) {\n return;\n }\n this.imagePayloadMissScheduled = true;\n queueMicrotask(() => {\n this.imagePayloadMissScheduled = false;\n const ids = [...this.pendingImagePayloadMissIds].sort();\n this.pendingImagePayloadMissIds.clear();\n if (ids.length > 0) {\n this.applyImagePayloadMissAdmission(\n ids,\n this.onImagePayloadMiss(ids)\n );\n }\n });\n }\n\n private applyImagePayloadMissAdmission(\n candidateIds: readonly string[],\n admittedIds: readonly string[] | void\n ): void {\n // Callbacks authored against the former void contract commonly use a\n // concise `array.push(...)` body, whose runtime result is a number.\n const acceptedIds = new Set(\n Array.isArray(admittedIds) ? admittedIds : candidateIds\n );\n for (const id of candidateIds) {\n const cached = this.imageCache.get(id);\n if (cached && !cached.image) {\n cached.missReported = acceptedIds.has(id);\n }\n }\n }\n\n private drawCell(\n context: CanvasRenderingContext2D,\n metrics: CanvasSurfaceMetrics,\n x: number,\n y: number,\n text: string,\n span: number,\n style?: WebHostSurfaceStyle | null\n ): void {\n const rectX = x * metrics.cellWidth;\n const rectY = y * metrics.cellHeight;\n const width = Math.max(1, span) * metrics.cellWidth;\n const background = resolvedSurfaceBackground(style, metrics.style);\n const foreground = resolvedSurfaceForeground(style, metrics.style);\n const opacity = style?.opacity ?? 1;\n\n if (background) {\n context.globalAlpha = opacity;\n context.fillStyle = background;\n context.fillRect(rectX, rectY, width, metrics.cellHeight);\n }\n\n if (text !== \" \") {\n context.globalAlpha = opacity;\n context.fillStyle = foreground;\n context.strokeStyle = foreground;\n if (!canRenderBoxDrawing(text) || !drawBoxDrawing(context, text, {\n x: rectX,\n y: rectY,\n width,\n height: metrics.cellHeight,\n })) {\n context.font = fontForStyle(metrics.style, style);\n context.fillText(\n text,\n rectX,\n rectY + Math.floor((metrics.cellHeight + metrics.style.fontSize) / 2) - 2\n );\n }\n }\n\n this.drawTextLine(context, metrics, rectX, rectY, width, style?.underline, \"underline\", foreground);\n this.drawTextLine(context, metrics, rectX, rectY, width, style?.strikethrough, \"strike\", foreground);\n context.globalAlpha = 1;\n }\n\n private dirtyRegionForDamage(\n damage: WebHostSurfaceDamage | undefined,\n frame: WebHostSurfaceFrame,\n metrics: CanvasSurfaceMetrics\n ): DirtyRegion | undefined {\n if (!damage || damage.requiresFullTextRepaint || damage.requiresFullGraphicsReplay) {\n return undefined;\n }\n\n const rects: DirtyRect[] = [];\n const rows = new Map<number, DirtyRowRanges>();\n for (const [row, ranges] of damage.textRows) {\n if (row < 0 || row >= frame.height) {\n continue;\n }\n if (ranges.length === 0) {\n rects.push(cellRect(metrics, 0, row, frame.width));\n rows.set(row, \"full\");\n continue;\n }\n const rowRanges: DirtyCellRange[] = rows.get(row) === \"full\"\n ? []\n : [...(rows.get(row) as DirtyCellRange[] | undefined ?? [])];\n for (const [start, end] of ranges) {\n const lowerBound = Math.max(0, Math.min(frame.width, Math.floor(start)));\n const upperBound = Math.max(lowerBound, Math.min(frame.width, Math.ceil(end)));\n if (lowerBound >= upperBound) {\n continue;\n }\n rects.push(cellRect(metrics, lowerBound, row, upperBound - lowerBound));\n rowRanges.push({ start: lowerBound, end: upperBound });\n }\n if (rows.get(row) !== \"full\" && rowRanges.length > 0) {\n rows.set(row, normalizeCellRanges(rowRanges));\n }\n }\n return { rects, rows };\n }\n\n private drawTextLine(\n context: CanvasRenderingContext2D,\n metrics: CanvasSurfaceMetrics,\n x: number,\n y: number,\n width: number,\n line: WebHostSurfaceStyle[\"underline\"],\n placement: \"underline\" | \"strike\",\n fallbackColor: string\n ): void {\n if (!line) {\n return;\n }\n context.strokeStyle = line.color ?? fallbackColor;\n context.lineWidth = line.pattern === \"double\" ? 2 : 1;\n if (line.pattern === \"dot\") {\n context.setLineDash([1, 3]);\n } else if (line.pattern === \"dash\") {\n context.setLineDash([4, 3]);\n } else {\n context.setLineDash([]);\n }\n\n const lineY = placement === \"underline\"\n ? y + metrics.cellHeight - 2\n : y + Math.floor(metrics.cellHeight / 2);\n context.beginPath();\n context.moveTo(x, lineY);\n context.lineTo(x + width, lineY);\n context.stroke();\n context.setLineDash([]);\n }\n}\n\nfunction isPaintableSurfaceImage(\n image: WebHostSurfaceImage\n): boolean {\n const [, , boundsWidth, boundsHeight] = image.bounds;\n const [, , clipWidth, clipHeight] = image.visibleBounds;\n return isSupportedImageFormat(image.format)\n && boundsWidth > 0\n && boundsHeight > 0\n && clipWidth > 0\n && clipHeight > 0;\n}\n\n/**\n * The CSS font string for a cell, folding the surface emphasis bits (bold,\n * italic) over the host terminal's font size/family. Exposed so the host can\n * reuse the exact same metric when measuring cell dimensions.\n */\nexport function fontForStyle(\n terminalStyle: ResolvedWebHostTerminalStyle,\n style?: WebHostSurfaceStyle | null\n): string {\n const emphasis = style?.em ?? 0;\n const italic = (emphasis & 2) !== 0 ? \"italic \" : \"\";\n const weight = (emphasis & 1) !== 0 ? \"700 \" : \"\";\n return `${italic}${weight}${terminalStyle.fontSize}px ${terminalStyle.fontFamily}`;\n}\n\nfunction cellRect(\n metrics: CanvasSurfaceMetrics,\n x: number,\n y: number,\n span: number\n): DirtyRect {\n return {\n x: x * metrics.cellWidth,\n y: y * metrics.cellHeight,\n width: Math.max(1, span) * metrics.cellWidth,\n height: metrics.cellHeight,\n };\n}\n\nasync function decodeImage(\n dataBase64: string,\n format: NormalizedSurfaceImageFormat\n): Promise<CanvasImageSource> {\n const bytes = decodeBase64Bytes(dataBase64);\n const blob = new Blob([bytes], { type: `image/${format}` });\n\n if (typeof createImageBitmap === \"function\") {\n // Animated GIFs collapse to their first frame in createImageBitmap\n // — that matches the Kitty path's first-frame composite. Phase 7\n // will replace this with a frame ticker.\n return createImageBitmap(blob);\n }\n\n return new Promise((resolve, reject) => {\n const image = new Image();\n const url = URL.createObjectURL(blob);\n image.onload = () => {\n URL.revokeObjectURL(url);\n resolve(image);\n };\n image.onerror = () => {\n URL.revokeObjectURL(url);\n reject(new Error(`Failed to decode ${format} image`));\n };\n image.src = url;\n });\n}\n\nfunction decodeBase64Bytes(\n value: string\n): Uint8Array {\n if (typeof atob === \"function\") {\n const binary = atob(value);\n const bytes = new Uint8Array(binary.length);\n for (let index = 0; index < binary.length; index += 1) {\n bytes[index] = binary.charCodeAt(index);\n }\n return bytes;\n }\n\n return new Uint8Array(Buffer.from(value, \"base64\"));\n}\n\nfunction normalizeCellRanges(\n ranges: DirtyCellRange[]\n): DirtyCellRange[] {\n const sorted = ranges\n .filter((range) => range.end > range.start)\n .sort((lhs, rhs) => lhs.start - rhs.start || lhs.end - rhs.end);\n const normalized: DirtyCellRange[] = [];\n for (const range of sorted) {\n const previous = normalized[normalized.length - 1];\n if (previous && range.start <= previous.end) {\n previous.end = Math.max(previous.end, range.end);\n continue;\n }\n normalized.push({ ...range });\n }\n return normalized;\n}\n\nfunction cellIntersectsRanges(\n x: number,\n span: number,\n ranges: DirtyRowRanges\n): boolean {\n if (ranges === \"full\") {\n return true;\n }\n const start = Math.floor(x);\n const end = start + Math.max(1, Math.ceil(span));\n return ranges.some((range) => start < range.end && end > range.start);\n}\n\nfunction dirtyRegionIntersectsCellRect(\n region: DirtyRegion,\n x: number,\n y: number,\n width: number,\n height: number\n): boolean {\n const startRow = Math.max(0, Math.floor(y));\n const endRow = Math.max(startRow, Math.ceil(y + height));\n const rectRange = {\n start: Math.floor(x),\n end: Math.floor(x) + Math.max(1, Math.ceil(width)),\n };\n for (let row = startRow; row < endRow; row += 1) {\n const ranges = region.rows.get(row);\n if (!ranges) {\n continue;\n }\n if (cellIntersectsRanges(rectRange.start, rectRange.end - rectRange.start, ranges)) {\n return true;\n }\n }\n return false;\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import { WebHostSurfaceDamage, WebHostSurfaceFrame } from "./WebHostSurfaceTransport.js";
|
|
1
|
+
import { WebHostImagePayloadRequestHandler, WebHostSurfaceDamage, WebHostSurfaceFrame } from "./WebHostSurfaceTransport.js";
|
|
2
2
|
import { SurfaceMetrics, WebHostSurfacePainter } from "./SurfaceRenderer.js";
|
|
3
3
|
//#region src/DomSurfacePainter.d.ts
|
|
4
|
+
interface DomSurfacePainterOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Reports supported, positive-area image IDs absent from the retained DOM
|
|
7
|
+
* cache. Returning the admitted subset lets bounded transports keep overflow
|
|
8
|
+
* IDs eligible on the next presentation frame; `void` preserves legacy
|
|
9
|
+
* all-accepted behavior for custom hosts.
|
|
10
|
+
*/
|
|
11
|
+
onImagePayloadMiss?: WebHostImagePayloadRequestHandler;
|
|
12
|
+
}
|
|
4
13
|
/**
|
|
5
14
|
* Draws SwiftTUI surface frames as a DOM element tree instead of canvas
|
|
6
15
|
* pixels: one absolutely positioned row container per grid row, one `<span>`
|
|
@@ -19,6 +28,7 @@ import { SurfaceMetrics, WebHostSurfacePainter } from "./SurfaceRenderer.js";
|
|
|
19
28
|
* `letter-spacing`, measured once per font/size pair.
|
|
20
29
|
*/
|
|
21
30
|
declare class DomSurfacePainter implements WebHostSurfacePainter {
|
|
31
|
+
private readonly onImagePayloadMiss;
|
|
22
32
|
private root?;
|
|
23
33
|
private rowsLayer?;
|
|
24
34
|
private imagesLayer?;
|
|
@@ -28,12 +38,16 @@ declare class DomSurfacePainter implements WebHostSurfacePainter {
|
|
|
28
38
|
private renderedGridKey?;
|
|
29
39
|
private hasRenderedFrame;
|
|
30
40
|
private letterSpacing?;
|
|
41
|
+
private reportedMissingImageIds;
|
|
42
|
+
private lastImageRecoveryFrame?;
|
|
43
|
+
private lastEpoch?;
|
|
44
|
+
constructor(options?: DomSurfacePainterOptions);
|
|
31
45
|
/**
|
|
32
46
|
* Binds the container the painter renders into. The runtime owns the
|
|
33
47
|
* container's size; the painter owns everything inside it.
|
|
34
48
|
*/
|
|
35
49
|
attach(root: HTMLElement): void;
|
|
36
|
-
paint(metrics: SurfaceMetrics, frame: WebHostSurfaceFrame | undefined, damage?: WebHostSurfaceDamage): void;
|
|
50
|
+
paint(metrics: SurfaceMetrics, frame: WebHostSurfaceFrame | undefined, damage?: WebHostSurfaceDamage, _recoveredImagePayloadIds?: readonly string[]): void;
|
|
37
51
|
private rebuildRow;
|
|
38
52
|
private ensureRowElement;
|
|
39
53
|
private applyRootStyle;
|
|
@@ -47,5 +61,5 @@ declare class DomSurfacePainter implements WebHostSurfacePainter {
|
|
|
47
61
|
private reconcileImages;
|
|
48
62
|
}
|
|
49
63
|
//#endregion
|
|
50
|
-
export { DomSurfacePainter };
|
|
64
|
+
export { DomSurfacePainter, DomSurfacePainterOptions };
|
|
51
65
|
//# sourceMappingURL=DomSurfacePainter.d.ts.map
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { webTUITerminalBackgroundColor } from "./WebHostTerminalStyle.js";
|
|
2
|
+
import { isWebHostImageRecoveryId } from "./WebHostSurfaceTransport.js";
|
|
2
3
|
import { resolvedSurfaceBackground, resolvedSurfaceForeground } from "./SurfaceRenderer.js";
|
|
3
4
|
import { isSupportedImageFormat, normalizeScalingMode } from "./normalizeWireTokens.js";
|
|
5
|
+
import { registerDomSurfacePainterConformanceControl } from "./SurfacePainterConformanceControl.js";
|
|
4
6
|
import { fontForStyle } from "./CanvasSurfacePainter.js";
|
|
5
7
|
//#region src/DomSurfacePainter.ts
|
|
6
8
|
/**
|
|
@@ -21,6 +23,7 @@ import { fontForStyle } from "./CanvasSurfacePainter.js";
|
|
|
21
23
|
* `letter-spacing`, measured once per font/size pair.
|
|
22
24
|
*/
|
|
23
25
|
var DomSurfacePainter = class {
|
|
26
|
+
onImagePayloadMiss;
|
|
24
27
|
root;
|
|
25
28
|
rowsLayer;
|
|
26
29
|
imagesLayer;
|
|
@@ -30,6 +33,22 @@ var DomSurfacePainter = class {
|
|
|
30
33
|
renderedGridKey;
|
|
31
34
|
hasRenderedFrame = false;
|
|
32
35
|
letterSpacing;
|
|
36
|
+
reportedMissingImageIds = /* @__PURE__ */ new Set();
|
|
37
|
+
lastImageRecoveryFrame;
|
|
38
|
+
lastEpoch;
|
|
39
|
+
constructor(options = {}) {
|
|
40
|
+
this.onImagePayloadMiss = options.onImagePayloadMiss ?? (() => {});
|
|
41
|
+
registerDomSurfacePainterConformanceControl(this, {
|
|
42
|
+
evictImages: (ids) => {
|
|
43
|
+
for (const id of ids) {
|
|
44
|
+
this.renderedImages.get(id)?.container.remove();
|
|
45
|
+
this.renderedImages.delete(id);
|
|
46
|
+
this.reportedMissingImageIds.delete(id);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
visibleImageIDs: () => [...this.renderedImages.keys()].sort()
|
|
50
|
+
});
|
|
51
|
+
}
|
|
33
52
|
/**
|
|
34
53
|
* Binds the container the painter renders into. The runtime owns the
|
|
35
54
|
* container's size; the painter owns everything inside it.
|
|
@@ -51,11 +70,18 @@ var DomSurfacePainter = class {
|
|
|
51
70
|
this.appliedMetricsKey = void 0;
|
|
52
71
|
this.renderedGridKey = void 0;
|
|
53
72
|
this.hasRenderedFrame = false;
|
|
73
|
+
this.reportedMissingImageIds.clear();
|
|
74
|
+
this.lastImageRecoveryFrame = void 0;
|
|
75
|
+
this.lastEpoch = void 0;
|
|
54
76
|
}
|
|
55
|
-
paint(metrics, frame, damage) {
|
|
77
|
+
paint(metrics, frame, damage, _recoveredImagePayloadIds) {
|
|
56
78
|
const root = this.root;
|
|
57
79
|
const rowsLayer = this.rowsLayer;
|
|
58
80
|
if (!root || !rowsLayer) return;
|
|
81
|
+
if (frame?.epoch !== void 0 && frame.epoch !== this.lastEpoch) {
|
|
82
|
+
this.lastEpoch = frame.epoch;
|
|
83
|
+
this.reportedMissingImageIds.clear();
|
|
84
|
+
}
|
|
59
85
|
const metricsKey = metricsKeyFor(metrics);
|
|
60
86
|
const metricsChanged = metricsKey !== this.appliedMetricsKey;
|
|
61
87
|
if (metricsChanged) {
|
|
@@ -65,7 +91,8 @@ var DomSurfacePainter = class {
|
|
|
65
91
|
if (!frame) {
|
|
66
92
|
this.rowElements = [];
|
|
67
93
|
rowsLayer.replaceChildren();
|
|
68
|
-
this.
|
|
94
|
+
this.lastImageRecoveryFrame = void 0;
|
|
95
|
+
this.reconcileImages([], metrics, false);
|
|
69
96
|
this.renderedGridKey = void 0;
|
|
70
97
|
this.hasRenderedFrame = false;
|
|
71
98
|
return;
|
|
@@ -81,7 +108,9 @@ var DomSurfacePainter = class {
|
|
|
81
108
|
if (row < 0 || row >= frame.rows.length) continue;
|
|
82
109
|
this.rebuildRow(row, frame, metrics);
|
|
83
110
|
}
|
|
84
|
-
|
|
111
|
+
const allowRecoveryRequests = frame !== this.lastImageRecoveryFrame;
|
|
112
|
+
this.lastImageRecoveryFrame = frame;
|
|
113
|
+
this.reconcileImages(frame.images ?? [], metrics, allowRecoveryRequests);
|
|
85
114
|
this.hasRenderedFrame = true;
|
|
86
115
|
}
|
|
87
116
|
rebuildRow(y, frame, metrics) {
|
|
@@ -143,10 +172,12 @@ var DomSurfacePainter = class {
|
|
|
143
172
|
};
|
|
144
173
|
return value;
|
|
145
174
|
}
|
|
146
|
-
reconcileImages(images, metrics) {
|
|
175
|
+
reconcileImages(images, metrics, allowRecoveryRequests) {
|
|
147
176
|
const layer = this.imagesLayer;
|
|
148
177
|
if (!layer) return;
|
|
149
178
|
const next = /* @__PURE__ */ new Map();
|
|
179
|
+
const currentMissingImageIds = /* @__PURE__ */ new Set();
|
|
180
|
+
const newlyMissingImageIds = /* @__PURE__ */ new Set();
|
|
150
181
|
for (const rawImage of images) {
|
|
151
182
|
if (!isSupportedImageFormat(rawImage.format)) continue;
|
|
152
183
|
const image = {
|
|
@@ -156,7 +187,13 @@ var DomSurfacePainter = class {
|
|
|
156
187
|
const [boundsX, boundsY, boundsWidth, boundsHeight] = image.bounds;
|
|
157
188
|
const [clipX, clipY, clipWidth, clipHeight] = image.visibleBounds;
|
|
158
189
|
const existing = this.renderedImages.get(image.id);
|
|
159
|
-
if (
|
|
190
|
+
if (boundsWidth <= 0 || boundsHeight <= 0 || clipWidth <= 0 || clipHeight <= 0) continue;
|
|
191
|
+
if (!existing && image.dataBase64 === void 0) {
|
|
192
|
+
if (!isWebHostImageRecoveryId(image.id)) continue;
|
|
193
|
+
currentMissingImageIds.add(image.id);
|
|
194
|
+
if (!this.reportedMissingImageIds.has(image.id)) newlyMissingImageIds.add(image.id);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
160
197
|
const entry = existing ?? makeImageEntry();
|
|
161
198
|
entry.container.style.left = `${clipX * metrics.cellWidth}px`;
|
|
162
199
|
entry.container.style.top = `${clipY * metrics.cellHeight}px`;
|
|
@@ -178,6 +215,14 @@ var DomSurfacePainter = class {
|
|
|
178
215
|
}
|
|
179
216
|
for (const [id, entry] of this.renderedImages) if (!next.has(id)) entry.container.remove();
|
|
180
217
|
this.renderedImages = next;
|
|
218
|
+
for (const id of this.reportedMissingImageIds) if (!currentMissingImageIds.has(id)) this.reportedMissingImageIds.delete(id);
|
|
219
|
+
if (allowRecoveryRequests && newlyMissingImageIds.size > 0) {
|
|
220
|
+
const candidateIds = [...newlyMissingImageIds].sort();
|
|
221
|
+
const admittedIds = this.onImagePayloadMiss(candidateIds);
|
|
222
|
+
const acceptedIds = Array.isArray(admittedIds) ? admittedIds : candidateIds;
|
|
223
|
+
const candidates = new Set(candidateIds);
|
|
224
|
+
for (const id of acceptedIds) if (candidates.has(id)) this.reportedMissingImageIds.add(id);
|
|
225
|
+
}
|
|
181
226
|
}
|
|
182
227
|
};
|
|
183
228
|
/**
|