@invinite-org/chartlang-adapter-kit 1.2.1 → 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 +157 -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/capabilities/capabilities.d.ts +9 -7
- package/dist/capabilities/capabilities.d.ts.map +1 -1
- package/dist/capabilities/capabilities.js +14 -8
- package/dist/capabilities/capabilities.js.map +1 -1
- 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/dist/types.d.ts +54 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/validation/validateEmission.d.ts.map +1 -1
- package/dist/validation/validateEmission.js +47 -0
- package/dist/validation/validateEmission.js.map +1 -1
- package/package.json +9 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,162 @@
|
|
|
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
|
+
|
|
55
|
+
## 1.3.0
|
|
56
|
+
|
|
57
|
+
### Minor Changes
|
|
58
|
+
|
|
59
|
+
- ca19e20: Bidirectional plot `offset` — negative offsets shift a plotted series left.
|
|
60
|
+
|
|
61
|
+
`offset` becomes a presentation-only **display shift** in bars with the
|
|
62
|
+
fixed sign convention `+n` = right (future), `−n` = left (past); the
|
|
63
|
+
numeric series value is unshifted. This replaces the old value-read model
|
|
64
|
+
(where a positive offset made `series.current` read the value N bars ago
|
|
65
|
+
and a negative offset resolved to `NaN`). The `*Opts` `offset` JSDoc (and
|
|
66
|
+
ALMA's `barShift`) now describe both directions and drop the old
|
|
67
|
+
"negative ⇒ NaN" wording (`AlmaOpts.offset`, the Gaussian-centre
|
|
68
|
+
position, is unchanged).
|
|
69
|
+
|
|
70
|
+
`PlotEmission` gains an optional presentation field `xShift?: number`
|
|
71
|
+
(signed integer bars; omitted/`0` ≡ no shift, so a no-shift emission is
|
|
72
|
+
byte-identical to today). `validateEmission` rejects a non-integer
|
|
73
|
+
`xShift`. The compiler no longer counts `offset` toward `maxLookback`
|
|
74
|
+
(the value is no longer read from a deeper slot). The runtime threads the
|
|
75
|
+
declared offset onto the emission as `xShift` (reading a
|
|
76
|
+
`WeakMap<Series, number>` offset tag set by `makeShiftedSeriesView`; ALMA
|
|
77
|
+
tags `opts.barShift`) and stops the old value-read shift so
|
|
78
|
+
`series.current` is unshifted; the reference adapter renders it by
|
|
79
|
+
projecting `xShift` onto the x-axis (extending the viewport for
|
|
80
|
+
future-shifted points).
|
|
81
|
+
|
|
82
|
+
The Pine converter now maps `plot(<ta.* call>, offset=N)` onto the
|
|
83
|
+
emitted `ta.*` call's `offset` opt (signed, both directions); a plot
|
|
84
|
+
whose value is not a direct `ta.*` call drops the offset and emits the
|
|
85
|
+
new `plot-offset-needs-ta-call` warning, and a plot-level offset
|
|
86
|
+
replacing the ta call's own `offset=` emits `plot-offset-overrides-ta-offset`.
|
|
87
|
+
|
|
88
|
+
The conformance harness's `plot-field` assertion gains an `xShift` field,
|
|
89
|
+
and a new scenario pins both shift directions plus the unshifted value
|
|
90
|
+
series.
|
|
91
|
+
|
|
92
|
+
- 3bf391a: Add the `draw.fillBetween(edgeA, edgeB, opts?)` drawing primitive — a
|
|
93
|
+
native filled ribbon between two edges (the closed polygon `edgeA`
|
|
94
|
+
forward then `edgeB` reversed). It is the chartlang equivalent of Pine's
|
|
95
|
+
`linefill.new(line1, line2, color)` / `fill(plot1, plot2)`. The
|
|
96
|
+
pine-converter now lowers static two-line `linefill.new` to it instead of
|
|
97
|
+
approximating with `draw.rotatedRectangle`, retiring the
|
|
98
|
+
`linefill-rotatedrect-approximated` diagnostic.
|
|
99
|
+
- 8086003: Add an optional presentation-only `z` (render-order / z-index) option to
|
|
100
|
+
`plot()` and every `draw.*` primitive. Default `0`; higher renders on
|
|
101
|
+
top, ties fall back to the existing group + declaration order. Finite
|
|
102
|
+
numbers only. Affects stacking only — values, alerts, and `state.*` are
|
|
103
|
+
unchanged.
|
|
104
|
+
|
|
105
|
+
Adapter kit: `PlotEmission` and `DrawingEmission` gain the matching
|
|
106
|
+
presentation-only `z?: number` wire field, validated by
|
|
107
|
+
`validateEmission` as a finite number (NaN / ±Infinity rejected;
|
|
108
|
+
fractional and negative allowed). Omitted/`0` stays byte-identical to a
|
|
109
|
+
pre-feature emission, so existing goldens and conformance hashes are
|
|
110
|
+
untouched.
|
|
111
|
+
|
|
112
|
+
Runtime: `plotImpl` reads `opts.z`, and the drawing-emit path
|
|
113
|
+
(`createDrawingHandle`) lifts `z` out of `state.style` — into a shallow
|
|
114
|
+
clone with `z` removed, where the per-kind `draw.*` impls fold the opts
|
|
115
|
+
bag — and threads it onto the top-level `PlotEmission.z` /
|
|
116
|
+
`DrawingEmission.z` with the same omit-when-`0` conditional spread used
|
|
117
|
+
for `xShift`. `z` is persisted **beside** the drawing slot's `state`
|
|
118
|
+
(never inside `DrawingState`), so an `update` retains the last value. A
|
|
119
|
+
no-`z` plot or drawing emits no `z` key — byte-identical to the
|
|
120
|
+
pre-feature baseline. `draw.table` / `draw.group` do not carry `z` in
|
|
121
|
+
v1.
|
|
122
|
+
|
|
123
|
+
Pine converter: `explicit_plot_zorder` is now a recognized no-op instead
|
|
124
|
+
of an unmapped warning. chartlang already layers marks by declaration
|
|
125
|
+
order within their group (the normative ordering contract), which is
|
|
126
|
+
exactly what Pine's `explicit_plot_zorder=true` makes authoritative — so
|
|
127
|
+
the flag is satisfied by default and needs no chartlang option.
|
|
128
|
+
`mapDeclarationArgs` no longer raises `indicator-arg-not-mapped` for it;
|
|
129
|
+
instead it emits a single `explicit-plot-zorder-default` info note
|
|
130
|
+
(covering both `explicit_plot_zorder=true` and the Pine-default
|
|
131
|
+
`=false`). The converter still never _emits_ a numeric `z` — Pine has no
|
|
132
|
+
per-element z source construct. Other unmapped `indicator(...)` args
|
|
133
|
+
(`timeframe`, etc.) keep warning.
|
|
134
|
+
|
|
135
|
+
Compiler: the ambient `@invinite-org/chartlang-core` `.d.ts` shim gains a
|
|
136
|
+
`ZOrdered { z?: number }` mixin intersected into `PlotOpts` and every
|
|
137
|
+
`draw.*` option type (mirroring core's `drawingStyle.ts`), so a compiled
|
|
138
|
+
script's `plot(value, { z })` **and** `draw.*(…, { z })` type-check (the
|
|
139
|
+
shim stays in lockstep with core).
|
|
140
|
+
|
|
141
|
+
Conformance: a new `z-order` scenario pins the plot `z` →
|
|
142
|
+
`PlotEmission.z` wire contract — a `plot(value, { z: -1 })` emits
|
|
143
|
+
`z: -1`, a no-`z` plot omits the field (omit-when-`0` byte-identity), and
|
|
144
|
+
a value-hash proves `z` never transforms the series. The `plot-field`
|
|
145
|
+
assertion's `field` union widens to also accept `"z"`.
|
|
146
|
+
|
|
147
|
+
### Patch Changes
|
|
148
|
+
|
|
149
|
+
- Updated dependencies [850ae21]
|
|
150
|
+
- Updated dependencies [ca19e20]
|
|
151
|
+
- Updated dependencies [6235ad7]
|
|
152
|
+
- Updated dependencies [3bf391a]
|
|
153
|
+
- Updated dependencies [8086003]
|
|
154
|
+
- Updated dependencies [850ae21]
|
|
155
|
+
- Updated dependencies [073f41b]
|
|
156
|
+
- Updated dependencies [5a9c24d]
|
|
157
|
+
- Updated dependencies [08c536c]
|
|
158
|
+
- @invinite-org/chartlang-core@1.2.0
|
|
159
|
+
|
|
3
160
|
## 1.2.1
|
|
4
161
|
|
|
5
162
|
### Patch 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"}
|