ntk 8.9.0 → 8.10.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/README.md +22 -0
- package/lib/glswapchain.js +51 -5
- package/lib/shadow.js +18 -122
- package/lib/shadowmath.js +134 -0
- package/package.json +10 -2
package/README.md
CHANGED
|
@@ -120,6 +120,28 @@ Server-side resources support `using` / `await using` (Node 24+):
|
|
|
120
120
|
} // pixmap freed, connection closed
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
+
## Importing less
|
|
124
|
+
|
|
125
|
+
`ntk` is the whole package: the X11 client, the font engine, the SVG parser
|
|
126
|
+
and the image decoders all load with it. A program that needs one piece —
|
|
127
|
+
a renderer drawing through something other than X11 that wants the colour
|
|
128
|
+
parser, say — can import just that piece, and loads only what it needs:
|
|
129
|
+
|
|
130
|
+
| Import | What | Loads with it |
|
|
131
|
+
| ------------------- | ------------------------------------------------------- | --------------------------- |
|
|
132
|
+
| `ntk/color` | `cssColor`, `cssColorStraight`, `premultiply` | `parse-color` |
|
|
133
|
+
| `ntk/imagedata` | `ImageData`, `pixelLayout`, `toStraightRgba`, … | — |
|
|
134
|
+
| `ntk/path` | `Path2D`, `parseSvgPath` | — |
|
|
135
|
+
| `ntk/gl` | `GLError`, `GL_MODES`, `DEFAULT_GL_POLICY`, … | — |
|
|
136
|
+
| `ntk/shadow-math` | `shadowSigma`, `shadowReach`, `blurScale`, … | — |
|
|
137
|
+
| `ntk/image` | `Image`, `decodeImage`, `loadImage` | `jpeg-js`, `pngjs` |
|
|
138
|
+
| `ntk/svg` | `SvgView` (default export) | `htmlparser2`, `domutils` |
|
|
139
|
+
| `ntk/font` | `Font` (default export) | `fontkit` |
|
|
140
|
+
| `ntk/xembed` | the XEmbed protocol ([docs/xembed.md](docs/xembed.md)) | `x11` |
|
|
141
|
+
|
|
142
|
+
Each is the same module the root re-exports, so importing a name both ways
|
|
143
|
+
gives the same function or class: nothing is loaded, or instantiated, twice.
|
|
144
|
+
|
|
123
145
|
## 3d graphics
|
|
124
146
|
|
|
125
147
|
Two backends, chosen by `glPolicy`
|
package/lib/glswapchain.js
CHANGED
|
@@ -108,23 +108,69 @@ export class GLSwapchain {
|
|
|
108
108
|
const gen = this.generation;
|
|
109
109
|
if (gen && gen.width === w && gen.height === h) return gen.surface;
|
|
110
110
|
this._retire(gen);
|
|
111
|
-
// a generation that had to go linear keeps that layout: whatever
|
|
112
|
-
//
|
|
111
|
+
// a generation that had to go linear keeps that layout: whatever refused
|
|
112
|
+
// the tiled buffer — the server, or EGL itself — has not changed since
|
|
113
113
|
return this._newGeneration(w, h, gen ? gen.linear : false);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/** A GBM surface at this size, in the layout asked for. */
|
|
117
|
+
_createSurface(w, h, linear) {
|
|
118
|
+
return linear
|
|
119
|
+
? this.gpu.createSurface(w, h, this.dri.GBM_USE.RENDERING | this.dri.GBM_USE.LINEAR)
|
|
120
|
+
: this.gpu.createSurface(w, h);
|
|
121
|
+
}
|
|
122
|
+
|
|
116
123
|
_newGeneration(w, h, linear) {
|
|
117
|
-
const use = linear ? this.dri.GBM_USE.RENDERING | this.dri.GBM_USE.LINEAR : undefined;
|
|
118
124
|
let surface;
|
|
119
125
|
try {
|
|
120
|
-
surface =
|
|
126
|
+
surface = this._createSurface(w, h, linear);
|
|
121
127
|
} catch (err) {
|
|
122
|
-
|
|
128
|
+
// Some drivers will not give EGL a window surface over a render-only
|
|
129
|
+
// GBM buffer at all: NVIDIA's answers eglCreateWindowSurface with
|
|
130
|
+
// EGL_BAD_MATCH, and takes the very same size and format once the
|
|
131
|
+
// buffer is linear. That is the retry an import refusal gets below,
|
|
132
|
+
// one step earlier — there the server would not read the buffer, here
|
|
133
|
+
// EGL would not draw into it, and a linear layout is what both of them
|
|
134
|
+
// accept. Cheap to try: the alternative is no direct rendering at all.
|
|
135
|
+
if (linear || !this.policy.linearFallback) {
|
|
136
|
+
throw this._surfaceError(w, h, linear ? 'linear' : 'tiled', err.message, err);
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
surface = this._createSurface(w, h, true);
|
|
140
|
+
} catch (retryErr) {
|
|
141
|
+
throw this._surfaceError(
|
|
142
|
+
w,
|
|
143
|
+
h,
|
|
144
|
+
'tiled or linear',
|
|
145
|
+
`${err.message}; linear: ${retryErr.message}`,
|
|
146
|
+
err
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
linear = true;
|
|
123
150
|
}
|
|
124
151
|
this.generation = new Generation(surface, w, h, linear);
|
|
125
152
|
return surface;
|
|
126
153
|
}
|
|
127
154
|
|
|
155
|
+
/** No surface, naming the layouts that were refused and what to change. */
|
|
156
|
+
_surfaceError(w, h, tried, detail, cause) {
|
|
157
|
+
const hint =
|
|
158
|
+
tried === 'tiled'
|
|
159
|
+
? 'A driver that will not draw into a tiled buffer (NVIDIA) takes a linear one,\n' +
|
|
160
|
+
'which is the retry glPolicy: { linearFallback: true } — the default — makes.\n' +
|
|
161
|
+
'See docs/context-gles.md#buffer-layout.'
|
|
162
|
+
: 'No layout this device offers can back a window surface for this driver.\n' +
|
|
163
|
+
"Another render node may: glPolicy: { devicePath: '/dev/dri/renderD###' }.\n" +
|
|
164
|
+
"Failing that, glPolicy: 'indirect' draws with no GPU buffer at all.\n" +
|
|
165
|
+
'See docs/context-gles.md#buffer-layout.';
|
|
166
|
+
return glError(
|
|
167
|
+
GLError.CONTEXT_FAILED,
|
|
168
|
+
`could not create a ${w}x${h} GPU surface (${tried}): ${detail}`,
|
|
169
|
+
hint,
|
|
170
|
+
cause
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
128
174
|
/** Can a frame be drawn and presented right now? */
|
|
129
175
|
canRender() {
|
|
130
176
|
if (this.destroyed || this.error) return false;
|
package/lib/shadow.js
CHANGED
|
@@ -61,129 +61,25 @@
|
|
|
61
61
|
// they are the 2d context's bookkeeping, not a primitive.
|
|
62
62
|
import { Surface } from './surface.js';
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
* the request carries every tap, and the server multiplies each of them
|
|
73
|
-
* per pixel per pass, so an unbounded `shadowBlur` is an unbounded
|
|
74
|
-
* request and an unbounded stall. Past this the blur stops widening.
|
|
75
|
-
* - `maxPixels` — the largest coverage surface built for one shadow. Beyond
|
|
76
|
-
* it the shadow is dropped rather than turning one drawing into a
|
|
77
|
-
* multi-megabyte allocation; the drawing itself is unaffected.
|
|
78
|
-
* - `scaleSigma` — the σ a reduced-scale blur is not allowed to fall below.
|
|
79
|
-
* A gaussian carries no detail finer than about σ/2 px, so a wide one does
|
|
80
|
-
* not need full resolution to resolve it: past twice this, coverage is
|
|
81
|
-
* shrunk by the largest power of two that keeps σ/scale at or above it,
|
|
82
|
-
* blurred there, and resolved back — `scale` off the kernel and `scale²`
|
|
83
|
-
* off the area, which is where a first paint's time goes (issue #338).
|
|
84
|
-
* What the shrink costs is set by that reduced σ and not by the ratio: 4
|
|
85
|
-
* holds the difference from an exact blur inside three levels of 8-bit
|
|
86
|
-
* alpha, where 3 is worth four levels and 2 is worth seven.
|
|
87
|
-
* - `maxScale` — how far the shrink may go whatever the floor allows, so a
|
|
88
|
-
* `maxSigma`-wide blur cannot resample its way down to a thumbnail.
|
|
89
|
-
* `maxScale: 1` blurs everything at full resolution, which is what 8.6
|
|
90
|
-
* did, and is the setting for a caller that needs the exact kernel.
|
|
91
|
-
*/
|
|
92
|
-
export const DEFAULT_SHADOW_POLICY = {
|
|
93
|
-
cacheBytes: 4 << 20,
|
|
94
|
-
maxSigma: 32,
|
|
95
|
-
maxPixels: 8 << 20,
|
|
96
|
-
scaleSigma: 4,
|
|
97
|
-
maxScale: 4
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
/** the policy for one app, merged over the defaults */
|
|
101
|
-
export function shadowPolicyOf(app) {
|
|
102
|
-
return app?.shadowPolicy
|
|
103
|
-
? { ...DEFAULT_SHADOW_POLICY, ...app.shadowPolicy }
|
|
104
|
-
: DEFAULT_SHADOW_POLICY;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* The gaussian a `shadowBlur` asks for.
|
|
109
|
-
*
|
|
110
|
-
* `shadowBlur` is a **diameter**, not a radius: the canvas spec says a
|
|
111
|
-
* shadow is blurred by a gaussian whose standard deviation is half of it.
|
|
112
|
-
* Getting this wrong is invisible until someone compares against a browser,
|
|
113
|
-
* so it is one line with a test on it — `shadowBlur: 8` must be σ = 4 here
|
|
114
|
-
* exactly as it is in Chrome and Firefox.
|
|
115
|
-
*
|
|
116
|
-
* Clamped to the policy's `maxSigma`, which is the only place a shadow
|
|
117
|
-
* silently stops matching a browser; the cap is chosen so that no shadow a
|
|
118
|
-
* UI actually draws reaches it.
|
|
119
|
-
*/
|
|
120
|
-
export function shadowSigma(blur, policy = DEFAULT_SHADOW_POLICY) {
|
|
121
|
-
const sigma = blur / 2;
|
|
122
|
-
return sigma > policy.maxSigma ? policy.maxSigma : sigma;
|
|
123
|
-
}
|
|
64
|
+
import {
|
|
65
|
+
DEFAULT_SHADOW_POLICY,
|
|
66
|
+
blurScale,
|
|
67
|
+
gaussianKernel1d,
|
|
68
|
+
shadowPolicyOf,
|
|
69
|
+
shadowReach,
|
|
70
|
+
shadowSigma
|
|
71
|
+
} from './shadowmath.js';
|
|
124
72
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* A normalized 1d gaussian, `2 * reach + 1` taps wide.
|
|
138
|
-
*
|
|
139
|
-
* Normalizing the *truncated* kernel rather than the ideal one is what keeps
|
|
140
|
-
* a flat interior at full coverage: a shadow under an opaque box must stay
|
|
141
|
-
* opaque in the middle, and a kernel summing to 0.997 would leave it at 254.
|
|
142
|
-
*/
|
|
143
|
-
export function gaussianKernel1d(sigma, reach = shadowReach(sigma)) {
|
|
144
|
-
const values = new Array(reach * 2 + 1);
|
|
145
|
-
let sum = 0;
|
|
146
|
-
for (let i = -reach; i <= reach; i++) {
|
|
147
|
-
const v = Math.exp(-(i * i) / (2 * sigma * sigma));
|
|
148
|
-
values[i + reach] = v;
|
|
149
|
-
sum += v;
|
|
150
|
-
}
|
|
151
|
-
for (let i = 0; i < values.length; i++) values[i] /= sum;
|
|
152
|
-
return values;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* The scale a blur of this sigma is run at: 1, 2 or 4 by default, meaning
|
|
157
|
-
* "shrink the coverage by this much, blur at `sigma / scale`, resolve back".
|
|
158
|
-
*
|
|
159
|
-
* A gaussian is a low-pass filter — it carries nothing finer than about σ/2
|
|
160
|
-
* px — so resolving a wide one at full resolution spends most of its time on
|
|
161
|
-
* detail the result cannot hold. Shrinking first takes `scale` off the
|
|
162
|
-
* kernel and `scale²` off the area it runs over: at σ 21 over 552×396, k = 4
|
|
163
|
-
* turns 55.5M multiply-accumulates into 0.9M, which on a software-RENDER
|
|
164
|
-
* server (XQuartz) is most of a first paint (issue #338).
|
|
165
|
-
*
|
|
166
|
-
* The scale is a power of two so that each shrink is an exact 2×2 average,
|
|
167
|
-
* and it is capped both by `maxScale` and by the σ floor `scaleSigma`, which
|
|
168
|
-
* is what keeps the reduced blur wide enough to still be a gaussian — the
|
|
169
|
-
* difference from an exact blur is set by that reduced σ, and stays inside
|
|
170
|
-
* three levels of 8-bit alpha at the 4 the policy defaults to. σ under
|
|
171
|
-
* `2 * scaleSigma` gets 1: below that the two resampling composites cost
|
|
172
|
-
* more than the kernel they save.
|
|
173
|
-
*
|
|
174
|
-
* Exported for a caller that draws its own shapes and can therefore skip the
|
|
175
|
-
* shrink entirely: draw the shape into a surface `1 / scale` the size (its
|
|
176
|
-
* padding scaled with it), blur at `sigma / scale`, and composite through a
|
|
177
|
-
* `1 / scale` picture transform. See docs/surface.md#baking-a-blur.
|
|
178
|
-
*/
|
|
179
|
-
export function blurScale(sigma, policy = DEFAULT_SHADOW_POLICY) {
|
|
180
|
-
const floor = policy.scaleSigma ?? DEFAULT_SHADOW_POLICY.scaleSigma;
|
|
181
|
-
const max = policy.maxScale ?? DEFAULT_SHADOW_POLICY.maxScale;
|
|
182
|
-
if (!(sigma > 0) || !(floor > 0) || !(max > 1)) return 1;
|
|
183
|
-
let scale = 1;
|
|
184
|
-
while (scale * 2 <= max && sigma / (scale * 2) >= floor) scale *= 2;
|
|
185
|
-
return scale;
|
|
186
|
-
}
|
|
73
|
+
// the arithmetic lives in shadowmath.js, which carries no surface; these
|
|
74
|
+
// were defined here, and every name stays importable from here
|
|
75
|
+
export {
|
|
76
|
+
DEFAULT_SHADOW_POLICY,
|
|
77
|
+
blurScale,
|
|
78
|
+
gaussianKernel1d,
|
|
79
|
+
shadowPolicyOf,
|
|
80
|
+
shadowReach,
|
|
81
|
+
shadowSigma
|
|
82
|
+
};
|
|
187
83
|
|
|
188
84
|
/**
|
|
189
85
|
* The smallest a shrunk coverage surface is allowed to get on either side.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// The arithmetic of a drop shadow — the policy, the gaussian a `shadowBlur`
|
|
2
|
+
// asks for, how far it reaches and the scale it is blurred at — with no
|
|
3
|
+
// surface and no X connection in it (shadow.js has those).
|
|
4
|
+
//
|
|
5
|
+
// Its own module so that a renderer drawing shadows some other way can size
|
|
6
|
+
// them the way this package does without loading the X11 client that the
|
|
7
|
+
// surfaces need: react-x11's Cocoa, Windows and Wayland backends ask how far
|
|
8
|
+
// a box shadow reaches here, and nothing else of ntk's. Exported as
|
|
9
|
+
// `ntk/shadow-math`; shadow.js re-exports every name, so nothing that
|
|
10
|
+
// imported them from there or from the package root changes.
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Shadow policy — the cost ceilings, per app via `app.shadowPolicy`
|
|
14
|
+
* (partial objects are merged over the defaults). See docs/context-2d.md.
|
|
15
|
+
*
|
|
16
|
+
* - `cacheBytes` — LRU budget for retained shadow coverage per connection.
|
|
17
|
+
* Keyed by (text, font, blur) for text; least-recently-drawn surfaces are
|
|
18
|
+
* destroyed server-side once the total goes over.
|
|
19
|
+
* - `maxSigma` — the widest gaussian actually run. The kernel is 6σ+1 wide,
|
|
20
|
+
* the request carries every tap, and the server multiplies each of them
|
|
21
|
+
* per pixel per pass, so an unbounded `shadowBlur` is an unbounded
|
|
22
|
+
* request and an unbounded stall. Past this the blur stops widening.
|
|
23
|
+
* - `maxPixels` — the largest coverage surface built for one shadow. Beyond
|
|
24
|
+
* it the shadow is dropped rather than turning one drawing into a
|
|
25
|
+
* multi-megabyte allocation; the drawing itself is unaffected.
|
|
26
|
+
* - `scaleSigma` — the σ a reduced-scale blur is not allowed to fall below.
|
|
27
|
+
* A gaussian carries no detail finer than about σ/2 px, so a wide one does
|
|
28
|
+
* not need full resolution to resolve it: past twice this, coverage is
|
|
29
|
+
* shrunk by the largest power of two that keeps σ/scale at or above it,
|
|
30
|
+
* blurred there, and resolved back — `scale` off the kernel and `scale²`
|
|
31
|
+
* off the area, which is where a first paint's time goes (issue #338).
|
|
32
|
+
* What the shrink costs is set by that reduced σ and not by the ratio: 4
|
|
33
|
+
* holds the difference from an exact blur inside three levels of 8-bit
|
|
34
|
+
* alpha, where 3 is worth four levels and 2 is worth seven.
|
|
35
|
+
* - `maxScale` — how far the shrink may go whatever the floor allows, so a
|
|
36
|
+
* `maxSigma`-wide blur cannot resample its way down to a thumbnail.
|
|
37
|
+
* `maxScale: 1` blurs everything at full resolution, which is what 8.6
|
|
38
|
+
* did, and is the setting for a caller that needs the exact kernel.
|
|
39
|
+
*/
|
|
40
|
+
export const DEFAULT_SHADOW_POLICY = {
|
|
41
|
+
cacheBytes: 4 << 20,
|
|
42
|
+
maxSigma: 32,
|
|
43
|
+
maxPixels: 8 << 20,
|
|
44
|
+
scaleSigma: 4,
|
|
45
|
+
maxScale: 4
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** the policy for one app, merged over the defaults */
|
|
49
|
+
export function shadowPolicyOf(app) {
|
|
50
|
+
return app?.shadowPolicy
|
|
51
|
+
? { ...DEFAULT_SHADOW_POLICY, ...app.shadowPolicy }
|
|
52
|
+
: DEFAULT_SHADOW_POLICY;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The gaussian a `shadowBlur` asks for.
|
|
57
|
+
*
|
|
58
|
+
* `shadowBlur` is a **diameter**, not a radius: the canvas spec says a
|
|
59
|
+
* shadow is blurred by a gaussian whose standard deviation is half of it.
|
|
60
|
+
* Getting this wrong is invisible until someone compares against a browser,
|
|
61
|
+
* so it is one line with a test on it — `shadowBlur: 8` must be σ = 4 here
|
|
62
|
+
* exactly as it is in Chrome and Firefox.
|
|
63
|
+
*
|
|
64
|
+
* Clamped to the policy's `maxSigma`, which is the only place a shadow
|
|
65
|
+
* silently stops matching a browser; the cap is chosen so that no shadow a
|
|
66
|
+
* UI actually draws reaches it.
|
|
67
|
+
*/
|
|
68
|
+
export function shadowSigma(blur, policy = DEFAULT_SHADOW_POLICY) {
|
|
69
|
+
const sigma = blur / 2;
|
|
70
|
+
return sigma > policy.maxSigma ? policy.maxSigma : sigma;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* How far the blur spreads coverage, in pixels — the kernel's half-width,
|
|
75
|
+
* and therefore the padding every coverage surface needs on each side.
|
|
76
|
+
*
|
|
77
|
+
* Truncating a gaussian at 3σ leaves 0.3% of its weight outside, which is
|
|
78
|
+
* below one step of the 8-bit coverage it is convolving.
|
|
79
|
+
*/
|
|
80
|
+
export function shadowReach(sigma) {
|
|
81
|
+
return sigma > 0 ? Math.ceil(sigma * 3) : 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A normalized 1d gaussian, `2 * reach + 1` taps wide.
|
|
86
|
+
*
|
|
87
|
+
* Normalizing the *truncated* kernel rather than the ideal one is what keeps
|
|
88
|
+
* a flat interior at full coverage: a shadow under an opaque box must stay
|
|
89
|
+
* opaque in the middle, and a kernel summing to 0.997 would leave it at 254.
|
|
90
|
+
*/
|
|
91
|
+
export function gaussianKernel1d(sigma, reach = shadowReach(sigma)) {
|
|
92
|
+
const values = new Array(reach * 2 + 1);
|
|
93
|
+
let sum = 0;
|
|
94
|
+
for (let i = -reach; i <= reach; i++) {
|
|
95
|
+
const v = Math.exp(-(i * i) / (2 * sigma * sigma));
|
|
96
|
+
values[i + reach] = v;
|
|
97
|
+
sum += v;
|
|
98
|
+
}
|
|
99
|
+
for (let i = 0; i < values.length; i++) values[i] /= sum;
|
|
100
|
+
return values;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The scale a blur of this sigma is run at: 1, 2 or 4 by default, meaning
|
|
105
|
+
* "shrink the coverage by this much, blur at `sigma / scale`, resolve back".
|
|
106
|
+
*
|
|
107
|
+
* A gaussian is a low-pass filter — it carries nothing finer than about σ/2
|
|
108
|
+
* px — so resolving a wide one at full resolution spends most of its time on
|
|
109
|
+
* detail the result cannot hold. Shrinking first takes `scale` off the
|
|
110
|
+
* kernel and `scale²` off the area it runs over: at σ 21 over 552×396, k = 4
|
|
111
|
+
* turns 55.5M multiply-accumulates into 0.9M, which on a software-RENDER
|
|
112
|
+
* server (XQuartz) is most of a first paint (issue #338).
|
|
113
|
+
*
|
|
114
|
+
* The scale is a power of two so that each shrink is an exact 2×2 average,
|
|
115
|
+
* and it is capped both by `maxScale` and by the σ floor `scaleSigma`, which
|
|
116
|
+
* is what keeps the reduced blur wide enough to still be a gaussian — the
|
|
117
|
+
* difference from an exact blur is set by that reduced σ, and stays inside
|
|
118
|
+
* three levels of 8-bit alpha at the 4 the policy defaults to. σ under
|
|
119
|
+
* `2 * scaleSigma` gets 1: below that the two resampling composites cost
|
|
120
|
+
* more than the kernel they save.
|
|
121
|
+
*
|
|
122
|
+
* Exported for a caller that draws its own shapes and can therefore skip the
|
|
123
|
+
* shrink entirely: draw the shape into a surface `1 / scale` the size (its
|
|
124
|
+
* padding scaled with it), blur at `sigma / scale`, and composite through a
|
|
125
|
+
* `1 / scale` picture transform. See docs/surface.md#baking-a-blur.
|
|
126
|
+
*/
|
|
127
|
+
export function blurScale(sigma, policy = DEFAULT_SHADOW_POLICY) {
|
|
128
|
+
const floor = policy.scaleSigma ?? DEFAULT_SHADOW_POLICY.scaleSigma;
|
|
129
|
+
const max = policy.maxScale ?? DEFAULT_SHADOW_POLICY.maxScale;
|
|
130
|
+
if (!(sigma > 0) || !(floor > 0) || !(max > 1)) return 1;
|
|
131
|
+
let scale = 1;
|
|
132
|
+
while (scale * 2 <= max && sigma / (scale * 2) >= floor) scale *= 2;
|
|
133
|
+
return scale;
|
|
134
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ntk",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.10.0",
|
|
4
4
|
"description": "Desktop UI toolkit for X11 with canvas-like 2d and OpenGL rendering",
|
|
5
5
|
"author": "Andrey Sidorov <sidorares@yandex.ru>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,7 +25,15 @@
|
|
|
25
25
|
"main": "./lib/index.js",
|
|
26
26
|
"exports": {
|
|
27
27
|
".": "./lib/index.js",
|
|
28
|
-
"./xembed": "./lib/xembed.js"
|
|
28
|
+
"./xembed": "./lib/xembed.js",
|
|
29
|
+
"./color": "./lib/color.js",
|
|
30
|
+
"./imagedata": "./lib/imagedata.js",
|
|
31
|
+
"./path": "./lib/path.js",
|
|
32
|
+
"./gl": "./lib/gl.js",
|
|
33
|
+
"./shadow-math": "./lib/shadowmath.js",
|
|
34
|
+
"./image": "./lib/image.js",
|
|
35
|
+
"./svg": "./lib/widgets/svgview.js",
|
|
36
|
+
"./font": "./lib/text/font.js"
|
|
29
37
|
},
|
|
30
38
|
"files": [
|
|
31
39
|
"lib"
|