@stroke-stabilizer/core 0.2.15 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -3
- package/dist/StabilizedPointer.d.ts +1 -1
- package/dist/filters/DouglasPeuckerFilter.d.ts +37 -0
- package/dist/filters/DouglasPeuckerFilter.d.ts.map +1 -0
- package/dist/filters/EmaFilter.d.ts.map +1 -1
- package/dist/filters/KalmanFilter.d.ts.map +1 -1
- package/dist/filters/LinearPredictionFilter.d.ts.map +1 -1
- package/dist/filters/MovingAverageFilter.d.ts.map +1 -1
- package/dist/filters/OneEuroFilter.d.ts.map +1 -1
- package/dist/filters/StringFilter.d.ts.map +1 -1
- package/dist/filters/index.d.ts +2 -0
- package/dist/filters/index.d.ts.map +1 -1
- package/dist/index.cjs +578 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +578 -14
- package/dist/index.js.map +1 -1
- package/dist/prediction.d.ts +102 -0
- package/dist/prediction.d.ts.map +1 -0
- package/dist/spline.d.ts +53 -0
- package/dist/spline.d.ts.map +1 -0
- package/dist/svg.d.ts +79 -0
- package/dist/svg.d.ts.map +1 -0
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @stroke-stabilizer/core
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@stroke-stabilizer/core)
|
|
4
|
+
[](https://github.com/usapopopooon/stroke-stabilizer/actions/workflows/ci.yml)
|
|
5
|
+
[](https://github.com/usapopopooon/stroke-stabilizer)
|
|
4
6
|
|
|
5
7
|
[日本語](./docs/README.ja.md)
|
|
6
8
|
|
|
@@ -14,11 +16,15 @@ Reduce hand tremor and smooth pen/mouse input in real-time using a flexible filt
|
|
|
14
16
|
|
|
15
17
|
## Features
|
|
16
18
|
|
|
17
|
-
- **[
|
|
19
|
+
- **[Mutable Pipeline Pattern](https://medium.com/@usapopopooon/the-mutable-pipeline-pattern-when-method-chains-need-to-change-at-runtime-8e835829de07)** - Add, remove, and update filters at runtime without rebuilding
|
|
18
20
|
- **Two-layer Processing** - Real-time filters + post-processing convolution
|
|
19
21
|
- **Automatic Endpoint Correction** - Strokes end at the actual input point
|
|
20
22
|
- **rAF Batch Processing** - Coalesce high-frequency pointer events into animation frames
|
|
21
23
|
- **8 Built-in Filters** - From simple moving average to adaptive One Euro Filter
|
|
24
|
+
- **Douglas-Peucker Simplification** - Reduce point count while preserving shape
|
|
25
|
+
- **SVG Path Output** - Convert strokes to SVG path data
|
|
26
|
+
- **Stroke Prediction** - Reduce perceived latency with velocity-based prediction
|
|
27
|
+
- **Catmull-Rom Interpolation** - Generate smooth curves between points
|
|
22
28
|
- **Edge-preserving Smoothing** - Bilateral kernel for sharp corner preservation
|
|
23
29
|
- **TypeScript First** - Full type safety with exported types
|
|
24
30
|
- **Zero Dependencies** - Pure JavaScript, works anywhere
|
|
@@ -120,6 +126,7 @@ Without `getCoalescedEvents()`, fast strokes will appear jagged regardless of fi
|
|
|
120
126
|
| `stringFilter` | Lazy Brush algorithm | Delayed, smooth strokes |
|
|
121
127
|
| `oneEuroFilter` | Adaptive lowpass filter | Best balance of smoothness/latency |
|
|
122
128
|
| `linearPredictionFilter` | Predicts next position | Lag compensation |
|
|
129
|
+
| `douglasPeuckerFilter` | Simplifies point sequences | Reduce data size |
|
|
123
130
|
|
|
124
131
|
### Post-processing Kernels
|
|
125
132
|
|
|
@@ -390,6 +397,103 @@ bilateralKernel({
|
|
|
390
397
|
})
|
|
391
398
|
```
|
|
392
399
|
|
|
400
|
+
### Douglas-Peucker Simplification
|
|
401
|
+
|
|
402
|
+
Reduce the number of points while preserving the shape of the stroke.
|
|
403
|
+
|
|
404
|
+
```ts
|
|
405
|
+
import { douglasPeuckerFilter, simplify } from '@stroke-stabilizer/core'
|
|
406
|
+
|
|
407
|
+
// As a filter in the pipeline
|
|
408
|
+
const pointer = new StabilizedPointer().addFilter(
|
|
409
|
+
douglasPeuckerFilter({ epsilon: 2 })
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
// As a standalone function
|
|
413
|
+
const simplified = simplify(points, 2) // epsilon = 2px tolerance
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### SVG Path Output
|
|
417
|
+
|
|
418
|
+
Convert processed strokes to SVG path data for rendering or export.
|
|
419
|
+
|
|
420
|
+
```ts
|
|
421
|
+
import {
|
|
422
|
+
toSVGPath,
|
|
423
|
+
toSVGPathSmooth,
|
|
424
|
+
toSVGPathCubic,
|
|
425
|
+
} from '@stroke-stabilizer/core'
|
|
426
|
+
|
|
427
|
+
const points = pointer.finish()
|
|
428
|
+
|
|
429
|
+
// Simple polyline (M/L commands)
|
|
430
|
+
const pathData = toSVGPath(points)
|
|
431
|
+
// "M 10.00 20.00 L 30.00 40.00 L 50.00 60.00"
|
|
432
|
+
|
|
433
|
+
// Quadratic Bezier curves (smoother)
|
|
434
|
+
const smoothPath = toSVGPathSmooth(points, { tension: 0.5 })
|
|
435
|
+
|
|
436
|
+
// Cubic Bezier curves (smoothest)
|
|
437
|
+
const cubicPath = toSVGPathCubic(points, { smoothing: 0.25 })
|
|
438
|
+
|
|
439
|
+
// Use in SVG
|
|
440
|
+
svgElement.innerHTML = `<path d="${pathData}" stroke="black" fill="none"/>`
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Stroke Prediction
|
|
444
|
+
|
|
445
|
+
Reduce perceived latency by predicting the next pen position based on velocity.
|
|
446
|
+
|
|
447
|
+
```ts
|
|
448
|
+
import { StrokePredictor } from '@stroke-stabilizer/core'
|
|
449
|
+
|
|
450
|
+
const predictor = new StrokePredictor({
|
|
451
|
+
historySize: 4, // Points used for velocity estimation
|
|
452
|
+
maxPredictionMs: 50, // Maximum prediction time
|
|
453
|
+
minVelocity: 0.1, // Minimum velocity to trigger prediction
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
canvas.addEventListener('pointermove', (e) => {
|
|
457
|
+
const stabilized = pointer.process({
|
|
458
|
+
x: e.offsetX,
|
|
459
|
+
y: e.offsetY,
|
|
460
|
+
timestamp: e.timeStamp,
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
if (stabilized) {
|
|
464
|
+
predictor.addPoint(stabilized)
|
|
465
|
+
|
|
466
|
+
// Get predicted point 16ms ahead
|
|
467
|
+
const predicted = predictor.predict(16)
|
|
468
|
+
if (predicted) {
|
|
469
|
+
drawPreview(predicted.x, predicted.y)
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
})
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Catmull-Rom Spline Interpolation
|
|
476
|
+
|
|
477
|
+
Generate smooth curves through a series of points, useful for upsampling or rendering.
|
|
478
|
+
|
|
479
|
+
```ts
|
|
480
|
+
import {
|
|
481
|
+
interpolateCatmullRom,
|
|
482
|
+
resampleByArcLength,
|
|
483
|
+
} from '@stroke-stabilizer/core'
|
|
484
|
+
|
|
485
|
+
const points = pointer.finish()
|
|
486
|
+
|
|
487
|
+
// Interpolate with Catmull-Rom spline
|
|
488
|
+
const smooth = interpolateCatmullRom(points, {
|
|
489
|
+
tension: 0.5, // 0=loose, 1=tight
|
|
490
|
+
segmentDivisions: 10, // Points per segment
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
// Resample at uniform arc length intervals
|
|
494
|
+
const uniform = resampleByArcLength(points, 5) // 5px between points
|
|
495
|
+
```
|
|
496
|
+
|
|
393
497
|
## API Reference
|
|
394
498
|
|
|
395
499
|
### StabilizedPointer
|
|
@@ -435,8 +539,10 @@ interface Point {
|
|
|
435
539
|
}
|
|
436
540
|
|
|
437
541
|
interface PointerPoint extends Point {
|
|
438
|
-
pressure?: number
|
|
439
|
-
|
|
542
|
+
pressure?: number // Pen pressure (0-1)
|
|
543
|
+
tiltX?: number // Pen tilt on X axis (-90 to 90 degrees)
|
|
544
|
+
tiltY?: number // Pen tilt on Y axis (-90 to 90 degrees)
|
|
545
|
+
timestamp: number // Event timestamp in ms
|
|
440
546
|
}
|
|
441
547
|
|
|
442
548
|
type PaddingMode = 'reflect' | 'edge' | 'zero'
|
|
@@ -21,7 +21,7 @@ export interface StabilizedPointerOptions {
|
|
|
21
21
|
appendEndpoint?: boolean;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
24
|
+
* Mutable Pipeline Pattern implementation
|
|
25
25
|
*
|
|
26
26
|
* A pipeline that allows adding, removing, and updating filters at runtime.
|
|
27
27
|
* Always ready to execute without requiring a .build() call.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Filter, PointerPoint } from '../types';
|
|
2
|
+
export interface DouglasPeuckerFilterParams {
|
|
3
|
+
/** Distance threshold (px). Points with perpendicular distance less than this are removed */
|
|
4
|
+
epsilon: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Create a Douglas-Peucker simplification filter
|
|
8
|
+
*
|
|
9
|
+
* This filter reduces the number of points while preserving the overall shape
|
|
10
|
+
* of the stroke. Useful for reducing data size or smoothing jaggy lines.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const pointer = new StabilizedPointer()
|
|
15
|
+
* .addFilter(douglasPeuckerFilter({ epsilon: 2 }))
|
|
16
|
+
*
|
|
17
|
+
* // Process points
|
|
18
|
+
* points.forEach(p => pointer.process(p))
|
|
19
|
+
*
|
|
20
|
+
* // Get simplified result
|
|
21
|
+
* const filter = pointer.getFilter('douglasPeucker') as ReturnType<typeof douglasPeuckerFilter>
|
|
22
|
+
* const simplified = filter.getSimplified()
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function douglasPeuckerFilter(params: DouglasPeuckerFilterParams): Filter;
|
|
26
|
+
/**
|
|
27
|
+
* Simplify an array of points using the Douglas-Peucker algorithm
|
|
28
|
+
*
|
|
29
|
+
* Standalone function for one-shot simplification without using a filter.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* const simplified = simplify(points, 2)
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function simplify(points: PointerPoint[], epsilon: number): PointerPoint[];
|
|
37
|
+
//# sourceMappingURL=DouglasPeuckerFilter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DouglasPeuckerFilter.d.ts","sourceRoot":"","sources":["../../src/filters/DouglasPeuckerFilter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAmB,MAAM,UAAU,CAAA;AAErE,MAAM,WAAW,0BAA0B;IACzC,6FAA6F;IAC7F,OAAO,EAAE,MAAM,CAAA;CAChB;AA2HD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,0BAA0B,GACjC,MAAM,CAER;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CACtB,MAAM,EAAE,YAAY,EAAE,EACtB,OAAO,EAAE,MAAM,GACd,YAAY,EAAE,CAKhB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmaFilter.d.ts","sourceRoot":"","sources":["../../src/filters/EmaFilter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAiC,MAAM,UAAU,CAAA;AAErE,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAA;CACd;
|
|
1
|
+
{"version":3,"file":"EmaFilter.d.ts","sourceRoot":"","sources":["../../src/filters/EmaFilter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAiC,MAAM,UAAU,CAAA;AAErE,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAA;CACd;AA+ED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAEzD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KalmanFilter.d.ts","sourceRoot":"","sources":["../../src/filters/KalmanFilter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAiC,MAAM,UAAU,CAAA;AAErE,MAAM,WAAW,kBAAkB;IACjC,4DAA4D;IAC5D,YAAY,EAAE,MAAM,CAAA;IACpB,wEAAwE;IACxE,gBAAgB,EAAE,MAAM,CAAA;CACzB;
|
|
1
|
+
{"version":3,"file":"KalmanFilter.d.ts","sourceRoot":"","sources":["../../src/filters/KalmanFilter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAiC,MAAM,UAAU,CAAA;AAErE,MAAM,WAAW,kBAAkB;IACjC,4DAA4D;IAC5D,YAAY,EAAE,MAAM,CAAA;IACpB,wEAAwE;IACxE,gBAAgB,EAAE,MAAM,CAAA;CACzB;AA8ED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAE/D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LinearPredictionFilter.d.ts","sourceRoot":"","sources":["../../src/filters/LinearPredictionFilter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAiC,MAAM,UAAU,CAAA;AAErE,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,gBAAgB,EAAE,MAAM,CAAA;IACxB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;
|
|
1
|
+
{"version":3,"file":"LinearPredictionFilter.d.ts","sourceRoot":"","sources":["../../src/filters/LinearPredictionFilter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAiC,MAAM,UAAU,CAAA;AAErE,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,gBAAgB,EAAE,MAAM,CAAA;IACxB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AA0PD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,4BAA4B,GACnC,MAAM,CAER"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MovingAverageFilter.d.ts","sourceRoot":"","sources":["../../src/filters/MovingAverageFilter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAiC,MAAM,UAAU,CAAA;AAErE,MAAM,WAAW,yBAAyB;IACxC,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAA;CACnB;
|
|
1
|
+
{"version":3,"file":"MovingAverageFilter.d.ts","sourceRoot":"","sources":["../../src/filters/MovingAverageFilter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAiC,MAAM,UAAU,CAAA;AAErE,MAAM,WAAW,yBAAyB;IACxC,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAA;CACnB;AAoFD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,yBAAyB,GAAG,MAAM,CAE7E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OneEuroFilter.d.ts","sourceRoot":"","sources":["../../src/filters/OneEuroFilter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAiC,MAAM,UAAU,CAAA;AAErE,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;
|
|
1
|
+
{"version":3,"file":"OneEuroFilter.d.ts","sourceRoot":"","sources":["../../src/filters/OneEuroFilter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAiC,MAAM,UAAU,CAAA;AAErE,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AA2LD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAEjE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StringFilter.d.ts","sourceRoot":"","sources":["../../src/filters/StringFilter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAiC,MAAM,UAAU,CAAA;AAErE,MAAM,WAAW,kBAAkB;IACjC,2CAA2C;IAC3C,YAAY,EAAE,MAAM,CAAA;CACrB;
|
|
1
|
+
{"version":3,"file":"StringFilter.d.ts","sourceRoot":"","sources":["../../src/filters/StringFilter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAiC,MAAM,UAAU,CAAA;AAErE,MAAM,WAAW,kBAAkB;IACjC,2CAA2C;IAC3C,YAAY,EAAE,MAAM,CAAA;CACrB;AAkED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAE/D"}
|
package/dist/filters/index.d.ts
CHANGED
|
@@ -12,4 +12,6 @@ export { oneEuroFilter } from './OneEuroFilter';
|
|
|
12
12
|
export type { OneEuroFilterParams } from './OneEuroFilter';
|
|
13
13
|
export { linearPredictionFilter } from './LinearPredictionFilter';
|
|
14
14
|
export type { LinearPredictionFilterParams } from './LinearPredictionFilter';
|
|
15
|
+
export { douglasPeuckerFilter, simplify } from './DouglasPeuckerFilter';
|
|
16
|
+
export type { DouglasPeuckerFilterParams } from './DouglasPeuckerFilter';
|
|
15
17
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/filters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAEtD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAExD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC3D,YAAY,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAA;AAEtE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAExD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAElD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,YAAY,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAE1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAA;AACjE,YAAY,EAAE,4BAA4B,EAAE,MAAM,0BAA0B,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/filters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAEtD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAExD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC3D,YAAY,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAA;AAEtE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAExD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAElD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,YAAY,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAE1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAA;AACjE,YAAY,EAAE,4BAA4B,EAAE,MAAM,0BAA0B,CAAA;AAE5E,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACvE,YAAY,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAA"}
|