ntk 8.18.0 → 8.18.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.
- package/lib/app.js +3 -0
- package/lib/precise.js +90 -36
- package/lib/renderingcontext_2d.js +107 -83
- package/lib/trapezoid.js +48 -26
- package/package.json +1 -1
package/lib/app.js
CHANGED
|
@@ -991,6 +991,9 @@ export default class App {
|
|
|
991
991
|
p = new Picture(this, { id: pid });
|
|
992
992
|
p._sourcePixmap = pixmap; // keep the 1x1 pixmap alive alongside the picture
|
|
993
993
|
}
|
|
994
|
+
// what it paints with, so a drawing can fold `globalAlpha` into it: glyphs
|
|
995
|
+
// composite straight from their source, with no mask slot to carry one
|
|
996
|
+
p._rgba = Object.freeze([r, g, b, a]);
|
|
994
997
|
this._solidPictures.set(key, p);
|
|
995
998
|
return p;
|
|
996
999
|
}
|
package/lib/precise.js
CHANGED
|
@@ -144,22 +144,27 @@ function edgeInit(edge, yStart, xTop, yTop, xBot, yBot) {
|
|
|
144
144
|
let signdx = 0;
|
|
145
145
|
let rem = 0;
|
|
146
146
|
if (dy) {
|
|
147
|
+
// Plain float divisions truncate exactly here. A quotient of integers
|
|
148
|
+
// whose dividend is below 2^45 sits at least 1/dy from any integer it
|
|
149
|
+
// is not, and a double holds it to within (dividend / dy) · 2^-52, far
|
|
150
|
+
// closer than that. The step from the line's top below, whose dividend
|
|
151
|
+
// can pass 2^53, keeps idiv.
|
|
147
152
|
if (dx >= 0) {
|
|
148
153
|
signdx = 1;
|
|
149
|
-
stepx =
|
|
150
|
-
rem = dx
|
|
154
|
+
stepx = Math.trunc(dx / dy);
|
|
155
|
+
rem = dx - stepx * dy;
|
|
151
156
|
e = -dy;
|
|
152
157
|
} else {
|
|
153
158
|
signdx = -1;
|
|
154
|
-
stepx = -
|
|
155
|
-
rem = -dx
|
|
159
|
+
stepx = -Math.trunc(-dx / dy);
|
|
160
|
+
rem = -dx + stepx * dy;
|
|
156
161
|
}
|
|
157
162
|
// _pixman_edge_multi_init for the small and the big step; n is at most
|
|
158
163
|
// 4370, so every product here is exact in a double
|
|
159
164
|
let ne = STEP_Y_SMALL * rem;
|
|
160
165
|
let sx = (STEP_Y_SMALL * stepx) | 0;
|
|
161
166
|
if (ne > 0) {
|
|
162
|
-
const nx =
|
|
167
|
+
const nx = Math.trunc(ne / dy);
|
|
163
168
|
ne -= nx * dy;
|
|
164
169
|
sx = (sx + nx * signdx) | 0;
|
|
165
170
|
}
|
|
@@ -168,7 +173,7 @@ function edgeInit(edge, yStart, xTop, yTop, xBot, yBot) {
|
|
|
168
173
|
ne = STEP_Y_BIG * rem;
|
|
169
174
|
sx = (STEP_Y_BIG * stepx) | 0;
|
|
170
175
|
if (ne > 0) {
|
|
171
|
-
const nx =
|
|
176
|
+
const nx = Math.trunc(ne / dy);
|
|
172
177
|
ne -= nx * dy;
|
|
173
178
|
sx = (sx + nx * signdx) | 0;
|
|
174
179
|
}
|
|
@@ -219,9 +224,20 @@ function edgeInit(edge, yStart, xTop, yTop, xBot, yBot) {
|
|
|
219
224
|
edge[SIGNDX] = signdx;
|
|
220
225
|
}
|
|
221
226
|
|
|
227
|
+
// RENDER_SAMPLES_X for every fraction of a pixel: how many of a pixel's 17
|
|
228
|
+
// sample columns lie left of an edge that crosses it there
|
|
229
|
+
const SAMPLES_X = new Uint8Array(65536);
|
|
230
|
+
for (let f = 0; f < 65536; f++) SAMPLES_X[f] = ((f + X_FRAC_FIRST) / STEP_X_SMALL) | 0;
|
|
231
|
+
|
|
222
232
|
/**
|
|
223
233
|
* rasterize_edges_8 between `left` and `right`, from sample row `t` to `b`
|
|
224
234
|
* inclusive, into the difference array `acc` (rows `stride` apart).
|
|
235
|
+
*
|
|
236
|
+
* A span adds 17 - ls to the pixel it starts in, 17 to each pixel after, rs
|
|
237
|
+
* to the pixel it ends in and nothing past it: the difference of two steps,
|
|
238
|
+
* each two cells of `acc`. While an edge stays in one pixel from sample row
|
|
239
|
+
* to sample row, as a steep one does for all fifteen, its two cells are
|
|
240
|
+
* summed here and written once.
|
|
225
241
|
*/
|
|
226
242
|
function addSpans(acc, stride, width, t, b) {
|
|
227
243
|
let y = t;
|
|
@@ -245,54 +261,92 @@ function addSpans(acc, stride, width, t, b) {
|
|
|
245
261
|
// pixman clips the span to the image: from 0, and to the last pixel of
|
|
246
262
|
// the row taken whole. Both are what an unclipped span would add there.
|
|
247
263
|
const lastX = (width << 16) - 1;
|
|
264
|
+
// each edge's step not yet written: the pixel it is in (-1 for none), and
|
|
265
|
+
// what it adds to that cell and to the next
|
|
266
|
+
let lPixel = -1;
|
|
267
|
+
let lAt = 0;
|
|
268
|
+
let lPast = 0;
|
|
269
|
+
let rPixel = -1;
|
|
270
|
+
let rAt = 0;
|
|
271
|
+
let rPast = 0;
|
|
272
|
+
// an edge's step carries one more unit of x when its error term passes
|
|
273
|
+
// zero: computed, not branched on, since a slope's carries come in no
|
|
274
|
+
// pattern a branch predictor can learn
|
|
275
|
+
let c = 0;
|
|
248
276
|
for (;;) {
|
|
249
277
|
const l = lx < 0 ? 0 : lx;
|
|
250
278
|
const r = rx >> 16 >= width ? lastX : rx;
|
|
251
279
|
if (r > l) {
|
|
252
|
-
// the span adds 17 - ls to the pixel it starts in, 17 to each pixel
|
|
253
|
-
// after, rs to the pixel it ends in and nothing past it: the
|
|
254
|
-
// difference of two steps, each two cells of `acc`
|
|
255
280
|
const li = l >> 16;
|
|
281
|
+
const ls = SAMPLES_X[l & 0xffff];
|
|
282
|
+
if (li !== lPixel) {
|
|
283
|
+
if (lPixel >= 0) {
|
|
284
|
+
acc[row + lPixel] += lAt;
|
|
285
|
+
acc[row + lPixel + 1] += lPast;
|
|
286
|
+
}
|
|
287
|
+
lPixel = li;
|
|
288
|
+
lAt = 0;
|
|
289
|
+
lPast = 0;
|
|
290
|
+
}
|
|
291
|
+
lAt += N_X_FRAC - ls;
|
|
292
|
+
lPast += ls;
|
|
256
293
|
const ri = r >> 16;
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
294
|
+
const rs = SAMPLES_X[r & 0xffff];
|
|
295
|
+
if (ri !== rPixel) {
|
|
296
|
+
if (rPixel >= 0) {
|
|
297
|
+
acc[row + rPixel] -= rAt;
|
|
298
|
+
acc[row + rPixel + 1] -= rPast;
|
|
299
|
+
}
|
|
300
|
+
rPixel = ri;
|
|
301
|
+
rAt = 0;
|
|
302
|
+
rPast = 0;
|
|
303
|
+
}
|
|
304
|
+
rAt += N_X_FRAC - rs;
|
|
305
|
+
rPast += rs;
|
|
263
306
|
}
|
|
264
|
-
if (y === b)
|
|
307
|
+
if (y === b) break;
|
|
265
308
|
if ((y & 0xffff) !== Y_FRAC_LAST) {
|
|
266
|
-
lx = (lx + lsxs) | 0;
|
|
267
309
|
le = (le + ldxs) | 0;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
rx = (rx + rsxs) | 0;
|
|
310
|
+
c = -le >>> 31;
|
|
311
|
+
le = (le - c * ldy) | 0;
|
|
312
|
+
lx = (lx + lsxs + c * lsign) | 0;
|
|
273
313
|
re = (re + rdxs) | 0;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
314
|
+
c = -re >>> 31;
|
|
315
|
+
re = (re - c * rdy) | 0;
|
|
316
|
+
rx = (rx + rsxs + c * rsign) | 0;
|
|
278
317
|
y = (y + STEP_Y_SMALL) | 0;
|
|
279
318
|
} else {
|
|
280
|
-
lx = (lx + lsxb) | 0;
|
|
281
319
|
le = (le + ldxb) | 0;
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
rx = (rx + rsxb) | 0;
|
|
320
|
+
c = -le >>> 31;
|
|
321
|
+
le = (le - c * ldy) | 0;
|
|
322
|
+
lx = (lx + lsxb + c * lsign) | 0;
|
|
287
323
|
re = (re + rdxb) | 0;
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
324
|
+
c = -re >>> 31;
|
|
325
|
+
re = (re - c * rdy) | 0;
|
|
326
|
+
rx = (rx + rsxb + c * rsign) | 0;
|
|
292
327
|
y = (y + STEP_Y_BIG) | 0;
|
|
328
|
+
// the pixel row is done: write what it left pending
|
|
329
|
+
if (lPixel >= 0) {
|
|
330
|
+
acc[row + lPixel] += lAt;
|
|
331
|
+
acc[row + lPixel + 1] += lPast;
|
|
332
|
+
lPixel = -1;
|
|
333
|
+
}
|
|
334
|
+
if (rPixel >= 0) {
|
|
335
|
+
acc[row + rPixel] -= rAt;
|
|
336
|
+
acc[row + rPixel + 1] -= rPast;
|
|
337
|
+
rPixel = -1;
|
|
338
|
+
}
|
|
293
339
|
row += stride;
|
|
294
340
|
}
|
|
295
341
|
}
|
|
342
|
+
if (lPixel >= 0) {
|
|
343
|
+
acc[row + lPixel] += lAt;
|
|
344
|
+
acc[row + lPixel + 1] += lPast;
|
|
345
|
+
}
|
|
346
|
+
if (rPixel >= 0) {
|
|
347
|
+
acc[row + rPixel] -= rAt;
|
|
348
|
+
acc[row + rPixel + 1] -= rPast;
|
|
349
|
+
}
|
|
296
350
|
}
|
|
297
351
|
|
|
298
352
|
/** the first sample row of a band from `top`, inside the grid */
|
|
@@ -3169,8 +3169,12 @@ class RenderingContext2d {
|
|
|
3169
3169
|
* relative positions, painted under the glyphs (issue #283). A paragraph
|
|
3170
3170
|
* whose spans change colour draws as several calls, and — as several
|
|
3171
3171
|
* `fillText`s would — casts a shadow per call.
|
|
3172
|
+
*
|
|
3173
|
+
* So does `globalAlpha`, to the glyphs and their shadow both, as it does
|
|
3174
|
+
* to every fill: at 0 nothing is drawn, not even the shadow.
|
|
3172
3175
|
*/
|
|
3173
3176
|
drawGlyphs(op, src, positioned) {
|
|
3177
|
+
if (this.globalAlpha <= 0) return;
|
|
3174
3178
|
const m = this._m;
|
|
3175
3179
|
if (!matIsIdentity(m)) {
|
|
3176
3180
|
positioned = positioned.map((p) => {
|
|
@@ -3195,12 +3199,19 @@ class RenderingContext2d {
|
|
|
3195
3199
|
* instead, intersect that with the clip, and paint the real source
|
|
3196
3200
|
* through the result — the same shape as _fillPolys. A rectangular clip
|
|
3197
3201
|
* takes the server-side fast path below instead of the mask.
|
|
3202
|
+
*
|
|
3203
|
+
* Nor has it a mask slot for `globalAlpha`, which is why text used to be
|
|
3204
|
+
* drawn at full opacity whatever it was. A solid source takes the alpha
|
|
3205
|
+
* in its colour, as `fillRect`'s does, and the glyphs go the way they
|
|
3206
|
+
* always went; a gradient or a pattern has no one colour to fold it
|
|
3207
|
+
* into, and paints through the scratch mask with the alpha in there.
|
|
3198
3208
|
*/
|
|
3199
3209
|
_drawGlyphsDevice(op, src, positioned) {
|
|
3210
|
+
if (this.globalAlpha <= 0) return;
|
|
3200
3211
|
const app = this.window.app;
|
|
3201
|
-
const R = this.Render;
|
|
3202
3212
|
if (!prepareStyle(src, this._m, this.width, this.height)) return;
|
|
3203
|
-
|
|
3213
|
+
const faded = this._fadedSource(src);
|
|
3214
|
+
if (faded && !this._hasPolyClip) {
|
|
3204
3215
|
// Fast path: a rectangular clip is something the server can do itself.
|
|
3205
3216
|
// Two small requests around the ordinary glyph composite, instead of
|
|
3206
3217
|
// clearing and compositing a full-surface a8 mask three times — which
|
|
@@ -3215,77 +3226,37 @@ class RenderingContext2d {
|
|
|
3215
3226
|
drawGlyphRuns(
|
|
3216
3227
|
app,
|
|
3217
3228
|
op,
|
|
3218
|
-
|
|
3229
|
+
faded.id,
|
|
3219
3230
|
this._dst(),
|
|
3220
3231
|
positioned,
|
|
3221
3232
|
rect ?? { x: 0, y: 0, w: this.width, h: this.height },
|
|
3222
|
-
sourceOrigin(
|
|
3233
|
+
sourceOrigin(faded),
|
|
3223
3234
|
);
|
|
3224
3235
|
if (rect) this._invalidatePictureClip();
|
|
3225
3236
|
this._markDirty();
|
|
3226
3237
|
return;
|
|
3227
3238
|
}
|
|
3228
|
-
const
|
|
3229
|
-
this.
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
R.PictOp.
|
|
3233
|
-
this.fillMask.id,
|
|
3234
|
-
[0, 0, 0, 0],
|
|
3235
|
-
[0, 0, this.width, this.height],
|
|
3236
|
-
);
|
|
3237
|
-
// solid white through the glyphs leaves their coverage in the a8 mask
|
|
3238
|
-
drawGlyphRuns(
|
|
3239
|
-
app,
|
|
3240
|
-
R.PictOp.Over,
|
|
3241
|
-
this._glyphSource.id,
|
|
3242
|
-
this.fillMask.id,
|
|
3243
|
-
positioned,
|
|
3244
|
-
{ x: 0, y: 0, w: this.width, h: this.height },
|
|
3245
|
-
);
|
|
3246
|
-
R.Composite(
|
|
3247
|
-
R.PictOp.In,
|
|
3248
|
-
clipMask.id,
|
|
3249
|
-
0,
|
|
3250
|
-
this.fillMask.id,
|
|
3251
|
-
0,
|
|
3252
|
-
0,
|
|
3253
|
-
0,
|
|
3254
|
-
0,
|
|
3255
|
-
0,
|
|
3256
|
-
0,
|
|
3257
|
-
this.width,
|
|
3258
|
-
this.height,
|
|
3259
|
-
);
|
|
3260
|
-
const [ox, oy] = sourceOrigin(src);
|
|
3261
|
-
R.Composite(
|
|
3262
|
-
op,
|
|
3263
|
-
src.id,
|
|
3264
|
-
this.fillMask.id,
|
|
3265
|
-
this._dst(),
|
|
3266
|
-
-ox,
|
|
3267
|
-
-oy,
|
|
3268
|
-
0,
|
|
3269
|
-
0,
|
|
3270
|
-
0,
|
|
3271
|
-
0,
|
|
3272
|
-
this.width,
|
|
3273
|
-
this.height,
|
|
3239
|
+
const R = this.Render;
|
|
3240
|
+
const alpha = faded ? 1 : this.globalAlpha;
|
|
3241
|
+
const surface = { x: 0, y: 0, w: this.width, h: this.height };
|
|
3242
|
+
this._paintThroughScratch(op, faded ?? src, alpha, (white, mask) =>
|
|
3243
|
+
drawGlyphRuns(app, R.PictOp.Over, white, mask, positioned, surface),
|
|
3274
3244
|
);
|
|
3275
|
-
this._markDirty();
|
|
3276
3245
|
}
|
|
3277
3246
|
|
|
3278
3247
|
/**
|
|
3279
3248
|
* Trapezoid coverage under the clip — `drawGlyphs` for vector shapes that
|
|
3280
3249
|
* are already trapezoidized (KaTeX radicals and rules go this way).
|
|
3281
3250
|
* Without it they composite straight to the destination picture and spill
|
|
3282
|
-
* out of clipped boxes, which is what glyphs used to do.
|
|
3251
|
+
* out of clipped boxes, which is what glyphs used to do. `globalAlpha`
|
|
3252
|
+
* applies as it does to glyphs, so a radical fades with the formula.
|
|
3283
3253
|
*/
|
|
3284
3254
|
drawTraps(op, src, traps) {
|
|
3285
|
-
if (!traps.length) return;
|
|
3255
|
+
if (!traps.length || this.globalAlpha <= 0) return;
|
|
3286
3256
|
const app = this.window.app;
|
|
3287
|
-
|
|
3288
|
-
|
|
3257
|
+
if (!prepareStyle(src, this._m, this.width, this.height)) return;
|
|
3258
|
+
const faded = this._fadedSource(src);
|
|
3259
|
+
if (faded && !this._hasPolyClip) {
|
|
3289
3260
|
const rect = this._clipRect();
|
|
3290
3261
|
if (rect) {
|
|
3291
3262
|
if (rect.w === 0 || rect.h === 0) return;
|
|
@@ -3294,18 +3265,57 @@ class RenderingContext2d {
|
|
|
3294
3265
|
compositeTraps(
|
|
3295
3266
|
app,
|
|
3296
3267
|
op,
|
|
3297
|
-
|
|
3268
|
+
faded.id,
|
|
3298
3269
|
this._dst(),
|
|
3299
3270
|
traps,
|
|
3300
3271
|
undefined,
|
|
3301
3272
|
undefined,
|
|
3302
|
-
sourceOrigin(
|
|
3273
|
+
sourceOrigin(faded),
|
|
3303
3274
|
);
|
|
3304
3275
|
if (rect) this._invalidatePictureClip();
|
|
3305
3276
|
this._markDirty();
|
|
3306
3277
|
return;
|
|
3307
3278
|
}
|
|
3308
|
-
const
|
|
3279
|
+
const alpha = faded ? 1 : this.globalAlpha;
|
|
3280
|
+
this._paintThroughScratch(op, faded ?? src, alpha, (white, mask) =>
|
|
3281
|
+
compositeTraps(app, this.Render.PictOp.Over, white, mask, traps),
|
|
3282
|
+
);
|
|
3283
|
+
}
|
|
3284
|
+
|
|
3285
|
+
/**
|
|
3286
|
+
* `src` with `globalAlpha` folded into it: the source itself at full
|
|
3287
|
+
* opacity, and at less, a solid of its colour with all four premultiplied
|
|
3288
|
+
* components scaled — which is compositing it at that opacity, for any op
|
|
3289
|
+
* (`_foldedColor`). Null when there is an alpha to apply and no colour to
|
|
3290
|
+
* fold it into: a gradient, a pattern, a picture of the caller's, or a
|
|
3291
|
+
* solid from a host whose `solidPicture` does not say what it paints with
|
|
3292
|
+
* (App#solidPicture does).
|
|
3293
|
+
*/
|
|
3294
|
+
_fadedSource(src) {
|
|
3295
|
+
const a = this.globalAlpha;
|
|
3296
|
+
if (a >= 1) return src;
|
|
3297
|
+
const c = src._rgba;
|
|
3298
|
+
if (!c) return null;
|
|
3299
|
+
return this.createSolidPicture(c[0] * a, c[1] * a, c[2] * a, c[3] * a);
|
|
3300
|
+
}
|
|
3301
|
+
|
|
3302
|
+
/**
|
|
3303
|
+
* Paint `src` through coverage the server draws into the scratch a8 mask:
|
|
3304
|
+
* how glyphs and trapezoids reach the surface when their own composite
|
|
3305
|
+
* cannot carry what applies to it — a clip that is not a rectangle, or a
|
|
3306
|
+
* `globalAlpha` with no colour to fold into.
|
|
3307
|
+
*
|
|
3308
|
+
* `drawCoverage(white, mask)` composites the coverage into the mask, with
|
|
3309
|
+
* a solid white source: what that leaves in an a8 picture is exactly the
|
|
3310
|
+
* coverage. `alpha` scales it, the clip cuts it — the clip mask for a
|
|
3311
|
+
* stack with a path in it, the picture's clip rectangle for one of
|
|
3312
|
+
* rectangles — and `src` is composited through the result.
|
|
3313
|
+
*/
|
|
3314
|
+
_paintThroughScratch(op, src, alpha, drawCoverage) {
|
|
3315
|
+
const R = this.Render;
|
|
3316
|
+
const rect = this._clipRect();
|
|
3317
|
+
if (rect && (rect.w === 0 || rect.h === 0)) return;
|
|
3318
|
+
const clipMask = this._hasPolyClip ? this._requireClipMask() : null;
|
|
3309
3319
|
this._ensureFillMask();
|
|
3310
3320
|
this._glyphSource ??= this.createSolidPicture(1, 1, 1, 1);
|
|
3311
3321
|
R.FillRectangles(
|
|
@@ -3314,27 +3324,34 @@ class RenderingContext2d {
|
|
|
3314
3324
|
[0, 0, 0, 0],
|
|
3315
3325
|
[0, 0, this.width, this.height],
|
|
3316
3326
|
);
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3327
|
+
drawCoverage(this._glyphSource.id, this.fillMask.id);
|
|
3328
|
+
if (alpha < 1) {
|
|
3329
|
+
// In with a constant color scales the a8 coverage by that alpha
|
|
3330
|
+
R.FillRectangles(
|
|
3331
|
+
R.PictOp.In,
|
|
3332
|
+
this.fillMask.id,
|
|
3333
|
+
[0, 0, 0, alpha],
|
|
3334
|
+
[0, 0, this.width, this.height],
|
|
3335
|
+
);
|
|
3336
|
+
}
|
|
3337
|
+
if (clipMask) {
|
|
3338
|
+
R.Composite(
|
|
3339
|
+
R.PictOp.In,
|
|
3340
|
+
clipMask.id,
|
|
3341
|
+
0,
|
|
3342
|
+
this.fillMask.id,
|
|
3343
|
+
0,
|
|
3344
|
+
0,
|
|
3345
|
+
0,
|
|
3346
|
+
0,
|
|
3347
|
+
0,
|
|
3348
|
+
0,
|
|
3349
|
+
this.width,
|
|
3350
|
+
this.height,
|
|
3351
|
+
);
|
|
3352
|
+
} else if (rect) {
|
|
3353
|
+
this._setPictureClip(rect);
|
|
3354
|
+
}
|
|
3338
3355
|
const [ox, oy] = sourceOrigin(src);
|
|
3339
3356
|
R.Composite(
|
|
3340
3357
|
op,
|
|
@@ -3350,6 +3367,7 @@ class RenderingContext2d {
|
|
|
3350
3367
|
this.width,
|
|
3351
3368
|
this.height,
|
|
3352
3369
|
);
|
|
3370
|
+
if (rect) this._invalidatePictureClip();
|
|
3353
3371
|
this._markDirty();
|
|
3354
3372
|
}
|
|
3355
3373
|
|
|
@@ -4723,7 +4741,8 @@ class RenderingContext2d {
|
|
|
4723
4741
|
*/
|
|
4724
4742
|
fillText(text, x, y) {
|
|
4725
4743
|
text = String(text ?? "");
|
|
4726
|
-
|
|
4744
|
+
// nothing to draw, and at globalAlpha 0 nothing to shape it for
|
|
4745
|
+
if (!text || this.globalAlpha <= 0) return;
|
|
4727
4746
|
if (this._shadowed()) this._shadowOfText(text, x, y);
|
|
4728
4747
|
const style = this._resolvedTextStyle();
|
|
4729
4748
|
const app = this.window.app;
|
|
@@ -5216,14 +5235,19 @@ class RenderingContext2d {
|
|
|
5216
5235
|
* into it. The mask slot is taken by the coverage, so the alpha has to go
|
|
5217
5236
|
* somewhere — and a 1x1 solid is free, where a surface-sized alpha mask is
|
|
5218
5237
|
* not. Falls back to the fill picture for gradients, which have no single
|
|
5219
|
-
* colour to fold into; `_drawCoverage` routes those through the scratch.
|
|
5238
|
+
* colour to fold into; `_drawCoverage` routes those through the scratch.
|
|
5239
|
+
*
|
|
5240
|
+
* All four components are scaled, not the alpha alone: the colour is
|
|
5241
|
+
* premultiplied, and one whose alpha shrank under unchanged components is
|
|
5242
|
+
* brighter than it can be — `#0000ff` at 0.5 over black came out
|
|
5243
|
+
* `#0000ff`, where `fillRect` draws `#000080`. */
|
|
5220
5244
|
_coverageSource() {
|
|
5221
5245
|
const style = this._fillStyle;
|
|
5222
5246
|
if (this.globalAlpha >= 1 || !isPlainColor(style)) {
|
|
5223
5247
|
return this._backgroundPicture;
|
|
5224
5248
|
}
|
|
5225
|
-
const c =
|
|
5226
|
-
return this.createSolidPicture(c[0], c[1], c[2], c[3]
|
|
5249
|
+
const c = this._foldedColor(style);
|
|
5250
|
+
return this.createSolidPicture(c[0], c[1], c[2], c[3]);
|
|
5227
5251
|
}
|
|
5228
5252
|
|
|
5229
5253
|
/**
|
package/lib/trapezoid.js
CHANGED
|
@@ -37,7 +37,7 @@ export function trapezoidize(polys, dx = 0, dy = 0, out = [], rule = 'nonzero')
|
|
|
37
37
|
const evenodd = rule === 'evenodd';
|
|
38
38
|
// collect non-horizontal edges, normalized to y0 < y1 with winding dir
|
|
39
39
|
const edges = [];
|
|
40
|
-
|
|
40
|
+
let ys = [];
|
|
41
41
|
// the largest coordinate magnitude, which sets the rounding noise
|
|
42
42
|
let reach = 1;
|
|
43
43
|
for (const poly of polys) {
|
|
@@ -66,14 +66,17 @@ export function trapezoidize(polys, dx = 0, dy = 0, out = [], rule = 'nonzero')
|
|
|
66
66
|
// coordinate that fits one
|
|
67
67
|
const eps = reach * 2 ** -44;
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
// a typed array sorts numerically without a comparator call per step
|
|
70
|
+
ys = Float64Array.from(ys).sort();
|
|
70
71
|
edges.sort((a, b) => a.y0 - b.y0);
|
|
71
72
|
|
|
72
73
|
const active = [];
|
|
73
74
|
let nextEdge = 0;
|
|
74
|
-
// spans open from the previous slab, for vertical merging
|
|
75
|
-
//
|
|
75
|
+
// spans open from the previous slab, for vertical merging, and the ones
|
|
76
|
+
// this slab opens: flat [leftEdge, rightEdge, index of the trap in `out`]
|
|
77
|
+
// triples, two buffers that trade places every slab
|
|
76
78
|
let prevSpans = [];
|
|
79
|
+
let spans = [];
|
|
77
80
|
|
|
78
81
|
for (let yi = 0; yi < ys.length - 1; ++yi) {
|
|
79
82
|
const ya = ys[yi];
|
|
@@ -81,25 +84,38 @@ export function trapezoidize(polys, dx = 0, dy = 0, out = [], rule = 'nonzero')
|
|
|
81
84
|
if (yb === ya) continue;
|
|
82
85
|
|
|
83
86
|
// update the active edge list for this slab
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
let kept = 0;
|
|
88
|
+
for (let i = 0; i < active.length; ++i) {
|
|
89
|
+
if (active[i].y1 > ya) active[kept++] = active[i];
|
|
86
90
|
}
|
|
91
|
+
active.length = kept;
|
|
87
92
|
while (nextEdge < edges.length && edges[nextEdge].y0 <= ya) {
|
|
88
93
|
if (edges[nextEdge].y1 > ya) active.push(edges[nextEdge]);
|
|
89
94
|
nextEdge++;
|
|
90
95
|
}
|
|
91
96
|
if (active.length === 0) {
|
|
92
|
-
prevSpans =
|
|
97
|
+
prevSpans.length = 0;
|
|
93
98
|
continue;
|
|
94
99
|
}
|
|
95
100
|
|
|
96
101
|
// x at slab top/bottom for each active edge; sort left-to-right just
|
|
97
102
|
// below the top (edges leaving one point go by where they are headed)
|
|
98
|
-
for (
|
|
103
|
+
for (let i = 0; i < active.length; ++i) {
|
|
104
|
+
const e = active[i];
|
|
99
105
|
e.xt = e.x0 + (ya - e.y0) * e.slope;
|
|
100
106
|
e.xb = e.x0 + (yb - e.y0) * e.slope;
|
|
101
107
|
}
|
|
102
|
-
|
|
108
|
+
// a slab holds a handful of edges, mostly in last slab's order: an
|
|
109
|
+
// insertion sort, stable as Array#sort is, so the order is the same
|
|
110
|
+
for (let i = 1; i < active.length; ++i) {
|
|
111
|
+
const e = active[i];
|
|
112
|
+
let j = i - 1;
|
|
113
|
+
while (j >= 0 && (active[j].xt - e.xt || active[j].xb - e.xb) > 0) {
|
|
114
|
+
active[j + 1] = active[j];
|
|
115
|
+
--j;
|
|
116
|
+
}
|
|
117
|
+
active[j + 1] = e;
|
|
118
|
+
}
|
|
103
119
|
|
|
104
120
|
// cut the slab at each crossing, top to bottom; `y` is where the part
|
|
105
121
|
// still to emit starts, and `xt` each edge's x there
|
|
@@ -121,13 +137,18 @@ export function trapezoidize(polys, dx = 0, dy = 0, out = [], rule = 'nonzero')
|
|
|
121
137
|
}
|
|
122
138
|
}
|
|
123
139
|
if (k < 0 || yc >= yb - eps) {
|
|
124
|
-
|
|
140
|
+
emitSpans(active, y, yb, true, evenodd, prevSpans, spans, out);
|
|
141
|
+
[prevSpans, spans] = [spans, prevSpans];
|
|
125
142
|
break;
|
|
126
143
|
}
|
|
127
144
|
if (yc > y + eps) {
|
|
128
|
-
for (
|
|
129
|
-
|
|
130
|
-
|
|
145
|
+
for (let i = 0; i < active.length; ++i) {
|
|
146
|
+
const e = active[i];
|
|
147
|
+
e.xc = e.x0 + (yc - e.y0) * e.slope;
|
|
148
|
+
}
|
|
149
|
+
emitSpans(active, y, yc, false, evenodd, prevSpans, spans, out);
|
|
150
|
+
[prevSpans, spans] = [spans, prevSpans];
|
|
151
|
+
for (let i = 0; i < active.length; ++i) active[i].xt = active[i].xc;
|
|
131
152
|
y = yc;
|
|
132
153
|
}
|
|
133
154
|
const a = active[k];
|
|
@@ -144,15 +165,16 @@ export function trapezoidize(polys, dx = 0, dy = 0, out = [], rule = 'nonzero')
|
|
|
144
165
|
* parity for even-odd). Each edge is at `xt` at the top, and at the bottom
|
|
145
166
|
* at `xb` when this is the `last` part of its vertex slab, else at `xc`. A
|
|
146
167
|
* span bounded by the same two edges as one ending at `ya` extends that
|
|
147
|
-
* trapezoid instead.
|
|
148
|
-
*
|
|
168
|
+
* trapezoid instead. The spans go into `spans`, emptied first, for the next
|
|
169
|
+
* slab to merge with.
|
|
149
170
|
*/
|
|
150
|
-
function emitSpans(active, ya, yb, last, evenodd, prevSpans, out) {
|
|
151
|
-
|
|
171
|
+
function emitSpans(active, ya, yb, last, evenodd, prevSpans, spans, out) {
|
|
172
|
+
spans.length = 0;
|
|
152
173
|
let winding = 0;
|
|
153
174
|
let crossings = 0;
|
|
154
175
|
let left = null;
|
|
155
|
-
for (
|
|
176
|
+
for (let i = 0; i < active.length; ++i) {
|
|
177
|
+
const e = active[i];
|
|
156
178
|
const wasInside = evenodd ? (crossings & 1) === 1 : winding !== 0;
|
|
157
179
|
winding += e.dir;
|
|
158
180
|
crossings++;
|
|
@@ -164,24 +186,24 @@ function emitSpans(active, ya, yb, last, evenodd, prevSpans, out) {
|
|
|
164
186
|
const rb = last ? e.xb : e.xc;
|
|
165
187
|
// merge with a span from the previous slab bounded by the same edges
|
|
166
188
|
let merged = false;
|
|
167
|
-
for (
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
out[
|
|
171
|
-
out[
|
|
172
|
-
|
|
189
|
+
for (let p = 0; p < prevSpans.length; p += 3) {
|
|
190
|
+
const at = prevSpans[p + 2];
|
|
191
|
+
if (prevSpans[p] === left && prevSpans[p + 1] === e && out[at + 5] === ya) {
|
|
192
|
+
out[at + 3] = lb;
|
|
193
|
+
out[at + 4] = rb;
|
|
194
|
+
out[at + 5] = yb;
|
|
195
|
+
spans.push(left, e, at);
|
|
173
196
|
merged = true;
|
|
174
197
|
break;
|
|
175
198
|
}
|
|
176
199
|
}
|
|
177
200
|
if (!merged && (e.xt > left.xt || rb > lb)) {
|
|
178
|
-
spans.push(
|
|
201
|
+
spans.push(left, e, out.length);
|
|
179
202
|
out.push(left.xt, e.xt, ya, lb, rb, yb);
|
|
180
203
|
}
|
|
181
204
|
left = null;
|
|
182
205
|
}
|
|
183
206
|
}
|
|
184
|
-
return spans;
|
|
185
207
|
}
|
|
186
208
|
|
|
187
209
|
/** area covered by a trapezoid list (useful for tests/diagnostics) */
|