ntk 8.17.3 → 8.17.5
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/lib/fontconfig.js +426 -97
- package/lib/renderingcontext_2d.js +173 -61
- package/lib/text/glyphs.js +34 -13
- package/lib/widgets/svgview.js +209 -72
- package/package.json +1 -1
|
@@ -387,15 +387,63 @@ function patternSourceOf(app, source) {
|
|
|
387
387
|
* after a `translate` moves with the fill, and one created under a translate
|
|
388
388
|
* and filled without it does not).
|
|
389
389
|
*
|
|
390
|
-
* Returns false when nothing would be painted (a singular matrix
|
|
391
|
-
* anything else, including every
|
|
390
|
+
* Returns false when nothing would be painted (a singular matrix, or one
|
|
391
|
+
* the wire cannot carry), true for anything else, including every
|
|
392
|
+
* plain-colour style. `width` and `height` are the surface's: the style's
|
|
393
|
+
* source origin is kept on it (`sourceOrigin`).
|
|
392
394
|
*/
|
|
393
|
-
function prepareStyle(src, m) {
|
|
395
|
+
function prepareStyle(src, m, width, height) {
|
|
394
396
|
return src instanceof CanvasPattern || src instanceof CanvasGradient
|
|
395
|
-
? src._sync(m)
|
|
397
|
+
? src._sync(m, width, height)
|
|
396
398
|
: true;
|
|
397
399
|
}
|
|
398
400
|
|
|
401
|
+
const NO_ORIGIN = Object.freeze([0, 0]);
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Where a composite samples `src` from, as a device point: each composite
|
|
405
|
+
* that reads a style at its destination's own coordinates passes those
|
|
406
|
+
* coordinates less this as its source offset.
|
|
407
|
+
*
|
|
408
|
+
* A gradient's or a pattern's picture transform is the inverse of the CTM,
|
|
409
|
+
* and sampling it at device coordinates put the CTM's translation, times
|
|
410
|
+
* its downscale, into the transform: a box a fortieth of its size at x
|
|
411
|
+
* 2,200 asked for 88,000, past what 16.16 fixed point carries. `_sync`
|
|
412
|
+
* folds this origin into the transform instead, and puts it where the
|
|
413
|
+
* style's own origin lands, so the translation is small wherever on the
|
|
414
|
+
* surface the style is painted. Everything else — a solid, a picture —
|
|
415
|
+
* samples at device coordinates, from (0, 0).
|
|
416
|
+
*/
|
|
417
|
+
function sourceOrigin(src) {
|
|
418
|
+
return src instanceof CanvasPattern || src instanceof CanvasGradient
|
|
419
|
+
? src._origin
|
|
420
|
+
: NO_ORIGIN;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* The origin `_sync` picks for a style whose own origin lands at device
|
|
425
|
+
* (x, y): the nearest whole pixel, kept on the surface. On it, a composite's
|
|
426
|
+
* source offset — its destination's, less this — is within the surface's
|
|
427
|
+
* size of 0, which the 16-bit field carries whatever the transform; and the
|
|
428
|
+
* transform's translation is the style's coordinate there, which for a
|
|
429
|
+
* style painted on the surface is one the fill samples anyway.
|
|
430
|
+
*/
|
|
431
|
+
function originOn(x, y, width, height) {
|
|
432
|
+
const on = (v, size) => (v > 0 ? Math.min(Math.round(v), size) : 0);
|
|
433
|
+
return [on(x, width), on(y, height)];
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Whether every entry can be written as XRender's 16.16 fixed point, whose
|
|
438
|
+
* whole part is a signed 16-bit number. A picture transform is written that
|
|
439
|
+
* way, and the request encoder throws on an entry past it — out of the paint
|
|
440
|
+
* that asked for it, and out of everything drawn after it in the same frame.
|
|
441
|
+
* Not finite fails too.
|
|
442
|
+
*/
|
|
443
|
+
function fitsFixed(values) {
|
|
444
|
+
return values.every((v) => Math.abs(v) < 32768);
|
|
445
|
+
}
|
|
446
|
+
|
|
399
447
|
function isPictureSource(image) {
|
|
400
448
|
return (
|
|
401
449
|
image instanceof Image ||
|
|
@@ -2376,14 +2424,15 @@ class RenderingContext2d {
|
|
|
2376
2424
|
);
|
|
2377
2425
|
}
|
|
2378
2426
|
// src is either a 1x1 repeating solid (offset irrelevant) or a
|
|
2379
|
-
//
|
|
2427
|
+
// gradient/pattern, sampled at the same offset from its own origin
|
|
2428
|
+
const [ox, oy] = sourceOrigin(src);
|
|
2380
2429
|
R.Composite(
|
|
2381
2430
|
op,
|
|
2382
2431
|
src.id,
|
|
2383
2432
|
this.fillMask.id,
|
|
2384
2433
|
this._dst(),
|
|
2385
|
-
out.x,
|
|
2386
|
-
out.y,
|
|
2434
|
+
out.x - ox,
|
|
2435
|
+
out.y - oy,
|
|
2387
2436
|
out.x,
|
|
2388
2437
|
out.y,
|
|
2389
2438
|
out.x,
|
|
@@ -2406,7 +2455,7 @@ class RenderingContext2d {
|
|
|
2406
2455
|
op = op ?? this._op();
|
|
2407
2456
|
alpha = alpha ?? this.globalAlpha;
|
|
2408
2457
|
if (alpha <= 0) return;
|
|
2409
|
-
if (!prepareStyle(src, this._m)) return;
|
|
2458
|
+
if (!prepareStyle(src, this._m, this.width, this.height)) return;
|
|
2410
2459
|
|
|
2411
2460
|
// one box per subpath, so a path holding disjoint ones can be masked as
|
|
2412
2461
|
// the pieces it is rather than as the box around all of them — each the
|
|
@@ -2462,7 +2511,7 @@ class RenderingContext2d {
|
|
|
2462
2511
|
_strokePolys(polys, { src = null } = {}) {
|
|
2463
2512
|
src = src ?? this._strokePicture;
|
|
2464
2513
|
if (this.globalAlpha <= 0) return;
|
|
2465
|
-
if (!prepareStyle(src, this._m)) return;
|
|
2514
|
+
if (!prepareStyle(src, this._m, this.width, this.height)) return;
|
|
2466
2515
|
// approximate transform-aware line width by the average scale factor
|
|
2467
2516
|
const det = this._m[0] * this._m[3] - this._m[1] * this._m[2];
|
|
2468
2517
|
const scale = Math.sqrt(Math.abs(det)) || 1;
|
|
@@ -2830,6 +2879,7 @@ class RenderingContext2d {
|
|
|
2830
2879
|
op === this.Render.PictOp.Over;
|
|
2831
2880
|
const chunk = 4000 * 6;
|
|
2832
2881
|
if (direct) {
|
|
2882
|
+
const [ox, oy] = sourceOrigin(src);
|
|
2833
2883
|
this._cullTris(tris);
|
|
2834
2884
|
if (!tris.length) return;
|
|
2835
2885
|
const cut = this._wireCut(tris);
|
|
@@ -2847,11 +2897,12 @@ class RenderingContext2d {
|
|
|
2847
2897
|
// convention drawGlyphRuns and compositeTraps use — without it every
|
|
2848
2898
|
// non-constant stroke style (a gradient, a pattern) is offset by
|
|
2849
2899
|
// wherever the stroke happens to start, and shifts as it moves.
|
|
2900
|
+
// Less the style's own origin, as every fill samples it.
|
|
2850
2901
|
this.Render.Triangles(
|
|
2851
2902
|
op,
|
|
2852
2903
|
src.id,
|
|
2853
|
-
Math.floor(batch[0]),
|
|
2854
|
-
Math.floor(batch[1]),
|
|
2904
|
+
Math.floor(batch[0]) - ox,
|
|
2905
|
+
Math.floor(batch[1]) - oy,
|
|
2855
2906
|
this._dst(),
|
|
2856
2907
|
this.Render.a8,
|
|
2857
2908
|
batch,
|
|
@@ -3117,7 +3168,7 @@ class RenderingContext2d {
|
|
|
3117
3168
|
_drawGlyphsDevice(op, src, positioned) {
|
|
3118
3169
|
const app = this.window.app;
|
|
3119
3170
|
const R = this.Render;
|
|
3120
|
-
if (!prepareStyle(src, this._m)) return;
|
|
3171
|
+
if (!prepareStyle(src, this._m, this.width, this.height)) return;
|
|
3121
3172
|
if (!this._hasPolyClip) {
|
|
3122
3173
|
// Fast path: a rectangular clip is something the server can do itself.
|
|
3123
3174
|
// Two small requests around the ordinary glyph composite, instead of
|
|
@@ -3137,6 +3188,7 @@ class RenderingContext2d {
|
|
|
3137
3188
|
this._dst(),
|
|
3138
3189
|
positioned,
|
|
3139
3190
|
rect ?? { x: 0, y: 0, w: this.width, h: this.height },
|
|
3191
|
+
sourceOrigin(src),
|
|
3140
3192
|
);
|
|
3141
3193
|
if (rect) this._invalidatePictureClip();
|
|
3142
3194
|
this._markDirty();
|
|
@@ -3174,13 +3226,14 @@ class RenderingContext2d {
|
|
|
3174
3226
|
this.width,
|
|
3175
3227
|
this.height,
|
|
3176
3228
|
);
|
|
3229
|
+
const [ox, oy] = sourceOrigin(src);
|
|
3177
3230
|
R.Composite(
|
|
3178
3231
|
op,
|
|
3179
3232
|
src.id,
|
|
3180
3233
|
this.fillMask.id,
|
|
3181
3234
|
this._dst(),
|
|
3182
|
-
|
|
3183
|
-
|
|
3235
|
+
-ox,
|
|
3236
|
+
-oy,
|
|
3184
3237
|
0,
|
|
3185
3238
|
0,
|
|
3186
3239
|
0,
|
|
@@ -3207,7 +3260,16 @@ class RenderingContext2d {
|
|
|
3207
3260
|
if (rect.w === 0 || rect.h === 0) return;
|
|
3208
3261
|
this._setPictureClip(rect);
|
|
3209
3262
|
}
|
|
3210
|
-
compositeTraps(
|
|
3263
|
+
compositeTraps(
|
|
3264
|
+
app,
|
|
3265
|
+
op,
|
|
3266
|
+
src.id,
|
|
3267
|
+
this._dst(),
|
|
3268
|
+
traps,
|
|
3269
|
+
undefined,
|
|
3270
|
+
undefined,
|
|
3271
|
+
sourceOrigin(src),
|
|
3272
|
+
);
|
|
3211
3273
|
if (rect) this._invalidatePictureClip();
|
|
3212
3274
|
this._markDirty();
|
|
3213
3275
|
return;
|
|
@@ -3242,13 +3304,14 @@ class RenderingContext2d {
|
|
|
3242
3304
|
this.width,
|
|
3243
3305
|
this.height,
|
|
3244
3306
|
);
|
|
3307
|
+
const [ox, oy] = sourceOrigin(src);
|
|
3245
3308
|
R.Composite(
|
|
3246
3309
|
op,
|
|
3247
3310
|
src.id,
|
|
3248
3311
|
this.fillMask.id,
|
|
3249
3312
|
this._dst(),
|
|
3250
|
-
|
|
3251
|
-
|
|
3313
|
+
-ox,
|
|
3314
|
+
-oy,
|
|
3252
3315
|
0,
|
|
3253
3316
|
0,
|
|
3254
3317
|
0,
|
|
@@ -3486,7 +3549,8 @@ class RenderingContext2d {
|
|
|
3486
3549
|
w = x1 - x0;
|
|
3487
3550
|
h = y1 - y0;
|
|
3488
3551
|
}
|
|
3489
|
-
|
|
3552
|
+
const style = this._backgroundPicture;
|
|
3553
|
+
if (!prepareStyle(style, this._m, this.width, this.height)) return;
|
|
3490
3554
|
const op = this._op();
|
|
3491
3555
|
// A rectangular clip is a smaller rectangle to fill, not a mask — this
|
|
3492
3556
|
// was the one drawing path that still built a surface-sized a8 for one
|
|
@@ -3498,13 +3562,14 @@ class RenderingContext2d {
|
|
|
3498
3562
|
w > 0 && h > 0 ? this._boxedComposite({ x, y, w, h }, op) : null;
|
|
3499
3563
|
if (direct && !direct.box) return; // the clip rejects the fill whole
|
|
3500
3564
|
const box = direct ? direct.box : { x, y, w, h };
|
|
3565
|
+
const [ox, oy] = sourceOrigin(style);
|
|
3501
3566
|
this.Render.Composite(
|
|
3502
3567
|
op,
|
|
3503
|
-
|
|
3568
|
+
style.id,
|
|
3504
3569
|
direct ? direct.mask : this._compositeMask(box),
|
|
3505
3570
|
this._dst(),
|
|
3506
|
-
box.x,
|
|
3507
|
-
box.y,
|
|
3571
|
+
box.x - ox,
|
|
3572
|
+
box.y - oy,
|
|
3508
3573
|
box.x,
|
|
3509
3574
|
box.y,
|
|
3510
3575
|
box.x,
|
|
@@ -3656,7 +3721,8 @@ class RenderingContext2d {
|
|
|
3656
3721
|
if (y + h > y1) y1 = y + h;
|
|
3657
3722
|
}
|
|
3658
3723
|
if (!disjointRects(rects)) return false;
|
|
3659
|
-
|
|
3724
|
+
const style = this._backgroundPicture;
|
|
3725
|
+
if (!prepareStyle(style, this._m, this.width, this.height)) return true;
|
|
3660
3726
|
// the box they cover, on the surface: nothing outside it is sampled
|
|
3661
3727
|
const bx = Math.max(0, x0);
|
|
3662
3728
|
const by = Math.max(0, y0);
|
|
@@ -3690,13 +3756,14 @@ class RenderingContext2d {
|
|
|
3690
3756
|
bw,
|
|
3691
3757
|
bh,
|
|
3692
3758
|
);
|
|
3759
|
+
const [ox, oy] = sourceOrigin(style);
|
|
3693
3760
|
R.Composite(
|
|
3694
3761
|
op,
|
|
3695
|
-
|
|
3762
|
+
style.id,
|
|
3696
3763
|
mask,
|
|
3697
3764
|
this._dst(),
|
|
3698
|
-
bx,
|
|
3699
|
-
by,
|
|
3765
|
+
bx - ox,
|
|
3766
|
+
by - oy,
|
|
3700
3767
|
bx,
|
|
3701
3768
|
by,
|
|
3702
3769
|
bx,
|
|
@@ -5145,7 +5212,8 @@ class RenderingContext2d {
|
|
|
5145
5212
|
const R = this.Render;
|
|
5146
5213
|
// the coverage is painted in the current fillStyle, on both branches
|
|
5147
5214
|
// below — a gradient/pattern whose transform collapsed paints nothing
|
|
5148
|
-
|
|
5215
|
+
const style = this._backgroundPicture;
|
|
5216
|
+
if (!prepareStyle(style, this._m, this.width, this.height)) return;
|
|
5149
5217
|
const scaled = dw !== sw || dh !== sh;
|
|
5150
5218
|
if (scaled) {
|
|
5151
5219
|
R.SetPictureTransform(picture.id, [
|
|
@@ -5235,18 +5303,19 @@ class RenderingContext2d {
|
|
|
5235
5303
|
this._setPictureClip(rect);
|
|
5236
5304
|
}
|
|
5237
5305
|
// The scratch mask is surface-sized and surface-aligned — the coverage
|
|
5238
|
-
// went into it at (dx, dy) —
|
|
5239
|
-
//
|
|
5240
|
-
// dw x dh window out of the wrong
|
|
5241
|
-
//
|
|
5242
|
-
// all unless dx and dy are both zero.
|
|
5306
|
+
// went into it at (dx, dy) — so it is sampled at the destination
|
|
5307
|
+
// offset, and a gradient or pattern at that offset from its origin.
|
|
5308
|
+
// Reading the mask from (0, 0) draws a dw x dh window out of the wrong
|
|
5309
|
+
// part of a full-surface picture: the region just cleared to zero, so
|
|
5310
|
+
// nothing paints at all unless dx and dy are both zero.
|
|
5311
|
+
const [ox, oy] = sourceOrigin(style);
|
|
5243
5312
|
R.Composite(
|
|
5244
5313
|
op,
|
|
5245
|
-
|
|
5314
|
+
style.id,
|
|
5246
5315
|
this.fillMask.id,
|
|
5247
5316
|
this._dst(),
|
|
5248
|
-
dx,
|
|
5249
|
-
dy,
|
|
5317
|
+
dx - ox,
|
|
5318
|
+
dy - oy,
|
|
5250
5319
|
dx,
|
|
5251
5320
|
dy,
|
|
5252
5321
|
dx,
|
|
@@ -5258,15 +5327,17 @@ class RenderingContext2d {
|
|
|
5258
5327
|
} else if (!rect || (rect.w > 0 && rect.h > 0)) {
|
|
5259
5328
|
if (rect) this._setPictureClip(rect);
|
|
5260
5329
|
// the source is a 1x1 repeating solid (offset irrelevant) or the fill
|
|
5261
|
-
//
|
|
5262
|
-
//
|
|
5330
|
+
// style, sampled at the destination's offset from its own origin,
|
|
5331
|
+
// exactly as in _fillPolys
|
|
5332
|
+
const src = this._coverageSource();
|
|
5333
|
+
const [ox, oy] = sourceOrigin(src);
|
|
5263
5334
|
R.Composite(
|
|
5264
5335
|
op,
|
|
5265
|
-
|
|
5336
|
+
src.id,
|
|
5266
5337
|
picture.id,
|
|
5267
5338
|
this._dst(),
|
|
5268
|
-
dx,
|
|
5269
|
-
dy,
|
|
5339
|
+
dx - ox,
|
|
5340
|
+
dy - oy,
|
|
5270
5341
|
mx,
|
|
5271
5342
|
my,
|
|
5272
5343
|
dx,
|
|
@@ -5503,6 +5574,11 @@ class RenderingContext2d {
|
|
|
5503
5574
|
const M = matMultiply(this._m, [dw / sw, 0, 0, dh / sh, dx, dy]);
|
|
5504
5575
|
const inv = matInvert(M);
|
|
5505
5576
|
if (!inv) return;
|
|
5577
|
+
// The inverse's linear part is how many source pixels one device pixel
|
|
5578
|
+
// steps across, and it goes on the wire as 16.16 fixed point. Past
|
|
5579
|
+
// 32,767 the image is drawn at under 1/32,768 of its size: less than a
|
|
5580
|
+
// pixel across for any image X can hold, and nothing to see.
|
|
5581
|
+
if (!fitsFixed(inv.slice(0, 4))) return;
|
|
5506
5582
|
|
|
5507
5583
|
// device bbox of the transformed dest rect, clamped to the canvas
|
|
5508
5584
|
const corners = [
|
|
@@ -5523,6 +5599,24 @@ class RenderingContext2d {
|
|
|
5523
5599
|
);
|
|
5524
5600
|
if (x1 <= x0 || y1 <= y0) return;
|
|
5525
5601
|
|
|
5602
|
+
// Device -> source sample, measured from the bounding box's corner: the
|
|
5603
|
+
// corner's device position is folded into the transform, and the
|
|
5604
|
+
// composite's source origin is its box's offset from that corner. With
|
|
5605
|
+
// the source origin at device coordinates instead, the translation was
|
|
5606
|
+
// the device position times the downscale — a 50x30 thumbnail of a
|
|
5607
|
+
// 2000x1200 image at x 2,200 is 88,000, past what 16.16 carries, and
|
|
5608
|
+
// the encoder threw. From the corner it is the source coordinate the
|
|
5609
|
+
// corner samples: the image's own size or so, wherever on the surface it
|
|
5610
|
+
// is drawn. The corner is the unclipped one, so a clip that narrows the
|
|
5611
|
+
// box below samples the same points the mask route does.
|
|
5612
|
+
const [a, b, c, d] = inv;
|
|
5613
|
+
const e = a * x0 + c * y0 + inv[4] + sx;
|
|
5614
|
+
const f = b * x0 + d * y0 + inv[5] + sy;
|
|
5615
|
+
// A corner out of reach even so is a draw near the limit above, or an
|
|
5616
|
+
// image over 20,000 pixels across under a rotation; it is not drawn,
|
|
5617
|
+
// rather than thrown out of the paint.
|
|
5618
|
+
if (!fitsFixed([e, f])) return;
|
|
5619
|
+
|
|
5526
5620
|
// A rectangular clip shrinks the box instead of becoming a mask (issue
|
|
5527
5621
|
// #307). Decided before the picture transform is installed, so a draw
|
|
5528
5622
|
// the clip rejects does not have to put it back.
|
|
@@ -5531,27 +5625,15 @@ class RenderingContext2d {
|
|
|
5531
5625
|
if (direct && !direct.box) return;
|
|
5532
5626
|
const box = direct ? direct.box : bbox;
|
|
5533
5627
|
|
|
5534
|
-
|
|
5535
|
-
const srcM = [inv[0], inv[1], inv[2], inv[3], inv[4] + sx, inv[5] + sy];
|
|
5536
|
-
this.Render.SetPictureTransform(picture.id, [
|
|
5537
|
-
srcM[0],
|
|
5538
|
-
srcM[2],
|
|
5539
|
-
srcM[4],
|
|
5540
|
-
srcM[1],
|
|
5541
|
-
srcM[3],
|
|
5542
|
-
srcM[5],
|
|
5543
|
-
0,
|
|
5544
|
-
0,
|
|
5545
|
-
1,
|
|
5546
|
-
]);
|
|
5628
|
+
this.Render.SetPictureTransform(picture.id, [a, c, e, b, d, f, 0, 0, 1]);
|
|
5547
5629
|
picture.setFilter("bilinear");
|
|
5548
5630
|
this.Render.Composite(
|
|
5549
5631
|
op,
|
|
5550
5632
|
picture.id,
|
|
5551
5633
|
direct ? direct.mask : this._compositeMask(box),
|
|
5552
5634
|
this._dst(),
|
|
5553
|
-
box.x,
|
|
5554
|
-
box.y,
|
|
5635
|
+
box.x - x0,
|
|
5636
|
+
box.y - y0,
|
|
5555
5637
|
box.x,
|
|
5556
5638
|
box.y,
|
|
5557
5639
|
box.x,
|
|
@@ -5596,6 +5678,9 @@ class CanvasGradient {
|
|
|
5596
5678
|
// what the server currently holds: a fresh gradient picture is
|
|
5597
5679
|
// untransformed, so an untransformed fill costs no extra request
|
|
5598
5680
|
this._applied = [1, 0, 0, 1, 0, 0];
|
|
5681
|
+
// the device point fills sample it from (`sourceOrigin`), set with the
|
|
5682
|
+
// transform it belongs with
|
|
5683
|
+
this._origin = NO_ORIGIN;
|
|
5599
5684
|
|
|
5600
5685
|
this.x0 = p0;
|
|
5601
5686
|
this.y0 = p1;
|
|
@@ -5685,13 +5770,14 @@ class CanvasGradient {
|
|
|
5685
5770
|
/**
|
|
5686
5771
|
* Make the server-side mapping match the CTM this paint runs under. The
|
|
5687
5772
|
* gradient's own coordinates are user space and every fill samples the
|
|
5688
|
-
* source at device coordinates
|
|
5689
|
-
* source coordinate to a gradient one — is the CTM's
|
|
5773
|
+
* source at device coordinates less `_origin`, so the picture transform —
|
|
5774
|
+
* which takes a source coordinate to a gradient one — is the CTM's
|
|
5775
|
+
* inverse, from that origin.
|
|
5690
5776
|
*
|
|
5691
5777
|
* Returns false when the CTM collapses (a zero scale), which paints
|
|
5692
5778
|
* nothing, exactly as the canvas spec says.
|
|
5693
5779
|
*/
|
|
5694
|
-
_sync(ctm) {
|
|
5780
|
+
_sync(ctm, width = 0, height = 0) {
|
|
5695
5781
|
let inv = matInvert(ctm);
|
|
5696
5782
|
if (!inv) return false;
|
|
5697
5783
|
const id = this.id; // lazily creates the picture
|
|
@@ -5699,9 +5785,13 @@ class CanvasGradient {
|
|
|
5699
5785
|
// much nearer its origin
|
|
5700
5786
|
const k = this._scale;
|
|
5701
5787
|
if (k !== 1) inv = inv.map((v) => v / k);
|
|
5788
|
+
// sampled from where user space's origin lands (`sourceOrigin`)
|
|
5789
|
+
const origin = originOn(ctm[4], ctm[5], width, height);
|
|
5790
|
+
inv = matMultiply(inv, [1, 0, 0, 1, origin[0], origin[1]]);
|
|
5702
5791
|
// the transform is 16.16 fixed point too: one that cannot be carried
|
|
5703
5792
|
// fills nothing, rather than throwing the rest of the paint away
|
|
5704
|
-
if (!inv
|
|
5793
|
+
if (!fitsFixed(inv)) return false;
|
|
5794
|
+
this._origin = origin;
|
|
5705
5795
|
const a = this._applied;
|
|
5706
5796
|
if (
|
|
5707
5797
|
inv[0] !== a[0] ||
|
|
@@ -5779,6 +5869,7 @@ class CanvasPattern {
|
|
|
5779
5869
|
// what the server currently holds: a fresh picture is untransformed and
|
|
5780
5870
|
// filtered nearest, so an untransformed fill costs no extra request
|
|
5781
5871
|
this._applied = [1, 0, 0, 1, 0, 0];
|
|
5872
|
+
this._origin = NO_ORIGIN;
|
|
5782
5873
|
this._filter = "nearest";
|
|
5783
5874
|
}
|
|
5784
5875
|
|
|
@@ -5822,15 +5913,36 @@ class CanvasPattern {
|
|
|
5822
5913
|
* Make the server-side mapping match `ctm ∘ patternMatrix`. XRender's
|
|
5823
5914
|
* picture transform runs the other way — it takes a coordinate in the
|
|
5824
5915
|
* composite's source space (which every fill here keeps equal to device
|
|
5825
|
-
* space) to a texel — so it is the inverse
|
|
5916
|
+
* space less `_origin`) to a texel — so it is the inverse, from that
|
|
5917
|
+
* origin.
|
|
5826
5918
|
*
|
|
5827
5919
|
* Returns false when that composition collapses (a zero scale), which
|
|
5828
5920
|
* paints nothing, exactly as the canvas spec says.
|
|
5829
5921
|
*/
|
|
5830
|
-
_sync(ctm) {
|
|
5922
|
+
_sync(ctm, width = 0, height = 0) {
|
|
5831
5923
|
const m = matMultiply(ctm, this._m);
|
|
5832
|
-
|
|
5924
|
+
let inv = matInvert(m);
|
|
5833
5925
|
if (!inv) return false;
|
|
5926
|
+
// sampled from where the tile's origin lands (`sourceOrigin`)
|
|
5927
|
+
const origin = originOn(m[4], m[5], width, height);
|
|
5928
|
+
inv = matMultiply(inv, [1, 0, 0, 1, origin[0], origin[1]]);
|
|
5929
|
+
// A tile that repeats is the same a whole number of tiles along, so the
|
|
5930
|
+
// translation — the texel the origin samples — is taken to the first
|
|
5931
|
+
// tile. A grid scrolled 100,000 pixels has its origin far off the
|
|
5932
|
+
// surface and samples texels 100,000 along; from the first tile they
|
|
5933
|
+
// are in reach of the wire.
|
|
5934
|
+
const period =
|
|
5935
|
+
this._repeat === 1 ? 1 : this._repeat === 3 ? 2 : 0; // Normal, Reflect
|
|
5936
|
+
if (period && this.width > 0 && this.height > 0) {
|
|
5937
|
+
const pw = this.width * period;
|
|
5938
|
+
const ph = this.height * period;
|
|
5939
|
+
inv[4] -= pw * Math.floor(inv[4] / pw);
|
|
5940
|
+
inv[5] -= ph * Math.floor(inv[5] / ph);
|
|
5941
|
+
}
|
|
5942
|
+
// 16.16 fixed point, as a gradient's is: one that cannot be carried
|
|
5943
|
+
// fills nothing rather than throwing the rest of the paint away
|
|
5944
|
+
if (!fitsFixed(inv)) return false;
|
|
5945
|
+
this._origin = origin;
|
|
5834
5946
|
const a = this._applied;
|
|
5835
5947
|
if (
|
|
5836
5948
|
inv[0] !== a[0] ||
|
package/lib/text/glyphs.js
CHANGED
|
@@ -583,8 +583,12 @@ export function routeGlyphSize(app, font, size, policy = policyOf(app), textRend
|
|
|
583
583
|
* @param {{x: number, y: number, w: number, h: number}} [bounds] the part of
|
|
584
584
|
* the destination that can show anything — the clip, or the surface. A
|
|
585
585
|
* glyph wholly outside it is not sent (`visibleGlyph`).
|
|
586
|
+
* @param {[number, number]} [srcOrigin] the destination point the source's
|
|
587
|
+
* (0, 0) is aligned with. A gradient or pattern the 2d context has
|
|
588
|
+
* transformed samples from its own origin; anything else is aligned with
|
|
589
|
+
* the destination, from (0, 0).
|
|
586
590
|
*/
|
|
587
|
-
export function drawGlyphRuns(app, op, srcId, dstId, positioned, bounds = null) {
|
|
591
|
+
export function drawGlyphRuns(app, op, srcId, dstId, positioned, bounds = null, srcOrigin = [0, 0]) {
|
|
588
592
|
const policy = policyOf(app);
|
|
589
593
|
let bitmap = positioned;
|
|
590
594
|
let vector = null;
|
|
@@ -602,8 +606,8 @@ export function drawGlyphRuns(app, op, srcId, dstId, positioned, bounds = null)
|
|
|
602
606
|
bitmap.push(positioned[i]);
|
|
603
607
|
}
|
|
604
608
|
}
|
|
605
|
-
if (bitmap.length) drawBitmapGlyphRuns(app, op, srcId, dstId, bitmap, policy, bounds);
|
|
606
|
-
if (vector) drawVectorGlyphRuns(app, op, srcId, dstId, vector, bounds);
|
|
609
|
+
if (bitmap.length) drawBitmapGlyphRuns(app, op, srcId, dstId, bitmap, policy, bounds, srcOrigin);
|
|
610
|
+
if (vector) drawVectorGlyphRuns(app, op, srcId, dstId, vector, bounds, srcOrigin);
|
|
607
611
|
}
|
|
608
612
|
|
|
609
613
|
/**
|
|
@@ -630,7 +634,7 @@ function visibleGlyph(bounds, x, y, size) {
|
|
|
630
634
|
);
|
|
631
635
|
}
|
|
632
636
|
|
|
633
|
-
function drawBitmapGlyphRuns(app, op, srcId, dstId, positioned, policy, bounds) {
|
|
637
|
+
function drawBitmapGlyphRuns(app, op, srcId, dstId, positioned, policy, bounds, srcOrigin) {
|
|
634
638
|
const Render = app.display.Render;
|
|
635
639
|
const items = [];
|
|
636
640
|
let bits = 8;
|
|
@@ -652,8 +656,9 @@ function drawBitmapGlyphRuns(app, op, srcId, dstId, positioned, policy, bounds)
|
|
|
652
656
|
const encoded = encodeGlyphItems(items, bits);
|
|
653
657
|
if (!encoded) return;
|
|
654
658
|
// srcX/srcY align the source with the FIRST glyph's origin (RENDER spec);
|
|
655
|
-
// passing that origin itself
|
|
656
|
-
// coordinates, so gradient fill styles
|
|
659
|
+
// passing that origin itself, less the source's own, makes source
|
|
660
|
+
// coordinates destination coordinates from there, so gradient fill styles
|
|
661
|
+
// line up with canvas space
|
|
657
662
|
Render.CompositeGlyphs(
|
|
658
663
|
encoded.bits,
|
|
659
664
|
op,
|
|
@@ -661,8 +666,8 @@ function drawBitmapGlyphRuns(app, op, srcId, dstId, positioned, policy, bounds)
|
|
|
661
666
|
dstId,
|
|
662
667
|
0,
|
|
663
668
|
encoded.gsid,
|
|
664
|
-
items[0].x,
|
|
665
|
-
items[0].y,
|
|
669
|
+
items[0].x - srcOrigin[0],
|
|
670
|
+
items[0].y - srcOrigin[1],
|
|
666
671
|
encoded.elts
|
|
667
672
|
);
|
|
668
673
|
trimGlyphPages(app, policy, new Set(pages.values()));
|
|
@@ -683,7 +688,7 @@ const MAX_TRAPS_PER_REQUEST = 6000;
|
|
|
683
688
|
* Positions are intentionally NOT rounded to whole pixels — fractional
|
|
684
689
|
* advances and origins keep zoom animations smooth.
|
|
685
690
|
*/
|
|
686
|
-
function drawVectorGlyphRuns(app, op, srcId, dstId, positioned, bounds = null) {
|
|
691
|
+
function drawVectorGlyphRuns(app, op, srcId, dstId, positioned, bounds = null, srcOrigin = [0, 0]) {
|
|
687
692
|
// unrounded glyph origins + ink bounding box
|
|
688
693
|
const placed = [];
|
|
689
694
|
let minX = Infinity;
|
|
@@ -742,7 +747,7 @@ function drawVectorGlyphRuns(app, op, srcId, dstId, positioned, bounds = null) {
|
|
|
742
747
|
bx = x0;
|
|
743
748
|
by = y0;
|
|
744
749
|
}
|
|
745
|
-
compositeTraps(app, op, srcId, dstId, traps, bx, by);
|
|
750
|
+
compositeTraps(app, op, srcId, dstId, traps, bx, by, srcOrigin);
|
|
746
751
|
}
|
|
747
752
|
|
|
748
753
|
/**
|
|
@@ -753,8 +758,10 @@ function drawVectorGlyphRuns(app, op, srcId, dstId, positioned, bounds = null) {
|
|
|
753
758
|
*
|
|
754
759
|
* When bx/by are omitted they are derived from the trapezoid bounds (the
|
|
755
760
|
* trap coordinates are then treated as absolute device coordinates).
|
|
761
|
+
* `srcOrigin` is the destination point the source's (0, 0) is aligned with,
|
|
762
|
+
* as for `drawGlyphRuns`.
|
|
756
763
|
*/
|
|
757
|
-
export function compositeTraps(app, op, srcId, dstId, traps, bx, by) {
|
|
764
|
+
export function compositeTraps(app, op, srcId, dstId, traps, bx, by, srcOrigin = [0, 0]) {
|
|
758
765
|
if (traps.length === 0) return;
|
|
759
766
|
const Render = app.display.Render;
|
|
760
767
|
|
|
@@ -800,8 +807,22 @@ export function compositeTraps(app, op, srcId, dstId, traps, bx, by) {
|
|
|
800
807
|
for (let i = 0; i < traps.length; i += MAX_TRAPS_PER_REQUEST * 6) {
|
|
801
808
|
Render.AddTraps(mask.picture.id, 0, 0, traps.slice(i, i + MAX_TRAPS_PER_REQUEST * 6));
|
|
802
809
|
}
|
|
803
|
-
// src coords = dst coords
|
|
804
|
-
|
|
810
|
+
// src coords = dst coords from the source's origin (see
|
|
811
|
+
// drawBitmapGlyphRuns) so gradients line up
|
|
812
|
+
Render.Composite(
|
|
813
|
+
op,
|
|
814
|
+
srcId,
|
|
815
|
+
mask.picture.id,
|
|
816
|
+
dstId,
|
|
817
|
+
bx - srcOrigin[0],
|
|
818
|
+
by - srcOrigin[1],
|
|
819
|
+
0,
|
|
820
|
+
0,
|
|
821
|
+
bx,
|
|
822
|
+
by,
|
|
823
|
+
bw,
|
|
824
|
+
bh
|
|
825
|
+
);
|
|
805
826
|
releaseScratchMask(app);
|
|
806
827
|
}
|
|
807
828
|
|