ntk 2.2.0 → 3.0.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 +156 -151
- package/lib/app.js +54 -21
- package/lib/cleanup.js +14 -0
- package/lib/drawable.js +12 -18
- package/lib/events_map.js +92 -98
- package/lib/fontconfig.js +123 -17
- package/lib/glyphset.js +40 -21
- package/lib/image.js +138 -0
- package/lib/index.js +123 -57
- package/lib/path.js +734 -0
- package/lib/picture.js +84 -41
- package/lib/pixmap.js +50 -39
- package/lib/rasterize.js +178 -0
- package/lib/renderingcontext_2d.js +1078 -695
- package/lib/renderingcontext_opengl.js +53 -46
- package/lib/renderingcontext_x11.js +59 -67
- package/lib/text/font.js +196 -0
- package/lib/text/fontmanager.js +233 -0
- package/lib/text/fontsource.js +179 -0
- package/lib/text/glyphs.js +456 -0
- package/lib/text/layout.js +356 -0
- package/lib/text/shape.js +141 -0
- package/lib/trapezoid.js +131 -0
- package/lib/widgets/css.js +676 -0
- package/lib/widgets/highlight.js +97 -0
- package/lib/widgets/htmlview.js +879 -0
- package/lib/widgets/markdown.js +148 -0
- package/lib/widgets/markdownview.js +487 -0
- package/lib/widgets/mermaid.js +720 -0
- package/lib/widgets/svgview.js +528 -0
- package/lib/widgets/tex.js +681 -0
- package/lib/window.js +642 -308
- package/package.json +60 -56
- package/examples/example-await.js +0 -23
- package/examples/example-co.js +0 -13
- package/examples/example.js +0 -14
- package/examples/fill-styles.js +0 -39
- package/examples/glclock.js +0 -100
- package/examples/glxpixmap.js +0 -12
- package/examples/gradient.js +0 -41
- package/examples/package.json +0 -17
- package/examples/simpletex.js +0 -72
- package/examples/teapot.js +0 -104
- package/examples/teapot.json +0 -6
- package/examples/text-rendering.js +0 -44
- package/lib/fontface.js +0 -103
- package/lib/widgets/webview.js +0 -33
package/lib/picture.js
CHANGED
|
@@ -1,41 +1,84 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
this.
|
|
14
|
-
|
|
15
|
-
this.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
1
|
+
import { safeRelease } from './cleanup.js';
|
|
2
|
+
|
|
3
|
+
const registry = new FinalizationRegistry(({ Render, X, id }) => {
|
|
4
|
+
safeRelease(X, () => {
|
|
5
|
+
Render.FreePicture(id);
|
|
6
|
+
X.ReleaseID(id);
|
|
7
|
+
});
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export default class Picture {
|
|
11
|
+
constructor(app, args = {}) {
|
|
12
|
+
const X = app.X;
|
|
13
|
+
this.X = X;
|
|
14
|
+
this.Render = app.display.Render;
|
|
15
|
+
this.display = app.display;
|
|
16
|
+
|
|
17
|
+
if (typeof args.id === 'undefined') {
|
|
18
|
+
this.id = X.AllocID();
|
|
19
|
+
this.format = args.format || this.Render.rgb24;
|
|
20
|
+
this.Render.CreatePicture(this.id, args.drawable.id, this.format, args);
|
|
21
|
+
} else {
|
|
22
|
+
this.id = args.id;
|
|
23
|
+
}
|
|
24
|
+
this._owned = true;
|
|
25
|
+
registry.register(this, { Render: this.Render, X, id: this.id }, this);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setFilter(name, params) {
|
|
29
|
+
this.Render.SetPictureFilter(this.id, name, params);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
setBlurFilter(radius, sigma) {
|
|
33
|
+
if (radius === 0) {
|
|
34
|
+
return this.setFilter('convolution', [1, 1, 1]);
|
|
35
|
+
}
|
|
36
|
+
if (!sigma) sigma = radius / 2;
|
|
37
|
+
if (radius % 2 === 0) radius++;
|
|
38
|
+
const params = [radius, radius, ...gaussianKernel(radius, sigma)];
|
|
39
|
+
this.setFilter('convolution', params);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
destroy() {
|
|
43
|
+
if (!this._owned) return;
|
|
44
|
+
this._owned = false;
|
|
45
|
+
registry.unregister(this);
|
|
46
|
+
safeRelease(this.X, () => {
|
|
47
|
+
this.Render.FreePicture(this.id);
|
|
48
|
+
this.X.ReleaseID(this.id);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Stop tracking without sending FreePicture. Used when the server already
|
|
54
|
+
* destroyed the picture implicitly — pictures on a *window* are freed by
|
|
55
|
+
* the server when the window is destroyed (RENDER spec); a later explicit
|
|
56
|
+
* FreePicture would raise BadPicture. Pixmap-backed pictures don't need
|
|
57
|
+
* this (pixmap storage is refcounted).
|
|
58
|
+
*/
|
|
59
|
+
forget() {
|
|
60
|
+
if (!this._owned) return;
|
|
61
|
+
this._owned = false;
|
|
62
|
+
registry.unregister(this);
|
|
63
|
+
safeRelease(this.X, () => this.X.ReleaseID(this.id));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
[Symbol.dispose]() {
|
|
67
|
+
this.destroy();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// normalized 2d gaussian, row-major size x size
|
|
72
|
+
function gaussianKernel(size, sigma) {
|
|
73
|
+
const kernel = new Array(size * size);
|
|
74
|
+
const center = (size - 1) / 2;
|
|
75
|
+
let sum = 0;
|
|
76
|
+
for (let y = 0; y < size; ++y) {
|
|
77
|
+
for (let x = 0; x < size; ++x) {
|
|
78
|
+
const v = Math.exp(-((x - center) ** 2 + (y - center) ** 2) / (2 * sigma * sigma));
|
|
79
|
+
kernel[y * size + x] = v;
|
|
80
|
+
sum += v;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return kernel.map((v) => v / sum);
|
|
84
|
+
}
|
package/lib/pixmap.js
CHANGED
|
@@ -1,39 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
this.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
X.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
1
|
+
import { safeRelease } from './cleanup.js';
|
|
2
|
+
import Drawable from './drawable.js';
|
|
3
|
+
|
|
4
|
+
// GC fallback: free the server-side pixmap when the wrapper is collected
|
|
5
|
+
// without an explicit destroy()
|
|
6
|
+
const registry = new FinalizationRegistry(({ X, id }) => {
|
|
7
|
+
safeRelease(X, () => {
|
|
8
|
+
X.FreePixmap(id);
|
|
9
|
+
X.ReleaseID(id);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export default class Pixmap extends Drawable {
|
|
14
|
+
constructor(app, args = {}) {
|
|
15
|
+
super();
|
|
16
|
+
this.app = app;
|
|
17
|
+
const X = app.X;
|
|
18
|
+
this.X = X;
|
|
19
|
+
this.display = app.display;
|
|
20
|
+
|
|
21
|
+
const parentId = args.parent ? args.parent.id : app.display.screen[0].root;
|
|
22
|
+
this.depth = args.depth || 24;
|
|
23
|
+
|
|
24
|
+
if (!args.id) {
|
|
25
|
+
this.id = X.AllocID();
|
|
26
|
+
X.CreatePixmap(this.id, parentId, this.depth, args.width, args.height);
|
|
27
|
+
this.width = args.width;
|
|
28
|
+
this.height = args.height;
|
|
29
|
+
this._owned = true;
|
|
30
|
+
registry.register(this, { X, id: this.id }, this);
|
|
31
|
+
} else {
|
|
32
|
+
this.id = args.id;
|
|
33
|
+
this._owned = false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
destroy() {
|
|
38
|
+
if (!this._owned) return;
|
|
39
|
+
this._owned = false;
|
|
40
|
+
registry.unregister(this);
|
|
41
|
+
safeRelease(this.X, () => {
|
|
42
|
+
this.X.FreePixmap(this.id);
|
|
43
|
+
this.X.ReleaseID(this.id);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
[Symbol.dispose]() {
|
|
48
|
+
this.destroy();
|
|
49
|
+
}
|
|
50
|
+
}
|
package/lib/rasterize.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// Minimal pure-JS scanline rasterizer for glyph outlines.
|
|
2
|
+
//
|
|
3
|
+
// Takes path commands in y-down pixel coordinates with the origin at the
|
|
4
|
+
// glyph baseline (produced by Font.rasterize from fontkit outlines) and
|
|
5
|
+
// produces an antialiased 8-bit alpha bitmap suitable for XRender
|
|
6
|
+
// AddGlyphs. Non-zero winding rule, 4x4 supersampling.
|
|
7
|
+
|
|
8
|
+
const DEFAULT_SS = 4; // supersampling factor per axis
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {Array} commands path commands ({type: M|L|Q|C|Z, x, y, x1, y1, x2, y2})
|
|
12
|
+
* @param {number} [ss] supersampling factor per axis (large glyphs pass 2 —
|
|
13
|
+
* AA quality is indistinguishable above ~96px and rasterization is 4x cheaper)
|
|
14
|
+
* @returns {{width, height, left, top, data: Buffer}|null}
|
|
15
|
+
* left/top are offsets of the bitmap's top-left corner relative to the
|
|
16
|
+
* glyph origin (left is typically small, top is typically negative —
|
|
17
|
+
* above the baseline). Returns null for empty outlines (e.g. space).
|
|
18
|
+
*/
|
|
19
|
+
export function rasterizePath(commands, ss = DEFAULT_SS) {
|
|
20
|
+
const SS = ss;
|
|
21
|
+
const polys = flatten(commands);
|
|
22
|
+
if (polys.length === 0) return null;
|
|
23
|
+
|
|
24
|
+
let minX = Infinity;
|
|
25
|
+
let minY = Infinity;
|
|
26
|
+
let maxX = -Infinity;
|
|
27
|
+
let maxY = -Infinity;
|
|
28
|
+
for (const poly of polys) {
|
|
29
|
+
for (let i = 0; i < poly.length; i += 2) {
|
|
30
|
+
if (poly[i] < minX) minX = poly[i];
|
|
31
|
+
if (poly[i] > maxX) maxX = poly[i];
|
|
32
|
+
if (poly[i + 1] < minY) minY = poly[i + 1];
|
|
33
|
+
if (poly[i + 1] > maxY) maxY = poly[i + 1];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const left = Math.floor(minX);
|
|
38
|
+
const top = Math.floor(minY);
|
|
39
|
+
const width = Math.ceil(maxX) - left;
|
|
40
|
+
const height = Math.ceil(maxY) - top;
|
|
41
|
+
if (width <= 0 || height <= 0) return null;
|
|
42
|
+
|
|
43
|
+
// pad rows to 4 bytes as required by the AddGlyphs wire format
|
|
44
|
+
const stride = (width + 3) & ~3;
|
|
45
|
+
const coverage = new Uint16Array(stride * height);
|
|
46
|
+
|
|
47
|
+
// closed edges in supersampled bitmap space
|
|
48
|
+
const edges = [];
|
|
49
|
+
for (const poly of polys) {
|
|
50
|
+
const n = poly.length / 2;
|
|
51
|
+
for (let i = 0; i < n; ++i) {
|
|
52
|
+
const j = (i + 1) % n;
|
|
53
|
+
const x0 = (poly[i * 2] - left) * SS;
|
|
54
|
+
const y0 = (poly[i * 2 + 1] - top) * SS;
|
|
55
|
+
const x1 = (poly[j * 2] - left) * SS;
|
|
56
|
+
const y1 = (poly[j * 2 + 1] - top) * SS;
|
|
57
|
+
if (y0 !== y1) edges.push([x0, y0, x1, y1]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const crossings = [];
|
|
62
|
+
for (let row = 0; row < height * SS; ++row) {
|
|
63
|
+
const y = row + 0.5;
|
|
64
|
+
crossings.length = 0;
|
|
65
|
+
for (const [x0, y0, x1, y1] of edges) {
|
|
66
|
+
if ((y0 <= y && y1 > y) || (y1 <= y && y0 > y)) {
|
|
67
|
+
const x = x0 + ((y - y0) * (x1 - x0)) / (y1 - y0);
|
|
68
|
+
crossings.push([x, y1 > y0 ? 1 : -1]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
crossings.sort((a, b) => a[0] - b[0]);
|
|
72
|
+
|
|
73
|
+
let winding = 0;
|
|
74
|
+
let spanStart = 0;
|
|
75
|
+
const py = (row / SS) | 0;
|
|
76
|
+
for (const [x, dir] of crossings) {
|
|
77
|
+
if (winding === 0) spanStart = x;
|
|
78
|
+
winding += dir;
|
|
79
|
+
if (winding === 0) {
|
|
80
|
+
// fill supersample cells whose centers are inside [spanStart, x)
|
|
81
|
+
const from = Math.max(0, Math.ceil(spanStart - 0.5));
|
|
82
|
+
const to = Math.min(width * SS - 1, Math.floor(x - 0.5));
|
|
83
|
+
for (let cell = from; cell <= to; ++cell) {
|
|
84
|
+
coverage[py * stride + ((cell / SS) | 0)]++;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const data = Buffer.alloc(stride * height);
|
|
91
|
+
const full = SS * SS;
|
|
92
|
+
for (let i = 0; i < coverage.length; ++i) {
|
|
93
|
+
const c = coverage[i];
|
|
94
|
+
data[i] = c >= full ? 255 : ((c * 255) / full) | 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { width: stride, height, left, top, data };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// flatten M/L/Q/C/Z commands into closed polygons [x0,y0, x1,y1, ...]
|
|
101
|
+
// (shared with the vector/trapezoid glyph path — see lib/trapezoid.js)
|
|
102
|
+
export function flatten(commands) {
|
|
103
|
+
const polys = [];
|
|
104
|
+
let cur = null;
|
|
105
|
+
let x = 0;
|
|
106
|
+
let y = 0;
|
|
107
|
+
|
|
108
|
+
const finish = () => {
|
|
109
|
+
if (cur && cur.length >= 6) polys.push(cur);
|
|
110
|
+
cur = null;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
for (const c of commands) {
|
|
114
|
+
switch (c.type) {
|
|
115
|
+
case 'M':
|
|
116
|
+
finish();
|
|
117
|
+
cur = [c.x, c.y];
|
|
118
|
+
x = c.x;
|
|
119
|
+
y = c.y;
|
|
120
|
+
break;
|
|
121
|
+
case 'L':
|
|
122
|
+
cur.push(c.x, c.y);
|
|
123
|
+
x = c.x;
|
|
124
|
+
y = c.y;
|
|
125
|
+
break;
|
|
126
|
+
case 'Q': {
|
|
127
|
+
const [x0, y0] = [x, y];
|
|
128
|
+
const steps = curveSteps(x0, y0, c.x1, c.y1, c.x, c.y);
|
|
129
|
+
for (let i = 1; i <= steps; ++i) {
|
|
130
|
+
const t = i / steps;
|
|
131
|
+
const mt = 1 - t;
|
|
132
|
+
cur.push(
|
|
133
|
+
mt * mt * x0 + 2 * mt * t * c.x1 + t * t * c.x,
|
|
134
|
+
mt * mt * y0 + 2 * mt * t * c.y1 + t * t * c.y
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
x = c.x;
|
|
138
|
+
y = c.y;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case 'C': {
|
|
142
|
+
const [x0, y0] = [x, y];
|
|
143
|
+
const steps = curveSteps(x0, y0, c.x1, c.y1, c.x, c.y, c.x2, c.y2);
|
|
144
|
+
for (let i = 1; i <= steps; ++i) {
|
|
145
|
+
const t = i / steps;
|
|
146
|
+
const mt = 1 - t;
|
|
147
|
+
cur.push(
|
|
148
|
+
mt * mt * mt * x0 + 3 * mt * mt * t * c.x1 + 3 * mt * t * t * c.x2 + t * t * t * c.x,
|
|
149
|
+
mt * mt * mt * y0 + 3 * mt * mt * t * c.y1 + 3 * mt * t * t * c.y2 + t * t * t * c.y
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
x = c.x;
|
|
153
|
+
y = c.y;
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case 'Z':
|
|
157
|
+
finish();
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
finish();
|
|
162
|
+
return polys;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function curveSteps(...coords) {
|
|
166
|
+
let minX = Infinity;
|
|
167
|
+
let minY = Infinity;
|
|
168
|
+
let maxX = -Infinity;
|
|
169
|
+
let maxY = -Infinity;
|
|
170
|
+
for (let i = 0; i < coords.length; i += 2) {
|
|
171
|
+
minX = Math.min(minX, coords[i]);
|
|
172
|
+
maxX = Math.max(maxX, coords[i]);
|
|
173
|
+
minY = Math.min(minY, coords[i + 1]);
|
|
174
|
+
maxY = Math.max(maxY, coords[i + 1]);
|
|
175
|
+
}
|
|
176
|
+
const size = Math.max(maxX - minX, maxY - minY);
|
|
177
|
+
return Math.min(24, Math.max(3, Math.ceil(size / 3)));
|
|
178
|
+
}
|