@michaelmagan/dnumb 0.1.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/AGENTS.md +180 -0
- package/LICENSE +21 -0
- package/README.md +297 -0
- package/dist/analysis/cluster.cjs +114 -0
- package/dist/analysis/cluster.d.cts +68 -0
- package/dist/analysis/cluster.d.ts +68 -0
- package/dist/analysis/cluster.js +109 -0
- package/dist/analysis/distances.cjs +124 -0
- package/dist/analysis/distances.d.cts +58 -0
- package/dist/analysis/distances.d.ts +58 -0
- package/dist/analysis/distances.js +119 -0
- package/dist/analysis/geometry.cjs +60 -0
- package/dist/analysis/geometry.d.cts +34 -0
- package/dist/analysis/geometry.d.ts +34 -0
- package/dist/analysis/geometry.js +53 -0
- package/dist/analysis/layout-tree.cjs +245 -0
- package/dist/analysis/layout-tree.d.cts +45 -0
- package/dist/analysis/layout-tree.d.ts +45 -0
- package/dist/analysis/layout-tree.js +242 -0
- package/dist/analysis/region-query.cjs +71 -0
- package/dist/analysis/region-query.d.cts +25 -0
- package/dist/analysis/region-query.d.ts +25 -0
- package/dist/analysis/region-query.js +67 -0
- package/dist/analysis/types.cjs +7 -0
- package/dist/analysis/types.d.cts +143 -0
- package/dist/analysis/types.d.ts +143 -0
- package/dist/analysis/types.js +6 -0
- package/dist/bundle.cjs +182 -0
- package/dist/bundle.d.cts +91 -0
- package/dist/bundle.d.ts +91 -0
- package/dist/bundle.js +175 -0
- package/dist/cli.cjs +755 -0
- package/dist/cli.d.cts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +719 -0
- package/dist/diff/changes.cjs +656 -0
- package/dist/diff/changes.d.cts +2 -0
- package/dist/diff/changes.d.ts +2 -0
- package/dist/diff/changes.js +653 -0
- package/dist/diff/identity.cjs +167 -0
- package/dist/diff/identity.d.cts +62 -0
- package/dist/diff/identity.d.ts +62 -0
- package/dist/diff/identity.js +159 -0
- package/dist/diff/index.cjs +84 -0
- package/dist/diff/index.d.cts +76 -0
- package/dist/diff/index.d.ts +76 -0
- package/dist/diff/index.js +68 -0
- package/dist/diff/match.cjs +156 -0
- package/dist/diff/match.d.cts +17 -0
- package/dist/diff/match.d.ts +17 -0
- package/dist/diff/match.js +152 -0
- package/dist/diff/render.cjs +540 -0
- package/dist/diff/render.d.cts +2 -0
- package/dist/diff/render.d.ts +2 -0
- package/dist/diff/render.js +537 -0
- package/dist/diff/types.cjs +2 -0
- package/dist/diff/types.d.cts +211 -0
- package/dist/diff/types.d.ts +211 -0
- package/dist/diff/types.js +1 -0
- package/dist/digest/index.cjs +8 -0
- package/dist/digest/index.d.cts +4 -0
- package/dist/digest/index.d.ts +4 -0
- package/dist/digest/index.js +2 -0
- package/dist/digest/parse.cjs +163 -0
- package/dist/digest/parse.d.cts +80 -0
- package/dist/digest/parse.d.ts +80 -0
- package/dist/digest/parse.js +160 -0
- package/dist/digest/render.cjs +1575 -0
- package/dist/digest/render.d.cts +41 -0
- package/dist/digest/render.d.ts +41 -0
- package/dist/digest/render.js +1571 -0
- package/dist/digest/types.cjs +10 -0
- package/dist/digest/types.d.cts +86 -0
- package/dist/digest/types.d.ts +86 -0
- package/dist/digest/types.js +9 -0
- package/dist/index.cjs +61 -0
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +19 -0
- package/dist/page.cjs +35 -0
- package/dist/page.d.cts +40 -0
- package/dist/page.d.ts +40 -0
- package/dist/page.js +31 -0
- package/dist/playwright.cjs +138 -0
- package/dist/playwright.d.cts +69 -0
- package/dist/playwright.d.ts +69 -0
- package/dist/playwright.js +131 -0
- package/dist/query.cjs +1152 -0
- package/dist/query.d.cts +47 -0
- package/dist/query.d.ts +47 -0
- package/dist/query.js +1146 -0
- package/dist/snapshot/capture.cjs +80 -0
- package/dist/snapshot/capture.d.cts +34 -0
- package/dist/snapshot/capture.d.ts +34 -0
- package/dist/snapshot/capture.js +77 -0
- package/dist/snapshot/cdp.cjs +450 -0
- package/dist/snapshot/cdp.d.cts +36 -0
- package/dist/snapshot/cdp.d.ts +36 -0
- package/dist/snapshot/cdp.js +447 -0
- package/dist/snapshot/clip.cjs +100 -0
- package/dist/snapshot/clip.d.cts +26 -0
- package/dist/snapshot/clip.d.ts +26 -0
- package/dist/snapshot/clip.js +96 -0
- package/dist/snapshot/collect.cjs +358 -0
- package/dist/snapshot/collect.d.cts +18 -0
- package/dist/snapshot/collect.d.ts +18 -0
- package/dist/snapshot/collect.js +355 -0
- package/dist/snapshot/color.cjs +1039 -0
- package/dist/snapshot/color.d.cts +316 -0
- package/dist/snapshot/color.d.ts +316 -0
- package/dist/snapshot/color.js +1026 -0
- package/dist/snapshot/facts.cjs +167 -0
- package/dist/snapshot/facts.d.cts +48 -0
- package/dist/snapshot/facts.d.ts +48 -0
- package/dist/snapshot/facts.js +161 -0
- package/dist/snapshot/limits.cjs +69 -0
- package/dist/snapshot/limits.d.cts +59 -0
- package/dist/snapshot/limits.d.ts +59 -0
- package/dist/snapshot/limits.js +65 -0
- package/dist/snapshot/not-measured.cjs +44 -0
- package/dist/snapshot/not-measured.d.cts +14 -0
- package/dist/snapshot/not-measured.d.ts +14 -0
- package/dist/snapshot/not-measured.js +41 -0
- package/dist/snapshot/types.cjs +2 -0
- package/dist/snapshot/types.d.cts +238 -0
- package/dist/snapshot/types.d.ts +238 -0
- package/dist/snapshot/types.js +1 -0
- package/dist/spec/index.cjs +9 -0
- package/dist/spec/index.d.cts +15 -0
- package/dist/spec/index.d.ts +15 -0
- package/dist/spec/index.js +6 -0
- package/package.json +141 -0
- package/skills/dnumb/SKILL.md +136 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color, composited and disclosed -- never adjudicated.
|
|
3
|
+
*
|
|
4
|
+
* The design of record is `docs/notes/03-color.md`; this file is stage 1 of
|
|
5
|
+
* it. Three things carry over from that note verbatim, because they are the
|
|
6
|
+
* whole shape of the module:
|
|
7
|
+
*
|
|
8
|
+
* 1. **The backdrop composite runs Node-side, over `ElementRecord[]`, exactly
|
|
9
|
+
* like `clip.ts`.** Both capture paths already produce the raw fields this
|
|
10
|
+
* needs (`backgroundColor`, `backgroundImage`, `opacity`, the overlap
|
|
11
|
+
* inputs); nothing here touches a page or a browser, so both paths agree
|
|
12
|
+
* on every derived color fact by construction.
|
|
13
|
+
* 2. **`ratio` is a number or it is `unmeasured (<reason>)`. Never a verdict.**
|
|
14
|
+
* No output token here ever says `pass`, `fail`, `AA`, `AAA`, `low`, or
|
|
15
|
+
* `poor`. §5 of the design note draws this line in four rules; this module
|
|
16
|
+
* is the code that holds it.
|
|
17
|
+
* 3. **A gradient, an image, or a non-ancestor overlay makes "the background
|
|
18
|
+
* color" not exist**, and printing a number anyway is fabrication, not
|
|
19
|
+
* suppression-avoidance (design note §5's rule 4). `blendedBackgroundColors`
|
|
20
|
+
* -- CDP's own composite -- gets this wrong on exactly these two shapes
|
|
21
|
+
* (verified in the design note's probes, and again in
|
|
22
|
+
* `test/browser/color-backdrop.test.ts`), which is why it is a test oracle
|
|
23
|
+
* for the alpha-chain case it gets right and never a source at runtime.
|
|
24
|
+
*
|
|
25
|
+
* The color-space conversions (`oklch`/`oklab`/`lab`/`lch`/`color(srgb*
|
|
26
|
+
* )`/`color(display-p3 ...)`) are verified against real Chromium canvas
|
|
27
|
+
* readback, not derived from memory of the spec math alone --
|
|
28
|
+
* `test/browser/color-space-conversion.test.ts` paints each one and diffs
|
|
29
|
+
* channels, per the design note's own §10 experiment 4.
|
|
30
|
+
*/
|
|
31
|
+
import type { DocumentBackground, ElementRecord } from "./types.cjs";
|
|
32
|
+
/** 0-255 per channel, alpha 0-1. */
|
|
33
|
+
export interface RGBA {
|
|
34
|
+
r: number;
|
|
35
|
+
g: number;
|
|
36
|
+
b: number;
|
|
37
|
+
a: number;
|
|
38
|
+
}
|
|
39
|
+
export type ParsedColor = {
|
|
40
|
+
kind: 'resolved';
|
|
41
|
+
rgba: RGBA;
|
|
42
|
+
} | {
|
|
43
|
+
kind: 'verbatim';
|
|
44
|
+
raw: string;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Parse a computed-style color string to sRGB, or refuse honestly.
|
|
48
|
+
*
|
|
49
|
+
* Every space `docs/notes/03-color.md` §3 calls out as a real capture
|
|
50
|
+
* problem -- `oklch`, `oklab`, `lab`, `lch`, `color(srgb ...)`,
|
|
51
|
+
* `color(srgb-linear ...)`, `color(display-p3 ...)` -- converts. Anything
|
|
52
|
+
* else (a future space, a syntax this parser does not recognise) returns the
|
|
53
|
+
* raw string verbatim rather than guessing: rule 4 of the design note's §5
|
|
54
|
+
* applies to color parsing exactly as it does to the ratio itself.
|
|
55
|
+
*/
|
|
56
|
+
export declare function parseCssColor(raw: string): ParsedColor;
|
|
57
|
+
/** `#rrggbb`, or `#rrggbbaa` when `alpha` is given and < 1. */
|
|
58
|
+
export declare function toHex(rgb: {
|
|
59
|
+
r: number;
|
|
60
|
+
g: number;
|
|
61
|
+
b: number;
|
|
62
|
+
}, alpha?: number): string;
|
|
63
|
+
/** WCAG 2.x relative luminance. The name of a formula, never a threshold. */
|
|
64
|
+
export declare function relativeLuminance(rgb: {
|
|
65
|
+
r: number;
|
|
66
|
+
g: number;
|
|
67
|
+
b: number;
|
|
68
|
+
}): number;
|
|
69
|
+
/** WCAG 2.x contrast ratio between two opaque sRGB colors. */
|
|
70
|
+
export declare function contrastRatio(a: {
|
|
71
|
+
r: number;
|
|
72
|
+
g: number;
|
|
73
|
+
b: number;
|
|
74
|
+
}, b: {
|
|
75
|
+
r: number;
|
|
76
|
+
g: number;
|
|
77
|
+
b: number;
|
|
78
|
+
}): number;
|
|
79
|
+
export type Backdrop = {
|
|
80
|
+
kind: 'opaque';
|
|
81
|
+
color: string;
|
|
82
|
+
via: string;
|
|
83
|
+
self: boolean;
|
|
84
|
+
layers: number;
|
|
85
|
+
} | {
|
|
86
|
+
kind: 'gradient';
|
|
87
|
+
from: string;
|
|
88
|
+
to: string;
|
|
89
|
+
via: string;
|
|
90
|
+
} | {
|
|
91
|
+
kind: 'image';
|
|
92
|
+
via: string;
|
|
93
|
+
coverage: ImageCoverage;
|
|
94
|
+
} | {
|
|
95
|
+
kind: 'overlay';
|
|
96
|
+
reason: 'under' | 'behind';
|
|
97
|
+
via: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* `via`'s own `backgroundImage` is `null` -- this specific capture never
|
|
101
|
+
* measured it (A3; see `ElementRecord.backgroundImage`'s doc comment and
|
|
102
|
+
* `bundle.ts#normalizeElement`). Compositing cannot honestly continue past
|
|
103
|
+
* an element whose background-image is unknown: it might be a gradient or
|
|
104
|
+
* a photo sitting behind the text, and there is no way to tell from here.
|
|
105
|
+
* Refuse to guess, the same discipline `detectOverlay` already applies to
|
|
106
|
+
* a missing `hitStackIds`/`paintOrder`.
|
|
107
|
+
*/
|
|
108
|
+
| {
|
|
109
|
+
kind: 'not-captured';
|
|
110
|
+
via: string;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The ancestor walk ran off the top of `<body>` with nothing left to
|
|
114
|
+
* check, and either no `DocumentBackground` was supplied at all (a caller
|
|
115
|
+
* that has not been updated to pass one -- see this module's header) or
|
|
116
|
+
* the one supplied cannot honestly resolve to a single color (an
|
|
117
|
+
* unconvertible `background-color`, or a `color-scheme` that lists both
|
|
118
|
+
* `light` and `dark` with no way to know which the reader's browser would
|
|
119
|
+
* pick). See `canvasBackdrop` and A2.
|
|
120
|
+
*/
|
|
121
|
+
| {
|
|
122
|
+
kind: 'canvas';
|
|
123
|
+
};
|
|
124
|
+
/** `<html>`'s own background-color/color-scheme -- see `DocumentBackground`
|
|
125
|
+
* in `snapshot/types.ts`, and this module's header for why a compositing
|
|
126
|
+
* walk needs it. Re-exported under this name here because `resolveBackdrop`
|
|
127
|
+
* et al. take it as a plain, optional parameter (never a required one: every
|
|
128
|
+
* existing caller that does not yet pass one -- see A2's routing note --
|
|
129
|
+
* must keep compiling and must keep refusing to guess, never regress to
|
|
130
|
+
* assuming white). */
|
|
131
|
+
export type CanvasBackground = DocumentBackground;
|
|
132
|
+
/**
|
|
133
|
+
* The `image` backdrop's weakest signal, strengthened: a real-page soak
|
|
134
|
+
* found `unmeasured (image <id>)` printing identically for a 12x12 decorative
|
|
135
|
+
* sort-arrow in one corner of a table header and a 1440x600 full-bleed hero
|
|
136
|
+
* photo -- an agent cannot tell "irrelevant corner decoration" from
|
|
137
|
+
* "genuinely unmeasurable backdrop" from the token alone, and would spend a
|
|
138
|
+
* screenshot round-trip finding out something already on the record.
|
|
139
|
+
*
|
|
140
|
+
* `box` is the rendered size of the element that actually carries the
|
|
141
|
+
* `background-image` (already captured, no new measurement); `of` is the
|
|
142
|
+
* text element's own box, so `image e_th (12x12 of 180x40)` and
|
|
143
|
+
* `image e_hero (1440x600 of 1440x600)` read as the different sentences they
|
|
144
|
+
* are. This is a size proxy, not the image's true painted footprint --
|
|
145
|
+
* `background-size`/`background-position` can still make a small box show a
|
|
146
|
+
* cropped, dominant image, or a large box show a small tiled icon -- so it
|
|
147
|
+
* narrows the question rather than closing it, honestly.
|
|
148
|
+
*/
|
|
149
|
+
export interface ImageCoverage {
|
|
150
|
+
boxWidth: number;
|
|
151
|
+
boxHeight: number;
|
|
152
|
+
ofWidth: number;
|
|
153
|
+
ofHeight: number;
|
|
154
|
+
}
|
|
155
|
+
/** One layer the ancestor walk actually visited: its own color and alpha, and
|
|
156
|
+
* the element it came from. Diagnostic detail for `q color <id>`'s long form
|
|
157
|
+
* ("the full composite chain") -- not needed by the grammar line itself,
|
|
158
|
+
* which only ever shows the final result plus a layer *count*. */
|
|
159
|
+
export interface BackdropLayer {
|
|
160
|
+
id: string;
|
|
161
|
+
color: string;
|
|
162
|
+
alpha: number;
|
|
163
|
+
}
|
|
164
|
+
export interface ResolvedBackdrop {
|
|
165
|
+
backdrop: Backdrop;
|
|
166
|
+
/** The final composited, opaque sRGB color -- `null` whenever `backdrop`
|
|
167
|
+
* is not a single resolved color (gradient, image, overlay, not-captured,
|
|
168
|
+
* canvas) or its terminating layer's own color could not be converted. */
|
|
169
|
+
rgb: {
|
|
170
|
+
r: number;
|
|
171
|
+
g: number;
|
|
172
|
+
b: number;
|
|
173
|
+
} | null;
|
|
174
|
+
/** Every layer visited, from `el` itself outward, including the
|
|
175
|
+
* terminating one (opaque, canvas, or the unresolvable/gradient/image/
|
|
176
|
+
* not-captured layer that ended the walk). */
|
|
177
|
+
chain: BackdropLayer[];
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Walk up from `el` accumulating translucent `background-color` layers
|
|
181
|
+
* *and* CSS `opacity` until something fully opaque is reached, then report
|
|
182
|
+
* the composited result -- an incremental left-to-right fold rather than
|
|
183
|
+
* "collect layers, then composite once at the end", specifically so
|
|
184
|
+
* `opacity` folds in correctly (A5): unlike a plain `background-color`
|
|
185
|
+
* alpha, `opacity` scales *everything already composited so far* as one
|
|
186
|
+
* group at the exact point it is applied, so each ancestor's own
|
|
187
|
+
* contribution has to be resolved against what is beneath it before the
|
|
188
|
+
* next ancestor's `opacity` scales the combined result again. The pure
|
|
189
|
+
* `background-color`-only case (no `opacity < 1` anywhere in the chain)
|
|
190
|
+
* reduces to exactly the old flat-stack composite -- *verified* exact
|
|
191
|
+
* against Chromium's own `blendedBackgroundColors` on an alpha chain
|
|
192
|
+
* (`test/browser/color-backdrop.test.ts`) -- and the opacity-aware general
|
|
193
|
+
* case is verified against real Chromium canvas pixel readback for both a
|
|
194
|
+
* single opacity boundary and nested multi-level opacity (see this
|
|
195
|
+
* module's header and `test/browser/color-backdrop.test.ts`'s A5 cases).
|
|
196
|
+
*
|
|
197
|
+
* Aborts to a `gradient`/`image`/`not-captured` backdrop the instant any
|
|
198
|
+
* layer's own `backgroundImage` says so (A3), and to `canvas` when the walk
|
|
199
|
+
* runs off the top of `<body>` with no way to know the real default fill
|
|
200
|
+
* (A2, see `canvasBackdrop`). A non-ancestor overlay is checked by the
|
|
201
|
+
* caller before this ever runs (see `colorFactFor`), since an overlay makes
|
|
202
|
+
* the ancestor chain irrelevant regardless of what it would have found.
|
|
203
|
+
*/
|
|
204
|
+
export declare function resolveBackdrop(el: ElementRecord, byId: Map<string, ElementRecord>, canvas?: CanvasBackground | null): ResolvedBackdrop;
|
|
205
|
+
/**
|
|
206
|
+
* A non-ancestor element painted `under` (in front of, obscuring the text
|
|
207
|
+
* itself) or `behind` (between the text and where the ancestor walk would
|
|
208
|
+
* have looked) this one -- the two `incompleteData` cases
|
|
209
|
+
* `blendedBackgroundColors` gets silently wrong (design note §3): a
|
|
210
|
+
* `rgba(255,0,0,0.8)` sibling scrim painted directly over text reads back as
|
|
211
|
+
* `rgb(255,255,255)`, the page background, as if the overlay were not there.
|
|
212
|
+
*
|
|
213
|
+
* Two independent input signals, one per capture path (design note §7):
|
|
214
|
+
* `el.hitStackIds` (the `walk` path's `elementsFromPoint` result, already
|
|
215
|
+
* ordered front-to-back) when present, else `paintOrder` plus rect
|
|
216
|
+
* intersection (the `cdp` path, Node-side, whole-rect rather than
|
|
217
|
+
* centre-point). Neither present -- a hand-built fixture, a bundle written
|
|
218
|
+
* before this capability existed -- reports no overlay rather than guessing:
|
|
219
|
+
* rule 4 of the design note's §5 (refuse rather than fabricate) applies here
|
|
220
|
+
* exactly as it does to the ratio itself.
|
|
221
|
+
*/
|
|
222
|
+
export declare function detectOverlay(el: ElementRecord, byId: Map<string, ElementRecord>, elements: ElementRecord[]): {
|
|
223
|
+
reason: 'under' | 'behind';
|
|
224
|
+
via: string;
|
|
225
|
+
} | null;
|
|
226
|
+
export type RatioReason = 'gradient' | 'image' | 'under' | 'behind' | 'color-space' | 'not-captured' | 'canvas';
|
|
227
|
+
export interface ColorFact {
|
|
228
|
+
elementId: string;
|
|
229
|
+
/** The effective ink: computed `color`, composited at effective opacity,
|
|
230
|
+
* as lowercase hex (with an alpha suffix below full opacity) -- or the raw
|
|
231
|
+
* CSS string verbatim when the color space could not be converted. */
|
|
232
|
+
fg: string;
|
|
233
|
+
backdrop: Backdrop;
|
|
234
|
+
/** WCAG 2.x contrast ratio, or `null` when it cannot be honestly computed
|
|
235
|
+
* -- see `ratioReason`. Never a verdict. */
|
|
236
|
+
ratio: number | null;
|
|
237
|
+
ratioReason: RatioReason | null;
|
|
238
|
+
fontSizePx: number;
|
|
239
|
+
fontWeight: number;
|
|
240
|
+
/** The CSS `opacity` multiplier, present only when it is below 1 -- "so the
|
|
241
|
+
* reader can recover the raw value" (design note §4). */
|
|
242
|
+
effectiveOpacity: number | null;
|
|
243
|
+
shadowColor: string | null;
|
|
244
|
+
strokeWidthPx: number | null;
|
|
245
|
+
strokeColor: string | null;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* The color fact for one element, or `null` when the measurement gate does
|
|
249
|
+
* not apply: "every visible element with its own text" (design note §4) --
|
|
250
|
+
* no text, no ink-on-backdrop question to answer.
|
|
251
|
+
*
|
|
252
|
+
* A6 note: a closed `<sl-dialog>` (`opacity: 0` on an ancestor) still gets a
|
|
253
|
+
* fact here for each visible-by-its-own-styling descendant -- `el.visible`
|
|
254
|
+
* only reflects the element's *own* opacity/display/visibility/size, not an
|
|
255
|
+
* ancestor's. That fact's `ratio` genuinely does come out `1.00` (ink
|
|
256
|
+
* composited to exactly equal its own backdrop at zero *effective* opacity)
|
|
257
|
+
* -- mathematically correct, not fabricated, and never suppressed here:
|
|
258
|
+
* "facts, with counter-facts, never adjudicate" means this module does not
|
|
259
|
+
* get to decide the fact is uninteresting and drop it. `effectiveOpacity`
|
|
260
|
+
* below is the counter-fact that explains it (`opacity ×0`, kept on
|
|
261
|
+
* `ColorFact` and, via `ColorPair.effectiveOpacity`, on `q color`'s grouped
|
|
262
|
+
* rows too -- see that field's doc comment) so a reader sees *why* the
|
|
263
|
+
* ratio is round, rather than the fact disappearing and the explanation
|
|
264
|
+
* disappearing with it.
|
|
265
|
+
*/
|
|
266
|
+
export declare function colorFactFor(el: ElementRecord, byId: Map<string, ElementRecord>, elements: ElementRecord[], canvas?: CanvasBackground | null): ColorFact | null;
|
|
267
|
+
/** Every color fact the capture supports, in document order -- the gate from
|
|
268
|
+
* `colorFactFor` applied once per element. */
|
|
269
|
+
export declare function colorFactsForAll(elements: ElementRecord[], canvas?: CanvasBackground | null): ColorFact[];
|
|
270
|
+
/** Just the `<bg>` half of the grammar -- reused by `q color`'s pair table
|
|
271
|
+
* and the digest header's `lowest` clause, which both need it without the
|
|
272
|
+
* rest of the line. */
|
|
273
|
+
export declare function backdropLabel(backdrop: Backdrop): string;
|
|
274
|
+
/**
|
|
275
|
+
* `color <fg> on <bg>; ratio <r>; <size>px/<weight>[; opacity ×<o>][; shadow
|
|
276
|
+
* <color>][; stroke <w>px <color>]` -- `docs/notes/03-color.md` §4, exactly.
|
|
277
|
+
*/
|
|
278
|
+
export declare function renderColorFact(fact: ColorFact): string;
|
|
279
|
+
export interface ColorPair {
|
|
280
|
+
fg: string;
|
|
281
|
+
bgLabel: string;
|
|
282
|
+
ratio: number | null;
|
|
283
|
+
ratioReason: RatioReason | null;
|
|
284
|
+
elementIds: string[];
|
|
285
|
+
fontSizesPx: number[];
|
|
286
|
+
weights: number[];
|
|
287
|
+
/**
|
|
288
|
+
* The CSS `opacity` multiplier, present only when it is below 1 -- same
|
|
289
|
+
* meaning and same "present only below 1" convention as
|
|
290
|
+
* `ColorFact.effectiveOpacity`, taken from the first fact in the group
|
|
291
|
+
* exactly like `ratio`/`ratioReason` already are (elements sharing one
|
|
292
|
+
* (ink, backdrop) pair by construction share the composited result, and
|
|
293
|
+
* in the overwhelming common case share the same ancestor chain too).
|
|
294
|
+
*
|
|
295
|
+
* Added for A6: `q color`'s pair table used to have no way to carry this
|
|
296
|
+
* at all, so even a caller that *wanted* to show `opacity ×0` (or `×0.5`)
|
|
297
|
+
* next to a row -- the fact that explains a suspiciously round ratio like
|
|
298
|
+
* `1.00` on an ancestor-hidden element -- had nothing to read it from off
|
|
299
|
+
* a `ColorPair` directly. `query.ts`'s own fix for the same gap
|
|
300
|
+
* (`pairOpacities`) instead recovers the full, deduped set of a pair's
|
|
301
|
+
* member opacities from the ungrouped facts by `elementIds`, which this
|
|
302
|
+
* single first-fact value cannot replace when a pair's members disagree
|
|
303
|
+
* (rare, but possible) -- both are kept: this one for a caller that only
|
|
304
|
+
* needs "does this pair carry a reduced-opacity member" cheaply, without
|
|
305
|
+
* re-deriving a `factsById` map of its own.
|
|
306
|
+
*/
|
|
307
|
+
effectiveOpacity: number | null;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* One row per (ink, backdrop) pair, not per element -- "thousands of
|
|
311
|
+
* elements compress to dozens of rows" (design note §6). `fg` and the
|
|
312
|
+
* rendered `<bg>` form together are the grouping key: two elements with the
|
|
313
|
+
* same ink over the same backdrop are, by construction, the same measured
|
|
314
|
+
* ratio.
|
|
315
|
+
*/
|
|
316
|
+
export declare function groupColorFacts(facts: ColorFact[]): ColorPair[];
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color, composited and disclosed -- never adjudicated.
|
|
3
|
+
*
|
|
4
|
+
* The design of record is `docs/notes/03-color.md`; this file is stage 1 of
|
|
5
|
+
* it. Three things carry over from that note verbatim, because they are the
|
|
6
|
+
* whole shape of the module:
|
|
7
|
+
*
|
|
8
|
+
* 1. **The backdrop composite runs Node-side, over `ElementRecord[]`, exactly
|
|
9
|
+
* like `clip.ts`.** Both capture paths already produce the raw fields this
|
|
10
|
+
* needs (`backgroundColor`, `backgroundImage`, `opacity`, the overlap
|
|
11
|
+
* inputs); nothing here touches a page or a browser, so both paths agree
|
|
12
|
+
* on every derived color fact by construction.
|
|
13
|
+
* 2. **`ratio` is a number or it is `unmeasured (<reason>)`. Never a verdict.**
|
|
14
|
+
* No output token here ever says `pass`, `fail`, `AA`, `AAA`, `low`, or
|
|
15
|
+
* `poor`. §5 of the design note draws this line in four rules; this module
|
|
16
|
+
* is the code that holds it.
|
|
17
|
+
* 3. **A gradient, an image, or a non-ancestor overlay makes "the background
|
|
18
|
+
* color" not exist**, and printing a number anyway is fabrication, not
|
|
19
|
+
* suppression-avoidance (design note §5's rule 4). `blendedBackgroundColors`
|
|
20
|
+
* -- CDP's own composite -- gets this wrong on exactly these two shapes
|
|
21
|
+
* (verified in the design note's probes, and again in
|
|
22
|
+
* `test/browser/color-backdrop.test.ts`), which is why it is a test oracle
|
|
23
|
+
* for the alpha-chain case it gets right and never a source at runtime.
|
|
24
|
+
*
|
|
25
|
+
* The color-space conversions (`oklch`/`oklab`/`lab`/`lch`/`color(srgb*
|
|
26
|
+
* )`/`color(display-p3 ...)`) are verified against real Chromium canvas
|
|
27
|
+
* readback, not derived from memory of the spec math alone --
|
|
28
|
+
* `test/browser/color-space-conversion.test.ts` paints each one and diffs
|
|
29
|
+
* channels, per the design note's own §10 experiment 4.
|
|
30
|
+
*/
|
|
31
|
+
import type { DocumentBackground, ElementRecord } from "./types.js";
|
|
32
|
+
/** 0-255 per channel, alpha 0-1. */
|
|
33
|
+
export interface RGBA {
|
|
34
|
+
r: number;
|
|
35
|
+
g: number;
|
|
36
|
+
b: number;
|
|
37
|
+
a: number;
|
|
38
|
+
}
|
|
39
|
+
export type ParsedColor = {
|
|
40
|
+
kind: 'resolved';
|
|
41
|
+
rgba: RGBA;
|
|
42
|
+
} | {
|
|
43
|
+
kind: 'verbatim';
|
|
44
|
+
raw: string;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Parse a computed-style color string to sRGB, or refuse honestly.
|
|
48
|
+
*
|
|
49
|
+
* Every space `docs/notes/03-color.md` §3 calls out as a real capture
|
|
50
|
+
* problem -- `oklch`, `oklab`, `lab`, `lch`, `color(srgb ...)`,
|
|
51
|
+
* `color(srgb-linear ...)`, `color(display-p3 ...)` -- converts. Anything
|
|
52
|
+
* else (a future space, a syntax this parser does not recognise) returns the
|
|
53
|
+
* raw string verbatim rather than guessing: rule 4 of the design note's §5
|
|
54
|
+
* applies to color parsing exactly as it does to the ratio itself.
|
|
55
|
+
*/
|
|
56
|
+
export declare function parseCssColor(raw: string): ParsedColor;
|
|
57
|
+
/** `#rrggbb`, or `#rrggbbaa` when `alpha` is given and < 1. */
|
|
58
|
+
export declare function toHex(rgb: {
|
|
59
|
+
r: number;
|
|
60
|
+
g: number;
|
|
61
|
+
b: number;
|
|
62
|
+
}, alpha?: number): string;
|
|
63
|
+
/** WCAG 2.x relative luminance. The name of a formula, never a threshold. */
|
|
64
|
+
export declare function relativeLuminance(rgb: {
|
|
65
|
+
r: number;
|
|
66
|
+
g: number;
|
|
67
|
+
b: number;
|
|
68
|
+
}): number;
|
|
69
|
+
/** WCAG 2.x contrast ratio between two opaque sRGB colors. */
|
|
70
|
+
export declare function contrastRatio(a: {
|
|
71
|
+
r: number;
|
|
72
|
+
g: number;
|
|
73
|
+
b: number;
|
|
74
|
+
}, b: {
|
|
75
|
+
r: number;
|
|
76
|
+
g: number;
|
|
77
|
+
b: number;
|
|
78
|
+
}): number;
|
|
79
|
+
export type Backdrop = {
|
|
80
|
+
kind: 'opaque';
|
|
81
|
+
color: string;
|
|
82
|
+
via: string;
|
|
83
|
+
self: boolean;
|
|
84
|
+
layers: number;
|
|
85
|
+
} | {
|
|
86
|
+
kind: 'gradient';
|
|
87
|
+
from: string;
|
|
88
|
+
to: string;
|
|
89
|
+
via: string;
|
|
90
|
+
} | {
|
|
91
|
+
kind: 'image';
|
|
92
|
+
via: string;
|
|
93
|
+
coverage: ImageCoverage;
|
|
94
|
+
} | {
|
|
95
|
+
kind: 'overlay';
|
|
96
|
+
reason: 'under' | 'behind';
|
|
97
|
+
via: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* `via`'s own `backgroundImage` is `null` -- this specific capture never
|
|
101
|
+
* measured it (A3; see `ElementRecord.backgroundImage`'s doc comment and
|
|
102
|
+
* `bundle.ts#normalizeElement`). Compositing cannot honestly continue past
|
|
103
|
+
* an element whose background-image is unknown: it might be a gradient or
|
|
104
|
+
* a photo sitting behind the text, and there is no way to tell from here.
|
|
105
|
+
* Refuse to guess, the same discipline `detectOverlay` already applies to
|
|
106
|
+
* a missing `hitStackIds`/`paintOrder`.
|
|
107
|
+
*/
|
|
108
|
+
| {
|
|
109
|
+
kind: 'not-captured';
|
|
110
|
+
via: string;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The ancestor walk ran off the top of `<body>` with nothing left to
|
|
114
|
+
* check, and either no `DocumentBackground` was supplied at all (a caller
|
|
115
|
+
* that has not been updated to pass one -- see this module's header) or
|
|
116
|
+
* the one supplied cannot honestly resolve to a single color (an
|
|
117
|
+
* unconvertible `background-color`, or a `color-scheme` that lists both
|
|
118
|
+
* `light` and `dark` with no way to know which the reader's browser would
|
|
119
|
+
* pick). See `canvasBackdrop` and A2.
|
|
120
|
+
*/
|
|
121
|
+
| {
|
|
122
|
+
kind: 'canvas';
|
|
123
|
+
};
|
|
124
|
+
/** `<html>`'s own background-color/color-scheme -- see `DocumentBackground`
|
|
125
|
+
* in `snapshot/types.ts`, and this module's header for why a compositing
|
|
126
|
+
* walk needs it. Re-exported under this name here because `resolveBackdrop`
|
|
127
|
+
* et al. take it as a plain, optional parameter (never a required one: every
|
|
128
|
+
* existing caller that does not yet pass one -- see A2's routing note --
|
|
129
|
+
* must keep compiling and must keep refusing to guess, never regress to
|
|
130
|
+
* assuming white). */
|
|
131
|
+
export type CanvasBackground = DocumentBackground;
|
|
132
|
+
/**
|
|
133
|
+
* The `image` backdrop's weakest signal, strengthened: a real-page soak
|
|
134
|
+
* found `unmeasured (image <id>)` printing identically for a 12x12 decorative
|
|
135
|
+
* sort-arrow in one corner of a table header and a 1440x600 full-bleed hero
|
|
136
|
+
* photo -- an agent cannot tell "irrelevant corner decoration" from
|
|
137
|
+
* "genuinely unmeasurable backdrop" from the token alone, and would spend a
|
|
138
|
+
* screenshot round-trip finding out something already on the record.
|
|
139
|
+
*
|
|
140
|
+
* `box` is the rendered size of the element that actually carries the
|
|
141
|
+
* `background-image` (already captured, no new measurement); `of` is the
|
|
142
|
+
* text element's own box, so `image e_th (12x12 of 180x40)` and
|
|
143
|
+
* `image e_hero (1440x600 of 1440x600)` read as the different sentences they
|
|
144
|
+
* are. This is a size proxy, not the image's true painted footprint --
|
|
145
|
+
* `background-size`/`background-position` can still make a small box show a
|
|
146
|
+
* cropped, dominant image, or a large box show a small tiled icon -- so it
|
|
147
|
+
* narrows the question rather than closing it, honestly.
|
|
148
|
+
*/
|
|
149
|
+
export interface ImageCoverage {
|
|
150
|
+
boxWidth: number;
|
|
151
|
+
boxHeight: number;
|
|
152
|
+
ofWidth: number;
|
|
153
|
+
ofHeight: number;
|
|
154
|
+
}
|
|
155
|
+
/** One layer the ancestor walk actually visited: its own color and alpha, and
|
|
156
|
+
* the element it came from. Diagnostic detail for `q color <id>`'s long form
|
|
157
|
+
* ("the full composite chain") -- not needed by the grammar line itself,
|
|
158
|
+
* which only ever shows the final result plus a layer *count*. */
|
|
159
|
+
export interface BackdropLayer {
|
|
160
|
+
id: string;
|
|
161
|
+
color: string;
|
|
162
|
+
alpha: number;
|
|
163
|
+
}
|
|
164
|
+
export interface ResolvedBackdrop {
|
|
165
|
+
backdrop: Backdrop;
|
|
166
|
+
/** The final composited, opaque sRGB color -- `null` whenever `backdrop`
|
|
167
|
+
* is not a single resolved color (gradient, image, overlay, not-captured,
|
|
168
|
+
* canvas) or its terminating layer's own color could not be converted. */
|
|
169
|
+
rgb: {
|
|
170
|
+
r: number;
|
|
171
|
+
g: number;
|
|
172
|
+
b: number;
|
|
173
|
+
} | null;
|
|
174
|
+
/** Every layer visited, from `el` itself outward, including the
|
|
175
|
+
* terminating one (opaque, canvas, or the unresolvable/gradient/image/
|
|
176
|
+
* not-captured layer that ended the walk). */
|
|
177
|
+
chain: BackdropLayer[];
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Walk up from `el` accumulating translucent `background-color` layers
|
|
181
|
+
* *and* CSS `opacity` until something fully opaque is reached, then report
|
|
182
|
+
* the composited result -- an incremental left-to-right fold rather than
|
|
183
|
+
* "collect layers, then composite once at the end", specifically so
|
|
184
|
+
* `opacity` folds in correctly (A5): unlike a plain `background-color`
|
|
185
|
+
* alpha, `opacity` scales *everything already composited so far* as one
|
|
186
|
+
* group at the exact point it is applied, so each ancestor's own
|
|
187
|
+
* contribution has to be resolved against what is beneath it before the
|
|
188
|
+
* next ancestor's `opacity` scales the combined result again. The pure
|
|
189
|
+
* `background-color`-only case (no `opacity < 1` anywhere in the chain)
|
|
190
|
+
* reduces to exactly the old flat-stack composite -- *verified* exact
|
|
191
|
+
* against Chromium's own `blendedBackgroundColors` on an alpha chain
|
|
192
|
+
* (`test/browser/color-backdrop.test.ts`) -- and the opacity-aware general
|
|
193
|
+
* case is verified against real Chromium canvas pixel readback for both a
|
|
194
|
+
* single opacity boundary and nested multi-level opacity (see this
|
|
195
|
+
* module's header and `test/browser/color-backdrop.test.ts`'s A5 cases).
|
|
196
|
+
*
|
|
197
|
+
* Aborts to a `gradient`/`image`/`not-captured` backdrop the instant any
|
|
198
|
+
* layer's own `backgroundImage` says so (A3), and to `canvas` when the walk
|
|
199
|
+
* runs off the top of `<body>` with no way to know the real default fill
|
|
200
|
+
* (A2, see `canvasBackdrop`). A non-ancestor overlay is checked by the
|
|
201
|
+
* caller before this ever runs (see `colorFactFor`), since an overlay makes
|
|
202
|
+
* the ancestor chain irrelevant regardless of what it would have found.
|
|
203
|
+
*/
|
|
204
|
+
export declare function resolveBackdrop(el: ElementRecord, byId: Map<string, ElementRecord>, canvas?: CanvasBackground | null): ResolvedBackdrop;
|
|
205
|
+
/**
|
|
206
|
+
* A non-ancestor element painted `under` (in front of, obscuring the text
|
|
207
|
+
* itself) or `behind` (between the text and where the ancestor walk would
|
|
208
|
+
* have looked) this one -- the two `incompleteData` cases
|
|
209
|
+
* `blendedBackgroundColors` gets silently wrong (design note §3): a
|
|
210
|
+
* `rgba(255,0,0,0.8)` sibling scrim painted directly over text reads back as
|
|
211
|
+
* `rgb(255,255,255)`, the page background, as if the overlay were not there.
|
|
212
|
+
*
|
|
213
|
+
* Two independent input signals, one per capture path (design note §7):
|
|
214
|
+
* `el.hitStackIds` (the `walk` path's `elementsFromPoint` result, already
|
|
215
|
+
* ordered front-to-back) when present, else `paintOrder` plus rect
|
|
216
|
+
* intersection (the `cdp` path, Node-side, whole-rect rather than
|
|
217
|
+
* centre-point). Neither present -- a hand-built fixture, a bundle written
|
|
218
|
+
* before this capability existed -- reports no overlay rather than guessing:
|
|
219
|
+
* rule 4 of the design note's §5 (refuse rather than fabricate) applies here
|
|
220
|
+
* exactly as it does to the ratio itself.
|
|
221
|
+
*/
|
|
222
|
+
export declare function detectOverlay(el: ElementRecord, byId: Map<string, ElementRecord>, elements: ElementRecord[]): {
|
|
223
|
+
reason: 'under' | 'behind';
|
|
224
|
+
via: string;
|
|
225
|
+
} | null;
|
|
226
|
+
export type RatioReason = 'gradient' | 'image' | 'under' | 'behind' | 'color-space' | 'not-captured' | 'canvas';
|
|
227
|
+
export interface ColorFact {
|
|
228
|
+
elementId: string;
|
|
229
|
+
/** The effective ink: computed `color`, composited at effective opacity,
|
|
230
|
+
* as lowercase hex (with an alpha suffix below full opacity) -- or the raw
|
|
231
|
+
* CSS string verbatim when the color space could not be converted. */
|
|
232
|
+
fg: string;
|
|
233
|
+
backdrop: Backdrop;
|
|
234
|
+
/** WCAG 2.x contrast ratio, or `null` when it cannot be honestly computed
|
|
235
|
+
* -- see `ratioReason`. Never a verdict. */
|
|
236
|
+
ratio: number | null;
|
|
237
|
+
ratioReason: RatioReason | null;
|
|
238
|
+
fontSizePx: number;
|
|
239
|
+
fontWeight: number;
|
|
240
|
+
/** The CSS `opacity` multiplier, present only when it is below 1 -- "so the
|
|
241
|
+
* reader can recover the raw value" (design note §4). */
|
|
242
|
+
effectiveOpacity: number | null;
|
|
243
|
+
shadowColor: string | null;
|
|
244
|
+
strokeWidthPx: number | null;
|
|
245
|
+
strokeColor: string | null;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* The color fact for one element, or `null` when the measurement gate does
|
|
249
|
+
* not apply: "every visible element with its own text" (design note §4) --
|
|
250
|
+
* no text, no ink-on-backdrop question to answer.
|
|
251
|
+
*
|
|
252
|
+
* A6 note: a closed `<sl-dialog>` (`opacity: 0` on an ancestor) still gets a
|
|
253
|
+
* fact here for each visible-by-its-own-styling descendant -- `el.visible`
|
|
254
|
+
* only reflects the element's *own* opacity/display/visibility/size, not an
|
|
255
|
+
* ancestor's. That fact's `ratio` genuinely does come out `1.00` (ink
|
|
256
|
+
* composited to exactly equal its own backdrop at zero *effective* opacity)
|
|
257
|
+
* -- mathematically correct, not fabricated, and never suppressed here:
|
|
258
|
+
* "facts, with counter-facts, never adjudicate" means this module does not
|
|
259
|
+
* get to decide the fact is uninteresting and drop it. `effectiveOpacity`
|
|
260
|
+
* below is the counter-fact that explains it (`opacity ×0`, kept on
|
|
261
|
+
* `ColorFact` and, via `ColorPair.effectiveOpacity`, on `q color`'s grouped
|
|
262
|
+
* rows too -- see that field's doc comment) so a reader sees *why* the
|
|
263
|
+
* ratio is round, rather than the fact disappearing and the explanation
|
|
264
|
+
* disappearing with it.
|
|
265
|
+
*/
|
|
266
|
+
export declare function colorFactFor(el: ElementRecord, byId: Map<string, ElementRecord>, elements: ElementRecord[], canvas?: CanvasBackground | null): ColorFact | null;
|
|
267
|
+
/** Every color fact the capture supports, in document order -- the gate from
|
|
268
|
+
* `colorFactFor` applied once per element. */
|
|
269
|
+
export declare function colorFactsForAll(elements: ElementRecord[], canvas?: CanvasBackground | null): ColorFact[];
|
|
270
|
+
/** Just the `<bg>` half of the grammar -- reused by `q color`'s pair table
|
|
271
|
+
* and the digest header's `lowest` clause, which both need it without the
|
|
272
|
+
* rest of the line. */
|
|
273
|
+
export declare function backdropLabel(backdrop: Backdrop): string;
|
|
274
|
+
/**
|
|
275
|
+
* `color <fg> on <bg>; ratio <r>; <size>px/<weight>[; opacity ×<o>][; shadow
|
|
276
|
+
* <color>][; stroke <w>px <color>]` -- `docs/notes/03-color.md` §4, exactly.
|
|
277
|
+
*/
|
|
278
|
+
export declare function renderColorFact(fact: ColorFact): string;
|
|
279
|
+
export interface ColorPair {
|
|
280
|
+
fg: string;
|
|
281
|
+
bgLabel: string;
|
|
282
|
+
ratio: number | null;
|
|
283
|
+
ratioReason: RatioReason | null;
|
|
284
|
+
elementIds: string[];
|
|
285
|
+
fontSizesPx: number[];
|
|
286
|
+
weights: number[];
|
|
287
|
+
/**
|
|
288
|
+
* The CSS `opacity` multiplier, present only when it is below 1 -- same
|
|
289
|
+
* meaning and same "present only below 1" convention as
|
|
290
|
+
* `ColorFact.effectiveOpacity`, taken from the first fact in the group
|
|
291
|
+
* exactly like `ratio`/`ratioReason` already are (elements sharing one
|
|
292
|
+
* (ink, backdrop) pair by construction share the composited result, and
|
|
293
|
+
* in the overwhelming common case share the same ancestor chain too).
|
|
294
|
+
*
|
|
295
|
+
* Added for A6: `q color`'s pair table used to have no way to carry this
|
|
296
|
+
* at all, so even a caller that *wanted* to show `opacity ×0` (or `×0.5`)
|
|
297
|
+
* next to a row -- the fact that explains a suspiciously round ratio like
|
|
298
|
+
* `1.00` on an ancestor-hidden element -- had nothing to read it from off
|
|
299
|
+
* a `ColorPair` directly. `query.ts`'s own fix for the same gap
|
|
300
|
+
* (`pairOpacities`) instead recovers the full, deduped set of a pair's
|
|
301
|
+
* member opacities from the ungrouped facts by `elementIds`, which this
|
|
302
|
+
* single first-fact value cannot replace when a pair's members disagree
|
|
303
|
+
* (rare, but possible) -- both are kept: this one for a caller that only
|
|
304
|
+
* needs "does this pair carry a reduced-opacity member" cheaply, without
|
|
305
|
+
* re-deriving a `factsById` map of its own.
|
|
306
|
+
*/
|
|
307
|
+
effectiveOpacity: number | null;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* One row per (ink, backdrop) pair, not per element -- "thousands of
|
|
311
|
+
* elements compress to dozens of rows" (design note §6). `fg` and the
|
|
312
|
+
* rendered `<bg>` form together are the grouping key: two elements with the
|
|
313
|
+
* same ink over the same backdrop are, by construction, the same measured
|
|
314
|
+
* ratio.
|
|
315
|
+
*/
|
|
316
|
+
export declare function groupColorFacts(facts: ColorFact[]): ColorPair[];
|