emf-converter 3.1.0 → 3.2.0

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.
Files changed (4) hide show
  1. package/README.md +10 -5
  2. package/dist/index.js +1623 -1112
  3. package/dist/index.mjs +1623 -1112
  4. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -50,6 +50,428 @@ function invertCssColor(color) {
50
50
  return color;
51
51
  }
52
52
 
53
+ // src/emf-dib-rle-decoder.ts
54
+ function decodeRleBitmap(view, bitsOffset, bitsSize, width, height, _topDown, isRle4, colorTable, out, setPixel) {
55
+ let x = 0;
56
+ let y = height - 1;
57
+ let off = bitsOffset;
58
+ const endOff = bitsOffset + bitsSize;
59
+ while (off + 1 < endOff && y >= 0) {
60
+ const first = view.getUint8(off);
61
+ const second = view.getUint8(off + 1);
62
+ off += 2;
63
+ if (first === 0) {
64
+ if (second === 0) {
65
+ x = 0;
66
+ y--;
67
+ } else if (second === 1) {
68
+ break;
69
+ } else if (second === 2) {
70
+ if (off + 1 >= endOff) {
71
+ break;
72
+ }
73
+ x += view.getUint8(off);
74
+ y -= view.getUint8(off + 1);
75
+ off += 2;
76
+ } else {
77
+ const count = second;
78
+ if (!isRle4) {
79
+ for (let i = 0; i < count && off < endOff && x < width; i++) {
80
+ const idx = view.getUint8(off++);
81
+ if (idx < colorTable.length) {
82
+ setPixel(
83
+ x,
84
+ height - 1 - y,
85
+ colorTable[idx][0],
86
+ colorTable[idx][1],
87
+ colorTable[idx][2],
88
+ 255
89
+ );
90
+ }
91
+ x++;
92
+ }
93
+ if (count & 1) {
94
+ off++;
95
+ }
96
+ } else {
97
+ const bytes = Math.ceil(count / 2);
98
+ let pi = 0;
99
+ for (let i = 0; i < bytes && off < endOff; i++) {
100
+ const byte = view.getUint8(off++);
101
+ for (let nibble = 0; nibble < 2 && pi < count; nibble++) {
102
+ const idx = nibble === 0 ? byte >> 4 & 15 : byte & 15;
103
+ if (idx < colorTable.length && x < width) {
104
+ setPixel(
105
+ x,
106
+ height - 1 - y,
107
+ colorTable[idx][0],
108
+ colorTable[idx][1],
109
+ colorTable[idx][2],
110
+ 255
111
+ );
112
+ }
113
+ x++;
114
+ pi++;
115
+ }
116
+ }
117
+ if (bytes & 1) {
118
+ off++;
119
+ }
120
+ }
121
+ }
122
+ } else if (!isRle4) {
123
+ const idx = second;
124
+ const c = idx < colorTable.length ? colorTable[idx] : [0, 0, 0];
125
+ for (let i = 0; i < first && x < width; i++) {
126
+ setPixel(x, height - 1 - y, c[0], c[1], c[2], 255);
127
+ x++;
128
+ }
129
+ } else {
130
+ const hi = second >> 4 & 15;
131
+ const lo = second & 15;
132
+ for (let i = 0; i < first && x < width; i++) {
133
+ const idx = (i & 1) === 0 ? hi : lo;
134
+ if (idx < colorTable.length) {
135
+ setPixel(
136
+ x,
137
+ height - 1 - y,
138
+ colorTable[idx][0],
139
+ colorTable[idx][1],
140
+ colorTable[idx][2],
141
+ 255
142
+ );
143
+ }
144
+ x++;
145
+ }
146
+ }
147
+ }
148
+ return createImageDataCompat(
149
+ new Uint8ClampedArray(out.buffer, out.byteOffset, out.byteLength),
150
+ width,
151
+ height
152
+ );
153
+ }
154
+
155
+ // src/emf-dib-uncompressed.ts
156
+ function countTrailingZeros(v) {
157
+ if (v === 0) {
158
+ return 0;
159
+ }
160
+ let c = 0;
161
+ let val = v;
162
+ while ((val & 1) === 0) {
163
+ val >>>= 1;
164
+ c++;
165
+ }
166
+ return c;
167
+ }
168
+ var BI_BITFIELDS = 3;
169
+ function parseBitfieldMasks(view, bmiOffset, headerSize, compression, bitCount) {
170
+ let rMask = 0, gMask = 0, bMask = 0;
171
+ let rShift = 0, gShift = 0, bShift = 0;
172
+ let rMax = 1, gMax = 1, bMax = 1;
173
+ if (compression === BI_BITFIELDS) {
174
+ const bfOff = bmiOffset + headerSize;
175
+ if (bfOff + 12 > view.byteLength) {
176
+ return null;
177
+ }
178
+ rMask = view.getUint32(bfOff, true);
179
+ gMask = view.getUint32(bfOff + 4, true);
180
+ bMask = view.getUint32(bfOff + 8, true);
181
+ rShift = countTrailingZeros(rMask);
182
+ gShift = countTrailingZeros(gMask);
183
+ bShift = countTrailingZeros(bMask);
184
+ rMax = rMask >>> rShift || 1;
185
+ gMax = gMask >>> gShift || 1;
186
+ bMax = bMask >>> bShift || 1;
187
+ } else if (bitCount === 16) {
188
+ rMask = 31744;
189
+ gMask = 992;
190
+ bMask = 31;
191
+ rShift = 10;
192
+ gShift = 5;
193
+ bShift = 0;
194
+ rMax = 31;
195
+ gMax = 31;
196
+ bMax = 31;
197
+ }
198
+ return { rMask, gMask, bMask, rShift, gShift, bShift, rMax, gMax, bMax };
199
+ }
200
+ function decodeUncompressedRows(view, bitsOffset, width, height, topDown, bitCount, colorTable, masks, out) {
201
+ const rowStride = Math.floor((bitCount * width + 31) / 32) * 4;
202
+ const { rMask, gMask, bMask, rShift, gShift, bShift, rMax, gMax, bMax } = masks;
203
+ for (let y = 0; y < height; y++) {
204
+ const srcY = topDown ? y : height - 1 - y;
205
+ const rowStart = bitsOffset + srcY * rowStride;
206
+ if (rowStart + rowStride > view.byteLength) {
207
+ continue;
208
+ }
209
+ for (let x = 0; x < width; x++) {
210
+ const dstPx = (y * width + x) * 4;
211
+ if (bitCount === 1) {
212
+ const byteIdx = rowStart + (x >> 3);
213
+ const bit = view.getUint8(byteIdx) >> 7 - (x & 7) & 1;
214
+ if (bit < colorTable.length) {
215
+ out[dstPx] = colorTable[bit][0];
216
+ out[dstPx + 1] = colorTable[bit][1];
217
+ out[dstPx + 2] = colorTable[bit][2];
218
+ }
219
+ out[dstPx + 3] = 255;
220
+ } else if (bitCount === 4) {
221
+ const byteIdx = rowStart + (x >> 1);
222
+ const nibble = (x & 1) === 0 ? view.getUint8(byteIdx) >> 4 & 15 : view.getUint8(byteIdx) & 15;
223
+ if (nibble < colorTable.length) {
224
+ out[dstPx] = colorTable[nibble][0];
225
+ out[dstPx + 1] = colorTable[nibble][1];
226
+ out[dstPx + 2] = colorTable[nibble][2];
227
+ }
228
+ out[dstPx + 3] = 255;
229
+ } else if (bitCount === 8) {
230
+ const idx = view.getUint8(rowStart + x);
231
+ if (idx < colorTable.length) {
232
+ out[dstPx] = colorTable[idx][0];
233
+ out[dstPx + 1] = colorTable[idx][1];
234
+ out[dstPx + 2] = colorTable[idx][2];
235
+ }
236
+ out[dstPx + 3] = 255;
237
+ } else if (bitCount === 16) {
238
+ const val = view.getUint16(rowStart + x * 2, true);
239
+ out[dstPx] = Math.round(((val & rMask) >>> rShift) * 255 / rMax);
240
+ out[dstPx + 1] = Math.round(((val & gMask) >>> gShift) * 255 / gMax);
241
+ out[dstPx + 2] = Math.round(((val & bMask) >>> bShift) * 255 / bMax);
242
+ out[dstPx + 3] = 255;
243
+ } else if (bitCount === 24) {
244
+ const srcPx = rowStart + x * 3;
245
+ out[dstPx] = view.getUint8(srcPx + 2);
246
+ out[dstPx + 1] = view.getUint8(srcPx + 1);
247
+ out[dstPx + 2] = view.getUint8(srcPx);
248
+ out[dstPx + 3] = 255;
249
+ } else {
250
+ const srcPx = rowStart + x * 4;
251
+ const bb = view.getUint8(srcPx);
252
+ const gg = view.getUint8(srcPx + 1);
253
+ const rr = view.getUint8(srcPx + 2);
254
+ const aa = view.getUint8(srcPx + 3);
255
+ out[dstPx] = rr;
256
+ out[dstPx + 1] = gg;
257
+ out[dstPx + 2] = bb;
258
+ out[dstPx + 3] = aa === 0 ? 255 : aa;
259
+ }
260
+ }
261
+ }
262
+ }
263
+
264
+ // src/emf-dib-decoder.ts
265
+ function decodeDibToImageData(view, bmiOffset, bitsOffset, bitsSize) {
266
+ if (bmiOffset < 0 || bitsOffset < 0 || bmiOffset + 40 > view.byteLength || bitsOffset + bitsSize > view.byteLength) {
267
+ return null;
268
+ }
269
+ const headerSize = view.getUint32(bmiOffset, true);
270
+ if (headerSize < 40 || bmiOffset + headerSize > view.byteLength) {
271
+ return null;
272
+ }
273
+ const width = view.getInt32(bmiOffset + 4, true);
274
+ const heightRaw = view.getInt32(bmiOffset + 8, true);
275
+ const planes = view.getUint16(bmiOffset + 12, true);
276
+ const bitCount = view.getUint16(bmiOffset + 14, true);
277
+ const compression = view.getUint32(bmiOffset + 16, true);
278
+ if (planes !== 1 || width <= 0 || heightRaw === 0) {
279
+ return null;
280
+ }
281
+ if (width > 8192 || Math.abs(heightRaw) > 8192) {
282
+ return null;
283
+ }
284
+ const BI_RGB = 0;
285
+ const BI_RLE8 = 1;
286
+ const BI_RLE4 = 2;
287
+ const BI_BITFIELDS2 = 3;
288
+ if (bitCount !== 1 && bitCount !== 4 && bitCount !== 8 && bitCount !== 16 && bitCount !== 24 && bitCount !== 32) {
289
+ return null;
290
+ }
291
+ if (compression === BI_RLE8 && bitCount !== 8) {
292
+ return null;
293
+ }
294
+ if (compression === BI_RLE4 && bitCount !== 4) {
295
+ return null;
296
+ }
297
+ if (compression === BI_BITFIELDS2 && bitCount !== 16 && bitCount !== 32) {
298
+ return null;
299
+ }
300
+ if (compression !== BI_RGB && compression !== BI_RLE8 && compression !== BI_RLE4 && compression !== BI_BITFIELDS2) {
301
+ return null;
302
+ }
303
+ const height = Math.abs(heightRaw);
304
+ const topDown = heightRaw < 0;
305
+ const colorTable = [];
306
+ if (bitCount <= 8) {
307
+ const maxColors = 1 << bitCount;
308
+ const colorsUsed = view.getUint32(bmiOffset + 32, true) || maxColors;
309
+ const numColors = Math.min(colorsUsed, maxColors);
310
+ const ctOffset = bmiOffset + headerSize;
311
+ if (ctOffset + numColors * 4 > view.byteLength) {
312
+ return null;
313
+ }
314
+ for (let i = 0; i < numColors; i++) {
315
+ const b = view.getUint8(ctOffset + i * 4);
316
+ const g = view.getUint8(ctOffset + i * 4 + 1);
317
+ const r = view.getUint8(ctOffset + i * 4 + 2);
318
+ colorTable.push([r, g, b]);
319
+ }
320
+ }
321
+ const masks = parseBitfieldMasks(view, bmiOffset, headerSize, compression, bitCount);
322
+ if (!masks) {
323
+ return null;
324
+ }
325
+ const out = new Uint8ClampedArray(width * height * 4);
326
+ if (compression === BI_RLE8 || compression === BI_RLE4) {
327
+ const setPixel = (x, y, r, g, b, a) => {
328
+ const dstPx = (y * width + x) * 4;
329
+ out[dstPx] = r;
330
+ out[dstPx + 1] = g;
331
+ out[dstPx + 2] = b;
332
+ out[dstPx + 3] = a;
333
+ };
334
+ return decodeRleBitmap(
335
+ view,
336
+ bitsOffset,
337
+ bitsSize,
338
+ width,
339
+ height,
340
+ topDown,
341
+ compression === BI_RLE4,
342
+ colorTable,
343
+ out,
344
+ setPixel
345
+ );
346
+ }
347
+ decodeUncompressedRows(
348
+ view,
349
+ bitsOffset,
350
+ width,
351
+ height,
352
+ topDown,
353
+ bitCount,
354
+ colorTable,
355
+ masks,
356
+ out
357
+ );
358
+ return createImageDataCompat(out, width, height);
359
+ }
360
+
361
+ // src/emf-gdi-brush-pattern.ts
362
+ var HATCH_ROWS = [
363
+ [0, 0, 0, 255, 0, 0, 0, 0],
364
+ // HS_HORIZONTAL
365
+ [8, 8, 8, 8, 8, 8, 8, 8],
366
+ // HS_VERTICAL
367
+ [128, 64, 32, 16, 8, 4, 2, 1],
368
+ // HS_FDIAGONAL
369
+ [1, 2, 4, 8, 16, 32, 64, 128],
370
+ // HS_BDIAGONAL
371
+ [8, 8, 8, 255, 8, 8, 8, 8],
372
+ // HS_CROSS
373
+ [129, 66, 36, 24, 24, 36, 66, 129]
374
+ // HS_DIAGCROSS
375
+ ];
376
+ function hatchBit(hatch, x, y) {
377
+ const rows = HATCH_ROWS[hatch] ?? HATCH_ROWS[0];
378
+ return (rows[y & 7] >> 7 - (x & 7) & 1) === 1;
379
+ }
380
+ function cssHexToRgb(color) {
381
+ const m = /^#([0-9a-f]{6}|[0-9a-f]{3})$/i.exec(color.trim());
382
+ if (!m) {
383
+ return 0;
384
+ }
385
+ const hex = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];
386
+ return parseInt(hex, 16);
387
+ }
388
+ function decodeMonoBits(view, bmiOffset, bitsOffset) {
389
+ if (bmiOffset + 16 > view.byteLength) {
390
+ return null;
391
+ }
392
+ const width = view.getInt32(bmiOffset + 4, true);
393
+ const heightRaw = view.getInt32(bmiOffset + 8, true);
394
+ const bitCount = view.getUint16(bmiOffset + 14, true);
395
+ const height = Math.abs(heightRaw);
396
+ if (bitCount !== 1 || width <= 0 || height === 0 || width > 256 || height > 256) {
397
+ return null;
398
+ }
399
+ const stride = (width + 31 >> 5) * 4;
400
+ if (bitsOffset + stride * height > view.byteLength) {
401
+ return null;
402
+ }
403
+ const bits = new Uint8Array(width * height);
404
+ for (let row = 0; row < height; row++) {
405
+ const y = heightRaw > 0 ? height - 1 - row : row;
406
+ const rowOff = bitsOffset + row * stride;
407
+ for (let x = 0; x < width; x++) {
408
+ bits[y * width + x] = view.getUint8(rowOff + (x >> 3)) >> 7 - (x & 7) & 1;
409
+ }
410
+ }
411
+ return { width, height, bits };
412
+ }
413
+ function parsePatternBrush(view, offset, dataOff, mono) {
414
+ if (dataOff + 24 > view.byteLength) {
415
+ return null;
416
+ }
417
+ const offBmi = view.getUint32(dataOff + 8, true);
418
+ const offBits = view.getUint32(dataOff + 16, true);
419
+ const cbBits = view.getUint32(dataOff + 20, true);
420
+ const bmi = offset + offBmi;
421
+ const bitsAt = offset + offBits;
422
+ const bitCount = bmi + 16 <= view.byteLength ? view.getUint16(bmi + 14, true) : 0;
423
+ if (mono || bitCount === 1) {
424
+ const decoded = decodeMonoBits(view, bmi, bitsAt);
425
+ if (decoded && mono) {
426
+ return { kind: "mono", ...decoded };
427
+ }
428
+ }
429
+ const image = decodeDibToImageData(view, bmi, bitsAt, cbBits);
430
+ if (!image) {
431
+ return null;
432
+ }
433
+ const rgb = new Uint32Array(image.width * image.height);
434
+ const d = image.data;
435
+ for (let i = 0; i < rgb.length; i++) {
436
+ rgb[i] = d[i * 4] << 16 | d[i * 4 + 1] << 8 | d[i * 4 + 2];
437
+ }
438
+ return { kind: "bitmap", width: image.width, height: image.height, rgb };
439
+ }
440
+ function realizeBrush(state) {
441
+ if (state.brushStyle === 1) {
442
+ return { kind: "none" };
443
+ }
444
+ const pattern = state.brushPattern;
445
+ if (!pattern) {
446
+ return { kind: "solid", rgb: cssHexToRgb(state.brushColor) };
447
+ }
448
+ if (pattern.kind === "bitmap") {
449
+ return { kind: "tile", width: pattern.width, height: pattern.height, rgb: pattern.rgb };
450
+ }
451
+ const bk = cssHexToRgb(state.bkColor);
452
+ if (pattern.kind === "hatch") {
453
+ const fg = cssHexToRgb(state.brushColor);
454
+ const rgb2 = new Uint32Array(64);
455
+ for (let y = 0; y < 8; y++) {
456
+ for (let x = 0; x < 8; x++) {
457
+ rgb2[y * 8 + x] = hatchBit(pattern.hatch, x, y) ? fg : bk;
458
+ }
459
+ }
460
+ return { kind: "tile", width: 8, height: 8, rgb: rgb2 };
461
+ }
462
+ const text = cssHexToRgb(state.textColor);
463
+ const rgb = new Uint32Array(pattern.width * pattern.height);
464
+ for (let i = 0; i < rgb.length; i++) {
465
+ rgb[i] = pattern.bits[i] ? bk : text;
466
+ }
467
+ return { kind: "tile", width: pattern.width, height: pattern.height, rgb };
468
+ }
469
+ function sampleTile(tile, devX, devY, orgX, orgY) {
470
+ const tx = ((devX - orgX) % tile.width + tile.width) % tile.width;
471
+ const ty = ((devY - orgY) % tile.height + tile.height) % tile.height;
472
+ return tile.rgb[ty * tile.width + tx];
473
+ }
474
+
53
475
  // src/emf-gdi-text-layout.ts
