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.
Files changed (47) hide show
  1. package/README.md +156 -151
  2. package/lib/app.js +54 -21
  3. package/lib/cleanup.js +14 -0
  4. package/lib/drawable.js +12 -18
  5. package/lib/events_map.js +92 -98
  6. package/lib/fontconfig.js +123 -17
  7. package/lib/glyphset.js +40 -21
  8. package/lib/image.js +138 -0
  9. package/lib/index.js +123 -57
  10. package/lib/path.js +734 -0
  11. package/lib/picture.js +84 -41
  12. package/lib/pixmap.js +50 -39
  13. package/lib/rasterize.js +178 -0
  14. package/lib/renderingcontext_2d.js +1078 -695
  15. package/lib/renderingcontext_opengl.js +53 -46
  16. package/lib/renderingcontext_x11.js +59 -67
  17. package/lib/text/font.js +196 -0
  18. package/lib/text/fontmanager.js +233 -0
  19. package/lib/text/fontsource.js +179 -0
  20. package/lib/text/glyphs.js +456 -0
  21. package/lib/text/layout.js +356 -0
  22. package/lib/text/shape.js +141 -0
  23. package/lib/trapezoid.js +131 -0
  24. package/lib/widgets/css.js +676 -0
  25. package/lib/widgets/highlight.js +97 -0
  26. package/lib/widgets/htmlview.js +879 -0
  27. package/lib/widgets/markdown.js +148 -0
  28. package/lib/widgets/markdownview.js +487 -0
  29. package/lib/widgets/mermaid.js +720 -0
  30. package/lib/widgets/svgview.js +528 -0
  31. package/lib/widgets/tex.js +681 -0
  32. package/lib/window.js +642 -308
  33. package/package.json +60 -56
  34. package/examples/example-await.js +0 -23
  35. package/examples/example-co.js +0 -13
  36. package/examples/example.js +0 -14
  37. package/examples/fill-styles.js +0 -39
  38. package/examples/glclock.js +0 -100
  39. package/examples/glxpixmap.js +0 -12
  40. package/examples/gradient.js +0 -41
  41. package/examples/package.json +0 -17
  42. package/examples/simpletex.js +0 -72
  43. package/examples/teapot.js +0 -104
  44. package/examples/teapot.json +0 -6
  45. package/examples/text-rendering.js +0 -44
  46. package/lib/fontface.js +0 -103
  47. package/lib/widgets/webview.js +0 -33
package/lib/picture.js CHANGED
@@ -1,41 +1,84 @@
1
- var weak = require('weak-napi');
2
-
3
- function Picture(app, args) {
4
- var X = app.X;
5
- this.X = X;
6
- this.Render = app.display.Render;
7
- var display = app.display;
8
- this.display = display;
9
- if (typeof args.id == 'undefined') {
10
- this.id = X.AllocID();
11
- var format = args.format || this.Render.rgb24;
12
- this.format = format;
13
- this.Render.CreatePicture(this.id, args.drawable.id, format, args);
14
- } else {
15
- this.id = args.id;
16
- }
17
- var id = this.id;
18
- var ref = weak(this, function() {
19
- app.display.Render.FreePicture(id);
20
- X.ReleaseID(id);
21
- });
22
- }
23
-
24
- Picture.prototype.setFilter = function(name, params) {
25
- this.Render.SetPictureFilter(this.id, name, params);
26
- };
27
-
28
- Picture.prototype.setBlurFilter = function(radius, sigma) {
29
- if (radius === 0) {
30
- return this.setFilter('convolution', [1, 1, 1]);
31
- }
32
-
33
- if (!sigma) sigma = radius / 2;
34
- var dim = parseInt(radius);
35
- if (radius % 2 == 0) radius++;
36
- var generateGaussianKernel = require('gaussian-convolution-kernel');
37
- var params = [radius, radius].concat(generateGaussianKernel(radius, sigma));
38
- this.setFilter('convolution', params);
39
- };
40
-
41
- module.exports = Picture;
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
- var util = require('util');
2
- var weak = require('weak-napi');
3
-
4
- var Drawable = require('./drawable.js');
5
-
6
- module.exports = Pixmap;
7
-
8
- function Pixmap(app, args) {
9
- this.app = app;
10
- Drawable.call(this);
11
- var X = app.X;
12
-
13
- var parentId;
14
- if (!args.parent) parentId = app.display.screen[0].root;
15
- else {
16
- parentId = args.parent.id;
17
- }
18
-
19
- this.depth = args.depth || 24;
20
-
21
- if (!args.id) {
22
- this.id = X.AllocID();
23
- X.CreatePixmap(this.id, parentId, this.depth, args.width, args.height);
24
- var id = this.id;
25
- var ref = weak(this, function(obj) {
26
- X.FreePixmap(id);
27
- X.ReleaseID(id);
28
- });
29
- this.width = args.width;
30
- this.height = args.height;
31
- } else {
32
- this.id = args.id;
33
- }
34
-
35
- this.X = X;
36
- var display = app.display;
37
- this.display = display;
38
- }
39
- util.inherits(Pixmap, Drawable);
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
+ }
@@ -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
+ }