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/fontconfig.js
CHANGED
|
@@ -1,17 +1,123 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
// node:child_process is fetched lazily (not via static import) so that
|
|
2
|
+
// browser bundles of the package never try to resolve it; in non-node
|
|
3
|
+
// environments use a custom FontSource instead (see text/fontsource.js).
|
|
4
|
+
function execFileSync(...args) {
|
|
5
|
+
const cp = globalThis.process?.getBuiltinModule?.('node:child_process');
|
|
6
|
+
if (!cp) {
|
|
7
|
+
throw new Error(
|
|
8
|
+
'fontconfig matching needs node (fc-match CLI); in this environment pass a custom fontSource — see docs/fonts.md'
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
return cp.execFileSync(...args);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// css weight -> fontconfig weight constants
|
|
15
|
+
const cssToFcWeight = {
|
|
16
|
+
100: 0, // thin
|
|
17
|
+
200: 40, // extralight
|
|
18
|
+
300: 50, // light
|
|
19
|
+
400: 80, // regular
|
|
20
|
+
500: 100, // medium
|
|
21
|
+
600: 180, // demibold
|
|
22
|
+
700: 200, // bold
|
|
23
|
+
800: 205, // extrabold
|
|
24
|
+
900: 210 // black
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// formats fontkit can parse
|
|
28
|
+
const supported = /\.(ttf|otf|woff|woff2|ttc|dfont)$/i;
|
|
29
|
+
|
|
30
|
+
const sortedCache = new Map();
|
|
31
|
+
|
|
32
|
+
function patternFor({ family, weight, style }) {
|
|
33
|
+
let fc = family || 'sans-serif';
|
|
34
|
+
const fcWeight = normalizeWeight(weight);
|
|
35
|
+
if (fcWeight !== undefined) fc += `:weight=${fcWeight}`;
|
|
36
|
+
if (style && style.includes('italic')) fc += ':slant=italic';
|
|
37
|
+
return fc;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Full fontconfig match list for a pattern, best match first — this is the
|
|
42
|
+
* system's font fallback chain. Each candidate carries the unicode coverage
|
|
43
|
+
* fontconfig knows about (`charset`, lazily parsed via `charsetHas`), so a
|
|
44
|
+
* fallback font for a codepoint can be chosen without opening font files.
|
|
45
|
+
*
|
|
46
|
+
* Cached per pattern; one fc-match invocation (~50ms) per distinct pattern.
|
|
47
|
+
*
|
|
48
|
+
* @returns {Array<{path, postscriptName, charset: string}>}
|
|
49
|
+
*/
|
|
50
|
+
export function matchSortedSync(pattern) {
|
|
51
|
+
const fc = patternFor(pattern);
|
|
52
|
+
let list = sortedCache.get(fc);
|
|
53
|
+
if (list) return list;
|
|
54
|
+
|
|
55
|
+
const out = execFileSync(
|
|
56
|
+
'fc-match',
|
|
57
|
+
['-s', '--format', '%{file}\t%{postscriptname}\t%{charset}\n', fc],
|
|
58
|
+
{ encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }
|
|
59
|
+
);
|
|
60
|
+
list = [];
|
|
61
|
+
for (const line of out.split('\n')) {
|
|
62
|
+
const [path, postscriptName, charset] = line.split('\t');
|
|
63
|
+
if (path && supported.test(path)) {
|
|
64
|
+
list.push({ path, postscriptName, charset: charset || '', _ranges: null });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (list.length === 0) {
|
|
68
|
+
throw new Error(`No usable font found for pattern "${fc}" (need .ttf/.otf/.ttc)`);
|
|
69
|
+
}
|
|
70
|
+
sortedCache.set(fc, list);
|
|
71
|
+
return list;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Resolve a font pattern ({family, weight, style}) to the best matching font
|
|
76
|
+
* file. Returns { path, postscriptName } or throws if nothing suitable is
|
|
77
|
+
* installed. Requires the fc-match CLI (fontconfig), present on any system
|
|
78
|
+
* running X11.
|
|
79
|
+
*/
|
|
80
|
+
export function listFontsSync(pattern) {
|
|
81
|
+
const [best] = matchSortedSync(pattern);
|
|
82
|
+
return { path: best.path, postscriptName: best.postscriptName };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Does a fc-match candidate's charset cover a codepoint?
|
|
87
|
+
* The charset string is fontconfig's range format: "20-7e a0-ff 131 ...".
|
|
88
|
+
*/
|
|
89
|
+
export function charsetHas(candidate, codepoint) {
|
|
90
|
+
if (candidate._ranges === null) {
|
|
91
|
+
const ranges = [];
|
|
92
|
+
for (const part of candidate.charset.split(' ')) {
|
|
93
|
+
if (!part) continue;
|
|
94
|
+
const dash = part.indexOf('-');
|
|
95
|
+
if (dash === -1) {
|
|
96
|
+
const v = parseInt(part, 16);
|
|
97
|
+
ranges.push(v, v);
|
|
98
|
+
} else {
|
|
99
|
+
ranges.push(parseInt(part.slice(0, dash), 16), parseInt(part.slice(dash + 1), 16));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
candidate._ranges = ranges;
|
|
103
|
+
}
|
|
104
|
+
const r = candidate._ranges;
|
|
105
|
+
let lo = 0;
|
|
106
|
+
let hi = r.length / 2 - 1;
|
|
107
|
+
while (lo <= hi) {
|
|
108
|
+
const mid = (lo + hi) >> 1;
|
|
109
|
+
if (codepoint < r[mid * 2]) hi = mid - 1;
|
|
110
|
+
else if (codepoint > r[mid * 2 + 1]) lo = mid + 1;
|
|
111
|
+
else return true;
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function normalizeWeight(weight) {
|
|
117
|
+
if (weight === undefined) return undefined;
|
|
118
|
+
if (weight === 'normal') return cssToFcWeight[400];
|
|
119
|
+
if (weight === 'bold') return cssToFcWeight[700];
|
|
120
|
+
const n = parseInt(weight, 10);
|
|
121
|
+
if (Number.isNaN(n)) return undefined;
|
|
122
|
+
return cssToFcWeight[n] !== undefined ? cssToFcWeight[n] : n;
|
|
123
|
+
}
|
package/lib/glyphset.js
CHANGED
|
@@ -1,21 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
X.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
import { safeRelease } from './cleanup.js';
|
|
2
|
+
|
|
3
|
+
const registry = new FinalizationRegistry(({ Render, X, id }) => {
|
|
4
|
+
safeRelease(X, () => {
|
|
5
|
+
Render.FreeGlyphSet(id);
|
|
6
|
+
X.ReleaseID(id);
|
|
7
|
+
});
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export default class GlyphSet {
|
|
11
|
+
constructor(app, format) {
|
|
12
|
+
const X = app.X;
|
|
13
|
+
this.X = X;
|
|
14
|
+
this.display = app.display;
|
|
15
|
+
this.Render = app.display.Render;
|
|
16
|
+
this.id = X.AllocID();
|
|
17
|
+
this.Render.CreateGlyphSet(this.id, format || this.Render.a8);
|
|
18
|
+
this._owned = true;
|
|
19
|
+
registry.register(this, { Render: this.Render, X, id: this.id }, this);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
addGlyphs(glyphs) {
|
|
23
|
+
if (glyphs.length === 0) return;
|
|
24
|
+
this.Render.AddGlyphs(this.id, glyphs);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
destroy() {
|
|
28
|
+
if (!this._owned) return;
|
|
29
|
+
this._owned = false;
|
|
30
|
+
registry.unregister(this);
|
|
31
|
+
safeRelease(this.X, () => {
|
|
32
|
+
this.Render.FreeGlyphSet(this.id);
|
|
33
|
+
this.X.ReleaseID(this.id);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
[Symbol.dispose]() {
|
|
38
|
+
this.destroy();
|
|
39
|
+
}
|
|
40
|
+
}
|
package/lib/image.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import jpeg from 'jpeg-js';
|
|
2
|
+
import { PNG } from 'pngjs';
|
|
3
|
+
|
|
4
|
+
import Picture from './picture.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A decoded raster image: `width`, `height` and non-premultiplied RGBA
|
|
8
|
+
* pixels in `data`. Images are client-side objects independent of any X
|
|
9
|
+
* connection; the first time one is drawn it is uploaded to that server
|
|
10
|
+
* as a 32-bit pixmap and cached, so repeated draws are a single
|
|
11
|
+
* server-side composite.
|
|
12
|
+
*
|
|
13
|
+
* Create with `loadImage(pathOrBuffer)` / `decodeImage(buffer)`, or from
|
|
14
|
+
* raw pixels: `new Image({ width, height, data })`.
|
|
15
|
+
*/
|
|
16
|
+
export class Image {
|
|
17
|
+
constructor({ width, height, data }) {
|
|
18
|
+
if (!Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0) {
|
|
19
|
+
throw new Error('Image: width and height must be positive integers');
|
|
20
|
+
}
|
|
21
|
+
if (!data || data.length !== width * height * 4) {
|
|
22
|
+
throw new Error(`Image: data must be RGBA, ${width * height * 4} bytes`);
|
|
23
|
+
}
|
|
24
|
+
this.width = width;
|
|
25
|
+
this.height = height;
|
|
26
|
+
this.data = data;
|
|
27
|
+
this._uploads = new Map(); // App -> { pixmap, picture }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The server-side Picture for this image on `app`'s display (uploaded
|
|
32
|
+
* and cached on first use). Used by `ctx.drawImage`; useful directly
|
|
33
|
+
* when compositing manually via the Render extension.
|
|
34
|
+
*/
|
|
35
|
+
picture(app) {
|
|
36
|
+
let upload = this._uploads.get(app);
|
|
37
|
+
if (upload) return upload.picture;
|
|
38
|
+
|
|
39
|
+
const X = app.X;
|
|
40
|
+
const Render = app.display.Render;
|
|
41
|
+
const pixmap = app.createPixmap({ depth: 32, width: this.width, height: this.height });
|
|
42
|
+
|
|
43
|
+
// XRender composites premultiplied alpha; PutImage on a 32-bit ZPixmap
|
|
44
|
+
// wants BGRA byte order (see AGENTS.md gotchas)
|
|
45
|
+
const bgra = Buffer.allocUnsafe(this.width * this.height * 4);
|
|
46
|
+
const src = this.data;
|
|
47
|
+
for (let i = 0; i < bgra.length; i += 4) {
|
|
48
|
+
const a = src[i + 3];
|
|
49
|
+
if (a === 255) {
|
|
50
|
+
bgra[i] = src[i + 2];
|
|
51
|
+
bgra[i + 1] = src[i + 1];
|
|
52
|
+
bgra[i + 2] = src[i];
|
|
53
|
+
} else {
|
|
54
|
+
bgra[i] = (src[i + 2] * a + 127) / 255;
|
|
55
|
+
bgra[i + 1] = (src[i + 1] * a + 127) / 255;
|
|
56
|
+
bgra[i + 2] = (src[i] * a + 127) / 255;
|
|
57
|
+
}
|
|
58
|
+
bgra[i + 3] = a;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// node-x11 has no FreeGC — share one upload GC per app (valid for any
|
|
62
|
+
// depth-32 drawable on the screen)
|
|
63
|
+
let gc = app._imageUploadGC;
|
|
64
|
+
if (!gc) {
|
|
65
|
+
gc = app._imageUploadGC = X.AllocID();
|
|
66
|
+
X.CreateGC(gc, pixmap.id);
|
|
67
|
+
}
|
|
68
|
+
// stay under the server's maximum request size by uploading row bands
|
|
69
|
+
const stride = this.width * 4;
|
|
70
|
+
const maxBytes = ((app.display.max_request_length ?? 65535) - 8) * 4;
|
|
71
|
+
const rowsPerBand = Math.max(1, Math.floor(maxBytes / stride));
|
|
72
|
+
for (let y = 0; y < this.height; y += rowsPerBand) {
|
|
73
|
+
const rows = Math.min(rowsPerBand, this.height - y);
|
|
74
|
+
X.PutImage(2, pixmap.id, gc, this.width, rows, 0, y, 0, 32, bgra.subarray(y * stride, (y + rows) * stride));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const picture = new Picture(app, { drawable: pixmap, format: Render.rgba32 });
|
|
78
|
+
upload = { pixmap, picture };
|
|
79
|
+
this._uploads.set(app, upload);
|
|
80
|
+
return picture;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** free server-side copies of this image (safe to draw again afterwards) */
|
|
84
|
+
destroy() {
|
|
85
|
+
for (const { pixmap, picture } of this._uploads.values()) {
|
|
86
|
+
picture.destroy();
|
|
87
|
+
pixmap.destroy();
|
|
88
|
+
}
|
|
89
|
+
this._uploads.clear();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
[Symbol.dispose]() {
|
|
93
|
+
this.destroy();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const PNG_MAGIC = [0x89, 0x50, 0x4e, 0x47];
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Decode a PNG or JPEG buffer (sniffed by magic bytes) into an Image.
|
|
101
|
+
*
|
|
102
|
+
* @param {Buffer|Uint8Array} buffer encoded image bytes
|
|
103
|
+
* @returns {Image}
|
|
104
|
+
*/
|
|
105
|
+
export function decodeImage(buffer) {
|
|
106
|
+
const buf = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
|
|
107
|
+
if (buf.length > 4 && PNG_MAGIC.every((b, i) => buf[i] === b)) {
|
|
108
|
+
const png = PNG.sync.read(buf);
|
|
109
|
+
return new Image({ width: png.width, height: png.height, data: png.data });
|
|
110
|
+
}
|
|
111
|
+
if (buf.length > 2 && buf[0] === 0xff && buf[1] === 0xd8) {
|
|
112
|
+
const img = jpeg.decode(buf, { useTArray: true, maxMemoryUsageInMB: 512 });
|
|
113
|
+
return new Image({ width: img.width, height: img.height, data: Buffer.from(img.data) });
|
|
114
|
+
}
|
|
115
|
+
throw new Error('decodeImage: unsupported image format (PNG and JPEG are supported)');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Load and decode a PNG or JPEG image.
|
|
120
|
+
*
|
|
121
|
+
* const img = await loadImage('logo.png');
|
|
122
|
+
* ctx.drawImage(img, 10, 10);
|
|
123
|
+
*
|
|
124
|
+
* @param {string|URL|Buffer|Uint8Array} source file path (or file URL), or
|
|
125
|
+
* an in-memory buffer of encoded bytes
|
|
126
|
+
* @returns {Promise<Image>}
|
|
127
|
+
*/
|
|
128
|
+
export async function loadImage(source) {
|
|
129
|
+
if (typeof source === 'string' || source instanceof URL) {
|
|
130
|
+
// lazy builtin lookup: browser bundles must not depend on node:fs
|
|
131
|
+
const fsp = globalThis.process?.getBuiltinModule?.('node:fs/promises');
|
|
132
|
+
if (!fsp) {
|
|
133
|
+
throw new Error('loadImage: file paths need node — pass the encoded bytes instead');
|
|
134
|
+
}
|
|
135
|
+
return decodeImage(await fsp.readFile(source));
|
|
136
|
+
}
|
|
137
|
+
return decodeImage(source);
|
|
138
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -1,57 +1,123 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
require('
|
|
57
|
-
require('
|
|
1
|
+
import x11 from 'x11';
|
|
2
|
+
|
|
3
|
+
import App from './app.js';
|
|
4
|
+
import Window from './window.js';
|
|
5
|
+
import Pixmap from './pixmap.js';
|
|
6
|
+
import Picture from './picture.js';
|
|
7
|
+
import { Image, decodeImage, loadImage } from './image.js';
|
|
8
|
+
import { Path2D, parseSvgPath } from './path.js';
|
|
9
|
+
import Font from './text/font.js';
|
|
10
|
+
import FontManager from './text/fontmanager.js';
|
|
11
|
+
import {
|
|
12
|
+
FontconfigFontSource,
|
|
13
|
+
StaticFontSource,
|
|
14
|
+
defaultFontSource,
|
|
15
|
+
setDefaultFontSource
|
|
16
|
+
} from './text/fontsource.js';
|
|
17
|
+
import { TextLayout } from './text/layout.js';
|
|
18
|
+
import HtmlView from './widgets/htmlview.js';
|
|
19
|
+
import SvgView from './widgets/svgview.js';
|
|
20
|
+
import MarkdownView from './widgets/markdownview.js';
|
|
21
|
+
import TexView, { configureTex, layoutTex, TexBox } from './widgets/tex.js';
|
|
22
|
+
import { tokenize as highlightCode } from './widgets/highlight.js';
|
|
23
|
+
|
|
24
|
+
// rendering context modules register themselves on Drawable
|
|
25
|
+
import './renderingcontext_x11.js';
|
|
26
|
+
import './renderingcontext_2d.js';
|
|
27
|
+
import './renderingcontext_opengl.js';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Connect to the X server and resolve with an App.
|
|
31
|
+
*
|
|
32
|
+
* const app = await createClient();
|
|
33
|
+
*
|
|
34
|
+
* The legacy node-style callback is still supported:
|
|
35
|
+
*
|
|
36
|
+
* createClient((err, app) => { ... });
|
|
37
|
+
*
|
|
38
|
+
* @param {object} [options] passed through to x11.createClient
|
|
39
|
+
* (e.g. { display: ':1' }); ntk additionally understands
|
|
40
|
+
* { fontSource } — a pluggable system-font lookup (see docs/fonts.md) —
|
|
41
|
+
* and { glxVisual } — the visual id for getContext('opengl') in
|
|
42
|
+
* environments where `glxinfo` cannot be shelled out to
|
|
43
|
+
* @param {function} [callback] optional node-style callback
|
|
44
|
+
* @returns {Promise<App>}
|
|
45
|
+
*/
|
|
46
|
+
export function createClient(options, callback) {
|
|
47
|
+
if (typeof options === 'function') {
|
|
48
|
+
callback = options;
|
|
49
|
+
options = {};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const promise = new Promise((resolve, reject) => {
|
|
53
|
+
x11.createClient(options || {}, (error, display) => {
|
|
54
|
+
if (error) return reject(error);
|
|
55
|
+
|
|
56
|
+
display.client.require('glx', (glxError, GLX) => {
|
|
57
|
+
display.client.require('render', (renderError, Render) => {
|
|
58
|
+
if (renderError) return reject(renderError);
|
|
59
|
+
// preload Render and GLX; GLX is optional ( indirect GLX is often
|
|
60
|
+
// disabled on modern servers )
|
|
61
|
+
display.Render = Render;
|
|
62
|
+
display.GLX = glxError ? null : GLX;
|
|
63
|
+
const X = display.client;
|
|
64
|
+
X.keycode2keysyms = {};
|
|
65
|
+
|
|
66
|
+
function updateKeyboardMapping(min, max) {
|
|
67
|
+
if (max <= min) return;
|
|
68
|
+
X.GetKeyboardMapping(min, max - min, (err, list) => {
|
|
69
|
+
if (err) return;
|
|
70
|
+
for (let i = 0; i < list.length; ++i) X.keycode2keysyms[i + min] = list[i].slice();
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
X.on('event', (ev) => {
|
|
75
|
+
// MappingNotify
|
|
76
|
+
if (ev.type === 34) {
|
|
77
|
+
updateKeyboardMapping(ev.firstKeyCode, ev.firstKeyCode + ev.count);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
updateKeyboardMapping(display.min_keycode, display.max_keycode);
|
|
81
|
+
|
|
82
|
+
resolve(new App(display, options || {}));
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (callback) {
|
|
89
|
+
promise.then(
|
|
90
|
+
(app) => callback(null, app),
|
|
91
|
+
(err) => callback(err)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return promise;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export {
|
|
98
|
+
App,
|
|
99
|
+
Window,
|
|
100
|
+
Pixmap,
|
|
101
|
+
Picture,
|
|
102
|
+
Image,
|
|
103
|
+
decodeImage,
|
|
104
|
+
loadImage,
|
|
105
|
+
Path2D,
|
|
106
|
+
parseSvgPath,
|
|
107
|
+
Font,
|
|
108
|
+
FontManager,
|
|
109
|
+
FontconfigFontSource,
|
|
110
|
+
StaticFontSource,
|
|
111
|
+
defaultFontSource,
|
|
112
|
+
setDefaultFontSource,
|
|
113
|
+
TextLayout,
|
|
114
|
+
HtmlView,
|
|
115
|
+
SvgView,
|
|
116
|
+
MarkdownView,
|
|
117
|
+
TexView,
|
|
118
|
+
TexBox,
|
|
119
|
+
layoutTex,
|
|
120
|
+
configureTex,
|
|
121
|
+
highlightCode
|
|
122
|
+
};
|
|
123
|
+
export default { createClient };
|