54
476
  function cumulativeGlyphOffsets(dx) {
55
477
  const offsets = [];
@@ -235,6 +657,7 @@ var EMFPLUS_OBJECTTYPE_STRINGFORMAT = 7;
235
657
  var EMFPLUS_OBJECTTYPE_REGION = 8;
236
658
  var EMFPLUS_BRUSHTYPE_SOLID = 0;
237
659
  var EMFPLUS_BRUSHTYPE_HATCHFILL = 1;
660
+ var EMFPLUS_BRUSHTYPE_TEXTUREFILL = 2;
238
661
  var EMFPLUS_BRUSHTYPE_PATHGRADIENT = 3;
239
662
  var EMFPLUS_BRUSHTYPE_LINEARGRADIENT = 4;
240
663
  var META_EOF = 0;
@@ -485,7 +908,8 @@ function applyPen(ctx, state) {
485
908
  function applyBrush(ctx, state) {
486
909
  const paint = rop2Paint(state.rop2);
487
910
  ctx.globalCompositeOperation = paint.gco;
488
- if (state.brushStyle === 1) {
911
+ const realized = realizeBrush(state);
912
+ if (realized.kind === "none") {
489
913
  ctx.fillStyle = "rgba(0,0,0,0)";
490
914
  return;
491
915
  }
@@ -637,793 +1061,784 @@ async function exportCanvasToPngDataUrl(canvas) {
637
1061
  emfLog(
638
1062
  `exportCanvasToPngDataUrl: using HTMLCanvasElement.toDataURL (${canvas.width}\xD7${canvas.height})`
639
1063
  );
640
- return canvas.toDataURL("image/png");
641
- }
642
- if (nodeCanvasModule && canvas instanceof nodeCanvasModule.Canvas) {
643
- emfLog(
644
- `exportCanvasToPngDataUrl: using @napi-rs/canvas toDataURLAsync (${canvas.width}\xD7${canvas.height})`
645
- );
646
- return canvas.toDataURLAsync();
647
- }
648
- return null;
649
- }
650
- async function decodeDeferredImageBytes(bytes, mime) {
651
- if (nodeCanvasModule) {
652
- const image = await nodeCanvasModule.loadImage(new Uint8Array(bytes));
653
- return { drawable: image, width: image.width, height: image.height, close: () => {
654
- } };
655
- }
656
- if (typeof createImageBitmap !== "function") {
657
- return null;
658
- }
659
- const blob = mime !== void 0 ? new Blob([bytes], { type: mime }) : new Blob([bytes]);
660
- const bitmap = await createImageBitmap(blob);
661
- return { drawable: bitmap, width: bitmap.width, height: bitmap.height, close: () => bitmap.close() };
662
- }
663
- function canvasDrawImage(ctx, image, dx, dy, dw, dh) {
664
- const draw = ctx.drawImage;
665
- draw.call(ctx, image, dx, dy, dw, dh);
666
- }
667
- function canvasGetImageData(ctx, x, y, w, h) {
668
- const get = ctx.getImageData;
669
- return get.call(ctx, x, y, w, h);
670
- }
671
- function canvasPutImageData(ctx, data, x, y) {
672
- const put = ctx.putImageData;
673
- put.call(ctx, data, x, y);
674
- }
675
- function canvasCreatePattern(ctx, image, repetition) {
676
- const create = ctx.createPattern;
677
- return create.call(ctx, image, repetition);
678
- }
679
- function createImageDataCompat(data, width, height) {
680
- if (typeof ImageData !== "undefined") {
681
- return new ImageData(data, width, height);
682
- }
683
- if (nodeCanvasModule) {
684
- const Ctor = nodeCanvasModule.ImageData;
685
- return new Ctor(data, width, height);
686
- }
687
- throw new ReferenceError("ImageData is not defined");
688
- }
689
-
690
- // src/emf-header-parser.ts
691
- function parseEmfHeader(view) {
692
- emfLog("parseEmfHeader: byteLength =", view.byteLength);
693
- if (view.byteLength < 88) {
694
- return null;
695
- }
696
- const recordType = view.getUint32(0, true);
697
- if (recordType !== EMR_HEADER) {
698
- return null;
699
- }
700
- const boundsLeft = view.getInt32(8, true);
701
- const boundsTop = view.getInt32(12, true);
702
- const boundsRight = view.getInt32(16, true);
703
- const boundsBottom = view.getInt32(20, true);
704
- const frameLeft = view.getInt32(24, true);
705
- const frameTop = view.getInt32(28, true);
706
- const frameRight = view.getInt32(32, true);
707
- const frameBottom = view.getInt32(36, true);
708
- const frameW = frameRight - frameLeft;
709
- const frameH = frameBottom - frameTop;
710
- return {
711
- bounds: {
712
- left: boundsLeft,
713
- top: boundsTop,
714
- right: boundsRight,
715
- bottom: boundsBottom
716
- },
717
- frameW,
718
- frameH
719
- };
720
- }
721
- function getRenderableEmfBounds(header) {
722
- const boundsW = header.bounds.right - header.bounds.left;
723
- const boundsH = header.bounds.bottom - header.bounds.top;
724
- if (boundsW > 0 && boundsH > 0) {
725
- return header.bounds;
726
- }
727
- if (header.frameW > 0 && header.frameH > 0) {
728
- emfLog(
729
- `getRenderableEmfBounds: bounds invalid (${boundsW}\xD7${boundsH}), falling back to frame ${header.frameW}\xD7${header.frameH}`
730
- );
731
- return { left: 0, top: 0, right: header.frameW, bottom: header.frameH };
732
- }
733
- return null;
734
- }
735
- function parseWmfHeader(view) {
736
- if (view.byteLength < 22) {
737
- return null;
738
- }
739
- const magic = view.getUint32(0, true);
740
- let headerOffset = 0;
741
- let boundsLeft = 0;
742
- let boundsTop = 0;
743
- let boundsRight = 800;
744
- let boundsBottom = 600;
745
- let unitsPerInch = 96;
746
- if (magic === 2596720087) {
747
- boundsLeft = view.getInt16(6, true);
748
- boundsTop = view.getInt16(8, true);
749
- boundsRight = view.getInt16(10, true);
750
- boundsBottom = view.getInt16(12, true);
751
- unitsPerInch = view.getUint16(14, true) || 96;
752
- headerOffset = 22;
753
- }
754
- if (headerOffset + 18 > view.byteLength) {
755
- return null;
756
- }
757
- const fileType = view.getUint16(headerOffset, true);
758
- if (fileType !== 1 && fileType !== 2) {
759
- return null;
760
- }
761
- const headerSize = view.getUint16(headerOffset + 2, true) * 2;
762
- const maxRecordSize = view.getUint32(headerOffset + 8, true) * 2;
763
- return {
764
- headerSize: headerOffset + headerSize,
765
- maxRecordSize,
766
- boundsLeft,
767
- boundsTop,
768
- boundsRight,
769
- boundsBottom,
770
- unitsPerInch
771
- };
772
- }
773
-
774
- // src/emf-gdi-coord.ts
775
- function gmx(r, x) {
776
- const wt = r.state.worldTransform;
777
- const px = wt[0] * x + wt[4];
778
- if (r.useMappingMode) {
779
- return (px - r.windowOrg.x) / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1) + r.viewportOrg.x;
780
- }
781
- return (px - r.bounds.left) * r.sx;
782
- }
783
- function gmy(r, y) {
784
- const wt = r.state.worldTransform;
785
- const py = wt[3] * y + wt[5];
786
- if (r.useMappingMode) {
787
- return (py - r.windowOrg.y) / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1) + r.viewportOrg.y;
788
- }
789
- return (py - r.bounds.top) * r.sy;
790
- }
791
- function gmw(r, w) {
792
- const pw = r.state.worldTransform[0] * w;
793
- if (r.useMappingMode) {
794
- return pw / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1);
795
- }
796
- return pw * r.sx;
797
- }
798
- function gmh(r, h) {
799
- const ph = r.state.worldTransform[3] * h;
800
- if (r.useMappingMode) {
801
- return ph / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1);
802
- }
803
- return ph * r.sy;
804
- }
805
- function activateGdiMappingMode(r) {
806
- r.useMappingMode = true;
807
- }
808
-
809
- // src/emf-gdi-draw-shapes.ts
810
- function handleSetPixelV(rCtx, dataOff, recSize) {
811
- const { ctx, view } = rCtx;
812
- if (recSize >= 20) {
813
- const x = view.getInt32(dataOff, true);
814
- const y = view.getInt32(dataOff + 4, true);
815
- const color = readColorRef(view, dataOff + 8);
816
- ctx.fillStyle = color;
817
- ctx.fillRect(gmx(rCtx, x), gmy(rCtx, y), 1, 1);
818
- }
819
- return true;
820
- }
821
- function handleMoveToEx(rCtx, dataOff, recSize) {
822
- const { ctx, view, state, inPath } = rCtx;
823
- if (recSize >= 16) {
824
- state.curX = view.getInt32(dataOff, true);
825
- state.curY = view.getInt32(dataOff + 4, true);
826
- if (inPath) {
827
- ctx.moveTo(gmx(rCtx, state.curX), gmy(rCtx, state.curY));
828
- }
829
- }
830
- return true;
831
- }
832
- function handleLineTo(rCtx, dataOff, recSize) {
833
- const { ctx, view, state, inPath } = rCtx;
834
- if (recSize >= 16) {
835
- const lx = view.getInt32(dataOff, true);
836
- const ly = view.getInt32(dataOff + 4, true);
837
- if (inPath) {
838
- ctx.lineTo(gmx(rCtx, lx), gmy(rCtx, ly));
839
- } else {
840
- applyPen(ctx, state);
841
- ctx.beginPath();
842
- ctx.moveTo(gmx(rCtx, state.curX), gmy(rCtx, state.curY));
843
- ctx.lineTo(gmx(rCtx, lx), gmy(rCtx, ly));
844
- ctx.stroke();
845
- }
846
- state.curX = lx;
847
- state.curY = ly;
1064
+ return canvas.toDataURL("image/png");
848
1065
  }
849
- return true;
850
- }
851
- function handleRectangle(rCtx, dataOff, recSize) {
852
- const { ctx, view, state, inPath } = rCtx;
853
- if (recSize >= 24) {
854
- const l = view.getInt32(dataOff, true);
855
- const t = view.getInt32(dataOff + 4, true);
856
- const r = view.getInt32(dataOff + 8, true);
857
- const b = view.getInt32(dataOff + 12, true);
858
- if (inPath) {
859
- ctx.rect(gmx(rCtx, l), gmy(rCtx, t), gmw(rCtx, r - l), gmh(rCtx, b - t));
860
- } else {
861
- applyBrush(ctx, state);
862
- ctx.fillRect(gmx(rCtx, l), gmy(rCtx, t), gmw(rCtx, r - l), gmh(rCtx, b - t));
863
- applyPen(ctx, state);
864
- ctx.strokeRect(gmx(rCtx, l), gmy(rCtx, t), gmw(rCtx, r - l), gmh(rCtx, b - t));
865
- }
1066
+ if (nodeCanvasModule && canvas instanceof nodeCanvasModule.Canvas) {
1067
+ emfLog(
1068
+ `exportCanvasToPngDataUrl: using @napi-rs/canvas toDataURLAsync (${canvas.width}\xD7${canvas.height})`
1069
+ );
1070
+ return canvas.toDataURLAsync();
866
1071
  }
867
- return true;
1072
+ return null;
868
1073
  }
869
- function handleRoundRect(rCtx, dataOff, recSize) {
870
- const { ctx, view, state, inPath } = rCtx;
871
- if (recSize >= 32) {
872
- const l = view.getInt32(dataOff, true);
873
- const t = view.getInt32(dataOff + 4, true);
874
- const r = view.getInt32(dataOff + 8, true);
875
- const b = view.getInt32(dataOff + 12, true);
876
- const rw = Math.abs(gmw(rCtx, view.getInt32(dataOff + 16, true))) / 2;
877
- const rh = Math.abs(gmh(rCtx, view.getInt32(dataOff + 20, true))) / 2;
878
- const x1 = gmx(rCtx, l);
879
- const y1 = gmy(rCtx, t);
880
- const w = gmw(rCtx, r - l);
881
- const h = gmh(rCtx, b - t);
882
- const drawRoundRect = () => {
883
- const radius = Math.min(rw, rh, w / 2, h / 2);
884
- ctx.moveTo(x1 + radius, y1);
885
- ctx.lineTo(x1 + w - radius, y1);
886
- ctx.arcTo(x1 + w, y1, x1 + w, y1 + radius, radius);
887
- ctx.lineTo(x1 + w, y1 + h - radius);
888
- ctx.arcTo(x1 + w, y1 + h, x1 + w - radius, y1 + h, radius);
889
- ctx.lineTo(x1 + radius, y1 + h);
890
- ctx.arcTo(x1, y1 + h, x1, y1 + h - radius, radius);
891
- ctx.lineTo(x1, y1 + radius);
892
- ctx.arcTo(x1, y1, x1 + radius, y1, radius);
893
- ctx.closePath();
894
- };
895
- if (inPath) {
896
- drawRoundRect();
897
- } else {
898
- ctx.beginPath();
899
- drawRoundRect();
900
- applyBrush(ctx, state);
901
- ctx.fill();
902
- applyPen(ctx, state);
903
- ctx.stroke();
904
- }
1074
+ async function decodeDeferredImageBytes(bytes, mime) {
1075
+ if (nodeCanvasModule) {
1076
+ const image = await nodeCanvasModule.loadImage(new Uint8Array(bytes));
1077
+ return { drawable: image, width: image.width, height: image.height, close: () => {
1078
+ } };
905
1079
  }
906
- return true;
907
- }
908
- function handleEllipse(rCtx, dataOff, recSize) {
909
- const { ctx, view, state, inPath } = rCtx;
910
- if (recSize >= 24) {
911
- const l = view.getInt32(dataOff, true);
912
- const t = view.getInt32(dataOff + 4, true);
913
- const r = view.getInt32(dataOff + 8, true);
914
- const b = view.getInt32(dataOff + 12, true);
915
- const cx = gmx(rCtx, (l + r) / 2);
916
- const cy = gmy(rCtx, (t + b) / 2);
917
- const rx = Math.abs(gmw(rCtx, r - l)) / 2;
918
- const ry = Math.abs(gmh(rCtx, b - t)) / 2;
919
- if (inPath) {
920
- ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
921
- } else {
922
- ctx.beginPath();
923
- ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
924
- applyBrush(ctx, state);
925
- ctx.fill();
926
- applyPen(ctx, state);
927
- ctx.stroke();
928
- }
1080
+ if (typeof createImageBitmap !== "function") {
1081
+ return null;
929
1082
  }
930
- return true;
1083
+ const blob = mime !== void 0 ? new Blob([bytes], { type: mime }) : new Blob([bytes]);
1084
+ const bitmap = await createImageBitmap(blob);
1085
+ return { drawable: bitmap, width: bitmap.width, height: bitmap.height, close: () => bitmap.close() };
931
1086
  }
932
- function handleArcFamily(rCtx, recType, dataOff, recSize) {
933
- const { ctx, view, state, inPath } = rCtx;
934
- if (recSize >= 40) {
935
- const l = view.getInt32(dataOff, true);
936
- const t = view.getInt32(dataOff + 4, true);
937
- const r = view.getInt32(dataOff + 8, true);
938
- const b = view.getInt32(dataOff + 12, true);
939
- const startX = view.getInt32(dataOff + 16, true);
940
- const startY = view.getInt32(dataOff + 20, true);
941
- const endX = view.getInt32(dataOff + 24, true);
942
- const endY = view.getInt32(dataOff + 28, true);
943
- const cxA = (l + r) / 2;
944
- const cyA = (t + b) / 2;
945
- const rx = Math.abs(r - l) / 2;
946
- const ry = Math.abs(b - t) / 2;
947
- const startAngle = Math.atan2((startY - cyA) / (ry || 1), (startX - cxA) / (rx || 1));
948
- const endAngle = Math.atan2((endY - cyA) / (ry || 1), (endX - cxA) / (rx || 1));
949
- const mcx = gmx(rCtx, cxA);
950
- const mcy = gmy(rCtx, cyA);
951
- const mrx = Math.abs(gmw(rCtx, rx));
952
- const mry = Math.abs(gmh(rCtx, ry));
953
- const isArcTo = recType === EMR_ARCTO;
954
- const needsFill = recType === EMR_PIE || recType === EMR_CHORD;
955
- if (!inPath) {
956
- ctx.beginPath();
957
- }
958
- if (recType === EMR_PIE) {
959
- ctx.moveTo(mcx, mcy);
960
- }
961
- if (isArcTo) {
962
- ctx.lineTo(mcx + mrx * Math.cos(startAngle), mcy + mry * Math.sin(startAngle));
963
- }
964
- ctx.ellipse(mcx, mcy, mrx, mry, 0, startAngle, endAngle, false);
965
- if (recType === EMR_PIE || recType === EMR_CHORD) {
966
- ctx.closePath();
967
- }
968
- if (!inPath) {
969
- if (needsFill) {
970
- applyBrush(ctx, state);
971
- ctx.fill();
972
- }
973
- applyPen(ctx, state);
974
- ctx.stroke();
975
- }
976
- if (isArcTo) {
977
- state.curX = endX;
978
- state.curY = endY;
979
- }
980
- }
981
- return true;
1087
+ function canvasDrawImage(ctx, image, dx, dy, dw, dh) {
1088
+ const draw = ctx.drawImage;
1089
+ draw.call(ctx, image, dx, dy, dw, dh);
982
1090
  }
983
- function handleEmfGdiShapeRecord(rCtx, recType, dataOff, recSize) {
984
- switch (recType) {
985
- case EMR_SETPIXELV:
986
- return handleSetPixelV(rCtx, dataOff, recSize);
987
- case EMR_MOVETOEX:
988
- return handleMoveToEx(rCtx, dataOff, recSize);
989
- case EMR_LINETO:
990
- return handleLineTo(rCtx, dataOff, recSize);
991
- case EMR_RECTANGLE:
992
- return handleRectangle(rCtx, dataOff, recSize);
993
- case EMR_ROUNDRECT:
994
- return handleRoundRect(rCtx, dataOff, recSize);
995
- case EMR_ELLIPSE:
996
- return handleEllipse(rCtx, dataOff, recSize);
997
- case EMR_ARC:
998
- case EMR_ARCTO:
999
- case EMR_CHORD:
1000
- case EMR_PIE:
1001
- return handleArcFamily(rCtx, recType, dataOff, recSize);
1002
- default:
1003
- return false;
1091
+ function canvasGetImageData(ctx, x, y, w, h) {
1092
+ const get = ctx.getImageData;
1093
+ return get.call(ctx, x, y, w, h);
1094
+ }
1095
+ function canvasPutImageData(ctx, data, x, y) {
1096
+ const put = ctx.putImageData;
1097
+ put.call(ctx, data, x, y);
1098
+ }
1099
+ function canvasCreatePattern(ctx, image, repetition) {
1100
+ const create = ctx.createPattern;
1101
+ return create.call(ctx, image, repetition);
1102
+ }
1103
+ function createImageDataCompat(data, width, height) {
1104
+ if (typeof ImageData !== "undefined") {
1105
+ return new ImageData(data, width, height);
1106
+ }
1107
+ if (nodeCanvasModule) {
1108
+ const Ctor = nodeCanvasModule.ImageData;
1109
+ return new Ctor(data, width, height);
1004
1110
  }
1111
+ throw new ReferenceError("ImageData is not defined");
1005
1112
  }
1006
1113
 
1007
- // src/emf-dib-rle-decoder.ts
1008
- function decodeRleBitmap(view, bitsOffset, bitsSize, width, height, _topDown, isRle4, colorTable, out, setPixel) {
1009
- let x = 0;
1010
- let y = height - 1;
1011
- let off = bitsOffset;
1012
- const endOff = bitsOffset + bitsSize;
1013
- while (off + 1 < endOff && y >= 0) {
1014
- const first = view.getUint8(off);
1015
- const second = view.getUint8(off + 1);
1016
- off += 2;
1017
- if (first === 0) {
1018
- if (second === 0) {
1019
- x = 0;
1020
- y--;
1021
- } else if (second === 1) {
1022
- break;
1023
- } else if (second === 2) {
1024
- if (off + 1 >= endOff) {
1025
- break;
1026
- }
1027
- x += view.getUint8(off);
1028
- y -= view.getUint8(off + 1);
1029
- off += 2;
1030
- } else {
1031
- const count = second;
1032
- if (!isRle4) {
1033
- for (let i = 0; i < count && off < endOff && x < width; i++) {
1034
- const idx = view.getUint8(off++);
1035
- if (idx < colorTable.length) {
1036
- setPixel(
1037
- x,
1038
- height - 1 - y,
1039
- colorTable[idx][0],
1040
- colorTable[idx][1],
1041
- colorTable[idx][2],
1042
- 255
1043
- );
1044
- }
1045
- x++;
1046
- }
1047
- if (count & 1) {
1048
- off++;
1049
- }
1050
- } else {
1051
- const bytes = Math.ceil(count / 2);
1052
- let pi = 0;
1053
- for (let i = 0; i < bytes && off < endOff; i++) {
1054
- const byte = view.getUint8(off++);
1055
- for (let nibble = 0; nibble < 2 && pi < count; nibble++) {
1056
- const idx = nibble === 0 ? byte >> 4 & 15 : byte & 15;
1057
- if (idx < colorTable.length && x < width) {
1058
- setPixel(
1059
- x,
1060
- height - 1 - y,
1061
- colorTable[idx][0],
1062
- colorTable[idx][1],
1063
- colorTable[idx][2],
1064
- 255
1065
- );
1066
- }
1067
- x++;
1068
- pi++;
1069
- }
1070
- }
1071
- if (bytes & 1) {
1072
- off++;
1073
- }
1074
- }
1075
- }
1076
- } else if (!isRle4) {
1077
- const idx = second;
1078
- const c = idx < colorTable.length ? colorTable[idx] : [0, 0, 0];
1079
- for (let i = 0; i < first && x < width; i++) {
1080
- setPixel(x, height - 1 - y, c[0], c[1], c[2], 255);
1081
- x++;
1082
- }
1083
- } else {
1084
- const hi = second >> 4 & 15;
1085
- const lo = second & 15;
1086
- for (let i = 0; i < first && x < width; i++) {
1087
- const idx = (i & 1) === 0 ? hi : lo;
1088
- if (idx < colorTable.length) {
1089
- setPixel(
1090
- x,
1091
- height - 1 - y,
1092
- colorTable[idx][0],
1093
- colorTable[idx][1],
1094
- colorTable[idx][2],
1095
- 255
1096
- );
1097
- }
1098
- x++;
1099
- }
1100
- }
1114
+ // src/emf-header-parser.ts
1115
+ function parseEmfHeader(view) {
1116
+ emfLog("parseEmfHeader: byteLength =", view.byteLength);
1117
+ if (view.byteLength < 88) {
1118
+ return null;
1119
+ }
1120
+ const recordType = view.getUint32(0, true);
1121
+ if (recordType !== EMR_HEADER) {
1122
+ return null;
1123
+ }
1124
+ const boundsLeft = view.getInt32(8, true);
1125
+ const boundsTop = view.getInt32(12, true);
1126
+ const boundsRight = view.getInt32(16, true);
1127
+ const boundsBottom = view.getInt32(20, true);
1128
+ const frameLeft = view.getInt32(24, true);
1129
+ const frameTop = view.getInt32(28, true);
1130
+ const frameRight = view.getInt32(32, true);
1131
+ const frameBottom = view.getInt32(36, true);
1132
+ const frameW = frameRight - frameLeft;
1133
+ const frameH = frameBottom - frameTop;
1134
+ return {
1135
+ bounds: {
1136
+ left: boundsLeft,
1137
+ top: boundsTop,
1138
+ right: boundsRight,
1139
+ bottom: boundsBottom
1140
+ },
1141
+ frameW,
1142
+ frameH
1143
+ };
1144
+ }
1145
+ function getRenderableEmfBounds(header) {
1146
+ const boundsW = header.bounds.right - header.bounds.left;
1147
+ const boundsH = header.bounds.bottom - header.bounds.top;
1148
+ if (boundsW > 0 && boundsH > 0) {
1149
+ return header.bounds;
1101
1150
  }
1102
- return createImageDataCompat(
1103
- new Uint8ClampedArray(out.buffer, out.byteOffset, out.byteLength),
1104
- width,
1105
- height
1106
- );
1151
+ if (header.frameW > 0 && header.frameH > 0) {
1152
+ emfLog(
1153
+ `getRenderableEmfBounds: bounds invalid (${boundsW}\xD7${boundsH}), falling back to frame ${header.frameW}\xD7${header.frameH}`
1154
+ );
1155
+ return { left: 0, top: 0, right: header.frameW, bottom: header.frameH };
1156
+ }
1157
+ return null;
1158
+ }
1159
+ function parseWmfHeader(view) {
1160
+ if (view.byteLength < 22) {
1161
+ return null;
1162
+ }
1163
+ const magic = view.getUint32(0, true);
1164
+ let headerOffset = 0;
1165
+ let boundsLeft = 0;
1166
+ let boundsTop = 0;
1167
+ let boundsRight = 800;
1168
+ let boundsBottom = 600;
1169
+ let unitsPerInch = 96;
1170
+ if (magic === 2596720087) {
1171
+ boundsLeft = view.getInt16(6, true);
1172
+ boundsTop = view.getInt16(8, true);
1173
+ boundsRight = view.getInt16(10, true);
1174
+ boundsBottom = view.getInt16(12, true);
1175
+ unitsPerInch = view.getUint16(14, true) || 96;
1176
+ headerOffset = 22;
1177
+ }
1178
+ if (headerOffset + 18 > view.byteLength) {
1179
+ return null;
1180
+ }
1181
+ const fileType = view.getUint16(headerOffset, true);
1182
+ if (fileType !== 1 && fileType !== 2) {
1183
+ return null;
1184
+ }
1185
+ const headerSize = view.getUint16(headerOffset + 2, true) * 2;
1186
+ const maxRecordSize = view.getUint32(headerOffset + 8, true) * 2;
1187
+ return {
1188
+ headerSize: headerOffset + headerSize,
1189
+ maxRecordSize,
1190
+ boundsLeft,
1191
+ boundsTop,
1192
+ boundsRight,
1193
+ boundsBottom,
1194
+ unitsPerInch
1195
+ };
1107
1196
  }
1108
1197
 
1109
- // src/emf-dib-uncompressed.ts
1110
- function countTrailingZeros(v) {
1111
- if (v === 0) {
1112
- return 0;
1198
+ // src/emf-gdi-coord.ts
1199
+ function gmx(r, x) {
1200
+ const wt = r.state.worldTransform;
1201
+ const px = wt[0] * x + wt[4];
1202
+ if (r.useMappingMode) {
1203
+ return (px - r.windowOrg.x) / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1) + r.viewportOrg.x;
1113
1204
  }
1114
- let c = 0;
1115
- let val = v;
1116
- while ((val & 1) === 0) {
1117
- val >>>= 1;
1118
- c++;
1205
+ return (px - r.bounds.left) * r.sx;
1206
+ }
1207
+ function gmy(r, y) {
1208
+ const wt = r.state.worldTransform;
1209
+ const py = wt[3] * y + wt[5];
1210
+ if (r.useMappingMode) {
1211
+ return (py - r.windowOrg.y) / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1) + r.viewportOrg.y;
1119
1212
  }
1120
- return c;
1213
+ return (py - r.bounds.top) * r.sy;
1121
1214
  }
1122
- var BI_BITFIELDS = 3;
1123
- function parseBitfieldMasks(view, bmiOffset, headerSize, compression, bitCount) {
1124
- let rMask = 0, gMask = 0, bMask = 0;
1125
- let rShift = 0, gShift = 0, bShift = 0;
1126
- let rMax = 1, gMax = 1, bMax = 1;
1127
- if (compression === BI_BITFIELDS) {
1128
- const bfOff = bmiOffset + headerSize;
1129
- if (bfOff + 12 > view.byteLength) {
1130
- return null;
1131
- }
1132
- rMask = view.getUint32(bfOff, true);
1133
- gMask = view.getUint32(bfOff + 4, true);
1134
- bMask = view.getUint32(bfOff + 8, true);
1135
- rShift = countTrailingZeros(rMask);
1136
- gShift = countTrailingZeros(gMask);
1137
- bShift = countTrailingZeros(bMask);
1138
- rMax = rMask >>> rShift || 1;
1139
- gMax = gMask >>> gShift || 1;
1140
- bMax = bMask >>> bShift || 1;
1141
- } else if (bitCount === 16) {
1142
- rMask = 31744;
1143
- gMask = 992;
1144
- bMask = 31;
1145
- rShift = 10;
1146
- gShift = 5;
1147
- bShift = 0;
1148
- rMax = 31;
1149
- gMax = 31;
1150
- bMax = 31;
1215
+ function gmw(r, w) {
1216
+ const pw = r.state.worldTransform[0] * w;
1217
+ if (r.useMappingMode) {
1218
+ return pw / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1);
1151
1219
  }
1152
- return { rMask, gMask, bMask, rShift, gShift, bShift, rMax, gMax, bMax };
1220
+ return pw * r.sx;
1153
1221
  }
1154
- function decodeUncompressedRows(view, bitsOffset, width, height, topDown, bitCount, colorTable, masks, out) {
1155
- const rowStride = Math.floor((bitCount * width + 31) / 32) * 4;
1156
- const { rMask, gMask, bMask, rShift, gShift, bShift, rMax, gMax, bMax } = masks;
1157
- for (let y = 0; y < height; y++) {
1158
- const srcY = topDown ? y : height - 1 - y;
1159
- const rowStart = bitsOffset + srcY * rowStride;
1160
- if (rowStart + rowStride > view.byteLength) {
1161
- continue;
1222
+ function gmh(r, h) {
1223
+ const ph = r.state.worldTransform[3] * h;
1224
+ if (r.useMappingMode) {
1225
+ return ph / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1);
1226
+ }
1227
+ return ph * r.sy;
1228
+ }
1229
+ function activateGdiMappingMode(r) {
1230
+ r.useMappingMode = true;
1231
+ }
1232
+ function hasWorldRotation(r) {
1233
+ const wt = r.state.worldTransform;
1234
+ return wt[1] !== 0 || wt[2] !== 0;
1235
+ }
1236
+ function gdiDeviceMatrix(r) {
1237
+ const wt = r.state.worldTransform;
1238
+ if (r.useMappingMode) {
1239
+ const kx = (r.viewportExt.cx || 1) / (r.windowExt.cx || 1);
1240
+ const ky = (r.viewportExt.cy || 1) / (r.windowExt.cy || 1);
1241
+ return [
1242
+ wt[0] * kx,
1243
+ wt[1] * ky,
1244
+ wt[2] * kx,
1245
+ wt[3] * ky,
1246
+ (wt[4] - r.windowOrg.x) * kx + r.viewportOrg.x,
1247
+ (wt[5] - r.windowOrg.y) * ky + r.viewportOrg.y
1248
+ ];
1249
+ }
1250
+ const sx = r.sx || 1;
1251
+ const sy = r.sy || 1;
1252
+ return [wt[0] * sx, wt[1] * sy, wt[2] * sx, wt[3] * sy, (wt[4] - r.bounds.left) * sx, (wt[5] - r.bounds.top) * sy];
1253
+ }
1254
+ function gmapPoint(r, x, y) {
1255
+ const m = gdiDeviceMatrix(r);
1256
+ return { x: m[0] * x + m[2] * y + m[4], y: m[1] * x + m[3] * y + m[5] };
1257
+ }
1258
+ function gdiEllipseParams(r, cx0, cy0, rx0, ry0) {
1259
+ const m = gdiDeviceMatrix(r);
1260
+ const center = gmapPoint(r, cx0, cy0);
1261
+ const a11 = m[0] * rx0;
1262
+ const a12 = m[2] * ry0;
1263
+ const a21 = m[1] * rx0;
1264
+ const a22 = m[3] * ry0;
1265
+ const p = a11 * a11 + a12 * a12;
1266
+ const q = a11 * a21 + a12 * a22;
1267
+ const s = a21 * a21 + a22 * a22;
1268
+ const mid = (p + s) / 2;
1269
+ const spread = Math.sqrt(Math.max(0, ((p - s) / 2) ** 2 + q * q));
1270
+ const lambda1 = Math.max(0, mid + spread);
1271
+ const lambda2 = Math.max(0, mid - spread);
1272
+ const rotation = 0.5 * Math.atan2(2 * q, p - s);
1273
+ return {
1274
+ cx: center.x,
1275
+ cy: center.y,
1276
+ rx: Math.sqrt(lambda1),
1277
+ ry: Math.sqrt(lambda2),
1278
+ rotation
1279
+ };
1280
+ }
1281
+
1282
+ // src/emf-rop3.ts
1283
+ function rop3Index(rop) {
1284
+ return rop >>> 16 & 255;
1285
+ }
1286
+ function rop3Operands(index) {
1287
+ const t = index & 255;
1288
+ return {
1289
+ usesP: (t >> 4 & 15) !== (t & 15),
1290
+ usesS: (t >> 2 & 51) !== (t & 51),
1291
+ usesD: (t >> 1 & 85) !== (t & 85)
1292
+ };
1293
+ }
1294
+ function evalRop3(index, p, s, d, mask = 16777215) {
1295
+ let out = 0;
1296
+ const np = ~p;
1297
+ const ns = ~s;
1298
+ const nd = ~d;
1299
+ for (let i = 0; i < 8; i++) {
1300
+ if (index & 1 << i) {
1301
+ out |= (i & 4 ? p : np) & (i & 2 ? s : ns) & (i & 1 ? d : nd);
1162
1302
  }
1163
- for (let x = 0; x < width; x++) {
1164
- const dstPx = (y * width + x) * 4;
1165
- if (bitCount === 1) {
1166
- const byteIdx = rowStart + (x >> 3);
1167
- const bit = view.getUint8(byteIdx) >> 7 - (x & 7) & 1;
1168
- if (bit < colorTable.length) {
1169
- out[dstPx] = colorTable[bit][0];
1170
- out[dstPx + 1] = colorTable[bit][1];
1171
- out[dstPx + 2] = colorTable[bit][2];
1172
- }
1173
- out[dstPx + 3] = 255;
1174
- } else if (bitCount === 4) {
1175
- const byteIdx = rowStart + (x >> 1);
1176
- const nibble = (x & 1) === 0 ? view.getUint8(byteIdx) >> 4 & 15 : view.getUint8(byteIdx) & 15;
1177
- if (nibble < colorTable.length) {
1178
- out[dstPx] = colorTable[nibble][0];
1179
- out[dstPx + 1] = colorTable[nibble][1];
1180
- out[dstPx + 2] = colorTable[nibble][2];
1181
- }
1182
- out[dstPx + 3] = 255;
1183
- } else if (bitCount === 8) {
1184
- const idx = view.getUint8(rowStart + x);
1185
- if (idx < colorTable.length) {
1186
- out[dstPx] = colorTable[idx][0];
1187
- out[dstPx + 1] = colorTable[idx][1];
1188
- out[dstPx + 2] = colorTable[idx][2];
1189
- }
1190
- out[dstPx + 3] = 255;
1191
- } else if (bitCount === 16) {
1192
- const val = view.getUint16(rowStart + x * 2, true);
1193
- out[dstPx] = Math.round(((val & rMask) >>> rShift) * 255 / rMax);
1194
- out[dstPx + 1] = Math.round(((val & gMask) >>> gShift) * 255 / gMax);
1195
- out[dstPx + 2] = Math.round(((val & bMask) >>> bShift) * 255 / bMax);
1196
- out[dstPx + 3] = 255;
1197
- } else if (bitCount === 24) {
1198
- const srcPx = rowStart + x * 3;
1199
- out[dstPx] = view.getUint8(srcPx + 2);
1200
- out[dstPx + 1] = view.getUint8(srcPx + 1);
1201
- out[dstPx + 2] = view.getUint8(srcPx);
1202
- out[dstPx + 3] = 255;
1203
- } else {
1204
- const srcPx = rowStart + x * 4;
1205
- const bb = view.getUint8(srcPx);
1206
- const gg = view.getUint8(srcPx + 1);
1207
- const rr = view.getUint8(srcPx + 2);
1208
- const aa = view.getUint8(srcPx + 3);
1209
- out[dstPx] = rr;
1210
- out[dstPx + 1] = gg;
1211
- out[dstPx + 2] = bb;
1212
- out[dstPx + 3] = aa === 0 ? 255 : aa;
1213
- }
1303
+ }
1304
+ return out & mask;
1305
+ }
1306
+ function classifyRop3(rop) {
1307
+ const index = rop3Index(rop);
1308
+ switch (index) {
1309
+ case 204:
1310
+ return { kind: "copy" };
1311
+ case 0:
1312
+ return { kind: "solid", color: "black" };
1313
+ case 255:
1314
+ return { kind: "solid", color: "white" };
1315
+ case 170:
1316
+ return { kind: "noop" };
1317
+ case 85:
1318
+ return { kind: "invert-dest" };
1319
+ default:
1320
+ return { kind: "ternary", index, operands: rop3Operands(index) };
1321
+ }
1322
+ }
1323
+ function applyRop3(dst, src, pattern, index, originX, originY) {
1324
+ const d = dst.data;
1325
+ const s = src ? src.data : null;
1326
+ const w = dst.width;
1327
+ const h = dst.height;
1328
+ const solidP = typeof pattern === "number" ? pattern : 0;
1329
+ const sampleP = typeof pattern === "function" ? pattern : null;
1330
+ for (let y = 0; y < h; y++) {
1331
+ for (let x = 0; x < w; x++) {
1332
+ const i = (y * w + x) * 4;
1333
+ const dv = d[i] << 16 | d[i + 1] << 8 | d[i + 2];
1334
+ const sv = s ? s[i] << 16 | s[i + 1] << 8 | s[i + 2] : 0;
1335
+ const pv = sampleP ? sampleP(originX + x, originY + y) : solidP;
1336
+ const r = evalRop3(index, pv, sv, dv);
1337
+ d[i] = r >> 16 & 255;
1338
+ d[i + 1] = r >> 8 & 255;
1339
+ d[i + 2] = r & 255;
1340
+ d[i + 3] = 255;
1214
1341
  }
1215
1342
  }
1216
1343
  }
1217
-
1218
- // src/emf-dib-decoder.ts
1219
- function decodeDibToImageData(view, bmiOffset, bitsOffset, bitsSize) {
1220
- if (bmiOffset < 0 || bitsOffset < 0 || bmiOffset + 40 > view.byteLength || bitsOffset + bitsSize > view.byteLength) {
1221
- return null;
1222
- }
1223
- const headerSize = view.getUint32(bmiOffset, true);
1224
- if (headerSize < 40 || bmiOffset + headerSize > view.byteLength) {
1344
+ function clampPositiveRect(x, y, w, h, canvasW, canvasH) {
1345
+ let left = w < 0 ? x + w : x;
1346
+ let top = h < 0 ? y + h : y;
1347
+ let width = Math.abs(w);
1348
+ let height = Math.abs(h);
1349
+ left = Math.round(left);
1350
+ top = Math.round(top);
1351
+ width = Math.round(width);
1352
+ height = Math.round(height);
1353
+ const right = Math.min(left + width, Math.round(canvasW));
1354
+ const bottom = Math.min(top + height, Math.round(canvasH));
1355
+ left = Math.max(left, 0);
1356
+ top = Math.max(top, 0);
1357
+ width = right - left;
1358
+ height = bottom - top;
1359
+ if (width <= 0 || height <= 0) {
1225
1360
  return null;
1226
1361
  }
1227
- const width = view.getInt32(bmiOffset + 4, true);
1228
- const heightRaw = view.getInt32(bmiOffset + 8, true);
1229
- const planes = view.getUint16(bmiOffset + 12, true);
1230
- const bitCount = view.getUint16(bmiOffset + 14, true);
1231
- const compression = view.getUint32(bmiOffset + 16, true);
1232
- if (planes !== 1 || width <= 0 || heightRaw === 0) {
1233
- return null;
1362
+ return { x: left, y: top, w: width, h: height };
1363
+ }
1364
+
1365
+ // src/emf-rop2-exact.ts
1366
+ var ROP2_EXACT_INDEX = {
1367
+ [R2_MASKPEN]: 160,
1368
+ // P & D
1369
+ [R2_MERGEPEN]: 250,
1370
+ // P | D
1371
+ [R2_XORPEN]: 90,
1372
+ // P ^ D
1373
+ [R2_NOTXORPEN]: 165,
1374
+ // ~(P ^ D)
1375
+ [R2_MASKPENNOT]: 80,
1376
+ // P & ~D
1377
+ [R2_MERGEPENNOT]: 245,
1378
+ // P | ~D
1379
+ [R2_MASKNOTPEN]: 10,
1380
+ // ~P & D
1381
+ [R2_MERGENOTPEN]: 175,
1382
+ // ~P | D
1383
+ [R2_NOTMASKPEN]: 95,
1384
+ // ~(P & D)
1385
+ [R2_NOTMERGEPEN]: 5
1386
+ // ~(P | D)
1387
+ };
1388
+ function isExactRop2Bitwise(rop2) {
1389
+ return rop2 in ROP2_EXACT_INDEX;
1390
+ }
1391
+ function packRgb(r, g, b) {
1392
+ return r << 16 | g << 8 | b;
1393
+ }
1394
+ function surfaceSize(ctx) {
1395
+ const canvas = ctx.canvas;
1396
+ const w = canvas?.width;
1397
+ const h = canvas?.height;
1398
+ return typeof w === "number" && typeof h === "number" && w > 0 && h > 0 ? { w, h } : null;
1399
+ }
1400
+ function paintWithExactRop2(ctx, rop2, color, draw) {
1401
+ const index = ROP2_EXACT_INDEX[rop2];
1402
+ if (index === void 0) {
1403
+ return false;
1234
1404
  }
1235
- if (width > 8192 || Math.abs(heightRaw) > 8192) {
1236
- return null;
1405
+ const size = surfaceSize(ctx);
1406
+ if (!size) {
1407
+ return false;
1237
1408
  }
1238
- const BI_RGB = 0;
1239
- const BI_RLE8 = 1;
1240
- const BI_RLE4 = 2;
1241
- const BI_BITFIELDS2 = 3;
1242
- if (bitCount !== 1 && bitCount !== 4 && bitCount !== 8 && bitCount !== 16 && bitCount !== 24 && bitCount !== 32) {
1243
- return null;
1409
+ try {
1410
+ const temp = createTempCanvas(size.w, size.h);
1411
+ if (!temp) {
1412
+ return false;
1413
+ }
1414
+ temp.ctx.fillStyle = color;
1415
+ temp.ctx.strokeStyle = color;
1416
+ draw(temp.ctx);
1417
+ const paint = canvasGetImageData(temp.ctx, 0, 0, size.w, size.h);
1418
+ const dest = canvasGetImageData(ctx, 0, 0, size.w, size.h);
1419
+ const pd = paint.data;
1420
+ const dd = dest.data;
1421
+ for (let i = 0; i < dd.length; i += 4) {
1422
+ const coverage = pd[i + 3] / 255;
1423
+ if (coverage <= 0) {
1424
+ continue;
1425
+ }
1426
+ const p = packRgb(pd[i], pd[i + 1], pd[i + 2]);
1427
+ const d = packRgb(dd[i], dd[i + 1], dd[i + 2]);
1428
+ const combined = evalRop3(index, p, d, d);
1429
+ const cr = combined >> 16 & 255;
1430
+ const cg = combined >> 8 & 255;
1431
+ const cb = combined & 255;
1432
+ dd[i] = Math.round(dd[i] + (cr - dd[i]) * coverage);
1433
+ dd[i + 1] = Math.round(dd[i + 1] + (cg - dd[i + 1]) * coverage);
1434
+ dd[i + 2] = Math.round(dd[i + 2] + (cb - dd[i + 2]) * coverage);
1435
+ dd[i + 3] = 255;
1436
+ }
1437
+ canvasPutImageData(ctx, dest, 0, 0);
1438
+ return true;
1439
+ } catch {
1440
+ return false;
1244
1441
  }
1245
- if (compression === BI_RLE8 && bitCount !== 8) {
1246
- return null;
1442
+ }
1443
+
1444
+ // src/emf-gdi-shape-paint.ts
1445
+ function surfaceSize2(ctx) {
1446
+ const canvas = ctx.canvas;
1447
+ const w = canvas?.width;
1448
+ const h = canvas?.height;
1449
+ return typeof w === "number" && typeof h === "number" && w > 0 && h > 0 ? { w, h } : null;
1450
+ }
1451
+ function fillCurrentPathWithGdiPattern(rCtx, fillRule) {
1452
+ const { ctx, bounds, state } = rCtx;
1453
+ const realized = realizeBrush(state);
1454
+ if (realized.kind !== "tile") {
1455
+ return false;
1247
1456
  }
1248
- if (compression === BI_RLE4 && bitCount !== 4) {
1249
- return null;
1457
+ const size = surfaceSize2(ctx);
1458
+ if (!size || typeof ctx.isPointInPath !== "function") {
1459
+ return false;
1250
1460
  }
1251
- if (compression === BI_BITFIELDS2 && bitCount !== 16 && bitCount !== 32) {
1252
- return null;
1461
+ const sx = rCtx.sx || 1;
1462
+ const sy = rCtx.sy || 1;
1463
+ try {
1464
+ const img = canvasGetImageData(ctx, 0, 0, size.w, size.h);
1465
+ const d = img.data;
1466
+ for (let y = 0; y < size.h; y++) {
1467
+ for (let x = 0; x < size.w; x++) {
1468
+ if (!ctx.isPointInPath(x + 0.5, y + 0.5, fillRule)) {
1469
+ continue;
1470
+ }
1471
+ const lx = Math.floor(bounds.left + (x + 0.5) / sx);
1472
+ const ly = Math.floor(bounds.top + (y + 0.5) / sy);
1473
+ const c = sampleTile(realized, lx, ly, state.brushOrgX, state.brushOrgY);
1474
+ const o = (y * size.w + x) * 4;
1475
+ d[o] = c >>> 16 & 255;
1476
+ d[o + 1] = c >>> 8 & 255;
1477
+ d[o + 2] = c & 255;
1478
+ d[o + 3] = 255;
1479
+ }
1480
+ }
1481
+ canvasPutImageData(ctx, img, 0, 0);
1482
+ return true;
1483
+ } catch {
1484
+ return false;
1253
1485
  }
1254
- if (compression !== BI_RGB && compression !== BI_RLE8 && compression !== BI_RLE4 && compression !== BI_BITFIELDS2) {
1255
- return null;
1486
+ }
1487
+ function fillShapeExactOrFast(rCtx, buildPath, fillRule = "nonzero") {
1488
+ const { ctx, state } = rCtx;
1489
+ if (state.brushStyle === 1) {
1490
+ return;
1256
1491
  }
1257
- const height = Math.abs(heightRaw);
1258
- const topDown = heightRaw < 0;
1259
- const colorTable = [];
1260
- if (bitCount <= 8) {
1261
- const maxColors = 1 << bitCount;
1262
- const colorsUsed = view.getUint32(bmiOffset + 32, true) || maxColors;
1263
- const numColors = Math.min(colorsUsed, maxColors);
1264
- const ctOffset = bmiOffset + headerSize;
1265
- if (ctOffset + numColors * 4 > view.byteLength) {
1266
- return null;
1492
+ if (realizeBrush(state).kind === "tile") {
1493
+ if (fillCurrentPathWithGdiPattern(rCtx, fillRule)) {
1494
+ return;
1267
1495
  }
1268
- for (let i = 0; i < numColors; i++) {
1269
- const b = view.getUint8(ctOffset + i * 4);
1270
- const g = view.getUint8(ctOffset + i * 4 + 1);
1271
- const r = view.getUint8(ctOffset + i * 4 + 2);
1272
- colorTable.push([r, g, b]);
1496
+ } else {
1497
+ const paint = rop2Paint(state.rop2);
1498
+ if (!paint.exact && isExactRop2Bitwise(state.rop2)) {
1499
+ const handled = paintWithExactRop2(ctx, state.rop2, state.brushColor, (scratch) => {
1500
+ buildPath(scratch);
1501
+ scratch.fill(fillRule);
1502
+ });
1503
+ if (handled) {
1504
+ return;
1505
+ }
1273
1506
  }
1274
1507
  }
1275
- const masks = parseBitfieldMasks(view, bmiOffset, headerSize, compression, bitCount);
1276
- if (!masks) {
1277
- return null;
1508
+ applyBrush(ctx, state);
1509
+ ctx.fill(fillRule);
1510
+ }
1511
+ function applyPenGeometry(ctx, state) {
1512
+ ctx.lineWidth = Math.max(state.penWidth, 1);
1513
+ switch (state.penStyle) {
1514
+ case 1:
1515
+ ctx.setLineDash([8, 4]);
1516
+ break;
1517
+ case 2:
1518
+ ctx.setLineDash([2, 2]);
1519
+ break;
1520
+ case 3:
1521
+ ctx.setLineDash([8, 4, 2, 4]);
1522
+ break;
1523
+ case 4:
1524
+ ctx.setLineDash([8, 4, 2, 4, 2, 4]);
1525
+ break;
1526
+ default:
1527
+ ctx.setLineDash([]);
1528
+ break;
1278
1529
  }
1279
- const out = new Uint8ClampedArray(width * height * 4);
1280
- if (compression === BI_RLE8 || compression === BI_RLE4) {
1281
- const setPixel = (x, y, r, g, b, a) => {
1282
- const dstPx = (y * width + x) * 4;
1283
- out[dstPx] = r;
1284
- out[dstPx + 1] = g;
1285
- out[dstPx + 2] = b;
1286
- out[dstPx + 3] = a;
1287
- };
1288
- return decodeRleBitmap(
1289
- view,
1290
- bitsOffset,
1291
- bitsSize,
1292
- width,
1293
- height,
1294
- topDown,
1295
- compression === BI_RLE4,
1296
- colorTable,
1297
- out,
1298
- setPixel
1299
- );
1530
+ }
1531
+ function strokeShapeExactOrFast(rCtx, buildPath) {
1532
+ const { ctx, state } = rCtx;
1533
+ const paint = rop2Paint(state.rop2);
1534
+ if (!paint.exact && isExactRop2Bitwise(state.rop2) && state.penStyle !== 5) {
1535
+ const handled = paintWithExactRop2(ctx, state.rop2, state.penColor, (scratch) => {
1536
+ applyPenGeometry(scratch, state);
1537
+ buildPath(scratch);
1538
+ scratch.stroke();
1539
+ });
1540
+ if (handled) {
1541
+ return;
1542
+ }
1300
1543
  }
1301
- decodeUncompressedRows(
1302
- view,
1303
- bitsOffset,
1304
- width,
1305
- height,
1306
- topDown,
1307
- bitCount,
1308
- colorTable,
1309
- masks,
1310
- out
1311
- );
1312
- return createImageDataCompat(out, width, height);
1544
+ applyPen(ctx, state);
1545
+ ctx.stroke();
1313
1546
  }
1314
1547
 
1315
- // src/emf-gdi-brush-pattern.ts
1316
- var HATCH_ROWS = [
1317
- [0, 0, 0, 255, 0, 0, 0, 0],
1318
- // HS_HORIZONTAL
1319
- [8, 8, 8, 8, 8, 8, 8, 8],
1320
- // HS_VERTICAL
1321
- [128, 64, 32, 16, 8, 4, 2, 1],
1322
- // HS_FDIAGONAL
1323
- [1, 2, 4, 8, 16, 32, 64, 128],
1324
- // HS_BDIAGONAL
1325
- [8, 8, 8, 255, 8, 8, 8, 8],
1326
- // HS_CROSS
1327
- [129, 66, 36, 24, 24, 36, 66, 129]
1328
- // HS_DIAGCROSS
1329
- ];
1330
- function hatchBit(hatch, x, y) {
1331
- const rows = HATCH_ROWS[hatch] ?? HATCH_ROWS[0];
1332
- return (rows[y & 7] >> 7 - (x & 7) & 1) === 1;
1548
+ // src/emf-gdi-draw-shapes.ts
1549
+ function needsPathBasedRectangle(rCtx) {
1550
+ const paint = rop2Paint(rCtx.state.rop2);
1551
+ const ropExact = !paint.exact && isExactRop2Bitwise(rCtx.state.rop2);
1552
+ return ropExact || realizeBrush(rCtx.state).kind === "tile";
1333
1553
  }
1334
- function cssHexToRgb(color) {
1335
- const m = /^#([0-9a-f]{6}|[0-9a-f]{3})$/i.exec(color.trim());
1336
- if (!m) {
1337
- return 0;
1554
+ function handleSetPixelV(rCtx, dataOff, recSize) {
1555
+ const { ctx, view } = rCtx;
1556
+ if (recSize >= 20) {
1557
+ const x = view.getInt32(dataOff, true);
1558
+ const y = view.getInt32(dataOff + 4, true);
1559
+ const color = readColorRef(view, dataOff + 8);
1560
+ const p = hasWorldRotation(rCtx) ? gmapPoint(rCtx, x, y) : { x: gmx(rCtx, x), y: gmy(rCtx, y) };
1561
+ ctx.fillStyle = color;
1562
+ ctx.fillRect(p.x, p.y, 1, 1);
1338
1563
  }
1339
- const hex = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];
1340
- return parseInt(hex, 16);
1564
+ return true;
1341
1565
  }
1342
- function decodeMonoBits(view, bmiOffset, bitsOffset) {
1343
- if (bmiOffset + 16 > view.byteLength) {
1344
- return null;
1345
- }
1346
- const width = view.getInt32(bmiOffset + 4, true);
1347
- const heightRaw = view.getInt32(bmiOffset + 8, true);
1348
- const bitCount = view.getUint16(bmiOffset + 14, true);
1349
- const height = Math.abs(heightRaw);
1350
- if (bitCount !== 1 || width <= 0 || height === 0 || width > 256 || height > 256) {
1351
- return null;
1566
+ function handleMoveToEx(rCtx, dataOff, recSize) {
1567
+ const { ctx, view, state, inPath } = rCtx;
1568
+ if (recSize >= 16) {
1569
+ state.curX = view.getInt32(dataOff, true);
1570
+ state.curY = view.getInt32(dataOff + 4, true);
1571
+ if (inPath) {
1572
+ const p = hasWorldRotation(rCtx) ? gmapPoint(rCtx, state.curX, state.curY) : { x: gmx(rCtx, state.curX), y: gmy(rCtx, state.curY) };
1573
+ ctx.moveTo(p.x, p.y);
1574
+ }
1352
1575
  }
1353
- const stride = (width + 31 >> 5) * 4;
1354
- if (bitsOffset + stride * height > view.byteLength) {
1355
- return null;
1576
+ return true;
1577
+ }
1578
+ function handleLineTo(rCtx, dataOff, recSize) {
1579
+ const { ctx, view, state, inPath } = rCtx;
1580
+ if (recSize >= 16) {
1581
+ const lx = view.getInt32(dataOff, true);
1582
+ const ly = view.getInt32(dataOff + 4, true);
1583
+ const rotated = hasWorldRotation(rCtx);
1584
+ const to = rotated ? gmapPoint(rCtx, lx, ly) : { x: gmx(rCtx, lx), y: gmy(rCtx, ly) };
1585
+ if (inPath) {
1586
+ ctx.lineTo(to.x, to.y);
1587
+ } else {
1588
+ const from = rotated ? gmapPoint(rCtx, state.curX, state.curY) : { x: gmx(rCtx, state.curX), y: gmy(rCtx, state.curY) };
1589
+ const build = (c) => {
1590
+ c.beginPath();
1591
+ c.moveTo(from.x, from.y);
1592
+ c.lineTo(to.x, to.y);
1593
+ };
1594
+ build(ctx);
1595
+ strokeShapeExactOrFast(rCtx, build);
1596
+ }
1597
+ state.curX = lx;
1598
+ state.curY = ly;
1356
1599
  }
1357
- const bits = new Uint8Array(width * height);
1358
- for (let row = 0; row < height; row++) {
1359
- const y = heightRaw > 0 ? height - 1 - row : row;
1360
- const rowOff = bitsOffset + row * stride;
1361
- for (let x = 0; x < width; x++) {
1362
- bits[y * width + x] = view.getUint8(rowOff + (x >> 3)) >> 7 - (x & 7) & 1;
1600
+ return true;
1601
+ }
1602
+ function handleRectangle(rCtx, dataOff, recSize) {
1603
+ const { ctx, view, state, inPath } = rCtx;
1604
+ if (recSize >= 24) {
1605
+ const l = view.getInt32(dataOff, true);
1606
+ const t = view.getInt32(dataOff + 4, true);
1607
+ const r = view.getInt32(dataOff + 8, true);
1608
+ const b = view.getInt32(dataOff + 12, true);
1609
+ const rotated = hasWorldRotation(rCtx);
1610
+ if (rotated) {
1611
+ const p1 = gmapPoint(rCtx, l, t);
1612
+ const p2 = gmapPoint(rCtx, r, t);
1613
+ const p3 = gmapPoint(rCtx, r, b);
1614
+ const p4 = gmapPoint(rCtx, l, b);
1615
+ const build = (c) => {
1616
+ c.beginPath();
1617
+ c.moveTo(p1.x, p1.y);
1618
+ c.lineTo(p2.x, p2.y);
1619
+ c.lineTo(p3.x, p3.y);
1620
+ c.lineTo(p4.x, p4.y);
1621
+ c.closePath();
1622
+ };
1623
+ if (inPath) {
1624
+ build(ctx);
1625
+ } else {
1626
+ build(ctx);
1627
+ fillShapeExactOrFast(rCtx, build);
1628
+ strokeShapeExactOrFast(rCtx, build);
1629
+ }
1630
+ return true;
1631
+ }
1632
+ if (inPath) {
1633
+ ctx.rect(gmx(rCtx, l), gmy(rCtx, t), gmw(rCtx, r - l), gmh(rCtx, b - t));
1634
+ } else {
1635
+ const x = gmx(rCtx, l);
1636
+ const y = gmy(rCtx, t);
1637
+ const w = gmw(rCtx, r - l);
1638
+ const h = gmh(rCtx, b - t);
1639
+ if (needsPathBasedRectangle(rCtx)) {
1640
+ const build = (c) => {
1641
+ c.beginPath();
1642
+ c.rect(x, y, w, h);
1643
+ };
1644
+ build(ctx);
1645
+ fillShapeExactOrFast(rCtx, build);
1646
+ strokeShapeExactOrFast(rCtx, build);
1647
+ } else {
1648
+ applyBrush(ctx, state);
1649
+ ctx.fillRect(x, y, w, h);
1650
+ applyPen(ctx, state);
1651
+ ctx.strokeRect(x, y, w, h);
1652
+ }
1363
1653
  }
1364
1654
  }
1365
- return { width, height, bits };
1655
+ return true;
1366
1656
  }
1367
- function parsePatternBrush(view, offset, dataOff, mono) {
1368
- if (dataOff + 24 > view.byteLength) {
1369
- return null;
1370
- }
1371
- const offBmi = view.getUint32(dataOff + 8, true);
1372
- const offBits = view.getUint32(dataOff + 16, true);
1373
- const cbBits = view.getUint32(dataOff + 20, true);
1374
- const bmi = offset + offBmi;
1375
- const bitsAt = offset + offBits;
1376
- const bitCount = bmi + 16 <= view.byteLength ? view.getUint16(bmi + 14, true) : 0;
1377
- if (mono || bitCount === 1) {
1378
- const decoded = decodeMonoBits(view, bmi, bitsAt);
1379
- if (decoded && mono) {
1380
- return { kind: "mono", ...decoded };
1657
+ function handleRoundRect(rCtx, dataOff, recSize) {
1658
+ const { ctx, view, inPath } = rCtx;
1659
+ if (recSize >= 32) {
1660
+ const l = view.getInt32(dataOff, true);
1661
+ const t = view.getInt32(dataOff + 4, true);
1662
+ const r = view.getInt32(dataOff + 8, true);
1663
+ const b = view.getInt32(dataOff + 12, true);
1664
+ const cornerW = view.getInt32(dataOff + 16, true);
1665
+ const cornerH = view.getInt32(dataOff + 20, true);
1666
+ const rotated = hasWorldRotation(rCtx);
1667
+ const rw = Math.abs(rotated ? cornerW / 2 : gmw(rCtx, cornerW) / 2);
1668
+ const rh = Math.abs(rotated ? cornerH / 2 : gmh(rCtx, cornerH) / 2);
1669
+ const x1 = rotated ? l : gmx(rCtx, l);
1670
+ const y1 = rotated ? t : gmy(rCtx, t);
1671
+ const w = rotated ? r - l : gmw(rCtx, r - l);
1672
+ const h = rotated ? b - t : gmh(rCtx, b - t);
1673
+ const map = (x, y) => rotated ? gmapPoint(rCtx, x, y) : { x, y };
1674
+ const drawRoundRect = (c) => {
1675
+ const radius = Math.min(rw, rh, Math.abs(w) / 2, Math.abs(h) / 2);
1676
+ const start = map(x1 + radius, y1);
1677
+ c.moveTo(start.x, start.y);
1678
+ if (rotated) {
1679
+ const corners = [
1680
+ [x1 + w - radius, y1],
1681
+ [x1 + w, y1 + radius],
1682
+ [x1 + w, y1 + h - radius],
1683
+ [x1 + w - radius, y1 + h],
1684
+ [x1 + radius, y1 + h],
1685
+ [x1, y1 + h - radius],
1686
+ [x1, y1 + radius],
1687
+ [x1 + radius, y1]
1688
+ ];
1689
+ for (const [px, py] of corners) {
1690
+ const q = map(px, py);
1691
+ c.lineTo(q.x, q.y);
1692
+ }
1693
+ } else {
1694
+ const corner2 = map(x1 + w - radius, y1);
1695
+ c.lineTo(corner2.x, corner2.y);
1696
+ const arc1a = map(x1 + w, y1);
1697
+ const arc1b = map(x1 + w, y1 + radius);
1698
+ c.arcTo(arc1a.x, arc1a.y, arc1b.x, arc1b.y, radius);
1699
+ const corner3 = map(x1 + w, y1 + h - radius);
1700
+ c.lineTo(corner3.x, corner3.y);
1701
+ const arc2a = map(x1 + w, y1 + h);
1702
+ const arc2b = map(x1 + w - radius, y1 + h);
1703
+ c.arcTo(arc2a.x, arc2a.y, arc2b.x, arc2b.y, radius);
1704
+ const corner4 = map(x1 + radius, y1 + h);
1705
+ c.lineTo(corner4.x, corner4.y);
1706
+ const arc3a = map(x1, y1 + h);
1707
+ const arc3b = map(x1, y1 + h - radius);
1708
+ c.arcTo(arc3a.x, arc3a.y, arc3b.x, arc3b.y, radius);
1709
+ const corner1 = map(x1, y1 + radius);
1710
+ c.lineTo(corner1.x, corner1.y);
1711
+ const arc4a = map(x1, y1);
1712
+ const arc4b = map(x1 + radius, y1);
1713
+ c.arcTo(arc4a.x, arc4a.y, arc4b.x, arc4b.y, radius);
1714
+ }
1715
+ c.closePath();
1716
+ };
1717
+ if (inPath) {
1718
+ drawRoundRect(ctx);
1719
+ } else {
1720
+ const build = (c) => {
1721
+ c.beginPath();
1722
+ drawRoundRect(c);
1723
+ };
1724
+ build(ctx);
1725
+ fillShapeExactOrFast(rCtx, build);
1726
+ strokeShapeExactOrFast(rCtx, build);
1381
1727
  }
1382
1728
  }
1383
- const image = decodeDibToImageData(view, bmi, bitsAt, cbBits);
1384
- if (!image) {
1385
- return null;
1386
- }
1387
- const rgb = new Uint32Array(image.width * image.height);
1388
- const d = image.data;
1389
- for (let i = 0; i < rgb.length; i++) {
1390
- rgb[i] = d[i * 4] << 16 | d[i * 4 + 1] << 8 | d[i * 4 + 2];
1391
- }
1392
- return { kind: "bitmap", width: image.width, height: image.height, rgb };
1729
+ return true;
1393
1730
  }
1394
- function realizeBrush(state) {
1395
- if (state.brushStyle === 1) {
1396
- return { kind: "none" };
1397
- }
1398
- const pattern = state.brushPattern;
1399
- if (!pattern) {
1400
- return { kind: "solid", rgb: cssHexToRgb(state.brushColor) };
1401
- }
1402
- if (pattern.kind === "bitmap") {
1403
- return { kind: "tile", width: pattern.width, height: pattern.height, rgb: pattern.rgb };
1731
+ function handleEllipse(rCtx, dataOff, recSize) {
1732
+ const { ctx, view, inPath } = rCtx;
1733
+ if (recSize >= 24) {
1734
+ const l = view.getInt32(dataOff, true);
1735
+ const t = view.getInt32(dataOff + 4, true);
1736
+ const r = view.getInt32(dataOff + 8, true);
1737
+ const b = view.getInt32(dataOff + 12, true);
1738
+ const params = hasWorldRotation(rCtx) ? gdiEllipseParams(rCtx, (l + r) / 2, (t + b) / 2, Math.abs(r - l) / 2, Math.abs(b - t) / 2) : {
1739
+ cx: gmx(rCtx, (l + r) / 2),
1740
+ cy: gmy(rCtx, (t + b) / 2),
1741
+ rx: Math.abs(gmw(rCtx, r - l)) / 2,
1742
+ ry: Math.abs(gmh(rCtx, b - t)) / 2,
1743
+ rotation: 0
1744
+ };
1745
+ if (inPath) {
1746
+ ctx.ellipse(params.cx, params.cy, params.rx, params.ry, params.rotation, 0, Math.PI * 2);
1747
+ } else {
1748
+ const build = (c) => {
1749
+ c.beginPath();
1750
+ c.ellipse(params.cx, params.cy, params.rx, params.ry, params.rotation, 0, Math.PI * 2);
1751
+ };
1752
+ build(ctx);
1753
+ fillShapeExactOrFast(rCtx, build);
1754
+ strokeShapeExactOrFast(rCtx, build);
1755
+ }
1404
1756
  }
1405
- const bk = cssHexToRgb(state.bkColor);
1406
- if (pattern.kind === "hatch") {
1407
- const fg = cssHexToRgb(state.brushColor);
1408
- const rgb2 = new Uint32Array(64);
1409
- for (let y = 0; y < 8; y++) {
1410
- for (let x = 0; x < 8; x++) {
1411
- rgb2[y * 8 + x] = hatchBit(pattern.hatch, x, y) ? fg : bk;
1757
+ return true;
1758
+ }
1759
+ function handleArcFamily(rCtx, recType, dataOff, recSize) {
1760
+ const { ctx, view, state, inPath } = rCtx;
1761
+ if (recSize >= 40) {
1762
+ const l = view.getInt32(dataOff, true);
1763
+ const t = view.getInt32(dataOff + 4, true);
1764
+ const r = view.getInt32(dataOff + 8, true);
1765
+ const b = view.getInt32(dataOff + 12, true);
1766
+ const startX = view.getInt32(dataOff + 16, true);
1767
+ const startY = view.getInt32(dataOff + 20, true);
1768
+ const endX = view.getInt32(dataOff + 24, true);
1769
+ const endY = view.getInt32(dataOff + 28, true);
1770
+ const cxA = (l + r) / 2;
1771
+ const cyA = (t + b) / 2;
1772
+ const rx = Math.abs(r - l) / 2;
1773
+ const ry = Math.abs(b - t) / 2;
1774
+ const startAngle = Math.atan2((startY - cyA) / (ry || 1), (startX - cxA) / (rx || 1));
1775
+ const endAngle = Math.atan2((endY - cyA) / (ry || 1), (endX - cxA) / (rx || 1));
1776
+ const rotated = hasWorldRotation(rCtx);
1777
+ const params = rotated ? gdiEllipseParams(rCtx, cxA, cyA, rx, ry) : {
1778
+ cx: gmx(rCtx, cxA),
1779
+ cy: gmy(rCtx, cyA),
1780
+ rx: Math.abs(gmw(rCtx, rx)),
1781
+ ry: Math.abs(gmh(rCtx, ry)),
1782
+ rotation: 0
1783
+ };
1784
+ const isArcTo = recType === EMR_ARCTO;
1785
+ const needsFill = recType === EMR_PIE || recType === EMR_CHORD;
1786
+ const startPoint = rotated ? gmapPoint(rCtx, cxA + rx * Math.cos(startAngle), cyA + ry * Math.sin(startAngle)) : { x: params.cx + params.rx * Math.cos(startAngle), y: params.cy + params.ry * Math.sin(startAngle) };
1787
+ const build = (c) => {
1788
+ if (recType === EMR_PIE) {
1789
+ c.moveTo(params.cx, params.cy);
1790
+ }
1791
+ if (isArcTo) {
1792
+ c.lineTo(startPoint.x, startPoint.y);
1793
+ }
1794
+ c.ellipse(params.cx, params.cy, params.rx, params.ry, params.rotation, startAngle, endAngle, false);
1795
+ if (needsFill) {
1796
+ c.closePath();
1797
+ }
1798
+ };
1799
+ if (!inPath) {
1800
+ ctx.beginPath();
1801
+ }
1802
+ build(ctx);
1803
+ if (!inPath) {
1804
+ const buildWithPath = (c) => {
1805
+ c.beginPath();
1806
+ build(c);
1807
+ };
1808
+ if (needsFill) {
1809
+ fillShapeExactOrFast(rCtx, buildWithPath);
1412
1810
  }
1811
+ strokeShapeExactOrFast(rCtx, buildWithPath);
1812
+ }
1813
+ if (isArcTo) {
1814
+ state.curX = endX;
1815
+ state.curY = endY;
1413
1816
  }
1414
- return { kind: "tile", width: 8, height: 8, rgb: rgb2 };
1415
- }
1416
- const text = cssHexToRgb(state.textColor);
1417
- const rgb = new Uint32Array(pattern.width * pattern.height);
1418
- for (let i = 0; i < rgb.length; i++) {
1419
- rgb[i] = pattern.bits[i] ? bk : text;
1420
1817
  }
1421
- return { kind: "tile", width: pattern.width, height: pattern.height, rgb };
1818
+ return true;
1422
1819
  }
1423
- function sampleTile(tile, devX, devY, orgX, orgY) {
1424
- const tx = ((devX - orgX) % tile.width + tile.width) % tile.width;
1425
- const ty = ((devY - orgY) % tile.height + tile.height) % tile.height;
1426
- return tile.rgb[ty * tile.width + tx];
1820
+ function handleEmfGdiShapeRecord(rCtx, recType, dataOff, recSize) {
1821
+ switch (recType) {
1822
+ case EMR_SETPIXELV:
1823
+ return handleSetPixelV(rCtx, dataOff, recSize);
1824
+ case EMR_MOVETOEX:
1825
+ return handleMoveToEx(rCtx, dataOff, recSize);
1826
+ case EMR_LINETO:
1827
+ return handleLineTo(rCtx, dataOff, recSize);
1828
+ case EMR_RECTANGLE:
1829
+ return handleRectangle(rCtx, dataOff, recSize);
1830
+ case EMR_ROUNDRECT:
1831
+ return handleRoundRect(rCtx, dataOff, recSize);
1832
+ case EMR_ELLIPSE:
1833
+ return handleEllipse(rCtx, dataOff, recSize);
1834
+ case EMR_ARC:
1835
+ case EMR_ARCTO:
1836
+ case EMR_CHORD:
1837
+ case EMR_PIE:
1838
+ return handleArcFamily(rCtx, recType, dataOff, recSize);
1839
+ default:
1840
+ return false;
1841
+ }
1427
1842
  }
1428
1843
 
1429
1844
  // src/emf-gdi-stretch.ts
@@ -1478,108 +1893,25 @@ function stretchGdi(src, sx, sy, sw, sh, dw, dh, mode) {
1478
1893
  let first = true;
1479
1894
  for (const yy of ys) {
1480
1895
  for (const xx of xs) {
1481
- const p = read(xx, yy);
1482
- if (mode === BLACKONWHITE) {
1483
- v &= p;
1484
- } else if (mode === WHITEONBLACK) {
1485
- v |= p;
1486
- } else if (first) {
1487
- v = p;
1488
- }
1489
- first = false;
1490
- }
1491
- }
1492
- const o = (y * W + x) * 4;
1493
- data[o] = v >> 16 & 255;
1494
- data[o + 1] = v >> 8 & 255;
1495
- data[o + 2] = v & 255;
1496
- data[o + 3] = 255;
1497
- }
1498
- }
1499
- return { width: W, height: H, data };
1500
- }
1501
-
1502
- // src/emf-rop3.ts
1503
- function rop3Index(rop) {
1504
- return rop >>> 16 & 255;
1505
- }
1506
- function rop3Operands(index) {
1507
- const t = index & 255;
1508
- return {
1509
- usesP: (t >> 4 & 15) !== (t & 15),
1510
- usesS: (t >> 2 & 51) !== (t & 51),
1511
- usesD: (t >> 1 & 85) !== (t & 85)
1512
- };
1513
- }
1514
- function evalRop3(index, p, s, d, mask = 16777215) {
1515
- let out = 0;
1516
- const np = ~p;
1517
- const ns = ~s;
1518
- const nd = ~d;
1519
- for (let i = 0; i < 8; i++) {
1520
- if (index & 1 << i) {
1521
- out |= (i & 4 ? p : np) & (i & 2 ? s : ns) & (i & 1 ? d : nd);
1522
- }
1523
- }
1524
- return out & mask;
1525
- }
1526
- function classifyRop3(rop) {
1527
- const index = rop3Index(rop);
1528
- switch (index) {
1529
- case 204:
1530
- return { kind: "copy" };
1531
- case 0:
1532
- return { kind: "solid", color: "black" };
1533
- case 255:
1534
- return { kind: "solid", color: "white" };
1535
- case 170:
1536
- return { kind: "noop" };
1537
- case 85:
1538
- return { kind: "invert-dest" };
1539
- default:
1540
- return { kind: "ternary", index, operands: rop3Operands(index) };
1541
- }
1542
- }
1543
- function applyRop3(dst, src, pattern, index, originX, originY) {
1544
- const d = dst.data;
1545
- const s = src ? src.data : null;
1546
- const w = dst.width;
1547
- const h = dst.height;
1548
- const solidP = typeof pattern === "number" ? pattern : 0;
1549
- const sampleP = typeof pattern === "function" ? pattern : null;
1550
- for (let y = 0; y < h; y++) {
1551
- for (let x = 0; x < w; x++) {
1552
- const i = (y * w + x) * 4;
1553
- const dv = d[i] << 16 | d[i + 1] << 8 | d[i + 2];
1554
- const sv = s ? s[i] << 16 | s[i + 1] << 8 | s[i + 2] : 0;
1555
- const pv = sampleP ? sampleP(originX + x, originY + y) : solidP;
1556
- const r = evalRop3(index, pv, sv, dv);
1557
- d[i] = r >> 16 & 255;
1558
- d[i + 1] = r >> 8 & 255;
1559
- d[i + 2] = r & 255;
1560
- d[i + 3] = 255;
1561
- }
1562
- }
1563
- }
1564
- function clampPositiveRect(x, y, w, h, canvasW, canvasH) {
1565
- let left = w < 0 ? x + w : x;
1566
- let top = h < 0 ? y + h : y;
1567
- let width = Math.abs(w);
1568
- let height = Math.abs(h);
1569
- left = Math.round(left);
1570
- top = Math.round(top);
1571
- width = Math.round(width);
1572
- height = Math.round(height);
1573
- const right = Math.min(left + width, Math.round(canvasW));
1574
- const bottom = Math.min(top + height, Math.round(canvasH));
1575
- left = Math.max(left, 0);
1576
- top = Math.max(top, 0);
1577
- width = right - left;
1578
- height = bottom - top;
1579
- if (width <= 0 || height <= 0) {
1580
- return null;
1896
+ const p = read(xx, yy);
1897
+ if (mode === BLACKONWHITE) {
1898
+ v &= p;
1899
+ } else if (mode === WHITEONBLACK) {
1900
+ v |= p;
1901
+ } else if (first) {
1902
+ v = p;
1903
+ }
1904
+ first = false;
1905
+ }
1906
+ }
1907
+ const o = (y * W + x) * 4;
1908
+ data[o] = v >> 16 & 255;
1909
+ data[o + 1] = v >> 8 & 255;
1910
+ data[o + 2] = v & 255;
1911
+ data[o + 3] = 255;
1912
+ }
1581
1913
  }
1582
- return { x: left, y: top, w: width, h: height };
1914
+ return { width: W, height: H, data };
1583
1915
  }
1584
1916
 
1585
1917
  // src/emf-gdi-draw-bitmap.ts
@@ -2315,6 +2647,25 @@ function handleEmfGdiDrawRecord(rCtx, recType, offset, dataOff, recSize) {
2315
2647
  }
2316
2648
 
2317
2649
  // src/emf-gdi-polypolygon-helpers.ts
2650
+ function buildPolyPolygonPath(target, rCtx, readPoint, countsOff, numPolys, totalPoints, close) {
2651
+ const { view } = rCtx;
2652
+ let pIdx = 0;
2653
+ for (let p = 0; p < numPolys; p++) {
2654
+ const count = view.getUint32(countsOff + p * 4, true);
2655
+ for (let i = 0; i < count && pIdx < totalPoints; i++) {
2656
+ const pt = readPoint(pIdx);
2657
+ if (i === 0) {
2658
+ target.moveTo(pt.x, pt.y);
2659
+ } else {
2660
+ target.lineTo(pt.x, pt.y);
2661
+ }
2662
+ pIdx++;
2663
+ }
2664
+ if (close) {
2665
+ target.closePath();
2666
+ }
2667
+ }
2668
+ }
2318
2669
  function handlePolyPolygon32(rCtx, offset, dataOff, recSize) {
2319
2670
  const { ctx, view, state, inPath } = rCtx;
2320
2671
  const numPolys = view.getUint32(dataOff + 16, true);
@@ -2327,33 +2678,25 @@ function handlePolyPolygon32(rCtx, offset, dataOff, recSize) {
2327
2678
  if (ptOff + totalPoints * 8 > offset + recSize) {
2328
2679
  return;
2329
2680
  }
2681
+ const readPoint = (pIdx) => gmapPoint(rCtx, view.getInt32(ptOff + pIdx * 8, true), view.getInt32(ptOff + pIdx * 8 + 4, true));
2682
+ const build = (target) => {
2683
+ buildPolyPolygonPath(target, rCtx, readPoint, countsOff, numPolys, totalPoints, true);
2684
+ };
2330
2685
  if (!inPath) {
2331
2686
  ctx.beginPath();
2332
2687
  }
2333
- let pIdx = 0;
2334
- for (let p = 0; p < numPolys; p++) {
2335
- const count = view.getUint32(countsOff + p * 4, true);
2336
- for (let i = 0; i < count && pIdx < totalPoints; i++) {
2337
- const px = view.getInt32(ptOff + pIdx * 8, true);
2338
- const py = view.getInt32(ptOff + pIdx * 8 + 4, true);
2339
- if (i === 0) {
2340
- ctx.moveTo(gmx(rCtx, px), gmy(rCtx, py));
2341
- } else {
2342
- ctx.lineTo(gmx(rCtx, px), gmy(rCtx, py));
2343
- }
2344
- pIdx++;
2345
- }
2346
- ctx.closePath();
2347
- }
2688
+ build(ctx);
2348
2689
  if (!inPath) {
2349
- applyBrush(ctx, state);
2350
- ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
2351
- applyPen(ctx, state);
2352
- ctx.stroke();
2690
+ const buildWithPath = (target) => {
2691
+ target.beginPath();
2692
+ build(target);
2693
+ };
2694
+ fillShapeExactOrFast(rCtx, buildWithPath, state.polyFillMode === 2 ? "nonzero" : "evenodd");
2695
+ strokeShapeExactOrFast(rCtx, buildWithPath);
2353
2696
  }
2354
2697
  }
2355
2698
  function handlePolyPolyline32(rCtx, offset, dataOff, recSize) {
2356
- const { ctx, view, state, inPath } = rCtx;
2699
+ const { ctx, view, inPath } = rCtx;
2357
2700
  const numPolys = view.getUint32(dataOff + 16, true);
2358
2701
  const totalPoints = view.getUint32(dataOff + 20, true);
2359
2702
  if (numPolys === 0 || numPolys >= 1e4 || totalPoints >= 1e5) {
@@ -2364,26 +2707,20 @@ function handlePolyPolyline32(rCtx, offset, dataOff, recSize) {
2364
2707
  if (ptOff + totalPoints * 8 > offset + recSize) {
2365
2708
  return;
2366
2709
  }
2710
+ const readPoint = (pIdx) => gmapPoint(rCtx, view.getInt32(ptOff + pIdx * 8, true), view.getInt32(ptOff + pIdx * 8 + 4, true));
2711
+ const build = (target) => {
2712
+ buildPolyPolygonPath(target, rCtx, readPoint, countsOff, numPolys, totalPoints, false);
2713
+ };
2367
2714
  if (!inPath) {
2368
2715
  ctx.beginPath();
2369
2716
  }
2370
- let pIdx = 0;
2371
- for (let p = 0; p < numPolys; p++) {
2372
- const count = view.getUint32(countsOff + p * 4, true);
2373
- for (let i = 0; i < count && pIdx < totalPoints; i++) {
2374
- const px = view.getInt32(ptOff + pIdx * 8, true);
2375
- const py = view.getInt32(ptOff + pIdx * 8 + 4, true);
2376
- if (i === 0) {
2377
- ctx.moveTo(gmx(rCtx, px), gmy(rCtx, py));
2378
- } else {
2379
- ctx.lineTo(gmx(rCtx, px), gmy(rCtx, py));
2380
- }
2381
- pIdx++;
2382
- }
2383
- }
2717
+ build(ctx);
2384
2718
  if (!inPath) {
2385
- applyPen(ctx, state);
2386
- ctx.stroke();
2719
+ const buildWithPath = (target) => {
2720
+ target.beginPath();
2721
+ build(target);
2722
+ };
2723
+ strokeShapeExactOrFast(rCtx, buildWithPath);
2387
2724
  }
2388
2725
  }
2389
2726
  function handlePolyPolygon16(rCtx, offset, dataOff, recSize) {
@@ -2398,29 +2735,21 @@ function handlePolyPolygon16(rCtx, offset, dataOff, recSize) {
2398
2735
  if (ptOff + totalPoints * 4 > offset + recSize) {
2399
2736
  return;
2400
2737
  }
2738
+ const readPoint = (pIdx) => gmapPoint(rCtx, view.getInt16(ptOff + pIdx * 4, true), view.getInt16(ptOff + pIdx * 4 + 2, true));
2739
+ const build = (target) => {
2740
+ buildPolyPolygonPath(target, rCtx, readPoint, countsOff, numPolys, totalPoints, true);
2741
+ };
2401
2742
  if (!inPath) {
2402
2743
  ctx.beginPath();
2403
2744
  }
2404
- let pIdx = 0;
2405
- for (let p = 0; p < numPolys; p++) {
2406
- const count = view.getUint32(countsOff + p * 4, true);
2407
- for (let i = 0; i < count && pIdx < totalPoints; i++) {
2408
- const px = view.getInt16(ptOff + pIdx * 4, true);
2409
- const py = view.getInt16(ptOff + pIdx * 4 + 2, true);
2410
- if (i === 0) {
2411
- ctx.moveTo(gmx(rCtx, px), gmy(rCtx, py));
2412
- } else {
2413
- ctx.lineTo(gmx(rCtx, px), gmy(rCtx, py));
2414
- }
2415
- pIdx++;
2416
- }
2417
- ctx.closePath();
2418
- }
2745
+ build(ctx);
2419
2746
  if (!inPath) {
2420
- applyBrush(ctx, state);
2421
- ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
2422
- applyPen(ctx, state);
2423
- ctx.stroke();
2747
+ const buildWithPath = (target) => {
2748
+ target.beginPath();
2749
+ build(target);
2750
+ };
2751
+ fillShapeExactOrFast(rCtx, buildWithPath, state.polyFillMode === 2 ? "nonzero" : "evenodd");
2752
+ strokeShapeExactOrFast(rCtx, buildWithPath);
2424
2753
  }
2425
2754
  }
2426
2755
 
@@ -2438,43 +2767,44 @@ function handlePoly32(rCtx, recType, offset, dataOff, recSize) {
2438
2767
  const isPolygon = recType === EMR_POLYGON;
2439
2768
  const isBezier = recType === EMR_POLYBEZIER || recType === EMR_POLYBEZIERTO;
2440
2769
  const isTo = recType === EMR_POLYBEZIERTO || recType === EMR_POLYLINETO;
2441
- if (!inPath) {
2442
- ctx.beginPath();
2443
- }
2444
- if (!isTo) {
2445
- ctx.moveTo(gmx(rCtx, view.getInt32(ptOff, true)), gmy(rCtx, view.getInt32(ptOff + 4, true)));
2446
- }
2447
- let i = isTo ? 0 : 1;
2448
- if (isBezier) {
2449
- while (i + 2 < count) {
2450
- ctx.bezierCurveTo(
2451
- gmx(rCtx, view.getInt32(ptOff + i * 8, true)),
2452
- gmy(rCtx, view.getInt32(ptOff + i * 8 + 4, true)),
2453
- gmx(rCtx, view.getInt32(ptOff + (i + 1) * 8, true)),
2454
- gmy(rCtx, view.getInt32(ptOff + (i + 1) * 8 + 4, true)),
2455
- gmx(rCtx, view.getInt32(ptOff + (i + 2) * 8, true)),
2456
- gmy(rCtx, view.getInt32(ptOff + (i + 2) * 8 + 4, true))
2457
- );
2458
- i += 3;
2770
+ const pt = (i) => gmapPoint(rCtx, view.getInt32(ptOff + i * 8, true), view.getInt32(ptOff + i * 8 + 4, true));
2771
+ const build = (target) => {
2772
+ if (!isTo) {
2773
+ const p0 = pt(0);
2774
+ target.moveTo(p0.x, p0.y);
2775
+ }
2776
+ let i = isTo ? 0 : 1;
2777
+ if (isBezier) {
2778
+ while (i + 2 < count) {
2779
+ const p1 = pt(i);
2780
+ const p2 = pt(i + 1);
2781
+ const p3 = pt(i + 2);
2782
+ target.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
2783
+ i += 3;
2784
+ }
2785
+ } else {
2786
+ for (; i < count; i++) {
2787
+ const p = pt(i);
2788
+ target.lineTo(p.x, p.y);
2789
+ }
2459
2790
  }
2460
- } else {
2461
- for (; i < count; i++) {
2462
- ctx.lineTo(
2463
- gmx(rCtx, view.getInt32(ptOff + i * 8, true)),
2464
- gmy(rCtx, view.getInt32(ptOff + i * 8 + 4, true))
2465
- );
2791
+ if (isPolygon) {
2792
+ target.closePath();
2466
2793
  }
2794
+ };
2795
+ if (!inPath) {
2796
+ ctx.beginPath();
2467
2797
  }
2468
- if (isPolygon) {
2469
- ctx.closePath();
2470
- }
2798
+ build(ctx);
2471
2799
  if (!inPath) {
2800
+ const buildWithPath = (target) => {
2801
+ target.beginPath();
2802
+ build(target);
2803
+ };
2472
2804
  if (isPolygon) {
2473
- applyBrush(ctx, state);
2474
- ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
2805
+ fillShapeExactOrFast(rCtx, buildWithPath, state.polyFillMode === 2 ? "nonzero" : "evenodd");
2475
2806
  }
2476
- applyPen(ctx, state);
2477
- ctx.stroke();
2807
+ strokeShapeExactOrFast(rCtx, buildWithPath);
2478
2808
  }
2479
2809
  if (count > 0) {
2480
2810
  const last = count - 1;
@@ -2496,43 +2826,44 @@ function handlePoly16(rCtx, recType, offset, dataOff, recSize) {
2496
2826
  const isPolygon = recType === EMR_POLYGON16;
2497
2827
  const isBezier = recType === EMR_POLYBEZIER16 || recType === EMR_POLYBEZIERTO16;
2498
2828
  const isTo = recType === EMR_POLYBEZIERTO16 || recType === EMR_POLYLINETO16;
2499
- if (!inPath) {
2500
- ctx.beginPath();
2501
- }
2502
- if (!isTo) {
2503
- ctx.moveTo(gmx(rCtx, view.getInt16(ptOff, true)), gmy(rCtx, view.getInt16(ptOff + 2, true)));
2504
- }
2505
- let i = isTo ? 0 : 1;
2506
- if (isBezier) {
2507
- while (i + 2 < count) {
2508
- ctx.bezierCurveTo(
2509
- gmx(rCtx, view.getInt16(ptOff + i * 4, true)),
2510
- gmy(rCtx, view.getInt16(ptOff + i * 4 + 2, true)),
2511
- gmx(rCtx, view.getInt16(ptOff + (i + 1) * 4, true)),
2512
- gmy(rCtx, view.getInt16(ptOff + (i + 1) * 4 + 2, true)),
2513
- gmx(rCtx, view.getInt16(ptOff + (i + 2) * 4, true)),
2514
- gmy(rCtx, view.getInt16(ptOff + (i + 2) * 4 + 2, true))
2515
- );
2516
- i += 3;
2829
+ const pt = (i) => gmapPoint(rCtx, view.getInt16(ptOff + i * 4, true), view.getInt16(ptOff + i * 4 + 2, true));
2830
+ const build = (target) => {
2831
+ if (!isTo) {
2832
+ const p0 = pt(0);
2833
+ target.moveTo(p0.x, p0.y);
2834
+ }
2835
+ let i = isTo ? 0 : 1;
2836
+ if (isBezier) {
2837
+ while (i + 2 < count) {
2838
+ const p1 = pt(i);
2839
+ const p2 = pt(i + 1);
2840
+ const p3 = pt(i + 2);
2841
+ target.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
2842
+ i += 3;
2843
+ }
2844
+ } else {
2845
+ for (; i < count; i++) {
2846
+ const p = pt(i);
2847
+ target.lineTo(p.x, p.y);
2848
+ }
2517
2849
  }
2518
- } else {
2519
- for (; i < count; i++) {
2520
- ctx.lineTo(
2521
- gmx(rCtx, view.getInt16(ptOff + i * 4, true)),
2522
- gmy(rCtx, view.getInt16(ptOff + i * 4 + 2, true))
2523
- );
2850
+ if (isPolygon) {
2851
+ target.closePath();
2524
2852
  }
2853
+ };
2854
+ if (!inPath) {
2855
+ ctx.beginPath();
2525
2856
  }
2526
- if (isPolygon) {
2527
- ctx.closePath();
2528
- }
2857
+ build(ctx);
2529
2858
  if (!inPath) {
2859
+ const buildWithPath = (target) => {
2860
+ target.beginPath();
2861
+ build(target);
2862
+ };
2530
2863
  if (isPolygon) {
2531
- applyBrush(ctx, state);
2532
- ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
2864
+ fillShapeExactOrFast(rCtx, buildWithPath, state.polyFillMode === 2 ? "nonzero" : "evenodd");
2533
2865
  }
2534
- applyPen(ctx, state);
2535
- ctx.stroke();
2866
+ strokeShapeExactOrFast(rCtx, buildWithPath);
2536
2867
  }
2537
2868
  if (count > 0) {
2538
2869
  const last = count - 1;
@@ -2585,16 +2916,24 @@ function handleEmfGdiPolyPathRecord(rCtx, recType, offset, dataOff, recSize) {
2585
2916
  case EMR_CLOSEFIGURE:
2586
2917
  ctx.closePath();
2587
2918
  return true;
2588
- case EMR_FILLPATH:
2589
- applyBrush(ctx, state);
2590
- ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
2919
+ case EMR_FILLPATH: {
2920
+ const fillRule = state.polyFillMode === 2 ? "nonzero" : "evenodd";
2921
+ if (!fillCurrentPathWithGdiPattern(rCtx, fillRule)) {
2922
+ applyBrush(ctx, state);
2923
+ ctx.fill(fillRule);
2924
+ }
2591
2925
  return true;
2592
- case EMR_STROKEANDFILLPATH:
2593
- applyBrush(ctx, state);
2594
- ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
2926
+ }
2927
+ case EMR_STROKEANDFILLPATH: {
2928
+ const fillRule = state.polyFillMode === 2 ? "nonzero" : "evenodd";
2929
+ if (!fillCurrentPathWithGdiPattern(rCtx, fillRule)) {
2930
+ applyBrush(ctx, state);
2931
+ ctx.fill(fillRule);
2932
+ }
2595
2933
  applyPen(ctx, state);
2596
2934
  ctx.stroke();
2597
2935
  return true;
2936
+ }
2598
2937
  case EMR_STROKEPATH:
2599
2938
  applyPen(ctx, state);
2600
2939
  ctx.stroke();
@@ -3311,14 +3650,14 @@ function plainLinear(ctx, grad) {
3311
3650
  }
3312
3651
  var SEAM_BIAS = 1e-5;
3313
3652
  var MAX_PERIODS = 2048;
3314
- function surfaceSize(ctx) {
3653
+ function surfaceSize3(ctx) {
3315
3654
  const canvas = ctx.canvas;
3316
3655
  const w = canvas?.width;
3317
3656
  const h = canvas?.height;
3318
3657
  return typeof w === "number" && typeof h === "number" && w > 0 && h > 0 ? { w, h } : null;
3319
3658
  }
3320
3659
  function tiledLinear(ctx, grad, rect, device) {
3321
- const size = surfaceSize(ctx);
3660
+ const size = surfaceSize3(ctx);
3322
3661
  const inv = invertLinear(device);
3323
3662
  if (!size || !inv || typeof ctx.createLinearGradient !== "function") {
3324
3663
  return null;
@@ -3460,6 +3799,42 @@ function createBrushGradient(ctx, grad, device = IDENTITY) {
3460
3799
  }
3461
3800
  }
3462
3801
 
3802
+ // src/emf-plus-brush-texture.ts
3803
+ var IDENTITY2 = [1, 0, 0, 1, 0, 0];
3804
+ function packArgb(rgba, i) {
3805
+ return (rgba[i + 3] << 24 | rgba[i] << 16 | rgba[i + 1] << 8 | rgba[i + 2]) >>> 0;
3806
+ }
3807
+ function createBrushTexture(ctx, texture, device = IDENTITY2) {
3808
+ const { width, height, rgba } = texture;
3809
+ if (width <= 0 || height <= 0) {
3810
+ return null;
3811
+ }
3812
+ try {
3813
+ const brush = texture.transform ?? IDENTITY2;
3814
+ const colorAt = (bx, by) => {
3815
+ const ix = Math.floor(bx);
3816
+ const iy = Math.floor(by);
3817
+ if (ix < 0 || iy < 0 || ix >= width || iy >= height) {
3818
+ return null;
3819
+ }
3820
+ return packArgb(rgba, (iy * width + ix) * 4);
3821
+ };
3822
+ return buildPattern(
3823
+ ctx,
3824
+ { x: 0, y: 0, w: width, h: height },
3825
+ width,
3826
+ height,
3827
+ texture.wrapMode,
3828
+ brush,
3829
+ halfPixelDelta(device),
3830
+ { phaseX: 0, lagX: 0, lagY: 0 },
3831
+ colorAt
3832
+ );
3833
+ } catch {
3834
+ return null;
3835
+ }
3836
+ }
3837
+
3463
3838
  // src/emf-plus-path.ts
3464
3839
  function parseEmfPlusPath(data, off, maxLen) {
3465
3840
  if (maxLen < 12) {
@@ -3606,6 +3981,12 @@ function resolveBrushPaint(rCtx, flags, brushIdOrColor) {
3606
3981
  return g;
3607
3982
  }
3608
3983
  }
3984
+ if (obj.texture) {
3985
+ const p = createBrushTexture(rCtx.ctx, obj.texture, plusWorldMatrix(rCtx));
3986
+ if (p) {
3987
+ return p;
3988
+ }
3989
+ }
3609
3990
  return obj.color;
3610
3991
  }
3611
3992
  return "rgba(0,0,0,1)";
@@ -4101,64 +4482,238 @@ function handleEmfPlusDrawRecord(rCtx, recType, recFlags, dataOff, recDataSize)
4101
4482
  } else {
4102
4483
  ctx.stroke();
4103
4484
  }
4104
- return true;
4485
+ return true;
4486
+ }
4487
+ case EMFPLUS_DRAWLINES: {
4488
+ if (recDataSize >= 4) {
4489
+ const penId = recFlags & 255;
4490
+ const pen = objectTable.get(penId);
4491
+ const count = view.getUint32(dataOff, true);
4492
+ const compressed = (recFlags & 16384) !== 0;
4493
+ const ptSize = compressed ? 4 : 8;
4494
+ if (pen && pen.kind === "plus-pen") {
4495
+ applyEmfPlusPen(ctx, pen);
4496
+ }
4497
+ applyPlusWorldTransform(rCtx);
4498
+ ctx.beginPath();
4499
+ let pOff = dataOff + 4;
4500
+ for (let i = 0; i < count && pOff + ptSize <= dataOff + recDataSize; i++) {
4501
+ const pt = readPointFromView(view, pOff, compressed);
4502
+ if (i === 0) {
4503
+ ctx.moveTo(pt.x, pt.y);
4504
+ } else {
4505
+ ctx.lineTo(pt.x, pt.y);
4506
+ }
4507
+ pOff += ptSize;
4508
+ }
4509
+ if (recFlags & 8192) {
4510
+ ctx.closePath();
4511
+ }
4512
+ ctx.stroke();
4513
+ }
4514
+ return true;
4515
+ }
4516
+ case EMFPLUS_FILLPOLYGON: {
4517
+ if (recDataSize >= 8) {
4518
+ const brushVal = view.getUint32(dataOff, true);
4519
+ const count = view.getUint32(dataOff + 4, true);
4520
+ const compressed = (recFlags & 16384) !== 0;
4521
+ const ptSize = compressed ? 4 : 8;
4522
+ ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
4523
+ applyPlusWorldTransform(rCtx);
4524
+ ctx.beginPath();
4525
+ let pOff = dataOff + 8;
4526
+ for (let i = 0; i < count && pOff + ptSize <= dataOff + recDataSize; i++) {
4527
+ const pt = readPointFromView(view, pOff, compressed);
4528
+ if (i === 0) {
4529
+ ctx.moveTo(pt.x, pt.y);
4530
+ } else {
4531
+ ctx.lineTo(pt.x, pt.y);
4532
+ }
4533
+ pOff += ptSize;
4534
+ }
4535
+ ctx.closePath();
4536
+ ctx.fill();
4537
+ }
4538
+ return true;
4539
+ }
4540
+ default:
4541
+ return false;
4542
+ }
4543
+ }
4544
+
4545
+ // src/emf-plus-bitmap-decoder.ts
4546
+ var PIXELFORMAT_24BPP_RGB = 137224;
4547
+ var PIXELFORMAT_32BPP_RGB = 139273;
4548
+ var PIXELFORMAT_32BPP_ARGB = 2498570;
4549
+ var PIXELFORMAT_32BPP_PARGB = 925707;
4550
+ function decodeEmfPlusBitmapPixels(view, pixelStart, width, height, stride, pixelFormat) {
4551
+ const absStride = Math.abs(stride);
4552
+ const topDown = stride > 0;
4553
+ const rowBytes = width * 4;
4554
+ const bmpRowStride = rowBytes + 3 & -4;
4555
+ const pixelDataSize = bmpRowStride * height;
4556
+ const bmpData = new Uint8Array(pixelDataSize);
4557
+ for (let y = 0; y < height; y++) {
4558
+ const srcRow = topDown ? y : height - 1 - y;
4559
+ const rowOff = pixelStart + srcRow * absStride;
4560
+ const dstRow = (height - 1 - y) * bmpRowStride;
4561
+ switch (pixelFormat) {
4562
+ case PIXELFORMAT_32BPP_ARGB:
4563
+ case PIXELFORMAT_32BPP_PARGB: {
4564
+ for (let x = 0; x < width; x++) {
4565
+ const off = rowOff + x * 4;
4566
+ if (off + 3 >= view.byteLength) {
4567
+ break;
4568
+ }
4569
+ let b = view.getUint8(off);
4570
+ let g = view.getUint8(off + 1);
4571
+ let r = view.getUint8(off + 2);
4572
+ const a = view.getUint8(off + 3);
4573
+ if (pixelFormat === PIXELFORMAT_32BPP_PARGB && a > 0 && a < 255) {
4574
+ r = Math.min(255, Math.round(r * 255 / a));
4575
+ g = Math.min(255, Math.round(g * 255 / a));
4576
+ b = Math.min(255, Math.round(b * 255 / a));
4577
+ }
4578
+ const di = dstRow + x * 4;
4579
+ bmpData[di] = b;
4580
+ bmpData[di + 1] = g;
4581
+ bmpData[di + 2] = r;
4582
+ bmpData[di + 3] = a;
4583
+ }
4584
+ break;
4585
+ }
4586
+ case PIXELFORMAT_32BPP_RGB: {
4587
+ for (let x = 0; x < width; x++) {
4588
+ const off = rowOff + x * 4;
4589
+ if (off + 3 >= view.byteLength) {
4590
+ break;
4591
+ }
4592
+ const di = dstRow + x * 4;
4593
+ bmpData[di] = view.getUint8(off);
4594
+ bmpData[di + 1] = view.getUint8(off + 1);
4595
+ bmpData[di + 2] = view.getUint8(off + 2);
4596
+ bmpData[di + 3] = 255;
4597
+ }
4598
+ break;
4599
+ }
4600
+ case PIXELFORMAT_24BPP_RGB: {
4601
+ for (let x = 0; x < width; x++) {
4602
+ const off = rowOff + x * 3;
4603
+ if (off + 2 >= view.byteLength) {
4604
+ break;
4605
+ }
4606
+ const di = dstRow + x * 4;
4607
+ bmpData[di] = view.getUint8(off);
4608
+ bmpData[di + 1] = view.getUint8(off + 1);
4609
+ bmpData[di + 2] = view.getUint8(off + 2);
4610
+ bmpData[di + 3] = 255;
4611
+ }
4612
+ break;
4613
+ }
4614
+ default:
4615
+ return null;
4105
4616
  }
4106
- case EMFPLUS_DRAWLINES: {
4107
- if (recDataSize >= 4) {
4108
- const penId = recFlags & 255;
4109
- const pen = objectTable.get(penId);
4110
- const count = view.getUint32(dataOff, true);
4111
- const compressed = (recFlags & 16384) !== 0;
4112
- const ptSize = compressed ? 4 : 8;
4113
- if (pen && pen.kind === "plus-pen") {
4114
- applyEmfPlusPen(ctx, pen);
4115
- }
4116
- applyPlusWorldTransform(rCtx);
4117
- ctx.beginPath();
4118
- let pOff = dataOff + 4;
4119
- for (let i = 0; i < count && pOff + ptSize <= dataOff + recDataSize; i++) {
4120
- const pt = readPointFromView(view, pOff, compressed);
4121
- if (i === 0) {
4122
- ctx.moveTo(pt.x, pt.y);
4123
- } else {
4124
- ctx.lineTo(pt.x, pt.y);
4617
+ }
4618
+ return wrapBmpFile(bmpData, bmpRowStride, width, height, pixelDataSize);
4619
+ }
4620
+ function decodeEmfPlusBitmapPixelsToRgba(view, pixelStart, width, height, stride, pixelFormat) {
4621
+ const absStride = Math.abs(stride);
4622
+ const topDown = stride > 0;
4623
+ const rgba = new Uint8ClampedArray(width * height * 4);
4624
+ for (let y = 0; y < height; y++) {
4625
+ const srcRow = topDown ? y : height - 1 - y;
4626
+ const rowOff = pixelStart + srcRow * absStride;
4627
+ const dstRow = y * width * 4;
4628
+ switch (pixelFormat) {
4629
+ case PIXELFORMAT_32BPP_ARGB:
4630
+ case PIXELFORMAT_32BPP_PARGB: {
4631
+ for (let x = 0; x < width; x++) {
4632
+ const off = rowOff + x * 4;
4633
+ if (off + 3 >= view.byteLength) {
4634
+ break;
4125
4635
  }
4126
- pOff += ptSize;
4636
+ let b = view.getUint8(off);
4637
+ let g = view.getUint8(off + 1);
4638
+ let r = view.getUint8(off + 2);
4639
+ const a = view.getUint8(off + 3);
4640
+ if (pixelFormat === PIXELFORMAT_32BPP_PARGB && a > 0 && a < 255) {
4641
+ r = Math.min(255, Math.round(r * 255 / a));
4642
+ g = Math.min(255, Math.round(g * 255 / a));
4643
+ b = Math.min(255, Math.round(b * 255 / a));
4644
+ }
4645
+ const di = dstRow + x * 4;
4646
+ rgba[di] = r;
4647
+ rgba[di + 1] = g;
4648
+ rgba[di + 2] = b;
4649
+ rgba[di + 3] = a;
4127
4650
  }
4128
- if (recFlags & 8192) {
4129
- ctx.closePath();
4651
+ break;
4652
+ }
4653
+ case PIXELFORMAT_32BPP_RGB: {
4654
+ for (let x = 0; x < width; x++) {
4655
+ const off = rowOff + x * 4;
4656
+ if (off + 3 >= view.byteLength) {
4657
+ break;
4658
+ }
4659
+ const di = dstRow + x * 4;
4660
+ rgba[di] = view.getUint8(off + 2);
4661
+ rgba[di + 1] = view.getUint8(off + 1);
4662
+ rgba[di + 2] = view.getUint8(off);
4663
+ rgba[di + 3] = 255;
4130
4664
  }
4131
- ctx.stroke();
4665
+ break;
4132
4666
  }
4133
- return true;
4134
- }
4135
- case EMFPLUS_FILLPOLYGON: {
4136
- if (recDataSize >= 8) {
4137
- const brushVal = view.getUint32(dataOff, true);
4138
- const count = view.getUint32(dataOff + 4, true);
4139
- const compressed = (recFlags & 16384) !== 0;
4140
- const ptSize = compressed ? 4 : 8;
4141
- ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
4142
- applyPlusWorldTransform(rCtx);
4143
- ctx.beginPath();
4144
- let pOff = dataOff + 8;
4145
- for (let i = 0; i < count && pOff + ptSize <= dataOff + recDataSize; i++) {
4146
- const pt = readPointFromView(view, pOff, compressed);
4147
- if (i === 0) {
4148
- ctx.moveTo(pt.x, pt.y);
4149
- } else {
4150
- ctx.lineTo(pt.x, pt.y);
4667
+ case PIXELFORMAT_24BPP_RGB: {
4668
+ for (let x = 0; x < width; x++) {
4669
+ const off = rowOff + x * 3;
4670
+ if (off + 2 >= view.byteLength) {
4671
+ break;
4151
4672
  }
4152
- pOff += ptSize;
4673
+ const di = dstRow + x * 4;
4674
+ rgba[di] = view.getUint8(off + 2);
4675
+ rgba[di + 1] = view.getUint8(off + 1);
4676
+ rgba[di + 2] = view.getUint8(off);
4677
+ rgba[di + 3] = 255;
4153
4678
  }
4154
- ctx.closePath();
4155
- ctx.fill();
4679
+ break;
4156
4680
  }
4157
- return true;
4681
+ default:
4682
+ return null;
4158
4683
  }
4159
- default:
4160
- return false;
4161
4684
  }
4685
+ return rgba;
4686
+ }
4687
+ function wrapBmpFile(bmpData, bmpRowStride, width, height, pixelDataSize) {
4688
+ const fileHeaderSize = 14;
4689
+ const dibHeaderSize = 108;
4690
+ const fileSize = fileHeaderSize + dibHeaderSize + pixelDataSize;
4691
+ const bmpFile = new ArrayBuffer(fileSize);
4692
+ const bmpView = new DataView(bmpFile);
4693
+ const bmpBytes = new Uint8Array(bmpFile);
4694
+ bmpView.setUint8(0, 66);
4695
+ bmpView.setUint8(1, 77);
4696
+ bmpView.setUint32(2, fileSize, true);
4697
+ bmpView.setUint32(6, 0, true);
4698
+ bmpView.setUint32(10, fileHeaderSize + dibHeaderSize, true);
4699
+ bmpView.setUint32(14, dibHeaderSize, true);
4700
+ bmpView.setInt32(18, width, true);
4701
+ bmpView.setInt32(22, height, true);
4702
+ bmpView.setUint16(26, 1, true);
4703
+ bmpView.setUint16(28, 32, true);
4704
+ bmpView.setUint32(30, 3, true);
4705
+ bmpView.setUint32(34, pixelDataSize, true);
4706
+ bmpView.setInt32(38, 2835, true);
4707
+ bmpView.setInt32(42, 2835, true);
4708
+ bmpView.setUint32(46, 0, true);
4709
+ bmpView.setUint32(50, 0, true);
4710
+ bmpView.setUint32(54, 16711680, true);
4711
+ bmpView.setUint32(58, 65280, true);
4712
+ bmpView.setUint32(62, 255, true);
4713
+ bmpView.setUint32(66, 4278190080, true);
4714
+ bmpView.setUint32(70, 1934772034, true);
4715
+ bmpBytes.set(bmpData, fileHeaderSize + dibHeaderSize);
4716
+ return bmpFile;
4162
4717
  }
4163
4718
 
4164
4719
  // src/emf-plus-brush-parser.ts
@@ -4314,6 +4869,62 @@ function parseLinearGradient(view, b, end) {
4314
4869
  }
4315
4870
  };
4316
4871
  }
4872
+ function decodeTextureImage(view, off, end) {
4873
+ if (off + 28 > end) {
4874
+ return null;
4875
+ }
4876
+ const imageDataType = view.getUint32(off + 4, true);
4877
+ if (imageDataType !== 1) {
4878
+ return null;
4879
+ }
4880
+ const width = view.getInt32(off + 8, true);
4881
+ const height = view.getInt32(off + 12, true);
4882
+ const stride = view.getInt32(off + 16, true);
4883
+ const pixelFormat = view.getUint32(off + 20, true);
4884
+ const pixelStart = off + 28;
4885
+ const absStride = Math.abs(stride);
4886
+ if (width <= 0 || height <= 0 || width > 8192 || height > 8192 || pixelStart + absStride * height > end) {
4887
+ return null;
4888
+ }
4889
+ const rgba = decodeEmfPlusBitmapPixelsToRgba(view, pixelStart, width, height, stride, pixelFormat);
4890
+ return rgba ? { width, height, rgba } : null;
4891
+ }
4892
+ function parseTextureBrush(view, b, end) {
4893
+ if (b + 8 > end) {
4894
+ return null;
4895
+ }
4896
+ const flags = view.getUint32(b, true);
4897
+ const wrapMode = readWrapMode(view.getUint32(b + 4, true));
4898
+ let o = b + 8;
4899
+ let transform = null;
4900
+ if (flags & BRUSH_DATA_TRANSFORM && o + 24 <= end) {
4901
+ transform = readTransform(view, o);
4902
+ o += 24;
4903
+ }
4904
+ const image = decodeTextureImage(view, o, end);
4905
+ if (!image) {
4906
+ return null;
4907
+ }
4908
+ const texture = {
4909
+ width: image.width,
4910
+ height: image.height,
4911
+ rgba: image.rgba,
4912
+ wrapMode,
4913
+ transform
4914
+ };
4915
+ let r = 0;
4916
+ let g = 0;
4917
+ let bl = 0;
4918
+ const n = image.width * image.height;
4919
+ for (let i = 0; i < n; i++) {
4920
+ r += image.rgba[i * 4];
4921
+ g += image.rgba[i * 4 + 1];
4922
+ bl += image.rgba[i * 4 + 2];
4923
+ }
4924
+ const avg = n > 0 ? `rgba(${Math.round(r / n)},${Math.round(g / n)},${Math.round(bl / n)},1)` : "rgba(128,128,128,1)";
4925
+ emfLog(`parseEmfPlusBrushObject: texture fill ${image.width}x${image.height}, wrapMode=${wrapMode}`);
4926
+ return { kind: "plus-brush", color: avg, texture };
4927
+ }
4317
4928
  function parsePathGradient(view, b, end) {
4318
4929
  if (b + 24 > end) {
4319
4930
  return null;
@@ -4521,6 +5132,10 @@ function parseEmfPlusBrushObject(view, dataOff, recDataSize) {
4521
5132
  return { kind: "plus-brush", color: argbToRgba(view.getUint32(b + 4, true)) };
4522
5133
  }
4523
5134
  return { kind: "plus-brush", color: "rgba(0,0,0,1)" };
5135
+ case EMFPLUS_BRUSHTYPE_TEXTUREFILL: {
5136
+ const brush = parseTextureBrush(view, b, end);
5137
+ return brush ?? { kind: "plus-brush", color: "rgba(0,0,0,1)" };
5138
+ }
4524
5139
  case EMFPLUS_BRUSHTYPE_LINEARGRADIENT: {
4525
5140
  const brush = parseLinearGradient(view, b, end);
4526
5141
  return brush ?? { kind: "plus-brush", color: "rgba(0,0,0,1)" };
@@ -4534,110 +5149,6 @@ function parseEmfPlusBrushObject(view, dataOff, recDataSize) {
4534
5149
  }
4535
5150
  }
4536
5151
 
4537
- // src/emf-plus-bitmap-decoder.ts
4538
- var PIXELFORMAT_24BPP_RGB = 137224;
4539
- var PIXELFORMAT_32BPP_RGB = 139273;
4540
- var PIXELFORMAT_32BPP_ARGB = 2498570;
4541
- var PIXELFORMAT_32BPP_PARGB = 925707;
4542
- function decodeEmfPlusBitmapPixels(view, pixelStart, width, height, stride, pixelFormat) {
4543
- const absStride = Math.abs(stride);
4544
- const topDown = stride > 0;
4545
- const rowBytes = width * 4;
4546
- const bmpRowStride = rowBytes + 3 & -4;
4547
- const pixelDataSize = bmpRowStride * height;
4548
- const bmpData = new Uint8Array(pixelDataSize);
4549
- for (let y = 0; y < height; y++) {
4550
- const srcRow = topDown ? y : height - 1 - y;
4551
- const rowOff = pixelStart + srcRow * absStride;
4552
- const dstRow = (height - 1 - y) * bmpRowStride;
4553
- switch (pixelFormat) {
4554
- case PIXELFORMAT_32BPP_ARGB:
4555
- case PIXELFORMAT_32BPP_PARGB: {
4556
- for (let x = 0; x < width; x++) {
4557
- const off = rowOff + x * 4;
4558
- if (off + 3 >= view.byteLength) {
4559
- break;
4560
- }
4561
- let b = view.getUint8(off);
4562
- let g = view.getUint8(off + 1);
4563
- let r = view.getUint8(off + 2);
4564
- const a = view.getUint8(off + 3);
4565
- if (pixelFormat === PIXELFORMAT_32BPP_PARGB && a > 0 && a < 255) {
4566
- r = Math.min(255, Math.round(r * 255 / a));
4567
- g = Math.min(255, Math.round(g * 255 / a));
4568
- b = Math.min(255, Math.round(b * 255 / a));
4569
- }
4570
- const di = dstRow + x * 4;
4571
- bmpData[di] = b;
4572
- bmpData[di + 1] = g;
4573
- bmpData[di + 2] = r;
4574
- bmpData[di + 3] = a;
4575
- }
4576
- break;
4577
- }
4578
- case PIXELFORMAT_32BPP_RGB: {
4579
- for (let x = 0; x < width; x++) {
4580
- const off = rowOff + x * 4;
4581
- if (off + 3 >= view.byteLength) {
4582
- break;
4583
- }
4584
- const di = dstRow + x * 4;
4585
- bmpData[di] = view.getUint8(off);
4586
- bmpData[di + 1] = view.getUint8(off + 1);
4587
- bmpData[di + 2] = view.getUint8(off + 2);
4588
- bmpData[di + 3] = 255;
4589
- }
4590
- break;
4591
- }
4592
- case PIXELFORMAT_24BPP_RGB: {
4593
- for (let x = 0; x < width; x++) {
4594
- const off = rowOff + x * 3;
4595
- if (off + 2 >= view.byteLength) {
4596
- break;
4597
- }
4598
- const di = dstRow + x * 4;
4599
- bmpData[di] = view.getUint8(off);
4600
- bmpData[di + 1] = view.getUint8(off + 1);
4601
- bmpData[di + 2] = view.getUint8(off + 2);
4602
- bmpData[di + 3] = 255;
4603
- }
4604
- break;
4605
- }
4606
- default:
4607
- return null;
4608
- }
4609
- }
4610
- const fileHeaderSize = 14;
4611
- const dibHeaderSize = 108;
4612
- const fileSize = fileHeaderSize + dibHeaderSize + pixelDataSize;
4613
- const bmpFile = new ArrayBuffer(fileSize);
4614
- const bmpView = new DataView(bmpFile);
4615
- const bmpBytes = new Uint8Array(bmpFile);
4616
- bmpView.setUint8(0, 66);
4617
- bmpView.setUint8(1, 77);
4618
- bmpView.setUint32(2, fileSize, true);
4619
- bmpView.setUint32(6, 0, true);
4620
- bmpView.setUint32(10, fileHeaderSize + dibHeaderSize, true);
4621
- bmpView.setUint32(14, dibHeaderSize, true);
4622
- bmpView.setInt32(18, width, true);
4623
- bmpView.setInt32(22, height, true);
4624
- bmpView.setUint16(26, 1, true);
4625
- bmpView.setUint16(28, 32, true);
4626
- bmpView.setUint32(30, 3, true);
4627
- bmpView.setUint32(34, pixelDataSize, true);
4628
- bmpView.setInt32(38, 2835, true);
4629
- bmpView.setInt32(42, 2835, true);
4630
- bmpView.setUint32(46, 0, true);
4631
- bmpView.setUint32(50, 0, true);
4632
- bmpView.setUint32(54, 16711680, true);
4633
- bmpView.setUint32(58, 65280, true);
4634
- bmpView.setUint32(62, 255, true);
4635
- bmpView.setUint32(66, 4278190080, true);
4636
- bmpView.setUint32(70, 1934772034, true);
4637
- bmpBytes.set(bmpData, fileHeaderSize + dibHeaderSize);
4638
- return bmpFile;
4639
- }
4640
-
4641
5152
  // src/emf-plus-object-complex.ts
4642
5153
  function parseEmfPlusPenObject(view, dataOff, recDataSize) {
4643
5154
  if (recDataSize < 20) {