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