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