emf-converter 3.0.2 → 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 +13 -6
  2. package/dist/index.js +2691 -1106
  3. package/dist/index.mjs +2691 -1106
  4. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -50,6 +50,469 @@ function invertCssColor(color) {
50
50
  return color;
51
51
  }
52
52
 
53
+ // src/emf-dib-rle-decoder.ts
54
+ function decodeRleBitmap(view, bitsOffset, bitsSize, width, height, _topDown, isRle4, colorTable, out, setPixel) {
55
+ let x = 0;
56
+ let y = height - 1;
57
+ let off = bitsOffset;
58
+ const endOff = bitsOffset + bitsSize;
59
+ while (off + 1 < endOff && y >= 0) {
60
+ const first = view.getUint8(off);
61
+ const second = view.getUint8(off + 1);
62
+ off += 2;
63
+ if (first === 0) {
64
+ if (second === 0) {
65
+ x = 0;
66
+ y--;
67
+ } else if (second === 1) {
68
+ break;
69
+ } else if (second === 2) {
70
+ if (off + 1 >= endOff) {
71
+ break;
72
+ }
73
+ x += view.getUint8(off);
74
+ y -= view.getUint8(off + 1);
75
+ off += 2;
76
+ } else {
77
+ const count = second;
78
+ if (!isRle4) {
79
+ for (let i = 0; i < count && off < endOff && x < width; i++) {
80
+ const idx = view.getUint8(off++);
81
+ if (idx < colorTable.length) {
82
+ setPixel(
83
+ x,
84
+ height - 1 - y,
85
+ colorTable[idx][0],
86
+ colorTable[idx][1],
87
+ colorTable[idx][2],
88
+ 255
89
+ );
90
+ }
91
+ x++;
92
+ }
93
+ if (count & 1) {
94
+ off++;
95
+ }
96
+ } else {
97
+ const bytes = Math.ceil(count / 2);
98
+ let pi = 0;
99
+ for (let i = 0; i < bytes && off < endOff; i++) {
100
+ const byte = view.getUint8(off++);
101
+ for (let nibble = 0; nibble < 2 && pi < count; nibble++) {
102
+ const idx = nibble === 0 ? byte >> 4 & 15 : byte & 15;
103
+ if (idx < colorTable.length && x < width) {
104
+ setPixel(
105
+ x,
106
+ height - 1 - y,
107
+ colorTable[idx][0],
108
+ colorTable[idx][1],
109
+ colorTable[idx][2],
110
+ 255
111
+ );
112
+ }
113
+ x++;
114
+ pi++;
115
+ }
116
+ }
117
+ if (bytes & 1) {
118
+ off++;
119
+ }
120
+ }
121
+ }
122
+ } else if (!isRle4) {
123
+ const idx = second;
124
+ const c = idx < colorTable.length ? colorTable[idx] : [0, 0, 0];
125
+ for (let i = 0; i < first && x < width; i++) {
126
+ setPixel(x, height - 1 - y, c[0], c[1], c[2], 255);
127
+ x++;
128
+ }
129
+ } else {
130
+ const hi = second >> 4 & 15;
131
+ const lo = second & 15;
132
+ for (let i = 0; i < first && x < width; i++) {
133
+ const idx = (i & 1) === 0 ? hi : lo;
134
+ if (idx < colorTable.length) {
135
+ setPixel(
136
+ x,
137
+ height - 1 - y,
138
+ colorTable[idx][0],
139
+ colorTable[idx][1],
140
+ colorTable[idx][2],
141
+ 255
142
+ );
143
+ }
144
+ x++;
145
+ }
146
+ }
147
+ }
148
+ return createImageDataCompat(
149
+ new Uint8ClampedArray(out.buffer, out.byteOffset, out.byteLength),
150
+ width,
151
+ height
152
+ );
153
+ }
154
+
155
+ // src/emf-dib-uncompressed.ts
156
+ function countTrailingZeros(v) {
157
+ if (v === 0) {
158
+ return 0;
159
+ }
160
+ let c = 0;
161
+ let val = v;
162
+ while ((val & 1) === 0) {
163
+ val >>>= 1;
164
+ c++;
165
+ }
166
+ return c;
167
+ }
168
+ var BI_BITFIELDS = 3;
169
+ function parseBitfieldMasks(view, bmiOffset, headerSize, compression, bitCount) {
170
+ let rMask = 0, gMask = 0, bMask = 0;
171
+ let rShift = 0, gShift = 0, bShift = 0;
172
+ let rMax = 1, gMax = 1, bMax = 1;
173
+ if (compression === BI_BITFIELDS) {
174
+ const bfOff = bmiOffset + headerSize;
175
+ if (bfOff + 12 > view.byteLength) {
176
+ return null;
177
+ }
178
+ rMask = view.getUint32(bfOff, true);
179
+ gMask = view.getUint32(bfOff + 4, true);
180
+ bMask = view.getUint32(bfOff + 8, true);
181
+ rShift = countTrailingZeros(rMask);
182
+ gShift = countTrailingZeros(gMask);
183
+ bShift = countTrailingZeros(bMask);
184
+ rMax = rMask >>> rShift || 1;
185
+ gMax = gMask >>> gShift || 1;
186
+ bMax = bMask >>> bShift || 1;
187
+ } else if (bitCount === 16) {
188
+ rMask = 31744;
189
+ gMask = 992;
190
+ bMask = 31;
191
+ rShift = 10;
192
+ gShift = 5;
193
+ bShift = 0;
194
+ rMax = 31;
195
+ gMax = 31;
196
+ bMax = 31;
197
+ }
198
+ return { rMask, gMask, bMask, rShift, gShift, bShift, rMax, gMax, bMax };
199
+ }
200
+ function decodeUncompressedRows(view, bitsOffset, width, height, topDown, bitCount, colorTable, masks, out) {
201
+ const rowStride = Math.floor((bitCount * width + 31) / 32) * 4;
202
+ const { rMask, gMask, bMask, rShift, gShift, bShift, rMax, gMax, bMax } = masks;
203
+ for (let y = 0; y < height; y++) {
204
+ const srcY = topDown ? y : height - 1 - y;
205
+ const rowStart = bitsOffset + srcY * rowStride;
206
+ if (rowStart + rowStride > view.byteLength) {
207
+ continue;
208
+ }
209
+ for (let x = 0; x < width; x++) {
210
+ const dstPx = (y * width + x) * 4;
211
+ if (bitCount === 1) {
212
+ const byteIdx = rowStart + (x >> 3);
213
+ const bit = view.getUint8(byteIdx) >> 7 - (x & 7) & 1;
214
+ if (bit < colorTable.length) {
215
+ out[dstPx] = colorTable[bit][0];
216
+ out[dstPx + 1] = colorTable[bit][1];
217
+ out[dstPx + 2] = colorTable[bit][2];
218
+ }
219
+ out[dstPx + 3] = 255;
220
+ } else if (bitCount === 4) {
221
+ const byteIdx = rowStart + (x >> 1);
222
+ const nibble = (x & 1) === 0 ? view.getUint8(byteIdx) >> 4 & 15 : view.getUint8(byteIdx) & 15;
223
+ if (nibble < colorTable.length) {
224
+ out[dstPx] = colorTable[nibble][0];
225
+ out[dstPx + 1] = colorTable[nibble][1];
226
+ out[dstPx + 2] = colorTable[nibble][2];
227
+ }
228
+ out[dstPx + 3] = 255;
229
+ } else if (bitCount === 8) {
230
+ const idx = view.getUint8(rowStart + x);
231
+ if (idx < colorTable.length) {
232
+ out[dstPx] = colorTable[idx][0];
233
+ out[dstPx + 1] = colorTable[idx][1];
234
+ out[dstPx + 2] = colorTable[idx][2];
235
+ }
236
+ out[dstPx + 3] = 255;
237
+ } else if (bitCount === 16) {
238
+ const val = view.getUint16(rowStart + x * 2, true);
239
+ out[dstPx] = Math.round(((val & rMask) >>> rShift) * 255 / rMax);
240
+ out[dstPx + 1] = Math.round(((val & gMask) >>> gShift) * 255 / gMax);
241
+ out[dstPx + 2] = Math.round(((val & bMask) >>> bShift) * 255 / bMax);
242
+ out[dstPx + 3] = 255;
243
+ } else if (bitCount === 24) {
244
+ const srcPx = rowStart + x * 3;
245
+ out[dstPx] = view.getUint8(srcPx + 2);
246
+ out[dstPx + 1] = view.getUint8(srcPx + 1);
247
+ out[dstPx + 2] = view.getUint8(srcPx);
248
+ out[dstPx + 3] = 255;
249
+ } else {
250
+ const srcPx = rowStart + x * 4;
251
+ const bb = view.getUint8(srcPx);
252
+ const gg = view.getUint8(srcPx + 1);
253
+ const rr = view.getUint8(srcPx + 2);
254
+ const aa = view.getUint8(srcPx + 3);
255
+ out[dstPx] = rr;
256
+ out[dstPx + 1] = gg;
257
+ out[dstPx + 2] = bb;
258
+ out[dstPx + 3] = aa === 0 ? 255 : aa;
259
+ }
260
+ }
261
+ }
262
+ }
263
+
264
+ // src/emf-dib-decoder.ts
265
+ function decodeDibToImageData(view, bmiOffset, bitsOffset, bitsSize) {
266
+ if (bmiOffset < 0 || bitsOffset < 0 || bmiOffset + 40 > view.byteLength || bitsOffset + bitsSize > view.byteLength) {
267
+ return null;
268
+ }
269
+ const headerSize = view.getUint32(bmiOffset, true);
270
+ if (headerSize < 40 || bmiOffset + headerSize > view.byteLength) {
271
+ return null;
272
+ }
273
+ const width = view.getInt32(bmiOffset + 4, true);
274
+ const heightRaw = view.getInt32(bmiOffset + 8, true);
275
+ const planes = view.getUint16(bmiOffset + 12, true);
276
+ const bitCount = view.getUint16(bmiOffset + 14, true);
277
+ const compression = view.getUint32(bmiOffset + 16, true);
278
+ if (planes !== 1 || width <= 0 || heightRaw === 0) {
279
+ return null;
280
+ }
281
+ if (width > 8192 || Math.abs(heightRaw) > 8192) {
282
+ return null;
283
+ }
284
+ const BI_RGB = 0;
285
+ const BI_RLE8 = 1;
286
+ const BI_RLE4 = 2;
287
+ const BI_BITFIELDS2 = 3;
288
+ if (bitCount !== 1 && bitCount !== 4 && bitCount !== 8 && bitCount !== 16 && bitCount !== 24 && bitCount !== 32) {
289
+ return null;
290
+ }
291
+ if (compression === BI_RLE8 && bitCount !== 8) {
292
+ return null;
293
+ }
294
+ if (compression === BI_RLE4 && bitCount !== 4) {
295
+ return null;
296
+ }
297
+ if (compression === BI_BITFIELDS2 && bitCount !== 16 && bitCount !== 32) {
298
+ return null;
299
+ }
300
+ if (compression !== BI_RGB && compression !== BI_RLE8 && compression !== BI_RLE4 && compression !== BI_BITFIELDS2) {
301
+ return null;
302
+ }
303
+ const height = Math.abs(heightRaw);
304
+ const topDown = heightRaw < 0;
305
+ const colorTable = [];
306
+ if (bitCount <= 8) {
307
+ const maxColors = 1 << bitCount;
308
+ const colorsUsed = view.getUint32(bmiOffset + 32, true) || maxColors;
309
+ const numColors = Math.min(colorsUsed, maxColors);
310
+ const ctOffset = bmiOffset + headerSize;
311
+ if (ctOffset + numColors * 4 > view.byteLength) {
312
+ return null;
313
+ }
314
+ for (let i = 0; i < numColors; i++) {
315
+ const b = view.getUint8(ctOffset + i * 4);
316
+ const g = view.getUint8(ctOffset + i * 4 + 1);
317
+ const r = view.getUint8(ctOffset + i * 4 + 2);
318
+ colorTable.push([r, g, b]);
319
+ }
320
+ }
321
+ const masks = parseBitfieldMasks(view, bmiOffset, headerSize, compression, bitCount);
322
+ if (!masks) {
323
+ return null;
324
+ }
325
+ const out = new Uint8ClampedArray(width * height * 4);
326
+ if (compression === BI_RLE8 || compression === BI_RLE4) {
327
+ const setPixel = (x, y, r, g, b, a) => {
328
+ const dstPx = (y * width + x) * 4;
329
+ out[dstPx] = r;
330
+ out[dstPx + 1] = g;
331
+ out[dstPx + 2] = b;
332
+ out[dstPx + 3] = a;
333
+ };
334
+ return decodeRleBitmap(
335
+ view,
336
+ bitsOffset,
337
+ bitsSize,
338
+ width,
339
+ height,
340
+ topDown,
341
+ compression === BI_RLE4,
342
+ colorTable,
343
+ out,
344
+ setPixel
345
+ );
346
+ }
347
+ decodeUncompressedRows(
348
+ view,
349
+ bitsOffset,
350
+ width,
351
+ height,
352
+ topDown,
353
+ bitCount,
354
+ colorTable,
355
+ masks,
356
+ out
357
+ );
358
+ return createImageDataCompat(out, width, height);
359
+ }
360
+
361
+ // src/emf-gdi-brush-pattern.ts
362
+ var HATCH_ROWS = [
363
+ [0, 0, 0, 255, 0, 0, 0, 0],
364
+ // HS_HORIZONTAL
365
+ [8, 8, 8, 8, 8, 8, 8, 8],
366
+ // HS_VERTICAL
367
+ [128, 64, 32, 16, 8, 4, 2, 1],
368
+ // HS_FDIAGONAL
369
+ [1, 2, 4, 8, 16, 32, 64, 128],
370
+ // HS_BDIAGONAL
371
+ [8, 8, 8, 255, 8, 8, 8, 8],
372
+ // HS_CROSS
373
+ [129, 66, 36, 24, 24, 36, 66, 129]
374
+ // HS_DIAGCROSS
375
+ ];
376
+ function hatchBit(hatch, x, y) {
377
+ const rows = HATCH_ROWS[hatch] ?? HATCH_ROWS[0];
378
+ return (rows[y & 7] >> 7 - (x & 7) & 1) === 1;
379
+ }
380
+ function cssHexToRgb(color) {
381
+ const m = /^#([0-9a-f]{6}|[0-9a-f]{3})$/i.exec(color.trim());
382
+ if (!m) {
383
+ return 0;
384
+ }
385
+ const hex = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];
386
+ return parseInt(hex, 16);
387
+ }
388
+ function decodeMonoBits(view, bmiOffset, bitsOffset) {
389
+ if (bmiOffset + 16 > view.byteLength) {
390
+ return null;
391
+ }
392
+ const width = view.getInt32(bmiOffset + 4, true);
393
+ const heightRaw = view.getInt32(bmiOffset + 8, true);
394
+ const bitCount = view.getUint16(bmiOffset + 14, true);
395
+ const height = Math.abs(heightRaw);
396
+ if (bitCount !== 1 || width <= 0 || height === 0 || width > 256 || height > 256) {
397
+ return null;
398
+ }
399
+ const stride = (width + 31 >> 5) * 4;
400
+ if (bitsOffset + stride * height > view.byteLength) {
401
+ return null;
402
+ }
403
+ const bits = new Uint8Array(width * height);
404
+ for (let row = 0; row < height; row++) {
405
+ const y = heightRaw > 0 ? height - 1 - row : row;
406
+ const rowOff = bitsOffset + row * stride;
407
+ for (let x = 0; x < width; x++) {
408
+ bits[y * width + x] = view.getUint8(rowOff + (x >> 3)) >> 7 - (x & 7) & 1;
409
+ }
410
+ }
411
+ return { width, height, bits };
412
+ }
413
+ function parsePatternBrush(view, offset, dataOff, mono) {
414
+ if (dataOff + 24 > view.byteLength) {
415
+ return null;
416
+ }
417
+ const offBmi = view.getUint32(dataOff + 8, true);
418
+ const offBits = view.getUint32(dataOff + 16, true);
419
+ const cbBits = view.getUint32(dataOff + 20, true);
420
+ const bmi = offset + offBmi;
421
+ const bitsAt = offset + offBits;
422
+ const bitCount = bmi + 16 <= view.byteLength ? view.getUint16(bmi + 14, true) : 0;
423
+ if (mono || bitCount === 1) {
424
+ const decoded = decodeMonoBits(view, bmi, bitsAt);
425
+ if (decoded && mono) {
426
+ return { kind: "mono", ...decoded };
427
+ }
428
+ }
429
+ const image = decodeDibToImageData(view, bmi, bitsAt, cbBits);
430
+ if (!image) {
431
+ return null;
432
+ }
433
+ const rgb = new Uint32Array(image.width * image.height);
434
+ const d = image.data;
435
+ for (let i = 0; i < rgb.length; i++) {
436
+ rgb[i] = d[i * 4] << 16 | d[i * 4 + 1] << 8 | d[i * 4 + 2];
437
+ }
438
+ return { kind: "bitmap", width: image.width, height: image.height, rgb };
439
+ }
440
+ function realizeBrush(state) {
441
+ if (state.brushStyle === 1) {
442
+ return { kind: "none" };
443
+ }
444
+ const pattern = state.brushPattern;
445
+ if (!pattern) {
446
+ return { kind: "solid", rgb: cssHexToRgb(state.brushColor) };
447
+ }
448
+ if (pattern.kind === "bitmap") {
449
+ return { kind: "tile", width: pattern.width, height: pattern.height, rgb: pattern.rgb };
450
+ }
451
+ const bk = cssHexToRgb(state.bkColor);
452
+ if (pattern.kind === "hatch") {
453
+ const fg = cssHexToRgb(state.brushColor);
454
+ const rgb2 = new Uint32Array(64);
455
+ for (let y = 0; y < 8; y++) {
456
+ for (let x = 0; x < 8; x++) {
457
+ rgb2[y * 8 + x] = hatchBit(pattern.hatch, x, y) ? fg : bk;
458
+ }
459
+ }
460
+ return { kind: "tile", width: 8, height: 8, rgb: rgb2 };
461
+ }
462
+ const text = cssHexToRgb(state.textColor);
463
+ const rgb = new Uint32Array(pattern.width * pattern.height);
464
+ for (let i = 0; i < rgb.length; i++) {
465
+ rgb[i] = pattern.bits[i] ? bk : text;
466
+ }
467
+ return { kind: "tile", width: pattern.width, height: pattern.height, rgb };
468
+ }
469
+ function sampleTile(tile, devX, devY, orgX, orgY) {
470
+ const tx = ((devX - orgX) % tile.width + tile.width) % tile.width;
471
+ const ty = ((devY - orgY) % tile.height + tile.height) % tile.height;
472
+ return tile.rgb[ty * tile.width + tx];
473
+ }
474
+
475
+ // src/emf-gdi-text-layout.ts
476
+ function cumulativeGlyphOffsets(dx) {
477
+ const offsets = [];
478
+ let acc = 0;
479
+ for (let i = 0; i < dx.length; i++) {
480
+ offsets.push(acc);
481
+ acc += dx[i];
482
+ }
483
+ return offsets;
484
+ }
485
+ function totalGlyphAdvance(dx) {
486
+ let sum = 0;
487
+ for (const d of dx) {
488
+ sum += d;
489
+ }
490
+ return sum;
491
+ }
492
+ function alignmentStartOffset(totalWidth, align) {
493
+ if (align === "center") {
494
+ return -totalWidth / 2;
495
+ }
496
+ if (align === "right") {
497
+ return -totalWidth;
498
+ }
499
+ return 0;
500
+ }
501
+ var DEG_TO_RAD = Math.PI / 180;
502
+ function escapementToCanvasRadians(tenthsOfDegree) {
503
+ return -(tenthsOfDegree / 10) * DEG_TO_RAD;
504
+ }
505
+ var CELL_TO_CHAR_HEIGHT_RATIO = 1.15;
506
+ function resolveFontPixelHeight(lfHeight) {
507
+ if (lfHeight === 0) {
508
+ return 0;
509
+ }
510
+ if (lfHeight < 0) {
511
+ return -lfHeight;
512
+ }
513
+ return lfHeight / CELL_TO_CHAR_HEIGHT_RATIO;
514
+ }
515
+
53
516
  // src/emf-constants.ts
54
517
  var EMR_HEADER = 1;
55
518
  var EMR_POLYBEZIER = 2;
@@ -64,6 +527,9 @@ var EMR_SETWINDOWORGEX = 10;
64
527
  var EMR_SETVIEWPORTEXTEX = 11;
65
528
  var EMR_SETVIEWPORTORGEX = 12;
66
529
  var EMR_SETBRUSHORGEX = 13;
530
+ var EMR_CREATEMONOBRUSH = 93;
531
+ var EMR_CREATEDIBPATTERNBRUSHPT = 94;
532
+ var EMR_STRETCHBLT = 77;
67
533
  var EMR_EOF = 14;
68
534
  var EMR_SETPIXELV = 15;
69
535
  var EMR_SETMAPMODE = 17;
@@ -191,6 +657,7 @@ var EMFPLUS_OBJECTTYPE_STRINGFORMAT = 7;
191
657
  var EMFPLUS_OBJECTTYPE_REGION = 8;
192
658
  var EMFPLUS_BRUSHTYPE_SOLID = 0;
193
659
  var EMFPLUS_BRUSHTYPE_HATCHFILL = 1;
660
+ var EMFPLUS_BRUSHTYPE_TEXTUREFILL = 2;
194
661
  var EMFPLUS_BRUSHTYPE_PATHGRADIENT = 3;
195
662
  var EMFPLUS_BRUSHTYPE_LINEARGRADIENT = 4;
196
663
  var META_EOF = 0;
