pdf-raster-cpu 1.0.1 → 1.0.3
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/dist/index.cjs +104 -84
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +104 -84
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -1,50 +1,62 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let byte_codec = require("byte-codec");
|
|
3
|
+
function firstSampleAtOrAfter(coordinate) {
|
|
4
|
+
return Math.max(0, Math.ceil(coordinate * 4 - .5));
|
|
5
|
+
}
|
|
6
|
+
function lastSampleBefore(coordinate, sizePx) {
|
|
7
|
+
return Math.min(4 * sizePx - 1, Math.floor(coordinate * 4 - .5));
|
|
8
|
+
}
|
|
3
9
|
var CoverageMask = class {
|
|
4
10
|
widthPx;
|
|
5
11
|
heightPx;
|
|
6
12
|
counts;
|
|
13
|
+
markedFirst;
|
|
14
|
+
markedLast;
|
|
7
15
|
constructor(widthPx, heightPx) {
|
|
8
16
|
this.widthPx = widthPx;
|
|
9
17
|
this.heightPx = heightPx;
|
|
10
18
|
this.counts = new Uint16Array(widthPx * heightPx);
|
|
19
|
+
this.markedFirst = this.counts.length;
|
|
20
|
+
this.markedLast = 0;
|
|
11
21
|
}
|
|
12
22
|
reset() {
|
|
13
23
|
this.counts.fill(0);
|
|
24
|
+
this.markedFirst = this.counts.length;
|
|
25
|
+
this.markedLast = 0;
|
|
14
26
|
}
|
|
15
27
|
countAt(pixelIndex) {
|
|
16
28
|
return this.counts[pixelIndex] ?? 0;
|
|
17
29
|
}
|
|
30
|
+
markedRange() {
|
|
31
|
+
return {
|
|
32
|
+
first: this.markedFirst,
|
|
33
|
+
last: this.markedLast
|
|
34
|
+
};
|
|
35
|
+
}
|
|
18
36
|
fillPolygons(polygons, fillRule) {
|
|
19
|
-
|
|
20
|
-
let minY = Infinity;
|
|
21
|
-
let maxY = -Infinity;
|
|
37
|
+
const boundedYs = [];
|
|
22
38
|
for (const polygon of polygons) {
|
|
23
39
|
if (polygon.length < 3) continue;
|
|
24
|
-
for (const point of polygon)
|
|
25
|
-
minY = Math.min(minY, point.y);
|
|
26
|
-
maxY = Math.max(maxY, point.y);
|
|
27
|
-
}
|
|
40
|
+
for (const point of polygon) boundedYs.push(point.y);
|
|
28
41
|
}
|
|
29
|
-
if (
|
|
30
|
-
const rowMin =
|
|
31
|
-
const rowMax =
|
|
42
|
+
if (boundedYs.length === 0) return;
|
|
43
|
+
const rowMin = firstSampleAtOrAfter(boundedYs.reduce((lowest, y) => Math.min(lowest, y)));
|
|
44
|
+
const rowMax = lastSampleBefore(boundedYs.reduce((highest, y) => Math.max(highest, y)), this.heightPx);
|
|
32
45
|
const crossings = [];
|
|
33
46
|
for (let row = rowMin; row <= rowMax; row++) {
|
|
34
47
|
const y = (row + .5) / 4;
|
|
35
48
|
crossings.length = 0;
|
|
36
|
-
for (const polygon of polygons) for (
|
|
37
|
-
const a = polygon[i];
|
|
49
|
+
for (const polygon of polygons) for (const [i, a] of polygon.entries()) {
|
|
38
50
|
const b = polygon[(i + 1) % polygon.length];
|
|
39
|
-
if (
|
|
40
|
-
|
|
51
|
+
if (b === void 0) continue;
|
|
52
|
+
const aAtOrAboveRow = a.y <= y;
|
|
53
|
+
if (aAtOrAboveRow === b.y <= y) continue;
|
|
41
54
|
const t = (y - a.y) / (b.y - a.y);
|
|
42
55
|
crossings.push({
|
|
43
56
|
x: a.x + t * (b.x - a.x),
|
|
44
|
-
|
|
57
|
+
downward: aAtOrAboveRow
|
|
45
58
|
});
|
|
46
59
|
}
|
|
47
|
-
if (crossings.length === 0) continue;
|
|
48
60
|
crossings.sort((p, q) => p.x - q.x);
|
|
49
61
|
const pixelRowBase = Math.floor(row / 4) * this.widthPx;
|
|
50
62
|
let spanStartX;
|
|
@@ -58,9 +70,10 @@ var CoverageMask = class {
|
|
|
58
70
|
}
|
|
59
71
|
continue;
|
|
60
72
|
}
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
73
|
+
const wasZero = winding === 0;
|
|
74
|
+
if (crossing.downward) winding++;
|
|
75
|
+
else winding--;
|
|
76
|
+
if (wasZero) spanStartX = crossing.x;
|
|
64
77
|
else if (spanStartX !== void 0 && winding === 0) {
|
|
65
78
|
this.markSpan(pixelRowBase, spanStartX, crossing.x);
|
|
66
79
|
spanStartX = void 0;
|
|
@@ -70,12 +83,16 @@ var CoverageMask = class {
|
|
|
70
83
|
}
|
|
71
84
|
markSpan(pixelRowBase, startX, endX) {
|
|
72
85
|
if (endX <= startX) return;
|
|
73
|
-
const columnFirst =
|
|
74
|
-
const columnLast =
|
|
86
|
+
const columnFirst = firstSampleAtOrAfter(startX);
|
|
87
|
+
const columnLast = lastSampleBefore(endX, this.widthPx);
|
|
75
88
|
for (let c = columnFirst; c <= columnLast; c++) {
|
|
76
89
|
const cellIndex = pixelRowBase + Math.floor(c / 4);
|
|
77
90
|
const cell = this.counts[cellIndex];
|
|
78
|
-
if (cell !== void 0)
|
|
91
|
+
if (cell !== void 0) {
|
|
92
|
+
this.counts[cellIndex] = cell + 1;
|
|
93
|
+
this.markedFirst = Math.min(this.markedFirst, cellIndex);
|
|
94
|
+
this.markedLast = Math.max(this.markedLast, cellIndex);
|
|
95
|
+
}
|
|
79
96
|
}
|
|
80
97
|
}
|
|
81
98
|
};
|
|
@@ -156,10 +173,9 @@ function strokeOutlinePolygons(subpaths, widthPx, dashPx) {
|
|
|
156
173
|
const half = Math.max(widthPx, 1) / 2;
|
|
157
174
|
const polygons = [];
|
|
158
175
|
const emitPiece = (piece) => {
|
|
159
|
-
for (
|
|
160
|
-
const p = piece[i];
|
|
176
|
+
for (const [i, p] of piece.entries()) {
|
|
161
177
|
const q = piece[i + 1];
|
|
162
|
-
if (
|
|
178
|
+
if (q === void 0) continue;
|
|
163
179
|
const d = unitDirection(p, q);
|
|
164
180
|
if (d === void 0) continue;
|
|
165
181
|
const n = {
|
|
@@ -185,11 +201,10 @@ function strokeOutlinePolygons(subpaths, widthPx, dashPx) {
|
|
|
185
201
|
}
|
|
186
202
|
]);
|
|
187
203
|
}
|
|
188
|
-
for (
|
|
189
|
-
const vertex = piece[i];
|
|
204
|
+
for (const [i, vertex] of piece.entries()) {
|
|
190
205
|
const before = piece[i - 1];
|
|
191
206
|
const after = piece[i + 1];
|
|
192
|
-
if (
|
|
207
|
+
if (before === void 0 || after === void 0) continue;
|
|
193
208
|
emitJoinWedge(polygons, vertex, before, after, half);
|
|
194
209
|
}
|
|
195
210
|
};
|
|
@@ -198,7 +213,7 @@ function strokeOutlinePolygons(subpaths, widthPx, dashPx) {
|
|
|
198
213
|
const first = subpath.points[0];
|
|
199
214
|
if (first === void 0) continue;
|
|
200
215
|
emitPiece(subpath.closed ? [...subpath.points, first] : subpath.points);
|
|
201
|
-
if (subpath.closed
|
|
216
|
+
if (subpath.closed) {
|
|
202
217
|
const last = subpath.points[subpath.points.length - 1];
|
|
203
218
|
const second = subpath.points[1];
|
|
204
219
|
if (last !== void 0 && second !== void 0) emitJoinWedge(polygons, first, last, second, half);
|
|
@@ -215,14 +230,20 @@ function emitJoinWedge(polygons, vertex, before, after, half) {
|
|
|
215
230
|
if (d1 === void 0 || d2 === void 0) return;
|
|
216
231
|
const cross = d1.x * d2.y - d1.y * d2.x;
|
|
217
232
|
if (Math.abs(cross) < 1e-12) return;
|
|
218
|
-
const
|
|
219
|
-
const n1 = {
|
|
220
|
-
x:
|
|
221
|
-
y: d1.x
|
|
233
|
+
const outward = turnsOutwardPositive(cross);
|
|
234
|
+
const n1 = outward ? {
|
|
235
|
+
x: d1.y,
|
|
236
|
+
y: -d1.x
|
|
237
|
+
} : {
|
|
238
|
+
x: -d1.y,
|
|
239
|
+
y: d1.x
|
|
222
240
|
};
|
|
223
|
-
const n2 = {
|
|
224
|
-
x:
|
|
225
|
-
y: d2.x
|
|
241
|
+
const n2 = outward ? {
|
|
242
|
+
x: d2.y,
|
|
243
|
+
y: -d2.x
|
|
244
|
+
} : {
|
|
245
|
+
x: -d2.y,
|
|
246
|
+
y: d2.x
|
|
226
247
|
};
|
|
227
248
|
const a1 = {
|
|
228
249
|
x: vertex.x + n1.x * half,
|
|
@@ -232,10 +253,9 @@ function emitJoinWedge(polygons, vertex, before, after, half) {
|
|
|
232
253
|
x: vertex.x + n2.x * half,
|
|
233
254
|
y: vertex.y + n2.y * half
|
|
234
255
|
};
|
|
235
|
-
const
|
|
236
|
-
const denominator = 1 + dot;
|
|
256
|
+
const denominator = 1 + (n1.x * n2.x + n1.y * n2.y);
|
|
237
257
|
let wedge;
|
|
238
|
-
if (denominator
|
|
258
|
+
if (takesMiterBranch(denominator)) {
|
|
239
259
|
const scale = half / denominator;
|
|
240
260
|
wedge = [
|
|
241
261
|
a1,
|
|
@@ -251,17 +271,22 @@ function emitJoinWedge(polygons, vertex, before, after, half) {
|
|
|
251
271
|
vertex,
|
|
252
272
|
a2
|
|
253
273
|
];
|
|
254
|
-
polygons.push(
|
|
274
|
+
polygons.push(withNegativeWinding(wedge));
|
|
275
|
+
}
|
|
276
|
+
function turnsOutwardPositive(cross) {
|
|
277
|
+
return cross > 0;
|
|
278
|
+
}
|
|
279
|
+
function takesMiterBranch(denominator) {
|
|
280
|
+
return 2 <= 100 * denominator;
|
|
255
281
|
}
|
|
256
|
-
function
|
|
257
|
-
return
|
|
282
|
+
function withNegativeWinding(polygon) {
|
|
283
|
+
return polygonSignedArea(polygon) < 0 ? [...polygon] : [...polygon].reverse();
|
|
258
284
|
}
|
|
259
285
|
function polygonSignedArea(polygon) {
|
|
260
286
|
let area = 0;
|
|
261
|
-
for (
|
|
262
|
-
const a = polygon[i];
|
|
287
|
+
for (const [i, a] of polygon.entries()) {
|
|
263
288
|
const b = polygon[(i + 1) % polygon.length];
|
|
264
|
-
if (
|
|
289
|
+
if (b === void 0) continue;
|
|
265
290
|
area += a.x * b.y - b.x * a.y;
|
|
266
291
|
}
|
|
267
292
|
return area;
|
|
@@ -278,17 +303,17 @@ function unitDirection(p, q) {
|
|
|
278
303
|
}
|
|
279
304
|
function dashPolyline(subpath, dashPx) {
|
|
280
305
|
if (!dashPx.some((length) => length > 0)) return [];
|
|
281
|
-
const pattern =
|
|
282
|
-
const
|
|
283
|
-
const start = points[0];
|
|
306
|
+
const pattern = [...dashPx, ...dashPx];
|
|
307
|
+
const start = subpath.points[0];
|
|
284
308
|
if (start === void 0) return [];
|
|
309
|
+
const points = subpath.closed ? [...subpath.points, start] : subpath.points;
|
|
285
310
|
const pieces = [];
|
|
286
311
|
const endOnPiece = () => {
|
|
287
312
|
if (current.length >= 2) pieces.push(current);
|
|
288
313
|
current = [];
|
|
289
314
|
};
|
|
290
315
|
const atBoundary = () => {
|
|
291
|
-
|
|
316
|
+
endOnPiece();
|
|
292
317
|
for (;;) {
|
|
293
318
|
index = (index + 1) % pattern.length;
|
|
294
319
|
const entry = pattern[index];
|
|
@@ -307,16 +332,15 @@ function dashPolyline(subpath, dashPx) {
|
|
|
307
332
|
if (firstEntry !== void 0 && firstEntry > 0) {
|
|
308
333
|
remaining = firstEntry;
|
|
309
334
|
current = [cursor];
|
|
310
|
-
}
|
|
311
|
-
for (
|
|
312
|
-
const from = points[i];
|
|
335
|
+
}
|
|
336
|
+
for (const [i, from] of points.entries()) {
|
|
313
337
|
const to = points[i + 1];
|
|
314
|
-
if (
|
|
338
|
+
if (to === void 0) continue;
|
|
315
339
|
const direction = unitDirection(from, to);
|
|
316
340
|
if (direction === void 0) continue;
|
|
317
341
|
cursor = from;
|
|
318
342
|
let segmentRemaining = Math.hypot(to.x - from.x, to.y - from.y);
|
|
319
|
-
while (segmentRemaining > 0
|
|
343
|
+
while (segmentRemaining > 0) {
|
|
320
344
|
const step = Math.min(segmentRemaining, remaining);
|
|
321
345
|
cursor = {
|
|
322
346
|
x: cursor.x + direction.x * step,
|
|
@@ -328,7 +352,7 @@ function dashPolyline(subpath, dashPx) {
|
|
|
328
352
|
if (remaining === 0) atBoundary();
|
|
329
353
|
}
|
|
330
354
|
}
|
|
331
|
-
|
|
355
|
+
endOnPiece();
|
|
332
356
|
return pieces;
|
|
333
357
|
}
|
|
334
358
|
//#endregion
|
|
@@ -351,6 +375,9 @@ var CpuRasteriser = class {
|
|
|
351
375
|
};
|
|
352
376
|
this.decodedImages.clear();
|
|
353
377
|
}
|
|
378
|
+
decodedImageForTesting(bytes) {
|
|
379
|
+
return this.decodedImages.get(decodeCacheKey(bytes));
|
|
380
|
+
}
|
|
354
381
|
draw(op) {
|
|
355
382
|
if (op.kind === "fillRect") {
|
|
356
383
|
this.fillRectOp(op);
|
|
@@ -380,10 +407,9 @@ var CpuRasteriser = class {
|
|
|
380
407
|
fillRectOp(op) {
|
|
381
408
|
const { geometry, canvas } = this.requirePage("draw");
|
|
382
409
|
const left = Math.max(op.xPx, 0);
|
|
383
|
-
const right = Math.min(op.xPx + op.widthPx, geometry.widthPx);
|
|
384
410
|
const top = Math.max(op.yPx, 0);
|
|
385
|
-
const
|
|
386
|
-
|
|
411
|
+
const right = Math.max(Math.min(op.xPx + op.widthPx, geometry.widthPx), left);
|
|
412
|
+
const bottom = Math.max(Math.min(op.yPx + op.heightPx, geometry.heightPx), top);
|
|
387
413
|
const colour = colourBytes(op.color);
|
|
388
414
|
const columnStart = Math.floor(left);
|
|
389
415
|
const columnEnd = Math.ceil(right);
|
|
@@ -391,11 +417,9 @@ var CpuRasteriser = class {
|
|
|
391
417
|
const rowEnd = Math.ceil(bottom);
|
|
392
418
|
for (let row = rowStart; row < rowEnd; row++) {
|
|
393
419
|
const coverageY = pixelOverlap(row, top, bottom);
|
|
394
|
-
if (coverageY === 0) continue;
|
|
395
420
|
const rowBase = row * geometry.widthPx;
|
|
396
421
|
for (let column = columnStart; column < columnEnd; column++) {
|
|
397
422
|
const coverage = coverageY * pixelOverlap(column, left, right);
|
|
398
|
-
if (coverage === 0) continue;
|
|
399
423
|
this.blendPixel(canvas, rowBase + column, colour, coverage);
|
|
400
424
|
}
|
|
401
425
|
}
|
|
@@ -430,27 +454,21 @@ var CpuRasteriser = class {
|
|
|
430
454
|
mask.reset();
|
|
431
455
|
mask.fillPolygons([quad], "nonzero");
|
|
432
456
|
const inverse = invertMatrix(op.matrix);
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
this.blendPixel(canvas, rowBase + column, {
|
|
445
|
-
r,
|
|
446
|
-
g,
|
|
447
|
-
b
|
|
448
|
-
}, samples / 16 * a);
|
|
449
|
-
}
|
|
457
|
+
const { first, last } = mask.markedRange();
|
|
458
|
+
for (let i = first; i <= last; i++) {
|
|
459
|
+
const samples = mask.countAt(i);
|
|
460
|
+
const row = Math.floor(i / geometry.widthPx);
|
|
461
|
+
const [u, v] = inverse(i % geometry.widthPx + .5, row + .5);
|
|
462
|
+
const [r, g, b, a] = sampleBilinear(source, u, v);
|
|
463
|
+
this.blendPixel(canvas, i, {
|
|
464
|
+
r,
|
|
465
|
+
g,
|
|
466
|
+
b
|
|
467
|
+
}, samples / 16 * a);
|
|
450
468
|
}
|
|
451
469
|
}
|
|
452
470
|
decodeCached(bytes) {
|
|
453
|
-
const key =
|
|
471
|
+
const key = decodeCacheKey(bytes);
|
|
454
472
|
const cached = this.decodedImages.get(key);
|
|
455
473
|
if (cached !== void 0) return cached;
|
|
456
474
|
const decoded = (0, byte_codec.decodePng)(bytes);
|
|
@@ -459,10 +477,9 @@ var CpuRasteriser = class {
|
|
|
459
477
|
}
|
|
460
478
|
blendMask(canvas, mask, color) {
|
|
461
479
|
const colour = colourBytes(color);
|
|
462
|
-
const
|
|
463
|
-
for (let i =
|
|
480
|
+
const { first, last } = mask.markedRange();
|
|
481
|
+
for (let i = first; i <= last; i++) {
|
|
464
482
|
const samples = mask.countAt(i);
|
|
465
|
-
if (samples === 0) continue;
|
|
466
483
|
this.blendPixel(canvas, i, colour, samples / 16);
|
|
467
484
|
}
|
|
468
485
|
}
|
|
@@ -480,8 +497,11 @@ function colourBytes(color) {
|
|
|
480
497
|
b: Math.round(color.b * 255)
|
|
481
498
|
};
|
|
482
499
|
}
|
|
500
|
+
function decodeCacheKey(bytes) {
|
|
501
|
+
return `${bytes.length}:${(0, byte_codec.crc32)(bytes)}`;
|
|
502
|
+
}
|
|
483
503
|
function pixelOverlap(index, edgeLow, edgeHigh) {
|
|
484
|
-
return Math.
|
|
504
|
+
return Math.min(index + 1, edgeHigh) - Math.max(index, edgeLow);
|
|
485
505
|
}
|
|
486
506
|
function quadCorners(matrix) {
|
|
487
507
|
const [a, b, c, d, e, f] = matrix;
|
|
@@ -512,8 +532,8 @@ function sampleBilinear(source, u, v) {
|
|
|
512
532
|
const sy = Math.min(Math.max(clampedV * source.height - .5, 0), source.height - 1);
|
|
513
533
|
const x0 = Math.floor(sx);
|
|
514
534
|
const y0 = Math.floor(sy);
|
|
515
|
-
const x1 =
|
|
516
|
-
const y1 =
|
|
535
|
+
const x1 = x0 + 1;
|
|
536
|
+
const y1 = y0 + 1;
|
|
517
537
|
const fx = sx - x0;
|
|
518
538
|
const fy = sy - y0;
|
|
519
539
|
const channelCount = source.channels === 1 ? 1 : 3;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RawImage } from "byte-codec";
|
|
1
2
|
import { PageRasteriser, RasterDrawOp, RasterPageGeometry } from "pdf-codec/raster";
|
|
2
3
|
//#region src/rasteriser.d.ts
|
|
3
4
|
interface RasterCpuDiagnostic {
|
|
@@ -14,6 +15,7 @@ declare class CpuRasteriser implements PageRasteriser {
|
|
|
14
15
|
private readonly decodedImages;
|
|
15
16
|
constructor(options?: CpuRasteriserOptions);
|
|
16
17
|
beginPage(geometry: RasterPageGeometry): void;
|
|
18
|
+
decodedImageForTesting(bytes: Uint8Array<ArrayBuffer>): RawImage | undefined;
|
|
17
19
|
draw(op: RasterDrawOp): void;
|
|
18
20
|
finish(): Uint8Array<ArrayBuffer>;
|
|
19
21
|
private requirePage;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RawImage } from "byte-codec";
|
|
1
2
|
import { PageRasteriser, RasterDrawOp, RasterPageGeometry } from "pdf-codec/raster";
|
|
2
3
|
//#region src/rasteriser.d.ts
|
|
3
4
|
interface RasterCpuDiagnostic {
|
|
@@ -14,6 +15,7 @@ declare class CpuRasteriser implements PageRasteriser {
|
|
|
14
15
|
private readonly decodedImages;
|
|
15
16
|
constructor(options?: CpuRasteriserOptions);
|
|
16
17
|
beginPage(geometry: RasterPageGeometry): void;
|
|
18
|
+
decodedImageForTesting(bytes: Uint8Array<ArrayBuffer>): RawImage | undefined;
|
|
17
19
|
draw(op: RasterDrawOp): void;
|
|
18
20
|
finish(): Uint8Array<ArrayBuffer>;
|
|
19
21
|
private requirePage;
|
package/dist/index.js
CHANGED
|
@@ -1,49 +1,61 @@
|
|
|
1
1
|
import { crc32, decodePng, encodePng } from "byte-codec";
|
|
2
|
+
function firstSampleAtOrAfter(coordinate) {
|
|
3
|
+
return Math.max(0, Math.ceil(coordinate * 4 - .5));
|
|
4
|
+
}
|
|
5
|
+
function lastSampleBefore(coordinate, sizePx) {
|
|
6
|
+
return Math.min(4 * sizePx - 1, Math.floor(coordinate * 4 - .5));
|
|
7
|
+
}
|
|
2
8
|
var CoverageMask = class {
|
|
3
9
|
widthPx;
|
|
4
10
|
heightPx;
|
|
5
11
|
counts;
|
|
12
|
+
markedFirst;
|
|
13
|
+
markedLast;
|
|
6
14
|
constructor(widthPx, heightPx) {
|
|
7
15
|
this.widthPx = widthPx;
|
|
8
16
|
this.heightPx = heightPx;
|
|
9
17
|
this.counts = new Uint16Array(widthPx * heightPx);
|
|
18
|
+
this.markedFirst = this.counts.length;
|
|
19
|
+
this.markedLast = 0;
|
|
10
20
|
}
|
|
11
21
|
reset() {
|
|
12
22
|
this.counts.fill(0);
|
|
23
|
+
this.markedFirst = this.counts.length;
|
|
24
|
+
this.markedLast = 0;
|
|
13
25
|
}
|
|
14
26
|
countAt(pixelIndex) {
|
|
15
27
|
return this.counts[pixelIndex] ?? 0;
|
|
16
28
|
}
|
|
29
|
+
markedRange() {
|
|
30
|
+
return {
|
|
31
|
+
first: this.markedFirst,
|
|
32
|
+
last: this.markedLast
|
|
33
|
+
};
|
|
34
|
+
}
|
|
17
35
|
fillPolygons(polygons, fillRule) {
|
|
18
|
-
|
|
19
|
-
let minY = Infinity;
|
|
20
|
-
let maxY = -Infinity;
|
|
36
|
+
const boundedYs = [];
|
|
21
37
|
for (const polygon of polygons) {
|
|
22
38
|
if (polygon.length < 3) continue;
|
|
23
|
-
for (const point of polygon)
|
|
24
|
-
minY = Math.min(minY, point.y);
|
|
25
|
-
maxY = Math.max(maxY, point.y);
|
|
26
|
-
}
|
|
39
|
+
for (const point of polygon) boundedYs.push(point.y);
|
|
27
40
|
}
|
|
28
|
-
if (
|
|
29
|
-
const rowMin =
|
|
30
|
-
const rowMax =
|
|
41
|
+
if (boundedYs.length === 0) return;
|
|
42
|
+
const rowMin = firstSampleAtOrAfter(boundedYs.reduce((lowest, y) => Math.min(lowest, y)));
|
|
43
|
+
const rowMax = lastSampleBefore(boundedYs.reduce((highest, y) => Math.max(highest, y)), this.heightPx);
|
|
31
44
|
const crossings = [];
|
|
32
45
|
for (let row = rowMin; row <= rowMax; row++) {
|
|
33
46
|
const y = (row + .5) / 4;
|
|
34
47
|
crossings.length = 0;
|
|
35
|
-
for (const polygon of polygons) for (
|
|
36
|
-
const a = polygon[i];
|
|
48
|
+
for (const polygon of polygons) for (const [i, a] of polygon.entries()) {
|
|
37
49
|
const b = polygon[(i + 1) % polygon.length];
|
|
38
|
-
if (
|
|
39
|
-
|
|
50
|
+
if (b === void 0) continue;
|
|
51
|
+
const aAtOrAboveRow = a.y <= y;
|
|
52
|
+
if (aAtOrAboveRow === b.y <= y) continue;
|
|
40
53
|
const t = (y - a.y) / (b.y - a.y);
|
|
41
54
|
crossings.push({
|
|
42
55
|
x: a.x + t * (b.x - a.x),
|
|
43
|
-
|
|
56
|
+
downward: aAtOrAboveRow
|
|
44
57
|
});
|
|
45
58
|
}
|
|
46
|
-
if (crossings.length === 0) continue;
|
|
47
59
|
crossings.sort((p, q) => p.x - q.x);
|
|
48
60
|
const pixelRowBase = Math.floor(row / 4) * this.widthPx;
|
|
49
61
|
let spanStartX;
|
|
@@ -57,9 +69,10 @@ var CoverageMask = class {
|
|
|
57
69
|
}
|
|
58
70
|
continue;
|
|
59
71
|
}
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
72
|
+
const wasZero = winding === 0;
|
|
73
|
+
if (crossing.downward) winding++;
|
|
74
|
+
else winding--;
|
|
75
|
+
if (wasZero) spanStartX = crossing.x;
|
|
63
76
|
else if (spanStartX !== void 0 && winding === 0) {
|
|
64
77
|
this.markSpan(pixelRowBase, spanStartX, crossing.x);
|
|
65
78
|
spanStartX = void 0;
|
|
@@ -69,12 +82,16 @@ var CoverageMask = class {
|
|
|
69
82
|
}
|
|
70
83
|
markSpan(pixelRowBase, startX, endX) {
|
|
71
84
|
if (endX <= startX) return;
|
|
72
|
-
const columnFirst =
|
|
73
|
-
const columnLast =
|
|
85
|
+
const columnFirst = firstSampleAtOrAfter(startX);
|
|
86
|
+
const columnLast = lastSampleBefore(endX, this.widthPx);
|
|
74
87
|
for (let c = columnFirst; c <= columnLast; c++) {
|
|
75
88
|
const cellIndex = pixelRowBase + Math.floor(c / 4);
|
|
76
89
|
const cell = this.counts[cellIndex];
|
|
77
|
-
if (cell !== void 0)
|
|
90
|
+
if (cell !== void 0) {
|
|
91
|
+
this.counts[cellIndex] = cell + 1;
|
|
92
|
+
this.markedFirst = Math.min(this.markedFirst, cellIndex);
|
|
93
|
+
this.markedLast = Math.max(this.markedLast, cellIndex);
|
|
94
|
+
}
|
|
78
95
|
}
|
|
79
96
|
}
|
|
80
97
|
};
|
|
@@ -155,10 +172,9 @@ function strokeOutlinePolygons(subpaths, widthPx, dashPx) {
|
|
|
155
172
|
const half = Math.max(widthPx, 1) / 2;
|
|
156
173
|
const polygons = [];
|
|
157
174
|
const emitPiece = (piece) => {
|
|
158
|
-
for (
|
|
159
|
-
const p = piece[i];
|
|
175
|
+
for (const [i, p] of piece.entries()) {
|
|
160
176
|
const q = piece[i + 1];
|
|
161
|
-
if (
|
|
177
|
+
if (q === void 0) continue;
|
|
162
178
|
const d = unitDirection(p, q);
|
|
163
179
|
if (d === void 0) continue;
|
|
164
180
|
const n = {
|
|
@@ -184,11 +200,10 @@ function strokeOutlinePolygons(subpaths, widthPx, dashPx) {
|
|
|
184
200
|
}
|
|
185
201
|
]);
|
|
186
202
|
}
|
|
187
|
-
for (
|
|
188
|
-
const vertex = piece[i];
|
|
203
|
+
for (const [i, vertex] of piece.entries()) {
|
|
189
204
|
const before = piece[i - 1];
|
|
190
205
|
const after = piece[i + 1];
|
|
191
|
-
if (
|
|
206
|
+
if (before === void 0 || after === void 0) continue;
|
|
192
207
|
emitJoinWedge(polygons, vertex, before, after, half);
|
|
193
208
|
}
|
|
194
209
|
};
|
|
@@ -197,7 +212,7 @@ function strokeOutlinePolygons(subpaths, widthPx, dashPx) {
|
|
|
197
212
|
const first = subpath.points[0];
|
|
198
213
|
if (first === void 0) continue;
|
|
199
214
|
emitPiece(subpath.closed ? [...subpath.points, first] : subpath.points);
|
|
200
|
-
if (subpath.closed
|
|
215
|
+
if (subpath.closed) {
|
|
201
216
|
const last = subpath.points[subpath.points.length - 1];
|
|
202
217
|
const second = subpath.points[1];
|
|
203
218
|
if (last !== void 0 && second !== void 0) emitJoinWedge(polygons, first, last, second, half);
|
|
@@ -214,14 +229,20 @@ function emitJoinWedge(polygons, vertex, before, after, half) {
|
|
|
214
229
|
if (d1 === void 0 || d2 === void 0) return;
|
|
215
230
|
const cross = d1.x * d2.y - d1.y * d2.x;
|
|
216
231
|
if (Math.abs(cross) < 1e-12) return;
|
|
217
|
-
const
|
|
218
|
-
const n1 = {
|
|
219
|
-
x:
|
|
220
|
-
y: d1.x
|
|
232
|
+
const outward = turnsOutwardPositive(cross);
|
|
233
|
+
const n1 = outward ? {
|
|
234
|
+
x: d1.y,
|
|
235
|
+
y: -d1.x
|
|
236
|
+
} : {
|
|
237
|
+
x: -d1.y,
|
|
238
|
+
y: d1.x
|
|
221
239
|
};
|
|
222
|
-
const n2 = {
|
|
223
|
-
x:
|
|
224
|
-
y: d2.x
|
|
240
|
+
const n2 = outward ? {
|
|
241
|
+
x: d2.y,
|
|
242
|
+
y: -d2.x
|
|
243
|
+
} : {
|
|
244
|
+
x: -d2.y,
|
|
245
|
+
y: d2.x
|
|
225
246
|
};
|
|
226
247
|
const a1 = {
|
|
227
248
|
x: vertex.x + n1.x * half,
|
|
@@ -231,10 +252,9 @@ function emitJoinWedge(polygons, vertex, before, after, half) {
|
|
|
231
252
|
x: vertex.x + n2.x * half,
|
|
232
253
|
y: vertex.y + n2.y * half
|
|
233
254
|
};
|
|
234
|
-
const
|
|
235
|
-
const denominator = 1 + dot;
|
|
255
|
+
const denominator = 1 + (n1.x * n2.x + n1.y * n2.y);
|
|
236
256
|
let wedge;
|
|
237
|
-
if (denominator
|
|
257
|
+
if (takesMiterBranch(denominator)) {
|
|
238
258
|
const scale = half / denominator;
|
|
239
259
|
wedge = [
|
|
240
260
|
a1,
|
|
@@ -250,17 +270,22 @@ function emitJoinWedge(polygons, vertex, before, after, half) {
|
|
|
250
270
|
vertex,
|
|
251
271
|
a2
|
|
252
272
|
];
|
|
253
|
-
polygons.push(
|
|
273
|
+
polygons.push(withNegativeWinding(wedge));
|
|
274
|
+
}
|
|
275
|
+
function turnsOutwardPositive(cross) {
|
|
276
|
+
return cross > 0;
|
|
277
|
+
}
|
|
278
|
+
function takesMiterBranch(denominator) {
|
|
279
|
+
return 2 <= 100 * denominator;
|
|
254
280
|
}
|
|
255
|
-
function
|
|
256
|
-
return
|
|
281
|
+
function withNegativeWinding(polygon) {
|
|
282
|
+
return polygonSignedArea(polygon) < 0 ? [...polygon] : [...polygon].reverse();
|
|
257
283
|
}
|
|
258
284
|
function polygonSignedArea(polygon) {
|
|
259
285
|
let area = 0;
|
|
260
|
-
for (
|
|
261
|
-
const a = polygon[i];
|
|
286
|
+
for (const [i, a] of polygon.entries()) {
|
|
262
287
|
const b = polygon[(i + 1) % polygon.length];
|
|
263
|
-
if (
|
|
288
|
+
if (b === void 0) continue;
|
|
264
289
|
area += a.x * b.y - b.x * a.y;
|
|
265
290
|
}
|
|
266
291
|
return area;
|
|
@@ -277,17 +302,17 @@ function unitDirection(p, q) {
|
|
|
277
302
|
}
|
|
278
303
|
function dashPolyline(subpath, dashPx) {
|
|
279
304
|
if (!dashPx.some((length) => length > 0)) return [];
|
|
280
|
-
const pattern =
|
|
281
|
-
const
|
|
282
|
-
const start = points[0];
|
|
305
|
+
const pattern = [...dashPx, ...dashPx];
|
|
306
|
+
const start = subpath.points[0];
|
|
283
307
|
if (start === void 0) return [];
|
|
308
|
+
const points = subpath.closed ? [...subpath.points, start] : subpath.points;
|
|
284
309
|
const pieces = [];
|
|
285
310
|
const endOnPiece = () => {
|
|
286
311
|
if (current.length >= 2) pieces.push(current);
|
|
287
312
|
current = [];
|
|
288
313
|
};
|
|
289
314
|
const atBoundary = () => {
|
|
290
|
-
|
|
315
|
+
endOnPiece();
|
|
291
316
|
for (;;) {
|
|
292
317
|
index = (index + 1) % pattern.length;
|
|
293
318
|
const entry = pattern[index];
|
|
@@ -306,16 +331,15 @@ function dashPolyline(subpath, dashPx) {
|
|
|
306
331
|
if (firstEntry !== void 0 && firstEntry > 0) {
|
|
307
332
|
remaining = firstEntry;
|
|
308
333
|
current = [cursor];
|
|
309
|
-
}
|
|
310
|
-
for (
|
|
311
|
-
const from = points[i];
|
|
334
|
+
}
|
|
335
|
+
for (const [i, from] of points.entries()) {
|
|
312
336
|
const to = points[i + 1];
|
|
313
|
-
if (
|
|
337
|
+
if (to === void 0) continue;
|
|
314
338
|
const direction = unitDirection(from, to);
|
|
315
339
|
if (direction === void 0) continue;
|
|
316
340
|
cursor = from;
|
|
317
341
|
let segmentRemaining = Math.hypot(to.x - from.x, to.y - from.y);
|
|
318
|
-
while (segmentRemaining > 0
|
|
342
|
+
while (segmentRemaining > 0) {
|
|
319
343
|
const step = Math.min(segmentRemaining, remaining);
|
|
320
344
|
cursor = {
|
|
321
345
|
x: cursor.x + direction.x * step,
|
|
@@ -327,7 +351,7 @@ function dashPolyline(subpath, dashPx) {
|
|
|
327
351
|
if (remaining === 0) atBoundary();
|
|
328
352
|
}
|
|
329
353
|
}
|
|
330
|
-
|
|
354
|
+
endOnPiece();
|
|
331
355
|
return pieces;
|
|
332
356
|
}
|
|
333
357
|
//#endregion
|
|
@@ -350,6 +374,9 @@ var CpuRasteriser = class {
|
|
|
350
374
|
};
|
|
351
375
|
this.decodedImages.clear();
|
|
352
376
|
}
|
|
377
|
+
decodedImageForTesting(bytes) {
|
|
378
|
+
return this.decodedImages.get(decodeCacheKey(bytes));
|
|
379
|
+
}
|
|
353
380
|
draw(op) {
|
|
354
381
|
if (op.kind === "fillRect") {
|
|
355
382
|
this.fillRectOp(op);
|
|
@@ -379,10 +406,9 @@ var CpuRasteriser = class {
|
|
|
379
406
|
fillRectOp(op) {
|
|
380
407
|
const { geometry, canvas } = this.requirePage("draw");
|
|
381
408
|
const left = Math.max(op.xPx, 0);
|
|
382
|
-
const right = Math.min(op.xPx + op.widthPx, geometry.widthPx);
|
|
383
409
|
const top = Math.max(op.yPx, 0);
|
|
384
|
-
const
|
|
385
|
-
|
|
410
|
+
const right = Math.max(Math.min(op.xPx + op.widthPx, geometry.widthPx), left);
|
|
411
|
+
const bottom = Math.max(Math.min(op.yPx + op.heightPx, geometry.heightPx), top);
|
|
386
412
|
const colour = colourBytes(op.color);
|
|
387
413
|
const columnStart = Math.floor(left);
|
|
388
414
|
const columnEnd = Math.ceil(right);
|
|
@@ -390,11 +416,9 @@ var CpuRasteriser = class {
|
|
|
390
416
|
const rowEnd = Math.ceil(bottom);
|
|
391
417
|
for (let row = rowStart; row < rowEnd; row++) {
|
|
392
418
|
const coverageY = pixelOverlap(row, top, bottom);
|
|
393
|
-
if (coverageY === 0) continue;
|
|
394
419
|
const rowBase = row * geometry.widthPx;
|
|
395
420
|
for (let column = columnStart; column < columnEnd; column++) {
|
|
396
421
|
const coverage = coverageY * pixelOverlap(column, left, right);
|
|
397
|
-
if (coverage === 0) continue;
|
|
398
422
|
this.blendPixel(canvas, rowBase + column, colour, coverage);
|
|
399
423
|
}
|
|
400
424
|
}
|
|
@@ -429,27 +453,21 @@ var CpuRasteriser = class {
|
|
|
429
453
|
mask.reset();
|
|
430
454
|
mask.fillPolygons([quad], "nonzero");
|
|
431
455
|
const inverse = invertMatrix(op.matrix);
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
this.blendPixel(canvas, rowBase + column, {
|
|
444
|
-
r,
|
|
445
|
-
g,
|
|
446
|
-
b
|
|
447
|
-
}, samples / 16 * a);
|
|
448
|
-
}
|
|
456
|
+
const { first, last } = mask.markedRange();
|
|
457
|
+
for (let i = first; i <= last; i++) {
|
|
458
|
+
const samples = mask.countAt(i);
|
|
459
|
+
const row = Math.floor(i / geometry.widthPx);
|
|
460
|
+
const [u, v] = inverse(i % geometry.widthPx + .5, row + .5);
|
|
461
|
+
const [r, g, b, a] = sampleBilinear(source, u, v);
|
|
462
|
+
this.blendPixel(canvas, i, {
|
|
463
|
+
r,
|
|
464
|
+
g,
|
|
465
|
+
b
|
|
466
|
+
}, samples / 16 * a);
|
|
449
467
|
}
|
|
450
468
|
}
|
|
451
469
|
decodeCached(bytes) {
|
|
452
|
-
const key =
|
|
470
|
+
const key = decodeCacheKey(bytes);
|
|
453
471
|
const cached = this.decodedImages.get(key);
|
|
454
472
|
if (cached !== void 0) return cached;
|
|
455
473
|
const decoded = decodePng(bytes);
|
|
@@ -458,10 +476,9 @@ var CpuRasteriser = class {
|
|
|
458
476
|
}
|
|
459
477
|
blendMask(canvas, mask, color) {
|
|
460
478
|
const colour = colourBytes(color);
|
|
461
|
-
const
|
|
462
|
-
for (let i =
|
|
479
|
+
const { first, last } = mask.markedRange();
|
|
480
|
+
for (let i = first; i <= last; i++) {
|
|
463
481
|
const samples = mask.countAt(i);
|
|
464
|
-
if (samples === 0) continue;
|
|
465
482
|
this.blendPixel(canvas, i, colour, samples / 16);
|
|
466
483
|
}
|
|
467
484
|
}
|
|
@@ -479,8 +496,11 @@ function colourBytes(color) {
|
|
|
479
496
|
b: Math.round(color.b * 255)
|
|
480
497
|
};
|
|
481
498
|
}
|
|
499
|
+
function decodeCacheKey(bytes) {
|
|
500
|
+
return `${bytes.length}:${crc32(bytes)}`;
|
|
501
|
+
}
|
|
482
502
|
function pixelOverlap(index, edgeLow, edgeHigh) {
|
|
483
|
-
return Math.
|
|
503
|
+
return Math.min(index + 1, edgeHigh) - Math.max(index, edgeLow);
|
|
484
504
|
}
|
|
485
505
|
function quadCorners(matrix) {
|
|
486
506
|
const [a, b, c, d, e, f] = matrix;
|
|
@@ -511,8 +531,8 @@ function sampleBilinear(source, u, v) {
|
|
|
511
531
|
const sy = Math.min(Math.max(clampedV * source.height - .5, 0), source.height - 1);
|
|
512
532
|
const x0 = Math.floor(sx);
|
|
513
533
|
const y0 = Math.floor(sy);
|
|
514
|
-
const x1 =
|
|
515
|
-
const y1 =
|
|
534
|
+
const x1 = x0 + 1;
|
|
535
|
+
const y1 = y0 + 1;
|
|
516
536
|
const fx = sx - x0;
|
|
517
537
|
const fy = sy - y0;
|
|
518
538
|
const channelCount = source.channels === 1 ? 1 : 3;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pdf-raster-cpu",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Pure-software reference backend for pdf-codec's PageRasteriser port: an RGBA scanline rasteriser that renders a page (or a located figure region) to PNG bytes with no canvas dependency, on any Worker-isomorphic runtime.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -78,8 +78,8 @@
|
|
|
78
78
|
"license": "MIT",
|
|
79
79
|
"packageManager": "pnpm@11.6.0",
|
|
80
80
|
"dependencies": {
|
|
81
|
-
"byte-codec": "1.5.
|
|
82
|
-
"pdf-codec": "4.8.
|
|
81
|
+
"byte-codec": "1.5.2",
|
|
82
|
+
"pdf-codec": "4.8.2"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@arethetypeswrong/cli": "0.18.5",
|