gesso-core 0.4.0 → 0.4.1

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/testing.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Aa as UiTouchScroller, Cn as CanvasHost, Ds as Canvas2DContext, Eo as UiHitTester, Ii as LayoutEngine, Ja as UiKeyboardController, Oa as UiEditingController, Os as Canvas2DGradient, Qa as ScrollSink, Xa as UiPointerController, Ya as PointerControllerOptions, Za as ScrollContainerState, ao as UiGestureRecognizer, ba as UiPlatformAdapter, cc as TextMeasurer, fn as Canvas2DRenderer, gp as UiInputDispatcher, im as LayoutResult, ka as TouchScrollerOptions, mh as LayoutRecord, nm as Constraints, pm as UiNode, rm as LayoutBox, tl as UiGraph, to as UiWheelController, uo as UiFocusManager, va as PlatformEventTarget, wn as CanvasSurface, ya as PlatformSurface, yh as UiNodeType } from "./index-BzXn0Bci.js";
1
+ import { $a as UiPointerController, As as Canvas2DContext, Ca as UiPlatformAdapter, Dn as CanvasSurface, En as CanvasHost, Ma as TouchScrollerOptions, Na as UiTouchScroller, Qa as PointerControllerOptions, Sa as PlatformSurface, Sh as UiNodeType, Za as UiKeyboardController, _h as LayoutRecord, am as Constraints, co as UiGestureRecognizer, dc as TextMeasurer, eo as ScrollContainerState, fn as Canvas2DRenderer, gm as UiNode, il as UiGraph, io as UiWheelController, ja as UiEditingController, js as Canvas2DGradient, ko as UiHitTester, om as LayoutBox, po as UiFocusManager, sm as LayoutResult, to as ScrollSink, xa as PlatformEventTarget, yp as UiInputDispatcher, zi as LayoutEngine } from "./index-CfUSbphd.js";
2
2
  //#region src/layout/LayoutTestUtils.d.ts
3
3
  /**
4
4
  * Shared helpers for layout specs: builds UiNode trees through
@@ -174,6 +174,13 @@ declare class RecordingCanvasContext implements Canvas2DContext {
174
174
  set textAlign(value: CanvasTextAlign);
175
175
  get textBaseline(): CanvasTextBaseline;
176
176
  set textBaseline(value: CanvasTextBaseline);
177
+ /**
178
+ * Drawing state saved by `save`, as a real context keeps it. Without
179
+ * it a spec that renders twice read the first frame's leftovers: the
180
+ * renderer multiplies `globalAlpha` inside a `save`, and a double that
181
+ * did not restore it compounded the opacity frame over frame.
182
+ */
183
+ private readonly saved;
177
184
  save(): void;
178
185
  restore(): void;
179
186
  translate(x: number, y: number): void;
@@ -195,6 +202,11 @@ declare class RecordingCanvasContext implements Canvas2DContext {
195
202
  fillText(text: string, x: number, y: number, maxWidth?: number): void;
196
203
  measureText(text: string): TextMetrics;
197
204
  drawImage(image: ImageBitmap, dx: number, dy: number, dw: number, dh: number): void;
205
+ transform(a: number, b: number, c: number, d: number, e: number, f: number): void;
206
+ quadraticCurveTo(cx: number, cy: number, x: number, y: number): void;
207
+ bezierCurveTo(c1x: number, c1y: number, c2x: number, c2y: number, x: number, y: number): void;
208
+ arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, counterclockwise?: boolean): void;
209
+ setLineDash(segments: readonly number[]): void;
198
210
  createLinearGradient(x0: number, y0: number, x1: number, y1: number): RecordedGradient;
199
211
  createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): RecordedGradient;
200
212
  private record;
package/dist/testing.js CHANGED
@@ -1,4 +1,4 @@
1
- import { N as Constraints, Tt as CharacterCountTextMeasurer, X as UiPlatformAdapter, at as UiWheelController, b as LayoutEngine, ct as UiPointerController, lt as UiFocusManager, nt as UiTouchScroller, pr as UiGraph, r as Canvas2DRenderer, rt as UiGestureRecognizer, st as UiKeyboardController, t as CanvasSurface, tt as UiEditingController, ut as UiHitTester, vt as UiInputDispatcher } from "./CanvasSurface-BR7hECHm.js";
1
+ import { N as Constraints, Tt as CharacterCountTextMeasurer, X as UiPlatformAdapter, at as UiWheelController, b as LayoutEngine, ct as UiPointerController, lt as UiFocusManager, nt as UiTouchScroller, pr as UiGraph, r as Canvas2DRenderer, rt as UiGestureRecognizer, st as UiKeyboardController, t as CanvasSurface, tt as UiEditingController, ut as UiHitTester, vt as UiInputDispatcher } from "./CanvasSurface-CaNWsAlm.js";
2
2
  //#region src/layout/LayoutTestUtils.ts
3
3
  /**
4
4
  * Shared helpers for layout specs: builds UiNode trees through
@@ -335,10 +335,40 @@ var RecordingCanvasContext = class {
335
335
  this._textBaseline = value;
336
336
  this.record("set:textBaseline", [value]);
337
337
  }
338
+ /**
339
+ * Drawing state saved by `save`, as a real context keeps it. Without
340
+ * it a spec that renders twice read the first frame's leftovers: the
341
+ * renderer multiplies `globalAlpha` inside a `save`, and a double that
342
+ * did not restore it compounded the opacity frame over frame.
343
+ */
344
+ saved = [];
338
345
  save() {
346
+ this.saved.push({
347
+ fillStyle: this._fillStyle,
348
+ strokeStyle: this._strokeStyle,
349
+ lineWidth: this._lineWidth,
350
+ lineJoin: this._lineJoin,
351
+ globalAlpha: this._globalAlpha,
352
+ font: this._font,
353
+ textAlign: this._textAlign,
354
+ textBaseline: this._textBaseline,
355
+ letterSpacing: this.letterSpacing
356
+ });
339
357
  this.record("save", []);
340
358
  }
341
359
  restore() {
360
+ const state = this.saved.pop();
361
+ if (state !== void 0) {
362
+ this._fillStyle = state.fillStyle;
363
+ this._strokeStyle = state.strokeStyle;
364
+ this._lineWidth = state.lineWidth;
365
+ this._lineJoin = state.lineJoin;
366
+ this._globalAlpha = state.globalAlpha;
367
+ this._font = state.font;
368
+ this._textAlign = state.textAlign;
369
+ this._textBaseline = state.textBaseline;
370
+ this.letterSpacing = state.letterSpacing;
371
+ }
342
372
  this.record("restore", []);
343
373
  }
344
374
  translate(x, y) {
@@ -448,6 +478,47 @@ var RecordingCanvasContext = class {
448
478
  dh
449
479
  ]);
450
480
  }