@@ -441,7 +908,8 @@ function applyPen(ctx, state) {
441
908
  function applyBrush(ctx, state) {
442
909
  const paint = rop2Paint(state.rop2);
443
910
  ctx.globalCompositeOperation = paint.gco;
444
- if (state.brushStyle === 1) {
911
+ const realized = realizeBrush(state);
912
+ if (realized.kind === "none") {
445
913
  ctx.fillStyle = "rgba(0,0,0,0)";
446
914
  return;
447
915
  }
@@ -468,7 +936,7 @@ function mapFontFamily(face, map) {
468
936
  return resolved;
469
937
  }
470
938
  function fontSizePx(state, scale = 1) {
471
- return Math.max(Math.abs(state.fontHeight) * Math.abs(scale || 1), 8);
939
+ return Math.max(resolveFontPixelHeight(state.fontHeight) * Math.abs(scale || 1), 8);
472
940
  }
473
941
  function applyFont(ctx, state, scale = 1) {
474
942
  const italic = state.fontItalic ? "italic " : "";
@@ -620,6 +1088,28 @@ function canvasDrawImage(ctx, image, dx, dy, dw, dh) {
620
1088
  const draw = ctx.drawImage;
621
1089
  draw.call(ctx, image, dx, dy, dw, dh);
622
1090
  }
1091
+ function canvasGetImageData(ctx, x, y, w, h) {
1092
+ const get = ctx.getImageData;
1093
+ return get.call(ctx, x, y, w, h);
1094
+ }
1095
+ function canvasPutImageData(ctx, data, x, y) {
1096
+ const put = ctx.putImageData;
1097
+ put.call(ctx, data, x, y);
1098
+ }
1099
+ function canvasCreatePattern(ctx, image, repetition) {
1100
+ const create = ctx.createPattern;
1101
+ return create.call(ctx, image, repetition);
1102
+ }
1103
+ function createImageDataCompat(data, width, height) {
1104
+ if (typeof ImageData !== "undefined") {
1105
+ return new ImageData(data, width, height);
1106
+ }
1107
+ if (nodeCanvasModule) {
1108
+ const Ctor = nodeCanvasModule.ImageData;
1109
+ return new Ctor(data, width, height);
1110
+ }
1111
+ throw new ReferenceError("ImageData is not defined");
1112
+ }
623
1113
 
624
1114
  // src/emf-header-parser.ts
625
1115
  function parseEmfHeader(view) {
@@ -739,854 +1229,1267 @@ function gmh(r, h) {
739
1229
  function activateGdiMappingMode(r) {
740
1230
  r.useMappingMode = true;
741
1231
  }
742
-
743
- // src/emf-gdi-draw-shapes.ts
744
- function handleSetPixelV(rCtx, dataOff, recSize) {
745
- const { ctx, view } = rCtx;
746
- if (recSize >= 20) {
747
- const x = view.getInt32(dataOff, true);
748
- const y = view.getInt32(dataOff + 4, true);
749
- const color = readColorRef(view, dataOff + 8);
750
- ctx.fillStyle = color;
751
- ctx.fillRect(gmx(rCtx, x), gmy(rCtx, y), 1, 1);
752
- }
753
- return true;
754
- }
755
- function handleMoveToEx(rCtx, dataOff, recSize) {
756
- const { ctx, view, state, inPath } = rCtx;
757
- if (recSize >= 16) {
758
- state.curX = view.getInt32(dataOff, true);
759
- state.curY = view.getInt32(dataOff + 4, true);
760
- if (inPath) {
761
- ctx.moveTo(gmx(rCtx, state.curX), gmy(rCtx, state.curY));
762
- }
763
- }
764
- return true;
765
- }
766
- function handleLineTo(rCtx, dataOff, recSize) {
767
- const { ctx, view, state, inPath } = rCtx;
768
- if (recSize >= 16) {
769
- const lx = view.getInt32(dataOff, true);
770
- const ly = view.getInt32(dataOff + 4, true);
771
- if (inPath) {
772
- ctx.lineTo(gmx(rCtx, lx), gmy(rCtx, ly));
773
- } else {
774
- applyPen(ctx, state);
775
- ctx.beginPath();
776
- ctx.moveTo(gmx(rCtx, state.curX), gmy(rCtx, state.curY));
777
- ctx.lineTo(gmx(rCtx, lx), gmy(rCtx, ly));
778
- ctx.stroke();
779
- }
780
- state.curX = lx;
781
- state.curY = ly;
782
- }
783
- return true;
784
- }
785
- function handleRectangle(rCtx, dataOff, recSize) {
786
- const { ctx, view, state, inPath } = rCtx;
787
- if (recSize >= 24) {
788
- const l = view.getInt32(dataOff, true);
789
- const t = view.getInt32(dataOff + 4, true);
790
- const r = view.getInt32(dataOff + 8, true);
791
- const b = view.getInt32(dataOff + 12, true);
792
- if (inPath) {
793
- ctx.rect(gmx(rCtx, l), gmy(rCtx, t), gmw(rCtx, r - l), gmh(rCtx, b - t));
794
- } else {
795
- applyBrush(ctx, state);
796
- ctx.fillRect(gmx(rCtx, l), gmy(rCtx, t), gmw(rCtx, r - l), gmh(rCtx, b - t));
797
- applyPen(ctx, state);
798
- ctx.strokeRect(gmx(rCtx, l), gmy(rCtx, t), gmw(rCtx, r - l), gmh(rCtx, b - t));
799
- }
800
- }
801
- return true;
802
- }
803
- function handleRoundRect(rCtx, dataOff, recSize) {
804
- const { ctx, view, state, inPath } = rCtx;
805
- if (recSize >= 32) {
806
- const l = view.getInt32(dataOff, true);
807
- const t = view.getInt32(dataOff + 4, true);
808
- const r = view.getInt32(dataOff + 8, true);
809
- const b = view.getInt32(dataOff + 12, true);
810
- const rw = Math.abs(gmw(rCtx, view.getInt32(dataOff + 16, true))) / 2;
811
- const rh = Math.abs(gmh(rCtx, view.getInt32(dataOff + 20, true))) / 2;
812
- const x1 = gmx(rCtx, l);
813
- const y1 = gmy(rCtx, t);
814
- const w = gmw(rCtx, r - l);
815
- const h = gmh(rCtx, b - t);
816
- const drawRoundRect = () => {
817
- const radius = Math.min(rw, rh, w / 2, h / 2);
818
- ctx.moveTo(x1 + radius, y1);
819
- ctx.lineTo(x1 + w - radius, y1);
820
- ctx.arcTo(x1 + w, y1, x1 + w, y1 + radius, radius);
821
- ctx.lineTo(x1 + w, y1 + h - radius);
822
- ctx.arcTo(x1 + w, y1 + h, x1 + w - radius, y1 + h, radius);
823
- ctx.lineTo(x1 + radius, y1 + h);
824
- ctx.arcTo(x1, y1 + h, x1, y1 + h - radius, radius);
825
- ctx.lineTo(x1, y1 + radius);
826
- ctx.arcTo(x1, y1, x1 + radius, y1, radius);
827
- ctx.closePath();
828
- };
829
- if (inPath) {
830
- drawRoundRect();
831
- } else {
832
- ctx.beginPath();
833
- drawRoundRect();
834
- applyBrush(ctx, state);
835
- ctx.fill();
836
- applyPen(ctx, state);
837
- ctx.stroke();
838
- }
839
- }
840
- return true;
841
- }
842
- function handleEllipse(rCtx, dataOff, recSize) {
843
- const { ctx, view, state, inPath } = rCtx;
844
- if (recSize >= 24) {
845
- const l = view.getInt32(dataOff, true);
846
- const t = view.getInt32(dataOff + 4, true);
847
- const r = view.getInt32(dataOff + 8, true);
848
- const b = view.getInt32(dataOff + 12, true);
849
- const cx = gmx(rCtx, (l + r) / 2);
850
- const cy = gmy(rCtx, (t + b) / 2);
851
- const rx = Math.abs(gmw(rCtx, r - l)) / 2;
852
- const ry = Math.abs(gmh(rCtx, b - t)) / 2;
853
- if (inPath) {
854
- ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
855
- } else {
856
- ctx.beginPath();
857
- ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
858
- applyBrush(ctx, state);
859
- ctx.fill();
860
- applyPen(ctx, state);
861
- ctx.stroke();
862
- }
863
- }
864
- return true;
865
- }
866
- function handleArcFamily(rCtx, recType, dataOff, recSize) {
867
- const { ctx, view, state, inPath } = rCtx;
868
- if (recSize >= 40) {
869
- const l = view.getInt32(dataOff, true);
870
- const t = view.getInt32(dataOff + 4, true);
871
- const r = view.getInt32(dataOff + 8, true);
872
- const b = view.getInt32(dataOff + 12, true);
873
- const startX = view.getInt32(dataOff + 16, true);
874
- const startY = view.getInt32(dataOff + 20, true);
875
- const endX = view.getInt32(dataOff + 24, true);
876
- const endY = view.getInt32(dataOff + 28, true);
877
- const cxA = (l + r) / 2;
878
- const cyA = (t + b) / 2;
879
- const rx = Math.abs(r - l) / 2;
880
- const ry = Math.abs(b - t) / 2;
881
- const startAngle = Math.atan2((startY - cyA) / (ry || 1), (startX - cxA) / (rx || 1));
882
- const endAngle = Math.atan2((endY - cyA) / (ry || 1), (endX - cxA) / (rx || 1));
883
- const mcx = gmx(rCtx, cxA);
884
- const mcy = gmy(rCtx, cyA);
885
- const mrx = Math.abs(gmw(rCtx, rx));
886
- const mry = Math.abs(gmh(rCtx, ry));
887
- const isArcTo = recType === EMR_ARCTO;
888
- const needsFill = recType === EMR_PIE || recType === EMR_CHORD;
889
- if (!inPath) {
890
- ctx.beginPath();
891
- }
892
- if (recType === EMR_PIE) {
893
- ctx.moveTo(mcx, mcy);
894
- }
895
- if (isArcTo) {
896
- ctx.lineTo(mcx + mrx * Math.cos(startAngle), mcy + mry * Math.sin(startAngle));
897
- }
898
- ctx.ellipse(mcx, mcy, mrx, mry, 0, startAngle, endAngle, false);
899
- if (recType === EMR_PIE || recType === EMR_CHORD) {
900
- ctx.closePath();
901
- }
902
- if (!inPath) {
903
- if (needsFill) {
904
- applyBrush(ctx, state);
905
- ctx.fill();
906
- }
907
- applyPen(ctx, state);
908
- ctx.stroke();
909
- }
910
- if (isArcTo) {
911
- state.curX = endX;
912
- state.curY = endY;
913
- }
914
- }
915
- return true;
916
- }
917
- function handleEmfGdiShapeRecord(rCtx, recType, dataOff, recSize) {
918
- switch (recType) {
919
- case EMR_SETPIXELV:
920
- return handleSetPixelV(rCtx, dataOff, recSize);
921
- case EMR_MOVETOEX:
922
- return handleMoveToEx(rCtx, dataOff, recSize);
923
- case EMR_LINETO:
924
- return handleLineTo(rCtx, dataOff, recSize);
925
- case EMR_RECTANGLE:
926
- return handleRectangle(rCtx, dataOff, recSize);
927
- case EMR_ROUNDRECT:
928
- return handleRoundRect(rCtx, dataOff, recSize);
929
- case EMR_ELLIPSE:
930
- return handleEllipse(rCtx, dataOff, recSize);
931
- case EMR_ARC:
932
- case EMR_ARCTO:
933
- case EMR_CHORD:
934
- case EMR_PIE:
935
- return handleArcFamily(rCtx, recType, dataOff, recSize);
936
- default:
937
- return false;
938
- }
939
- }
940
-
941
- // src/emf-clip-region.ts
942
- var CLIP_HUGE = 1 << 24;
943
- function rectClipShape(x, y, w, h) {
944
- return { cmds: [{ op: "rect", x, y, w, h }], fillRule: "nonzero", simple: true };
945
- }
946
- function rectsClipShape(rects) {
947
- return {
948
- cmds: rects.map((r) => ({ op: "rect", x: r.x, y: r.y, w: r.w, h: r.h })),
949
- fillRule: "nonzero",
950
- simple: true
951
- };
952
- }
953
- function emptyClipShape() {
954
- return { cmds: [{ op: "rect", x: 0, y: 0, w: 0, h: 0 }], fillRule: "nonzero", simple: true };
955
- }
956
- function translateClipShape(shape, dx, dy) {
957
- return {
958
- ...shape,
959
- cmds: shape.cmds.map((c) => {
960
- switch (c.op) {
961
- case "rect":
962
- return { ...c, x: c.x + dx, y: c.y + dy };
963
- case "moveTo":
964
- case "lineTo":
965
- return { ...c, x: c.x + dx, y: c.y + dy };
966
- case "bezierCurveTo":
967
- return {
968
- ...c,
969
- cp1x: c.cp1x + dx,
970
- cp1y: c.cp1y + dy,
971
- cp2x: c.cp2x + dx,
972
- cp2y: c.cp2y + dy,
973
- x: c.x + dx,
974
- y: c.y + dy
975
- };
976
- case "closePath":
977
- return c;
978
- }
979
- })
1232
+ function hasWorldRotation(r) {
1233
+ const wt = r.state.worldTransform;
1234
+ return wt[1] !== 0 || wt[2] !== 0;
1235
+ }
1236
+ function gdiDeviceMatrix(r) {
1237
+ const wt = r.state.worldTransform;
1238
+ if (r.useMappingMode) {
1239
+ const kx = (r.viewportExt.cx || 1) / (r.windowExt.cx || 1);
1240
+ const ky = (r.viewportExt.cy || 1) / (r.windowExt.cy || 1);
1241
+ return [
1242
+ wt[0] * kx,
1243
+ wt[1] * ky,
1244
+ wt[2] * kx,
1245
+ wt[3] * ky,
1246
+ (wt[4] - r.windowOrg.x) * kx + r.viewportOrg.x,
1247
+ (wt[5] - r.windowOrg.y) * ky + r.viewportOrg.y
1248
+ ];
1249
+ }
1250
+ const sx = r.sx || 1;
1251
+ const sy = r.sy || 1;
1252
+ return [wt[0] * sx, wt[1] * sy, wt[2] * sx, wt[3] * sy, (wt[4] - r.bounds.left) * sx, (wt[5] - r.bounds.top) * sy];
1253
+ }
1254
+ function gmapPoint(r, x, y) {
1255
+ const m = gdiDeviceMatrix(r);
1256
+ return { x: m[0] * x + m[2] * y + m[4], y: m[1] * x + m[3] * y + m[5] };
1257
+ }
1258
+ function gdiEllipseParams(r, cx0, cy0, rx0, ry0) {
1259
+ const m = gdiDeviceMatrix(r);
1260
+ const center = gmapPoint(r, cx0, cy0);
1261
+ const a11 = m[0] * rx0;
1262
+ const a12 = m[2] * ry0;
1263
+ const a21 = m[1] * rx0;
1264
+ const a22 = m[3] * ry0;
1265
+ const p = a11 * a11 + a12 * a12;
1266
+ const q = a11 * a21 + a12 * a22;
1267
+ const s = a21 * a21 + a22 * a22;
1268
+ const mid = (p + s) / 2;
1269
+ const spread = Math.sqrt(Math.max(0, ((p - s) / 2) ** 2 + q * q));
1270
+ const lambda1 = Math.max(0, mid + spread);
1271
+ const lambda2 = Math.max(0, mid - spread);
1272
+ const rotation = 0.5 * Math.atan2(2 * q, p - s);
1273
+ return {
1274
+ cx: center.x,
1275
+ cy: center.y,
1276
+ rx: Math.sqrt(lambda1),
1277
+ ry: Math.sqrt(lambda2),
1278
+ rotation
980
1279
  };
981
1280
  }
982
- function translateClipRegion(region, dx, dy) {
983
- if (!region) {
1281
+
1282
+ // src/emf-rop3.ts
1283
+ function rop3Index(rop) {
1284
+ return rop >>> 16 & 255;
1285
+ }
1286
+ function rop3Operands(index) {
1287
+ const t = index & 255;
1288
+ return {
1289
+ usesP: (t >> 4 & 15) !== (t & 15),
1290
+ usesS: (t >> 2 & 51) !== (t & 51),
1291
+ usesD: (t >> 1 & 85) !== (t & 85)
1292
+ };
1293
+ }
1294
+ function evalRop3(index, p, s, d, mask = 16777215) {
1295
+ let out = 0;
1296
+ const np = ~p;
1297
+ const ns = ~s;
1298
+ const nd = ~d;
1299
+ for (let i = 0; i < 8; i++) {
1300
+ if (index & 1 << i) {
1301
+ out |= (i & 4 ? p : np) & (i & 2 ? s : ns) & (i & 1 ? d : nd);
1302
+ }
1303
+ }
1304
+ return out & mask;
1305
+ }
1306
+ function classifyRop3(rop) {
1307
+ const index = rop3Index(rop);
1308
+ switch (index) {
1309
+ case 204:
1310
+ return { kind: "copy" };
1311
+ case 0:
1312
+ return { kind: "solid", color: "black" };
1313
+ case 255:
1314
+ return { kind: "solid", color: "white" };
1315
+ case 170:
1316
+ return { kind: "noop" };
1317
+ case 85:
1318
+ return { kind: "invert-dest" };
1319
+ default:
1320
+ return { kind: "ternary", index, operands: rop3Operands(index) };
1321
+ }
1322
+ }
1323
+ function applyRop3(dst, src, pattern, index, originX, originY) {
1324
+ const d = dst.data;
1325
+ const s = src ? src.data : null;
1326
+ const w = dst.width;
1327
+ const h = dst.height;
1328
+ const solidP = typeof pattern === "number" ? pattern : 0;
1329
+ const sampleP = typeof pattern === "function" ? pattern : null;
1330
+ for (let y = 0; y < h; y++) {
1331
+ for (let x = 0; x < w; x++) {
1332
+ const i = (y * w + x) * 4;
1333
+ const dv = d[i] << 16 | d[i + 1] << 8 | d[i + 2];
1334
+ const sv = s ? s[i] << 16 | s[i + 1] << 8 | s[i + 2] : 0;
1335
+ const pv = sampleP ? sampleP(originX + x, originY + y) : solidP;
1336
+ const r = evalRop3(index, pv, sv, dv);
1337
+ d[i] = r >> 16 & 255;
1338
+ d[i + 1] = r >> 8 & 255;
1339
+ d[i + 2] = r & 255;
1340
+ d[i + 3] = 255;
1341
+ }
1342
+ }
1343
+ }
1344
+ function clampPositiveRect(x, y, w, h, canvasW, canvasH) {
1345
+ let left = w < 0 ? x + w : x;
1346
+ let top = h < 0 ? y + h : y;
1347
+ let width = Math.abs(w);
1348
+ let height = Math.abs(h);
1349
+ left = Math.round(left);
1350
+ top = Math.round(top);
1351
+ width = Math.round(width);
1352
+ height = Math.round(height);
1353
+ const right = Math.min(left + width, Math.round(canvasW));
1354
+ const bottom = Math.min(top + height, Math.round(canvasH));
1355
+ left = Math.max(left, 0);
1356
+ top = Math.max(top, 0);
1357
+ width = right - left;
1358
+ height = bottom - top;
1359
+ if (width <= 0 || height <= 0) {
984
1360
  return null;
985
1361
  }
986
- return region.map((s) => translateClipShape(s, dx, dy));
1362
+ return { x: left, y: top, w: width, h: height };
987
1363
  }
988
- function isComposable(shape) {
989
- return shape.simple && shape.fillRule === "nonzero";
1364
+
1365
+ // src/emf-rop2-exact.ts
1366
+ var ROP2_EXACT_INDEX = {
1367
+ [R2_MASKPEN]: 160,
1368
+ // P & D
1369
+ [R2_MERGEPEN]: 250,
1370
+ // P | D
1371
+ [R2_XORPEN]: 90,
1372
+ // P ^ D
1373
+ [R2_NOTXORPEN]: 165,
1374
+ // ~(P ^ D)
1375
+ [R2_MASKPENNOT]: 80,
1376
+ // P & ~D
1377
+ [R2_MERGEPENNOT]: 245,
1378
+ // P | ~D
1379
+ [R2_MASKNOTPEN]: 10,
1380
+ // ~P & D
1381
+ [R2_MERGENOTPEN]: 175,
1382
+ // ~P | D
1383
+ [R2_NOTMASKPEN]: 95,
1384
+ // ~(P & D)
1385
+ [R2_NOTMERGEPEN]: 5
1386
+ // ~(P | D)
1387
+ };
1388
+ function isExactRop2Bitwise(rop2) {
1389
+ return rop2 in ROP2_EXACT_INDEX;
990
1390
  }
991
- function invertClipShape(shape) {
992
- return {
993
- cmds: [
994
- { op: "rect", x: -CLIP_HUGE, y: -CLIP_HUGE, w: 2 * CLIP_HUGE, h: 2 * CLIP_HUGE },
995
- ...shape.cmds
996
- ],
997
- fillRule: "evenodd",
998
- simple: false
999
- };
1391
+ function packRgb(r, g, b) {
1392
+ return r << 16 | g << 8 | b;
1000
1393
  }
1001
- function combineClip(current, shape, op) {
1002
- switch (op) {
1003
- case "replace":
1004
- return { region: [shape], exact: true };
1005
- case "intersect":
1006
- return { region: current ? [...current, shape] : [shape], exact: true };
1007
- case "exclude": {
1008
- if (!isComposable(shape)) {
1009
- return { region: current ? [...current, shape] : [shape], exact: false };
1010
- }
1011
- const inv = invertClipShape(shape);
1012
- return { region: current ? [...current, inv] : [inv], exact: true };
1394
+ function surfaceSize(ctx) {
1395
+ const canvas = ctx.canvas;
1396
+ const w = canvas?.width;
1397
+ const h = canvas?.height;
1398
+ return typeof w === "number" && typeof h === "number" && w > 0 && h > 0 ? { w, h } : null;
1399
+ }
1400
+ function paintWithExactRop2(ctx, rop2, color, draw) {
1401
+ const index = ROP2_EXACT_INDEX[rop2];
1402
+ if (index === void 0) {
1403
+ return false;
1404
+ }
1405
+ const size = surfaceSize(ctx);
1406
+ if (!size) {
1407
+ return false;
1408
+ }
1409
+ try {
1410
+ const temp = createTempCanvas(size.w, size.h);
1411
+ if (!temp) {
1412
+ return false;
1013
1413
  }
1014
- case "union": {
1015
- if (!current) {
1016
- return { region: null, exact: true };
1017
- }
1018
- if (current.length === 1 && isComposable(current[0]) && isComposable(shape)) {
1019
- return {
1020
- region: [
1021
- { cmds: [...current[0].cmds, ...shape.cmds], fillRule: "nonzero", simple: false }
1022
- ],
1023
- exact: false
1024
- };
1414
+ temp.ctx.fillStyle = color;
1415
+ temp.ctx.strokeStyle = color;
1416
+ draw(temp.ctx);
1417
+ const paint = canvasGetImageData(temp.ctx, 0, 0, size.w, size.h);
1418
+ const dest = canvasGetImageData(ctx, 0, 0, size.w, size.h);
1419
+ const pd = paint.data;
1420
+ const dd = dest.data;
1421
+ for (let i = 0; i < dd.length; i += 4) {
1422
+ const coverage = pd[i + 3] / 255;
1423
+ if (coverage <= 0) {
1424
+ continue;
1025
1425
  }
1026
- return { region: current, exact: false };
1027
- }
1028
- case "xor": {
1029
- if (!current) {
1030
- if (isComposable(shape)) {
1031
- return { region: [invertClipShape(shape)], exact: true };
1426
+ const p = packRgb(pd[i], pd[i + 1], pd[i + 2]);
1427
+ const d = packRgb(dd[i], dd[i + 1], dd[i + 2]);
1428
+ const combined = evalRop3(index, p, d, d);
1429
+ const cr = combined >> 16 & 255;
1430
+ const cg = combined >> 8 & 255;
1431
+ const cb = combined & 255;
1432
+ dd[i] = Math.round(dd[i] + (cr - dd[i]) * coverage);
1433
+ dd[i + 1] = Math.round(dd[i + 1] + (cg - dd[i + 1]) * coverage);
1434
+ dd[i + 2] = Math.round(dd[i + 2] + (cb - dd[i + 2]) * coverage);
1435
+ dd[i + 3] = 255;
1436
+ }
1437
+ canvasPutImageData(ctx, dest, 0, 0);
1438
+ return true;
1439
+ } catch {
1440
+ return false;
1441
+ }
1442
+ }
1443
+
1444
+ // src/emf-gdi-shape-paint.ts
1445
+ function surfaceSize2(ctx) {
1446
+ const canvas = ctx.canvas;
1447
+ const w = canvas?.width;
1448
+ const h = canvas?.height;
1449
+ return typeof w === "number" && typeof h === "number" && w > 0 && h > 0 ? { w, h } : null;
1450
+ }
1451
+ function fillCurrentPathWithGdiPattern(rCtx, fillRule) {
1452
+ const { ctx, bounds, state } = rCtx;
1453
+ const realized = realizeBrush(state);
1454
+ if (realized.kind !== "tile") {
1455
+ return false;
1456
+ }
1457
+ const size = surfaceSize2(ctx);
1458
+ if (!size || typeof ctx.isPointInPath !== "function") {
1459
+ return false;
1460
+ }
1461
+ const sx = rCtx.sx || 1;
1462
+ const sy = rCtx.sy || 1;
1463
+ try {
1464
+ const img = canvasGetImageData(ctx, 0, 0, size.w, size.h);
1465
+ const d = img.data;
1466
+ for (let y = 0; y < size.h; y++) {
1467
+ for (let x = 0; x < size.w; x++) {
1468
+ if (!ctx.isPointInPath(x + 0.5, y + 0.5, fillRule)) {
1469
+ continue;
1032
1470
  }
1033
- return { region: [shape], exact: false };
1034
- }
1035
- if (current.length === 1 && isComposable(current[0]) && isComposable(shape)) {
1036
- return {
1037
- region: [
1038
- { cmds: [...current[0].cmds, ...shape.cmds], fillRule: "evenodd", simple: false }
1039
- ],
1040
- exact: true
1041
- };
1042
- }
1043
- if (isComposable(shape)) {
1044
- return { region: [...current, invertClipShape(shape)], exact: false };
1045
- }
1046
- return { region: [...current, shape], exact: false };
1047
- }
1048
- case "complement": {
1049
- if (!current) {
1050
- return { region: [emptyClipShape()], exact: true };
1051
- }
1052
- if (current.length === 1 && isComposable(current[0])) {
1053
- return { region: [shape, invertClipShape(current[0])], exact: true };
1471
+ const lx = Math.floor(bounds.left + (x + 0.5) / sx);
1472
+ const ly = Math.floor(bounds.top + (y + 0.5) / sy);
1473
+ const c = sampleTile(realized, lx, ly, state.brushOrgX, state.brushOrgY);
1474
+ const o = (y * size.w + x) * 4;
1475
+ d[o] = c >>> 16 & 255;
1476
+ d[o + 1] = c >>> 8 & 255;
1477
+ d[o + 2] = c & 255;
1478
+ d[o + 3] = 255;
1054
1479
  }
1055
- return { region: [shape], exact: false };
1056
1480
  }
1481
+ canvasPutImageData(ctx, img, 0, 0);
1482
+ return true;
1483
+ } catch {
1484
+ return false;
1057
1485
  }
1058
1486
  }
1059
- function combineClipRegions(current, incoming, op) {
1060
- if (op === "replace") {
1061
- return { region: incoming, exact: true };
1062
- }
1063
- if (incoming && incoming.length === 1) {
1064
- return combineClip(current, incoming[0], op);
1487
+ function fillShapeExactOrFast(rCtx, buildPath, fillRule = "nonzero") {
1488
+ const { ctx, state } = rCtx;
1489
+ if (state.brushStyle === 1) {
1490
+ return;
1065
1491
  }
1066
- if (!incoming) {
1067
- switch (op) {
1068
- case "intersect":
1069
- return { region: current, exact: true };
1070
- case "union":
1071
- return { region: null, exact: true };
1072
- case "exclude":
1073
- return { region: [emptyClipShape()], exact: true };
1074
- case "xor":
1075
- case "complement": {
1076
- if (!current) {
1077
- return { region: [emptyClipShape()], exact: true };
1078
- }
1079
- if (current.length === 1) {
1080
- return combineClip(null, current[0], "exclude");
1081
- }
1082
- return { region: current, exact: false };
1083
- }
1492
+ if (realizeBrush(state).kind === "tile") {
1493
+ if (fillCurrentPathWithGdiPattern(rCtx, fillRule)) {
1494
+ return;
1084
1495
  }
1085
- }
1086
- switch (op) {
1087
- case "intersect":
1088
- return { region: current ? [...current, ...incoming] : incoming, exact: true };
1089
- case "union":
1090
- return { region: current, exact: false };
1091
- case "xor":
1092
- return { region: current ?? incoming, exact: false };
1093
- case "exclude":
1094
- return { region: current, exact: false };
1095
- case "complement": {
1096
- if (!current) {
1097
- return { region: [emptyClipShape()], exact: true };
1098
- }
1099
- if (current.length === 1) {
1100
- return combineClip(incoming, current[0], "exclude");
1496
+ } else {
1497
+ const paint = rop2Paint(state.rop2);
1498
+ if (!paint.exact && isExactRop2Bitwise(state.rop2)) {
1499
+ const handled = paintWithExactRop2(ctx, state.rop2, state.brushColor, (scratch) => {
1500
+ buildPath(scratch);
1501
+ scratch.fill(fillRule);
1502
+ });
1503
+ if (handled) {
1504
+ return;
1101
1505
  }
1102
- return { region: incoming, exact: false };
1103
1506
  }
1104
1507
  }
1508
+ applyBrush(ctx, state);
1509
+ ctx.fill(fillRule);
1105
1510
  }
1106
- function replayClipCmds(ctx, cmds) {
1107
- for (const c of cmds) {
1108
- switch (c.op) {
1109
- case "rect":
1110
- ctx.rect(c.x, c.y, c.w, c.h);
1111
- break;
1112
- case "moveTo":
1113
- ctx.moveTo(c.x, c.y);
1114
- break;
1115
- case "lineTo":
1116
- ctx.lineTo(c.x, c.y);
1117
- break;
1118
- case "bezierCurveTo":
1119
- ctx.bezierCurveTo(c.cp1x, c.cp1y, c.cp2x, c.cp2y, c.x, c.y);
1120
- break;
1121
- case "closePath":
1122
- ctx.closePath();
1123
- break;
1511
+ function applyPenGeometry(ctx, state) {
1512
+ ctx.lineWidth = Math.max(state.penWidth, 1);
1513
+ switch (state.penStyle) {
1514
+ case 1:
1515
+ ctx.setLineDash([8, 4]);
1516
+ break;
1517
+ case 2:
1518
+ ctx.setLineDash([2, 2]);
1519
+ break;
1520
+ case 3:
1521
+ ctx.setLineDash([8, 4, 2, 4]);
1522
+ break;
1523
+ case 4:
1524
+ ctx.setLineDash([8, 4, 2, 4, 2, 4]);
1525
+ break;
1526
+ default:
1527
+ ctx.setLineDash([]);
1528
+ break;
1529
+ }
1530
+ }
1531
+ function strokeShapeExactOrFast(rCtx, buildPath) {
1532
+ const { ctx, state } = rCtx;
1533
+ const paint = rop2Paint(state.rop2);
1534
+ if (!paint.exact && isExactRop2Bitwise(state.rop2) && state.penStyle !== 5) {
1535
+ const handled = paintWithExactRop2(ctx, state.rop2, state.penColor, (scratch) => {
1536
+ applyPenGeometry(scratch, state);
1537
+ buildPath(scratch);
1538
+ scratch.stroke();
1539
+ });
1540
+ if (handled) {
1541
+ return;
1124
1542
  }
1125
1543
  }
1544
+ applyPen(ctx, state);
1545
+ ctx.stroke();
1126
1546
  }
1127
- function applyClipShapes(ctx, shapes) {
1128
- for (const s of shapes) {
1129
- ctx.beginPath();
1130
- replayClipCmds(ctx, s.cmds);
1131
- try {
1132
- ctx.clip(s.fillRule);
1133
- } catch {
1547
+
1548
+ // src/emf-gdi-draw-shapes.ts
1549
+ function needsPathBasedRectangle(rCtx) {
1550
+ const paint = rop2Paint(rCtx.state.rop2);
1551
+ const ropExact = !paint.exact && isExactRop2Bitwise(rCtx.state.rop2);
1552
+ return ropExact || realizeBrush(rCtx.state).kind === "tile";
1553
+ }
1554
+ function handleSetPixelV(rCtx, dataOff, recSize) {
1555
+ const { ctx, view } = rCtx;
1556
+ if (recSize >= 20) {
1557
+ const x = view.getInt32(dataOff, true);
1558
+ const y = view.getInt32(dataOff + 4, true);
1559
+ const color = readColorRef(view, dataOff + 8);
1560
+ const p = hasWorldRotation(rCtx) ? gmapPoint(rCtx, x, y) : { x: gmx(rCtx, x), y: gmy(rCtx, y) };
1561
+ ctx.fillStyle = color;
1562
+ ctx.fillRect(p.x, p.y, 1, 1);
1563
+ }
1564
+ return true;
1565
+ }
1566
+ function handleMoveToEx(rCtx, dataOff, recSize) {
1567
+ const { ctx, view, state, inPath } = rCtx;
1568
+ if (recSize >= 16) {
1569
+ state.curX = view.getInt32(dataOff, true);
1570
+ state.curY = view.getInt32(dataOff + 4, true);
1571
+ if (inPath) {
1572
+ const p = hasWorldRotation(rCtx) ? gmapPoint(rCtx, state.curX, state.curY) : { x: gmx(rCtx, state.curX), y: gmy(rCtx, state.curY) };
1573
+ ctx.moveTo(p.x, p.y);
1134
1574
  }
1135
1575
  }
1576
+ return true;
1136
1577
  }
1137
- function reapplyClipRegion(holder, region, identityTransform = false) {
1138
- const { ctx } = holder;
1139
- while (holder.clipSaveDepth > 0) {
1140
- ctx.restore();
1141
- holder.clipSaveDepth--;
1578
+ function handleLineTo(rCtx, dataOff, recSize) {
1579
+ const { ctx, view, state, inPath } = rCtx;
1580
+ if (recSize >= 16) {
1581
+ const lx = view.getInt32(dataOff, true);
1582
+ const ly = view.getInt32(dataOff + 4, true);
1583
+ const rotated = hasWorldRotation(rCtx);
1584
+ const to = rotated ? gmapPoint(rCtx, lx, ly) : { x: gmx(rCtx, lx), y: gmy(rCtx, ly) };
1585
+ if (inPath) {
1586
+ ctx.lineTo(to.x, to.y);
1587
+ } else {
1588
+ const from = rotated ? gmapPoint(rCtx, state.curX, state.curY) : { x: gmx(rCtx, state.curX), y: gmy(rCtx, state.curY) };
1589
+ const build = (c) => {
1590
+ c.beginPath();
1591
+ c.moveTo(from.x, from.y);
1592
+ c.lineTo(to.x, to.y);
1593
+ };
1594
+ build(ctx);
1595
+ strokeShapeExactOrFast(rCtx, build);
1596
+ }
1597
+ state.curX = lx;
1598
+ state.curY = ly;
1142
1599
  }
1143
- if (region) {
1144
- ctx.save();
1145
- holder.clipSaveDepth = 1;
1146
- if (identityTransform) {
1147
- ctx.setTransform(1, 0, 0, 1, 0, 0);
1600
+ return true;
1601
+ }
1602
+ function handleRectangle(rCtx, dataOff, recSize) {
1603
+ const { ctx, view, state, inPath } = rCtx;
1604
+ if (recSize >= 24) {
1605
+ const l = view.getInt32(dataOff, true);
1606
+ const t = view.getInt32(dataOff + 4, true);
1607
+ const r = view.getInt32(dataOff + 8, true);
1608
+ const b = view.getInt32(dataOff + 12, true);
1609
+ const rotated = hasWorldRotation(rCtx);
1610
+ if (rotated) {
1611
+ const p1 = gmapPoint(rCtx, l, t);
1612
+ const p2 = gmapPoint(rCtx, r, t);
1613
+ const p3 = gmapPoint(rCtx, r, b);
1614
+ const p4 = gmapPoint(rCtx, l, b);
1615
+ const build = (c) => {
1616
+ c.beginPath();
1617
+ c.moveTo(p1.x, p1.y);
1618
+ c.lineTo(p2.x, p2.y);
1619
+ c.lineTo(p3.x, p3.y);
1620
+ c.lineTo(p4.x, p4.y);
1621
+ c.closePath();
1622
+ };
1623
+ if (inPath) {
1624
+ build(ctx);
1625
+ } else {
1626
+ build(ctx);
1627
+ fillShapeExactOrFast(rCtx, build);
1628
+ strokeShapeExactOrFast(rCtx, build);
1629
+ }
1630
+ return true;
1631
+ }
1632
+ if (inPath) {
1633
+ ctx.rect(gmx(rCtx, l), gmy(rCtx, t), gmw(rCtx, r - l), gmh(rCtx, b - t));
1634
+ } else {
1635
+ const x = gmx(rCtx, l);
1636
+ const y = gmy(rCtx, t);
1637
+ const w = gmw(rCtx, r - l);
1638
+ const h = gmh(rCtx, b - t);
1639
+ if (needsPathBasedRectangle(rCtx)) {
1640
+ const build = (c) => {
1641
+ c.beginPath();
1642
+ c.rect(x, y, w, h);
1643
+ };
1644
+ build(ctx);
1645
+ fillShapeExactOrFast(rCtx, build);
1646
+ strokeShapeExactOrFast(rCtx, build);
1647
+ } else {
1648
+ applyBrush(ctx, state);
1649
+ ctx.fillRect(x, y, w, h);
1650
+ applyPen(ctx, state);
1651
+ ctx.strokeRect(x, y, w, h);
1652
+ }
1148
1653
  }
1149
- applyClipShapes(ctx, region);
1150
1654
  }
1655
+ return true;
1151
1656
  }
1152
-
1153
- // src/emf-dib-rle-decoder.ts
1154
- function decodeRleBitmap(view, bitsOffset, bitsSize, width, height, _topDown, isRle4, colorTable, out, setPixel) {
1155
- let x = 0;
1156
- let y = height - 1;
1157
- let off = bitsOffset;
1158
- const endOff = bitsOffset + bitsSize;
1159
- while (off + 1 < endOff && y >= 0) {
1160
- const first = view.getUint8(off);
1161
- const second = view.getUint8(off + 1);
1162
- off += 2;
1163
- if (first === 0) {
1164
- if (second === 0) {
1165
- x = 0;
1166
- y--;
1167
- } else if (second === 1) {
1168
- break;
1169
- } else if (second === 2) {
1170
- if (off + 1 >= endOff) {
1171
- break;
1657
+ function handleRoundRect(rCtx, dataOff, recSize) {
1658
+ const { ctx, view, inPath } = rCtx;
1659
+ if (recSize >= 32) {
1660
+ const l = view.getInt32(dataOff, true);
1661
+ const t = view.getInt32(dataOff + 4, true);
1662
+ const r = view.getInt32(dataOff + 8, true);
1663
+ const b = view.getInt32(dataOff + 12, true);
1664
+ const cornerW = view.getInt32(dataOff + 16, true);
1665
+ const cornerH = view.getInt32(dataOff + 20, true);
1666
+ const rotated = hasWorldRotation(rCtx);
1667
+ const rw = Math.abs(rotated ? cornerW / 2 : gmw(rCtx, cornerW) / 2);
1668
+ const rh = Math.abs(rotated ? cornerH / 2 : gmh(rCtx, cornerH) / 2);
1669
+ const x1 = rotated ? l : gmx(rCtx, l);
1670
+ const y1 = rotated ? t : gmy(rCtx, t);
1671
+ const w = rotated ? r - l : gmw(rCtx, r - l);
1672
+ const h = rotated ? b - t : gmh(rCtx, b - t);
1673
+ const map = (x, y) => rotated ? gmapPoint(rCtx, x, y) : { x, y };
1674
+ const drawRoundRect = (c) => {
1675
+ const radius = Math.min(rw, rh, Math.abs(w) / 2, Math.abs(h) / 2);
1676
+ const start = map(x1 + radius, y1);
1677
+ c.moveTo(start.x, start.y);
1678
+ if (rotated) {
1679
+ const corners = [
1680
+ [x1 + w - radius, y1],
1681
+ [x1 + w, y1 + radius],
1682
+ [x1 + w, y1 + h - radius],
1683
+ [x1 + w - radius, y1 + h],
1684
+ [x1 + radius, y1 + h],
1685
+ [x1, y1 + h - radius],
1686
+ [x1, y1 + radius],
1687
+ [x1 + radius, y1]
1688
+ ];
1689
+ for (const [px, py] of corners) {
1690
+ const q = map(px, py);
1691
+ c.lineTo(q.x, q.y);
1172
1692
  }
1173
- x += view.getUint8(off);
1174
- y -= view.getUint8(off + 1);
1175
- off += 2;
1176
1693
  } else {
1177
- const count = second;
1178
- if (!isRle4) {
1179
- for (let i = 0; i < count && off < endOff && x < width; i++) {
1180
- const idx = view.getUint8(off++);
1181
- if (idx < colorTable.length) {
1182
- setPixel(
1183
- x,
1184
- height - 1 - y,
1185
- colorTable[idx][0],
1186
- colorTable[idx][1],
1187
- colorTable[idx][2],
1188
- 255
1189
- );
1190
- }
1191
- x++;
1192
- }
1193
- if (count & 1) {
1194
- off++;
1195
- }
1196
- } else {
1197
- const bytes = Math.ceil(count / 2);
1198
- let pi = 0;
1199
- for (let i = 0; i < bytes && off < endOff; i++) {
1200
- const byte = view.getUint8(off++);
1201
- for (let nibble = 0; nibble < 2 && pi < count; nibble++) {
1202
- const idx = nibble === 0 ? byte >> 4 & 15 : byte & 15;
1203
- if (idx < colorTable.length && x < width) {
1204
- setPixel(
1205
- x,
1206
- height - 1 - y,
1207
- colorTable[idx][0],
1208
- colorTable[idx][1],
1209
- colorTable[idx][2],
1210
- 255
1211
- );
1212
- }
1213
- x++;
1214
- pi++;
1215
- }
1216
- }
1217
- if (bytes & 1) {
1218
- off++;
1219
- }
1220
- }
1221
- }
1222
- } else if (!isRle4) {
1223
- const idx = second;
1224
- const c = idx < colorTable.length ? colorTable[idx] : [0, 0, 0];
1225
- for (let i = 0; i < first && x < width; i++) {
1226
- setPixel(x, height - 1 - y, c[0], c[1], c[2], 255);
1227
- x++;
1228
- }
1694
+ const corner2 = map(x1 + w - radius, y1);
1695
+ c.lineTo(corner2.x, corner2.y);
1696
+ const arc1a = map(x1 + w, y1);
1697
+ const arc1b = map(x1 + w, y1 + radius);
1698
+ c.arcTo(arc1a.x, arc1a.y, arc1b.x, arc1b.y, radius);
1699
+ const corner3 = map(x1 + w, y1 + h - radius);
1700
+ c.lineTo(corner3.x, corner3.y);
1701
+ const arc2a = map(x1 + w, y1 + h);
1702
+ const arc2b = map(x1 + w - radius, y1 + h);
1703
+ c.arcTo(arc2a.x, arc2a.y, arc2b.x, arc2b.y, radius);
1704
+ const corner4 = map(x1 + radius, y1 + h);
1705
+ c.lineTo(corner4.x, corner4.y);
1706
+ const arc3a = map(x1, y1 + h);
1707
+ const arc3b = map(x1, y1 + h - radius);
1708
+ c.arcTo(arc3a.x, arc3a.y, arc3b.x, arc3b.y, radius);
1709
+ const corner1 = map(x1, y1 + radius);
1710
+ c.lineTo(corner1.x, corner1.y);
1711
+ const arc4a = map(x1, y1);
1712
+ const arc4b = map(x1 + radius, y1);
1713
+ c.arcTo(arc4a.x, arc4a.y, arc4b.x, arc4b.y, radius);
1714
+ }
1715
+ c.closePath();
1716
+ };
1717
+ if (inPath) {
1718
+ drawRoundRect(ctx);
1229
1719
  } else {
1230
- const hi = second >> 4 & 15;
1231
- const lo = second & 15;
1232
- for (let i = 0; i < first && x < width; i++) {
1233
- const idx = (i & 1) === 0 ? hi : lo;
1234
- if (idx < colorTable.length) {
1235
- setPixel(
1236
- x,
1237
- height - 1 - y,
1238
- colorTable[idx][0],
1239
- colorTable[idx][1],
1240
- colorTable[idx][2],
1241
- 255
1242
- );
1243
- }
1244
- x++;
1245
- }
1720
+ const build = (c) => {
1721
+ c.beginPath();
1722
+ drawRoundRect(c);
1723
+ };
1724
+ build(ctx);
1725
+ fillShapeExactOrFast(rCtx, build);
1726
+ strokeShapeExactOrFast(rCtx, build);
1246
1727
  }
1247
1728
  }
1248
- return new ImageData(
1249
- new Uint8ClampedArray(out.buffer, out.byteOffset, out.byteLength),
1250
- width,
1251
- height
1252
- );
1729
+ return true;
1253
1730
  }
1254
-
1255
- // src/emf-dib-uncompressed.ts
1256
- function countTrailingZeros(v) {
1257
- if (v === 0) {
1258
- return 0;
1259
- }
1260
- let c = 0;
1261
- let val = v;
1262
- while ((val & 1) === 0) {
1263
- val >>>= 1;
1264
- c++;
1731
+ function handleEllipse(rCtx, dataOff, recSize) {
1732
+ const { ctx, view, inPath } = rCtx;
1733
+ if (recSize >= 24) {
1734
+ const l = view.getInt32(dataOff, true);
1735
+ const t = view.getInt32(dataOff + 4, true);
1736
+ const r = view.getInt32(dataOff + 8, true);
1737
+ const b = view.getInt32(dataOff + 12, true);
1738
+ const params = hasWorldRotation(rCtx) ? gdiEllipseParams(rCtx, (l + r) / 2, (t + b) / 2, Math.abs(r - l) / 2, Math.abs(b - t) / 2) : {
1739
+ cx: gmx(rCtx, (l + r) / 2),
1740
+ cy: gmy(rCtx, (t + b) / 2),
1741
+ rx: Math.abs(gmw(rCtx, r - l)) / 2,
1742
+ ry: Math.abs(gmh(rCtx, b - t)) / 2,
1743
+ rotation: 0
1744
+ };
1745
+ if (inPath) {
1746
+ ctx.ellipse(params.cx, params.cy, params.rx, params.ry, params.rotation, 0, Math.PI * 2);
1747
+ } else {
1748
+ const build = (c) => {
1749
+ c.beginPath();
1750
+ c.ellipse(params.cx, params.cy, params.rx, params.ry, params.rotation, 0, Math.PI * 2);
1751
+ };
1752
+ build(ctx);
1753
+ fillShapeExactOrFast(rCtx, build);
1754
+ strokeShapeExactOrFast(rCtx, build);
1755
+ }
1265
1756
  }
1266
- return c;
1757
+ return true;
1267
1758
  }
1268
- var BI_BITFIELDS = 3;
1269
- function parseBitfieldMasks(view, bmiOffset, headerSize, compression, bitCount) {
1270
- let rMask = 0, gMask = 0, bMask = 0;
1271
- let rShift = 0, gShift = 0, bShift = 0;
1272
- let rMax = 1, gMax = 1, bMax = 1;
1273
- if (compression === BI_BITFIELDS) {
1274
- const bfOff = bmiOffset + headerSize;
1275
- if (bfOff + 12 > view.byteLength) {
1276
- return null;
1759
+ function handleArcFamily(rCtx, recType, dataOff, recSize) {
1760
+ const { ctx, view, state, inPath } = rCtx;
1761
+ if (recSize >= 40) {
1762
+ const l = view.getInt32(dataOff, true);
1763
+ const t = view.getInt32(dataOff + 4, true);
1764
+ const r = view.getInt32(dataOff + 8, true);
1765
+ const b = view.getInt32(dataOff + 12, true);
1766
+ const startX = view.getInt32(dataOff + 16, true);
1767
+ const startY = view.getInt32(dataOff + 20, true);
1768
+ const endX = view.getInt32(dataOff + 24, true);
1769
+ const endY = view.getInt32(dataOff + 28, true);
1770
+ const cxA = (l + r) / 2;
1771
+ const cyA = (t + b) / 2;
1772
+ const rx = Math.abs(r - l) / 2;
1773
+ const ry = Math.abs(b - t) / 2;
1774
+ const startAngle = Math.atan2((startY - cyA) / (ry || 1), (startX - cxA) / (rx || 1));
1775
+ const endAngle = Math.atan2((endY - cyA) / (ry || 1), (endX - cxA) / (rx || 1));
1776
+ const rotated = hasWorldRotation(rCtx);
1777
+ const params = rotated ? gdiEllipseParams(rCtx, cxA, cyA, rx, ry) : {
1778
+ cx: gmx(rCtx, cxA),
1779
+ cy: gmy(rCtx, cyA),
1780
+ rx: Math.abs(gmw(rCtx, rx)),
1781
+ ry: Math.abs(gmh(rCtx, ry)),
1782
+ rotation: 0
1783
+ };
1784
+ const isArcTo = recType === EMR_ARCTO;
1785
+ const needsFill = recType === EMR_PIE || recType === EMR_CHORD;
1786
+ const startPoint = rotated ? gmapPoint(rCtx, cxA + rx * Math.cos(startAngle), cyA + ry * Math.sin(startAngle)) : { x: params.cx + params.rx * Math.cos(startAngle), y: params.cy + params.ry * Math.sin(startAngle) };
1787
+ const build = (c) => {
1788
+ if (recType === EMR_PIE) {
1789
+ c.moveTo(params.cx, params.cy);
1790
+ }
1791
+ if (isArcTo) {
1792
+ c.lineTo(startPoint.x, startPoint.y);
1793
+ }
1794
+ c.ellipse(params.cx, params.cy, params.rx, params.ry, params.rotation, startAngle, endAngle, false);
1795
+ if (needsFill) {
1796
+ c.closePath();
1797
+ }
1798
+ };
1799
+ if (!inPath) {
1800
+ ctx.beginPath();
1801
+ }
1802
+ build(ctx);
1803
+ if (!inPath) {
1804
+ const buildWithPath = (c) => {
1805
+ c.beginPath();
1806
+ build(c);
1807
+ };
1808
+ if (needsFill) {
1809
+ fillShapeExactOrFast(rCtx, buildWithPath);
1810
+ }
1811
+ strokeShapeExactOrFast(rCtx, buildWithPath);
1812
+ }
1813
+ if (isArcTo) {
1814
+ state.curX = endX;
1815
+ state.curY = endY;
1277
1816
  }
1278
- rMask = view.getUint32(bfOff, true);
1279
- gMask = view.getUint32(bfOff + 4, true);
1280
- bMask = view.getUint32(bfOff + 8, true);
1281
- rShift = countTrailingZeros(rMask);
1282
- gShift = countTrailingZeros(gMask);
1283
- bShift = countTrailingZeros(bMask);
1284
- rMax = rMask >>> rShift || 1;
1285
- gMax = gMask >>> gShift || 1;
1286
- bMax = bMask >>> bShift || 1;
1287
- } else if (bitCount === 16) {
1288
- rMask = 31744;
1289
- gMask = 992;
1290
- bMask = 31;
1291
- rShift = 10;
1292
- gShift = 5;
1293
- bShift = 0;
1294
- rMax = 31;
1295
- gMax = 31;
1296
- bMax = 31;
1297
1817
  }
1298
- return { rMask, gMask, bMask, rShift, gShift, bShift, rMax, gMax, bMax };
1818
+ return true;
1299
1819
  }
1300
- function decodeUncompressedRows(view, bitsOffset, width, height, topDown, bitCount, colorTable, masks, out) {
1301
- const rowStride = Math.floor((bitCount * width + 31) / 32) * 4;
1302
- const { rMask, gMask, bMask, rShift, gShift, bShift, rMax, gMax, bMax } = masks;
1303
- for (let y = 0; y < height; y++) {
1304
- const srcY = topDown ? y : height - 1 - y;
1305
- const rowStart = bitsOffset + srcY * rowStride;
1306
- if (rowStart + rowStride > view.byteLength) {
1307
- continue;
1308
- }
1309
- for (let x = 0; x < width; x++) {
1310
- const dstPx = (y * width + x) * 4;
1311
- if (bitCount === 1) {
1312
- const byteIdx = rowStart + (x >> 3);
1313
- const bit = view.getUint8(byteIdx) >> 7 - (x & 7) & 1;
1314
- if (bit < colorTable.length) {
1315
- out[dstPx] = colorTable[bit][0];
1316
- out[dstPx + 1] = colorTable[bit][1];
1317
- out[dstPx + 2] = colorTable[bit][2];
1318
- }
1319
- out[dstPx + 3] = 255;
1320
- } else if (bitCount === 4) {
1321
- const byteIdx = rowStart + (x >> 1);
1322
- const nibble = (x & 1) === 0 ? view.getUint8(byteIdx) >> 4 & 15 : view.getUint8(byteIdx) & 15;
1323
- if (nibble < colorTable.length) {
1324
- out[dstPx] = colorTable[nibble][0];
1325
- out[dstPx + 1] = colorTable[nibble][1];
1326
- out[dstPx + 2] = colorTable[nibble][2];
1327
- }
1328
- out[dstPx + 3] = 255;
1329
- } else if (bitCount === 8) {
1330
- const idx = view.getUint8(rowStart + x);
1331
- if (idx < colorTable.length) {
1332
- out[dstPx] = colorTable[idx][0];
1333
- out[dstPx + 1] = colorTable[idx][1];
1334
- out[dstPx + 2] = colorTable[idx][2];
1820
+ function handleEmfGdiShapeRecord(rCtx, recType, dataOff, recSize) {
1821
+ switch (recType) {
1822
+ case EMR_SETPIXELV:
1823
+ return handleSetPixelV(rCtx, dataOff, recSize);
1824
+ case EMR_MOVETOEX:
1825
+ return handleMoveToEx(rCtx, dataOff, recSize);
1826
+ case EMR_LINETO:
1827
+ return handleLineTo(rCtx, dataOff, recSize);
1828
+ case EMR_RECTANGLE:
1829
+ return handleRectangle(rCtx, dataOff, recSize);
1830
+ case EMR_ROUNDRECT:
1831
+ return handleRoundRect(rCtx, dataOff, recSize);
1832
+ case EMR_ELLIPSE:
1833
+ return handleEllipse(rCtx, dataOff, recSize);
1834
+ case EMR_ARC:
1835
+ case EMR_ARCTO:
1836
+ case EMR_CHORD:
1837
+ case EMR_PIE:
1838
+ return handleArcFamily(rCtx, recType, dataOff, recSize);
1839
+ default:
1840
+ return false;
1841
+ }
1842
+ }
1843
+
1844
+ // src/emf-gdi-stretch.ts
1845
+ var BLACKONWHITE = 1;
1846
+ var WHITEONBLACK = 2;
1847
+ var HALFTONE = 4;
1848
+ function gdiNearest(i, srcLen, dstLen) {
1849
+ const k = Math.floor((2 * i + 1) * srcLen / (2 * dstLen));
1850
+ return Math.max(0, Math.min(srcLen - 1, k));
1851
+ }
1852
+ function axisSamples(srcStart, srcLen, dstLen, reverse, combine) {
1853
+ const out = [];
1854
+ const at = (k) => reverse ? srcStart + srcLen - 1 - k : srcStart + k;
1855
+ if (dstLen <= 0 || srcLen <= 0) {
1856
+ return out;
1857
+ }
1858
+ let prev = -1;
1859
+ for (let i = 0; i < dstLen; i++) {
1860
+ const c = gdiNearest(i, srcLen, dstLen);
1861
+ const run = [];
1862
+ const from = combine && dstLen < srcLen ? prev + 1 : c;
1863
+ for (let k = from; k <= c; k++) {
1864
+ run.push(at(k));
1865
+ }
1866
+ out.push(run);
1867
+ prev = c;
1868
+ }
1869
+ return out;
1870
+ }
1871
+ function stretchGdi(src, sx, sy, sw, sh, dw, dh, mode) {
1872
+ const W = Math.max(0, Math.round(Math.abs(dw)));
1873
+ const H = Math.max(0, Math.round(Math.abs(dh)));
1874
+ const flipX = dw < 0 !== sw < 0;
1875
+ const flipY = dh < 0 !== sh < 0;
1876
+ const combine = mode === BLACKONWHITE || mode === WHITEONBLACK;
1877
+ const cols = axisSamples(Math.round(Math.min(sx, sx + sw)), Math.round(Math.abs(sw)), W, flipX, combine);
1878
+ const rows = axisSamples(Math.round(Math.min(sy, sy + sh)), Math.round(Math.abs(sh)), H, flipY, combine);
1879
+ const data = new Uint8ClampedArray(W * H * 4);
1880
+ const s = src.data;
1881
+ const read = (x, y) => {
1882
+ if (x < 0 || y < 0 || x >= src.width || y >= src.height) {
1883
+ return 0;
1884
+ }
1885
+ const i = (y * src.width + x) * 4;
1886
+ return s[i] << 16 | s[i + 1] << 8 | s[i + 2];
1887
+ };
1888
+ for (let y = 0; y < H; y++) {
1889
+ const ys = rows[y] ?? [];
1890
+ for (let x = 0; x < W; x++) {
1891
+ const xs = cols[x] ?? [];
1892
+ let v = mode === BLACKONWHITE ? 16777215 : 0;
1893
+ let first = true;
1894
+ for (const yy of ys) {
1895
+ for (const xx of xs) {
1896
+ const p = read(xx, yy);
1897
+ if (mode === BLACKONWHITE) {
1898
+ v &= p;
1899
+ } else if (mode === WHITEONBLACK) {
1900
+ v |= p;
1901
+ } else if (first) {
1902
+ v = p;
1903
+ }
1904
+ first = false;
1335
1905
  }
1336
- out[dstPx + 3] = 255;
1337
- } else if (bitCount === 16) {
1338
- const val = view.getUint16(rowStart + x * 2, true);
1339
- out[dstPx] = Math.round(((val & rMask) >>> rShift) * 255 / rMax);
1340
- out[dstPx + 1] = Math.round(((val & gMask) >>> gShift) * 255 / gMax);
1341
- out[dstPx + 2] = Math.round(((val & bMask) >>> bShift) * 255 / bMax);
1342
- out[dstPx + 3] = 255;
1343
- } else if (bitCount === 24) {
1344
- const srcPx = rowStart + x * 3;
1345
- out[dstPx] = view.getUint8(srcPx + 2);
1346
- out[dstPx + 1] = view.getUint8(srcPx + 1);
1347
- out[dstPx + 2] = view.getUint8(srcPx);
1348
- out[dstPx + 3] = 255;
1349
- } else {
1350
- const srcPx = rowStart + x * 4;
1351
- const bb = view.getUint8(srcPx);
1352
- const gg = view.getUint8(srcPx + 1);
1353
- const rr = view.getUint8(srcPx + 2);
1354
- const aa = view.getUint8(srcPx + 3);
1355
- out[dstPx] = rr;
1356
- out[dstPx + 1] = gg;
1357
- out[dstPx + 2] = bb;
1358
- out[dstPx + 3] = aa === 0 ? 255 : aa;
1359
1906
  }
1907
+ const o = (y * W + x) * 4;
1908
+ data[o] = v >> 16 & 255;
1909
+ data[o + 1] = v >> 8 & 255;
1910
+ data[o + 2] = v & 255;
1911
+ data[o + 3] = 255;
1360
1912
  }
1361
1913
  }
1914
+ return { width: W, height: H, data };
1362
1915
  }
1363
1916
 
1364
- // src/emf-dib-decoder.ts
1365
- function decodeDibToImageData(view, bmiOffset, bitsOffset, bitsSize) {
1366
- if (bmiOffset < 0 || bitsOffset < 0 || bmiOffset + 40 > view.byteLength || bitsOffset + bitsSize > view.byteLength) {
1367
- return null;
1917
+ // src/emf-gdi-draw-bitmap.ts
1918
+ function patternOperand(rCtx, brush) {
1919
+ if (brush.kind === "solid") {
1920
+ return brush.rgb;
1368
1921
  }
1369
- const headerSize = view.getUint32(bmiOffset, true);
1370
- if (headerSize < 40 || bmiOffset + headerSize > view.byteLength) {
1371
- return null;
1922
+ if (brush.kind === "none") {
1923
+ return 0;
1372
1924
  }
1373
- const width = view.getInt32(bmiOffset + 4, true);
1374
- const heightRaw = view.getInt32(bmiOffset + 8, true);
1375
- const planes = view.getUint16(bmiOffset + 12, true);
1376
- const bitCount = view.getUint16(bmiOffset + 14, true);
1377
- const compression = view.getUint32(bmiOffset + 16, true);
1378
- if (planes !== 1 || width <= 0 || heightRaw === 0) {
1379
- return null;
1925
+ const { state, bounds } = rCtx;
1926
+ const sx = rCtx.sx || 1;
1927
+ const sy = rCtx.sy || 1;
1928
+ return (x, y) => sampleTile(
1929
+ brush,
1930
+ Math.floor(bounds.left + (x + 0.5) / sx),
1931
+ Math.floor(bounds.top + (y + 0.5) / sy),
1932
+ state.brushOrgX,
1933
+ state.brushOrgY
1934
+ );
1935
+ }
1936
+ function drawSourceMapped(target, decoded, req, offsetX, offsetY, mode) {
1937
+ const dLeft = Math.min(req.dx, req.dx + req.dw) - offsetX;
1938
+ const dTop = Math.min(req.dy, req.dy + req.dh) - offsetY;
1939
+ if (mode !== HALFTONE) {
1940
+ const px = stretchGdi(decoded.pixels, req.sx, req.sy, req.sw, req.sh, req.dw, req.dh, mode);
1941
+ const tile = createTempCanvas(px.width, px.height);
1942
+ if (!tile) {
1943
+ return;
1944
+ }
1945
+ canvasPutImageData(tile.ctx, createImageDataCompat(px.data, px.width, px.height), 0, 0);
1946
+ target.save();
1947
+ target.globalCompositeOperation = "source-over";
1948
+ canvasDrawImage(target, tile.canvas, Math.round(dLeft), Math.round(dTop), px.width, px.height);
1949
+ target.restore();
1950
+ return;
1380
1951
  }
1381
- if (width > 8192 || Math.abs(heightRaw) > 8192) {
1952
+ const flipX = req.dw < 0 !== req.sw < 0;
1953
+ const flipY = req.dh < 0 !== req.sh < 0;
1954
+ const adw = Math.abs(req.dw);
1955
+ const adh = Math.abs(req.dh);
1956
+ target.save();
1957
+ target.imageSmoothingEnabled = true;
1958
+ target.transform(
1959
+ flipX ? -1 : 1,
1960
+ 0,
1961
+ 0,
1962
+ flipY ? -1 : 1,
1963
+ flipX ? dLeft * 2 + adw : 0,
1964
+ flipY ? dTop * 2 + adh : 0
1965
+ );
1966
+ const draw = target.drawImage;
1967
+ draw.call(
1968
+ target,
1969
+ decoded.canvas,
1970
+ Math.min(req.sx, req.sx + req.sw),
1971
+ Math.min(req.sy, req.sy + req.sh),
1972
+ Math.abs(req.sw),
1973
+ Math.abs(req.sh),
1974
+ dLeft,
1975
+ dTop,
1976
+ adw,
1977
+ adh
1978
+ );
1979
+ target.restore();
1980
+ }
1981
+ function decodeSource(rCtx, req) {
1982
+ if (!req.source) {
1382
1983
  return null;
1383
1984
  }
1384
- const BI_RGB = 0;
1385
- const BI_RLE8 = 1;
1386
- const BI_RLE4 = 2;
1387
- const BI_BITFIELDS2 = 3;
1388
- if (bitCount !== 1 && bitCount !== 4 && bitCount !== 8 && bitCount !== 16 && bitCount !== 24 && bitCount !== 32) {
1985
+ const image = decodeDibToImageData(rCtx.view, req.source.bmi, req.source.bits, req.source.cbBits);
1986
+ if (!image) {
1389
1987
  return null;
1390
1988
  }
1391
- if (compression === BI_RLE8 && bitCount !== 8) {
1989
+ const temp = createTempCanvas(image.width, image.height);
1990
+ if (!temp) {
1392
1991
  return null;
1393
1992
  }
1394
- if (compression === BI_RLE4 && bitCount !== 4) {
1993
+ canvasPutImageData(temp.ctx, image, 0, 0);
1994
+ let { sy } = req;
1995
+ if (req.dibOrigin === "bottom-left" && rCtx.view.getInt32(req.source.bmi + 8, true) > 0) {
1996
+ sy = image.height - sy - req.sh;
1997
+ }
1998
+ return {
1999
+ decoded: { pixels: image, canvas: temp.canvas },
2000
+ req: { ...req, sy }
2001
+ };
2002
+ }
2003
+ function fillRectWith(ctx, style, gco, req) {
2004
+ const prevGco = ctx.globalCompositeOperation;
2005
+ const prevFill = ctx.fillStyle;
2006
+ ctx.globalCompositeOperation = gco;
2007
+ ctx.fillStyle = style;
2008
+ ctx.fillRect(req.dx, req.dy, req.dw, req.dh);
2009
+ ctx.globalCompositeOperation = prevGco;
2010
+ ctx.fillStyle = prevFill;
2011
+ }
2012
+ function runTernary(rCtx, req, index, usesP, usesS) {
2013
+ const { ctx } = rCtx;
2014
+ const rect = clampPositiveRect(req.dx, req.dy, req.dw, req.dh, rCtx.canvasW, rCtx.canvasH);
2015
+ if (!rect) {
2016
+ return;
2017
+ }
2018
+ if (typeof ctx.getImageData !== "function") {
2019
+ return;
2020
+ }
2021
+ const brush = realizeBrush(rCtx.state);
2022
+ if (usesP && brush.kind === "none") {
2023
+ return;
2024
+ }
2025
+ const layer = createTempCanvas(rect.w, rect.h);
2026
+ if (!layer) {
2027
+ return;
2028
+ }
2029
+ let src = null;
2030
+ if (usesS) {
2031
+ const decoded = decodeSource(rCtx, req);
2032
+ if (!decoded) {
2033
+ return;
2034
+ }
2035
+ drawSourceMapped(layer.ctx, decoded.decoded, decoded.req, rect.x, rect.y, rCtx.state.stretchBltMode);
2036
+ src = canvasGetImageData(layer.ctx, 0, 0, rect.w, rect.h);
2037
+ }
2038
+ const dst = canvasGetImageData(ctx, rect.x, rect.y, rect.w, rect.h);
2039
+ applyRop3(dst, src, patternOperand(rCtx, brush), index, rect.x, rect.y);
2040
+ canvasPutImageData(layer.ctx, dst, 0, 0);
2041
+ ctx.save();
2042
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
2043
+ ctx.globalCompositeOperation = "source-over";
2044
+ ctx.globalAlpha = 1;
2045
+ canvasDrawImage(ctx, layer.canvas, rect.x, rect.y, rect.w, rect.h);
2046
+ ctx.restore();
2047
+ }
2048
+ function alignMirroredDest(rCtx, req) {
2049
+ const pxX = rCtx.useMappingMode ? 1 : rCtx.sx;
2050
+ const pxY = rCtx.useMappingMode ? 1 : rCtx.sy;
2051
+ return {
2052
+ ...req,
2053
+ dx: req.dw < 0 ? req.dx + pxX : req.dx,
2054
+ dy: req.dh < 0 ? req.dy + pxY : req.dy
2055
+ };
2056
+ }
2057
+ function executeBlit(rCtx, request) {
2058
+ const { ctx } = rCtx;
2059
+ const req = alignMirroredDest(rCtx, request);
2060
+ const plan = req.plan;
2061
+ switch (plan.kind) {
2062
+ case "noop":
2063
+ return;
2064
+ case "solid":
2065
+ fillRectWith(ctx, plan.color === "black" ? "#000000" : "#ffffff", "source-over", req);
2066
+ return;
2067
+ case "invert-dest":
2068
+ fillRectWith(ctx, "#ffffff", "difference", req);
2069
+ return;
2070
+ case "copy": {
2071
+ const decoded = decodeSource(rCtx, req);
2072
+ if (decoded) {
2073
+ drawSourceMapped(ctx, decoded.decoded, decoded.req, 0, 0, rCtx.state.stretchBltMode);
2074
+ }
2075
+ return;
2076
+ }
2077
+ case "ternary": {
2078
+ const brush = realizeBrush(rCtx.state);
2079
+ if (plan.index === 240 && brush.kind !== "tile") {
2080
+ if (brush.kind === "solid") {
2081
+ fillRectWith(ctx, rCtx.state.brushColor, "source-over", req);
2082
+ }
2083
+ return;
2084
+ }
2085
+ runTernary(rCtx, req, plan.index, plan.operands.usesP, plan.operands.usesS);
2086
+ }
2087
+ }
2088
+ }
2089
+ function sourceOf(offset, offBmi, cbBmi, offBits, cbBits) {
2090
+ return offBmi > 0 && cbBmi > 0 && offBits > 0 && cbBits > 0 ? { bmi: offset + offBmi, bits: offset + offBits, cbBits } : null;
2091
+ }
2092
+ function handleBlt(rCtx, offset, dataOff, recSize, stretch) {
2093
+ const { view } = rCtx;
2094
+ if (recSize < (stretch ? 108 : 96)) {
2095
+ return true;
2096
+ }
2097
+ const xDest = view.getInt32(dataOff + 16, true);
2098
+ const yDest = view.getInt32(dataOff + 20, true);
2099
+ const cxDest = view.getInt32(dataOff + 24, true);
2100
+ const cyDest = view.getInt32(dataOff + 28, true);
2101
+ const rop = view.getUint32(dataOff + 32, true);
2102
+ const xSrc = view.getInt32(dataOff + 36, true);
2103
+ const ySrc = view.getInt32(dataOff + 40, true);
2104
+ const m11 = view.getFloat32(dataOff + 44, true);
2105
+ const m12 = view.getFloat32(dataOff + 48, true);
2106
+ const m21 = view.getFloat32(dataOff + 52, true);
2107
+ const m22 = view.getFloat32(dataOff + 56, true);
2108
+ const mdx = view.getFloat32(dataOff + 60, true);
2109
+ const mdy = view.getFloat32(dataOff + 64, true);
2110
+ const cxSrc = stretch ? view.getInt32(dataOff + 92, true) : cxDest;
2111
+ const cySrc = stretch ? view.getInt32(dataOff + 96, true) : cyDest;
2112
+ const source = recSize >= 100 ? sourceOf(
2113
+ offset,
2114
+ view.getUint32(dataOff + 76, true),
2115
+ view.getUint32(dataOff + 80, true),
2116
+ view.getUint32(dataOff + 84, true),
2117
+ view.getUint32(dataOff + 88, true)
2118
+ ) : null;
2119
+ const identity = m11 === 0 && m22 === 0 && m12 === 0 && m21 === 0;
2120
+ const a = identity ? 1 : m11;
2121
+ const d = identity ? 1 : m22;
2122
+ executeBlit(rCtx, {
2123
+ plan: classifyRop3(rop),
2124
+ dx: gmx(rCtx, xDest),
2125
+ dy: gmy(rCtx, yDest),
2126
+ dw: gmw(rCtx, cxDest),
2127
+ dh: gmh(rCtx, cyDest),
2128
+ source,
2129
+ sx: a * xSrc + (identity ? 0 : m21 * ySrc + mdx),
2130
+ sy: d * ySrc + (identity ? 0 : m12 * xSrc + mdy),
2131
+ sw: a * cxSrc,
2132
+ sh: d * cySrc,
2133
+ dibOrigin: "top-left"
2134
+ });
2135
+ return true;
2136
+ }
2137
+ function handleStretchDibits(rCtx, offset, dataOff, recSize) {
2138
+ const { view } = rCtx;
2139
+ if (recSize < 80) {
2140
+ return true;
2141
+ }
2142
+ executeBlit(rCtx, {
2143
+ plan: classifyRop3(view.getUint32(dataOff + 60, true)),
2144
+ dx: gmx(rCtx, view.getInt32(dataOff + 16, true)),
2145
+ dy: gmy(rCtx, view.getInt32(dataOff + 20, true)),
2146
+ dw: gmw(rCtx, view.getInt32(dataOff + 64, true)),
2147
+ dh: gmh(rCtx, view.getInt32(dataOff + 68, true)),
2148
+ source: sourceOf(
2149
+ offset,
2150
+ view.getUint32(dataOff + 40, true),
2151
+ view.getUint32(dataOff + 44, true),
2152
+ view.getUint32(dataOff + 48, true),
2153
+ view.getUint32(dataOff + 52, true)
2154
+ ),
2155
+ sx: view.getInt32(dataOff + 24, true),
2156
+ sy: view.getInt32(dataOff + 28, true),
2157
+ sw: view.getInt32(dataOff + 32, true),
2158
+ sh: view.getInt32(dataOff + 36, true),
2159
+ dibOrigin: "bottom-left"
2160
+ });
2161
+ return true;
2162
+ }
2163
+ function handleEmfGdiBitmapRecord(rCtx, recType, offset, dataOff, recSize) {
2164
+ switch (recType) {
2165
+ case EMR_BITBLT:
2166
+ return handleBlt(rCtx, offset, dataOff, recSize, false);
2167
+ case EMR_STRETCHBLT:
2168
+ return handleBlt(rCtx, offset, dataOff, recSize, true);
2169
+ case EMR_STRETCHDIBITS:
2170
+ return handleStretchDibits(rCtx, offset, dataOff, recSize);
2171
+ default:
2172
+ return false;
2173
+ }
2174
+ }
2175
+
2176
+ // src/emf-gdi-draw-text.ts
2177
+ function readDxArray(view, offset, dataOff, nChars, viewEnd) {
2178
+ const offDx = view.getUint32(dataOff + 64, true);
2179
+ if (offDx === 0) {
1395
2180
  return null;
1396
2181
  }
1397
- if (compression === BI_BITFIELDS2 && bitCount !== 16 && bitCount !== 32) {
2182
+ const start = offset + offDx;
2183
+ const end = start + nChars * 4;
2184
+ if (start < 0 || end > viewEnd) {
1398
2185
  return null;
1399
2186
  }
1400
- if (compression !== BI_RGB && compression !== BI_RLE8 && compression !== BI_RLE4 && compression !== BI_BITFIELDS2) {
2187
+ const dx = [];
2188
+ for (let i = 0; i < nChars; i++) {
2189
+ dx.push(view.getUint32(start + i * 4, true));
2190
+ }
2191
+ return dx;
2192
+ }
2193
+ function horizontalAlign(textAlign) {
2194
+ if (textAlign & 2) {
2195
+ return "right";
2196
+ }
2197
+ if (textAlign & 6) {
2198
+ return "center";
2199
+ }
2200
+ return "left";
2201
+ }
2202
+ function verticalBaseline(textAlign) {
2203
+ const vAlign = textAlign & 24;
2204
+ return vAlign === 24 ? "alphabetic" : vAlign === 8 ? "bottom" : "top";
2205
+ }
2206
+ function paintRun(ctx, state, text, dxDevice, startX, y, align, fontScale) {
2207
+ const totalWidth = dxDevice ? totalGlyphAdvance(dxDevice) : ctx.measureText(text).width;
2208
+ const runStartX = dxDevice ? startX + alignmentStartOffset(totalWidth, align) : startX;
2209
+ if (state.bkMode === 2) {
2210
+ const bgH = fontSizePx(state, fontScale);
2211
+ const prevFill = ctx.fillStyle;
2212
+ ctx.fillStyle = state.bkColor;
2213
+ ctx.fillRect(runStartX, y - bgH, totalWidth, bgH);
2214
+ ctx.fillStyle = prevFill;
2215
+ }
2216
+ ctx.fillStyle = state.textColor;
2217
+ if (dxDevice) {
2218
+ const offsets = cumulativeGlyphOffsets(dxDevice);
2219
+ const prevAlign = ctx.textAlign;
2220
+ ctx.textAlign = "left";
2221
+ for (let i = 0; i < text.length && i < offsets.length; i++) {
2222
+ ctx.fillText(text[i], runStartX + offsets[i], y);
2223
+ }
2224
+ ctx.textAlign = prevAlign;
2225
+ } else {
2226
+ ctx.fillText(text, startX, y);
2227
+ }
2228
+ if (state.fontUnderline || state.fontStrikeOut) {
2229
+ drawTextDecorations(ctx, state, runStartX, y, totalWidth, fontScale);
2230
+ }
2231
+ }
2232
+ function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
2233
+ const { ctx, view, state } = rCtx;
2234
+ if (recSize < 76) {
2235
+ return true;
2236
+ }
2237
+ const refX = view.getInt32(dataOff + 28, true);
2238
+ const refY = view.getInt32(dataOff + 32, true);
2239
+ const nChars = view.getUint32(dataOff + 36, true);
2240
+ const offString = view.getUint32(dataOff + 40, true);
2241
+ const viewEnd = view.byteLength;
2242
+ if (nChars === 0 || offString === 0 || offset + offString + nChars * 2 > viewEnd) {
2243
+ return true;
2244
+ }
2245
+ const text = readUtf16LE(view, offset + offString, nChars);
2246
+ if (text.length === 0) {
2247
+ return true;
2248
+ }
2249
+ const fontScale = Math.abs(gmh(rCtx, 1));
2250
+ applyFont(ctx, state, fontScale);
2251
+ const align = horizontalAlign(state.textAlign);
2252
+ ctx.textBaseline = verticalBaseline(state.textAlign);
2253
+ ctx.textAlign = align === "center" ? "center" : align === "right" ? "right" : "left";
2254
+ const dxLogical = readDxArray(view, offset, dataOff, nChars, viewEnd);
2255
+ const dxDevice = dxLogical ? dxLogical.map((v) => gmw(rCtx, v)) : null;
2256
+ if (dxDevice) {
2257
+ ctx.textAlign = "left";
2258
+ }
2259
+ const baseX = gmx(rCtx, refX);
2260
+ const baseY = gmy(rCtx, refY);
2261
+ const radians = escapementToCanvasRadians(state.fontEscapementTenthDeg);
2262
+ if (radians !== 0) {
2263
+ ctx.save();
2264
+ ctx.translate(baseX, baseY);
2265
+ ctx.rotate(radians);
2266
+ paintRun(ctx, state, text, dxDevice, 0, 0, align, fontScale);
2267
+ ctx.restore();
2268
+ } else {
2269
+ paintRun(ctx, state, text, dxDevice, baseX, baseY, align, fontScale);
2270
+ }
2271
+ return true;
2272
+ }
2273
+ function handleEmfGdiDrawTextRecord(rCtx, recType, offset, dataOff, recSize) {
2274
+ if (recType === EMR_EXTTEXTOUTW) {
2275
+ return handleExtTextOutW(rCtx, offset, dataOff, recSize);
2276
+ }
2277
+ return false;
2278
+ }
2279
+
2280
+ // src/emf-clip-region.ts
2281
+ var CLIP_HUGE = 1 << 24;
2282
+ function rectClipShape(x, y, w, h) {
2283
+ return { cmds: [{ op: "rect", x, y, w, h }], fillRule: "nonzero", simple: true };
2284
+ }
2285
+ function rectsClipShape(rects) {
2286
+ return {
2287
+ cmds: rects.map((r) => ({ op: "rect", x: r.x, y: r.y, w: r.w, h: r.h })),
2288
+ fillRule: "nonzero",
2289
+ simple: true
2290
+ };
2291
+ }
2292
+ function emptyClipShape() {
2293
+ return { cmds: [{ op: "rect", x: 0, y: 0, w: 0, h: 0 }], fillRule: "nonzero", simple: true };
2294
+ }
2295
+ function translateClipShape(shape, dx, dy) {
2296
+ return {
2297
+ ...shape,
2298
+ cmds: shape.cmds.map((c) => {
2299
+ switch (c.op) {
2300
+ case "rect":
2301
+ return { ...c, x: c.x + dx, y: c.y + dy };
2302
+ case "moveTo":
2303
+ case "lineTo":
2304
+ return { ...c, x: c.x + dx, y: c.y + dy };
2305
+ case "bezierCurveTo":
2306
+ return {
2307
+ ...c,
2308
+ cp1x: c.cp1x + dx,
2309
+ cp1y: c.cp1y + dy,
2310
+ cp2x: c.cp2x + dx,
2311
+ cp2y: c.cp2y + dy,
2312
+ x: c.x + dx,
2313
+ y: c.y + dy
2314
+ };
2315
+ case "closePath":
2316
+ return c;
2317
+ }
2318
+ })
2319
+ };
2320
+ }
2321
+ function translateClipRegion(region, dx, dy) {
2322
+ if (!region) {
1401
2323
  return null;
1402
2324
  }
1403
- const height = Math.abs(heightRaw);
1404
- const topDown = heightRaw < 0;
1405
- const colorTable = [];
1406
- if (bitCount <= 8) {
1407
- const maxColors = 1 << bitCount;
1408
- const colorsUsed = view.getUint32(bmiOffset + 32, true) || maxColors;
1409
- const numColors = Math.min(colorsUsed, maxColors);
1410
- const ctOffset = bmiOffset + headerSize;
1411
- if (ctOffset + numColors * 4 > view.byteLength) {
1412
- return null;
2325
+ return region.map((s) => translateClipShape(s, dx, dy));
2326
+ }
2327
+ function isComposable(shape) {
2328
+ return shape.simple && shape.fillRule === "nonzero";
2329
+ }
2330
+ function invertClipShape(shape) {
2331
+ return {
2332
+ cmds: [
2333
+ { op: "rect", x: -CLIP_HUGE, y: -CLIP_HUGE, w: 2 * CLIP_HUGE, h: 2 * CLIP_HUGE },
2334
+ ...shape.cmds
2335
+ ],
2336
+ fillRule: "evenodd",
2337
+ simple: false
2338
+ };
2339
+ }
2340
+ function combineClip(current, shape, op) {
2341
+ switch (op) {
2342
+ case "replace":
2343
+ return { region: [shape], exact: true };
2344
+ case "intersect":
2345
+ return { region: current ? [...current, shape] : [shape], exact: true };
2346
+ case "exclude": {
2347
+ if (!isComposable(shape)) {
2348
+ return { region: current ? [...current, shape] : [shape], exact: false };
2349
+ }
2350
+ const inv = invertClipShape(shape);
2351
+ return { region: current ? [...current, inv] : [inv], exact: true };
1413
2352
  }
1414
- for (let i = 0; i < numColors; i++) {
1415
- const b = view.getUint8(ctOffset + i * 4);
1416
- const g = view.getUint8(ctOffset + i * 4 + 1);
1417
- const r = view.getUint8(ctOffset + i * 4 + 2);
1418
- colorTable.push([r, g, b]);
2353
+ case "union": {
2354
+ if (!current) {
2355
+ return { region: null, exact: true };
2356
+ }
2357
+ if (current.length === 1 && isComposable(current[0]) && isComposable(shape)) {
2358
+ return {
2359
+ region: [
2360
+ { cmds: [...current[0].cmds, ...shape.cmds], fillRule: "nonzero", simple: false }
2361
+ ],
2362
+ exact: false
2363
+ };
2364
+ }
2365
+ return { region: current, exact: false };
2366
+ }
2367
+ case "xor": {
2368
+ if (!current) {
2369
+ if (isComposable(shape)) {
2370
+ return { region: [invertClipShape(shape)], exact: true };
2371
+ }
2372
+ return { region: [shape], exact: false };
2373
+ }
2374
+ if (current.length === 1 && isComposable(current[0]) && isComposable(shape)) {
2375
+ return {
2376
+ region: [
2377
+ { cmds: [...current[0].cmds, ...shape.cmds], fillRule: "evenodd", simple: false }
2378
+ ],
2379
+ exact: true
2380
+ };
2381
+ }
2382
+ if (isComposable(shape)) {
2383
+ return { region: [...current, invertClipShape(shape)], exact: false };
2384
+ }
2385
+ return { region: [...current, shape], exact: false };
2386
+ }
2387
+ case "complement": {
2388
+ if (!current) {
2389
+ return { region: [emptyClipShape()], exact: true };
2390
+ }
2391
+ if (current.length === 1 && isComposable(current[0])) {
2392
+ return { region: [shape, invertClipShape(current[0])], exact: true };
2393
+ }
2394
+ return { region: [shape], exact: false };
1419
2395
  }
1420
2396
  }
1421
- const masks = parseBitfieldMasks(view, bmiOffset, headerSize, compression, bitCount);
1422
- if (!masks) {
1423
- return null;
2397
+ }
2398
+ function combineClipRegions(current, incoming, op) {
2399
+ if (op === "replace") {
2400
+ return { region: incoming, exact: true };
1424
2401
  }
1425
- const out = new Uint8ClampedArray(width * height * 4);
1426
- if (compression === BI_RLE8 || compression === BI_RLE4) {
1427
- const setPixel = (x, y, r, g, b, a) => {
1428
- const dstPx = (y * width + x) * 4;
1429
- out[dstPx] = r;
1430
- out[dstPx + 1] = g;
1431
- out[dstPx + 2] = b;
1432
- out[dstPx + 3] = a;
1433
- };
1434
- return decodeRleBitmap(
1435
- view,
1436
- bitsOffset,
1437
- bitsSize,
1438
- width,
1439
- height,
1440
- topDown,
1441
- compression === BI_RLE4,
1442
- colorTable,
1443
- out,
1444
- setPixel
1445
- );
2402
+ if (incoming && incoming.length === 1) {
2403
+ return combineClip(current, incoming[0], op);
1446
2404
  }
1447
- decodeUncompressedRows(
1448
- view,
1449
- bitsOffset,
1450
- width,
1451
- height,
1452
- topDown,
1453
- bitCount,
1454
- colorTable,
1455
- masks,
1456
- out
1457
- );
1458
- return new ImageData(out, width, height);
1459
- }
1460
-
1461
- // src/emf-gdi-draw-text-bitmap.ts
1462
- function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
1463
- const { ctx, view, state } = rCtx;
1464
- if (recSize >= 76) {
1465
- const refX = view.getInt32(dataOff + 28, true);
1466
- const refY = view.getInt32(dataOff + 32, true);
1467
- const nChars = view.getUint32(dataOff + 36, true);
1468
- const offString = view.getUint32(dataOff + 40, true);
1469
- const maxOffset = view.byteLength;
1470
- if (nChars > 0 && offString > 0 && offset + offString + nChars * 2 <= maxOffset) {
1471
- const text = readUtf16LE(view, offset + offString, nChars);
1472
- if (text.length > 0) {
1473
- const fontScale = Math.abs(gmh(rCtx, 1));
1474
- applyFont(ctx, state, fontScale);
1475
- ctx.fillStyle = state.textColor;
1476
- const vAlign = state.textAlign & 24;
1477
- const alignBaseline = vAlign === 24 ? "alphabetic" : vAlign === 8 ? "bottom" : "top";
1478
- let alignHoriz = "left";
1479
- if (state.textAlign & 6) {
1480
- alignHoriz = "center";
1481
- }
1482
- if (state.textAlign & 2) {
1483
- alignHoriz = "right";
1484
- }
1485
- ctx.textBaseline = alignBaseline;
1486
- ctx.textAlign = alignHoriz;
1487
- if (state.bkMode === 2) {
1488
- const measured = ctx.measureText(text);
1489
- const bgH = fontSizePx(state, fontScale);
1490
- ctx.fillStyle = state.bkColor;
1491
- ctx.fillRect(gmx(rCtx, refX), gmy(rCtx, refY) - bgH, measured.width, bgH);
1492
- ctx.fillStyle = state.textColor;
2405
+ if (!incoming) {
2406
+ switch (op) {
2407
+ case "intersect":
2408
+ return { region: current, exact: true };
2409
+ case "union":
2410
+ return { region: null, exact: true };
2411
+ case "exclude":
2412
+ return { region: [emptyClipShape()], exact: true };
2413
+ case "xor":
2414
+ case "complement": {
2415
+ if (!current) {
2416
+ return { region: [emptyClipShape()], exact: true };
1493
2417
  }
1494
- ctx.fillText(text, gmx(rCtx, refX), gmy(rCtx, refY));
1495
- if (state.fontUnderline || state.fontStrikeOut) {
1496
- const w = ctx.measureText(text).width;
1497
- const baseX = gmx(rCtx, refX);
1498
- const startX = alignHoriz === "center" ? baseX - w / 2 : alignHoriz === "right" ? baseX - w : baseX;
1499
- drawTextDecorations(ctx, state, startX, gmy(rCtx, refY), w, fontScale);
2418
+ if (current.length === 1) {
2419
+ return combineClip(null, current[0], "exclude");
1500
2420
  }
2421
+ return { region: current, exact: false };
1501
2422
  }
1502
2423
  }
1503
2424
  }
1504
- return true;
2425
+ switch (op) {
2426
+ case "intersect":
2427
+ return { region: current ? [...current, ...incoming] : incoming, exact: true };
2428
+ case "union":
2429
+ return { region: current, exact: false };
2430
+ case "xor":
2431
+ return { region: current ?? incoming, exact: false };
2432
+ case "exclude":
2433
+ return { region: current, exact: false };
2434
+ case "complement": {
2435
+ if (!current) {
2436
+ return { region: [emptyClipShape()], exact: true };
2437
+ }
2438
+ if (current.length === 1) {
2439
+ return combineClip(incoming, current[0], "exclude");
2440
+ }
2441
+ return { region: incoming, exact: false };
2442
+ }
2443
+ }
1505
2444
  }
1506
- var ROP_PATCOPY = 15728673;
1507
- var BS_NULL = 1;
1508
- function handleBitBlt(rCtx, offset, dataOff, recSize) {
1509
- const { ctx, view, state } = rCtx;
1510
- if (recSize >= 96) {
1511
- const dstX = view.getInt32(dataOff + 16, true);
1512
- const dstY = view.getInt32(dataOff + 20, true);
1513
- const dstW = view.getInt32(dataOff + 24, true);
1514
- const dstH = view.getInt32(dataOff + 28, true);
1515
- const rop = view.getUint32(dataOff + 32, true);
1516
- const offBmiSrc = view.getUint32(dataOff + 76, true);
1517
- const cbBmiSrc = view.getUint32(dataOff + 80, true);
1518
- const offBitsSrc = view.getUint32(dataOff + 84, true);
1519
- const cbBitsSrc = view.getUint32(dataOff + 88, true);
1520
- if (offBmiSrc === 0 && rop === ROP_PATCOPY) {
1521
- const prevFill = ctx.fillStyle;
1522
- if (state.brushStyle !== BS_NULL) {
1523
- ctx.fillStyle = state.brushColor;
1524
- ctx.fillRect(gmx(rCtx, dstX), gmy(rCtx, dstY), gmw(rCtx, dstW), gmh(rCtx, dstH));
1525
- }
1526
- ctx.fillStyle = prevFill;
1527
- return true;
2445
+ function replayClipCmds(ctx, cmds) {
2446
+ for (const c of cmds) {
2447
+ switch (c.op) {
2448
+ case "rect":
2449
+ ctx.rect(c.x, c.y, c.w, c.h);
2450
+ break;
2451
+ case "moveTo":
2452
+ ctx.moveTo(c.x, c.y);
2453
+ break;
2454
+ case "lineTo":
2455
+ ctx.lineTo(c.x, c.y);
2456
+ break;
2457
+ case "bezierCurveTo":
2458
+ ctx.bezierCurveTo(c.cp1x, c.cp1y, c.cp2x, c.cp2y, c.x, c.y);
2459
+ break;
2460
+ case "closePath":
2461
+ ctx.closePath();
2462
+ break;
1528
2463
  }
1529
- if (offBmiSrc > 0 && cbBmiSrc > 0 && offBitsSrc > 0 && cbBitsSrc > 0) {
1530
- const imageData = decodeDibToImageData(
1531
- view,
1532
- offset + offBmiSrc,
1533
- offset + offBitsSrc,
1534
- cbBitsSrc
1535
- );
1536
- if (imageData) {
1537
- const temp = createTempCanvas(imageData.width, imageData.height);
1538
- if (temp) {
1539
- temp.ctx.putImageData(imageData, 0, 0);
1540
- canvasDrawImage(
1541
- ctx,
1542
- temp.canvas,
1543
- gmx(rCtx, dstX),
1544
- gmy(rCtx, dstY),
1545
- gmw(rCtx, dstW),
1546
- gmh(rCtx, dstH)
1547
- );
1548
- }
1549
- }
2464
+ }
2465
+ }
2466
+ function applyClipShapes(ctx, shapes) {
2467
+ for (const s of shapes) {
2468
+ ctx.beginPath();
2469
+ replayClipCmds(ctx, s.cmds);
2470
+ try {
2471
+ ctx.clip(s.fillRule);
2472
+ } catch {
1550
2473
  }
1551
2474
  }
1552
- return true;
1553
2475
  }
1554
- function handleStretchDibits(rCtx, offset, dataOff, recSize) {
1555
- const { ctx, view } = rCtx;
1556
- if (recSize >= 80) {
1557
- const dstX = view.getInt32(dataOff + 16, true);
1558
- const dstY = view.getInt32(dataOff + 20, true);
1559
- const dstW = view.getInt32(dataOff + 64, true);
1560
- const dstH = view.getInt32(dataOff + 68, true);
1561
- const offBmiSrc = view.getUint32(dataOff + 40, true);
1562
- const cbBmiSrc = view.getUint32(dataOff + 44, true);
1563
- const offBitsSrc = view.getUint32(dataOff + 48, true);
1564
- const cbBitsSrc = view.getUint32(dataOff + 52, true);
1565
- if (offBmiSrc > 0 && cbBmiSrc > 0 && offBitsSrc > 0 && cbBitsSrc > 0) {
1566
- const imageData = decodeDibToImageData(
1567
- view,
1568
- offset + offBmiSrc,
1569
- offset + offBitsSrc,
1570
- cbBitsSrc
1571
- );
1572
- if (imageData) {
1573
- const temp = createTempCanvas(imageData.width, imageData.height);
1574
- if (temp) {
1575
- temp.ctx.putImageData(imageData, 0, 0);
1576
- canvasDrawImage(
1577
- ctx,
1578
- temp.canvas,
1579
- gmx(rCtx, dstX),
1580
- gmy(rCtx, dstY),
1581
- gmw(rCtx, dstW),
1582
- gmh(rCtx, dstH)
1583
- );
1584
- }
1585
- }
2476
+ function reapplyClipRegion(holder, region, identityTransform = false) {
2477
+ const { ctx } = holder;
2478
+ while (holder.clipSaveDepth > 0) {
2479
+ ctx.restore();
2480
+ holder.clipSaveDepth--;
2481
+ }
2482
+ if (region) {
2483
+ ctx.save();
2484
+ holder.clipSaveDepth = 1;
2485
+ if (identityTransform) {
2486
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
1586
2487
  }
2488
+ applyClipShapes(ctx, region);
1587
2489
  }
1588
- return true;
1589
2490
  }
2491
+
2492
+ // src/emf-gdi-clip-records.ts
1590
2493
  function gdiCombineClip(rCtx, shape, op) {
1591
2494
  const { ctx } = rCtx;
1592
2495
  if (rCtx.clipUntracked && op !== "replace") {
@@ -1718,14 +2621,8 @@ function handleOffsetClipRgn(rCtx, dataOff, recSize) {
1718
2621
  }
1719
2622
  return true;
1720
2623
  }
1721
- function handleEmfGdiTextBitmapRecord(rCtx, recType, offset, dataOff, recSize) {
2624
+ function handleEmfGdiClipRecord(rCtx, recType, dataOff, recSize) {
1722
2625
  switch (recType) {
1723
- case EMR_EXTTEXTOUTW:
1724
- return handleExtTextOutW(rCtx, offset, dataOff, recSize);
1725
- case EMR_BITBLT:
1726
- return handleBitBlt(rCtx, offset, dataOff, recSize);
1727
- case EMR_STRETCHDIBITS:
1728
- return handleStretchDibits(rCtx, offset, dataOff, recSize);
1729
2626
  case EMR_INTERSECTCLIPRECT:
1730
2627
  return handleIntersectClipRect(rCtx, dataOff, recSize);
1731
2628
  case EMR_EXTSELECTCLIPRGN:
@@ -1739,12 +2636,36 @@ function handleEmfGdiTextBitmapRecord(rCtx, recType, offset, dataOff, recSize) {
1739
2636
  }
1740
2637
  }
1741
2638
 
2639
+ // src/emf-gdi-draw-text-bitmap.ts
2640
+ function handleEmfGdiTextBitmapRecord(rCtx, recType, offset, dataOff, recSize) {
2641
+ return handleEmfGdiDrawTextRecord(rCtx, recType, offset, dataOff, recSize) || handleEmfGdiBitmapRecord(rCtx, recType, offset, dataOff, recSize) || handleEmfGdiClipRecord(rCtx, recType, dataOff, recSize);
2642
+ }
2643
+
1742
2644
  // src/emf-gdi-draw-handlers.ts
1743
2645
  function handleEmfGdiDrawRecord(rCtx, recType, offset, dataOff, recSize) {
1744
2646
  return handleEmfGdiShapeRecord(rCtx, recType, dataOff, recSize) || handleEmfGdiTextBitmapRecord(rCtx, recType, offset, dataOff, recSize);
1745
2647
  }
1746
2648
 
1747
2649
  // src/emf-gdi-polypolygon-helpers.ts
2650
+ function buildPolyPolygonPath(target, rCtx, readPoint, countsOff, numPolys, totalPoints, close) {
2651
+ const { view } = rCtx;
2652
+ let pIdx = 0;
2653
+ for (let p = 0; p < numPolys; p++) {
2654
+ const count = view.getUint32(countsOff + p * 4, true);
2655
+ for (let i = 0; i < count && pIdx < totalPoints; i++) {
2656
+ const pt = readPoint(pIdx);
2657
+ if (i === 0) {
2658
+ target.moveTo(pt.x, pt.y);
2659
+ } else {
2660
+ target.lineTo(pt.x, pt.y);
2661
+ }
2662
+ pIdx++;
2663
+ }
2664
+ if (close) {
2665
+ target.closePath();
2666
+ }
2667
+ }
2668
+ }
1748
2669
  function handlePolyPolygon32(rCtx, offset, dataOff, recSize) {
1749
2670
  const { ctx, view, state, inPath } = rCtx;
1750
2671
  const numPolys = view.getUint32(dataOff + 16, true);
@@ -1757,33 +2678,25 @@ function handlePolyPolygon32(rCtx, offset, dataOff, recSize) {
1757
2678
  if (ptOff + totalPoints * 8 > offset + recSize) {
1758
2679
  return;
1759
2680
  }
2681
+ const readPoint = (pIdx) => gmapPoint(rCtx, view.getInt32(ptOff + pIdx * 8, true), view.getInt32(ptOff + pIdx * 8 + 4, true));
2682
+ const build = (target) => {
2683
+ buildPolyPolygonPath(target, rCtx, readPoint, countsOff, numPolys, totalPoints, true);
2684
+ };
1760
2685
  if (!inPath) {
1761
2686
  ctx.beginPath();
1762
2687
  }
1763
- let pIdx = 0;
1764
- for (let p = 0; p < numPolys; p++) {
1765
- const count = view.getUint32(countsOff + p * 4, true);
1766
- for (let i = 0; i < count && pIdx < totalPoints; i++) {
1767
- const px = view.getInt32(ptOff + pIdx * 8, true);
1768
- const py = view.getInt32(ptOff + pIdx * 8 + 4, true);
1769
- if (i === 0) {
1770
- ctx.moveTo(gmx(rCtx, px), gmy(rCtx, py));
1771
- } else {
1772
- ctx.lineTo(gmx(rCtx, px), gmy(rCtx, py));
1773
- }
1774
- pIdx++;
1775
- }
1776
- ctx.closePath();
1777
- }
2688
+ build(ctx);
1778
2689
  if (!inPath) {
1779
- applyBrush(ctx, state);
1780
- ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
1781
- applyPen(ctx, state);
1782
- ctx.stroke();
2690
+ const buildWithPath = (target) => {
2691
+ target.beginPath();
2692
+ build(target);
2693
+ };
2694
+ fillShapeExactOrFast(rCtx, buildWithPath, state.polyFillMode === 2 ? "nonzero" : "evenodd");
2695
+ strokeShapeExactOrFast(rCtx, buildWithPath);
1783
2696
  }
1784
2697
  }
1785
2698
  function handlePolyPolyline32(rCtx, offset, dataOff, recSize) {
1786
- const { ctx, view, state, inPath } = rCtx;
2699
+ const { ctx, view, inPath } = rCtx;
1787
2700
  const numPolys = view.getUint32(dataOff + 16, true);
1788
2701
  const totalPoints = view.getUint32(dataOff + 20, true);
1789
2702
  if (numPolys === 0 || numPolys >= 1e4 || totalPoints >= 1e5) {
@@ -1794,26 +2707,20 @@ function handlePolyPolyline32(rCtx, offset, dataOff, recSize) {
1794
2707
  if (ptOff + totalPoints * 8 > offset + recSize) {
1795
2708
  return;
1796
2709
  }
2710
+ const readPoint = (pIdx) => gmapPoint(rCtx, view.getInt32(ptOff + pIdx * 8, true), view.getInt32(ptOff + pIdx * 8 + 4, true));
2711
+ const build = (target) => {
2712
+ buildPolyPolygonPath(target, rCtx, readPoint, countsOff, numPolys, totalPoints, false);
2713
+ };
1797
2714
  if (!inPath) {
1798
2715
  ctx.beginPath();
1799
2716
  }
1800
- let pIdx = 0;
1801
- for (let p = 0; p < numPolys; p++) {
1802
- const count = view.getUint32(countsOff + p * 4, true);
1803
- for (let i = 0; i < count && pIdx < totalPoints; i++) {
1804
- const px = view.getInt32(ptOff + pIdx * 8, true);
1805
- const py = view.getInt32(ptOff + pIdx * 8 + 4, true);
1806
- if (i === 0) {
1807
- ctx.moveTo(gmx(rCtx, px), gmy(rCtx, py));
1808
- } else {
1809
- ctx.lineTo(gmx(rCtx, px), gmy(rCtx, py));
1810
- }
1811
- pIdx++;
1812
- }
1813
- }
2717
+ build(ctx);
1814
2718
  if (!inPath) {
1815
- applyPen(ctx, state);
1816
- ctx.stroke();
2719
+ const buildWithPath = (target) => {
2720
+ target.beginPath();
2721
+ build(target);
2722
+ };
2723
+ strokeShapeExactOrFast(rCtx, buildWithPath);
1817
2724
  }
1818
2725
  }
1819
2726
  function handlePolyPolygon16(rCtx, offset, dataOff, recSize) {
@@ -1828,29 +2735,21 @@ function handlePolyPolygon16(rCtx, offset, dataOff, recSize) {
1828
2735
  if (ptOff + totalPoints * 4 > offset + recSize) {
1829
2736
  return;
1830
2737
  }
2738
+ const readPoint = (pIdx) => gmapPoint(rCtx, view.getInt16(ptOff + pIdx * 4, true), view.getInt16(ptOff + pIdx * 4 + 2, true));
2739
+ const build = (target) => {
2740
+ buildPolyPolygonPath(target, rCtx, readPoint, countsOff, numPolys, totalPoints, true);
2741
+ };
1831
2742
  if (!inPath) {
1832
2743
  ctx.beginPath();
1833
2744
  }
1834
- let pIdx = 0;
1835
- for (let p = 0; p < numPolys; p++) {
1836
- const count = view.getUint32(countsOff + p * 4, true);
1837
- for (let i = 0; i < count && pIdx < totalPoints; i++) {
1838
- const px = view.getInt16(ptOff + pIdx * 4, true);
1839
- const py = view.getInt16(ptOff + pIdx * 4 + 2, true);
1840
- if (i === 0) {
1841
- ctx.moveTo(gmx(rCtx, px), gmy(rCtx, py));
1842
- } else {
1843
- ctx.lineTo(gmx(rCtx, px), gmy(rCtx, py));
1844
- }
1845
- pIdx++;
1846
- }
1847
- ctx.closePath();
1848
- }
2745
+ build(ctx);
1849
2746
  if (!inPath) {
1850
- applyBrush(ctx, state);
1851
- ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
1852
- applyPen(ctx, state);
1853
- ctx.stroke();
2747
+ const buildWithPath = (target) => {
2748
+ target.beginPath();
2749
+ build(target);
2750
+ };
2751
+ fillShapeExactOrFast(rCtx, buildWithPath, state.polyFillMode === 2 ? "nonzero" : "evenodd");
2752
+ strokeShapeExactOrFast(rCtx, buildWithPath);
1854
2753
  }
1855
2754
  }
1856
2755
 
@@ -1868,43 +2767,44 @@ function handlePoly32(rCtx, recType, offset, dataOff, recSize) {
1868
2767
  const isPolygon = recType === EMR_POLYGON;
1869
2768
  const isBezier = recType === EMR_POLYBEZIER || recType === EMR_POLYBEZIERTO;
1870
2769
  const isTo = recType === EMR_POLYBEZIERTO || recType === EMR_POLYLINETO;
1871
- if (!inPath) {
1872
- ctx.beginPath();
1873
- }
1874
- if (!isTo) {
1875
- ctx.moveTo(gmx(rCtx, view.getInt32(ptOff, true)), gmy(rCtx, view.getInt32(ptOff + 4, true)));
1876
- }
1877
- let i = isTo ? 0 : 1;
1878
- if (isBezier) {
1879
- while (i + 2 < count) {
1880
- ctx.bezierCurveTo(
1881
- gmx(rCtx, view.getInt32(ptOff + i * 8, true)),
1882
- gmy(rCtx, view.getInt32(ptOff + i * 8 + 4, true)),
1883
- gmx(rCtx, view.getInt32(ptOff + (i + 1) * 8, true)),
1884
- gmy(rCtx, view.getInt32(ptOff + (i + 1) * 8 + 4, true)),
1885
- gmx(rCtx, view.getInt32(ptOff + (i + 2) * 8, true)),
1886
- gmy(rCtx, view.getInt32(ptOff + (i + 2) * 8 + 4, true))
1887
- );
1888
- i += 3;
2770
+ const pt = (i) => gmapPoint(rCtx, view.getInt32(ptOff + i * 8, true), view.getInt32(ptOff + i * 8 + 4, true));
2771
+ const build = (target) => {
2772
+ if (!isTo) {
2773
+ const p0 = pt(0);
2774
+ target.moveTo(p0.x, p0.y);
2775
+ }
2776
+ let i = isTo ? 0 : 1;
2777
+ if (isBezier) {
2778
+ while (i + 2 < count) {
2779
+ const p1 = pt(i);
2780
+ const p2 = pt(i + 1);
2781
+ const p3 = pt(i + 2);
2782
+ target.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
2783
+ i += 3;
2784
+ }
2785
+ } else {
2786
+ for (; i < count; i++) {
2787
+ const p = pt(i);
2788
+ target.lineTo(p.x, p.y);
2789
+ }
1889
2790
  }
1890
- } else {
1891
- for (; i < count; i++) {
1892
- ctx.lineTo(
1893
- gmx(rCtx, view.getInt32(ptOff + i * 8, true)),
1894
- gmy(rCtx, view.getInt32(ptOff + i * 8 + 4, true))
1895
- );
2791
+ if (isPolygon) {
2792
+ target.closePath();
1896
2793
  }
2794
+ };
2795
+ if (!inPath) {
2796
+ ctx.beginPath();
1897
2797
  }
1898
- if (isPolygon) {
1899
- ctx.closePath();
1900
- }
2798
+ build(ctx);
1901
2799
  if (!inPath) {
2800
+ const buildWithPath = (target) => {
2801
+ target.beginPath();
2802
+ build(target);
2803
+ };
1902
2804
  if (isPolygon) {
1903
- applyBrush(ctx, state);
1904
- ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
2805
+ fillShapeExactOrFast(rCtx, buildWithPath, state.polyFillMode === 2 ? "nonzero" : "evenodd");
1905
2806
  }
1906
- applyPen(ctx, state);
1907
- ctx.stroke();
2807
+ strokeShapeExactOrFast(rCtx, buildWithPath);
1908
2808
  }
1909
2809
  if (count > 0) {
1910
2810
  const last = count - 1;
@@ -1926,43 +2826,44 @@ function handlePoly16(rCtx, recType, offset, dataOff, recSize) {
1926
2826
  const isPolygon = recType === EMR_POLYGON16;
1927
2827
  const isBezier = recType === EMR_POLYBEZIER16 || recType === EMR_POLYBEZIERTO16;
1928
2828
  const isTo = recType === EMR_POLYBEZIERTO16 || recType === EMR_POLYLINETO16;
1929
- if (!inPath) {
1930
- ctx.beginPath();
1931
- }
1932
- if (!isTo) {
1933
- ctx.moveTo(gmx(rCtx, view.getInt16(ptOff, true)), gmy(rCtx, view.getInt16(ptOff + 2, true)));
1934
- }
1935
- let i = isTo ? 0 : 1;
1936
- if (isBezier) {
1937
- while (i + 2 < count) {
1938
- ctx.bezierCurveTo(
1939
- gmx(rCtx, view.getInt16(ptOff + i * 4, true)),
1940
- gmy(rCtx, view.getInt16(ptOff + i * 4 + 2, true)),
1941
- gmx(rCtx, view.getInt16(ptOff + (i + 1) * 4, true)),
1942
- gmy(rCtx, view.getInt16(ptOff + (i + 1) * 4 + 2, true)),
1943
- gmx(rCtx, view.getInt16(ptOff + (i + 2) * 4, true)),
1944
- gmy(rCtx, view.getInt16(ptOff + (i + 2) * 4 + 2, true))
1945
- );
1946
- i += 3;
2829
+ const pt = (i) => gmapPoint(rCtx, view.getInt16(ptOff + i * 4, true), view.getInt16(ptOff + i * 4 + 2, true));
2830
+ const build = (target) => {
2831
+ if (!isTo) {
2832
+ const p0 = pt(0);
2833
+ target.moveTo(p0.x, p0.y);
2834
+ }
2835
+ let i = isTo ? 0 : 1;
2836
+ if (isBezier) {
2837
+ while (i + 2 < count) {
2838
+ const p1 = pt(i);
2839
+ const p2 = pt(i + 1);
2840
+ const p3 = pt(i + 2);
2841
+ target.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
2842
+ i += 3;
2843
+ }
2844
+ } else {
2845
+ for (; i < count; i++) {
2846
+ const p = pt(i);
2847
+ target.lineTo(p.x, p.y);
2848
+ }
1947
2849
  }
1948
- } else {
1949
- for (; i < count; i++) {
1950
- ctx.lineTo(
1951
- gmx(rCtx, view.getInt16(ptOff + i * 4, true)),
1952
- gmy(rCtx, view.getInt16(ptOff + i * 4 + 2, true))
1953
- );
2850
+ if (isPolygon) {
2851
+ target.closePath();
1954
2852
  }
2853
+ };
2854
+ if (!inPath) {
2855
+ ctx.beginPath();
1955
2856
  }
1956
- if (isPolygon) {
1957
- ctx.closePath();
1958
- }
2857
+ build(ctx);
1959
2858
  if (!inPath) {
2859
+ const buildWithPath = (target) => {
2860
+ target.beginPath();
2861
+ build(target);
2862
+ };
1960
2863
  if (isPolygon) {
1961
- applyBrush(ctx, state);
1962
- ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
2864
+ fillShapeExactOrFast(rCtx, buildWithPath, state.polyFillMode === 2 ? "nonzero" : "evenodd");
1963
2865
  }
1964
- applyPen(ctx, state);
1965
- ctx.stroke();
2866
+ strokeShapeExactOrFast(rCtx, buildWithPath);
1966
2867
  }
1967
2868
  if (count > 0) {
1968
2869
  const last = count - 1;
@@ -2015,16 +2916,24 @@ function handleEmfGdiPolyPathRecord(rCtx, recType, offset, dataOff, recSize) {
2015
2916
  case EMR_CLOSEFIGURE:
2016
2917
  ctx.closePath();
2017
2918
  return true;
2018
- case EMR_FILLPATH:
2019
- applyBrush(ctx, state);
2020
- ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
2919
+ case EMR_FILLPATH: {
2920
+ const fillRule = state.polyFillMode === 2 ? "nonzero" : "evenodd";
2921
+ if (!fillCurrentPathWithGdiPattern(rCtx, fillRule)) {
2922
+ applyBrush(ctx, state);
2923
+ ctx.fill(fillRule);
2924
+ }
2021
2925
  return true;
2022
- case EMR_STROKEANDFILLPATH:
2023
- applyBrush(ctx, state);
2024
- ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
2926
+ }
2927
+ case EMR_STROKEANDFILLPATH: {
2928
+ const fillRule = state.polyFillMode === 2 ? "nonzero" : "evenodd";
2929
+ if (!fillCurrentPathWithGdiPattern(rCtx, fillRule)) {
2930
+ applyBrush(ctx, state);
2931
+ ctx.fill(fillRule);
2932
+ }
2025
2933
  applyPen(ctx, state);
2026
2934
  ctx.stroke();
2027
2935
  return true;
2936
+ }
2028
2937
  case EMR_STROKEPATH:
2029
2938
  applyPen(ctx, state);
2030
2939
  ctx.stroke();
@@ -2089,12 +2998,30 @@ function handleEmfObjectRecord(rCtx, recType, dataOff, recSize) {
2089
2998
  case EMR_CREATEBRUSHINDIRECT: {
2090
2999
  if (recSize >= 24) {
2091
3000
  const ihBrush = view.getUint32(dataOff, true);
2092
- const brushStyle = view.getUint32(dataOff + 4, true);
2093
- const color = readColorRef(view, dataOff + 8);
3001
+ const brushStyle = view.getUint32(dataOff + 4, true);
3002
+ const color = readColorRef(view, dataOff + 8);
3003
+ const hatch = view.getUint32(dataOff + 12, true);
3004
+ rCtx.objectTable.set(ihBrush, {
3005
+ kind: "brush",
3006
+ style: brushStyle,
3007
+ color,
3008
+ ...brushStyle === 2 && hatch <= 5 ? { pattern: { kind: "hatch", hatch } } : {}
3009
+ });
3010
+ }
3011
+ return true;
3012
+ }
3013
+ case EMR_CREATEMONOBRUSH:
3014
+ case EMR_CREATEDIBPATTERNBRUSHPT: {
3015
+ if (recSize >= 32) {
3016
+ const ihBrush = view.getUint32(dataOff, true);
3017
+ const mono = recType === EMR_CREATEMONOBRUSH;
3018
+ const pattern = parsePatternBrush(view, dataOff - 8, dataOff, mono);
2094
3019
  rCtx.objectTable.set(ihBrush, {
2095
3020
  kind: "brush",
2096
- style: brushStyle,
2097
- color
3021
+ style: pattern ? mono ? 3 : 6 : 0,
3022
+ // Flat stand-in for fills that do not realise the pattern.
3023
+ color: "#808080",
3024
+ ...pattern ? { pattern } : {}
2098
3025
  });
2099
3026
  }
2100
3027
  return true;
@@ -2103,19 +3030,23 @@ function handleEmfObjectRecord(rCtx, recType, dataOff, recSize) {
2103
3030
  if (recSize >= 332) {
2104
3031
  const ihFont = view.getUint32(dataOff, true);
2105
3032
  const height = view.getInt32(dataOff + 4, true);
3033
+ const escapementTenthDeg = view.getInt32(dataOff + 12, true);
2106
3034
  const weight = view.getInt32(dataOff + 20, true);
2107
3035
  const italic = view.getUint8(dataOff + 24);
2108
3036
  const underline = view.getUint8(dataOff + 25);
2109
3037
  const strikeOut = view.getUint8(dataOff + 26);
2110
- const family = readUtf16LE(view, dataOff + 28, 32) || "sans-serif";
3038
+ const family = readUtf16LE(view, dataOff + 32, 32) || "sans-serif";
2111
3039
  rCtx.objectTable.set(ihFont, {
2112
3040
  kind: "font",
2113
- height: Math.abs(height),
3041
+ // Sign kept (not Math.abs'd): resolveFontPixelHeight() needs it
3042
+ // to distinguish GDI's cell-height vs character-height convention.
3043
+ height,
2114
3044
  weight,
2115
3045
  italic: italic !== 0,
2116
3046
  underline: underline !== 0,
2117
3047
  strikeOut: strikeOut !== 0,
2118
- family
3048
+ family,
3049
+ escapementTenthDeg
2119
3050
  });
2120
3051
  }
2121
3052
  return true;
@@ -2134,6 +3065,7 @@ function handleEmfObjectRecord(rCtx, recType, dataOff, recSize) {
2134
3065
  case "brush":
2135
3066
  state.brushStyle = obj.style;
2136
3067
  state.brushColor = obj.color;
3068
+ state.brushPattern = obj.pattern ?? null;
2137
3069
  break;
2138
3070
  case "font":
2139
3071
  state.fontHeight = obj.height;
@@ -2142,6 +3074,7 @@ function handleEmfObjectRecord(rCtx, recType, dataOff, recSize) {
2142
3074
  state.fontUnderline = obj.underline;
2143
3075
  state.fontStrikeOut = obj.strikeOut;
2144
3076
  state.fontFamily = obj.family;
3077
+ state.fontEscapementTenthDeg = obj.escapementTenthDeg ?? 0;
2145
3078
  break;
2146
3079
  }
2147
3080
  }
@@ -2310,15 +3243,22 @@ function defaultState() {
2310
3243
  penStyle: 0,
2311
3244
  brushColor: "#ffffff",
2312
3245
  brushStyle: 0,
3246
+ brushPattern: null,
3247
+ brushOrgX: 0,
3248
+ brushOrgY: 0,
3249
+ stretchBltMode: 1,
2313
3250
  textColor: "#000000",
2314
3251
  bkColor: "#ffffff",
2315
3252
  bkMode: 1,
2316
- fontHeight: 12,
3253
+ // Negative (character-height convention) so the no-font-selected default
3254
+ // resolves to exactly 12px, matching this library's historical fallback.
3255
+ fontHeight: -12,
2317
3256
  fontWeight: 400,
2318
3257
  fontItalic: false,
2319
3258
  fontFamily: "sans-serif",
2320
3259
  fontUnderline: false,
2321
3260
  fontStrikeOut: false,
3261
+ fontEscapementTenthDeg: 0,
2322
3262
  rop2: 13,
2323
3263
  curX: 0,
2324
3264
  curY: 0,
@@ -2435,7 +3375,19 @@ function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
2435
3375
  }
2436
3376
  return true;
2437
3377
  }
2438
- case EMR_SETSTRETCHBLTMODE:
3378
+ case EMR_SETSTRETCHBLTMODE: {
3379
+ if (recSize >= 12) {
3380
+ state.stretchBltMode = view.getUint32(dataOff, true);
3381
+ }
3382
+ return true;
3383
+ }
3384
+ case EMR_SETBRUSHORGEX: {
3385
+ if (recSize >= 16) {
3386
+ state.brushOrgX = view.getInt32(dataOff, true);
3387
+ state.brushOrgY = view.getInt32(dataOff + 4, true);
3388
+ }
3389
+ return true;
3390
+ }
2439
3391
  case EMR_SETMITERLIMIT:
2440
3392
  case EMR_SETTEXTALIGN: {
2441
3393
  if (recType === EMR_SETTEXTALIGN && recSize >= 12) {
@@ -2478,6 +3430,411 @@ function readPointFromView(view, offset, compressed) {
2478
3430
  };
2479
3431
  }
2480
3432
 
3433
+ // src/emf-plus-brush-gradient.ts
3434
+ var IDENTITY = [1, 0, 0, 1, 0, 0];
3435
+ function mulMatrix(a, b) {
3436
+ return [
3437
+ a[0] * b[0] + a[2] * b[1],
3438
+ a[1] * b[0] + a[3] * b[1],
3439
+ a[0] * b[2] + a[2] * b[3],
3440
+ a[1] * b[2] + a[3] * b[3],
3441
+ a[0] * b[4] + a[2] * b[5] + a[4],
3442
+ a[1] * b[4] + a[3] * b[5] + a[5]
3443
+ ];
3444
+ }
3445
+ function invertLinear(m) {
3446
+ const det = m[0] * m[3] - m[1] * m[2];
3447
+ if (!Number.isFinite(det) || Math.abs(det) < 1e-12) {
3448
+ return null;
3449
+ }
3450
+ return [m[3] / det, -m[1] / det, -m[2] / det, m[0] / det];
3451
+ }
3452
+ function lerpArgb(a, b, t) {
3453
+ const ch = (shift) => {
3454
+ const ca = a >>> shift & 255;
3455
+ const cb = b >>> shift & 255;
3456
+ return Math.round(ca + (cb - ca) * t);
3457
+ };
3458
+ return (ch(24) << 24 | ch(16) << 16 | ch(8) << 8 | ch(0)) >>> 0;
3459
+ }
3460
+ function piecewise(xs, ys, x) {
3461
+ const n = Math.min(xs.length, ys.length);
3462
+ if (n === 0) {
3463
+ return x;
3464
+ }
3465
+ if (x <= xs[0]) {
3466
+ return ys[0];
3467
+ }
3468
+ for (let i = 1; i < n; i++) {
3469
+ if (x <= xs[i]) {
3470
+ const span = xs[i] - xs[i - 1];
3471
+ const f = span > 0 ? (x - xs[i - 1]) / span : 1;
3472
+ return ys[i - 1] + (ys[i] - ys[i - 1]) * f;
3473
+ }
3474
+ }
3475
+ return ys[n - 1];
3476
+ }
3477
+ function stopColorAt(stops, t) {
3478
+ if (stops.length === 0 || stops.some((s) => s.argb === void 0)) {
3479
+ return null;
3480
+ }
3481
+ if (t <= stops[0].offset) {
3482
+ return stops[0].argb;
3483
+ }
3484
+ for (let i = 1; i < stops.length; i++) {
3485
+ const b = stops[i];
3486
+ if (t <= b.offset) {
3487
+ const a = stops[i - 1];
3488
+ const span = b.offset - a.offset;
3489
+ return lerpArgb(a.argb, b.argb, span > 0 ? (t - a.offset) / span : 1);
3490
+ }
3491
+ }
3492
+ return stops[stops.length - 1].argb;
3493
+ }
3494
+ function pathGradientColorAt(shape, x, y) {
3495
+ const { center, boundary, boundaryArgb } = shape;
3496
+ const n = boundary.length;
3497
+ const px = x - center.x;
3498
+ const py = y - center.y;
3499
+ let found = -1;
3500
+ let bestS = 0;
3501
+ let bestU = 0;
3502
+ const eps = 1e-9;
3503
+ for (let i = 0; i < n; i++) {
3504
+ const v0 = boundary[i];
3505
+ const v1 = boundary[(i + 1) % n];
3506
+ const ax = v0.x - center.x;
3507
+ const ay = v0.y - center.y;
3508
+ const ex = v1.x - v0.x;
3509
+ const ey = v1.y - v0.y;
3510
+ const det = ax * ey - ay * ex;
3511
+ if (Math.abs(det) < 1e-12) {
3512
+ continue;
3513
+ }
3514
+ const alpha = (px * ey - py * ex) / det;
3515
+ const beta = (ax * py - ay * px) / det;
3516
+ if (alpha < -eps || beta < -eps || beta > alpha + eps || alpha > 1 + eps) {
3517
+ continue;
3518
+ }
3519
+ found = i;
3520
+ bestS = Math.max(0, alpha);
3521
+ bestU = alpha > 0 ? Math.min(1, Math.max(0, beta / alpha)) : 0;
3522
+ }
3523
+ if (found < 0) {
3524
+ return null;
3525
+ }
3526
+ let s = bestS;
3527
+ if (shape.focus) {
3528
+ const f = Math.min(0.999, Math.max(0, (shape.focus.x + shape.focus.y) / 2));
3529
+ s = s <= f ? 0 : (s - f) / (1 - f);
3530
+ }
3531
+ const pos = 1 - s;
3532
+ if (shape.preset && shape.preset.positions.length > 0) {
3533
+ const { positions, argb } = shape.preset;
3534
+ if (pos <= positions[0]) {
3535
+ return argb[0];
3536
+ }
3537
+ for (let k = 1; k < positions.length; k++) {
3538
+ if (pos <= positions[k]) {
3539
+ const span = positions[k] - positions[k - 1];
3540
+ return lerpArgb(argb[k - 1], argb[k], span > 0 ? (pos - positions[k - 1]) / span : 1);
3541
+ }
3542
+ }
3543
+ return argb[positions.length - 1];
3544
+ }
3545
+ const c0 = boundaryArgb[found] ?? shape.centerArgb;
3546
+ const c1 = boundaryArgb[(found + 1) % n] ?? c0;
3547
+ const surround = lerpArgb(c0, c1, bestU);
3548
+ const factor = shape.blend ? piecewise(shape.blend.positions, shape.blend.factors, pos) : pos;
3549
+ return lerpArgb(surround, shape.centerArgb, Math.min(1, Math.max(0, factor)));
3550
+ }
3551
+ var MAX_TILE = 2048;
3552
+ function mirrorFlags(wrap) {
3553
+ return {
3554
+ x: wrap === "tile-flip-x" || wrap === "tile-flip-xy",
3555
+ y: wrap === "tile-flip-y" || wrap === "tile-flip-xy"
3556
+ };
3557
+ }
3558
+ function buildPattern(ctx, rect, tw, th, wrap, brush, delta, sampling, colorAt) {
3559
+ if (typeof ctx.createPattern !== "function") {
3560
+ return null;
3561
+ }
3562
+ const mirror = wrap === "clamp" ? { x: false, y: false } : mirrorFlags(wrap);
3563
+ const w = tw * (mirror.x ? 2 : 1);
3564
+ const h = th * (mirror.y ? 2 : 1);
3565
+ const temp = createTempCanvas(w, h);
3566
+ if (!temp) {
3567
+ return null;
3568
+ }
3569
+ const stepX = rect.w / tw;
3570
+ const stepY = rect.h / th;
3571
+ const offX = (i) => {
3572
+ const d = i % tw * stepX + (0.5 + sampling.phaseX) * stepX;
3573
+ return i < tw ? d : rect.w - sampling.lagX - (d - (0.5 + sampling.phaseX) * stepX) - 0.5 * stepX;
3574
+ };
3575
+ const offY = (j) => {
3576
+ const d = (j % th + 0.5) * stepY;
3577
+ return j < th ? d : rect.h - sampling.lagY - d;
3578
+ };
3579
+ const data = new Uint8ClampedArray(w * h * 4);
3580
+ for (let j = 0; j < h; j++) {
3581
+ const by = rect.y + offY(j);
3582
+ for (let i = 0; i < w; i++) {
3583
+ const c = colorAt(rect.x + offX(i), by);
3584
+ if (c === null) {
3585
+ continue;
3586
+ }
3587
+ const o = (j * w + i) * 4;
3588
+ data[o] = c >>> 16 & 255;
3589
+ data[o + 1] = c >>> 8 & 255;
3590
+ data[o + 2] = c & 255;
3591
+ data[o + 3] = c >>> 24 & 255;
3592
+ }
3593
+ }
3594
+ canvasPutImageData(temp.ctx, createImageDataCompat(data, w, h), 0, 0);
3595
+ const pattern = canvasCreatePattern(ctx, temp.canvas, wrap === "clamp" ? "no-repeat" : "repeat");
3596
+ if (!pattern || typeof pattern.setTransform !== "function") {
3597
+ return null;
3598
+ }
3599
+ const place = [stepX, 0, 0, stepY, rect.x + sampling.phaseX * stepX, rect.y];
3600
+ const m = mulMatrix([1, 0, 0, 1, delta.x, delta.y], mulMatrix(brush, place));
3601
+ try {
3602
+ pattern.setTransform({ a: m[0], b: m[1], c: m[2], d: m[3], e: m[4], f: m[5] });
3603
+ } catch {
3604
+ return null;
3605
+ }
3606
+ return pattern;
3607
+ }
3608
+ function halfPixelDelta(device) {
3609
+ const inv = invertLinear(device);
3610
+ if (!inv) {
3611
+ return { x: 0, y: 0 };
3612
+ }
3613
+ return { x: inv[0] * 0.5 + inv[2] * 0.5, y: inv[1] * 0.5 + inv[3] * 0.5 };
3614
+ }
3615
+ var SUPERSAMPLE = 4;
3616
+ function texelsFor(full, axis, length) {
3617
+ const scale = axis === "x" ? Math.hypot(full[0], full[1]) : Math.hypot(full[2], full[3]);
3618
+ return Math.max(2, Math.min(MAX_TILE, Math.ceil(Math.abs(length) * scale * SUPERSAMPLE)));
3619
+ }
3620
+ function linearGradientEndpoints(rect, transform) {
3621
+ const m = transform ?? IDENTITY;
3622
+ const map = (x, y) => ({
3623
+ x: m[0] * x + m[2] * y + m[4],
3624
+ y: m[1] * x + m[3] * y + m[5]
3625
+ });
3626
+ const p1 = map(rect.x, rect.y + rect.h / 2);
3627
+ const pe = map(rect.x + rect.w, rect.y + rect.h / 2);
3628
+ const nx = m[3];
3629
+ const ny = -m[2];
3630
+ const nn = nx * nx + ny * ny;
3631
+ if (nn < 1e-12) {
3632
+ return { x1: p1.x, y1: p1.y, x2: pe.x, y2: pe.y };
3633
+ }
3634
+ const t = ((pe.x - p1.x) * nx + (pe.y - p1.y) * ny) / nn;
3635
+ return { x1: p1.x, y1: p1.y, x2: p1.x + nx * t, y2: p1.y + ny * t };
3636
+ }
3637
+ function plainLinear(ctx, grad) {
3638
+ if (typeof ctx.createLinearGradient !== "function") {
3639
+ return null;
3640
+ }
3641
+ const pts = grad.rect ? linearGradientEndpoints(grad.rect, grad.transform) : grad;
3642
+ if (pts.x1 === pts.x2 && pts.y1 === pts.y2) {
3643
+ return null;
3644
+ }
3645
+ const g = ctx.createLinearGradient(pts.x1, pts.y1, pts.x2, pts.y2);
3646
+ for (const stop of grad.stops) {
3647
+ g.addColorStop(stop.offset, stop.color);
3648
+ }
3649
+ return g;
3650
+ }
3651
+ var SEAM_BIAS = 1e-5;
3652
+ var MAX_PERIODS = 2048;
3653
+ function surfaceSize3(ctx) {
3654
+ const canvas = ctx.canvas;
3655
+ const w = canvas?.width;
3656
+ const h = canvas?.height;
3657
+ return typeof w === "number" && typeof h === "number" && w > 0 && h > 0 ? { w, h } : null;
3658
+ }
3659
+ function tiledLinear(ctx, grad, rect, device) {
3660
+ const size = surfaceSize3(ctx);
3661
+ const inv = invertLinear(device);
3662
+ if (!size || !inv || typeof ctx.createLinearGradient !== "function") {
3663
+ return null;
3664
+ }
3665
+ const e = linearGradientEndpoints(rect, grad.transform);
3666
+ const delta = halfPixelDelta(device);
3667
+ const dx = e.x2 - e.x1;
3668
+ const dy = e.y2 - e.y1;
3669
+ const x1 = e.x1 + delta.x + dx * SEAM_BIAS;
3670
+ const y1 = e.y1 + delta.y + dy * SEAM_BIAS;
3671
+ const len2 = dx * dx + dy * dy;
3672
+ if (!(len2 > 1e-12)) {
3673
+ return null;
3674
+ }
3675
+ let tMin = Infinity;
3676
+ let tMax = -Infinity;
3677
+ for (const [cx, cy] of [
3678
+ [0, 0],
3679
+ [size.w, 0],
3680
+ [0, size.h],
3681
+ [size.w, size.h]
3682
+ ]) {
3683
+ const px = cx - device[4];
3684
+ const py = cy - device[5];
3685
+ const wx = inv[0] * px + inv[2] * py;
3686
+ const wy = inv[1] * px + inv[3] * py;
3687
+ const t = ((wx - x1) * dx + (wy - y1) * dy) / len2;
3688
+ tMin = Math.min(tMin, t);
3689
+ tMax = Math.max(tMax, t);
3690
+ }
3691
+ const k0 = Math.floor(tMin);
3692
+ const k1 = Math.max(k0 + 1, Math.ceil(tMax));
3693
+ if (k1 - k0 > MAX_PERIODS) {
3694
+ return null;
3695
+ }
3696
+ const g = ctx.createLinearGradient(x1 + dx * k0, y1 + dy * k0, x1 + dx * k1, y1 + dy * k1);
3697
+ const span = k1 - k0;
3698
+ const mirrorX = mirrorFlags(grad.wrapMode).x;
3699
+ const reversed = [...grad.stops].reverse();
3700
+ for (let k = k0; k < k1; k++) {
3701
+ const mirrored = mirrorX && (k % 2 + 2) % 2 === 1;
3702
+ for (const stop of mirrored ? reversed : grad.stops) {
3703
+ const local = mirrored ? 1 - stop.offset : stop.offset;
3704
+ g.addColorStop(Math.min(1, Math.max(0, (k - k0 + local) / span)), stop.color);
3705
+ }
3706
+ }
3707
+ return g;
3708
+ }
3709
+ function linearPaint(ctx, grad, device) {
3710
+ const rect = grad.rect;
3711
+ if (grad.wrapMode !== "clamp" && rect && rect.w !== 0) {
3712
+ const unrolled = tiledLinear(ctx, grad, rect, device);
3713
+ if (unrolled) {
3714
+ return unrolled;
3715
+ }
3716
+ if (stopColorAt(grad.stops, 0) !== null) {
3717
+ const brush = grad.transform ?? IDENTITY;
3718
+ const tw = texelsFor(mulMatrix(device, brush), "x", rect.w);
3719
+ const pattern = buildPattern(
3720
+ ctx,
3721
+ rect,
3722
+ tw,
3723
+ 1,
3724
+ grad.wrapMode,
3725
+ brush,
3726
+ halfPixelDelta(device),
3727
+ { phaseX: 0.5, lagX: 0, lagY: 0 },
3728
+ (bx) => stopColorAt(grad.stops, (bx - rect.x) / rect.w)
3729
+ );
3730
+ if (pattern) {
3731
+ return pattern;
3732
+ }
3733
+ }
3734
+ }
3735
+ return plainLinear(ctx, grad);
3736
+ }
3737
+ function boundaryBox(points) {
3738
+ if (points.length < 3) {
3739
+ return null;
3740
+ }
3741
+ let x0 = Infinity;
3742
+ let y0 = Infinity;
3743
+ let x1 = -Infinity;
3744
+ let y1 = -Infinity;
3745
+ for (const p of points) {
3746
+ x0 = Math.min(x0, p.x);
3747
+ y0 = Math.min(y0, p.y);
3748
+ x1 = Math.max(x1, p.x);
3749
+ y1 = Math.max(y1, p.y);
3750
+ }
3751
+ return x1 > x0 && y1 > y0 ? { x: x0, y: y0, w: x1 - x0, h: y1 - y0 } : null;
3752
+ }
3753
+ function pathPaint(ctx, shape, wrap, device) {
3754
+ const box = boundaryBox(shape.boundary);
3755
+ if (!box) {
3756
+ return null;
3757
+ }
3758
+ const brush = shape.transform ?? IDENTITY;
3759
+ const full = mulMatrix(device, brush);
3760
+ const pxX = Math.hypot(full[0], full[1]);
3761
+ const pxY = Math.hypot(full[2], full[3]);
3762
+ return buildPattern(
3763
+ ctx,
3764
+ box,
3765
+ texelsFor(full, "x", box.w),
3766
+ texelsFor(full, "y", box.h),
3767
+ wrap,
3768
+ brush,
3769
+ halfPixelDelta(device),
3770
+ { phaseX: 0, lagX: pxX > 0 ? 1 / pxX : 0, lagY: pxY > 0 ? 1 / pxY : 0 },
3771
+ (bx, by) => pathGradientColorAt(shape, bx, by)
3772
+ );
3773
+ }
3774
+ function radialFallback(ctx, grad) {
3775
+ if (!(grad.r > 0) || typeof ctx.createRadialGradient !== "function") {
3776
+ return null;
3777
+ }
3778
+ const g = ctx.createRadialGradient(grad.cx, grad.cy, 0, grad.cx, grad.cy, grad.r);
3779
+ for (const stop of grad.stops) {
3780
+ g.addColorStop(stop.offset, stop.color);
3781
+ }
3782
+ return g;
3783
+ }
3784
+ function createBrushGradient(ctx, grad, device = IDENTITY) {
3785
+ try {
3786
+ if (grad.type === "linear") {
3787
+ return linearPaint(ctx, grad, device);
3788
+ }
3789
+ if (grad.shape) {
3790
+ const pattern = pathPaint(ctx, grad.shape, grad.wrapMode, device);
3791
+ if (pattern) {
3792
+ return pattern;
3793
+ }
3794
+ emfLog("createBrushGradient: path gradient pattern unavailable, using the radial approximation");
3795
+ }
3796
+ return radialFallback(ctx, grad);
3797
+ } catch {
3798
+ return null;
3799
+ }
3800
+ }
3801
+
3802
+ // src/emf-plus-brush-texture.ts
3803
+ var IDENTITY2 = [1, 0, 0, 1, 0, 0];
3804
+ function packArgb(rgba, i) {
3805
+ return (rgba[i + 3] << 24 | rgba[i] << 16 | rgba[i + 1] << 8 | rgba[i + 2]) >>> 0;
3806
+ }
3807
+ function createBrushTexture(ctx, texture, device = IDENTITY2) {
3808
+ const { width, height, rgba } = texture;
3809
+ if (width <= 0 || height <= 0) {
3810
+ return null;
3811
+ }
3812
+ try {
3813
+ const brush = texture.transform ?? IDENTITY2;
3814
+ const colorAt = (bx, by) => {
3815
+ const ix = Math.floor(bx);
3816
+ const iy = Math.floor(by);
3817
+ if (ix < 0 || iy < 0 || ix >= width || iy >= height) {
3818
+ return null;
3819
+ }
3820
+ return packArgb(rgba, (iy * width + ix) * 4);
3821
+ };
3822
+ return buildPattern(
3823
+ ctx,
3824
+ { x: 0, y: 0, w: width, h: height },
3825
+ width,
3826
+ height,
3827
+ texture.wrapMode,
3828
+ brush,
3829
+ halfPixelDelta(device),
3830
+ { phaseX: 0, lagX: 0, lagY: 0 },
3831
+ colorAt
3832
+ );
3833
+ } catch {
3834
+ return null;
3835
+ }
3836
+ }
3837
+
2481
3838
  // src/emf-plus-path.ts
2482
3839
  function parseEmfPlusPath(data, off, maxLen) {
2483
3840
  if (maxLen < 12) {
@@ -2612,32 +3969,6 @@ function multiplyMatrix(m1, m2) {
2612
3969
  m1[4] * m2[1] + m1[5] * m2[3] + m2[5]
2613
3970
  ];
2614
3971
  }
2615
- function createBrushGradient(rCtx, grad) {
2616
- const ctx = rCtx.ctx;
2617
- try {
2618
- let g = null;
2619
- if (grad.type === "linear" && typeof ctx.createLinearGradient === "function") {
2620
- if (grad.x1 === grad.x2 && grad.y1 === grad.y2) {
2621
- return null;
2622
- }
2623
- g = ctx.createLinearGradient(grad.x1, grad.y1, grad.x2, grad.y2);
2624
- } else if (grad.type === "radial" && typeof ctx.createRadialGradient === "function") {
2625
- if (!(grad.r > 0)) {
2626
- return null;
2627
- }
2628
- g = ctx.createRadialGradient(grad.cx, grad.cy, 0, grad.cx, grad.cy, grad.r);
2629
- }
2630
- if (!g) {
2631
- return null;
2632
- }
2633
- for (const stop of grad.stops) {
2634
- g.addColorStop(stop.offset, stop.color);
2635
- }
2636
- return g;
2637
- } catch {
2638
- return null;
2639
- }
2640
- }
2641
3972
  function resolveBrushPaint(rCtx, flags, brushIdOrColor) {
2642
3973
  if (flags & 32768) {
2643
3974
  return argbToRgba(brushIdOrColor);
@@ -2645,11 +3976,17 @@ function resolveBrushPaint(rCtx, flags, brushIdOrColor) {
2645
3976
  const obj = rCtx.objectTable.get(brushIdOrColor & 255);
2646
3977
  if (obj && obj.kind === "plus-brush") {
2647
3978
  if (obj.gradient) {
2648
- const g = createBrushGradient(rCtx, obj.gradient);
3979
+ const g = createBrushGradient(rCtx.ctx, obj.gradient, plusWorldMatrix(rCtx));
2649
3980
  if (g) {
2650
3981
  return g;
2651
3982
  }
2652
3983
  }
3984
+ if (obj.texture) {
3985
+ const p = createBrushTexture(rCtx.ctx, obj.texture, plusWorldMatrix(rCtx));
3986
+ if (p) {
3987
+ return p;
3988
+ }
3989
+ }
2653
3990
  return obj.color;
2654
3991
  }
2655
3992
  return "rgba(0,0,0,1)";
@@ -2680,18 +4017,14 @@ function getPageUnitMultiplier(pageUnit, pageScale) {
2680
4017
  }
2681
4018
  return unitToPixel * pageScale;
2682
4019
  }
2683
- function applyPlusWorldTransform(rCtx) {
4020
+ function plusWorldMatrix(rCtx) {
2684
4021
  const wt = rCtx.worldTransform;
2685
- const m = getPageUnitMultiplier(rCtx.pageUnit, rCtx.pageScale);
2686
- const d = rCtx.dpiScale;
2687
- rCtx.ctx.setTransform(
2688
- wt[0] * m * d,
2689
- wt[1] * m * d,
2690
- wt[2] * m * d,
2691
- wt[3] * m * d,
2692
- wt[4] * m * d,
2693
- wt[5] * m * d
2694
- );
4022
+ const k = getPageUnitMultiplier(rCtx.pageUnit, rCtx.pageScale) * rCtx.dpiScale;
4023
+ return [wt[0] * k, wt[1] * k, wt[2] * k, wt[3] * k, wt[4] * k, wt[5] * k];
4024
+ }
4025
+ function applyPlusWorldTransform(rCtx) {
4026
+ const m = plusWorldMatrix(rCtx);
4027
+ rCtx.ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]);
2695
4028
  }
2696
4029
  function pushState(rCtx, stackId) {
2697
4030
  rCtx.saveStack.push({
@@ -3171,42 +4504,216 @@ function handleEmfPlusDrawRecord(rCtx, recType, recFlags, dataOff, recDataSize)
3171
4504
  } else {
3172
4505
  ctx.lineTo(pt.x, pt.y);
3173
4506
  }
3174
- pOff += ptSize;
4507
+ pOff += ptSize;
4508
+ }
4509
+ if (recFlags & 8192) {
4510
+ ctx.closePath();
4511
+ }
4512
+ ctx.stroke();
4513
+ }
4514
+ return true;
4515
+ }
4516
+ case EMFPLUS_FILLPOLYGON: {
4517
+ if (recDataSize >= 8) {
4518
+ const brushVal = view.getUint32(dataOff, true);
4519
+ const count = view.getUint32(dataOff + 4, true);
4520
+ const compressed = (recFlags & 16384) !== 0;
4521
+ const ptSize = compressed ? 4 : 8;
4522
+ ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
4523
+ applyPlusWorldTransform(rCtx);
4524
+ ctx.beginPath();
4525
+ let pOff = dataOff + 8;
4526
+ for (let i = 0; i < count && pOff + ptSize <= dataOff + recDataSize; i++) {
4527
+ const pt = readPointFromView(view, pOff, compressed);
4528
+ if (i === 0) {
4529
+ ctx.moveTo(pt.x, pt.y);
4530
+ } else {
4531
+ ctx.lineTo(pt.x, pt.y);
4532
+ }
4533
+ pOff += ptSize;
4534
+ }
4535
+ ctx.closePath();
4536
+ ctx.fill();
4537
+ }
4538
+ return true;
4539
+ }
4540
+ default:
4541
+ return false;
4542
+ }
4543
+ }
4544
+
4545
+ // src/emf-plus-bitmap-decoder.ts
4546
+ var PIXELFORMAT_24BPP_RGB = 137224;
4547
+ var PIXELFORMAT_32BPP_RGB = 139273;
4548
+ var PIXELFORMAT_32BPP_ARGB = 2498570;
4549
+ var PIXELFORMAT_32BPP_PARGB = 925707;
4550
+ function decodeEmfPlusBitmapPixels(view, pixelStart, width, height, stride, pixelFormat) {
4551
+ const absStride = Math.abs(stride);
4552
+ const topDown = stride > 0;
4553
+ const rowBytes = width * 4;
4554
+ const bmpRowStride = rowBytes + 3 & -4;
4555
+ const pixelDataSize = bmpRowStride * height;
4556
+ const bmpData = new Uint8Array(pixelDataSize);
4557
+ for (let y = 0; y < height; y++) {
4558
+ const srcRow = topDown ? y : height - 1 - y;
4559
+ const rowOff = pixelStart + srcRow * absStride;
4560
+ const dstRow = (height - 1 - y) * bmpRowStride;
4561
+ switch (pixelFormat) {
4562
+ case PIXELFORMAT_32BPP_ARGB:
4563
+ case PIXELFORMAT_32BPP_PARGB: {
4564
+ for (let x = 0; x < width; x++) {
4565
+ const off = rowOff + x * 4;
4566
+ if (off + 3 >= view.byteLength) {
4567
+ break;
4568
+ }
4569
+ let b = view.getUint8(off);
4570
+ let g = view.getUint8(off + 1);
4571
+ let r = view.getUint8(off + 2);
4572
+ const a = view.getUint8(off + 3);
4573
+ if (pixelFormat === PIXELFORMAT_32BPP_PARGB && a > 0 && a < 255) {
4574
+ r = Math.min(255, Math.round(r * 255 / a));
4575
+ g = Math.min(255, Math.round(g * 255 / a));
4576
+ b = Math.min(255, Math.round(b * 255 / a));
4577
+ }
4578
+ const di = dstRow + x * 4;
4579
+ bmpData[di] = b;
4580
+ bmpData[di + 1] = g;
4581
+ bmpData[di + 2] = r;
4582
+ bmpData[di + 3] = a;
4583
+ }
4584
+ break;
4585
+ }
4586
+ case PIXELFORMAT_32BPP_RGB: {
4587
+ for (let x = 0; x < width; x++) {
4588
+ const off = rowOff + x * 4;
4589
+ if (off + 3 >= view.byteLength) {
4590
+ break;
4591
+ }
4592
+ const di = dstRow + x * 4;
4593
+ bmpData[di] = view.getUint8(off);
4594
+ bmpData[di + 1] = view.getUint8(off + 1);
4595
+ bmpData[di + 2] = view.getUint8(off + 2);
4596
+ bmpData[di + 3] = 255;
4597
+ }
4598
+ break;
4599
+ }
4600
+ case PIXELFORMAT_24BPP_RGB: {
4601
+ for (let x = 0; x < width; x++) {
4602
+ const off = rowOff + x * 3;
4603
+ if (off + 2 >= view.byteLength) {
4604
+ break;
4605
+ }
4606
+ const di = dstRow + x * 4;
4607
+ bmpData[di] = view.getUint8(off);
4608
+ bmpData[di + 1] = view.getUint8(off + 1);
4609
+ bmpData[di + 2] = view.getUint8(off + 2);
4610
+ bmpData[di + 3] = 255;
4611
+ }
4612
+ break;
4613
+ }
4614
+ default:
4615
+ return null;
4616
+ }
4617
+ }
4618
+ return wrapBmpFile(bmpData, bmpRowStride, width, height, pixelDataSize);
4619
+ }
4620
+ function decodeEmfPlusBitmapPixelsToRgba(view, pixelStart, width, height, stride, pixelFormat) {
4621
+ const absStride = Math.abs(stride);
4622
+ const topDown = stride > 0;
4623
+ const rgba = new Uint8ClampedArray(width * height * 4);
4624
+ for (let y = 0; y < height; y++) {
4625
+ const srcRow = topDown ? y : height - 1 - y;
4626
+ const rowOff = pixelStart + srcRow * absStride;
4627
+ const dstRow = y * width * 4;
4628
+ switch (pixelFormat) {
4629
+ case PIXELFORMAT_32BPP_ARGB:
4630
+ case PIXELFORMAT_32BPP_PARGB: {
4631
+ for (let x = 0; x < width; x++) {
4632
+ const off = rowOff + x * 4;
4633
+ if (off + 3 >= view.byteLength) {
4634
+ break;
4635
+ }
4636
+ let b = view.getUint8(off);
4637
+ let g = view.getUint8(off + 1);
4638
+ let r = view.getUint8(off + 2);
4639
+ const a = view.getUint8(off + 3);
4640
+ if (pixelFormat === PIXELFORMAT_32BPP_PARGB && a > 0 && a < 255) {
4641
+ r = Math.min(255, Math.round(r * 255 / a));
4642
+ g = Math.min(255, Math.round(g * 255 / a));
4643
+ b = Math.min(255, Math.round(b * 255 / a));
4644
+ }
4645
+ const di = dstRow + x * 4;
4646
+ rgba[di] = r;
4647
+ rgba[di + 1] = g;
4648
+ rgba[di + 2] = b;
4649
+ rgba[di + 3] = a;
3175
4650
  }
3176
- if (recFlags & 8192) {
3177
- ctx.closePath();
4651
+ break;
4652
+ }
4653
+ case PIXELFORMAT_32BPP_RGB: {
4654
+ for (let x = 0; x < width; x++) {
4655
+ const off = rowOff + x * 4;
4656
+ if (off + 3 >= view.byteLength) {
4657
+ break;
4658
+ }
4659
+ const di = dstRow + x * 4;
4660
+ rgba[di] = view.getUint8(off + 2);
4661
+ rgba[di + 1] = view.getUint8(off + 1);
4662
+ rgba[di + 2] = view.getUint8(off);
4663
+ rgba[di + 3] = 255;
3178
4664
  }
3179
- ctx.stroke();
4665
+ break;
3180
4666
  }
3181
- return true;
3182
- }
3183
- case EMFPLUS_FILLPOLYGON: {
3184
- if (recDataSize >= 8) {
3185
- const brushVal = view.getUint32(dataOff, true);
3186
- const count = view.getUint32(dataOff + 4, true);
3187
- const compressed = (recFlags & 16384) !== 0;
3188
- const ptSize = compressed ? 4 : 8;
3189
- ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
3190
- applyPlusWorldTransform(rCtx);
3191
- ctx.beginPath();
3192
- let pOff = dataOff + 8;
3193
- for (let i = 0; i < count && pOff + ptSize <= dataOff + recDataSize; i++) {
3194
- const pt = readPointFromView(view, pOff, compressed);
3195
- if (i === 0) {
3196
- ctx.moveTo(pt.x, pt.y);
3197
- } else {
3198
- ctx.lineTo(pt.x, pt.y);
4667
+ case PIXELFORMAT_24BPP_RGB: {
4668
+ for (let x = 0; x < width; x++) {
4669
+ const off = rowOff + x * 3;
4670
+ if (off + 2 >= view.byteLength) {
4671
+ break;
3199
4672
  }
3200
- pOff += ptSize;
4673
+ const di = dstRow + x * 4;
4674
+ rgba[di] = view.getUint8(off + 2);
4675
+ rgba[di + 1] = view.getUint8(off + 1);
4676
+ rgba[di + 2] = view.getUint8(off);
4677
+ rgba[di + 3] = 255;
3201
4678
  }
3202
- ctx.closePath();
3203
- ctx.fill();
4679
+ break;
3204
4680
  }
3205
- return true;
4681
+ default:
4682
+ return null;
3206
4683
  }
3207
- default:
3208
- return false;
3209
4684
  }
4685
+ return rgba;
4686
+ }
4687
+ function wrapBmpFile(bmpData, bmpRowStride, width, height, pixelDataSize) {
4688
+ const fileHeaderSize = 14;
4689
+ const dibHeaderSize = 108;
4690
+ const fileSize = fileHeaderSize + dibHeaderSize + pixelDataSize;
4691
+ const bmpFile = new ArrayBuffer(fileSize);
4692
+ const bmpView = new DataView(bmpFile);
4693
+ const bmpBytes = new Uint8Array(bmpFile);
4694
+ bmpView.setUint8(0, 66);
4695
+ bmpView.setUint8(1, 77);
4696
+ bmpView.setUint32(2, fileSize, true);
4697
+ bmpView.setUint32(6, 0, true);
4698
+ bmpView.setUint32(10, fileHeaderSize + dibHeaderSize, true);
4699
+ bmpView.setUint32(14, dibHeaderSize, true);
4700
+ bmpView.setInt32(18, width, true);
4701
+ bmpView.setInt32(22, height, true);
4702
+ bmpView.setUint16(26, 1, true);
4703
+ bmpView.setUint16(28, 32, true);
4704
+ bmpView.setUint32(30, 3, true);
4705
+ bmpView.setUint32(34, pixelDataSize, true);
4706
+ bmpView.setInt32(38, 2835, true);
4707
+ bmpView.setInt32(42, 2835, true);
4708
+ bmpView.setUint32(46, 0, true);
4709
+ bmpView.setUint32(50, 0, true);
4710
+ bmpView.setUint32(54, 16711680, true);
4711
+ bmpView.setUint32(58, 65280, true);
4712
+ bmpView.setUint32(62, 255, true);
4713
+ bmpView.setUint32(66, 4278190080, true);
4714
+ bmpView.setUint32(70, 1934772034, true);
4715
+ bmpBytes.set(bmpData, fileHeaderSize + dibHeaderSize);
4716
+ return bmpFile;
3210
4717
  }
3211
4718
 
3212
4719
  // src/emf-plus-brush-parser.ts
@@ -3214,6 +4721,7 @@ var BRUSH_DATA_PATH = 1;
3214
4721
  var BRUSH_DATA_TRANSFORM = 2;
3215
4722
  var BRUSH_DATA_PRESET_COLORS = 4;
3216
4723
  var BRUSH_DATA_BLEND_FACTORS_H = 8;
4724
+ var BRUSH_DATA_FOCUS_SCALES = 64;
3217
4725
  var MAX_GRADIENT_ELEMENTS = 4096;
3218
4726
  function looksLikeGraphicsVersion(v) {
3219
4727
  return v >>> 12 === 900097;
@@ -3234,8 +4742,22 @@ function applyMatrix(m, x, y) {
3234
4742
  function clamp01(v) {
3235
4743
  return Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : 0;
3236
4744
  }
4745
+ function readWrapMode(raw) {
4746
+ switch (raw) {
4747
+ case 0:
4748
+ return "tile";
4749
+ case 1:
4750
+ return "tile-flip-x";
4751
+ case 2:
4752
+ return "tile-flip-y";
4753
+ case 3:
4754
+ return "tile-flip-xy";
4755
+ default:
4756
+ return "clamp";
4757
+ }
4758
+ }
3237
4759
  function normaliseStops(stops) {
3238
- return stops.map((s) => ({ offset: clamp01(s.offset), color: s.color })).sort((a, b) => a.offset - b.offset);
4760
+ return stops.map((s) => ({ ...s, offset: clamp01(s.offset) })).sort((a, b) => a.offset - b.offset);
3239
4761
  }
3240
4762
  function readPresetColors(view, off, end) {
3241
4763
  if (off + 4 > end) {
@@ -3253,9 +4775,11 @@ function readPresetColors(view, off, end) {
3253
4775
  }
3254
4776
  const stops = [];
3255
4777
  for (let i = 0; i < count; i++) {
4778
+ const argb = view.getUint32(colOff + i * 4, true);
3256
4779
  stops.push({
3257
4780
  offset: view.getFloat32(posOff + i * 4, true),
3258
- color: argbToRgba(view.getUint32(colOff + i * 4, true))
4781
+ color: argbToRgba(argb),
4782
+ argb
3259
4783
  });
3260
4784
  }
3261
4785
  return { stops, next };
@@ -3288,6 +4812,7 @@ function parseLinearGradient(view, b, end) {
3288
4812
  return null;
3289
4813
  }
3290
4814
  const flags = view.getUint32(b, true);
4815
+ const wrapMode = readWrapMode(view.getUint32(b + 4, true));
3291
4816
  const rx = view.getFloat32(b + 8, true);
3292
4817
  const ry = view.getFloat32(b + 12, true);
3293
4818
  const rw = view.getFloat32(b + 16, true);
@@ -3301,8 +4826,8 @@ function parseLinearGradient(view, b, end) {
3301
4826
  o += 24;
3302
4827
  }
3303
4828
  let stops = [
3304
- { offset: 0, color: argbToRgba(startArgb) },
3305
- { offset: 1, color: argbToRgba(endArgb) }
4829
+ { offset: 0, color: argbToRgba(startArgb), argb: startArgb },
4830
+ { offset: 1, color: argbToRgba(endArgb), argb: endArgb }
3306
4831
  ];
3307
4832
  if (flags & BRUSH_DATA_PRESET_COLORS) {
3308
4833
  const preset = readPresetColors(view, o, end);
@@ -3314,7 +4839,8 @@ function parseLinearGradient(view, b, end) {
3314
4839
  if (blend) {
3315
4840
  stops = blend.entries.map((e) => ({
3316
4841
  offset: e.pos,
3317
- color: lerpArgbToRgba(startArgb, endArgb, e.factor)
4842
+ color: lerpArgbToRgba(startArgb, endArgb, e.factor),
4843
+ argb: lerpArgb2(startArgb, endArgb, e.factor)
3318
4844
  }));
3319
4845
  }
3320
4846
  }
@@ -3336,15 +4862,75 @@ function parseLinearGradient(view, b, end) {
3336
4862
  y1: p1.y,
3337
4863
  x2: p2.x,
3338
4864
  y2: p2.y,
3339
- stops: normaliseStops(stops)
4865
+ stops: normaliseStops(stops),
4866
+ wrapMode,
4867
+ rect: { x: rx, y: ry, w: rw, h: rh },
4868
+ transform
3340
4869
  }
3341
4870
  };
3342
4871
  }
4872
+ function decodeTextureImage(view, off, end) {
4873
+ if (off + 28 > end) {
4874
+ return null;
4875
+ }
4876
+ const imageDataType = view.getUint32(off + 4, true);
4877
+ if (imageDataType !== 1) {
4878
+ return null;
4879
+ }
4880
+ const width = view.getInt32(off + 8, true);
4881
+ const height = view.getInt32(off + 12, true);
4882
+ const stride = view.getInt32(off + 16, true);
4883
+ const pixelFormat = view.getUint32(off + 20, true);
4884
+ const pixelStart = off + 28;
4885
+ const absStride = Math.abs(stride);
4886
+ if (width <= 0 || height <= 0 || width > 8192 || height > 8192 || pixelStart + absStride * height > end) {
4887
+ return null;
4888
+ }
4889
+ const rgba = decodeEmfPlusBitmapPixelsToRgba(view, pixelStart, width, height, stride, pixelFormat);
4890
+ return rgba ? { width, height, rgba } : null;
4891
+ }
4892
+ function parseTextureBrush(view, b, end) {
4893
+ if (b + 8 > end) {
4894
+ return null;
4895
+ }
4896
+ const flags = view.getUint32(b, true);
4897
+ const wrapMode = readWrapMode(view.getUint32(b + 4, true));
4898
+ let o = b + 8;
4899
+ let transform = null;
4900
+ if (flags & BRUSH_DATA_TRANSFORM && o + 24 <= end) {
4901
+ transform = readTransform(view, o);
4902
+ o += 24;
4903
+ }
4904
+ const image = decodeTextureImage(view, o, end);
4905
+ if (!image) {
4906
+ return null;
4907
+ }
4908
+ const texture = {
4909
+ width: image.width,
4910
+ height: image.height,
4911
+ rgba: image.rgba,
4912
+ wrapMode,
4913
+ transform
4914
+ };
4915
+ let r = 0;
4916
+ let g = 0;
4917
+ let bl = 0;
4918
+ const n = image.width * image.height;
4919
+ for (let i = 0; i < n; i++) {
4920
+ r += image.rgba[i * 4];
4921
+ g += image.rgba[i * 4 + 1];
4922
+ bl += image.rgba[i * 4 + 2];
4923
+ }
4924
+ const avg = n > 0 ? `rgba(${Math.round(r / n)},${Math.round(g / n)},${Math.round(bl / n)},1)` : "rgba(128,128,128,1)";
4925
+ emfLog(`parseEmfPlusBrushObject: texture fill ${image.width}x${image.height}, wrapMode=${wrapMode}`);
4926
+ return { kind: "plus-brush", color: avg, texture };
4927
+ }
3343
4928
  function parsePathGradient(view, b, end) {
3344
4929
  if (b + 24 > end) {
3345
4930
  return null;
3346
4931
  }
3347
4932
  const flags = view.getUint32(b, true);
4933
+ const wrapMode = readWrapMode(view.getUint32(b + 4, true));
3348
4934
  const centerArgb = view.getUint32(b + 8, true);
3349
4935
  let cx = view.getFloat32(b + 12, true);
3350
4936
  let cy = view.getFloat32(b + 16, true);
@@ -3359,6 +4945,8 @@ function parsePathGradient(view, b, end) {
3359
4945
  o += 4;
3360
4946
  }
3361
4947
  let boundaryPts = [];
4948
+ let boundaryArgb = [];
4949
+ const surroundAt = (k) => surround.length > 0 ? surround[Math.min(k, surround.length - 1)] : centerArgb;
3362
4950
  if (flags & BRUSH_DATA_PATH) {
3363
4951
  if (o + 4 <= end) {
3364
4952
  const pathSize = view.getInt32(o, true);
@@ -3366,7 +4954,9 @@ function parsePathGradient(view, b, end) {
3366
4954
  if (pathSize > 0 && o + pathSize <= end) {
3367
4955
  const path = parseEmfPlusPath(view, o, pathSize);
3368
4956
  if (path) {
3369
- boundaryPts = path.points;
4957
+ const flat = flattenFirstFigure(path.points, path.types, surroundAt);
4958
+ boundaryPts = flat.points;
4959
+ boundaryArgb = flat.argb;
3370
4960
  }
3371
4961
  o += pathSize;
3372
4962
  }
@@ -3380,6 +4970,7 @@ function parsePathGradient(view, b, end) {
3380
4970
  x: view.getFloat32(o + i * 8, true),
3381
4971
  y: view.getFloat32(o + i * 8 + 4, true)
3382
4972
  });
4973
+ boundaryArgb.push(surroundAt(i));
3383
4974
  }
3384
4975
  o += ptCount * 8;
3385
4976
  }
@@ -3389,31 +4980,50 @@ function parsePathGradient(view, b, end) {
3389
4980
  transform = readTransform(view, o);
3390
4981
  o += 24;
3391
4982
  }
3392
- if (transform) {
3393
- ({ x: cx, y: cy } = applyMatrix(transform, cx, cy));
3394
- boundaryPts = boundaryPts.map((p) => applyMatrix(transform, p.x, p.y));
4983
+ const center = { x: cx, y: cy };
4984
+ const m = transform;
4985
+ const worldBoundary = m ? boundaryPts.map((p) => applyMatrix(m, p.x, p.y)) : boundaryPts;
4986
+ if (m) {
4987
+ ({ x: cx, y: cy } = applyMatrix(m, cx, cy));
3395
4988
  }
3396
4989
  const surroundArgb = surround.length > 0 ? surround[0] : centerArgb;
3397
4990
  let stops = [
3398
- { offset: 0, color: argbToRgba(centerArgb) },
3399
- { offset: 1, color: argbToRgba(surroundArgb) }
4991
+ { offset: 0, color: argbToRgba(centerArgb), argb: centerArgb },
4992
+ { offset: 1, color: argbToRgba(surroundArgb), argb: surroundArgb }
3400
4993
  ];
4994
+ let blend = null;
4995
+ let preset = null;
3401
4996
  if (flags & BRUSH_DATA_PRESET_COLORS) {
3402
- const preset = readPresetColors(view, o, end);
3403
- if (preset) {
3404
- stops = preset.stops.map((s) => ({ offset: 1 - s.offset, color: s.color }));
4997
+ const presetData = readPresetColors(view, o, end);
4998
+ if (presetData) {
4999
+ stops = presetData.stops.map((s) => ({ ...s, offset: 1 - s.offset }));
5000
+ preset = {
5001
+ positions: presetData.stops.map((s) => s.offset),
5002
+ argb: presetData.stops.map((s) => s.argb ?? 0)
5003
+ };
5004
+ o = presetData.next;
3405
5005
  }
3406
5006
  } else if (flags & BRUSH_DATA_BLEND_FACTORS_H) {
3407
- const blend = readBlendFactors(view, o, end);
3408
- if (blend) {
3409
- stops = blend.entries.map((e) => ({
5007
+ const blendData = readBlendFactors(view, o, end);
5008
+ if (blendData) {
5009
+ stops = blendData.entries.map((e) => ({
3410
5010
  offset: 1 - e.pos,
3411
- color: lerpArgbToRgba(surroundArgb, centerArgb, e.factor)
5011
+ color: lerpArgbToRgba(surroundArgb, centerArgb, e.factor),
5012
+ argb: lerpArgb2(surroundArgb, centerArgb, e.factor)
3412
5013
  }));
5014
+ blend = {
5015
+ positions: blendData.entries.map((e) => e.pos),
5016
+ factors: blendData.entries.map((e) => e.factor)
5017
+ };
5018
+ o = blendData.next;
3413
5019
  }
3414
5020
  }
5021
+ let focus = null;
5022
+ if (flags & BRUSH_DATA_FOCUS_SCALES && o + 12 <= end && view.getUint32(o, true) === 2) {
5023
+ focus = { x: view.getFloat32(o + 4, true), y: view.getFloat32(o + 8, true) };
5024
+ }
3415
5025
  let r = 0;
3416
- for (const p of boundaryPts) {
5026
+ for (const p of worldBoundary) {
3417
5027
  const d = Math.hypot(p.x - cx, p.y - cy);
3418
5028
  if (d > r) {
3419
5029
  r = d;
@@ -3423,14 +5033,85 @@ function parsePathGradient(view, b, end) {
3423
5033
  return { kind: "plus-brush", color: argbToRgba(centerArgb) };
3424
5034
  }
3425
5035
  emfLog(
3426
- `parseEmfPlusBrushObject: path gradient centre=(${cx.toFixed(1)},${cy.toFixed(1)}), r=${r.toFixed(1)}, ${stops.length} stop(s)`
5036
+ `parseEmfPlusBrushObject: path gradient centre=(${cx.toFixed(1)},${cy.toFixed(1)}), ${boundaryPts.length} boundary point(s)`
3427
5037
  );
3428
5038
  return {
3429
5039
  kind: "plus-brush",
3430
5040
  color: argbToRgba(centerArgb),
3431
- gradient: { type: "radial", cx, cy, r, stops: normaliseStops(stops) }
5041
+ gradient: {
5042
+ type: "radial",
5043
+ cx,
5044
+ cy,
5045
+ r,
5046
+ stops: normaliseStops(stops),
5047
+ wrapMode,
5048
+ shape: {
5049
+ center,
5050
+ centerArgb,
5051
+ boundary: boundaryPts,
5052
+ boundaryArgb,
5053
+ blend,
5054
+ preset,
5055
+ focus,
5056
+ transform
5057
+ }
5058
+ }
3432
5059
  };
3433
5060
  }
5061
+ function lerpArgb2(a, b, t) {
5062
+ const tc = Math.min(1, Math.max(0, t));
5063
+ let out = 0;
5064
+ for (let shift = 24; shift >= 0; shift -= 8) {
5065
+ const ca = a >>> shift & 255;
5066
+ const cb = b >>> shift & 255;
5067
+ out = out * 256 + Math.round(ca + (cb - ca) * tc);
5068
+ }
5069
+ return out >>> 0;
5070
+ }
5071
+ var BEZIER_STEPS = 24;
5072
+ function flattenFirstFigure(points, types, colorAt) {
5073
+ const outPts = [];
5074
+ const outArgb = [];
5075
+ for (let k = 0; k < points.length; k++) {
5076
+ const kind = types[k] & 7;
5077
+ if (k > 0 && kind === 0) {
5078
+ break;
5079
+ }
5080
+ if (kind === 3 && k > 0 && k + 2 < points.length) {
5081
+ const p0 = points[k - 1];
5082
+ const p1 = points[k];
5083
+ const p2 = points[k + 1];
5084
+ const p3 = points[k + 2];
5085
+ const c0 = colorAt(k - 1);
5086
+ const c3 = colorAt(k + 2);
5087
+ for (let i = 1; i <= BEZIER_STEPS; i++) {
5088
+ const t = i / BEZIER_STEPS;
5089
+ const u = 1 - t;
5090
+ outPts.push({
5091
+ x: u * u * u * p0.x + 3 * u * u * t * p1.x + 3 * u * t * t * p2.x + t * t * t * p3.x,
5092
+ y: u * u * u * p0.y + 3 * u * u * t * p1.y + 3 * u * t * t * p2.y + t * t * t * p3.y
5093
+ });
5094
+ outArgb.push(lerpArgb2(c0, c3, t));
5095
+ }
5096
+ k += 2;
5097
+ if (types[k] & 128) {
5098
+ break;
5099
+ }
5100
+ continue;
5101
+ }
5102
+ outPts.push(points[k]);
5103
+ outArgb.push(colorAt(k));
5104
+ if (types[k] & 128) {
5105
+ break;
5106
+ }
5107
+ }
5108
+ const n = outPts.length;
5109
+ if (n > 1 && outPts[0].x === outPts[n - 1].x && outPts[0].y === outPts[n - 1].y) {
5110
+ outPts.pop();
5111
+ outArgb.pop();
5112
+ }
5113
+ return { points: outPts, argb: outArgb };
5114
+ }
3434
5115
  function parseEmfPlusBrushObject(view, dataOff, recDataSize) {
3435
5116
  if (recDataSize < 8) {
3436
5117
  return null;
@@ -3451,6 +5132,10 @@ function parseEmfPlusBrushObject(view, dataOff, recDataSize) {
3451
5132
  return { kind: "plus-brush", color: argbToRgba(view.getUint32(b + 4, true)) };
3452
5133
  }
3453
5134
  return { kind: "plus-brush", color: "rgba(0,0,0,1)" };
5135
+ case EMFPLUS_BRUSHTYPE_TEXTUREFILL: {
5136
+ const brush = parseTextureBrush(view, b, end);
5137
+ return brush ?? { kind: "plus-brush", color: "rgba(0,0,0,1)" };
5138
+ }
3454
5139
  case EMFPLUS_BRUSHTYPE_LINEARGRADIENT: {
3455
5140
  const brush = parseLinearGradient(view, b, end);
3456
5141
  return brush ?? { kind: "plus-brush", color: "rgba(0,0,0,1)" };
@@ -3464,110 +5149,6 @@ function parseEmfPlusBrushObject(view, dataOff, recDataSize) {
3464
5149
  }
3465
5150
  }
3466
5151
 
3467
- // src/emf-plus-bitmap-decoder.ts
3468
- var PIXELFORMAT_24BPP_RGB = 137224;
3469
- var PIXELFORMAT_32BPP_RGB = 139273;
3470
- var PIXELFORMAT_32BPP_ARGB = 2498570;
3471
- var PIXELFORMAT_32BPP_PARGB = 925707;
3472
- function decodeEmfPlusBitmapPixels(view, pixelStart, width, height, stride, pixelFormat) {
3473
- const absStride = Math.abs(stride);
3474
- const topDown = stride > 0;
3475
- const rowBytes = width * 4;
3476
- const bmpRowStride = rowBytes + 3 & -4;
3477
- const pixelDataSize = bmpRowStride * height;
3478
- const bmpData = new Uint8Array(pixelDataSize);
3479
- for (let y = 0; y < height; y++) {
3480
- const srcRow = topDown ? y : height - 1 - y;
3481
- const rowOff = pixelStart + srcRow * absStride;
3482
- const dstRow = (height - 1 - y) * bmpRowStride;
3483
- switch (pixelFormat) {
3484
- case PIXELFORMAT_32BPP_ARGB:
3485
- case PIXELFORMAT_32BPP_PARGB: {
3486
- for (let x = 0; x < width; x++) {
3487
- const off = rowOff + x * 4;
3488
- if (off + 3 >= view.byteLength) {
3489
- break;
3490
- }
3491
- let b = view.getUint8(off);
3492
- let g = view.getUint8(off + 1);
3493
- let r = view.getUint8(off + 2);
3494
- const a = view.getUint8(off + 3);
3495
- if (pixelFormat === PIXELFORMAT_32BPP_PARGB && a > 0 && a < 255) {
3496
- r = Math.min(255, Math.round(r * 255 / a));
3497
- g = Math.min(255, Math.round(g * 255 / a));
3498
- b = Math.min(255, Math.round(b * 255 / a));
3499
- }
3500
- const di = dstRow + x * 4;
3501
- bmpData[di] = b;
3502
- bmpData[di + 1] = g;
3503
- bmpData[di + 2] = r;
3504
- bmpData[di + 3] = a;
3505
- }
3506
- break;
3507
- }
3508
- case PIXELFORMAT_32BPP_RGB: {
3509
- for (let x = 0; x < width; x++) {
3510
- const off = rowOff + x * 4;
3511
- if (off + 3 >= view.byteLength) {
3512
- break;
3513
- }
3514
- const di = dstRow + x * 4;
3515
- bmpData[di] = view.getUint8(off);
3516
- bmpData[di + 1] = view.getUint8(off + 1);
3517
- bmpData[di + 2] = view.getUint8(off + 2);
3518
- bmpData[di + 3] = 255;
3519
- }
3520
- break;
3521
- }
3522
- case PIXELFORMAT_24BPP_RGB: {
3523
- for (let x = 0; x < width; x++) {
3524
- const off = rowOff + x * 3;
3525
- if (off + 2 >= view.byteLength) {
3526
- break;
3527
- }
3528
- const di = dstRow + x * 4;
3529
- bmpData[di] = view.getUint8(off);
3530
- bmpData[di + 1] = view.getUint8(off + 1);
3531
- bmpData[di + 2] = view.getUint8(off + 2);
3532
- bmpData[di + 3] = 255;
3533
- }
3534
- break;
3535
- }
3536
- default:
3537
- return null;
3538
- }
3539
- }
3540
- const fileHeaderSize = 14;
3541
- const dibHeaderSize = 108;
3542
- const fileSize = fileHeaderSize + dibHeaderSize + pixelDataSize;
3543
- const bmpFile = new ArrayBuffer(fileSize);
3544
- const bmpView = new DataView(bmpFile);
3545
- const bmpBytes = new Uint8Array(bmpFile);
3546
- bmpView.setUint8(0, 66);
3547
- bmpView.setUint8(1, 77);
3548
- bmpView.setUint32(2, fileSize, true);
3549
- bmpView.setUint32(6, 0, true);
3550
- bmpView.setUint32(10, fileHeaderSize + dibHeaderSize, true);
3551
- bmpView.setUint32(14, dibHeaderSize, true);
3552
- bmpView.setInt32(18, width, true);
3553
- bmpView.setInt32(22, height, true);
3554
- bmpView.setUint16(26, 1, true);
3555
- bmpView.setUint16(28, 32, true);
3556
- bmpView.setUint32(30, 3, true);
3557
- bmpView.setUint32(34, pixelDataSize, true);
3558
- bmpView.setInt32(38, 2835, true);
3559
- bmpView.setInt32(42, 2835, true);
3560
- bmpView.setUint32(46, 0, true);
3561
- bmpView.setUint32(50, 0, true);
3562
- bmpView.setUint32(54, 16711680, true);
3563
- bmpView.setUint32(58, 65280, true);
3564
- bmpView.setUint32(62, 255, true);
3565
- bmpView.setUint32(66, 4278190080, true);
3566
- bmpView.setUint32(70, 1934772034, true);
3567
- bmpBytes.set(bmpData, fileHeaderSize + dibHeaderSize);
3568
- return bmpFile;
3569
- }
3570
-
3571
5152
  // src/emf-plus-object-complex.ts
3572
5153
  function parseEmfPlusPenObject(view, dataOff, recDataSize) {
3573
5154
  if (recDataSize < 20) {
@@ -4451,7 +6032,7 @@ function replayEmfRecords(view, ctx, bounds, canvasW, canvasH, dpiScale = 1, rep
4451
6032
  );
4452
6033
  break;
4453
6034
  }
4454
- if (recType === EMR_SETBRUSHORGEX || recType === EMR_SETMETARGN || recType === EMR_SETICMMODE || recType === EMR_SETLAYOUT || recType === EMR_HEADER) {
6035
+ if (recType === EMR_SETMETARGN || recType === EMR_SETICMMODE || recType === EMR_SETLAYOUT || recType === EMR_HEADER) {
4455
6036
  offset += recSize;
4456
6037
  continue;
4457
6038
  }
@@ -4846,8 +6427,8 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH, replayOptions = {
4846
6427
  case META_CREATEFONTINDIRECT:
4847
6428
  if (recSize >= 24) {
4848
6429
  let family = "";
4849
- for (let i = 0; i < 32 && dataOff + 14 + i < offset + recSize; i++) {
4850
- const ch = view.getUint8(dataOff + 14 + i);
6430
+ for (let i = 0; i < 32 && dataOff + 18 + i < offset + recSize; i++) {
6431
+ const ch = view.getUint8(dataOff + 18 + i);
4851
6432
  if (ch === 0) {
4852
6433
  break;
4853
6434
  }
@@ -4856,12 +6437,15 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH, replayOptions = {
4856
6437
  const slot = allocObjectSlot();
4857
6438
  objectTable.set(slot, {
4858
6439
  kind: "font",
4859
- height: Math.abs(view.getInt16(dataOff, true)),
6440
+ // Sign kept (not Math.abs'd): resolveFontPixelHeight() needs it
6441
+ // to distinguish GDI's cell-height vs character-height convention.
6442
+ height: view.getInt16(dataOff, true),
4860
6443
  weight: view.getInt16(dataOff + 8, true),
4861
6444
  italic: view.getUint8(dataOff + 10) !== 0,
4862
6445
  underline: view.getUint8(dataOff + 11) !== 0,
4863
6446
  strikeOut: view.getUint8(dataOff + 12) !== 0,
4864
- family: family || "sans-serif"
6447
+ family: family || "sans-serif",
6448
+ escapementTenthDeg: view.getInt16(dataOff + 4, true)
4865
6449
  });
4866
6450
  }
4867
6451
  break;
@@ -4886,6 +6470,7 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH, replayOptions = {
4886
6470
  state.fontUnderline = obj.underline;
4887
6471
  state.fontStrikeOut = obj.strikeOut;
4888
6472
  state.fontFamily = obj.family;
6473
+ state.fontEscapementTenthDeg = obj.escapementTenthDeg ?? 0;
4889
6474
  break;
4890
6475
  }
4891
6476
  }