ntk 3.10.0 → 3.10.1
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.
|
@@ -999,6 +999,34 @@ class RenderingContext2d {
|
|
|
999
999
|
|
|
1000
1000
|
// rasterize one clip entry into a temp a8 and In-composite it onto the mask
|
|
1001
1001
|
_intersectClip(entry) {
|
|
1002
|
+
// Intersecting with a *rectangle* is just "keep the inside, clear the
|
|
1003
|
+
// outside", and up to four FillRectangles say that exactly — no temp
|
|
1004
|
+
// pixmap to allocate and free, no rasterization, and no full-surface
|
|
1005
|
+
// Composite. The general path below costs the whole surface per clip
|
|
1006
|
+
// however small the rectangle is, which is what made nested clipping the
|
|
1007
|
+
// dominant cost of a frame: scrolling a table clipped roughly 170 times
|
|
1008
|
+
// per repaint, and at 900x600 that alone was ~92 megapixels of work
|
|
1009
|
+
// before a single glyph was drawn.
|
|
1010
|
+
if (entry.rect) {
|
|
1011
|
+
const r = entry.rect;
|
|
1012
|
+
const x = Math.max(0, Math.min(r.x, this.width));
|
|
1013
|
+
const y = Math.max(0, Math.min(r.y, this.height));
|
|
1014
|
+
const right = Math.max(x, Math.min(r.x + r.w, this.width));
|
|
1015
|
+
const bottom = Math.max(y, Math.min(r.y + r.h, this.height));
|
|
1016
|
+
const outside = [];
|
|
1017
|
+
if (y > 0) outside.push(0, 0, this.width, y);
|
|
1018
|
+
if (bottom < this.height) {
|
|
1019
|
+
outside.push(0, bottom, this.width, this.height - bottom);
|
|
1020
|
+
}
|
|
1021
|
+
if (x > 0) outside.push(0, y, x, bottom - y);
|
|
1022
|
+
if (right < this.width) {
|
|
1023
|
+
outside.push(right, y, this.width - right, bottom - y);
|
|
1024
|
+
}
|
|
1025
|
+
if (outside.length) {
|
|
1026
|
+
this.Render.FillRectangles(this.Render.PictOp.Src, this.clipMask.id, [0, 0, 0, 0], outside);
|
|
1027
|
+
}
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1002
1030
|
const tmpPixmap = new Pixmap(this.window.app, { depth: 8, width: this.width, height: this.height });
|
|
1003
1031
|
const tmpMask = new Picture(this.window.app, { drawable: tmpPixmap, format: this.Render.a8 });
|
|
1004
1032
|
this.Render.FillRectangles(this.Render.PictOp.Src, tmpMask.id, [0, 0, 0, 0], [0, 0, this.width, this.height]);
|
|
@@ -352,14 +352,66 @@ export default class MarkdownView {
|
|
|
352
352
|
}
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
+
// Two widths per column from here on: `cols` is the outer width the grid
|
|
356
|
+
// is drawn at, `inner` is what a cell is laid out at. `inner` is kept
|
|
357
|
+
// alongside rather than derived as `cols[c] - hpad * 2`, and that is not
|
|
358
|
+
// tidiness. Adding a padding and subtracting it again is not the identity
|
|
359
|
+
// in binary floating point — for a natural width of 48.953125 at size 16
|
|
360
|
+
// the round trip returns 48.95312499999999 — and a token handed a maxWidth
|
|
361
|
+
// a few ULPs below its own measured width is *wider than its container*,
|
|
362
|
+
// so TextLayout force-breaks it. That is a `value` header rendered as
|
|
363
|
+
// `valu` over `e`, on some fonts and not others, depending on nothing more
|
|
364
|
+
// than whether the width happens to survive the round trip.
|
|
355
365
|
const cols = natural.map((w) => w + hpad * 2);
|
|
366
|
+
const inner = natural.slice();
|
|
356
367
|
const total = cols.reduce((a, b) => a + b, 0);
|
|
357
368
|
if (total > width) {
|
|
358
|
-
//
|
|
359
|
-
//
|
|
360
|
-
|
|
369
|
+
// Shrink towards each column's *min-content* width — the widest word it
|
|
370
|
+
// holds that cannot be broken — and never past it. The floor used to be
|
|
371
|
+
// a flat 2.5em guess with no relation to the content, so a column whose
|
|
372
|
+
// longest word was wider than that got a maxWidth below the word and the
|
|
373
|
+
// word came apart.
|
|
374
|
+
//
|
|
375
|
+
// Min-content is the widest whitespace-delimited token, each measured
|
|
376
|
+
// *unconstrained*. Laying the whole cell out at a tiny maxWidth looks
|
|
377
|
+
// like the obvious probe and is not one: a token wider than the
|
|
378
|
+
// container is force-broken rather than left to overflow whenever a
|
|
379
|
+
// single cluster fits, so the probe reports a fragment's width. It is
|
|
380
|
+
// not even monotonic in maxWidth — see the test that pins that. An
|
|
381
|
+
// unconstrained token cannot be broken at all.
|
|
382
|
+
//
|
|
383
|
+
// A token split across spans — `**val**ue` — is measured as its halves
|
|
384
|
+
// and so under-measured. Rare, and still far above the flat guess this
|
|
385
|
+
// replaced. Only measured here: a table that fits needs no minimums.
|
|
386
|
+
const content = new Array(ncols).fill(0);
|
|
387
|
+
for (const row of cellSpans) {
|
|
388
|
+
for (let c = 0; c < ncols; c++) {
|
|
389
|
+
for (const span of row[c]) {
|
|
390
|
+
for (const token of String(span.text ?? '').split(/\s+/)) {
|
|
391
|
+
if (!token) continue;
|
|
392
|
+
const measure = new TextLayout(fonts, [{ ...span, text: token }], styles.base, {
|
|
393
|
+
lineHeight: t.lineHeight
|
|
394
|
+
});
|
|
395
|
+
content[c] = Math.max(content[c], measure.width);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
const mins = content.map((w) => w + hpad * 2);
|
|
401
|
+
// Space above the minimums is shared in proportion to how much each
|
|
402
|
+
// column asked for, so a wide column gives up more than a narrow one.
|
|
403
|
+
// When the minimums themselves do not fit, the table overflows rather
|
|
404
|
+
// than breaking words — which is what a browser does with the same
|
|
405
|
+
// content, and is the lesser of the two evils.
|
|
406
|
+
const slack = width - mins.reduce((a, b) => a + b, 0);
|
|
407
|
+
const want = cols.reduce((a, w, c) => a + Math.max(0, w - mins[c]), 0);
|
|
361
408
|
for (let c = 0; c < ncols; c++) {
|
|
362
|
-
cols[c] =
|
|
409
|
+
cols[c] =
|
|
410
|
+
slack <= 0 || want <= 0
|
|
411
|
+
? mins[c]
|
|
412
|
+
: mins[c] + (Math.max(0, cols[c] - mins[c]) / want) * slack;
|
|
413
|
+
// Never below min-content, whatever the arithmetic above produced.
|
|
414
|
+
inner[c] = Math.max(content[c], cols[c] - hpad * 2);
|
|
363
415
|
}
|
|
364
416
|
}
|
|
365
417
|
const tableW = cols.reduce((a, b) => a + b, 0);
|
|
@@ -369,7 +421,7 @@ export default class MarkdownView {
|
|
|
369
421
|
for (let r = 0; r < cellSpans.length; r++) {
|
|
370
422
|
const layouts = cellSpans[r].map((spans, c) =>
|
|
371
423
|
new TextLayout(fonts, spans.length ? spans : '', styles.base, {
|
|
372
|
-
maxWidth:
|
|
424
|
+
maxWidth: inner[c],
|
|
373
425
|
align: block.align[c] ?? 'left',
|
|
374
426
|
lineHeight: t.lineHeight
|
|
375
427
|
})
|