ntk 3.8.0 → 3.9.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/lib/color.js +78 -0
- package/lib/index.js +6 -0
- package/lib/renderingcontext_2d.js +8 -3
- package/lib/widgets/css.js +7 -10
- package/lib/window.js +33 -0
- package/package.json +3 -3
package/lib/color.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Colour parsing, shared by the 2d context and the CSS cascade.
|
|
2
|
+
//
|
|
3
|
+
// Everything here returns **premultiplied** `[r, g, b, a]` floats in 0..1,
|
|
4
|
+
// because that is what XRender takes: a colour reaches the server through
|
|
5
|
+
// CreateSolidFill / FillRectangles / gradient stops, all of which read
|
|
6
|
+
// premultiplied ARGB, so each of r, g and b must be <= a. Straight (a.k.a.
|
|
7
|
+
// unassociated) alpha renders at full brightness instead — red at half alpha
|
|
8
|
+
// composites as #ff0000 rather than #800000 — and since x11 3.3.0 a
|
|
9
|
+
// component above 1 also warns.
|
|
10
|
+
//
|
|
11
|
+
// Hex is parsed here rather than handed to parse-color, which does not
|
|
12
|
+
// understand CSS hex alpha and fails silently on it:
|
|
13
|
+
//
|
|
14
|
+
// parse-color('#00000022') -> rgba [0, 0, 0, 34, 1] five entries, and
|
|
15
|
+
// the alpha is still a 0..255 byte
|
|
16
|
+
// parse-color('#0002') -> rgba [0, 2, 0, 1] read as a truncated
|
|
17
|
+
// six-digit hex
|
|
18
|
+
//
|
|
19
|
+
// The first is why `#RRGGBBAA` used to render fully opaque: 34 clamps to 1.
|
|
20
|
+
import parseColorRaw from 'parse-color';
|
|
21
|
+
|
|
22
|
+
/** `[r, g, b, a]` straight -> premultiplied. Opaque colours are unchanged. */
|
|
23
|
+
export function premultiply([r, g, b, a]) {
|
|
24
|
+
return a === 1 ? [r, g, b, a] : [r * a, g * a, b * a, a];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// #RGB, #RGBA, #RRGGBB, #RRGGBBAA. Five and seven digits are not CSS, so
|
|
28
|
+
// they are rejected rather than guessed at.
|
|
29
|
+
const HEX = /^#([0-9a-f]{3,8})$/i;
|
|
30
|
+
|
|
31
|
+
function parseHex(value) {
|
|
32
|
+
const m = HEX.exec(value);
|
|
33
|
+
if (!m) return null;
|
|
34
|
+
const h = m[1];
|
|
35
|
+
const short = h.length === 3 || h.length === 4;
|
|
36
|
+
if (!short && h.length !== 6 && h.length !== 8) return null;
|
|
37
|
+
const part = (i) =>
|
|
38
|
+
short
|
|
39
|
+
? parseInt(h[i], 16) * 0x11 // 'f' -> 0xff
|
|
40
|
+
: parseInt(h.slice(i * 2, i * 2 + 2), 16);
|
|
41
|
+
const hasAlpha = h.length === 4 || h.length === 8;
|
|
42
|
+
return [part(0) / 255, part(1) / 255, part(2) / 255, hasAlpha ? part(3) / 255 : 1];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Parse a CSS colour to premultipliable `[r, g, b, a]` floats, straight
|
|
47
|
+
* alpha, or null if it is not a colour. Exported for callers that need the
|
|
48
|
+
* unassociated values; use `cssColor` for anything heading to XRender.
|
|
49
|
+
*/
|
|
50
|
+
export function parseCssColorStraight(value) {
|
|
51
|
+
if (typeof value !== 'string') return null;
|
|
52
|
+
const v = value.trim();
|
|
53
|
+
if (!v) return null;
|
|
54
|
+
if (v.toLowerCase() === 'transparent') return [0, 0, 0, 0];
|
|
55
|
+
|
|
56
|
+
// '#' is ours alone: falling back to parse-color for a hex form we reject
|
|
57
|
+
// is how '#1234567' turns into rgba [18, 52, 86, 7, 1] -> alpha 7.
|
|
58
|
+
const rgba = v.startsWith('#') ? parseHex(v) : rawRgba(v);
|
|
59
|
+
if (!rgba) return null;
|
|
60
|
+
// NaN would otherwise reach the wire as a garbage fixed-point value
|
|
61
|
+
return rgba.every((c) => Number.isFinite(c)) ? rgba : null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function rawRgba(value) {
|
|
65
|
+
const c = parseColorRaw(value);
|
|
66
|
+
if (!c || !c.rgba) return null;
|
|
67
|
+
const [r, g, b, a] = c.rgba;
|
|
68
|
+
return [r / 255, g / 255, b / 255, a];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Parse a CSS colour to a premultiplied `[r, g, b, a]` in 0..1, ready for
|
|
73
|
+
* XRender, or null if `value` is not a colour.
|
|
74
|
+
*/
|
|
75
|
+
export function cssColor(value) {
|
|
76
|
+
const straight = parseCssColorStraight(value);
|
|
77
|
+
return straight && premultiply(straight);
|
|
78
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -64,6 +64,12 @@ export function createClient(options, callback) {
|
|
|
64
64
|
// preload Render and GLX; GLX is optional ( indirect GLX is often
|
|
65
65
|
// disabled on modern servers )
|
|
66
66
|
display.Render = Render;
|
|
67
|
+
// x11 >= 3.3.0 clamps an out-of-range colour component and warns
|
|
68
|
+
// once per connection; under NTK_STRICT_COLORS it throws instead,
|
|
69
|
+
// which is how `npm test` catches a colour that reaches XRender
|
|
70
|
+
// unpremultiplied (r, g or b above alpha) rather than silently
|
|
71
|
+
// rendering too bright.
|
|
72
|
+
if (process.env.NTK_STRICT_COLORS) Render.strictColors = true;
|
|
67
73
|
display.GLX = glxError ? null : GLX;
|
|
68
74
|
const X = display.client;
|
|
69
75
|
X.keycode2keysyms = {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import parseFontStyle from 'canvas-fontstyle';
|
|
2
2
|
import extrudePolyline from 'extrude-polyline';
|
|
3
|
-
import parseColorRaw from 'parse-color';
|
|
4
3
|
|
|
4
|
+
import { cssColor } from './color.js';
|
|
5
5
|
import Drawable from './drawable.js';
|
|
6
6
|
import { Image } from './image.js';
|
|
7
7
|
import {
|
|
@@ -121,10 +121,15 @@ function dashPolyline(pts, closed, pattern, offset) {
|
|
|
121
121
|
return { runs, closedLoop: false };
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
// An array is taken as already-premultiplied `[r, g, b, a]` in 0..1 (the
|
|
125
|
+
// documented form in docs/context-2d.md), so it passes through untouched; a
|
|
126
|
+
// string is a CSS colour and gets premultiplied on the way in. Both end up in
|
|
127
|
+
// createSolidPicture, which hands them to XRender.
|
|
124
128
|
function parseColor(value) {
|
|
125
129
|
if (Array.isArray(value)) return value;
|
|
126
|
-
const c =
|
|
127
|
-
|
|
130
|
+
const c = cssColor(value);
|
|
131
|
+
if (!c) throw new Error(`Not a color: ${JSON.stringify(value)}`);
|
|
132
|
+
return c;
|
|
128
133
|
}
|
|
129
134
|
|
|
130
135
|
/**
|
package/lib/widgets/css.js
CHANGED
|
@@ -4,16 +4,13 @@
|
|
|
4
4
|
import { selectAll } from 'css-select';
|
|
5
5
|
import postcss from 'postcss';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (!c || !c.rgba) return null;
|
|
15
|
-
return [c.rgba[0] / 255, c.rgba[1] / 255, c.rgba[2] / 255, c.rgba[3]];
|
|
16
|
-
}
|
|
7
|
+
// Parse a CSS color to premultiplied [r, g, b, a] floats in 0..1, or null.
|
|
8
|
+
// These land on `ctx.fillStyle` as arrays (see htmlview's paint), which is
|
|
9
|
+
// the already-premultiplied form, so the premultiplying happens in color.js.
|
|
10
|
+
// Imported as well as re-exported: the cascade below calls it too.
|
|
11
|
+
import { cssColor } from '../color.js';
|
|
12
|
+
|
|
13
|
+
export { cssColor };
|
|
17
14
|
|
|
18
15
|
const FONT_SIZE_KEYWORDS = {
|
|
19
16
|
'xx-small': 9,
|
package/lib/window.js
CHANGED
|
@@ -1099,6 +1099,39 @@ export default class Window extends Drawable {
|
|
|
1099
1099
|
});
|
|
1100
1100
|
}
|
|
1101
1101
|
|
|
1102
|
+
/**
|
|
1103
|
+
* Write a property — the counterpart of getProperty, and the general form
|
|
1104
|
+
* of setTitle/setClass/setSizeHints. Strings are written as UTF8_STRING,
|
|
1105
|
+
* arrays of numbers as 32-bit lists; `type` names the property type atom
|
|
1106
|
+
* ('ATOM', 'CARDINAL', 'WINDOW', 'UTF8_STRING', 'STRING'), which is what
|
|
1107
|
+
* EWMH properties are picky about.
|
|
1108
|
+
*
|
|
1109
|
+
* wnd.setProperty('_NET_WM_NAME', 'a title');
|
|
1110
|
+
* wnd.setProperty('_NET_CLIENT_LIST', ids, { type: 'WINDOW' });
|
|
1111
|
+
*
|
|
1112
|
+
* @param {string} name atom name of the property
|
|
1113
|
+
* @param {string|number[]|Buffer} value
|
|
1114
|
+
* @param {object} [options] `{ type, format }`
|
|
1115
|
+
*/
|
|
1116
|
+
async setProperty(name, value, options = {}) {
|
|
1117
|
+
const isList = Array.isArray(value);
|
|
1118
|
+
const { type = isList ? 'CARDINAL' : 'UTF8_STRING', format = isList ? 32 : 8 } =
|
|
1119
|
+
options;
|
|
1120
|
+
const [property, typeAtom] = await Promise.all([this.atom(name), this.atom(type)]);
|
|
1121
|
+
let data = value;
|
|
1122
|
+
if (isList) {
|
|
1123
|
+
const words = new Uint32Array(value);
|
|
1124
|
+
data = Buffer.from(words.buffer, words.byteOffset, words.byteLength);
|
|
1125
|
+
} else if (typeof value === 'string') {
|
|
1126
|
+
data = Buffer.from(value, type === 'STRING' ? 'latin1' : 'utf8');
|
|
1127
|
+
}
|
|
1128
|
+
if (this._destroyed) return this;
|
|
1129
|
+
safeRelease(this.X, () => {
|
|
1130
|
+
this.X.ChangeProperty(0, this.id, property, typeAtom, format, data);
|
|
1131
|
+
});
|
|
1132
|
+
return this;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1102
1135
|
/**
|
|
1103
1136
|
* Intern an atom by name. node-x11 caches them per connection, so asking
|
|
1104
1137
|
* again for one already interned costs no round trip.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ntk",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.9.1",
|
|
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",
|
|
@@ -51,10 +51,10 @@
|
|
|
51
51
|
"parse-color": "^1.0.0",
|
|
52
52
|
"pngjs": "^7.0.0",
|
|
53
53
|
"postcss": "^8.5.23",
|
|
54
|
-
"x11": "^3.
|
|
54
|
+
"x11": "^3.3.0",
|
|
55
55
|
"yoga-layout": "^3.2.1"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
|
-
"test": "node --test"
|
|
58
|
+
"test": "NTK_STRICT_COLORS=1 node --test"
|
|
59
59
|
}
|
|
60
60
|
}
|