react-x11 2.22.7 → 2.22.9
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/package.json +2 -2
- package/src/cocoa/fonts.js +159 -1
- package/src/glnodes.js +26 -0
- package/src/gloverlay.js +772 -79
- package/src/nodes/paint.js +11 -2
- package/src/nodes/text.js +7 -22
- package/src/nodes/textinput.js +8 -9
- package/src/win32/fonts.js +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-x11",
|
|
3
|
-
"version": "2.22.
|
|
3
|
+
"version": "2.22.9",
|
|
4
4
|
"description": "react renderer with X11 as a target",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"yoga-layout": "^3.2.1"
|
|
109
109
|
},
|
|
110
110
|
"optionalDependencies": {
|
|
111
|
-
"@windowkit/appkit": "^0.
|
|
111
|
+
"@windowkit/appkit": "^0.15.0",
|
|
112
112
|
"@windowkit/wayland": "^3.1.1",
|
|
113
113
|
"@windowkit/win32": "^0.0.5",
|
|
114
114
|
"dbus-native": "^0.15.1",
|
package/src/cocoa/fonts.js
CHANGED
|
@@ -150,6 +150,15 @@ function unpackLines(lineData = NO_GEOMETRY, runData = NO_GEOMETRY) {
|
|
|
150
150
|
return lines;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
/** Whether two spans read the same, field for field. */
|
|
154
|
+
function sameSpan(a, b) {
|
|
155
|
+
if (a === b) return true;
|
|
156
|
+
const keys = Object.keys(a);
|
|
157
|
+
if (keys.length !== Object.keys(b).length) return false;
|
|
158
|
+
for (const key of keys) if (a[key] !== b[key]) return false;
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
|
|
153
162
|
class CocoaTextLayout {
|
|
154
163
|
constructor(native, raw, text) {
|
|
155
164
|
this._native = native;
|
|
@@ -177,6 +186,118 @@ class CocoaTextLayout {
|
|
|
177
186
|
return t.length - 1;
|
|
178
187
|
}
|
|
179
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Hang each run off the span it was laid out from, as ntk's runs hang:
|
|
191
|
+
* `span` is the caller's own object, markers and all — what a painter
|
|
192
|
+
* reads a link's underline or a code chip's background off — and `run`
|
|
193
|
+
* the face and size it was set in, and its direction. CoreText makes one
|
|
194
|
+
* run of two neighbouring spans with the same attributes, a link beside
|
|
195
|
+
* plain text of the same colour, so such a run is cut where the spans
|
|
196
|
+
* meet, at the offset the native gives for that index.
|
|
197
|
+
*/
|
|
198
|
+
_hangRuns(sources, runOf) {
|
|
199
|
+
this._sources = sources;
|
|
200
|
+
const starts = [];
|
|
201
|
+
let at = 0;
|
|
202
|
+
for (const source of sources) {
|
|
203
|
+
starts.push(at);
|
|
204
|
+
at += String(source.text).length;
|
|
205
|
+
}
|
|
206
|
+
for (const line of this.lines) {
|
|
207
|
+
const runs = [];
|
|
208
|
+
for (const r of line.runs) this._cutRun(r, line, starts, runOf, runs);
|
|
209
|
+
line.runs = runs;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
_cutRun(r, line, starts, runOf, out) {
|
|
214
|
+
// the span holding the run's first unit
|
|
215
|
+
let lo = 0;
|
|
216
|
+
let hi = starts.length - 1;
|
|
217
|
+
while (lo < hi) {
|
|
218
|
+
const mid = (lo + hi + 1) >> 1;
|
|
219
|
+
if (starts[mid] <= r.start) lo = mid;
|
|
220
|
+
else hi = mid - 1;
|
|
221
|
+
}
|
|
222
|
+
const hang = (run, source) => {
|
|
223
|
+
run.span = this._sources[source];
|
|
224
|
+
run.run = runOf(source, run.rtl);
|
|
225
|
+
// which span, for a layout found again with another call's spans; out
|
|
226
|
+
// of sight, so a run is ntk's shape to anything that reads it whole
|
|
227
|
+
Object.defineProperty(run, '_source', { value: source });
|
|
228
|
+
return run;
|
|
229
|
+
};
|
|
230
|
+
let next = lo + 1;
|
|
231
|
+
if (next >= starts.length || starts[next] >= r.end) {
|
|
232
|
+
out.push(hang(r, lo));
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
// where each span that starts inside the run begins, from the line's
|
|
236
|
+
// origin, as the run's own `x` is measured
|
|
237
|
+
const edges = [r.start];
|
|
238
|
+
const at = [r.rtl ? r.x + r.width : r.x];
|
|
239
|
+
const sources = [lo];
|
|
240
|
+
for (; next < starts.length && starts[next] < r.end; next += 1) {
|
|
241
|
+
edges.push(starts[next]);
|
|
242
|
+
at.push(this._native.layoutCaret(this._handle, starts[next]).x - line.x);
|
|
243
|
+
sources.push(next);
|
|
244
|
+
}
|
|
245
|
+
edges.push(r.end);
|
|
246
|
+
at.push(r.rtl ? r.x : r.x + r.width);
|
|
247
|
+
const pieces = [];
|
|
248
|
+
for (let i = 0; i < sources.length; i += 1) {
|
|
249
|
+
const a = at[i];
|
|
250
|
+
const b = at[i + 1];
|
|
251
|
+
pieces.push(
|
|
252
|
+
hang(
|
|
253
|
+
{
|
|
254
|
+
...r,
|
|
255
|
+
start: edges[i],
|
|
256
|
+
end: edges[i + 1],
|
|
257
|
+
x: Math.min(a, b),
|
|
258
|
+
width: Math.abs(b - a),
|
|
259
|
+
},
|
|
260
|
+
sources[i],
|
|
261
|
+
),
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
// a line's runs go left to right, and a right-to-left run's first span
|
|
265
|
+
// is its rightmost piece
|
|
266
|
+
if (r.rtl) pieces.reverse();
|
|
267
|
+
out.push(...pieces);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* This layout for another call's spans: the same text set the same way,
|
|
272
|
+
* which is what found it in the cache. Spans that are the same, or read
|
|
273
|
+
* the same field for field, are the same layout — an immediate-mode caller
|
|
274
|
+
* that builds its spans afresh every paint is answered with the object it
|
|
275
|
+
* had. A caller whose markers differ — a link here, plain text there — has
|
|
276
|
+
* the runs pointed at its own spans, in a copy.
|
|
277
|
+
*/
|
|
278
|
+
_forSources(sources) {
|
|
279
|
+
const mine = this._sources;
|
|
280
|
+
if (
|
|
281
|
+
!mine ||
|
|
282
|
+
(mine.length === sources.length &&
|
|
283
|
+
mine.every((source, i) => sameSpan(source, sources[i])))
|
|
284
|
+
) {
|
|
285
|
+
return this;
|
|
286
|
+
}
|
|
287
|
+
const copy = Object.create(CocoaTextLayout.prototype);
|
|
288
|
+
Object.assign(copy, this);
|
|
289
|
+
copy._sources = sources;
|
|
290
|
+
copy.lines = this.lines.map((line) => ({
|
|
291
|
+
...line,
|
|
292
|
+
runs: line.runs.map((r) => {
|
|
293
|
+
const run = { ...r, span: sources[r._source] };
|
|
294
|
+
Object.defineProperty(run, '_source', { value: r._source });
|
|
295
|
+
return run;
|
|
296
|
+
}),
|
|
297
|
+
}));
|
|
298
|
+
return copy;
|
|
299
|
+
}
|
|
300
|
+
|
|
180
301
|
draw(ctx, x, y) {
|
|
181
302
|
ctx._drawLayout(this, x, y);
|
|
182
303
|
}
|
|
@@ -1084,17 +1205,20 @@ export class CocoaFontManager {
|
|
|
1084
1205
|
// refresh LRU position
|
|
1085
1206
|
this._layouts.delete(signature);
|
|
1086
1207
|
this._layouts.set(signature, hit);
|
|
1087
|
-
return hit;
|
|
1208
|
+
return hit._forSources(spans.filter((span) => String(span.text ?? '')));
|
|
1088
1209
|
}
|
|
1089
1210
|
const { maxWidth, align, lineHeight, maxLines, overflow, direction } =
|
|
1090
1211
|
options;
|
|
1091
1212
|
const nativeSpans = [];
|
|
1213
|
+
// the caller's spans with text, which the runs hang off (`_hangRuns`)
|
|
1214
|
+
const sources = [];
|
|
1092
1215
|
let text = '';
|
|
1093
1216
|
let contextInk = false;
|
|
1094
1217
|
for (const span of spans) {
|
|
1095
1218
|
const t = String(span.text ?? '');
|
|
1096
1219
|
if (!t) continue;
|
|
1097
1220
|
text += t;
|
|
1221
|
+
sources.push(span);
|
|
1098
1222
|
const size = span.size ?? base.size ?? 14;
|
|
1099
1223
|
const variations = span.variations ?? base.variations;
|
|
1100
1224
|
// A span may carry the face itself — an ntk Font from openFont(),
|
|
@@ -1191,6 +1315,40 @@ export class CocoaFontManager {
|
|
|
1191
1315
|
minContent ? laid.map((span) => span.text).join('') : text,
|
|
1192
1316
|
);
|
|
1193
1317
|
layout._contextInk = contextInk;
|
|
1318
|
+
if (!minContent) {
|
|
1319
|
+
const runs = new Map();
|
|
1320
|
+
layout._hangRuns(sources, (i, rtl) => {
|
|
1321
|
+
const key = `${i}|${rtl ? 1 : 0}`;
|
|
1322
|
+
let run = runs.get(key);
|
|
1323
|
+
if (!run) {
|
|
1324
|
+
const span = sources[i];
|
|
1325
|
+
const given = span.font ?? base.font;
|
|
1326
|
+
run = {
|
|
1327
|
+
font:
|
|
1328
|
+
typeof given?.metrics === 'function'
|
|
1329
|
+
? given
|
|
1330
|
+
: this.match(span.family ?? base.family, {
|
|
1331
|
+
weight: span.weight ?? base.weight,
|
|
1332
|
+
style: span.style ?? base.style,
|
|
1333
|
+
}),
|
|
1334
|
+
size: span.size ?? base.size ?? 14,
|
|
1335
|
+
direction: rtl ? 'rtl' : 'ltr',
|
|
1336
|
+
};
|
|
1337
|
+
runs.set(key, run);
|
|
1338
|
+
}
|
|
1339
|
+
return run;
|
|
1340
|
+
});
|
|
1341
|
+
// ntk's `truncated`: whether a line cap dropped anything. An eliding
|
|
1342
|
+
// layout does not say — its last line is cut to the mark, and where
|
|
1343
|
+
// the cut fell is the native's — so a caller asks the line ends.
|
|
1344
|
+
if (!elides) {
|
|
1345
|
+
const last = layout.lines[layout.lines.length - 1];
|
|
1346
|
+
layout.truncated =
|
|
1347
|
+
Number.isFinite(maxLines) &&
|
|
1348
|
+
!!last &&
|
|
1349
|
+
/\S/.test(text.slice(last.end));
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1194
1352
|
this._layouts.set(signature, layout);
|
|
1195
1353
|
if (this._layouts.size > 64) {
|
|
1196
1354
|
this._layouts.delete(this._layouts.keys().next().value);
|
package/src/glnodes.js
CHANGED
|
@@ -38,6 +38,10 @@ function recordGlxFailure(app, err) {
|
|
|
38
38
|
if (app && err && !glxFailures.has(app)) glxFailures.set(app, err);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// ChangeWindowAttributes' backing-store value: the server keeps what the
|
|
42
|
+
// window shows while it is mapped (`_create`)
|
|
43
|
+
const BACKING_STORE_WHEN_MAPPED = 1;
|
|
44
|
+
|
|
41
45
|
// Apps already told that a <glarea>'s children are not drawn on their
|
|
42
46
|
// display. Once per app, as for a <foreign> that cannot embed: the answer
|
|
43
47
|
// belongs to the server, so the next surface to find out carries no news.
|
|
@@ -301,6 +305,19 @@ export class GlAreaNode extends Node {
|
|
|
301
305
|
this.rect = rect;
|
|
302
306
|
this.config = config;
|
|
303
307
|
wnd._reactX11Node = this;
|
|
308
|
+
// The server keeps what the surface shows while it is mapped (X backing
|
|
309
|
+
// store), so a pane over it that moves away — a card dragged across a
|
|
310
|
+
// graph drawn in GL — uncovers the last frame at once. Without it the
|
|
311
|
+
// uncovered pixels were the pane's until the surface drew again: an
|
|
312
|
+
// Expose, a frame requested, and the next vblank, which is a trail of
|
|
313
|
+
// the card behind it on every step. ntk forwards no backing-store
|
|
314
|
+
// attribute (its `backingStore` is its own double buffer), and a
|
|
315
|
+
// backend with no X connection has none to set.
|
|
316
|
+
if (typeof this.app.X?.ChangeWindowAttributes === 'function') {
|
|
317
|
+
this.app.X.ChangeWindowAttributes(wnd.id, {
|
|
318
|
+
backingStore: BACKING_STORE_WHEN_MAPPED,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
304
321
|
// Above everything 2D in the owning window on both backends, and above
|
|
305
322
|
// every surface made before this one: X stacks a new child window over
|
|
306
323
|
// its siblings, and Core Animation a layer added later over one at the
|
|
@@ -328,6 +345,15 @@ export class GlAreaNode extends Node {
|
|
|
328
345
|
this.gl?.ready?.catch((err) => {
|
|
329
346
|
if (!this.destroyed) this._failed(err, { reported: true });
|
|
330
347
|
});
|
|
348
|
+
// …and a context that fails after it was ready — a swap the driver
|
|
349
|
+
// refused mid-life — says so here (ntk >= 8.12.11). Before ntk said so,
|
|
350
|
+
// the surface kept its last frame, or nothing, with no word to anyone:
|
|
351
|
+
// on NVIDIA every resize did that (sidorares/ntk#399).
|
|
352
|
+
if (this.gl && 'onError' in this.gl) {
|
|
353
|
+
this.gl.onError = (err) => {
|
|
354
|
+
if (!this.destroyed) this._failed(err);
|
|
355
|
+
};
|
|
356
|
+
}
|
|
331
357
|
wnd.on?.('expose', () => this.requestFrame());
|
|
332
358
|
wnd.map?.();
|
|
333
359
|
// made on top of its siblings — over the panes of children that were
|