@invinite-org/chartlang-adapter-kit 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/README.md +7 -0
- package/dist/canvas/index.d.ts +5 -0
- package/dist/canvas/index.d.ts.map +1 -0
- package/dist/canvas/index.js +5 -0
- package/dist/canvas/index.js.map +1 -0
- package/dist/canvas/mockContext.d.ts +168 -0
- package/dist/canvas/mockContext.d.ts.map +1 -0
- package/dist/canvas/mockContext.js +198 -0
- package/dist/canvas/mockContext.js.map +1 -0
- package/dist/canvas/paintPrimitive.d.ts +35 -0
- package/dist/canvas/paintPrimitive.d.ts.map +1 -0
- package/dist/canvas/paintPrimitive.js +171 -0
- package/dist/canvas/paintPrimitive.js.map +1 -0
- package/dist/canvas/renderCtx.d.ts +40 -0
- package/dist/canvas/renderCtx.d.ts.map +1 -0
- package/dist/canvas/renderCtx.js +4 -0
- package/dist/canvas/renderCtx.js.map +1 -0
- package/dist/geometry/_lib/arrowhead.d.ts +18 -0
- package/dist/geometry/_lib/arrowhead.d.ts.map +1 -0
- package/dist/geometry/_lib/arrowhead.js +38 -0
- package/dist/geometry/_lib/arrowhead.js.map +1 -0
- package/dist/geometry/_lib/bezier.d.ts +57 -0
- package/dist/geometry/_lib/bezier.d.ts.map +1 -0
- package/dist/geometry/_lib/bezier.js +84 -0
- package/dist/geometry/_lib/bezier.js.map +1 -0
- package/dist/geometry/_lib/chevron.d.ts +29 -0
- package/dist/geometry/_lib/chevron.d.ts.map +1 -0
- package/dist/geometry/_lib/chevron.js +37 -0
- package/dist/geometry/_lib/chevron.js.map +1 -0
- package/dist/geometry/_lib/dash.d.ts +30 -0
- package/dist/geometry/_lib/dash.d.ts.map +1 -0
- package/dist/geometry/_lib/dash.js +40 -0
- package/dist/geometry/_lib/dash.js.map +1 -0
- package/dist/geometry/_lib/fibLevels.d.ts +36 -0
- package/dist/geometry/_lib/fibLevels.d.ts.map +1 -0
- package/dist/geometry/_lib/fibLevels.js +44 -0
- package/dist/geometry/_lib/fibLevels.js.map +1 -0
- package/dist/geometry/_lib/gannLevels.d.ts +54 -0
- package/dist/geometry/_lib/gannLevels.d.ts.map +1 -0
- package/dist/geometry/_lib/gannLevels.js +88 -0
- package/dist/geometry/_lib/gannLevels.js.map +1 -0
- package/dist/geometry/_lib/lineExtend.d.ts +31 -0
- package/dist/geometry/_lib/lineExtend.d.ts.map +1 -0
- package/dist/geometry/_lib/lineExtend.js +48 -0
- package/dist/geometry/_lib/lineExtend.js.map +1 -0
- package/dist/geometry/_lib/namedPolyline.d.ts +25 -0
- package/dist/geometry/_lib/namedPolyline.d.ts.map +1 -0
- package/dist/geometry/_lib/namedPolyline.js +64 -0
- package/dist/geometry/_lib/namedPolyline.js.map +1 -0
- package/dist/geometry/_lib/pitchforkGeom.d.ts +46 -0
- package/dist/geometry/_lib/pitchforkGeom.d.ts.map +1 -0
- package/dist/geometry/_lib/pitchforkGeom.js +70 -0
- package/dist/geometry/_lib/pitchforkGeom.js.map +1 -0
- package/dist/geometry/_lib/shapeStyle.d.ts +21 -0
- package/dist/geometry/_lib/shapeStyle.d.ts.map +1 -0
- package/dist/geometry/_lib/shapeStyle.js +41 -0
- package/dist/geometry/_lib/shapeStyle.js.map +1 -0
- package/dist/geometry/_lib/strokeStyle.d.ts +34 -0
- package/dist/geometry/_lib/strokeStyle.d.ts.map +1 -0
- package/dist/geometry/_lib/strokeStyle.js +26 -0
- package/dist/geometry/_lib/strokeStyle.js.map +1 -0
- package/dist/geometry/_lib/textStyle.d.ts +70 -0
- package/dist/geometry/_lib/textStyle.d.ts.map +1 -0
- package/dist/geometry/_lib/textStyle.js +78 -0
- package/dist/geometry/_lib/textStyle.js.map +1 -0
- package/dist/geometry/decompose.d.ts +28 -0
- package/dist/geometry/decompose.d.ts.map +1 -0
- package/dist/geometry/decompose.js +176 -0
- package/dist/geometry/decompose.js.map +1 -0
- package/dist/geometry/index.d.ts +4 -0
- package/dist/geometry/index.d.ts.map +1 -0
- package/dist/geometry/index.js +5 -0
- package/dist/geometry/index.js.map +1 -0
- package/dist/geometry/kinds/annotations.d.ts +77 -0
- package/dist/geometry/kinds/annotations.d.ts.map +1 -0
- package/dist/geometry/kinds/annotations.js +219 -0
- package/dist/geometry/kinds/annotations.js.map +1 -0
- package/dist/geometry/kinds/boxes.d.ts +116 -0
- package/dist/geometry/kinds/boxes.d.ts.map +1 -0
- package/dist/geometry/kinds/boxes.js +285 -0
- package/dist/geometry/kinds/boxes.js.map +1 -0
- package/dist/geometry/kinds/channels.d.ts +72 -0
- package/dist/geometry/kinds/channels.d.ts.map +1 -0
- package/dist/geometry/kinds/channels.js +148 -0
- package/dist/geometry/kinds/channels.js.map +1 -0
- package/dist/geometry/kinds/containers.d.ts +54 -0
- package/dist/geometry/kinds/containers.d.ts.map +1 -0
- package/dist/geometry/kinds/containers.js +268 -0
- package/dist/geometry/kinds/containers.js.map +1 -0
- package/dist/geometry/kinds/curves.d.ts +53 -0
- package/dist/geometry/kinds/curves.d.ts.map +1 -0
- package/dist/geometry/kinds/curves.js +110 -0
- package/dist/geometry/kinds/curves.js.map +1 -0
- package/dist/geometry/kinds/cycles.d.ts +52 -0
- package/dist/geometry/kinds/cycles.d.ts.map +1 -0
- package/dist/geometry/kinds/cycles.js +158 -0
- package/dist/geometry/kinds/cycles.js.map +1 -0
- package/dist/geometry/kinds/elliott.d.ts +73 -0
- package/dist/geometry/kinds/elliott.d.ts.map +1 -0
- package/dist/geometry/kinds/elliott.js +116 -0
- package/dist/geometry/kinds/elliott.js.map +1 -0
- package/dist/geometry/kinds/fibonacci.d.ts +166 -0
- package/dist/geometry/kinds/fibonacci.d.ts.map +1 -0
- package/dist/geometry/kinds/fibonacci.js +458 -0
- package/dist/geometry/kinds/fibonacci.js.map +1 -0
- package/dist/geometry/kinds/freehand.d.ts +53 -0
- package/dist/geometry/kinds/freehand.d.ts.map +1 -0
- package/dist/geometry/kinds/freehand.js +115 -0
- package/dist/geometry/kinds/freehand.js.map +1 -0
- package/dist/geometry/kinds/gann.d.ts +63 -0
- package/dist/geometry/kinds/gann.d.ts.map +1 -0
- package/dist/geometry/kinds/gann.js +153 -0
- package/dist/geometry/kinds/gann.js.map +1 -0
- package/dist/geometry/kinds/lines.d.ts +90 -0
- package/dist/geometry/kinds/lines.d.ts.map +1 -0
- package/dist/geometry/kinds/lines.js +201 -0
- package/dist/geometry/kinds/lines.js.map +1 -0
- package/dist/geometry/kinds/marker.d.ts +21 -0
- package/dist/geometry/kinds/marker.d.ts.map +1 -0
- package/dist/geometry/kinds/marker.js +47 -0
- package/dist/geometry/kinds/marker.js.map +1 -0
- package/dist/geometry/kinds/patterns.d.ts +85 -0
- package/dist/geometry/kinds/patterns.d.ts.map +1 -0
- package/dist/geometry/kinds/patterns.js +133 -0
- package/dist/geometry/kinds/patterns.js.map +1 -0
- package/dist/geometry/kinds/pitchforks.d.ts +36 -0
- package/dist/geometry/kinds/pitchforks.d.ts.map +1 -0
- package/dist/geometry/kinds/pitchforks.js +109 -0
- package/dist/geometry/kinds/pitchforks.js.map +1 -0
- package/dist/geometry/project.d.ts +50 -0
- package/dist/geometry/project.d.ts.map +1 -0
- package/dist/geometry/project.js +62 -0
- package/dist/geometry/project.js.map +1 -0
- package/dist/geometry/types.d.ts +146 -0
- package/dist/geometry/types.d.ts.map +1 -0
- package/dist/geometry/types.js +4 -0
- package/dist/geometry/types.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +8 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
# @invinite-org/chartlang-adapter-kit
|
|
2
2
|
|
|
3
|
+
## 1.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 03f59bf: Complete the `adapter-kit` geometry layer with the final 23 drawing-kind
|
|
8
|
+
decomposers — 4 gann (`gann-box`, `gann-square-fixed`, `gann-square`,
|
|
9
|
+
`gann-fan`), 2 pitchforks (`pitchfork`, `pitchfan`), 6 harmonic patterns
|
|
10
|
+
(`xabcd-pattern`, `cypher-pattern`, `head-and-shoulders`, `abcd-pattern`,
|
|
11
|
+
`triangle-pattern`, `three-drives-pattern`), 5 elliott waves
|
|
12
|
+
(`elliott-impulse-wave`, `elliott-correction-wave`, `elliott-triangle-wave`,
|
|
13
|
+
`elliott-double-combo`, `elliott-triple-combo`), 3 cycles (`cyclic-lines`,
|
|
14
|
+
`time-cycles`, `sine-line`), and 3 containers (`group`, `frame`, `table`).
|
|
15
|
+
|
|
16
|
+
`decomposeDrawing` is now **exhaustive over all 63 `DrawingKind`s**: its
|
|
17
|
+
`default` arm is a `const _exhaustive: never` guard, so adding a future kind to
|
|
18
|
+
core fails `pnpm typecheck` until a decomposer is added. The `table` kind
|
|
19
|
+
decomposes in CSS-pixel/viewport space (it resolves `position` against the
|
|
20
|
+
`Viewport` rather than world coordinates).
|
|
21
|
+
|
|
22
|
+
Move the shared `gannLevels` (`GANN_LEVELS` / `GANN_FAN_RATIOS` /
|
|
23
|
+
`GANN_FAN_LABELS` / `formatGannRatio`) and `pitchforkGeom`
|
|
24
|
+
(`medianOriginFor` / `medianTargetFor`) helpers into package-private
|
|
25
|
+
`geometry/_lib/`, reused by the gann and pitchfork decomposers.
|
|
26
|
+
|
|
27
|
+
- 03f59bf: Extend the `adapter-kit` geometry layer with 20 more drawing-kind decomposers —
|
|
28
|
+
3 curves (`arc`, `curve`, `double-curve`), 3 freehand (`pen`, `highlighter`,
|
|
29
|
+
`brush`), 4 channels (`trend-channel`, `flat-top-bottom`, `disjoint-channel`,
|
|
30
|
+
`regression-trend`), and 10 fibonacci (`fib-retracement`, `fib-trend-extension`,
|
|
31
|
+
`fib-channel`, `fib-time-zone`, `fib-wedge`, `fib-speed-fan`, `fib-speed-arcs`,
|
|
32
|
+
`fib-spiral`, `fib-circles`, `fib-trend-time`). `decomposeDrawing` now covers 40
|
|
33
|
+
of the 63 kinds; the remaining 23 return `[]` until Task 3.
|
|
34
|
+
|
|
35
|
+
Add an optional `StrokeStyle.alpha` IR field (backward-compatible — omitted
|
|
36
|
+
strokes are byte-identical to before): `paintPrimitive` brackets the `stroke()`
|
|
37
|
+
in `globalAlpha` when set, expressing the `highlighter` translucency.
|
|
38
|
+
|
|
39
|
+
Move the shared `FIB_LEVELS` ratio array + `formatLevel` label formatter into a
|
|
40
|
+
package-private `geometry/_lib/fibLevels.ts`, reused by every fib decomposer.
|
|
41
|
+
|
|
42
|
+
- 03f59bf: Add a renderer-agnostic geometry layer to `adapter-kit`: the `Viewport` +
|
|
43
|
+
projection helpers (`timeToX`, `priceToY`, `worldPointToPixel`), the
|
|
44
|
+
`DrawPrimitive` IR (`polyline` / `arc` / `text` / `marker` with `StrokeStyle` /
|
|
45
|
+
`FillStyle`), and `decomposeDrawing(emission, viewport)` covering the 20 basic
|
|
46
|
+
drawing kinds (lines / rays, boxes / shapes incl. `fill-between`, annotations,
|
|
47
|
+
marker, text). The remaining 43 kinds return `[]` until Tasks 2–3 land their
|
|
48
|
+
decomposers.
|
|
49
|
+
|
|
50
|
+
Also ships the canvas-family sink under the new `./canvas` sub-path:
|
|
51
|
+
`RenderCtx`, `paintPrimitive(ctx, prim)`, the generalised `MockCanvasContext`
|
|
52
|
+
(records every method + setter), and `hashCallLog` for deterministic call-log
|
|
53
|
+
hashing.
|
|
54
|
+
|
|
3
55
|
## 1.3.0
|
|
4
56
|
|
|
5
57
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -35,6 +35,13 @@ pnpm add @invinite-org/chartlang-adapter-kit
|
|
|
35
35
|
`dep-error`, `dep-cycle`, `dep-unknown-output`,
|
|
36
36
|
`dep-invalid-input-override`, `dep-dynamic`, `dep-output-not-titled` for
|
|
37
37
|
the new `.output(...)` / `.withInputs(...)` surface.
|
|
38
|
+
- Geometry layer: `timeToX`, `priceToY`, `worldPointToPixel`,
|
|
39
|
+
`decomposeDrawing(emission, viewport) -> DrawPrimitive[]` (covers the basic
|
|
40
|
+
drawing kinds today; the rest land incrementally), plus the `Viewport`,
|
|
41
|
+
`Point2`, `DrawPrimitive`, `StrokeStyle`, and `FillStyle` types.
|
|
42
|
+
- Canvas sink (`@invinite-org/chartlang-adapter-kit/canvas`): `RenderCtx`,
|
|
43
|
+
`paintPrimitive(ctx, prim)`, `MockCanvasContext`, `RecordedCall`, and
|
|
44
|
+
`hashCallLog(mock.calls)` for deterministic, headless call-log hashing.
|
|
38
45
|
|
|
39
46
|
## Minimum-viable API call
|
|
40
47
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/canvas/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAClE,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Copyright (c) 2026 Invinite. Licensed under the MIT License.
|
|
2
|
+
// See the LICENSE file in the repo root for full license text.
|
|
3
|
+
export { hashCallLog, MockCanvasContext } from "./mockContext.js";
|
|
4
|
+
export { paintPrimitive } from "./paintPrimitive.js";
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/canvas/index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAE/D,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAElE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n\nexport { hashCallLog, MockCanvasContext } from \"./mockContext.js\";\nexport type { RecordedCall } from \"./mockContext.js\";\nexport { paintPrimitive } from \"./paintPrimitive.js\";\nexport type { RenderCtx } from \"./renderCtx.js\";\n"]}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One recorded call against {@link MockCanvasContext}. Tests inspect
|
|
3
|
+
* the array to assert a paint sequence. The union covers every method
|
|
4
|
+
* and setter on {@link import("./renderCtx").RenderCtx}; adding a new
|
|
5
|
+
* draw call means extending both the mock and this union together.
|
|
6
|
+
*
|
|
7
|
+
* @since 1.3
|
|
8
|
+
* @stable
|
|
9
|
+
* @example
|
|
10
|
+
* const call: RecordedCall = { kind: "clearRect", x: 0, y: 0, w: 100, h: 100 };
|
|
11
|
+
* void call;
|
|
12
|
+
*/
|
|
13
|
+
export type RecordedCall = {
|
|
14
|
+
readonly kind: "clearRect";
|
|
15
|
+
readonly x: number;
|
|
16
|
+
readonly y: number;
|
|
17
|
+
readonly w: number;
|
|
18
|
+
readonly h: number;
|
|
19
|
+
} | {
|
|
20
|
+
readonly kind: "translate";
|
|
21
|
+
readonly x: number;
|
|
22
|
+
readonly y: number;
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: "save";
|
|
25
|
+
} | {
|
|
26
|
+
readonly kind: "restore";
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: "beginPath";
|
|
29
|
+
} | {
|
|
30
|
+
readonly kind: "moveTo";
|
|
31
|
+
readonly x: number;
|
|
32
|
+
readonly y: number;
|
|
33
|
+
} | {
|
|
34
|
+
readonly kind: "lineTo";
|
|
35
|
+
readonly x: number;
|
|
36
|
+
readonly y: number;
|
|
37
|
+
} | {
|
|
38
|
+
readonly kind: "stroke";
|
|
39
|
+
} | {
|
|
40
|
+
readonly kind: "fillRect";
|
|
41
|
+
readonly x: number;
|
|
42
|
+
readonly y: number;
|
|
43
|
+
readonly w: number;
|
|
44
|
+
readonly h: number;
|
|
45
|
+
} | {
|
|
46
|
+
readonly kind: "fill";
|
|
47
|
+
} | {
|
|
48
|
+
readonly kind: "arc";
|
|
49
|
+
readonly x: number;
|
|
50
|
+
readonly y: number;
|
|
51
|
+
readonly radius: number;
|
|
52
|
+
readonly start: number;
|
|
53
|
+
readonly end: number;
|
|
54
|
+
} | {
|
|
55
|
+
readonly kind: "closePath";
|
|
56
|
+
} | {
|
|
57
|
+
readonly kind: "setLineDash";
|
|
58
|
+
readonly segments: ReadonlyArray<number>;
|
|
59
|
+
} | {
|
|
60
|
+
readonly kind: "fillText";
|
|
61
|
+
readonly text: string;
|
|
62
|
+
readonly x: number;
|
|
63
|
+
readonly y: number;
|
|
64
|
+
} | {
|
|
65
|
+
readonly kind: "set";
|
|
66
|
+
readonly prop: "strokeStyle";
|
|
67
|
+
readonly value: string;
|
|
68
|
+
} | {
|
|
69
|
+
readonly kind: "set";
|
|
70
|
+
readonly prop: "fillStyle";
|
|
71
|
+
readonly value: string;
|
|
72
|
+
} | {
|
|
73
|
+
readonly kind: "set";
|
|
74
|
+
readonly prop: "lineWidth";
|
|
75
|
+
readonly value: number;
|
|
76
|
+
} | {
|
|
77
|
+
readonly kind: "set";
|
|
78
|
+
readonly prop: "globalAlpha";
|
|
79
|
+
readonly value: number;
|
|
80
|
+
} | {
|
|
81
|
+
readonly kind: "set";
|
|
82
|
+
readonly prop: "font";
|
|
83
|
+
readonly value: string;
|
|
84
|
+
} | {
|
|
85
|
+
readonly kind: "set";
|
|
86
|
+
readonly prop: "textAlign";
|
|
87
|
+
readonly value: string;
|
|
88
|
+
} | {
|
|
89
|
+
readonly kind: "set";
|
|
90
|
+
readonly prop: "textBaseline";
|
|
91
|
+
readonly value: string;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Hand-rolled Canvas 2D mock that satisfies the canvas sink's
|
|
95
|
+
* {@link import("./renderCtx").RenderCtx} structural type. Every method
|
|
96
|
+
* and setter appends a typed record to `calls` so tests can assert the
|
|
97
|
+
* exact paint sequence without standing up `node-canvas` or a real
|
|
98
|
+
* browser. The canvas2d adapter re-exports this class as
|
|
99
|
+
* `MockCanvas2DContext` (implementation shared, public name unchanged).
|
|
100
|
+
*
|
|
101
|
+
* Property setters are tracked via accessor pairs — the mock stores the
|
|
102
|
+
* most recent value in private fields so `ctx.strokeStyle = "#x"` both
|
|
103
|
+
* records the call and survives a subsequent read.
|
|
104
|
+
*
|
|
105
|
+
* @since 1.3
|
|
106
|
+
* @stable
|
|
107
|
+
* @example
|
|
108
|
+
* const ctx = new MockCanvasContext();
|
|
109
|
+
* ctx.clearRect(0, 0, 100, 100);
|
|
110
|
+
* // ctx.calls[0] === { kind: "clearRect", x: 0, y: 0, w: 100, h: 100 }
|
|
111
|
+
* void ctx;
|
|
112
|
+
*/
|
|
113
|
+
export declare class MockCanvasContext {
|
|
114
|
+
readonly calls: RecordedCall[];
|
|
115
|
+
private _strokeStyle;
|
|
116
|
+
private _fillStyle;
|
|
117
|
+
private _lineWidth;
|
|
118
|
+
private _globalAlpha;
|
|
119
|
+
private _font;
|
|
120
|
+
private _textAlign;
|
|
121
|
+
private _textBaseline;
|
|
122
|
+
clearRect(x: number, y: number, w: number, h: number): void;
|
|
123
|
+
translate(x: number, y: number): void;
|
|
124
|
+
save(): void;
|
|
125
|
+
restore(): void;
|
|
126
|
+
beginPath(): void;
|
|
127
|
+
moveTo(x: number, y: number): void;
|
|
128
|
+
lineTo(x: number, y: number): void;
|
|
129
|
+
stroke(): void;
|
|
130
|
+
fillRect(x: number, y: number, w: number, h: number): void;
|
|
131
|
+
fill(): void;
|
|
132
|
+
arc(x: number, y: number, radius: number, start: number, end: number): void;
|
|
133
|
+
closePath(): void;
|
|
134
|
+
setLineDash(segments: ReadonlyArray<number>): void;
|
|
135
|
+
fillText(text: string, x: number, y: number): void;
|
|
136
|
+
get strokeStyle(): string;
|
|
137
|
+
set strokeStyle(value: string);
|
|
138
|
+
get fillStyle(): string;
|
|
139
|
+
set fillStyle(value: string);
|
|
140
|
+
get lineWidth(): number;
|
|
141
|
+
set lineWidth(value: number);
|
|
142
|
+
get globalAlpha(): number;
|
|
143
|
+
set globalAlpha(value: number);
|
|
144
|
+
get font(): string;
|
|
145
|
+
set font(value: string);
|
|
146
|
+
get textAlign(): "start" | "center" | "end" | "left" | "right";
|
|
147
|
+
set textAlign(value: "start" | "center" | "end" | "left" | "right");
|
|
148
|
+
get textBaseline(): "top" | "middle" | "bottom" | "alphabetic" | "hanging";
|
|
149
|
+
set textBaseline(value: "top" | "middle" | "bottom" | "alphabetic" | "hanging");
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Hash a recorded call log into a stable SHA-256 hex string. Floats are
|
|
153
|
+
* rounded to four decimal places and re-serialised in canonical-key
|
|
154
|
+
* JSON so a microscopic floating-point drift does not re-hash the log.
|
|
155
|
+
* Pass `mock.calls`. Adapters pin their integration render output
|
|
156
|
+
* against a single golden constant with this.
|
|
157
|
+
*
|
|
158
|
+
* @since 1.3
|
|
159
|
+
* @stable
|
|
160
|
+
* @example
|
|
161
|
+
* const ctx = new MockCanvasContext();
|
|
162
|
+
* ctx.clearRect(0, 0, 100, 100);
|
|
163
|
+
* const h = hashCallLog(ctx.calls);
|
|
164
|
+
* // h is a 64-char hex string
|
|
165
|
+
* void h;
|
|
166
|
+
*/
|
|
167
|
+
export declare function hashCallLog(calls: ReadonlyArray<RecordedCall>): string;
|
|
168
|
+
//# sourceMappingURL=mockContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mockContext.d.ts","sourceRoot":"","sources":["../../src/canvas/mockContext.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// Copyright (c) 2026 Invinite. Licensed under the MIT License.
|
|
2
|
+
// See the LICENSE file in the repo root for full license text.
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
/**
|
|
5
|
+
* Hand-rolled Canvas 2D mock that satisfies the canvas sink's
|
|
6
|
+
* {@link import("./renderCtx").RenderCtx} structural type. Every method
|
|
7
|
+
* and setter appends a typed record to `calls` so tests can assert the
|
|
8
|
+
* exact paint sequence without standing up `node-canvas` or a real
|
|
9
|
+
* browser. The canvas2d adapter re-exports this class as
|
|
10
|
+
* `MockCanvas2DContext` (implementation shared, public name unchanged).
|
|
11
|
+
*
|
|
12
|
+
* Property setters are tracked via accessor pairs — the mock stores the
|
|
13
|
+
* most recent value in private fields so `ctx.strokeStyle = "#x"` both
|
|
14
|
+
* records the call and survives a subsequent read.
|
|
15
|
+
*
|
|
16
|
+
* @since 1.3
|
|
17
|
+
* @stable
|
|
18
|
+
* @example
|
|
19
|
+
* const ctx = new MockCanvasContext();
|
|
20
|
+
* ctx.clearRect(0, 0, 100, 100);
|
|
21
|
+
* // ctx.calls[0] === { kind: "clearRect", x: 0, y: 0, w: 100, h: 100 }
|
|
22
|
+
* void ctx;
|
|
23
|
+
*/
|
|
24
|
+
export class MockCanvasContext {
|
|
25
|
+
calls = [];
|
|
26
|
+
_strokeStyle = "#000000";
|
|
27
|
+
_fillStyle = "#000000";
|
|
28
|
+
_lineWidth = 1;
|
|
29
|
+
_globalAlpha = 1;
|
|
30
|
+
_font = "10px sans-serif";
|
|
31
|
+
_textAlign = "start";
|
|
32
|
+
_textBaseline = "alphabetic";
|
|
33
|
+
clearRect(x, y, w, h) {
|
|
34
|
+
this.calls.push({ kind: "clearRect", x, y, w, h });
|
|
35
|
+
}
|
|
36
|
+
translate(x, y) {
|
|
37
|
+
this.calls.push({ kind: "translate", x, y });
|
|
38
|
+
}
|
|
39
|
+
save() {
|
|
40
|
+
this.calls.push({ kind: "save" });
|
|
41
|
+
}
|
|
42
|
+
restore() {
|
|
43
|
+
this.calls.push({ kind: "restore" });
|
|
44
|
+
}
|
|
45
|
+
beginPath() {
|
|
46
|
+
this.calls.push({ kind: "beginPath" });
|
|
47
|
+
}
|
|
48
|
+
moveTo(x, y) {
|
|
49
|
+
this.calls.push({ kind: "moveTo", x, y });
|
|
50
|
+
}
|
|
51
|
+
lineTo(x, y) {
|
|
52
|
+
this.calls.push({ kind: "lineTo", x, y });
|
|
53
|
+
}
|
|
54
|
+
stroke() {
|
|
55
|
+
this.calls.push({ kind: "stroke" });
|
|
56
|
+
}
|
|
57
|
+
fillRect(x, y, w, h) {
|
|
58
|
+
this.calls.push({ kind: "fillRect", x, y, w, h });
|
|
59
|
+
}
|
|
60
|
+
fill() {
|
|
61
|
+
this.calls.push({ kind: "fill" });
|
|
62
|
+
}
|
|
63
|
+
arc(x, y, radius, start, end) {
|
|
64
|
+
this.calls.push({ kind: "arc", x, y, radius, start, end });
|
|
65
|
+
}
|
|
66
|
+
closePath() {
|
|
67
|
+
this.calls.push({ kind: "closePath" });
|
|
68
|
+
}
|
|
69
|
+
setLineDash(segments) {
|
|
70
|
+
this.calls.push({ kind: "setLineDash", segments: segments.slice() });
|
|
71
|
+
}
|
|
72
|
+
fillText(text, x, y) {
|
|
73
|
+
this.calls.push({ kind: "fillText", text, x, y });
|
|
74
|
+
}
|
|
75
|
+
get strokeStyle() {
|
|
76
|
+
return this._strokeStyle;
|
|
77
|
+
}
|
|
78
|
+
set strokeStyle(value) {
|
|
79
|
+
this._strokeStyle = value;
|
|
80
|
+
this.calls.push({ kind: "set", prop: "strokeStyle", value });
|
|
81
|
+
}
|
|
82
|
+
get fillStyle() {
|
|
83
|
+
return this._fillStyle;
|
|
84
|
+
}
|
|
85
|
+
set fillStyle(value) {
|
|
86
|
+
this._fillStyle = value;
|
|
87
|
+
this.calls.push({ kind: "set", prop: "fillStyle", value });
|
|
88
|
+
}
|
|
89
|
+
get lineWidth() {
|
|
90
|
+
return this._lineWidth;
|
|
91
|
+
}
|
|
92
|
+
set lineWidth(value) {
|
|
93
|
+
this._lineWidth = value;
|
|
94
|
+
this.calls.push({ kind: "set", prop: "lineWidth", value });
|
|
95
|
+
}
|
|
96
|
+
get globalAlpha() {
|
|
97
|
+
return this._globalAlpha;
|
|
98
|
+
}
|
|
99
|
+
set globalAlpha(value) {
|
|
100
|
+
this._globalAlpha = value;
|
|
101
|
+
this.calls.push({ kind: "set", prop: "globalAlpha", value });
|
|
102
|
+
}
|
|
103
|
+
get font() {
|
|
104
|
+
return this._font;
|
|
105
|
+
}
|
|
106
|
+
set font(value) {
|
|
107
|
+
this._font = value;
|
|
108
|
+
this.calls.push({ kind: "set", prop: "font", value });
|
|
109
|
+
}
|
|
110
|
+
get textAlign() {
|
|
111
|
+
return this._textAlign;
|
|
112
|
+
}
|
|
113
|
+
set textAlign(value) {
|
|
114
|
+
this._textAlign = value;
|
|
115
|
+
this.calls.push({ kind: "set", prop: "textAlign", value });
|
|
116
|
+
}
|
|
117
|
+
get textBaseline() {
|
|
118
|
+
return this._textBaseline;
|
|
119
|
+
}
|
|
120
|
+
set textBaseline(value) {
|
|
121
|
+
this._textBaseline = value;
|
|
122
|
+
this.calls.push({ kind: "set", prop: "textBaseline", value });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const FLOAT_DECIMALS = 4;
|
|
126
|
+
function roundFloat(n) {
|
|
127
|
+
if (!Number.isFinite(n))
|
|
128
|
+
return String(n);
|
|
129
|
+
return Number(n.toFixed(FLOAT_DECIMALS));
|
|
130
|
+
}
|
|
131
|
+
function canonicalise(call) {
|
|
132
|
+
switch (call.kind) {
|
|
133
|
+
case "clearRect":
|
|
134
|
+
case "fillRect":
|
|
135
|
+
return {
|
|
136
|
+
kind: call.kind,
|
|
137
|
+
x: roundFloat(call.x),
|
|
138
|
+
y: roundFloat(call.y),
|
|
139
|
+
w: roundFloat(call.w),
|
|
140
|
+
h: roundFloat(call.h),
|
|
141
|
+
};
|
|
142
|
+
case "moveTo":
|
|
143
|
+
case "lineTo":
|
|
144
|
+
case "translate":
|
|
145
|
+
return { kind: call.kind, x: roundFloat(call.x), y: roundFloat(call.y) };
|
|
146
|
+
case "arc":
|
|
147
|
+
return {
|
|
148
|
+
kind: call.kind,
|
|
149
|
+
x: roundFloat(call.x),
|
|
150
|
+
y: roundFloat(call.y),
|
|
151
|
+
radius: roundFloat(call.radius),
|
|
152
|
+
start: roundFloat(call.start),
|
|
153
|
+
end: roundFloat(call.end),
|
|
154
|
+
};
|
|
155
|
+
case "setLineDash":
|
|
156
|
+
return { kind: call.kind, segments: call.segments.map((s) => roundFloat(s)) };
|
|
157
|
+
case "fillText":
|
|
158
|
+
return {
|
|
159
|
+
kind: call.kind,
|
|
160
|
+
text: call.text,
|
|
161
|
+
x: roundFloat(call.x),
|
|
162
|
+
y: roundFloat(call.y),
|
|
163
|
+
};
|
|
164
|
+
case "set": {
|
|
165
|
+
const value = typeof call.value === "number" ? roundFloat(call.value) : call.value;
|
|
166
|
+
return { kind: call.kind, prop: call.prop, value };
|
|
167
|
+
}
|
|
168
|
+
case "beginPath":
|
|
169
|
+
case "save":
|
|
170
|
+
case "restore":
|
|
171
|
+
case "stroke":
|
|
172
|
+
case "fill":
|
|
173
|
+
case "closePath":
|
|
174
|
+
return { kind: call.kind };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Hash a recorded call log into a stable SHA-256 hex string. Floats are
|
|
179
|
+
* rounded to four decimal places and re-serialised in canonical-key
|
|
180
|
+
* JSON so a microscopic floating-point drift does not re-hash the log.
|
|
181
|
+
* Pass `mock.calls`. Adapters pin their integration render output
|
|
182
|
+
* against a single golden constant with this.
|
|
183
|
+
*
|
|
184
|
+
* @since 1.3
|
|
185
|
+
* @stable
|
|
186
|
+
* @example
|
|
187
|
+
* const ctx = new MockCanvasContext();
|
|
188
|
+
* ctx.clearRect(0, 0, 100, 100);
|
|
189
|
+
* const h = hashCallLog(ctx.calls);
|
|
190
|
+
* // h is a 64-char hex string
|
|
191
|
+
* void h;
|
|
192
|
+
*/
|
|
193
|
+
export function hashCallLog(calls) {
|
|
194
|
+
const payload = calls.map(canonicalise);
|
|
195
|
+
const serialised = JSON.stringify(payload);
|
|
196
|
+
return createHash("sha256").update(serialised).digest("hex");
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=mockContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mockContext.js","sourceRoot":"","sources":["../../src/canvas/mockContext.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n\nimport { createHash } from \"node:crypto\";\n\n/**\n * One recorded call against {@link MockCanvasContext}. Tests inspect\n * the array to assert a paint sequence. The union covers every method\n * and setter on {@link import(\"./renderCtx\").RenderCtx}; adding a new\n * draw call means extending both the mock and this union together.\n *\n * @since 1.3\n * @stable\n * @example\n * const call: RecordedCall = { kind: \"clearRect\", x: 0, y: 0, w: 100, h: 100 };\n * void call;\n */\nexport type RecordedCall =\n | {\n readonly kind: \"clearRect\";\n readonly x: number;\n readonly y: number;\n readonly w: number;\n readonly h: number;\n }\n | { readonly kind: \"translate\"; readonly x: number; readonly y: number }\n | { readonly kind: \"save\" }\n | { readonly kind: \"restore\" }\n | { readonly kind: \"beginPath\" }\n | { readonly kind: \"moveTo\"; readonly x: number; readonly y: number }\n | { readonly kind: \"lineTo\"; readonly x: number; readonly y: number }\n | { readonly kind: \"stroke\" }\n | {\n readonly kind: \"fillRect\";\n readonly x: number;\n readonly y: number;\n readonly w: number;\n readonly h: number;\n }\n | { readonly kind: \"fill\" }\n | {\n readonly kind: \"arc\";\n readonly x: number;\n readonly y: number;\n readonly radius: number;\n readonly start: number;\n readonly end: number;\n }\n | { readonly kind: \"closePath\" }\n | { readonly kind: \"setLineDash\"; readonly segments: ReadonlyArray<number> }\n | { readonly kind: \"fillText\"; readonly text: string; readonly x: number; readonly y: number }\n | { readonly kind: \"set\"; readonly prop: \"strokeStyle\"; readonly value: string }\n | { readonly kind: \"set\"; readonly prop: \"fillStyle\"; readonly value: string }\n | { readonly kind: \"set\"; readonly prop: \"lineWidth\"; readonly value: number }\n | { readonly kind: \"set\"; readonly prop: \"globalAlpha\"; readonly value: number }\n | { readonly kind: \"set\"; readonly prop: \"font\"; readonly value: string }\n | { readonly kind: \"set\"; readonly prop: \"textAlign\"; readonly value: string }\n | { readonly kind: \"set\"; readonly prop: \"textBaseline\"; readonly value: string };\n\n/**\n * Hand-rolled Canvas 2D mock that satisfies the canvas sink's\n * {@link import(\"./renderCtx\").RenderCtx} structural type. Every method\n * and setter appends a typed record to `calls` so tests can assert the\n * exact paint sequence without standing up `node-canvas` or a real\n * browser. The canvas2d adapter re-exports this class as\n * `MockCanvas2DContext` (implementation shared, public name unchanged).\n *\n * Property setters are tracked via accessor pairs — the mock stores the\n * most recent value in private fields so `ctx.strokeStyle = \"#x\"` both\n * records the call and survives a subsequent read.\n *\n * @since 1.3\n * @stable\n * @example\n * const ctx = new MockCanvasContext();\n * ctx.clearRect(0, 0, 100, 100);\n * // ctx.calls[0] === { kind: \"clearRect\", x: 0, y: 0, w: 100, h: 100 }\n * void ctx;\n */\nexport class MockCanvasContext {\n readonly calls: RecordedCall[] = [];\n private _strokeStyle = \"#000000\";\n private _fillStyle = \"#000000\";\n private _lineWidth = 1;\n private _globalAlpha = 1;\n private _font = \"10px sans-serif\";\n private _textAlign: \"start\" | \"center\" | \"end\" | \"left\" | \"right\" = \"start\";\n private _textBaseline: \"top\" | \"middle\" | \"bottom\" | \"alphabetic\" | \"hanging\" = \"alphabetic\";\n\n clearRect(x: number, y: number, w: number, h: number): void {\n this.calls.push({ kind: \"clearRect\", x, y, w, h });\n }\n\n translate(x: number, y: number): void {\n this.calls.push({ kind: \"translate\", x, y });\n }\n\n save(): void {\n this.calls.push({ kind: \"save\" });\n }\n\n restore(): void {\n this.calls.push({ kind: \"restore\" });\n }\n\n beginPath(): void {\n this.calls.push({ kind: \"beginPath\" });\n }\n\n moveTo(x: number, y: number): void {\n this.calls.push({ kind: \"moveTo\", x, y });\n }\n\n lineTo(x: number, y: number): void {\n this.calls.push({ kind: \"lineTo\", x, y });\n }\n\n stroke(): void {\n this.calls.push({ kind: \"stroke\" });\n }\n\n fillRect(x: number, y: number, w: number, h: number): void {\n this.calls.push({ kind: \"fillRect\", x, y, w, h });\n }\n\n fill(): void {\n this.calls.push({ kind: \"fill\" });\n }\n\n arc(x: number, y: number, radius: number, start: number, end: number): void {\n this.calls.push({ kind: \"arc\", x, y, radius, start, end });\n }\n\n closePath(): void {\n this.calls.push({ kind: \"closePath\" });\n }\n\n setLineDash(segments: ReadonlyArray<number>): void {\n this.calls.push({ kind: \"setLineDash\", segments: segments.slice() });\n }\n\n fillText(text: string, x: number, y: number): void {\n this.calls.push({ kind: \"fillText\", text, x, y });\n }\n\n get strokeStyle(): string {\n return this._strokeStyle;\n }\n\n set strokeStyle(value: string) {\n this._strokeStyle = value;\n this.calls.push({ kind: \"set\", prop: \"strokeStyle\", value });\n }\n\n get fillStyle(): string {\n return this._fillStyle;\n }\n\n set fillStyle(value: string) {\n this._fillStyle = value;\n this.calls.push({ kind: \"set\", prop: \"fillStyle\", value });\n }\n\n get lineWidth(): number {\n return this._lineWidth;\n }\n\n set lineWidth(value: number) {\n this._lineWidth = value;\n this.calls.push({ kind: \"set\", prop: \"lineWidth\", value });\n }\n\n get globalAlpha(): number {\n return this._globalAlpha;\n }\n\n set globalAlpha(value: number) {\n this._globalAlpha = value;\n this.calls.push({ kind: \"set\", prop: \"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.calls.push({ kind: \"set\", prop: \"font\", value });\n }\n\n get textAlign(): \"start\" | \"center\" | \"end\" | \"left\" | \"right\" {\n return this._textAlign;\n }\n\n set textAlign(value: \"start\" | \"center\" | \"end\" | \"left\" | \"right\") {\n this._textAlign = value;\n this.calls.push({ kind: \"set\", prop: \"textAlign\", value });\n }\n\n get textBaseline(): \"top\" | \"middle\" | \"bottom\" | \"alphabetic\" | \"hanging\" {\n return this._textBaseline;\n }\n\n set textBaseline(value: \"top\" | \"middle\" | \"bottom\" | \"alphabetic\" | \"hanging\") {\n this._textBaseline = value;\n this.calls.push({ kind: \"set\", prop: \"textBaseline\", value });\n }\n}\n\nconst FLOAT_DECIMALS = 4;\n\nfunction roundFloat(n: number): number | string {\n if (!Number.isFinite(n)) return String(n);\n return Number(n.toFixed(FLOAT_DECIMALS));\n}\n\nfunction canonicalise(call: RecordedCall): Record<string, unknown> {\n switch (call.kind) {\n case \"clearRect\":\n case \"fillRect\":\n return {\n kind: call.kind,\n x: roundFloat(call.x),\n y: roundFloat(call.y),\n w: roundFloat(call.w),\n h: roundFloat(call.h),\n };\n case \"moveTo\":\n case \"lineTo\":\n case \"translate\":\n return { kind: call.kind, x: roundFloat(call.x), y: roundFloat(call.y) };\n case \"arc\":\n return {\n kind: call.kind,\n x: roundFloat(call.x),\n y: roundFloat(call.y),\n radius: roundFloat(call.radius),\n start: roundFloat(call.start),\n end: roundFloat(call.end),\n };\n case \"setLineDash\":\n return { kind: call.kind, segments: call.segments.map((s) => roundFloat(s)) };\n case \"fillText\":\n return {\n kind: call.kind,\n text: call.text,\n x: roundFloat(call.x),\n y: roundFloat(call.y),\n };\n case \"set\": {\n const value = typeof call.value === \"number\" ? roundFloat(call.value) : call.value;\n return { kind: call.kind, prop: call.prop, value };\n }\n case \"beginPath\":\n case \"save\":\n case \"restore\":\n case \"stroke\":\n case \"fill\":\n case \"closePath\":\n return { kind: call.kind };\n }\n}\n\n/**\n * Hash a recorded call log into a stable SHA-256 hex string. Floats are\n * rounded to four decimal places and re-serialised in canonical-key\n * JSON so a microscopic floating-point drift does not re-hash the log.\n * Pass `mock.calls`. Adapters pin their integration render output\n * against a single golden constant with this.\n *\n * @since 1.3\n * @stable\n * @example\n * const ctx = new MockCanvasContext();\n * ctx.clearRect(0, 0, 100, 100);\n * const h = hashCallLog(ctx.calls);\n * // h is a 64-char hex string\n * void h;\n */\nexport function hashCallLog(calls: ReadonlyArray<RecordedCall>): string {\n const payload = calls.map(canonicalise);\n const serialised = JSON.stringify(payload);\n return createHash(\"sha256\").update(serialised).digest(\"hex\");\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { DrawPrimitive } from "../geometry/types.js";
|
|
2
|
+
import type { RenderCtx } from "./renderCtx.js";
|
|
3
|
+
/**
|
|
4
|
+
* Paint one {@link DrawPrimitive} into a {@link RenderCtx}. The canvas
|
|
5
|
+
* sink shared by the canvas2d, lightweight-charts, and uplot adapters.
|
|
6
|
+
* For each primitive the painter applies the stroke style (when set),
|
|
7
|
+
* builds the path, fills before stroking (so the outline draws on top
|
|
8
|
+
* of the band), and resets `setLineDash([])` + `globalAlpha = 1` after
|
|
9
|
+
* use so downstream draws are unaffected — matching the per-kind
|
|
10
|
+
* renderer conventions the IR replaces. A `stroke.alpha` (the
|
|
11
|
+
* `highlighter` translucency) brackets the `stroke()` in `globalAlpha`;
|
|
12
|
+
* an omitted `alpha` emits no `globalAlpha` mutation, keeping the
|
|
13
|
+
* sequence byte-identical to a Task-1 stroke.
|
|
14
|
+
*
|
|
15
|
+
* `text.bgColor` is carried on the IR but NOT painted here (the
|
|
16
|
+
* structural `RenderCtx` exposes neither `measureText` nor a
|
|
17
|
+
* background-rect path), mirroring the source renderers. The IR
|
|
18
|
+
* `marker` primitive is painted as a sized glyph; no basic kind emits
|
|
19
|
+
* it today, but adapters / future kinds rely on the painter covering
|
|
20
|
+
* every IR shape.
|
|
21
|
+
*
|
|
22
|
+
* @since 1.3
|
|
23
|
+
* @stable
|
|
24
|
+
* @example
|
|
25
|
+
* declare const ctx: RenderCtx;
|
|
26
|
+
* paintPrimitive(ctx, {
|
|
27
|
+
* kind: "polyline",
|
|
28
|
+
* points: [{ x: 0, y: 0 }, { x: 10, y: 10 }],
|
|
29
|
+
* closed: false,
|
|
30
|
+
* stroke: { color: "#000000", width: 1, dash: [] },
|
|
31
|
+
* });
|
|
32
|
+
* void paintPrimitive;
|
|
33
|
+
*/
|
|
34
|
+
export declare function paintPrimitive(ctx: RenderCtx, p: DrawPrimitive): void;
|
|
35
|
+
//# sourceMappingURL=paintPrimitive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paintPrimitive.d.ts","sourceRoot":"","sources":["../../src/canvas/paintPrimitive.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAA0B,MAAM,sBAAsB,CAAC;AAClF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAgDhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,SAAS,EAAE,CAAC,EAAE,aAAa,GAAG,IAAI,CA8CrE"}
|