481
+ transform(a, b, c, d, e, f) {
482
+ this.record("transform", [
483
+ a,
484
+ b,
485
+ c,
486
+ d,
487
+ e,
488
+ f
489
+ ]);
490
+ }
491
+ quadraticCurveTo(cx, cy, x, y) {
492
+ this.record("quadraticCurveTo", [
493
+ cx,
494
+ cy,
495
+ x,
496
+ y
497
+ ]);
498
+ }
499
+ bezierCurveTo(c1x, c1y, c2x, c2y, x, y) {
500
+ this.record("bezierCurveTo", [
501
+ c1x,
502
+ c1y,
503
+ c2x,
504
+ c2y,
505
+ x,
506
+ y
507
+ ]);
508
+ }
509
+ arc(x, y, radius, startAngle, endAngle, counterclockwise = false) {
510
+ this.record("arc", [
511
+ x,
512
+ y,
513
+ radius,
514
+ startAngle,
515
+ endAngle,
516
+ counterclockwise
517
+ ]);
518
+ }
519
+ setLineDash(segments) {
520
+ this.record("setLineDash", [segments]);
521
+ }
451
522
  createLinearGradient(x0, y0, x1, y1) {
452
523
  const gradient = new RecordedGradient("linear", [
453
524
  x0,
@@ -1 +1 @@
1
- {"version":3,"file":"testing.js","names":[],"sources":["../src/layout/LayoutTestUtils.ts","../src/input/UiInputTestUtils.ts","../src/rendering/RenderTestUtils.ts","../src/media/Mp4TestUtils.ts"],"sourcesContent":["import { UiGraph } from '../graph/UiGraph';\nimport type { UiNode } from '../graph/UiNode';\nimport { UiNodeType } from '../graph/UiNodeType';\nimport { LayoutEngine } from './LayoutEngine';\nimport { Constraints } from './LayoutTypes';\nimport type { LayoutBox, LayoutResult } from './LayoutTypes';\nimport type { LayoutRecord } from './LayoutRecord';\nimport type { TextMeasurer } from './TextMeasurer';\n\n/**\n * Shared helpers for layout specs: builds UiNode trees through\n * the real graph and drives LayoutEngine.\n */\nexport class LayoutHarness {\n readonly graph = new UiGraph();\n readonly engine: LayoutEngine;\n\n constructor(textMeasurer?: TextMeasurer) {\n this.engine = new LayoutEngine(textMeasurer);\n }\n\n createNode(id: string, type: UiNodeType): UiNode {\n return this.graph.createNode(id, type);\n }\n\n append(parent: UiNode, ...children: UiNode[]): void {\n for (const child of children) {\n this.graph.appendChild(parent, child);\n }\n }\n\n layout(node: UiNode, constraints: Constraints = Constraints.unbounded()): LayoutResult {\n return this.engine.layout(node, constraints);\n }\n\n record(node: UiNode): LayoutRecord {\n const record = this.engine.recordFor(node);\n if (record === undefined) {\n throw new Error(`No layout record for '${node.id}'.`);\n }\n return record;\n }\n\n box(node: UiNode): LayoutBox {\n const record = this.record(node);\n return { x: record.x, y: record.y, width: record.width, height: record.height };\n }\n\n boxOf(node: UiNode): LayoutBox {\n return this.engine.worldBox(node);\n }\n\n /** Where the node is seen: scroll offsets and sticky shifts applied. */\n visibleBox(node: UiNode): LayoutBox {\n return this.engine.visibleBox(node);\n }\n}\n","import { UiNodeType } from '../graph/UiNodeType';\nimport type { UiNode } from '../graph/UiNode';\nimport { Constraints } from '../layout/LayoutTypes';\nimport { LayoutHarness } from '../layout/LayoutTestUtils';\nimport { UiHitTester } from './UiHitTester';\nimport { UiInputDispatcher } from './UiInputDispatcher';\nimport { UiPointerController, type PointerControllerOptions } from './UiPointerController';\nimport { UiGestureRecognizer } from './UiGestureRecognizer';\nimport { UiTouchScroller, type TouchScrollerOptions } from './UiTouchScroller';\nimport { UiFocusManager } from './UiFocusManager';\nimport { UiKeyboardController } from './UiKeyboardController';\nimport { UiEditingController, type EditingHost } from './UiEditingController';\nimport { UiWheelController, type ScrollContainerState, type ScrollSink } from './UiWheelController';\nimport { UiPlatformAdapter, type PlatformEventTarget, type PlatformSurface } from './UiPlatformAdapter';\nimport { CharacterCountTextMeasurer, type TextMeasurer } from '../layout/TextMeasurer';\n\n/**\n * Shared harness for input specs.\n *\n * Builds real UiNode trees through UiGraph + LayoutEngine so hit\n * testing, dispatch, focus and gestures are tested against actual\n * layout geometry rather than hand-written boxes.\n */\nexport class InputTestHarness {\n readonly layout: LayoutHarness;\n readonly root: UiNode;\n readonly dispatcher = new UiInputDispatcher();\n /** Shared, so a spec can read back what a controller scrolled. */\n readonly scrollSink = new HarnessScrollSink(this);\n\n /** A measurer is needed only by specs whose trees contain real text. */\n private readonly textMeasurer: TextMeasurer | undefined;\n\n constructor(width = 400, height = 400, textMeasurer?: TextMeasurer) {\n this.textMeasurer = textMeasurer;\n this.layout = new LayoutHarness(textMeasurer);\n this.root = this.layout.createNode('app', UiNodeType.Column);\n this.root.setProperty('width', width);\n this.root.setProperty('height', height);\n }\n\n node(id: string, type: UiNodeType, props: Record<string, unknown> = {}): UiNode {\n const node = this.layout.createNode(id, type);\n for (const [key, value] of Object.entries(props)) {\n node.setProperty(key, value);\n }\n return node;\n }\n\n add(parent: UiNode, ...children: UiNode[]): void {\n this.layout.append(parent, ...children);\n }\n\n layoutTree(): void {\n this.layout.layout(this.root, Constraints.loose(400, 400));\n }\n\n createHitTester(): UiHitTester {\n return new UiHitTester(this.layout.engine, this.root);\n }\n\n createPointerController(): UiPointerController {\n return new UiPointerController(this.createHitTester(), this.dispatcher);\n }\n\n createGestureRecognizer(): UiGestureRecognizer {\n return new UiGestureRecognizer(this.dispatcher);\n }\n\n createFocusManager(): UiFocusManager {\n return new UiFocusManager(this.root, this.dispatcher);\n }\n\n createKeyboardController(): UiKeyboardController {\n return new UiKeyboardController(this.dispatcher, this.createFocusManager(), this.root);\n }\n\n /**\n * An editing controller over this harness's layout and dispatcher.\n *\n * The host is the layout harness itself: everything `EditingHost`\n * asks for is something the layout already knows, which is why the\n * controller takes an interface rather than the engine.\n */\n createEditingController(focus: UiFocusManager = this.createFocusManager()): UiEditingController {\n const layout = this.layout;\n const hitTester = this.createHitTester();\n const host: EditingHost = {\n recordFor: node => layout.engine.recordFor(node),\n visibleBox: node => layout.engine.visibleBox(node),\n toLocal: (node, x, y) => hitTester.toLocal(node, x, y),\n // The controller measures only for caret geometry. A spec whose\n // tree has real text passes its own measurer to the harness; the\n // deterministic one is right for every other.\n measurer: this.textMeasurer ?? new CharacterCountTextMeasurer(),\n markDirty: () => undefined,\n reveal: () => undefined,\n now: () => 0\n };\n return new UiEditingController(host, this.dispatcher, focus);\n }\n\n createWheelController(): UiWheelController {\n return new UiWheelController(this.createHitTester(), this.dispatcher, this.scrollSink, () => this.root);\n }\n\n /**\n * A pointer controller with a gesture recognizer behind it, for the\n * specs that need a real Pan rather than a hand-made one.\n */\n createGesturePointerController(options: PointerControllerOptions = {}): UiPointerController {\n return new UiPointerController(this.createHitTester(), this.dispatcher, {\n gestures: new UiGestureRecognizer(this.dispatcher),\n scrollSink: this.scrollSink,\n ...options\n });\n }\n\n createTouchScroller(options: TouchScrollerOptions = {}): UiTouchScroller {\n return new UiTouchScroller(this.dispatcher, this.root, this.scrollSink, options);\n }\n\n createPlatformAdapter(): UiPlatformAdapter {\n return new UiPlatformAdapter({\n pointerController: this.createPointerController(),\n wheelController: this.createWheelController(),\n keyboardController: this.createKeyboardController()\n });\n }\n\n box(node: UiNode): { x: number; y: number; width: number; height: number } {\n return this.layout.boxOf(node);\n }\n}\n\n/**\n * Minimal fake event target for testing the platform adapter.\n */\nexport class FakeEventTarget implements PlatformEventTarget {\n private readonly listeners: { type: string; listener: (event: Event) => void }[] = [];\n\n addEventListener(type: string, listener: ((event: Event) => void) | null): void {\n if (listener !== null) {\n this.listeners.push({ type, listener });\n }\n }\n\n removeEventListener(type: string, listener: ((event: Event) => void) | null): void {\n if (listener === null) return;\n const index = this.listeners.findIndex(entry => entry.type === type && entry.listener === listener);\n if (index !== -1) {\n this.listeners.splice(index, 1);\n }\n }\n\n emit(type: string, event: Event): void {\n for (const entry of this.listeners) {\n if (entry.type === type) {\n entry.listener(event);\n }\n }\n }\n}\n\n/**\n * Fake PlatformSurface for tests. Emits DOM-like events from plain\n * objects so PointerEvent/WheelEvent/KeyboardEvent constructors are\n * not required in the test environment.\n */\nexport class FakePlatformSurface implements PlatformSurface {\n readonly pointerTarget = new FakeEventTarget();\n readonly keyboardTarget = new FakeEventTarget();\n localX = 0;\n localY = 0;\n /** The last `touch-action` the adapter asked for; '' before any. */\n touchAction = '';\n\n clientToLocal(_clientX: number, _clientY: number): { x: number; y: number } {\n return { x: this.localX, y: this.localY };\n }\n\n setTouchAction(value: string): void {\n this.touchAction = value;\n }\n}\n\n/**\n * ScrollSink backed by the harness LayoutEngine. Updates the\n * container's scrollX/scrollY properties, then re-lays out so the\n * engine re-reads and clamps the offset, mirroring how the app layer\n * drives scroll through node properties.\n */\nexport class HarnessScrollSink implements ScrollSink {\n /** Every scroll asked for, in order, so a spec can read the behaviour hint. */\n readonly calls: { node: UiNode; dx: number; dy: number; behavior: string }[] = [];\n /** Containers whose scrollbars were revealed. */\n readonly revealed: UiNode[] = [];\n\n constructor(private readonly harness: InputTestHarness) {}\n\n containerState(node: UiNode): ScrollContainerState | undefined {\n const record = this.harness.layout.engine.recordFor(node);\n if (record === undefined) {\n return undefined;\n }\n const horizontal = node.getProperty('direction') === 'row';\n return {\n scrollX: record.scrollX,\n scrollY: record.scrollY,\n maxScrollX: Math.max(0, record.contentWidth - record.width),\n maxScrollY: Math.max(0, record.contentHeight - record.height),\n horizontal,\n viewportWidth: record.width,\n viewportHeight: record.height\n };\n }\n\n scrollBy(node: UiNode, dx: number, dy: number, behavior: string = 'instant'): void {\n this.calls.push({ node, dx, dy, behavior });\n const record = this.harness.layout.engine.recordFor(node);\n if (record === undefined) {\n return;\n }\n const maxX = Math.max(0, record.contentWidth - record.width);\n const maxY = Math.max(0, record.contentHeight - record.height);\n node.setProperty('scrollX', clamp(record.scrollX + dx, 0, maxX));\n node.setProperty('scrollY', clamp(record.scrollY + dy, 0, maxY));\n this.harness.layoutTree();\n }\n\n scrollContainers(): Iterable<UiNode> {\n return this.harness.layout.engine.scrollContainers();\n }\n\n revealScrollbars(node: UiNode): void {\n this.revealed.push(node);\n }\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n","import { UiGraph } from '../graph/UiGraph';\nimport { UiNodeType } from '../graph/UiNodeType';\nimport type { UiNode } from '../graph/UiNode';\nimport { LayoutEngine } from '../layout/LayoutEngine';\nimport { CharacterCountTextMeasurer } from '../layout/TextMeasurer';\nimport type { TextMeasurer } from '../layout/TextMeasurer';\nimport { Constraints } from '../layout/LayoutTypes';\nimport type { LayoutBox, LayoutResult } from '../layout/LayoutTypes';\nimport type { LayoutRecord } from '../layout/LayoutRecord';\nimport { Canvas2DRenderer } from './canvas2d/Canvas2DRenderer';\nimport type { Canvas2DContext, Canvas2DGradient } from './canvas2d/Canvas2DContext';\nimport { CanvasSurface } from './canvas2d/CanvasSurface';\nimport type { CanvasHost } from './canvas2d/CanvasSurface';\nimport type { PaintContext2D, PaintGradient } from './PaintTarget';\nimport type { PaintCanvasFactory } from './PaintPicture';\nimport type { PaintLineCap, PaintLineJoin } from './PaintSurface';\n\nexport interface RecordedCall {\n name: string;\n args: unknown[];\n}\n\n/**\n * The gradient a recording context hands back, carrying what it was\n * asked for so a test can read the whole paint rather than a `[object\n * CanvasGradient]`. A real one is opaque, which is exactly what makes\n * a gradient hard to assert on without this.\n */\nexport class RecordedGradient implements Canvas2DGradient {\n readonly stops: { offset: number; color: string }[] = [];\n\n constructor(\n readonly kind: 'linear' | 'radial',\n readonly args: readonly number[]\n ) {}\n\n addColorStop(offset: number, color: string): void {\n this.stops.push({ offset, color });\n }\n}\n\n/**\n * Deterministic Canvas2D spy: records every call and style\n * assignment so tests can assert the exact draw sequence.\n */\nexport class RecordingCanvasContext implements Canvas2DContext {\n readonly calls: RecordedCall[] = [];\n\n private _fillStyle: string | Canvas2DGradient | CanvasPattern = '#000';\n private _strokeStyle: string | Canvas2DGradient | CanvasPattern = '#000';\n private _lineWidth = 1;\n private _lineJoin: CanvasLineJoin = 'miter';\n private _globalAlpha = 1;\n private _font = '';\n private _textAlign: CanvasTextAlign = 'start';\n private _textBaseline: CanvasTextBaseline = 'alphabetic';\n /**\n * Tracking, which is a context property rather than part of the font\n * shorthand — and sticky, so a spec can check it was cleared as well\n * as that it was set.\n */\n letterSpacing = '0px';\n\n get fillStyle(): string | Canvas2DGradient | CanvasPattern {\n return this._fillStyle;\n }\n\n set fillStyle(value: string | Canvas2DGradient | CanvasPattern) {\n this._fillStyle = value;\n this.record('set:fillStyle', [value]);\n }\n\n get strokeStyle(): string | Canvas2DGradient | CanvasPattern {\n return this._strokeStyle;\n }\n\n set strokeStyle(value: string | Canvas2DGradient | CanvasPattern) {\n this._strokeStyle = value;\n this.record('set:strokeStyle', [value]);\n }\n\n get lineWidth(): number {\n return this._lineWidth;\n }\n\n set lineWidth(value: number) {\n this._lineWidth = value;\n this.record('set:lineWidth', [value]);\n }\n\n get lineJoin(): CanvasLineJoin {\n return this._lineJoin;\n }\n\n set lineJoin(value: CanvasLineJoin) {\n this._lineJoin = value;\n this.record('set:lineJoin', [value]);\n }\n\n get globalAlpha(): number {\n return this._globalAlpha;\n }\n\n set globalAlpha(value: number) {\n this._globalAlpha = value;\n this.record('set:globalAlpha', [value]);\n }\n\n get font(): string {\n return this._font;\n }\n\n set font(value: string) {\n this._font = value;\n this.record('set:font', [value]);\n }\n\n get textAlign(): CanvasTextAlign {\n return this._textAlign;\n }\n\n set textAlign(value: CanvasTextAlign) {\n this._textAlign = value;\n this.record('set:textAlign', [value]);\n }\n\n get textBaseline(): CanvasTextBaseline {\n return this._textBaseline;\n }\n\n set textBaseline(value: CanvasTextBaseline) {\n this._textBaseline = value;\n this.record('set:textBaseline', [value]);\n }\n\n save(): void {\n this.record('save', []);\n }\n\n restore(): void {\n this.record('restore', []);\n }\n\n translate(x: number, y: number): void {\n this.record('translate', [x, y]);\n }\n\n scale(x: number, y: number): void {\n this.record('scale', [x, y]);\n }\n\n rotate(angle: number): void {\n this.record('rotate', [angle]);\n }\n\n setTransform(a: number, b: number, c: number, d: number, e: number, f: number): void {\n this.record('setTransform', [a, b, c, d, e, f]);\n }\n\n clearRect(x: number, y: number, width: number, height: number): void {\n this.record('clearRect', [x, y, width, height]);\n }\n\n fillRect(x: number, y: number, width: number, height: number): void {\n this.record('fillRect', [x, y, width, height]);\n }\n\n strokeRect(x: number, y: number, width: number, height: number): void {\n this.record('strokeRect', [x, y, width, height]);\n }\n\n beginPath(): void {\n this.record('beginPath', []);\n }\n\n moveTo(x: number, y: number): void {\n this.record('moveTo', [x, y]);\n }\n\n lineTo(x: number, y: number): void {\n this.record('lineTo', [x, y]);\n }\n\n arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void {\n this.record('arcTo', [x1, y1, x2, y2, radius]);\n }\n\n closePath(): void {\n this.record('closePath', []);\n }\n\n rect(x: number, y: number, width: number, height: number): void {\n this.record('rect', [x, y, width, height]);\n }\n\n clip(): void {\n this.record('clip', []);\n }\n\n fill(): void {\n this.record('fill', []);\n }\n\n stroke(): void {\n this.record('stroke', []);\n }\n\n fillText(text: string, x: number, y: number, maxWidth?: number): void {\n const args = maxWidth === undefined ? [text, x, y] : [text, x, y, maxWidth];\n this.record('fillText', args);\n }\n\n measureText(text: string): TextMetrics {\n this.record('measureText', [text]);\n return { width: text.length * 8 } as TextMetrics;\n }\n\n drawImage(image: ImageBitmap, dx: number, dy: number, dw: number, dh: number): void {\n this.record('drawImage', [image, dx, dy, dw, dh]);\n }\n\n createLinearGradient(x0: number, y0: number, x1: number, y1: number): RecordedGradient {\n const gradient = new RecordedGradient('linear', [x0, y0, x1, y1]);\n this.record('createLinearGradient', [x0, y0, x1, y1]);\n return gradient;\n }\n\n createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): RecordedGradient {\n const gradient = new RecordedGradient('radial', [x0, y0, r0, x1, y1, r1]);\n this.record('createRadialGradient', [x0, y0, r0, x1, y1, r1]);\n return gradient;\n }\n\n private record(name: string, args: unknown[]): void {\n this.calls.push({ name, args: args.map(normalizeNumber) });\n }\n}\n\n/**\n * Normalizes -0 to 0 so scroll translations assert cleanly.\n */\nfunction normalizeNumber(value: unknown): unknown {\n if (typeof value === 'number' && Object.is(value, -0)) {\n return 0;\n }\n return value;\n}\n\nexport function callNames(context: RecordingCanvasContext): string[] {\n return context.calls.map(call => call.name);\n}\n\nexport function callsOf(context: RecordingCanvasContext, name: string): RecordedCall[] {\n return context.calls.filter(call => call.name === name);\n}\n\nexport function callArgs(context: RecordingCanvasContext, name: string): unknown[][] {\n return callsOf(context, name).map(call => (call.name.startsWith('set:') ? call.args[0] : call.args) as unknown[]);\n}\n\nexport function savedDepth(context: RecordingCanvasContext): number {\n let depth = 0;\n for (const call of context.calls) {\n if (call.name === 'save') {\n depth++;\n } else if (call.name === 'restore') {\n depth--;\n }\n }\n return depth;\n}\n\n/**\n * Deterministic `PaintContext2D` spy: the picture rasteriser's\n * equivalent of `RecordingCanvasContext`, so a spec can assert what a\n * painter actually drew without a browser.\n */\nexport class RecordingPaintContext implements PaintContext2D {\n readonly calls: RecordedCall[] = [];\n\n lineDashOffset = 0;\n lineWidth = 1;\n lineCap: PaintLineCap = 'butt';\n lineJoin: PaintLineJoin = 'miter';\n miterLimit = 10;\n globalAlpha = 1;\n filter = 'none';\n fillStyle: string | PaintGradient = '#000';\n strokeStyle: string | PaintGradient = '#000';\n font = '';\n textAlign: 'left' | 'center' | 'right' = 'left';\n textBaseline = 'alphabetic' as const;\n\n save(): void {\n this.record('save', []);\n }\n\n restore(): void {\n this.record('restore', []);\n }\n\n translate(x: number, y: number): void {\n this.record('translate', [x, y]);\n }\n\n scale(x: number, y: number): void {\n this.record('scale', [x, y]);\n }\n\n rotate(angle: number): void {\n this.record('rotate', [angle]);\n }\n\n transform(a: number, b: number, c: number, d: number, e: number, f: number): void {\n this.record('transform', [a, b, c, d, e, f]);\n }\n\n beginPath(): void {\n this.record('beginPath', []);\n }\n\n moveTo(x: number, y: number): void {\n this.record('moveTo', [x, y]);\n }\n\n lineTo(x: number, y: number): void {\n this.record('lineTo', [x, y]);\n }\n\n quadraticCurveTo(cx: number, cy: number, x: number, y: number): void {\n this.record('quadraticCurveTo', [cx, cy, x, y]);\n }\n\n bezierCurveTo(c1x: number, c1y: number, c2x: number, c2y: number, x: number, y: number): void {\n this.record('bezierCurveTo', [c1x, c1y, c2x, c2y, x, y]);\n }\n\n arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, counterclockwise = false): void {\n this.record('arc', [x, y, radius, startAngle, endAngle, counterclockwise]);\n }\n\n arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void {\n this.record('arcTo', [x1, y1, x2, y2, radius]);\n }\n\n rect(x: number, y: number, width: number, height: number): void {\n this.record('rect', [x, y, width, height]);\n }\n\n closePath(): void {\n this.record('closePath', []);\n }\n\n fill(fillRule?: 'nonzero' | 'evenodd'): void {\n this.record('fill', [fillRule ?? 'nonzero', this.fillStyle]);\n }\n\n stroke(): void {\n this.record('stroke', [this.strokeStyle, this.lineWidth]);\n }\n\n clip(fillRule?: 'nonzero' | 'evenodd'): void {\n this.record('clip', [fillRule ?? 'nonzero']);\n }\n\n fillText(text: string, x: number, y: number): void {\n this.record('fillText', [text, x, y, this.font, this.textAlign]);\n }\n\n drawImage(image: ImageBitmap, dx: number, dy: number, dw: number, dh: number): void {\n this.record('drawImage', [image, dx, dy, dw, dh]);\n }\n\n setLineDash(segments: readonly number[]): void {\n this.record('setLineDash', [[...segments]]);\n }\n\n createLinearGradient(x0: number, y0: number, x1: number, y1: number): RecordedGradient {\n const gradient = new RecordedGradient('linear', [x0, y0, x1, y1]);\n this.record('createLinearGradient', [x0, y0, x1, y1]);\n return gradient;\n }\n\n createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): RecordedGradient {\n const gradient = new RecordedGradient('radial', [x0, y0, r0, x1, y1, r1]);\n this.record('createRadialGradient', [x0, y0, r0, x1, y1, r1]);\n return gradient;\n }\n\n private record(name: string, args: unknown[]): void {\n this.calls.push({ name, args: args.map(normalizeNumber) });\n }\n}\n\n/**\n * A picture that never becomes real pixels.\n *\n * `take()` hands back a stand-in for the `ImageBitmap` a browser would\n * transfer out of an `OffscreenCanvas`, carrying only the size and a\n * `close`, which is all either renderer reads off one. Every context\n * the factory hands out is kept, so a spec can assert both what was\n * drawn and how many times anything was drawn at all.\n */\nexport class FakePaintCanvases {\n readonly contexts: RecordingPaintContext[] = [];\n readonly bitmaps: { width: number; height: number; closed: boolean }[] = [];\n\n readonly create: PaintCanvasFactory = (width, height) => {\n const context = new RecordingPaintContext();\n this.contexts.push(context);\n const bitmap = { width, height, closed: false };\n this.bitmaps.push(bitmap);\n return {\n context: () => context,\n take: () =>\n ({\n width,\n height,\n close: () => {\n bitmap.closed = true;\n }\n }) as unknown as ImageBitmap\n };\n };\n\n /** The calls made into the most recent picture. */\n get last(): RecordingPaintContext {\n const context = this.contexts.at(-1);\n if (context === undefined) {\n throw new Error('No picture has been rasterised.');\n }\n return context;\n }\n\n reset(): void {\n this.contexts.length = 0;\n this.bitmaps.length = 0;\n }\n}\n\n/**\n * A canvas host backed by a recording context.\n */\nexport class FakeCanvasHost implements CanvasHost {\n width = 0;\n height = 0;\n private contextRequests = 0;\n\n constructor(private readonly context: Canvas2DContext | null) {}\n\n getContext(_contextId: '2d'): CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null {\n this.contextRequests++;\n return this.context as unknown as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n }\n\n get contextRequestCount(): number {\n return this.contextRequests;\n }\n}\n\n/**\n * Shared harness: a real UiGraph + LayoutEngine + Canvas2DRenderer\n * over a recording context. Deterministic layout comes from the\n * CharacterCountTextMeasurer, shared by layout and renderer.\n */\nexport class RenderHarness {\n readonly graph = new UiGraph();\n readonly measurer: TextMeasurer = new CharacterCountTextMeasurer();\n readonly engine = new LayoutEngine(this.measurer);\n readonly context = new RecordingCanvasContext();\n readonly host = new FakeCanvasHost(this.context);\n readonly surface = new CanvasSurface(this.host);\n readonly renderer = new Canvas2DRenderer({ surface: this.surface });\n\n constructor(logicalWidth: number = 800, logicalHeight: number = 600, dpr: number = 1) {\n this.surface.setLogicalSize(logicalWidth, logicalHeight, dpr);\n }\n\n createNode(id: string, type: UiNodeType): UiNode {\n return this.graph.createNode(id, type);\n }\n\n append(parent: UiNode, ...children: UiNode[]): void {\n for (const child of children) {\n this.graph.appendChild(parent, child);\n }\n }\n\n layout(node: UiNode, constraints: Constraints = Constraints.tight(800, 600)): LayoutResult {\n return this.engine.layout(node, constraints);\n }\n\n render(node: UiNode): void {\n // Far in the future: overlay scrollbars have faded, so draw-call\n // assertions see only the scene. Pass `now` yourself to test them.\n this.renderer.render(node, { layout: this.engine, text: this.measurer, now: Number.MAX_SAFE_INTEGER });\n }\n\n record(node: UiNode): LayoutRecord {\n const record = this.engine.recordFor(node);\n if (record === undefined) {\n throw new Error(`No layout record for '${node.id}'.`);\n }\n return record;\n }\n\n box(node: UiNode): LayoutBox {\n const record = this.record(node);\n return { x: record.x, y: record.y, width: record.width, height: record.height };\n }\n}\n","/**\n * A very small MP4 writer, and a decoder that decodes nothing.\n *\n * Both exist for the same reason the demuxer's spec gives for building\n * its files by hand: a fixture `.mp4` is a better test of reality and\n * a worse test of this code, because when it fails you do not know\n * which of forty boxes was misread. Building the file means a spec can\n * name the exact field it is about, and construct the awkward cases —\n * a chunk holding several samples, composition offsets reordering\n * them, a track with no `stss` — rather than hunting for a file that\n * has one.\n *\n * In `gesso-core/testing` rather than beside a spec because two specs\n * now want it: the demuxer's, which reads these files, and the\n * resolver's, which plays them.\n */\n\nfunction u32(value: number): number[] {\n return [(value >>> 24) & 0xff, (value >>> 16) & 0xff, (value >>> 8) & 0xff, value & 0xff];\n}\n\nfunction u16(value: number): number[] {\n return [(value >>> 8) & 0xff, value & 0xff];\n}\n\nfunction ascii(text: string): number[] {\n return [...text].map(character => character.charCodeAt(0));\n}\n\n/** `size type payload`, which is every box in the format. */\nexport function box(type: string, ...payload: number[][]): number[] {\n const body = payload.flat();\n return [...u32(body.length + 8), ...ascii(type), ...body];\n}\n\n/** A full box: a version and three flag bytes before the payload. */\nexport function fullBox(type: string, version: number, ...payload: number[][]): number[] {\n return box(type, [version, 0, 0, 0], ...payload);\n}\n\n/** An AAC audio track, for a file that has sound as well as pictures. */\nexport interface AudioTrackSpec {\n timescale: number;\n deltas: { count: number; delta: number }[];\n sizes: number[];\n chunkOffsets: number[];\n chunkRuns: { firstChunk: number; samplesPerChunk: number }[];\n channels: number;\n sampleRate: number;\n /**\n * The `AudioSpecificConfig`, whose top five bits are the audio\n * object type. The default is AAC-LC at 44.1kHz stereo, which is\n * `mp4a.40.2` and what nearly every file on the web carries.\n */\n config?: number[];\n}\n\nexport interface TrackSpec {\n timescale: number;\n /** One `stts` run per entry. */\n deltas: { count: number; delta: number }[];\n compositionOffsets?: { count: number; offset: number }[];\n sizes: number[];\n chunkOffsets: number[];\n chunkRuns: { firstChunk: number; samplesPerChunk: number }[];\n /** Omitted means no `stss` at all, which means every sample is a sync sample. */\n syncSamples?: number[];\n width: number;\n height: number;\n handler?: string;\n /** The sample entry's four character code. `avc1` unless said otherwise. */\n codec?: 'avc1' | 'av01' | 'vp09';\n /** The bytes of the configuration record after any fixed header; see `configurationBox`. */\n configBytes?: number[];\n}\n\n/**\n * The codec configuration box, which differs per codec and is where a\n * codec string comes from.\n */\nfunction configurationBox(spec: TrackSpec): number[] {\n if (spec.codec === 'av01') {\n // marker+version, then seq_profile/seq_level_idx, then the tier\n // and bit depth flags.\n return box('av1C', [0x81, ...(spec.configBytes ?? [0x04, 0x00])]);\n }\n if (spec.codec === 'vp09') {\n // A full box: version and flags, then profile, level and a byte\n // whose top nibble is the bit depth.\n return fullBox('vpcC', 1, spec.configBytes ?? [0x00, 0x1f, 0x80]);\n }\n // `avc1`: length, profile, compat, level, then whatever follows.\n return box('avcC', [1, 0x64, 0x00, 0x1f, 0xff, 0xe1]);\n}\n\nfunction visualSampleEntry(spec: TrackSpec): number[] {\n return box(\n spec.codec ?? 'avc1',\n [0, 0, 0, 0, 0, 0], // reserved\n u16(1), // data_reference_index\n Array.from({ length: 16 }, () => 0), // pre_defined + reserved\n u16(spec.width),\n u16(spec.height),\n u32(0x0048_0000), // horizresolution\n u32(0x0048_0000), // vertresolution\n u32(0), // reserved\n u16(1), // frame_count\n Array.from({ length: 32 }, () => 0), // compressorname\n u16(24), // depth\n [0xff, 0xff], // pre_defined = -1\n configurationBox(spec)\n );\n}\n\n/** `esds`: the descriptor tree down to the two numbers a codec string needs. */\nfunction esds(spec: AudioTrackSpec): number[] {\n const config = spec.config ?? [0x12, 0x10];\n // DecoderSpecificInfo (tag 5)\n const dsi = [0x05, config.length, ...config];\n // DecoderConfigDescriptor (tag 4): object type 0x40 is MPEG-4 audio,\n // then a stream type, a buffer size and two bitrates.\n // Thirteen bytes of header: an object type indication, a stream\n // type, three bytes of buffer size, and two four-byte bitrates.\n const dcd = [0x04, 13 + dsi.length, 0x40, 0x15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...dsi];\n // ES_Descriptor (tag 3): an id and a flags byte with nothing set.\n const esd = [0x03, 3 + dcd.length, 0, 1, 0, ...dcd];\n return fullBox('esds', 0, esd);\n}\n\nfunction audioSampleEntry(spec: AudioTrackSpec): number[] {\n return box(\n 'mp4a',\n [0, 0, 0, 0, 0, 0], // reserved\n u16(1), // data_reference_index\n u16(0), // version\n [0, 0, 0, 0, 0, 0], // revision and vendor\n u16(spec.channels),\n u16(16), // sample size\n u16(0), // pre_defined\n u16(0), // reserved\n u16(spec.sampleRate), // 16.16 fixed, whole part\n u16(0),\n esds(spec)\n );\n}\n\nfunction audioSampleTable(spec: AudioTrackSpec): number[] {\n return box(\n 'stbl',\n fullBox('stsd', 0, u32(1), audioSampleEntry(spec)),\n fullBox('stts', 0, u32(spec.deltas.length), ...spec.deltas.map(run => [...u32(run.count), ...u32(run.delta)])),\n fullBox(\n 'stsc',\n 0,\n u32(spec.chunkRuns.length),\n ...spec.chunkRuns.map(run => [...u32(run.firstChunk), ...u32(run.samplesPerChunk), ...u32(1)])\n ),\n fullBox('stsz', 0, u32(0), u32(spec.sizes.length), ...spec.sizes.map(size => u32(size))),\n fullBox('stco', 0, u32(spec.chunkOffsets.length), ...spec.chunkOffsets.map(offset => u32(offset)))\n );\n}\n\nfunction audioTrak(spec: AudioTrackSpec): number[] {\n return box(\n 'trak',\n box(\n 'mdia',\n fullBox('mdhd', 0, u32(0), u32(0), u32(spec.timescale), u32(1000)),\n fullBox(\n 'hdlr',\n 0,\n u32(0),\n ascii('soun'),\n Array.from({ length: 12 }, () => 0),\n [0]\n ),\n box('minf', audioSampleTable(spec))\n )\n );\n}\n\nfunction sampleTable(spec: TrackSpec): number[] {\n const boxes: number[][] = [\n fullBox('stsd', 0, u32(1), visualSampleEntry(spec)),\n fullBox('stts', 0, u32(spec.deltas.length), ...spec.deltas.map(run => [...u32(run.count), ...u32(run.delta)])),\n fullBox(\n 'stsc',\n 0,\n u32(spec.chunkRuns.length),\n ...spec.chunkRuns.map(run => [...u32(run.firstChunk), ...u32(run.samplesPerChunk), ...u32(1)])\n ),\n fullBox('stsz', 0, u32(0), u32(spec.sizes.length), ...spec.sizes.map(size => u32(size))),\n fullBox('stco', 0, u32(spec.chunkOffsets.length), ...spec.chunkOffsets.map(offset => u32(offset)))\n ];\n if (spec.compositionOffsets !== undefined) {\n boxes.splice(\n 2,\n 0,\n fullBox(\n 'ctts',\n 0,\n u32(spec.compositionOffsets.length),\n ...spec.compositionOffsets.map(run => [...u32(run.count), ...u32(run.offset)])\n )\n );\n }\n if (spec.syncSamples !== undefined) {\n boxes.push(fullBox('stss', 0, u32(spec.syncSamples.length), ...spec.syncSamples.map(number => u32(number))));\n }\n return box('stbl', ...boxes);\n}\n\nexport function buildMp4(spec: TrackSpec, options: { fragmented?: boolean; audio?: AudioTrackSpec } = {}): ArrayBuffer {\n const trak = box(\n 'trak',\n box(\n 'mdia',\n fullBox('mdhd', 0, u32(0), u32(0), u32(spec.timescale), u32(1000)),\n fullBox(\n 'hdlr',\n 0,\n u32(0),\n ascii(spec.handler ?? 'vide'),\n Array.from({ length: 12 }, () => 0),\n [0]\n ),\n box('minf', sampleTable(spec))\n )\n );\n const bytes = [\n ...box('ftyp', ascii('isom'), u32(512), ascii('isomiso2avc1mp41')),\n ...box(\n 'moov',\n fullBox(\n 'mvhd',\n 0,\n Array.from({ length: 100 }, () => 0)\n ),\n trak,\n ...(options.audio === undefined ? [] : [audioTrak(options.audio)])\n ),\n ...(options.fragmented === true ? box('moof', box('mfhd', u32(1))) : []),\n // A `mdat` big enough for every offset the tables name.\n ...box(\n 'mdat',\n Array.from({ length: 512 }, () => 0x41)\n )\n ];\n return new Uint8Array(bytes).buffer;\n}\n\n/** One run of samples inside a fragment. */\nexport interface FragmentSpec {\n /** Decode time the fragment starts at, in the track's timescale. Omitted means \"carry on\". */\n baseMediaDecodeTime?: number;\n samples: { duration: number; size: number; isKey: boolean }[];\n}\n\nexport interface FragmentedSpec {\n trackId: number;\n timescale: number;\n width: number;\n height: number;\n fragments: FragmentSpec[];\n /** Written into `trex`, and used by any sample whose `trun` omits the field. */\n defaultSampleDuration?: number;\n defaultSampleSize?: number;\n /** Leave a field out of the `trun` so the default has to supply it. */\n omit?: { duration?: boolean; size?: boolean };\n}\n\nfunction tkhd(trackId: number): number[] {\n return fullBox(\n 'tkhd',\n 0,\n u32(0), // creation time\n u32(0), // modification time\n u32(trackId),\n Array.from({ length: 68 }, () => 0)\n );\n}\n\n/**\n * A fragmented MP4: a `moov` that describes the track and promises\n * fragments, then the fragments.\n *\n * The `stbl` is a shell — `stsd` and four empty tables — which is what\n * a fragmenter writes and is exactly the shape that used to make this\n * demuxer return zero samples for a file the browser played.\n */\nexport function buildFragmentedMp4(spec: FragmentedSpec): ArrayBuffer {\n const shellSpec: TrackSpec = {\n timescale: spec.timescale,\n deltas: [],\n sizes: [],\n chunkOffsets: [],\n chunkRuns: [],\n width: spec.width,\n height: spec.height\n };\n const trak = box(\n 'trak',\n tkhd(spec.trackId),\n box(\n 'mdia',\n fullBox('mdhd', 0, u32(0), u32(0), u32(spec.timescale), u32(1000)),\n fullBox(\n 'hdlr',\n 0,\n u32(0),\n ascii('vide'),\n Array.from({ length: 12 }, () => 0),\n [0]\n ),\n box('minf', sampleTable(shellSpec))\n )\n );\n const mvex = box(\n 'mvex',\n fullBox(\n 'trex',\n 0,\n u32(spec.trackId),\n u32(1), // default_sample_description_index\n u32(spec.defaultSampleDuration ?? 0),\n u32(spec.defaultSampleSize ?? 0),\n u32(0) // default_sample_flags\n )\n );\n const moov = box(\n 'moov',\n fullBox(\n 'mvhd',\n 0,\n Array.from({ length: 100 }, () => 0)\n ),\n trak,\n mvex\n );\n\n const bytes = [...box('ftyp', ascii('isom'), u32(512), ascii('isomiso6avc1mp41')), ...moov];\n\n for (const fragment of spec.fragments) {\n const payloadSize = fragment.samples.reduce((total, sample) => total + sample.size, 0);\n // Built twice: the `trun`'s data offset is relative to the start\n // of the `moof` box, and is not known until the `moof` has been\n // sized. The first pass is only for its length.\n const build = (dataOffset: number): number[] => {\n const trunFlags =\n 0x000001 | // data-offset-present\n (spec.omit?.duration === true ? 0 : 0x000100) |\n (spec.omit?.size === true ? 0 : 0x000200) |\n 0x000400; // sample-flags-present\n const entries = fragment.samples.flatMap(sample => [\n ...(spec.omit?.duration === true ? [] : u32(sample.duration)),\n ...(spec.omit?.size === true ? [] : u32(sample.size)),\n // Bit 16 set means \"not a sync sample\".\n ...u32(sample.isKey ? 0 : 0x00010000)\n ]);\n const trun = box(\n 'trun',\n [0, (trunFlags >> 16) & 0xff, (trunFlags >> 8) & 0xff, trunFlags & 0xff],\n u32(fragment.samples.length),\n u32(dataOffset),\n entries\n );\n const tfhdFlags = 0x020000; // default-base-is-moof\n const traf = box(\n 'traf',\n box('tfhd', [0, (tfhdFlags >> 16) & 0xff, (tfhdFlags >> 8) & 0xff, tfhdFlags & 0xff], u32(spec.trackId)),\n ...(fragment.baseMediaDecodeTime === undefined ? [] : [fullBox('tfdt', 0, u32(fragment.baseMediaDecodeTime))]),\n trun\n );\n return box('moof', fullBox('mfhd', 0, u32(1)), traf);\n };\n const sized = build(0);\n // The `mdat` payload begins eight bytes past the end of the `moof`.\n const moof = build(sized.length + 8);\n bytes.push(\n ...moof,\n ...box(\n 'mdat',\n Array.from({ length: payloadSize }, () => 0x42)\n )\n );\n }\n return new Uint8Array(bytes).buffer;\n}\n\n/** The offset `mdat`'s payload lands at, for a table that must point into it. */\nexport function mdatStart(data: ArrayBuffer): number {\n const view = new DataView(data);\n let at = 0;\n while (at + 8 <= data.byteLength) {\n const size = view.getUint32(at);\n const type = String.fromCharCode(\n view.getUint8(at + 4),\n view.getUint8(at + 5),\n view.getUint8(at + 6),\n view.getUint8(at + 7)\n );\n if (type === 'mdat') {\n return at + 8;\n }\n at += size;\n }\n throw new Error('no mdat');\n}\n\n/** One chunk as the fake decoder saw it. */\nexport interface DecodedChunk {\n readonly timestamp: number;\n readonly type: 'key' | 'delta';\n}\n\n/**\n * A `VideoDecoder` that records what it was asked to decode and hands\n * back a frame for each.\n *\n * What the resolver's spec is about is *which samples are submitted*:\n * a seek that starts at the wrong keyframe, or one that decodes the\n * whole clip to get somewhere, is a bug in arithmetic that has nothing\n * to do with pixels. So this decodes nothing and the frames it emits\n * carry a timestamp and a `close` and no image at all.\n *\n * Emission is synchronous, which a real decoder's never is. That makes\n * the specs readable and is the one way this lies: the ordering\n * hazards a real decoder creates — a frame arriving after the position\n * has moved past it — are `Mp4Playback`'s to handle and are asserted\n * by pushing frames in by hand rather than by waiting for a thread.\n */\nexport class FakeVideoDecoder {\n static readonly submitted: DecodedChunk[] = [];\n /** How many times a decoder was configured: a seek costs one. */\n static configures = 0;\n static resets = 0;\n /** Set to stop frames being emitted, for a spec about a queue filling up. */\n static emit = true;\n\n static install(): () => void {\n const scope = globalThis as Record<string, unknown>;\n const previous = {\n VideoDecoder: scope.VideoDecoder,\n EncodedVideoChunk: scope.EncodedVideoChunk,\n VideoFrame: scope.VideoFrame,\n createImageBitmap: scope.createImageBitmap\n };\n FakeVideoDecoder.reset();\n scope.VideoDecoder = FakeVideoDecoder;\n scope.EncodedVideoChunk = class {\n readonly timestamp: number;\n readonly type: 'key' | 'delta';\n constructor(init: { timestamp: number; type: 'key' | 'delta' }) {\n this.timestamp = init.timestamp;\n this.type = init.type;\n }\n };\n scope.VideoFrame = class {};\n // Left undefined so `MutableVideoSurface` takes the path that keeps\n // the frame itself: converting to a bitmap is asynchronous and\n // would put every assertion behind a microtask for no gain.\n scope.createImageBitmap = undefined;\n return () => Object.assign(scope, previous);\n }\n\n static reset(): void {\n FakeVideoDecoder.submitted.length = 0;\n FakeVideoDecoder.configures = 0;\n FakeVideoDecoder.resets = 0;\n FakeVideoDecoder.emit = true;\n }\n\n /** Every timestamp submitted since the last configure, in order. */\n static get sinceConfigure(): number[] {\n return FakeVideoDecoder.submitted.map(chunk => chunk.timestamp);\n }\n\n static isConfigSupported(): Promise<{ supported: boolean }> {\n return Promise.resolve({ supported: true });\n }\n\n state: 'unconfigured' | 'configured' | 'closed' = 'unconfigured';\n decodeQueueSize = 0;\n private readonly output: (frame: { timestamp: number; close(): void }) => void;\n\n constructor(init: { output: (frame: { timestamp: number; close(): void }) => void; error: (e: unknown) => void }) {\n this.output = init.output;\n }\n\n configure(): void {\n this.state = 'configured';\n FakeVideoDecoder.configures++;\n }\n\n decode(chunk: DecodedChunk): void {\n FakeVideoDecoder.submitted.push({ timestamp: chunk.timestamp, type: chunk.type });\n if (!FakeVideoDecoder.emit) {\n this.decodeQueueSize++;\n return;\n }\n this.output({ timestamp: chunk.timestamp, close: () => {} });\n }\n\n reset(): void {\n FakeVideoDecoder.resets++;\n this.decodeQueueSize = 0;\n this.state = 'unconfigured';\n }\n\n close(): void {\n this.state = 'closed';\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"testing.js","names":[],"sources":["../src/layout/LayoutTestUtils.ts","../src/input/UiInputTestUtils.ts","../src/rendering/RenderTestUtils.ts","../src/media/Mp4TestUtils.ts"],"sourcesContent":["import { UiGraph } from '../graph/UiGraph';\nimport type { UiNode } from '../graph/UiNode';\nimport { UiNodeType } from '../graph/UiNodeType';\nimport { LayoutEngine } from './LayoutEngine';\nimport { Constraints } from './LayoutTypes';\nimport type { LayoutBox, LayoutResult } from './LayoutTypes';\nimport type { LayoutRecord } from './LayoutRecord';\nimport type { TextMeasurer } from './TextMeasurer';\n\n/**\n * Shared helpers for layout specs: builds UiNode trees through\n * the real graph and drives LayoutEngine.\n */\nexport class LayoutHarness {\n readonly graph = new UiGraph();\n readonly engine: LayoutEngine;\n\n constructor(textMeasurer?: TextMeasurer) {\n this.engine = new LayoutEngine(textMeasurer);\n }\n\n createNode(id: string, type: UiNodeType): UiNode {\n return this.graph.createNode(id, type);\n }\n\n append(parent: UiNode, ...children: UiNode[]): void {\n for (const child of children) {\n this.graph.appendChild(parent, child);\n }\n }\n\n layout(node: UiNode, constraints: Constraints = Constraints.unbounded()): LayoutResult {\n return this.engine.layout(node, constraints);\n }\n\n record(node: UiNode): LayoutRecord {\n const record = this.engine.recordFor(node);\n if (record === undefined) {\n throw new Error(`No layout record for '${node.id}'.`);\n }\n return record;\n }\n\n box(node: UiNode): LayoutBox {\n const record = this.record(node);\n return { x: record.x, y: record.y, width: record.width, height: record.height };\n }\n\n boxOf(node: UiNode): LayoutBox {\n return this.engine.worldBox(node);\n }\n\n /** Where the node is seen: scroll offsets and sticky shifts applied. */\n visibleBox(node: UiNode): LayoutBox {\n return this.engine.visibleBox(node);\n }\n}\n","import { UiNodeType } from '../graph/UiNodeType';\nimport type { UiNode } from '../graph/UiNode';\nimport { Constraints } from '../layout/LayoutTypes';\nimport { LayoutHarness } from '../layout/LayoutTestUtils';\nimport { UiHitTester } from './UiHitTester';\nimport { UiInputDispatcher } from './UiInputDispatcher';\nimport { UiPointerController, type PointerControllerOptions } from './UiPointerController';\nimport { UiGestureRecognizer } from './UiGestureRecognizer';\nimport { UiTouchScroller, type TouchScrollerOptions } from './UiTouchScroller';\nimport { UiFocusManager } from './UiFocusManager';\nimport { UiKeyboardController } from './UiKeyboardController';\nimport { UiEditingController, type EditingHost } from './UiEditingController';\nimport { UiWheelController, type ScrollContainerState, type ScrollSink } from './UiWheelController';\nimport { UiPlatformAdapter, type PlatformEventTarget, type PlatformSurface } from './UiPlatformAdapter';\nimport { CharacterCountTextMeasurer, type TextMeasurer } from '../layout/TextMeasurer';\n\n/**\n * Shared harness for input specs.\n *\n * Builds real UiNode trees through UiGraph + LayoutEngine so hit\n * testing, dispatch, focus and gestures are tested against actual\n * layout geometry rather than hand-written boxes.\n */\nexport class InputTestHarness {\n readonly layout: LayoutHarness;\n readonly root: UiNode;\n readonly dispatcher = new UiInputDispatcher();\n /** Shared, so a spec can read back what a controller scrolled. */\n readonly scrollSink = new HarnessScrollSink(this);\n\n /** A measurer is needed only by specs whose trees contain real text. */\n private readonly textMeasurer: TextMeasurer | undefined;\n\n constructor(width = 400, height = 400, textMeasurer?: TextMeasurer) {\n this.textMeasurer = textMeasurer;\n this.layout = new LayoutHarness(textMeasurer);\n this.root = this.layout.createNode('app', UiNodeType.Column);\n this.root.setProperty('width', width);\n this.root.setProperty('height', height);\n }\n\n node(id: string, type: UiNodeType, props: Record<string, unknown> = {}): UiNode {\n const node = this.layout.createNode(id, type);\n for (const [key, value] of Object.entries(props)) {\n node.setProperty(key, value);\n }\n return node;\n }\n\n add(parent: UiNode, ...children: UiNode[]): void {\n this.layout.append(parent, ...children);\n }\n\n layoutTree(): void {\n this.layout.layout(this.root, Constraints.loose(400, 400));\n }\n\n createHitTester(): UiHitTester {\n return new UiHitTester(this.layout.engine, this.root);\n }\n\n createPointerController(): UiPointerController {\n return new UiPointerController(this.createHitTester(), this.dispatcher);\n }\n\n createGestureRecognizer(): UiGestureRecognizer {\n return new UiGestureRecognizer(this.dispatcher);\n }\n\n createFocusManager(): UiFocusManager {\n return new UiFocusManager(this.root, this.dispatcher);\n }\n\n createKeyboardController(): UiKeyboardController {\n return new UiKeyboardController(this.dispatcher, this.createFocusManager(), this.root);\n }\n\n /**\n * An editing controller over this harness's layout and dispatcher.\n *\n * The host is the layout harness itself: everything `EditingHost`\n * asks for is something the layout already knows, which is why the\n * controller takes an interface rather than the engine.\n */\n createEditingController(focus: UiFocusManager = this.createFocusManager()): UiEditingController {\n const layout = this.layout;\n const hitTester = this.createHitTester();\n const host: EditingHost = {\n recordFor: node => layout.engine.recordFor(node),\n visibleBox: node => layout.engine.visibleBox(node),\n toLocal: (node, x, y) => hitTester.toLocal(node, x, y),\n // The controller measures only for caret geometry. A spec whose\n // tree has real text passes its own measurer to the harness; the\n // deterministic one is right for every other.\n measurer: this.textMeasurer ?? new CharacterCountTextMeasurer(),\n markDirty: () => undefined,\n reveal: () => undefined,\n now: () => 0\n };\n return new UiEditingController(host, this.dispatcher, focus);\n }\n\n createWheelController(): UiWheelController {\n return new UiWheelController(this.createHitTester(), this.dispatcher, this.scrollSink, () => this.root);\n }\n\n /**\n * A pointer controller with a gesture recognizer behind it, for the\n * specs that need a real Pan rather than a hand-made one.\n */\n createGesturePointerController(options: PointerControllerOptions = {}): UiPointerController {\n return new UiPointerController(this.createHitTester(), this.dispatcher, {\n gestures: new UiGestureRecognizer(this.dispatcher),\n scrollSink: this.scrollSink,\n ...options\n });\n }\n\n createTouchScroller(options: TouchScrollerOptions = {}): UiTouchScroller {\n return new UiTouchScroller(this.dispatcher, this.root, this.scrollSink, options);\n }\n\n createPlatformAdapter(): UiPlatformAdapter {\n return new UiPlatformAdapter({\n pointerController: this.createPointerController(),\n wheelController: this.createWheelController(),\n keyboardController: this.createKeyboardController()\n });\n }\n\n box(node: UiNode): { x: number; y: number; width: number; height: number } {\n return this.layout.boxOf(node);\n }\n}\n\n/**\n * Minimal fake event target for testing the platform adapter.\n */\nexport class FakeEventTarget implements PlatformEventTarget {\n private readonly listeners: { type: string; listener: (event: Event) => void }[] = [];\n\n addEventListener(type: string, listener: ((event: Event) => void) | null): void {\n if (listener !== null) {\n this.listeners.push({ type, listener });\n }\n }\n\n removeEventListener(type: string, listener: ((event: Event) => void) | null): void {\n if (listener === null) return;\n const index = this.listeners.findIndex(entry => entry.type === type && entry.listener === listener);\n if (index !== -1) {\n this.listeners.splice(index, 1);\n }\n }\n\n emit(type: string, event: Event): void {\n for (const entry of this.listeners) {\n if (entry.type === type) {\n entry.listener(event);\n }\n }\n }\n}\n\n/**\n * Fake PlatformSurface for tests. Emits DOM-like events from plain\n * objects so PointerEvent/WheelEvent/KeyboardEvent constructors are\n * not required in the test environment.\n */\nexport class FakePlatformSurface implements PlatformSurface {\n readonly pointerTarget = new FakeEventTarget();\n readonly keyboardTarget = new FakeEventTarget();\n localX = 0;\n localY = 0;\n /** The last `touch-action` the adapter asked for; '' before any. */\n touchAction = '';\n\n clientToLocal(_clientX: number, _clientY: number): { x: number; y: number } {\n return { x: this.localX, y: this.localY };\n }\n\n setTouchAction(value: string): void {\n this.touchAction = value;\n }\n}\n\n/**\n * ScrollSink backed by the harness LayoutEngine. Updates the\n * container's scrollX/scrollY properties, then re-lays out so the\n * engine re-reads and clamps the offset, mirroring how the app layer\n * drives scroll through node properties.\n */\nexport class HarnessScrollSink implements ScrollSink {\n /** Every scroll asked for, in order, so a spec can read the behaviour hint. */\n readonly calls: { node: UiNode; dx: number; dy: number; behavior: string }[] = [];\n /** Containers whose scrollbars were revealed. */\n readonly revealed: UiNode[] = [];\n\n constructor(private readonly harness: InputTestHarness) {}\n\n containerState(node: UiNode): ScrollContainerState | undefined {\n const record = this.harness.layout.engine.recordFor(node);\n if (record === undefined) {\n return undefined;\n }\n const horizontal = node.getProperty('direction') === 'row';\n return {\n scrollX: record.scrollX,\n scrollY: record.scrollY,\n maxScrollX: Math.max(0, record.contentWidth - record.width),\n maxScrollY: Math.max(0, record.contentHeight - record.height),\n horizontal,\n viewportWidth: record.width,\n viewportHeight: record.height\n };\n }\n\n scrollBy(node: UiNode, dx: number, dy: number, behavior: string = 'instant'): void {\n this.calls.push({ node, dx, dy, behavior });\n const record = this.harness.layout.engine.recordFor(node);\n if (record === undefined) {\n return;\n }\n const maxX = Math.max(0, record.contentWidth - record.width);\n const maxY = Math.max(0, record.contentHeight - record.height);\n node.setProperty('scrollX', clamp(record.scrollX + dx, 0, maxX));\n node.setProperty('scrollY', clamp(record.scrollY + dy, 0, maxY));\n this.harness.layoutTree();\n }\n\n scrollContainers(): Iterable<UiNode> {\n return this.harness.layout.engine.scrollContainers();\n }\n\n revealScrollbars(node: UiNode): void {\n this.revealed.push(node);\n }\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n","import { UiGraph } from '../graph/UiGraph';\nimport { UiNodeType } from '../graph/UiNodeType';\nimport type { UiNode } from '../graph/UiNode';\nimport { LayoutEngine } from '../layout/LayoutEngine';\nimport { CharacterCountTextMeasurer } from '../layout/TextMeasurer';\nimport type { TextMeasurer } from '../layout/TextMeasurer';\nimport { Constraints } from '../layout/LayoutTypes';\nimport type { LayoutBox, LayoutResult } from '../layout/LayoutTypes';\nimport type { LayoutRecord } from '../layout/LayoutRecord';\nimport { Canvas2DRenderer } from './canvas2d/Canvas2DRenderer';\nimport type { Canvas2DContext, Canvas2DGradient } from './canvas2d/Canvas2DContext';\nimport { CanvasSurface } from './canvas2d/CanvasSurface';\nimport type { CanvasHost } from './canvas2d/CanvasSurface';\nimport type { PaintContext2D, PaintGradient } from './PaintTarget';\nimport type { PaintCanvasFactory } from './PaintPicture';\nimport type { PaintLineCap, PaintLineJoin } from './PaintSurface';\n\nexport interface RecordedCall {\n name: string;\n args: unknown[];\n}\n\n/**\n * The gradient a recording context hands back, carrying what it was\n * asked for so a test can read the whole paint rather than a `[object\n * CanvasGradient]`. A real one is opaque, which is exactly what makes\n * a gradient hard to assert on without this.\n */\nexport class RecordedGradient implements Canvas2DGradient {\n readonly stops: { offset: number; color: string }[] = [];\n\n constructor(\n readonly kind: 'linear' | 'radial',\n readonly args: readonly number[]\n ) {}\n\n addColorStop(offset: number, color: string): void {\n this.stops.push({ offset, color });\n }\n}\n\n/**\n * Deterministic Canvas2D spy: records every call and style\n * assignment so tests can assert the exact draw sequence.\n */\nexport class RecordingCanvasContext implements Canvas2DContext {\n readonly calls: RecordedCall[] = [];\n\n private _fillStyle: string | Canvas2DGradient | CanvasPattern = '#000';\n private _strokeStyle: string | Canvas2DGradient | CanvasPattern = '#000';\n private _lineWidth = 1;\n private _lineJoin: CanvasLineJoin = 'miter';\n private _globalAlpha = 1;\n private _font = '';\n private _textAlign: CanvasTextAlign = 'start';\n private _textBaseline: CanvasTextBaseline = 'alphabetic';\n /**\n * Tracking, which is a context property rather than part of the font\n * shorthand — and sticky, so a spec can check it was cleared as well\n * as that it was set.\n */\n letterSpacing = '0px';\n\n get fillStyle(): string | Canvas2DGradient | CanvasPattern {\n return this._fillStyle;\n }\n\n set fillStyle(value: string | Canvas2DGradient | CanvasPattern) {\n this._fillStyle = value;\n this.record('set:fillStyle', [value]);\n }\n\n get strokeStyle(): string | Canvas2DGradient | CanvasPattern {\n return this._strokeStyle;\n }\n\n set strokeStyle(value: string | Canvas2DGradient | CanvasPattern) {\n this._strokeStyle = value;\n this.record('set:strokeStyle', [value]);\n }\n\n get lineWidth(): number {\n return this._lineWidth;\n }\n\n set lineWidth(value: number) {\n this._lineWidth = value;\n this.record('set:lineWidth', [value]);\n }\n\n get lineJoin(): CanvasLineJoin {\n return this._lineJoin;\n }\n\n set lineJoin(value: CanvasLineJoin) {\n this._lineJoin = value;\n this.record('set:lineJoin', [value]);\n }\n\n get globalAlpha(): number {\n return this._globalAlpha;\n }\n\n set globalAlpha(value: number) {\n this._globalAlpha = value;\n this.record('set:globalAlpha', [value]);\n }\n\n get font(): string {\n return this._font;\n }\n\n set font(value: string) {\n this._font = value;\n this.record('set:font', [value]);\n }\n\n get textAlign(): CanvasTextAlign {\n return this._textAlign;\n }\n\n set textAlign(value: CanvasTextAlign) {\n this._textAlign = value;\n this.record('set:textAlign', [value]);\n }\n\n get textBaseline(): CanvasTextBaseline {\n return this._textBaseline;\n }\n\n set textBaseline(value: CanvasTextBaseline) {\n this._textBaseline = value;\n this.record('set:textBaseline', [value]);\n }\n\n /**\n * Drawing state saved by `save`, as a real context keeps it. Without\n * it a spec that renders twice read the first frame's leftovers: the\n * renderer multiplies `globalAlpha` inside a `save`, and a double that\n * did not restore it compounded the opacity frame over frame.\n */\n private readonly saved: {\n fillStyle: string | Canvas2DGradient | CanvasPattern;\n strokeStyle: string | Canvas2DGradient | CanvasPattern;\n lineWidth: number;\n lineJoin: CanvasLineJoin;\n globalAlpha: number;\n font: string;\n textAlign: CanvasTextAlign;\n textBaseline: CanvasTextBaseline;\n letterSpacing: string;\n }[] = [];\n\n save(): void {\n this.saved.push({\n fillStyle: this._fillStyle,\n strokeStyle: this._strokeStyle,\n lineWidth: this._lineWidth,\n lineJoin: this._lineJoin,\n globalAlpha: this._globalAlpha,\n font: this._font,\n textAlign: this._textAlign,\n textBaseline: this._textBaseline,\n letterSpacing: this.letterSpacing\n });\n this.record('save', []);\n }\n\n restore(): void {\n const state = this.saved.pop();\n if (state !== undefined) {\n this._fillStyle = state.fillStyle;\n this._strokeStyle = state.strokeStyle;\n this._lineWidth = state.lineWidth;\n this._lineJoin = state.lineJoin;\n this._globalAlpha = state.globalAlpha;\n this._font = state.font;\n this._textAlign = state.textAlign;\n this._textBaseline = state.textBaseline;\n this.letterSpacing = state.letterSpacing;\n }\n this.record('restore', []);\n }\n\n translate(x: number, y: number): void {\n this.record('translate', [x, y]);\n }\n\n scale(x: number, y: number): void {\n this.record('scale', [x, y]);\n }\n\n rotate(angle: number): void {\n this.record('rotate', [angle]);\n }\n\n setTransform(a: number, b: number, c: number, d: number, e: number, f: number): void {\n this.record('setTransform', [a, b, c, d, e, f]);\n }\n\n clearRect(x: number, y: number, width: number, height: number): void {\n this.record('clearRect', [x, y, width, height]);\n }\n\n fillRect(x: number, y: number, width: number, height: number): void {\n this.record('fillRect', [x, y, width, height]);\n }\n\n strokeRect(x: number, y: number, width: number, height: number): void {\n this.record('strokeRect', [x, y, width, height]);\n }\n\n beginPath(): void {\n this.record('beginPath', []);\n }\n\n moveTo(x: number, y: number): void {\n this.record('moveTo', [x, y]);\n }\n\n lineTo(x: number, y: number): void {\n this.record('lineTo', [x, y]);\n }\n\n arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void {\n this.record('arcTo', [x1, y1, x2, y2, radius]);\n }\n\n closePath(): void {\n this.record('closePath', []);\n }\n\n rect(x: number, y: number, width: number, height: number): void {\n this.record('rect', [x, y, width, height]);\n }\n\n clip(): void {\n this.record('clip', []);\n }\n\n fill(): void {\n this.record('fill', []);\n }\n\n stroke(): void {\n this.record('stroke', []);\n }\n\n fillText(text: string, x: number, y: number, maxWidth?: number): void {\n const args = maxWidth === undefined ? [text, x, y] : [text, x, y, maxWidth];\n this.record('fillText', args);\n }\n\n measureText(text: string): TextMetrics {\n this.record('measureText', [text]);\n return { width: text.length * 8 } as TextMetrics;\n }\n\n drawImage(image: ImageBitmap, dx: number, dy: number, dw: number, dh: number): void {\n this.record('drawImage', [image, dx, dy, dw, dh]);\n }\n\n // Path calls a painter's recording makes when it is replayed straight\n // onto the frame's context (`PaintPictureCache.draw`), rather than\n // onto a picture canvas of its own.\n transform(a: number, b: number, c: number, d: number, e: number, f: number): void {\n this.record('transform', [a, b, c, d, e, f]);\n }\n\n quadraticCurveTo(cx: number, cy: number, x: number, y: number): void {\n this.record('quadraticCurveTo', [cx, cy, x, y]);\n }\n\n bezierCurveTo(c1x: number, c1y: number, c2x: number, c2y: number, x: number, y: number): void {\n this.record('bezierCurveTo', [c1x, c1y, c2x, c2y, x, y]);\n }\n\n arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, counterclockwise = false): void {\n this.record('arc', [x, y, radius, startAngle, endAngle, counterclockwise]);\n }\n\n setLineDash(segments: readonly number[]): void {\n this.record('setLineDash', [segments]);\n }\n\n createLinearGradient(x0: number, y0: number, x1: number, y1: number): RecordedGradient {\n const gradient = new RecordedGradient('linear', [x0, y0, x1, y1]);\n this.record('createLinearGradient', [x0, y0, x1, y1]);\n return gradient;\n }\n\n createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): RecordedGradient {\n const gradient = new RecordedGradient('radial', [x0, y0, r0, x1, y1, r1]);\n this.record('createRadialGradient', [x0, y0, r0, x1, y1, r1]);\n return gradient;\n }\n\n private record(name: string, args: unknown[]): void {\n this.calls.push({ name, args: args.map(normalizeNumber) });\n }\n}\n\n/**\n * Normalizes -0 to 0 so scroll translations assert cleanly.\n */\nfunction normalizeNumber(value: unknown): unknown {\n if (typeof value === 'number' && Object.is(value, -0)) {\n return 0;\n }\n return value;\n}\n\nexport function callNames(context: RecordingCanvasContext): string[] {\n return context.calls.map(call => call.name);\n}\n\nexport function callsOf(context: RecordingCanvasContext, name: string): RecordedCall[] {\n return context.calls.filter(call => call.name === name);\n}\n\nexport function callArgs(context: RecordingCanvasContext, name: string): unknown[][] {\n return callsOf(context, name).map(call => (call.name.startsWith('set:') ? call.args[0] : call.args) as unknown[]);\n}\n\nexport function savedDepth(context: RecordingCanvasContext): number {\n let depth = 0;\n for (const call of context.calls) {\n if (call.name === 'save') {\n depth++;\n } else if (call.name === 'restore') {\n depth--;\n }\n }\n return depth;\n}\n\n/**\n * Deterministic `PaintContext2D` spy: the picture rasteriser's\n * equivalent of `RecordingCanvasContext`, so a spec can assert what a\n * painter actually drew without a browser.\n */\nexport class RecordingPaintContext implements PaintContext2D {\n readonly calls: RecordedCall[] = [];\n\n lineDashOffset = 0;\n lineWidth = 1;\n lineCap: PaintLineCap = 'butt';\n lineJoin: PaintLineJoin = 'miter';\n miterLimit = 10;\n globalAlpha = 1;\n filter = 'none';\n fillStyle: string | PaintGradient = '#000';\n strokeStyle: string | PaintGradient = '#000';\n font = '';\n textAlign: 'left' | 'center' | 'right' = 'left';\n textBaseline = 'alphabetic' as const;\n\n save(): void {\n this.record('save', []);\n }\n\n restore(): void {\n this.record('restore', []);\n }\n\n translate(x: number, y: number): void {\n this.record('translate', [x, y]);\n }\n\n scale(x: number, y: number): void {\n this.record('scale', [x, y]);\n }\n\n rotate(angle: number): void {\n this.record('rotate', [angle]);\n }\n\n transform(a: number, b: number, c: number, d: number, e: number, f: number): void {\n this.record('transform', [a, b, c, d, e, f]);\n }\n\n beginPath(): void {\n this.record('beginPath', []);\n }\n\n moveTo(x: number, y: number): void {\n this.record('moveTo', [x, y]);\n }\n\n lineTo(x: number, y: number): void {\n this.record('lineTo', [x, y]);\n }\n\n quadraticCurveTo(cx: number, cy: number, x: number, y: number): void {\n this.record('quadraticCurveTo', [cx, cy, x, y]);\n }\n\n bezierCurveTo(c1x: number, c1y: number, c2x: number, c2y: number, x: number, y: number): void {\n this.record('bezierCurveTo', [c1x, c1y, c2x, c2y, x, y]);\n }\n\n arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, counterclockwise = false): void {\n this.record('arc', [x, y, radius, startAngle, endAngle, counterclockwise]);\n }\n\n arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void {\n this.record('arcTo', [x1, y1, x2, y2, radius]);\n }\n\n rect(x: number, y: number, width: number, height: number): void {\n this.record('rect', [x, y, width, height]);\n }\n\n closePath(): void {\n this.record('closePath', []);\n }\n\n fill(fillRule?: 'nonzero' | 'evenodd'): void {\n this.record('fill', [fillRule ?? 'nonzero', this.fillStyle]);\n }\n\n stroke(): void {\n this.record('stroke', [this.strokeStyle, this.lineWidth]);\n }\n\n clip(fillRule?: 'nonzero' | 'evenodd'): void {\n this.record('clip', [fillRule ?? 'nonzero']);\n }\n\n fillText(text: string, x: number, y: number): void {\n this.record('fillText', [text, x, y, this.font, this.textAlign]);\n }\n\n drawImage(image: ImageBitmap, dx: number, dy: number, dw: number, dh: number): void {\n this.record('drawImage', [image, dx, dy, dw, dh]);\n }\n\n setLineDash(segments: readonly number[]): void {\n this.record('setLineDash', [[...segments]]);\n }\n\n createLinearGradient(x0: number, y0: number, x1: number, y1: number): RecordedGradient {\n const gradient = new RecordedGradient('linear', [x0, y0, x1, y1]);\n this.record('createLinearGradient', [x0, y0, x1, y1]);\n return gradient;\n }\n\n createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): RecordedGradient {\n const gradient = new RecordedGradient('radial', [x0, y0, r0, x1, y1, r1]);\n this.record('createRadialGradient', [x0, y0, r0, x1, y1, r1]);\n return gradient;\n }\n\n private record(name: string, args: unknown[]): void {\n this.calls.push({ name, args: args.map(normalizeNumber) });\n }\n}\n\n/**\n * A picture that never becomes real pixels.\n *\n * `take()` hands back a stand-in for the `ImageBitmap` a browser would\n * transfer out of an `OffscreenCanvas`, carrying only the size and a\n * `close`, which is all either renderer reads off one. Every context\n * the factory hands out is kept, so a spec can assert both what was\n * drawn and how many times anything was drawn at all.\n */\nexport class FakePaintCanvases {\n readonly contexts: RecordingPaintContext[] = [];\n readonly bitmaps: { width: number; height: number; closed: boolean }[] = [];\n\n readonly create: PaintCanvasFactory = (width, height) => {\n const context = new RecordingPaintContext();\n this.contexts.push(context);\n const bitmap = { width, height, closed: false };\n this.bitmaps.push(bitmap);\n return {\n context: () => context,\n take: () =>\n ({\n width,\n height,\n close: () => {\n bitmap.closed = true;\n }\n }) as unknown as ImageBitmap\n };\n };\n\n /** The calls made into the most recent picture. */\n get last(): RecordingPaintContext {\n const context = this.contexts.at(-1);\n if (context === undefined) {\n throw new Error('No picture has been rasterised.');\n }\n return context;\n }\n\n reset(): void {\n this.contexts.length = 0;\n this.bitmaps.length = 0;\n }\n}\n\n/**\n * A canvas host backed by a recording context.\n */\nexport class FakeCanvasHost implements CanvasHost {\n width = 0;\n height = 0;\n private contextRequests = 0;\n\n constructor(private readonly context: Canvas2DContext | null) {}\n\n getContext(_contextId: '2d'): CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null {\n this.contextRequests++;\n return this.context as unknown as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n }\n\n get contextRequestCount(): number {\n return this.contextRequests;\n }\n}\n\n/**\n * Shared harness: a real UiGraph + LayoutEngine + Canvas2DRenderer\n * over a recording context. Deterministic layout comes from the\n * CharacterCountTextMeasurer, shared by layout and renderer.\n */\nexport class RenderHarness {\n readonly graph = new UiGraph();\n readonly measurer: TextMeasurer = new CharacterCountTextMeasurer();\n readonly engine = new LayoutEngine(this.measurer);\n readonly context = new RecordingCanvasContext();\n readonly host = new FakeCanvasHost(this.context);\n readonly surface = new CanvasSurface(this.host);\n readonly renderer = new Canvas2DRenderer({ surface: this.surface });\n\n constructor(logicalWidth: number = 800, logicalHeight: number = 600, dpr: number = 1) {\n this.surface.setLogicalSize(logicalWidth, logicalHeight, dpr);\n }\n\n createNode(id: string, type: UiNodeType): UiNode {\n return this.graph.createNode(id, type);\n }\n\n append(parent: UiNode, ...children: UiNode[]): void {\n for (const child of children) {\n this.graph.appendChild(parent, child);\n }\n }\n\n layout(node: UiNode, constraints: Constraints = Constraints.tight(800, 600)): LayoutResult {\n return this.engine.layout(node, constraints);\n }\n\n render(node: UiNode): void {\n // Far in the future: overlay scrollbars have faded, so draw-call\n // assertions see only the scene. Pass `now` yourself to test them.\n this.renderer.render(node, { layout: this.engine, text: this.measurer, now: Number.MAX_SAFE_INTEGER });\n }\n\n record(node: UiNode): LayoutRecord {\n const record = this.engine.recordFor(node);\n if (record === undefined) {\n throw new Error(`No layout record for '${node.id}'.`);\n }\n return record;\n }\n\n box(node: UiNode): LayoutBox {\n const record = this.record(node);\n return { x: record.x, y: record.y, width: record.width, height: record.height };\n }\n}\n","/**\n * A very small MP4 writer, and a decoder that decodes nothing.\n *\n * Both exist for the same reason the demuxer's spec gives for building\n * its files by hand: a fixture `.mp4` is a better test of reality and\n * a worse test of this code, because when it fails you do not know\n * which of forty boxes was misread. Building the file means a spec can\n * name the exact field it is about, and construct the awkward cases —\n * a chunk holding several samples, composition offsets reordering\n * them, a track with no `stss` — rather than hunting for a file that\n * has one.\n *\n * In `gesso-core/testing` rather than beside a spec because two specs\n * now want it: the demuxer's, which reads these files, and the\n * resolver's, which plays them.\n */\n\nfunction u32(value: number): number[] {\n return [(value >>> 24) & 0xff, (value >>> 16) & 0xff, (value >>> 8) & 0xff, value & 0xff];\n}\n\nfunction u16(value: number): number[] {\n return [(value >>> 8) & 0xff, value & 0xff];\n}\n\nfunction ascii(text: string): number[] {\n return [...text].map(character => character.charCodeAt(0));\n}\n\n/** `size type payload`, which is every box in the format. */\nexport function box(type: string, ...payload: number[][]): number[] {\n const body = payload.flat();\n return [...u32(body.length + 8), ...ascii(type), ...body];\n}\n\n/** A full box: a version and three flag bytes before the payload. */\nexport function fullBox(type: string, version: number, ...payload: number[][]): number[] {\n return box(type, [version, 0, 0, 0], ...payload);\n}\n\n/** An AAC audio track, for a file that has sound as well as pictures. */\nexport interface AudioTrackSpec {\n timescale: number;\n deltas: { count: number; delta: number }[];\n sizes: number[];\n chunkOffsets: number[];\n chunkRuns: { firstChunk: number; samplesPerChunk: number }[];\n channels: number;\n sampleRate: number;\n /**\n * The `AudioSpecificConfig`, whose top five bits are the audio\n * object type. The default is AAC-LC at 44.1kHz stereo, which is\n * `mp4a.40.2` and what nearly every file on the web carries.\n */\n config?: number[];\n}\n\nexport interface TrackSpec {\n timescale: number;\n /** One `stts` run per entry. */\n deltas: { count: number; delta: number }[];\n compositionOffsets?: { count: number; offset: number }[];\n sizes: number[];\n chunkOffsets: number[];\n chunkRuns: { firstChunk: number; samplesPerChunk: number }[];\n /** Omitted means no `stss` at all, which means every sample is a sync sample. */\n syncSamples?: number[];\n width: number;\n height: number;\n handler?: string;\n /** The sample entry's four character code. `avc1` unless said otherwise. */\n codec?: 'avc1' | 'av01' | 'vp09';\n /** The bytes of the configuration record after any fixed header; see `configurationBox`. */\n configBytes?: number[];\n}\n\n/**\n * The codec configuration box, which differs per codec and is where a\n * codec string comes from.\n */\nfunction configurationBox(spec: TrackSpec): number[] {\n if (spec.codec === 'av01') {\n // marker+version, then seq_profile/seq_level_idx, then the tier\n // and bit depth flags.\n return box('av1C', [0x81, ...(spec.configBytes ?? [0x04, 0x00])]);\n }\n if (spec.codec === 'vp09') {\n // A full box: version and flags, then profile, level and a byte\n // whose top nibble is the bit depth.\n return fullBox('vpcC', 1, spec.configBytes ?? [0x00, 0x1f, 0x80]);\n }\n // `avc1`: length, profile, compat, level, then whatever follows.\n return box('avcC', [1, 0x64, 0x00, 0x1f, 0xff, 0xe1]);\n}\n\nfunction visualSampleEntry(spec: TrackSpec): number[] {\n return box(\n spec.codec ?? 'avc1',\n [0, 0, 0, 0, 0, 0], // reserved\n u16(1), // data_reference_index\n Array.from({ length: 16 }, () => 0), // pre_defined + reserved\n u16(spec.width),\n u16(spec.height),\n u32(0x0048_0000), // horizresolution\n u32(0x0048_0000), // vertresolution\n u32(0), // reserved\n u16(1), // frame_count\n Array.from({ length: 32 }, () => 0), // compressorname\n u16(24), // depth\n [0xff, 0xff], // pre_defined = -1\n configurationBox(spec)\n );\n}\n\n/** `esds`: the descriptor tree down to the two numbers a codec string needs. */\nfunction esds(spec: AudioTrackSpec): number[] {\n const config = spec.config ?? [0x12, 0x10];\n // DecoderSpecificInfo (tag 5)\n const dsi = [0x05, config.length, ...config];\n // DecoderConfigDescriptor (tag 4): object type 0x40 is MPEG-4 audio,\n // then a stream type, a buffer size and two bitrates.\n // Thirteen bytes of header: an object type indication, a stream\n // type, three bytes of buffer size, and two four-byte bitrates.\n const dcd = [0x04, 13 + dsi.length, 0x40, 0x15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...dsi];\n // ES_Descriptor (tag 3): an id and a flags byte with nothing set.\n const esd = [0x03, 3 + dcd.length, 0, 1, 0, ...dcd];\n return fullBox('esds', 0, esd);\n}\n\nfunction audioSampleEntry(spec: AudioTrackSpec): number[] {\n return box(\n 'mp4a',\n [0, 0, 0, 0, 0, 0], // reserved\n u16(1), // data_reference_index\n u16(0), // version\n [0, 0, 0, 0, 0, 0], // revision and vendor\n u16(spec.channels),\n u16(16), // sample size\n u16(0), // pre_defined\n u16(0), // reserved\n u16(spec.sampleRate), // 16.16 fixed, whole part\n u16(0),\n esds(spec)\n );\n}\n\nfunction audioSampleTable(spec: AudioTrackSpec): number[] {\n return box(\n 'stbl',\n fullBox('stsd', 0, u32(1), audioSampleEntry(spec)),\n fullBox('stts', 0, u32(spec.deltas.length), ...spec.deltas.map(run => [...u32(run.count), ...u32(run.delta)])),\n fullBox(\n 'stsc',\n 0,\n u32(spec.chunkRuns.length),\n ...spec.chunkRuns.map(run => [...u32(run.firstChunk), ...u32(run.samplesPerChunk), ...u32(1)])\n ),\n fullBox('stsz', 0, u32(0), u32(spec.sizes.length), ...spec.sizes.map(size => u32(size))),\n fullBox('stco', 0, u32(spec.chunkOffsets.length), ...spec.chunkOffsets.map(offset => u32(offset)))\n );\n}\n\nfunction audioTrak(spec: AudioTrackSpec): number[] {\n return box(\n 'trak',\n box(\n 'mdia',\n fullBox('mdhd', 0, u32(0), u32(0), u32(spec.timescale), u32(1000)),\n fullBox(\n 'hdlr',\n 0,\n u32(0),\n ascii('soun'),\n Array.from({ length: 12 }, () => 0),\n [0]\n ),\n box('minf', audioSampleTable(spec))\n )\n );\n}\n\nfunction sampleTable(spec: TrackSpec): number[] {\n const boxes: number[][] = [\n fullBox('stsd', 0, u32(1), visualSampleEntry(spec)),\n fullBox('stts', 0, u32(spec.deltas.length), ...spec.deltas.map(run => [...u32(run.count), ...u32(run.delta)])),\n fullBox(\n 'stsc',\n 0,\n u32(spec.chunkRuns.length),\n ...spec.chunkRuns.map(run => [...u32(run.firstChunk), ...u32(run.samplesPerChunk), ...u32(1)])\n ),\n fullBox('stsz', 0, u32(0), u32(spec.sizes.length), ...spec.sizes.map(size => u32(size))),\n fullBox('stco', 0, u32(spec.chunkOffsets.length), ...spec.chunkOffsets.map(offset => u32(offset)))\n ];\n if (spec.compositionOffsets !== undefined) {\n boxes.splice(\n 2,\n 0,\n fullBox(\n 'ctts',\n 0,\n u32(spec.compositionOffsets.length),\n ...spec.compositionOffsets.map(run => [...u32(run.count), ...u32(run.offset)])\n )\n );\n }\n if (spec.syncSamples !== undefined) {\n boxes.push(fullBox('stss', 0, u32(spec.syncSamples.length), ...spec.syncSamples.map(number => u32(number))));\n }\n return box('stbl', ...boxes);\n}\n\nexport function buildMp4(spec: TrackSpec, options: { fragmented?: boolean; audio?: AudioTrackSpec } = {}): ArrayBuffer {\n const trak = box(\n 'trak',\n box(\n 'mdia',\n fullBox('mdhd', 0, u32(0), u32(0), u32(spec.timescale), u32(1000)),\n fullBox(\n 'hdlr',\n 0,\n u32(0),\n ascii(spec.handler ?? 'vide'),\n Array.from({ length: 12 }, () => 0),\n [0]\n ),\n box('minf', sampleTable(spec))\n )\n );\n const bytes = [\n ...box('ftyp', ascii('isom'), u32(512), ascii('isomiso2avc1mp41')),\n ...box(\n 'moov',\n fullBox(\n 'mvhd',\n 0,\n Array.from({ length: 100 }, () => 0)\n ),\n trak,\n ...(options.audio === undefined ? [] : [audioTrak(options.audio)])\n ),\n ...(options.fragmented === true ? box('moof', box('mfhd', u32(1))) : []),\n // A `mdat` big enough for every offset the tables name.\n ...box(\n 'mdat',\n Array.from({ length: 512 }, () => 0x41)\n )\n ];\n return new Uint8Array(bytes).buffer;\n}\n\n/** One run of samples inside a fragment. */\nexport interface FragmentSpec {\n /** Decode time the fragment starts at, in the track's timescale. Omitted means \"carry on\". */\n baseMediaDecodeTime?: number;\n samples: { duration: number; size: number; isKey: boolean }[];\n}\n\nexport interface FragmentedSpec {\n trackId: number;\n timescale: number;\n width: number;\n height: number;\n fragments: FragmentSpec[];\n /** Written into `trex`, and used by any sample whose `trun` omits the field. */\n defaultSampleDuration?: number;\n defaultSampleSize?: number;\n /** Leave a field out of the `trun` so the default has to supply it. */\n omit?: { duration?: boolean; size?: boolean };\n}\n\nfunction tkhd(trackId: number): number[] {\n return fullBox(\n 'tkhd',\n 0,\n u32(0), // creation time\n u32(0), // modification time\n u32(trackId),\n Array.from({ length: 68 }, () => 0)\n );\n}\n\n/**\n * A fragmented MP4: a `moov` that describes the track and promises\n * fragments, then the fragments.\n *\n * The `stbl` is a shell — `stsd` and four empty tables — which is what\n * a fragmenter writes and is exactly the shape that used to make this\n * demuxer return zero samples for a file the browser played.\n */\nexport function buildFragmentedMp4(spec: FragmentedSpec): ArrayBuffer {\n const shellSpec: TrackSpec = {\n timescale: spec.timescale,\n deltas: [],\n sizes: [],\n chunkOffsets: [],\n chunkRuns: [],\n width: spec.width,\n height: spec.height\n };\n const trak = box(\n 'trak',\n tkhd(spec.trackId),\n box(\n 'mdia',\n fullBox('mdhd', 0, u32(0), u32(0), u32(spec.timescale), u32(1000)),\n fullBox(\n 'hdlr',\n 0,\n u32(0),\n ascii('vide'),\n Array.from({ length: 12 }, () => 0),\n [0]\n ),\n box('minf', sampleTable(shellSpec))\n )\n );\n const mvex = box(\n 'mvex',\n fullBox(\n 'trex',\n 0,\n u32(spec.trackId),\n u32(1), // default_sample_description_index\n u32(spec.defaultSampleDuration ?? 0),\n u32(spec.defaultSampleSize ?? 0),\n u32(0) // default_sample_flags\n )\n );\n const moov = box(\n 'moov',\n fullBox(\n 'mvhd',\n 0,\n Array.from({ length: 100 }, () => 0)\n ),\n trak,\n mvex\n );\n\n const bytes = [...box('ftyp', ascii('isom'), u32(512), ascii('isomiso6avc1mp41')), ...moov];\n\n for (const fragment of spec.fragments) {\n const payloadSize = fragment.samples.reduce((total, sample) => total + sample.size, 0);\n // Built twice: the `trun`'s data offset is relative to the start\n // of the `moof` box, and is not known until the `moof` has been\n // sized. The first pass is only for its length.\n const build = (dataOffset: number): number[] => {\n const trunFlags =\n 0x000001 | // data-offset-present\n (spec.omit?.duration === true ? 0 : 0x000100) |\n (spec.omit?.size === true ? 0 : 0x000200) |\n 0x000400; // sample-flags-present\n const entries = fragment.samples.flatMap(sample => [\n ...(spec.omit?.duration === true ? [] : u32(sample.duration)),\n ...(spec.omit?.size === true ? [] : u32(sample.size)),\n // Bit 16 set means \"not a sync sample\".\n ...u32(sample.isKey ? 0 : 0x00010000)\n ]);\n const trun = box(\n 'trun',\n [0, (trunFlags >> 16) & 0xff, (trunFlags >> 8) & 0xff, trunFlags & 0xff],\n u32(fragment.samples.length),\n u32(dataOffset),\n entries\n );\n const tfhdFlags = 0x020000; // default-base-is-moof\n const traf = box(\n 'traf',\n box('tfhd', [0, (tfhdFlags >> 16) & 0xff, (tfhdFlags >> 8) & 0xff, tfhdFlags & 0xff], u32(spec.trackId)),\n ...(fragment.baseMediaDecodeTime === undefined ? [] : [fullBox('tfdt', 0, u32(fragment.baseMediaDecodeTime))]),\n trun\n );\n return box('moof', fullBox('mfhd', 0, u32(1)), traf);\n };\n const sized = build(0);\n // The `mdat` payload begins eight bytes past the end of the `moof`.\n const moof = build(sized.length + 8);\n bytes.push(\n ...moof,\n ...box(\n 'mdat',\n Array.from({ length: payloadSize }, () => 0x42)\n )\n );\n }\n return new Uint8Array(bytes).buffer;\n}\n\n/** The offset `mdat`'s payload lands at, for a table that must point into it. */\nexport function mdatStart(data: ArrayBuffer): number {\n const view = new DataView(data);\n let at = 0;\n while (at + 8 <= data.byteLength) {\n const size = view.getUint32(at);\n const type = String.fromCharCode(\n view.getUint8(at + 4),\n view.getUint8(at + 5),\n view.getUint8(at + 6),\n view.getUint8(at + 7)\n );\n if (type === 'mdat') {\n return at + 8;\n }\n at += size;\n }\n throw new Error('no mdat');\n}\n\n/** One chunk as the fake decoder saw it. */\nexport interface DecodedChunk {\n readonly timestamp: number;\n readonly type: 'key' | 'delta';\n}\n\n/**\n * A `VideoDecoder` that records what it was asked to decode and hands\n * back a frame for each.\n *\n * What the resolver's spec is about is *which samples are submitted*:\n * a seek that starts at the wrong keyframe, or one that decodes the\n * whole clip to get somewhere, is a bug in arithmetic that has nothing\n * to do with pixels. So this decodes nothing and the frames it emits\n * carry a timestamp and a `close` and no image at all.\n *\n * Emission is synchronous, which a real decoder's never is. That makes\n * the specs readable and is the one way this lies: the ordering\n * hazards a real decoder creates — a frame arriving after the position\n * has moved past it — are `Mp4Playback`'s to handle and are asserted\n * by pushing frames in by hand rather than by waiting for a thread.\n */\nexport class FakeVideoDecoder {\n static readonly submitted: DecodedChunk[] = [];\n /** How many times a decoder was configured: a seek costs one. */\n static configures = 0;\n static resets = 0;\n /** Set to stop frames being emitted, for a spec about a queue filling up. */\n static emit = true;\n\n static install(): () => void {\n const scope = globalThis as Record<string, unknown>;\n const previous = {\n VideoDecoder: scope.VideoDecoder,\n EncodedVideoChunk: scope.EncodedVideoChunk,\n VideoFrame: scope.VideoFrame,\n createImageBitmap: scope.createImageBitmap\n };\n FakeVideoDecoder.reset();\n scope.VideoDecoder = FakeVideoDecoder;\n scope.EncodedVideoChunk = class {\n readonly timestamp: number;\n readonly type: 'key' | 'delta';\n constructor(init: { timestamp: number; type: 'key' | 'delta' }) {\n this.timestamp = init.timestamp;\n this.type = init.type;\n }\n };\n scope.VideoFrame = class {};\n // Left undefined so `MutableVideoSurface` takes the path that keeps\n // the frame itself: converting to a bitmap is asynchronous and\n // would put every assertion behind a microtask for no gain.\n scope.createImageBitmap = undefined;\n return () => Object.assign(scope, previous);\n }\n\n static reset(): void {\n FakeVideoDecoder.submitted.length = 0;\n FakeVideoDecoder.configures = 0;\n FakeVideoDecoder.resets = 0;\n FakeVideoDecoder.emit = true;\n }\n\n /** Every timestamp submitted since the last configure, in order. */\n static get sinceConfigure(): number[] {\n return FakeVideoDecoder.submitted.map(chunk => chunk.timestamp);\n }\n\n static isConfigSupported(): Promise<{ supported: boolean }> {\n return Promise.resolve({ supported: true });\n }\n\n state: 'unconfigured' | 'configured' | 'closed' = 'unconfigured';\n decodeQueueSize = 0;\n private readonly output: (frame: { timestamp: number; close(): void }) => void;\n\n constructor(init: { output: (frame: { timestamp: number; close(): void }) => void; error: (e: unknown) => void }) {\n this.output = init.output;\n }\n\n configure(): void {\n this.state = 'configured';\n FakeVideoDecoder.configures++;\n }\n\n decode(chunk: DecodedChunk): void {\n FakeVideoDecoder.submitted.push({ timestamp: chunk.timestamp, type: chunk.type });\n if (!FakeVideoDecoder.emit) {\n this.decodeQueueSize++;\n return;\n }\n this.output({ timestamp: chunk.timestamp, close: () => {} });\n }\n\n reset(): void {\n FakeVideoDecoder.resets++;\n this.decodeQueueSize = 0;\n this.state = 'unconfigured';\n }\n\n close(): void {\n this.state = 'closed';\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gesso-core",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "The retained UI graph, the layout engine, the two renderers and input — everything below a component.",
5
5
  "license": "MIT",
6
6
  "author": "Kevin Baker",