ntk 3.9.0 → 3.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/lib/color.js +86 -0
- package/lib/index.js +9 -0
- package/lib/renderingcontext_2d.js +8 -3
- package/lib/widgets/css.js +7 -10
- package/package.json +7 -3
package/lib/color.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
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 `[r, g, b, a]` floats in 0..1 with **straight**
|
|
47
|
+
* (unassociated) alpha, or null if it is not a colour.
|
|
48
|
+
*
|
|
49
|
+
* `cssColor` is the one to use for anything heading to XRender. This is for
|
|
50
|
+
* the places that genuinely want unassociated components:
|
|
51
|
+
*
|
|
52
|
+
* - **OpenGL.** `glClearColor` and material colours take straight alpha;
|
|
53
|
+
* handing them premultiplied values renders translucent colours dark.
|
|
54
|
+
* - **Interpolating** two colours. Lerp straight, then premultiply once at
|
|
55
|
+
* the end — a round trip back to an `rgba()` string only closes if the
|
|
56
|
+
* components were never scaled.
|
|
57
|
+
*/
|
|
58
|
+
export function cssColorStraight(value) {
|
|
59
|
+
if (typeof value !== 'string') return null;
|
|
60
|
+
const v = value.trim();
|
|
61
|
+
if (!v) return null;
|
|
62
|
+
if (v.toLowerCase() === 'transparent') return [0, 0, 0, 0];
|
|
63
|
+
|
|
64
|
+
// '#' is ours alone: falling back to parse-color for a hex form we reject
|
|
65
|
+
// is how '#1234567' turns into rgba [18, 52, 86, 7, 1] -> alpha 7.
|
|
66
|
+
const rgba = v.startsWith('#') ? parseHex(v) : rawRgba(v);
|
|
67
|
+
if (!rgba) return null;
|
|
68
|
+
// NaN would otherwise reach the wire as a garbage fixed-point value
|
|
69
|
+
return rgba.every((c) => Number.isFinite(c)) ? rgba : null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function rawRgba(value) {
|
|
73
|
+
const c = parseColorRaw(value);
|
|
74
|
+
if (!c || !c.rgba) return null;
|
|
75
|
+
const [r, g, b, a] = c.rgba;
|
|
76
|
+
return [r / 255, g / 255, b / 255, a];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Parse a CSS colour to a premultiplied `[r, g, b, a]` in 0..1, ready for
|
|
81
|
+
* XRender, or null if `value` is not a colour.
|
|
82
|
+
*/
|
|
83
|
+
export function cssColor(value) {
|
|
84
|
+
const straight = cssColorStraight(value);
|
|
85
|
+
return straight && premultiply(straight);
|
|
86
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -22,6 +22,7 @@ import SvgView from './widgets/svgview.js';
|
|
|
22
22
|
import MarkdownView from './widgets/markdownview.js';
|
|
23
23
|
import TexView, { configureTex, layoutTex, TexBox } from './widgets/tex.js';
|
|
24
24
|
import { tokenize as highlightCode } from './widgets/highlight.js';
|
|
25
|
+
import { cssColorStraight, premultiply } from './color.js';
|
|
25
26
|
import { cssColor, cssLength } from './widgets/css.js';
|
|
26
27
|
|
|
27
28
|
// rendering context modules register themselves on Drawable
|
|
@@ -64,6 +65,12 @@ export function createClient(options, callback) {
|
|
|
64
65
|
// preload Render and GLX; GLX is optional ( indirect GLX is often
|
|
65
66
|
// disabled on modern servers )
|
|
66
67
|
display.Render = Render;
|
|
68
|
+
// x11 >= 3.3.0 clamps an out-of-range colour component and warns
|
|
69
|
+
// once per connection; under NTK_STRICT_COLORS it throws instead,
|
|
70
|
+
// which is how `npm test` catches a colour that reaches XRender
|
|
71
|
+
// unpremultiplied (r, g or b above alpha) rather than silently
|
|
72
|
+
// rendering too bright.
|
|
73
|
+
if (process.env.NTK_STRICT_COLORS) Render.strictColors = true;
|
|
67
74
|
display.GLX = glxError ? null : GLX;
|
|
68
75
|
const X = display.client;
|
|
69
76
|
X.keycode2keysyms = {};
|
|
@@ -129,6 +136,8 @@ export {
|
|
|
129
136
|
configureTex,
|
|
130
137
|
highlightCode,
|
|
131
138
|
cssColor,
|
|
139
|
+
cssColorStraight,
|
|
140
|
+
premultiply,
|
|
132
141
|
cssLength
|
|
133
142
|
};
|
|
134
143
|
// the yoga-layout instance ntk lays HtmlView out with — downstream layout
|
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ntk",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.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",
|
|
@@ -51,10 +51,14 @@
|
|
|
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
|
+
"check-release-message": "node scripts/check-release-message.mjs"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@conventional-commits/parser": "^0.4.1"
|
|
59
63
|
}
|
|
60
64
|
}
|