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