emf-converter 1.5.0 → 2.0.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.
package/dist/index.js CHANGED
@@ -1,5 +1,57 @@
1
1
  'use strict';
2
2
 
3
+ // src/emf-color-helpers.ts
4
+ function colorRefToHex(r, g, b) {
5
+ const toHex = (v) => v.toString(16).padStart(2, "0");
6
+ return `#${toHex(r & 255)}${toHex(g & 255)}${toHex(b & 255)}`;
7
+ }
8
+ function readColorRef(view, offset) {
9
+ const r = view.getUint8(offset);
10
+ const g = view.getUint8(offset + 1);
11
+ const b = view.getUint8(offset + 2);
12
+ return colorRefToHex(r, g, b);
13
+ }
14
+ function argbToRgba(argb) {
15
+ const a = (argb >>> 24 & 255) / 255;
16
+ const r = argb >>> 16 & 255;
17
+ const g = argb >>> 8 & 255;
18
+ const b = argb & 255;
19
+ return `rgba(${r},${g},${b},${a.toFixed(3)})`;
20
+ }
21
+ function lerpArgbToRgba(argbA, argbB, t) {
22
+ const tc = Math.min(1, Math.max(0, t));
23
+ const mix = (a2, b2) => Math.round(a2 + (b2 - a2) * tc);
24
+ const aA = argbA >>> 24 & 255;
25
+ const aB = argbB >>> 24 & 255;
26
+ const r = mix(argbA >>> 16 & 255, argbB >>> 16 & 255);
27
+ const g = mix(argbA >>> 8 & 255, argbB >>> 8 & 255);
28
+ const b = mix(argbA & 255, argbB & 255);
29
+ const a = mix(aA, aB) / 255;
30
+ return `rgba(${r},${g},${b},${a.toFixed(3)})`;
31
+ }
32
+ function invertCssColor(color) {
33
+ const hex = /^#([0-9a-f]{6})$/i.exec(color);
34
+ if (hex) {
35
+ const v = parseInt(hex[1], 16);
36
+ const inv = 16777215 ^ v;
37
+ return `#${inv.toString(16).padStart(6, "0")}`;
38
+ }
39
+ const shortHex = /^#([0-9a-f]{3})$/i.exec(color);
40
+ if (shortHex) {
41
+ const [r, g, b] = shortHex[1].split("").map((c) => parseInt(c + c, 16));
42
+ const toHex = (v) => (255 - v).toString(16).padStart(2, "0");
43
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
44
+ }
45
+ const rgba = /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+)\s*)?\)$/i.exec(color);
46
+ if (rgba) {
47
+ const r = 255 - Math.min(255, parseInt(rgba[1], 10));
48
+ const g = 255 - Math.min(255, parseInt(rgba[2], 10));
49
+ const b = 255 - Math.min(255, parseInt(rgba[3], 10));
50
+ return rgba[4] !== void 0 ? `rgba(${r},${g},${b},${rgba[4]})` : `rgb(${r},${g},${b})`;
51
+ }
52
+ return color;
53
+ }
54
+
3
55
  // src/emf-constants.ts
4
56
  var EMR_HEADER = 1;
5
57
  var EMR_POLYBEZIER = 2;
@@ -21,10 +73,21 @@ var EMR_SETBKMODE = 18;
21
73
  var EMR_SETPOLYFILLMODE = 19;
22
74
  var EMR_SETROP2 = 20;
23
75
  var EMR_SETSTRETCHBLTMODE = 21;
76
+ var R2_BLACK = 1;
77
+ var R2_NOTMERGEPEN = 2;
78
+ var R2_MASKNOTPEN = 3;
79
+ var R2_NOTCOPYPEN = 4;
80
+ var R2_MASKPENNOT = 5;
24
81
  var R2_NOT = 6;
25
82
  var R2_XORPEN = 7;
83
+ var R2_NOTMASKPEN = 8;
26
84
  var R2_MASKPEN = 9;
85
+ var R2_NOTXORPEN = 10;
86
+ var R2_NOP = 11;
87
+ var R2_MERGENOTPEN = 12;
88
+ var R2_MERGEPENNOT = 14;
27
89
  var R2_MERGEPEN = 15;
90
+ var R2_WHITE = 16;
28
91
  var MAX_CANVAS_DIMENSION = 8192;
29
92
  var MAX_RECORDS_DEFAULT = 2e5;
30
93
  var MAX_RECORDS_EMFPLUS_DEFAULT = 5e5;
@@ -257,28 +320,61 @@ function createTempCanvas(width, height) {
257
320
  }
258
321
  return null;
259
322
  }
260
- function rop2ToGco(rop2) {
323
+ function rop2Paint(rop2) {
261
324
  switch (rop2) {
325
+ case R2_BLACK:
326
+ return { gco: "source-over", colorTransform: "black", exact: true };
327
+ case R2_WHITE:
328
+ return { gco: "source-over", colorTransform: "white", exact: true };
329
+ case R2_NOP:
330
+ return { gco: "source-over", colorTransform: "skip", exact: true };
331
+ case R2_NOTCOPYPEN:
332
+ return { gco: "source-over", colorTransform: "invert", exact: true };
333
+ case R2_NOT:
334
+ return { gco: "difference", colorTransform: "white", exact: true };
262
335
  case R2_XORPEN:
263
- return "xor";
336
+ case R2_MASKPENNOT:
337
+ return { gco: "difference", colorTransform: "none", exact: false };
338
+ case R2_NOTXORPEN:
339
+ return { gco: "difference", colorTransform: "invert", exact: false };
264
340
  case R2_MASKPEN:
265
- return "multiply";
341
+ return { gco: "darken", colorTransform: "none", exact: false };
342
+ case R2_MASKNOTPEN:
343
+ case R2_NOTMERGEPEN:
344
+ return { gco: "darken", colorTransform: "invert", exact: false };
266
345
  case R2_MERGEPEN:
267
- return "lighten";
268
- case R2_NOT:
269
- return "difference";
346
+ case R2_MERGEPENNOT:
347
+ return { gco: "lighten", colorTransform: "none", exact: false };
348
+ case R2_MERGENOTPEN:
349
+ case R2_NOTMASKPEN:
350
+ return { gco: "lighten", colorTransform: "invert", exact: false };
351
+ default:
352
+ return { gco: "source-over", colorTransform: "none", exact: true };
353
+ }
354
+ }
355
+ function rop2TransformColor(color, transform) {
356
+ switch (transform) {
357
+ case "invert":
358
+ return invertCssColor(color);
359
+ case "black":
360
+ return "#000000";
361
+ case "white":
362
+ return "#ffffff";
363
+ case "skip":
364
+ return "rgba(0,0,0,0)";
270
365
  default:
271
- return "source-over";
366
+ return color;
272
367
  }
273
368
  }
274
369
  function applyPen(ctx, state) {
275
- ctx.globalCompositeOperation = rop2ToGco(state.rop2);
370
+ const paint = rop2Paint(state.rop2);
371
+ ctx.globalCompositeOperation = paint.gco;
276
372
  if (state.penStyle === 5) {
277
373
  ctx.strokeStyle = "rgba(0,0,0,0)";
278
374
  ctx.lineWidth = 0;
279
375
  return;
280
376
  }
281
- ctx.strokeStyle = state.penColor;
377
+ ctx.strokeStyle = rop2TransformColor(state.penColor, paint.colorTransform);
282
378
  ctx.lineWidth = Math.max(state.penWidth, 1);
283
379
  switch (state.penStyle) {
284
380
  case 1:
@@ -299,12 +395,13 @@ function applyPen(ctx, state) {
299
395
  }
300
396
  }
301
397
  function applyBrush(ctx, state) {
302
- ctx.globalCompositeOperation = rop2ToGco(state.rop2);
398
+ const paint = rop2Paint(state.rop2);
399
+ ctx.globalCompositeOperation = paint.gco;
303
400
  if (state.brushStyle === 1) {
304
401
  ctx.fillStyle = "rgba(0,0,0,0)";
305
402
  return;
306
403
  }
307
- ctx.fillStyle = state.brushColor;
404
+ ctx.fillStyle = rop2TransformColor(state.brushColor, paint.colorTransform);
308
405
  }
309
406
  function cssFontWeight(weight) {
310
407
  if (!weight || weight === 400) {
@@ -538,49 +635,36 @@ function parseWmfHeader(view) {
538
635
  };
539
636
  }
540
637
 
541
- // src/emf-color-helpers.ts
542
- function colorRefToHex(r, g, b) {
543
- const toHex = (v) => v.toString(16).padStart(2, "0");
544
- return `#${toHex(r & 255)}${toHex(g & 255)}${toHex(b & 255)}`;
545
- }
546
- function readColorRef(view, offset) {
547
- const r = view.getUint8(offset);
548
- const g = view.getUint8(offset + 1);
549
- const b = view.getUint8(offset + 2);
550
- return colorRefToHex(r, g, b);
551
- }
552
- function argbToRgba(argb) {
553
- const a = (argb >>> 24 & 255) / 255;
554
- const r = argb >>> 16 & 255;
555
- const g = argb >>> 8 & 255;
556
- const b = argb & 255;
557
- return `rgba(${r},${g},${b},${a.toFixed(3)})`;
558
- }
559
-
560
638
  // src/emf-gdi-coord.ts
561
639
  function gmx(r, x) {
640
+ const wt = r.state.worldTransform;
641
+ const px = wt[0] * x + wt[4];
562
642
  if (r.useMappingMode) {
563
- return (x - r.windowOrg.x) / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1) + r.viewportOrg.x;
643
+ return (px - r.windowOrg.x) / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1) + r.viewportOrg.x;
564
644
  }
565
- return (x - r.bounds.left) * r.sx;
645
+ return (px - r.bounds.left) * r.sx;
566
646
  }
567
647
  function gmy(r, y) {
648
+ const wt = r.state.worldTransform;
649
+ const py = wt[3] * y + wt[5];
568
650
  if (r.useMappingMode) {
569
- return (y - r.windowOrg.y) / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1) + r.viewportOrg.y;
651
+ return (py - r.windowOrg.y) / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1) + r.viewportOrg.y;
570
652
  }
571
- return (y - r.bounds.top) * r.sy;
653
+ return (py - r.bounds.top) * r.sy;
572
654
  }
573
655
  function gmw(r, w) {
656
+ const pw = r.state.worldTransform[0] * w;
574
657
  if (r.useMappingMode) {
575
- return w / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1);
658
+ return pw / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1);
576
659
  }
577
- return w * r.sx;
660
+ return pw * r.sx;
578
661
  }
579
662
  function gmh(r, h) {
663
+ const ph = r.state.worldTransform[3] * h;
580
664
  if (r.useMappingMode) {
581
- return h / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1);
665
+ return ph / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1);
582
666
  }
583
- return h * r.sy;
667
+ return ph * r.sy;
584
668
  }
585
669
  function activateGdiMappingMode(r) {
586
670
  r.useMappingMode = true;
@@ -784,6 +868,218 @@ function handleEmfGdiShapeRecord(rCtx, recType, dataOff, recSize) {
784
868
  }
785
869
  }
786
870
 
871
+ // src/emf-clip-region.ts
872
+ var CLIP_HUGE = 1 << 24;
873
+ function rectClipShape(x, y, w, h) {
874
+ return { cmds: [{ op: "rect", x, y, w, h }], fillRule: "nonzero", simple: true };
875
+ }
876
+ function rectsClipShape(rects) {
877
+ return {
878
+ cmds: rects.map((r) => ({ op: "rect", x: r.x, y: r.y, w: r.w, h: r.h })),
879
+ fillRule: "nonzero",
880
+ simple: true
881
+ };
882
+ }
883
+ function emptyClipShape() {
884
+ return { cmds: [{ op: "rect", x: 0, y: 0, w: 0, h: 0 }], fillRule: "nonzero", simple: true };
885
+ }
886
+ function translateClipShape(shape, dx, dy) {
887
+ return {
888
+ ...shape,
889
+ cmds: shape.cmds.map((c) => {
890
+ switch (c.op) {
891
+ case "rect":
892
+ return { ...c, x: c.x + dx, y: c.y + dy };
893
+ case "moveTo":
894
+ case "lineTo":
895
+ return { ...c, x: c.x + dx, y: c.y + dy };
896
+ case "bezierCurveTo":
897
+ return {
898
+ ...c,
899
+ cp1x: c.cp1x + dx,
900
+ cp1y: c.cp1y + dy,
901
+ cp2x: c.cp2x + dx,
902
+ cp2y: c.cp2y + dy,
903
+ x: c.x + dx,
904
+ y: c.y + dy
905
+ };
906
+ case "closePath":
907
+ return c;
908
+ }
909
+ })
910
+ };
911
+ }
912
+ function translateClipRegion(region, dx, dy) {
913
+ if (!region) {
914
+ return null;
915
+ }
916
+ return region.map((s) => translateClipShape(s, dx, dy));
917
+ }
918
+ function isComposable(shape) {
919
+ return shape.simple && shape.fillRule === "nonzero";
920
+ }
921
+ function invertClipShape(shape) {
922
+ return {
923
+ cmds: [
924
+ { op: "rect", x: -CLIP_HUGE, y: -CLIP_HUGE, w: 2 * CLIP_HUGE, h: 2 * CLIP_HUGE },
925
+ ...shape.cmds
926
+ ],
927
+ fillRule: "evenodd",
928
+ simple: false
929
+ };
930
+ }
931
+ function combineClip(current, shape, op) {
932
+ switch (op) {
933
+ case "replace":
934
+ return { region: [shape], exact: true };
935
+ case "intersect":
936
+ return { region: current ? [...current, shape] : [shape], exact: true };
937
+ case "exclude": {
938
+ if (!isComposable(shape)) {
939
+ return { region: current ? [...current, shape] : [shape], exact: false };
940
+ }
941
+ const inv = invertClipShape(shape);
942
+ return { region: current ? [...current, inv] : [inv], exact: true };
943
+ }
944
+ case "union": {
945
+ if (!current) {
946
+ return { region: null, exact: true };
947
+ }
948
+ if (current.length === 1 && isComposable(current[0]) && isComposable(shape)) {
949
+ return {
950
+ region: [
951
+ { cmds: [...current[0].cmds, ...shape.cmds], fillRule: "nonzero", simple: false }
952
+ ],
953
+ exact: false
954
+ };
955
+ }
956
+ return { region: current, exact: false };
957
+ }
958
+ case "xor": {
959
+ if (!current) {
960
+ if (isComposable(shape)) {
961
+ return { region: [invertClipShape(shape)], exact: true };
962
+ }
963
+ return { region: [shape], exact: false };
964
+ }
965
+ if (current.length === 1 && isComposable(current[0]) && isComposable(shape)) {
966
+ return {
967
+ region: [
968
+ { cmds: [...current[0].cmds, ...shape.cmds], fillRule: "evenodd", simple: false }
969
+ ],
970
+ exact: true
971
+ };
972
+ }
973
+ if (isComposable(shape)) {
974
+ return { region: [...current, invertClipShape(shape)], exact: false };
975
+ }
976
+ return { region: [...current, shape], exact: false };
977
+ }
978
+ case "complement": {
979
+ if (!current) {
980
+ return { region: [emptyClipShape()], exact: true };
981
+ }
982
+ if (current.length === 1 && isComposable(current[0])) {
983
+ return { region: [shape, invertClipShape(current[0])], exact: true };
984
+ }
985
+ return { region: [shape], exact: false };
986
+ }
987
+ }
988
+ }
989
+ function combineClipRegions(current, incoming, op) {
990
+ if (op === "replace") {
991
+ return { region: incoming, exact: true };
992
+ }
993
+ if (incoming && incoming.length === 1) {
994
+ return combineClip(current, incoming[0], op);
995
+ }
996
+ if (!incoming) {
997
+ switch (op) {
998
+ case "intersect":
999
+ return { region: current, exact: true };
1000
+ case "union":
1001
+ return { region: null, exact: true };
1002
+ case "exclude":
1003
+ return { region: [emptyClipShape()], exact: true };
1004
+ case "xor":
1005
+ case "complement": {
1006
+ if (!current) {
1007
+ return { region: [emptyClipShape()], exact: true };
1008
+ }
1009
+ if (current.length === 1) {
1010
+ return combineClip(null, current[0], "exclude");
1011
+ }
1012
+ return { region: current, exact: false };
1013
+ }
1014
+ }
1015
+ }
1016
+ switch (op) {
1017
+ case "intersect":
1018
+ return { region: current ? [...current, ...incoming] : incoming, exact: true };
1019
+ case "union":
1020
+ return { region: current, exact: false };
1021
+ case "xor":
1022
+ return { region: current ?? incoming, exact: false };
1023
+ case "exclude":
1024
+ return { region: current, exact: false };
1025
+ case "complement": {
1026
+ if (!current) {
1027
+ return { region: [emptyClipShape()], exact: true };
1028
+ }
1029
+ if (current.length === 1) {
1030
+ return combineClip(incoming, current[0], "exclude");
1031
+ }
1032
+ return { region: incoming, exact: false };
1033
+ }
1034
+ }
1035
+ }
1036
+ function replayClipCmds(ctx, cmds) {
1037
+ for (const c of cmds) {
1038
+ switch (c.op) {
1039
+ case "rect":
1040
+ ctx.rect(c.x, c.y, c.w, c.h);
1041
+ break;
1042
+ case "moveTo":
1043
+ ctx.moveTo(c.x, c.y);
1044
+ break;
1045
+ case "lineTo":
1046
+ ctx.lineTo(c.x, c.y);
1047
+ break;
1048
+ case "bezierCurveTo":
1049
+ ctx.bezierCurveTo(c.cp1x, c.cp1y, c.cp2x, c.cp2y, c.x, c.y);
1050
+ break;
1051
+ case "closePath":
1052
+ ctx.closePath();
1053
+ break;
1054
+ }
1055
+ }
1056
+ }
1057
+ function applyClipShapes(ctx, shapes) {
1058
+ for (const s of shapes) {
1059
+ ctx.beginPath();
1060
+ replayClipCmds(ctx, s.cmds);
1061
+ try {
1062
+ ctx.clip(s.fillRule);
1063
+ } catch {
1064
+ }
1065
+ }
1066
+ }
1067
+ function reapplyClipRegion(holder, region, identityTransform = false) {
1068
+ const { ctx } = holder;
1069
+ while (holder.clipSaveDepth > 0) {
1070
+ ctx.restore();
1071
+ holder.clipSaveDepth--;
1072
+ }
1073
+ if (region) {
1074
+ ctx.save();
1075
+ holder.clipSaveDepth = 1;
1076
+ if (identityTransform) {
1077
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
1078
+ }
1079
+ applyClipShapes(ctx, region);
1080
+ }
1081
+ }
1082
+
787
1083
  // src/emf-dib-rle-decoder.ts
788
1084
  function decodeRleBitmap(view, bitsOffset, bitsSize, width, height, _topDown, isRle4, colorTable, out, setPixel) {
789
1085
  let x = 0;
@@ -1106,14 +1402,9 @@ function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
1106
1402
  if (text.length > 0) {
1107
1403
  applyFont(ctx, state);
1108
1404
  ctx.fillStyle = state.textColor;
1109
- let alignBaseline = "alphabetic";
1405
+ const vAlign = state.textAlign & 24;
1406
+ const alignBaseline = vAlign === 24 ? "alphabetic" : vAlign === 8 ? "bottom" : "top";
1110
1407
  let alignHoriz = "left";
1111
- if (state.textAlign & 8) {
1112
- alignBaseline = "bottom";
1113
- }
1114
- if (state.textAlign & 24) {
1115
- alignBaseline = "alphabetic";
1116
- }
1117
1408
  if (state.textAlign & 6) {
1118
1409
  alignHoriz = "center";
1119
1410
  }
@@ -1141,17 +1432,29 @@ function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
1141
1432
  }
1142
1433
  return true;
1143
1434
  }
1435
+ var ROP_PATCOPY = 15728673;
1436
+ var BS_NULL = 1;
1144
1437
  function handleBitBlt(rCtx, offset, dataOff, recSize) {
1145
- const { ctx, view } = rCtx;
1438
+ const { ctx, view, state } = rCtx;
1146
1439
  if (recSize >= 96) {
1147
1440
  const dstX = view.getInt32(dataOff + 16, true);
1148
1441
  const dstY = view.getInt32(dataOff + 20, true);
1149
1442
  const dstW = view.getInt32(dataOff + 24, true);
1150
1443
  const dstH = view.getInt32(dataOff + 28, true);
1444
+ const rop = view.getUint32(dataOff + 32, true);
1151
1445
  const offBmiSrc = view.getUint32(dataOff + 76, true);
1152
1446
  const cbBmiSrc = view.getUint32(dataOff + 80, true);
1153
1447
  const offBitsSrc = view.getUint32(dataOff + 84, true);
1154
1448
  const cbBitsSrc = view.getUint32(dataOff + 88, true);
1449
+ if (offBmiSrc === 0 && rop === ROP_PATCOPY) {
1450
+ const prevFill = ctx.fillStyle;
1451
+ if (state.brushStyle !== BS_NULL) {
1452
+ ctx.fillStyle = state.brushColor;
1453
+ ctx.fillRect(gmx(rCtx, dstX), gmy(rCtx, dstY), gmw(rCtx, dstW), gmh(rCtx, dstH));
1454
+ }
1455
+ ctx.fillStyle = prevFill;
1456
+ return true;
1457
+ }
1155
1458
  if (offBmiSrc > 0 && cbBmiSrc > 0 && offBitsSrc > 0 && cbBitsSrc > 0) {
1156
1459
  const imageData = decodeDibToImageData(
1157
1460
  view,
@@ -1211,86 +1514,134 @@ function handleStretchDibits(rCtx, offset, dataOff, recSize) {
1211
1514
  }
1212
1515
  return true;
1213
1516
  }
1517
+ function gdiCombineClip(rCtx, shape, op) {
1518
+ const { ctx } = rCtx;
1519
+ if (rCtx.clipUntracked && op !== "replace") {
1520
+ switch (op) {
1521
+ case "intersect":
1522
+ case "complement": {
1523
+ ctx.save();
1524
+ rCtx.clipSaveDepth++;
1525
+ applyClipShapes(ctx, [shape]);
1526
+ return;
1527
+ }
1528
+ case "exclude":
1529
+ case "xor": {
1530
+ const inv = combineClip(null, shape, "exclude");
1531
+ ctx.save();
1532
+ rCtx.clipSaveDepth++;
1533
+ applyClipShapes(ctx, inv.region ?? [shape]);
1534
+ return;
1535
+ }
1536
+ case "union":
1537
+ return;
1538
+ }
1539
+ }
1540
+ const res = combineClip(op === "replace" ? null : rCtx.clipRegion ?? null, shape, op);
1541
+ if (!res.exact) ;
1542
+ rCtx.clipRegion = res.region;
1543
+ rCtx.clipUntracked = false;
1544
+ reapplyClipRegion(rCtx, res.region);
1545
+ }
1546
+ function readClipRectShape(rCtx, dataOff) {
1547
+ const { view } = rCtx;
1548
+ const left = view.getInt32(dataOff, true);
1549
+ const top = view.getInt32(dataOff + 4, true);
1550
+ const right = view.getInt32(dataOff + 8, true);
1551
+ const bottom = view.getInt32(dataOff + 12, true);
1552
+ return rectClipShape(
1553
+ gmx(rCtx, left),
1554
+ gmy(rCtx, top),
1555
+ gmw(rCtx, right - left),
1556
+ gmh(rCtx, bottom - top)
1557
+ );
1558
+ }
1214
1559
  function handleIntersectClipRect(rCtx, dataOff, recSize) {
1215
- const { ctx, view } = rCtx;
1216
1560
  if (recSize >= 24) {
1217
- const left = view.getInt32(dataOff, true);
1218
- const top = view.getInt32(dataOff + 4, true);
1219
- const right = view.getInt32(dataOff + 8, true);
1220
- const bottom = view.getInt32(dataOff + 12, true);
1221
- ctx.save();
1222
- rCtx.clipSaveDepth++;
1223
- ctx.beginPath();
1224
- ctx.rect(gmx(rCtx, left), gmy(rCtx, top), gmw(rCtx, right - left), gmh(rCtx, bottom - top));
1225
- try {
1226
- ctx.clip();
1227
- } catch {
1228
- }
1561
+ gdiCombineClip(rCtx, readClipRectShape(rCtx, dataOff), "intersect");
1229
1562
  }
1230
1563
  return true;
1231
1564
  }
1565
+ function handleExcludeClipRect(rCtx, dataOff, recSize) {
1566
+ if (recSize >= 24) {
1567
+ gdiCombineClip(rCtx, readClipRectShape(rCtx, dataOff), "exclude");
1568
+ }
1569
+ return true;
1570
+ }
1571
+ var RGN_MODE_OPS = {
1572
+ 1: "intersect",
1573
+ // RGN_AND
1574
+ 2: "union",
1575
+ // RGN_OR
1576
+ 3: "xor",
1577
+ // RGN_XOR
1578
+ 4: "exclude",
1579
+ // RGN_DIFF
1580
+ 5: "replace"
1581
+ // RGN_COPY
1582
+ };
1232
1583
  function handleExtSelectClipRgn(rCtx, dataOff, recSize) {
1233
- const { ctx, view } = rCtx;
1584
+ const { view } = rCtx;
1234
1585
  if (recSize < 16) {
1235
1586
  return true;
1236
1587
  }
1237
1588
  const cbRgnData = view.getUint32(dataOff, true);
1238
1589
  const iMode = view.getUint32(dataOff + 4, true);
1239
- if (iMode === 5) {
1240
- if (cbRgnData === 0) {
1241
- while (rCtx.clipSaveDepth > 0) {
1242
- ctx.restore();
1243
- rCtx.clipSaveDepth--;
1244
- }
1245
- return true;
1246
- }
1247
- const rgnStart = dataOff + 8;
1248
- if (cbRgnData < 32) {
1249
- return true;
1250
- }
1251
- const nCount = view.getUint32(rgnStart + 8, true);
1252
- if (nCount === 0) {
1253
- return true;
1254
- }
1255
- while (rCtx.clipSaveDepth > 0) {
1256
- ctx.restore();
1257
- rCtx.clipSaveDepth--;
1258
- }
1259
- ctx.save();
1260
- rCtx.clipSaveDepth++;
1261
- ctx.beginPath();
1262
- const rectsStart = rgnStart + 32;
1263
- for (let i = 0; i < nCount; i++) {
1264
- const rOff = rectsStart + i * 16;
1265
- if (rOff + 16 > dataOff + 8 + cbRgnData) {
1266
- break;
1267
- }
1268
- const left = view.getInt32(rOff, true);
1269
- const top = view.getInt32(rOff + 4, true);
1270
- const right = view.getInt32(rOff + 8, true);
1271
- const bottom = view.getInt32(rOff + 12, true);
1272
- ctx.rect(gmx(rCtx, left), gmy(rCtx, top), gmw(rCtx, right - left), gmh(rCtx, bottom - top));
1273
- }
1274
- try {
1275
- ctx.clip();
1276
- } catch {
1590
+ const op = RGN_MODE_OPS[iMode];
1591
+ if (!op) {
1592
+ return true;
1593
+ }
1594
+ if (cbRgnData === 0) {
1595
+ if (op === "replace") {
1596
+ rCtx.clipRegion = null;
1597
+ rCtx.clipUntracked = false;
1598
+ reapplyClipRegion(rCtx, null);
1277
1599
  }
1600
+ return true;
1278
1601
  }
1279
- return true;
1280
- }
1281
- function handleExcludeClipRect(rCtx, dataOff, recSize) {
1282
- if (recSize >= 24) {
1283
- rCtx.view.getInt32(dataOff, true);
1284
- rCtx.view.getInt32(dataOff + 4, true);
1285
- rCtx.view.getInt32(dataOff + 8, true);
1286
- rCtx.view.getInt32(dataOff + 12, true);
1602
+ const rgnStart = dataOff + 8;
1603
+ if (cbRgnData < 32) {
1604
+ return true;
1287
1605
  }
1606
+ const nCount = view.getUint32(rgnStart + 8, true);
1607
+ if (nCount === 0) {
1608
+ return true;
1609
+ }
1610
+ const rects = [];
1611
+ const rectsStart = rgnStart + 32;
1612
+ for (let i = 0; i < nCount; i++) {
1613
+ const rOff = rectsStart + i * 16;
1614
+ if (rOff + 16 > dataOff + 8 + cbRgnData) {
1615
+ break;
1616
+ }
1617
+ const left = view.getInt32(rOff, true);
1618
+ const top = view.getInt32(rOff + 4, true);
1619
+ const right = view.getInt32(rOff + 8, true);
1620
+ const bottom = view.getInt32(rOff + 12, true);
1621
+ rects.push({
1622
+ x: gmx(rCtx, left),
1623
+ y: gmy(rCtx, top),
1624
+ w: gmw(rCtx, right - left),
1625
+ h: gmh(rCtx, bottom - top)
1626
+ });
1627
+ }
1628
+ if (rects.length === 0) {
1629
+ return true;
1630
+ }
1631
+ gdiCombineClip(rCtx, rectsClipShape(rects), op);
1288
1632
  return true;
1289
1633
  }
1290
1634
  function handleOffsetClipRgn(rCtx, dataOff, recSize) {
1291
1635
  if (recSize >= 16) {
1292
- rCtx.view.getInt32(dataOff, true);
1293
- rCtx.view.getInt32(dataOff + 4, true);
1636
+ const dx = rCtx.view.getInt32(dataOff, true);
1637
+ const dy = rCtx.view.getInt32(dataOff + 4, true);
1638
+ if (rCtx.clipUntracked) {
1639
+ return true;
1640
+ }
1641
+ if (rCtx.clipRegion) {
1642
+ rCtx.clipRegion = translateClipRegion(rCtx.clipRegion, gmw(rCtx, dx), gmh(rCtx, dy));
1643
+ reapplyClipRegion(rCtx, rCtx.clipRegion);
1644
+ }
1294
1645
  }
1295
1646
  return true;
1296
1647
  }
@@ -1613,14 +1964,12 @@ function handleEmfGdiPolyPathRecord(rCtx, recType, offset, dataOff, recSize) {
1613
1964
  ctx.restore();
1614
1965
  rCtx.clipSaveDepth--;
1615
1966
  }
1616
- ctx.save();
1617
- rCtx.clipSaveDepth++;
1618
- ctx.clip();
1619
- } else {
1620
- ctx.save();
1621
- rCtx.clipSaveDepth++;
1622
- ctx.clip();
1623
1967
  }
1968
+ ctx.save();
1969
+ rCtx.clipSaveDepth++;
1970
+ ctx.clip(state.polyFillMode === 2 ? "nonzero" : "evenodd");
1971
+ rCtx.clipRegion = null;
1972
+ rCtx.clipUntracked = true;
1624
1973
  } catch {
1625
1974
  }
1626
1975
  return true;
@@ -1916,7 +2265,9 @@ function createEmfPlusState() {
1916
2265
  objectTable: /* @__PURE__ */ new Map(),
1917
2266
  worldTransform: [1, 0, 0, 1, 0, 0],
1918
2267
  saveStack: [],
1919
- saveIdMap: /* @__PURE__ */ new Map()
2268
+ saveIdMap: /* @__PURE__ */ new Map(),
2269
+ clipRegion: null,
2270
+ clipSaveDepth: 0
1920
2271
  };
1921
2272
  }
1922
2273
 
@@ -1936,8 +2287,19 @@ function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
1936
2287
  ctx.restore();
1937
2288
  rCtx.clipSaveDepth--;
1938
2289
  }
2290
+ rCtx.clipStack ?? (rCtx.clipStack = []);
2291
+ rCtx.clipStack.push({
2292
+ region: rCtx.clipRegion ?? null,
2293
+ untracked: rCtx.clipUntracked ?? false
2294
+ });
1939
2295
  rCtx.stateStack.push(cloneState(state));
1940
2296
  ctx.save();
2297
+ if (rCtx.clipUntracked) {
2298
+ rCtx.clipUntracked = false;
2299
+ rCtx.clipRegion = null;
2300
+ } else if (rCtx.clipRegion) {
2301
+ reapplyClipRegion(rCtx, rCtx.clipRegion);
2302
+ }
1941
2303
  return true;
1942
2304
  }
1943
2305
  case EMR_RESTOREDC: {
@@ -1948,16 +2310,23 @@ function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
1948
2310
  }
1949
2311
  let rel = view.getInt32(dataOff, true);
1950
2312
  if (rel < 0) {
1951
- rel = rCtx.stateStack.length + rel;
2313
+ rel = rCtx.stateStack.length + rel + 1;
1952
2314
  }
1953
2315
  while (rCtx.stateStack.length > rel && rCtx.stateStack.length > 0) {
1954
2316
  rCtx.stateStack.pop();
2317
+ rCtx.clipStack?.pop();
1955
2318
  ctx.restore();
1956
2319
  }
1957
2320
  const restored = rCtx.stateStack.pop();
1958
2321
  if (restored) {
2322
+ const clipSnapshot = rCtx.clipStack?.pop();
1959
2323
  Object.assign(state, restored);
1960
2324
  ctx.restore();
2325
+ rCtx.clipRegion = clipSnapshot?.region ?? null;
2326
+ rCtx.clipUntracked = false;
2327
+ if (rCtx.clipRegion) {
2328
+ reapplyClipRegion(rCtx, rCtx.clipRegion);
2329
+ }
1961
2330
  }
1962
2331
  }
1963
2332
  return true;
@@ -2076,6 +2445,47 @@ function parseEmfPlusPath(data, off, maxLen) {
2076
2445
  const types = new Uint8Array(data.buffer, data.byteOffset + alignedPOff, pointCount);
2077
2446
  return { kind: "plus-path", points, types: new Uint8Array(types) };
2078
2447
  }
2448
+ function emfPlusPathToClipCmds(path, m) {
2449
+ const tx = (x, y) => m[0] * x + m[2] * y + m[4];
2450
+ const ty = (x, y) => m[1] * x + m[3] * y + m[5];
2451
+ const cmds = [];
2452
+ const pts = path.points;
2453
+ const types = path.types;
2454
+ let i = 0;
2455
+ while (i < pts.length) {
2456
+ const t = types[i] & 15;
2457
+ const close = (types[i] & 128) !== 0;
2458
+ if (t === 0) {
2459
+ cmds.push({ op: "moveTo", x: tx(pts[i].x, pts[i].y), y: ty(pts[i].x, pts[i].y) });
2460
+ i++;
2461
+ } else if (t === 3) {
2462
+ if (i + 2 < pts.length) {
2463
+ cmds.push({
2464
+ op: "bezierCurveTo",
2465
+ cp1x: tx(pts[i].x, pts[i].y),
2466
+ cp1y: ty(pts[i].x, pts[i].y),
2467
+ cp2x: tx(pts[i + 1].x, pts[i + 1].y),
2468
+ cp2y: ty(pts[i + 1].x, pts[i + 1].y),
2469
+ x: tx(pts[i + 2].x, pts[i + 2].y),
2470
+ y: ty(pts[i + 2].x, pts[i + 2].y)
2471
+ });
2472
+ if ((types[i + 2] & 128) !== 0) {
2473
+ cmds.push({ op: "closePath" });
2474
+ }
2475
+ i += 3;
2476
+ continue;
2477
+ }
2478
+ break;
2479
+ } else {
2480
+ cmds.push({ op: "lineTo", x: tx(pts[i].x, pts[i].y), y: ty(pts[i].x, pts[i].y) });
2481
+ i++;
2482
+ }
2483
+ if (close) {
2484
+ cmds.push({ op: "closePath" });
2485
+ }
2486
+ }
2487
+ return cmds;
2488
+ }
2079
2489
  function replayEmfPlusPath(ctx, path) {
2080
2490
  ctx.beginPath();
2081
2491
  const pts = path.points;
@@ -2129,12 +2539,44 @@ function multiplyMatrix(m1, m2) {
2129
2539
  m1[4] * m2[1] + m1[5] * m2[3] + m2[5]
2130
2540
  ];
2131
2541
  }
2132
- function resolveBrushColor(rCtx, flags, brushIdOrColor) {
2542
+ function createBrushGradient(rCtx, grad) {
2543
+ const ctx = rCtx.ctx;
2544
+ try {
2545
+ let g = null;
2546
+ if (grad.type === "linear" && typeof ctx.createLinearGradient === "function") {
2547
+ if (grad.x1 === grad.x2 && grad.y1 === grad.y2) {
2548
+ return null;
2549
+ }
2550
+ g = ctx.createLinearGradient(grad.x1, grad.y1, grad.x2, grad.y2);
2551
+ } else if (grad.type === "radial" && typeof ctx.createRadialGradient === "function") {
2552
+ if (!(grad.r > 0)) {
2553
+ return null;
2554
+ }
2555
+ g = ctx.createRadialGradient(grad.cx, grad.cy, 0, grad.cx, grad.cy, grad.r);
2556
+ }
2557
+ if (!g) {
2558
+ return null;
2559
+ }
2560
+ for (const stop of grad.stops) {
2561
+ g.addColorStop(stop.offset, stop.color);
2562
+ }
2563
+ return g;
2564
+ } catch {
2565
+ return null;
2566
+ }
2567
+ }
2568
+ function resolveBrushPaint(rCtx, flags, brushIdOrColor) {
2133
2569
  if (flags & 32768) {
2134
2570
  return argbToRgba(brushIdOrColor);
2135
2571
  }
2136
2572
  const obj = rCtx.objectTable.get(brushIdOrColor & 255);
2137
2573
  if (obj && obj.kind === "plus-brush") {
2574
+ if (obj.gradient) {
2575
+ const g = createBrushGradient(rCtx, obj.gradient);
2576
+ if (g) {
2577
+ return g;
2578
+ }
2579
+ }
2138
2580
  return obj.color;
2139
2581
  }
2140
2582
  return "rgba(0,0,0,1)";
@@ -2198,88 +2640,83 @@ function popState(rCtx, stackId) {
2198
2640
  rCtx.saveIdMap = newMap;
2199
2641
  }
2200
2642
  }
2201
- function ensureClipSave(rCtx) {
2202
- if (rCtx.clipSaveDepth === 0) {
2203
- rCtx.ctx.save();
2204
- rCtx.clipSaveDepth = 1;
2205
- }
2643
+ function plusDeviceMatrix(rCtx) {
2644
+ const wt = rCtx.worldTransform;
2645
+ const s = getPageUnitMultiplier(rCtx.pageUnit, rCtx.pageScale) * rCtx.dpiScale;
2646
+ return [wt[0] * s, wt[1] * s, wt[2] * s, wt[3] * s, wt[4] * s, wt[5] * s];
2206
2647
  }
2207
- function resetClipState(rCtx) {
2208
- if (rCtx.clipSaveDepth > 0) {
2209
- rCtx.ctx.restore();
2210
- rCtx.clipSaveDepth = 0;
2211
- }
2648
+ function transformedRectShape(x, y, w, h, m) {
2649
+ const tx = (px, py) => m[0] * px + m[2] * py + m[4];
2650
+ const ty = (px, py) => m[1] * px + m[3] * py + m[5];
2651
+ const cmds = [
2652
+ { op: "moveTo", x: tx(x, y), y: ty(x, y) },
2653
+ { op: "lineTo", x: tx(x + w, y), y: ty(x + w, y) },
2654
+ { op: "lineTo", x: tx(x + w, y + h), y: ty(x + w, y + h) },
2655
+ { op: "lineTo", x: tx(x, y + h), y: ty(x, y + h) },
2656
+ { op: "closePath" }
2657
+ ];
2658
+ return { cmds, fillRule: "nonzero", simple: true };
2212
2659
  }
2213
- function applyClipCombineMode(rCtx, combineMode, opName, clipFn) {
2214
- switch (combineMode) {
2215
- case 0:
2216
- resetClipState(rCtx);
2217
- ensureClipSave(rCtx);
2218
- clipFn();
2219
- return true;
2220
- case 1:
2221
- ensureClipSave(rCtx);
2222
- clipFn();
2223
- return true;
2224
- case 2:
2225
- ensureClipSave(rCtx);
2226
- clipFn();
2227
- return true;
2228
- case 3:
2229
- return false;
2230
- case 4:
2231
- return false;
2232
- case 5:
2233
- return false;
2234
- default:
2235
- ensureClipSave(rCtx);
2236
- clipFn();
2237
- return true;
2238
- }
2660
+ function pathClipShape(path, m) {
2661
+ return { cmds: emfPlusPathToClipCmds(path.path, m), fillRule: "nonzero", simple: true };
2239
2662
  }
2240
- var MAX_REGION_TRACE_DEPTH = 64;
2241
- function traceRegionNodePath(ctx, node, depth = 0) {
2242
- if (depth > MAX_REGION_TRACE_DEPTH) {
2243
- ctx.rect(0, 0, 0, 0);
2244
- return;
2663
+ var REGION_NODE_OPS = {
2664
+ 0: "intersect",
2665
+ // legacy/lenient: treat 0 as And
2666
+ 1: "intersect",
2667
+ // RegionNodeDataTypeAnd
2668
+ 2: "union",
2669
+ // RegionNodeDataTypeOr
2670
+ 3: "xor",
2671
+ // RegionNodeDataTypeXor
2672
+ 4: "exclude",
2673
+ // RegionNodeDataTypeExclude
2674
+ 5: "complement"
2675
+ // RegionNodeDataTypeComplement
2676
+ };
2677
+ var MAX_REGION_FLATTEN_DEPTH = 64;
2678
+ function flattenRegionNode(node, m, depth = 0) {
2679
+ if (depth > MAX_REGION_FLATTEN_DEPTH) {
2680
+ return { region: [emptyClipShape()], exact: false };
2245
2681
  }
2246
2682
  switch (node.type) {
2247
2683
  case "rect":
2248
- ctx.rect(node.x, node.y, node.width, node.height);
2249
- break;
2684
+ return { region: [transformedRectShape(node.x, node.y, node.width, node.height, m)], exact: true };
2250
2685
  case "path":
2251
- replayEmfPlusPath(ctx, node.path);
2252
- break;
2686
+ return { region: [pathClipShape(node, m)], exact: true };
2253
2687
  case "infinite":
2254
- ctx.rect(-1e6, -1e6, 2e6, 2e6);
2255
- break;
2688
+ return { region: null, exact: true };
2256
2689
  case "empty":
2257
- ctx.rect(0, 0, 0, 0);
2258
- break;
2259
- case "combine":
2260
- traceRegionNodePath(ctx, node.left, depth + 1);
2261
- if (node.combineMode !== 0) {
2262
- emfWarn(
2263
- `traceRegionNodePath: combine mode ${node.combineMode} not fully supported, using left subtree only`
2264
- );
2265
- } else {
2266
- try {
2267
- ctx.clip();
2268
- } catch {
2269
- }
2270
- ctx.beginPath();
2271
- traceRegionNodePath(ctx, node.right, depth + 1);
2272
- }
2273
- break;
2690
+ return { region: [emptyClipShape()], exact: true };
2691
+ case "combine": {
2692
+ const left = flattenRegionNode(node.left, m, depth + 1);
2693
+ const right = flattenRegionNode(node.right, m, depth + 1);
2694
+ const op = REGION_NODE_OPS[node.combineMode] ?? "intersect";
2695
+ const combined = combineClipRegions(left.region, right.region, op);
2696
+ return { region: combined.region, exact: combined.exact && left.exact && right.exact };
2697
+ }
2274
2698
  }
2275
2699
  }
2276
- function applyRegionNodeClip(ctx, rootNode, combineMode) {
2277
- ctx.beginPath();
2278
- traceRegionNodePath(ctx, rootNode);
2279
- try {
2280
- ctx.clip();
2281
- } catch {
2282
- }
2700
+ var PLUS_COMBINE_OPS = {
2701
+ 0: "replace",
2702
+ 1: "intersect",
2703
+ 2: "union",
2704
+ 3: "xor",
2705
+ 4: "exclude",
2706
+ 5: "complement"
2707
+ };
2708
+ function reapplyPlusClip(rCtx) {
2709
+ reapplyClipRegion(rCtx, rCtx.clipRegion ?? null, true);
2710
+ }
2711
+ function applyPlusClipRegion(rCtx, incoming, combineMode, opName) {
2712
+ const op = PLUS_COMBINE_OPS[combineMode];
2713
+ const res = combineClipRegions(rCtx.clipRegion ?? null, incoming, op ?? "intersect");
2714
+ if (!res.exact) ;
2715
+ rCtx.clipRegion = res.region;
2716
+ reapplyPlusClip(rCtx);
2717
+ }
2718
+ function applyPlusClipShape(rCtx, shape, combineMode, opName) {
2719
+ applyPlusClipRegion(rCtx, [shape], combineMode);
2283
2720
  }
2284
2721
  function handleEmfPlusStateRecord(rCtx, recType, recFlags, dataOff, recDataSize) {
2285
2722
  const { view } = rCtx;
@@ -2381,30 +2818,24 @@ function handleEmfPlusStateRecord(rCtx, recType, recFlags, dataOff, recDataSize)
2381
2818
  const cy = view.getFloat32(dataOff + 4, true);
2382
2819
  const cw = view.getFloat32(dataOff + 8, true);
2383
2820
  const ch = view.getFloat32(dataOff + 12, true);
2384
- applyClipCombineMode(rCtx, combineMode, "SetClipRect", () => {
2385
- applyPlusWorldTransform(rCtx);
2386
- rCtx.ctx.beginPath();
2387
- rCtx.ctx.rect(cx, cy, cw, ch);
2388
- try {
2389
- rCtx.ctx.clip();
2390
- } catch {
2391
- }
2392
- });
2821
+ const shape = transformedRectShape(cx, cy, cw, ch, plusDeviceMatrix(rCtx));
2822
+ applyPlusClipShape(rCtx, shape, combineMode);
2393
2823
  }
2394
2824
  return true;
2395
2825
  }
2396
2826
  case EMFPLUS_RESETCLIP: {
2397
- resetClipState(rCtx);
2398
- ensureClipSave(rCtx);
2827
+ rCtx.clipRegion = null;
2828
+ reapplyPlusClip(rCtx);
2399
2829
  return true;
2400
2830
  }
2401
2831
  case EMFPLUS_SETCLIPREGION: {
2402
2832
  const regionId = recFlags & 255;
2833
+ const combineMode = recFlags >> 8 & 15;
2403
2834
  const regionObj = rCtx.objectTable.get(regionId);
2404
2835
  if (regionObj && regionObj.kind === "plus-region" && regionObj.nodes.length > 0) {
2405
- applyPlusWorldTransform(rCtx);
2406
- const rootNode = regionObj.nodes[0];
2407
- applyRegionNodeClip(rCtx.ctx, rootNode);
2836
+ const flattened = flattenRegionNode(regionObj.nodes[0], plusDeviceMatrix(rCtx));
2837
+ if (!flattened.exact) ;
2838
+ applyPlusClipRegion(rCtx, flattened.region, combineMode);
2408
2839
  }
2409
2840
  return true;
2410
2841
  }
@@ -2413,22 +2844,26 @@ function handleEmfPlusStateRecord(rCtx, recType, recFlags, dataOff, recDataSize)
2413
2844
  const combineMode = recFlags >> 8 & 15;
2414
2845
  const pathObj = rCtx.objectTable.get(pathId);
2415
2846
  if (pathObj && pathObj.kind === "plus-path") {
2416
- applyClipCombineMode(rCtx, combineMode, "SetClipPath", () => {
2417
- applyPlusWorldTransform(rCtx);
2418
- rCtx.ctx.beginPath();
2419
- replayEmfPlusPath(rCtx.ctx, pathObj);
2420
- try {
2421
- rCtx.ctx.clip();
2422
- } catch {
2423
- }
2424
- });
2847
+ const shape = {
2848
+ cmds: emfPlusPathToClipCmds(pathObj, plusDeviceMatrix(rCtx)),
2849
+ fillRule: "nonzero",
2850
+ simple: true
2851
+ };
2852
+ applyPlusClipShape(rCtx, shape, combineMode);
2425
2853
  }
2426
2854
  return true;
2427
2855
  }
2428
2856
  case EMFPLUS_OFFSETCLIP: {
2429
2857
  if (recDataSize >= 8) {
2430
- view.getFloat32(dataOff, true);
2431
- view.getFloat32(dataOff + 4, true);
2858
+ const dx = view.getFloat32(dataOff, true);
2859
+ const dy = view.getFloat32(dataOff + 4, true);
2860
+ if (rCtx.clipRegion) {
2861
+ const m = plusDeviceMatrix(rCtx);
2862
+ const ddx = m[0] * dx + m[2] * dy;
2863
+ const ddy = m[1] * dx + m[3] * dy;
2864
+ rCtx.clipRegion = translateClipRegion(rCtx.clipRegion, ddx, ddy);
2865
+ reapplyPlusClip(rCtx);
2866
+ }
2432
2867
  }
2433
2868
  return true;
2434
2869
  }
@@ -2501,7 +2936,7 @@ function handleEmfPlusDrawRecord(rCtx, recType, recFlags, dataOff, recDataSize)
2501
2936
  const count = view.getUint32(dataOff + 4, true);
2502
2937
  const compressed = (recFlags & 16384) !== 0;
2503
2938
  const rectSize = compressed ? 8 : 16;
2504
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
2939
+ ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
2505
2940
  applyPlusWorldTransform(rCtx);
2506
2941
  let rOff = dataOff + 8;
2507
2942
  for (let i = 0; i < count && rOff + rectSize <= dataOff + recDataSize; i++) {
@@ -2551,7 +2986,7 @@ function handleEmfPlusDrawRecord(rCtx, recType, recFlags, dataOff, recDataSize)
2551
2986
  w = view.getFloat32(dataOff + 12, true);
2552
2987
  h = view.getFloat32(dataOff + 16, true);
2553
2988
  }
2554
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
2989
+ ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
2555
2990
  applyPlusWorldTransform(rCtx);
2556
2991
  ctx.beginPath();
2557
2992
  ctx.ellipse(x + w / 2, y + h / 2, Math.abs(w) / 2, Math.abs(h) / 2, 0, 0, Math.PI * 2);
@@ -2597,7 +3032,7 @@ function handleEmfPlusDrawRecord(rCtx, recType, recFlags, dataOff, recDataSize)
2597
3032
  let aOff = dataOff;
2598
3033
  if (isFill) {
2599
3034
  const brushVal = view.getUint32(aOff, true);
2600
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
3035
+ ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
2601
3036
  aOff += 4;
2602
3037
  }
2603
3038
  const startAngle = view.getFloat32(aOff, true) * Math.PI / 180;
@@ -2678,7 +3113,7 @@ function handleEmfPlusDrawRecord(rCtx, recType, recFlags, dataOff, recDataSize)
2678
3113
  const count = view.getUint32(dataOff + 4, true);
2679
3114
  const compressed = (recFlags & 16384) !== 0;
2680
3115
  const ptSize = compressed ? 4 : 8;
2681
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
3116
+ ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
2682
3117
  applyPlusWorldTransform(rCtx);
2683
3118
  ctx.beginPath();
2684
3119
  let pOff = dataOff + 8;
@@ -2701,6 +3136,261 @@ function handleEmfPlusDrawRecord(rCtx, recType, recFlags, dataOff, recDataSize)
2701
3136
  }
2702
3137
  }
2703
3138
 
3139
+ // src/emf-plus-brush-parser.ts
3140
+ var BRUSH_DATA_PATH = 1;
3141
+ var BRUSH_DATA_TRANSFORM = 2;
3142
+ var BRUSH_DATA_PRESET_COLORS = 4;
3143
+ var BRUSH_DATA_BLEND_FACTORS_H = 8;
3144
+ var MAX_GRADIENT_ELEMENTS = 4096;
3145
+ function looksLikeGraphicsVersion(v) {
3146
+ return v >>> 12 === 900097;
3147
+ }
3148
+ function readTransform(view, off) {
3149
+ return [
3150
+ view.getFloat32(off, true),
3151
+ view.getFloat32(off + 4, true),
3152
+ view.getFloat32(off + 8, true),
3153
+ view.getFloat32(off + 12, true),
3154
+ view.getFloat32(off + 16, true),
3155
+ view.getFloat32(off + 20, true)
3156
+ ];
3157
+ }
3158
+ function applyMatrix(m, x, y) {
3159
+ return { x: m[0] * x + m[2] * y + m[4], y: m[1] * x + m[3] * y + m[5] };
3160
+ }
3161
+ function clamp01(v) {
3162
+ return Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : 0;
3163
+ }
3164
+ function normaliseStops(stops) {
3165
+ return stops.map((s) => ({ offset: clamp01(s.offset), color: s.color })).sort((a, b) => a.offset - b.offset);
3166
+ }
3167
+ function readPresetColors(view, off, end) {
3168
+ if (off + 4 > end) {
3169
+ return null;
3170
+ }
3171
+ const count = view.getUint32(off, true);
3172
+ if (count === 0 || count > MAX_GRADIENT_ELEMENTS) {
3173
+ return null;
3174
+ }
3175
+ const posOff = off + 4;
3176
+ const colOff = posOff + count * 4;
3177
+ const next = colOff + count * 4;
3178
+ if (next > end) {
3179
+ return null;
3180
+ }
3181
+ const stops = [];
3182
+ for (let i = 0; i < count; i++) {
3183
+ stops.push({
3184
+ offset: view.getFloat32(posOff + i * 4, true),
3185
+ color: argbToRgba(view.getUint32(colOff + i * 4, true))
3186
+ });
3187
+ }
3188
+ return { stops, next };
3189
+ }
3190
+ function readBlendFactors(view, off, end) {
3191
+ if (off + 4 > end) {
3192
+ return null;
3193
+ }
3194
+ const count = view.getUint32(off, true);
3195
+ if (count === 0 || count > MAX_GRADIENT_ELEMENTS) {
3196
+ return null;
3197
+ }
3198
+ const posOff = off + 4;
3199
+ const facOff = posOff + count * 4;
3200
+ const next = facOff + count * 4;
3201
+ if (next > end) {
3202
+ return null;
3203
+ }
3204
+ const entries = [];
3205
+ for (let i = 0; i < count; i++) {
3206
+ entries.push({
3207
+ pos: view.getFloat32(posOff + i * 4, true),
3208
+ factor: view.getFloat32(facOff + i * 4, true)
3209
+ });
3210
+ }
3211
+ return { entries, next };
3212
+ }
3213
+ function parseLinearGradient(view, b, end) {
3214
+ if (b + 40 > end) {
3215
+ return null;
3216
+ }
3217
+ const flags = view.getUint32(b, true);
3218
+ const rx = view.getFloat32(b + 8, true);
3219
+ const ry = view.getFloat32(b + 12, true);
3220
+ const rw = view.getFloat32(b + 16, true);
3221
+ const rh = view.getFloat32(b + 20, true);
3222
+ const startArgb = view.getUint32(b + 24, true);
3223
+ const endArgb = view.getUint32(b + 28, true);
3224
+ let o = b + 40;
3225
+ let transform = null;
3226
+ if (flags & BRUSH_DATA_TRANSFORM && o + 24 <= end) {
3227
+ transform = readTransform(view, o);
3228
+ o += 24;
3229
+ }
3230
+ let stops = [
3231
+ { offset: 0, color: argbToRgba(startArgb) },
3232
+ { offset: 1, color: argbToRgba(endArgb) }
3233
+ ];
3234
+ if (flags & BRUSH_DATA_PRESET_COLORS) {
3235
+ const preset = readPresetColors(view, o, end);
3236
+ if (preset) {
3237
+ stops = preset.stops;
3238
+ }
3239
+ } else if (flags & BRUSH_DATA_BLEND_FACTORS_H) {
3240
+ const blend = readBlendFactors(view, o, end);
3241
+ if (blend) {
3242
+ stops = blend.entries.map((e) => ({
3243
+ offset: e.pos,
3244
+ color: lerpArgbToRgba(startArgb, endArgb, e.factor)
3245
+ }));
3246
+ }
3247
+ }
3248
+ let p1 = { x: rx, y: ry + rh / 2 };
3249
+ let p2 = { x: rx + rw, y: ry + rh / 2 };
3250
+ if (transform) {
3251
+ p1 = applyMatrix(transform, p1.x, p1.y);
3252
+ p2 = applyMatrix(transform, p2.x, p2.y);
3253
+ }
3254
+ emfLog(
3255
+ `parseEmfPlusBrushObject: linear gradient (${p1.x.toFixed(1)},${p1.y.toFixed(1)})\u2192(${p2.x.toFixed(1)},${p2.y.toFixed(1)}), ${stops.length} stop(s)`
3256
+ );
3257
+ return {
3258
+ kind: "plus-brush",
3259
+ color: argbToRgba(startArgb),
3260
+ gradient: {
3261
+ type: "linear",
3262
+ x1: p1.x,
3263
+ y1: p1.y,
3264
+ x2: p2.x,
3265
+ y2: p2.y,
3266
+ stops: normaliseStops(stops)
3267
+ }
3268
+ };
3269
+ }
3270
+ function parsePathGradient(view, b, end) {
3271
+ if (b + 24 > end) {
3272
+ return null;
3273
+ }
3274
+ const flags = view.getUint32(b, true);
3275
+ const centerArgb = view.getUint32(b + 8, true);
3276
+ let cx = view.getFloat32(b + 12, true);
3277
+ let cy = view.getFloat32(b + 16, true);
3278
+ const surroundCount = view.getUint32(b + 20, true);
3279
+ if (surroundCount > MAX_GRADIENT_ELEMENTS) {
3280
+ return { kind: "plus-brush", color: argbToRgba(centerArgb) };
3281
+ }
3282
+ const surround = [];
3283
+ let o = b + 24;
3284
+ for (let i = 0; i < surroundCount && o + 4 <= end; i++) {
3285
+ surround.push(view.getUint32(o, true));
3286
+ o += 4;
3287
+ }
3288
+ let boundaryPts = [];
3289
+ if (flags & BRUSH_DATA_PATH) {
3290
+ if (o + 4 <= end) {
3291
+ const pathSize = view.getInt32(o, true);
3292
+ o += 4;
3293
+ if (pathSize > 0 && o + pathSize <= end) {
3294
+ const path = parseEmfPlusPath(view, o, pathSize);
3295
+ if (path) {
3296
+ boundaryPts = path.points;
3297
+ }
3298
+ o += pathSize;
3299
+ }
3300
+ }
3301
+ } else if (o + 4 <= end) {
3302
+ const ptCount = view.getUint32(o, true);
3303
+ o += 4;
3304
+ if (ptCount > 0 && ptCount <= MAX_GRADIENT_ELEMENTS && o + ptCount * 8 <= end) {
3305
+ for (let i = 0; i < ptCount; i++) {
3306
+ boundaryPts.push({
3307
+ x: view.getFloat32(o + i * 8, true),
3308
+ y: view.getFloat32(o + i * 8 + 4, true)
3309
+ });
3310
+ }
3311
+ o += ptCount * 8;
3312
+ }
3313
+ }
3314
+ let transform = null;
3315
+ if (flags & BRUSH_DATA_TRANSFORM && o + 24 <= end) {
3316
+ transform = readTransform(view, o);
3317
+ o += 24;
3318
+ }
3319
+ if (transform) {
3320
+ ({ x: cx, y: cy } = applyMatrix(transform, cx, cy));
3321
+ boundaryPts = boundaryPts.map((p) => applyMatrix(transform, p.x, p.y));
3322
+ }
3323
+ const surroundArgb = surround.length > 0 ? surround[0] : centerArgb;
3324
+ let stops = [
3325
+ { offset: 0, color: argbToRgba(centerArgb) },
3326
+ { offset: 1, color: argbToRgba(surroundArgb) }
3327
+ ];
3328
+ if (flags & BRUSH_DATA_PRESET_COLORS) {
3329
+ const preset = readPresetColors(view, o, end);
3330
+ if (preset) {
3331
+ stops = preset.stops.map((s) => ({ offset: 1 - s.offset, color: s.color }));
3332
+ }
3333
+ } else if (flags & BRUSH_DATA_BLEND_FACTORS_H) {
3334
+ const blend = readBlendFactors(view, o, end);
3335
+ if (blend) {
3336
+ stops = blend.entries.map((e) => ({
3337
+ offset: 1 - e.pos,
3338
+ color: lerpArgbToRgba(surroundArgb, centerArgb, e.factor)
3339
+ }));
3340
+ }
3341
+ }
3342
+ let r = 0;
3343
+ for (const p of boundaryPts) {
3344
+ const d = Math.hypot(p.x - cx, p.y - cy);
3345
+ if (d > r) {
3346
+ r = d;
3347
+ }
3348
+ }
3349
+ if (!(r > 0)) {
3350
+ return { kind: "plus-brush", color: argbToRgba(centerArgb) };
3351
+ }
3352
+ emfLog(
3353
+ `parseEmfPlusBrushObject: path gradient centre=(${cx.toFixed(1)},${cy.toFixed(1)}), r=${r.toFixed(1)}, ${stops.length} stop(s)`
3354
+ );
3355
+ return {
3356
+ kind: "plus-brush",
3357
+ color: argbToRgba(centerArgb),
3358
+ gradient: { type: "radial", cx, cy, r, stops: normaliseStops(stops) }
3359
+ };
3360
+ }
3361
+ function parseEmfPlusBrushObject(view, dataOff, recDataSize) {
3362
+ if (recDataSize < 8) {
3363
+ return null;
3364
+ }
3365
+ const end = dataOff + recDataSize;
3366
+ const hasVersion = looksLikeGraphicsVersion(view.getUint32(dataOff, true));
3367
+ const typeOff = dataOff + (hasVersion ? 4 : 0);
3368
+ if (typeOff + 8 > end) {
3369
+ return null;
3370
+ }
3371
+ const brushType = view.getUint32(typeOff, true);
3372
+ const b = typeOff + 4;
3373
+ switch (brushType) {
3374
+ case EMFPLUS_BRUSHTYPE_SOLID:
3375
+ return { kind: "plus-brush", color: argbToRgba(view.getUint32(b, true)) };
3376
+ case EMFPLUS_BRUSHTYPE_HATCHFILL:
3377
+ if (b + 8 <= end) {
3378
+ return { kind: "plus-brush", color: argbToRgba(view.getUint32(b + 4, true)) };
3379
+ }
3380
+ return { kind: "plus-brush", color: "rgba(0,0,0,1)" };
3381
+ case EMFPLUS_BRUSHTYPE_LINEARGRADIENT: {
3382
+ const brush = parseLinearGradient(view, b, end);
3383
+ return brush ?? { kind: "plus-brush", color: "rgba(0,0,0,1)" };
3384
+ }
3385
+ case EMFPLUS_BRUSHTYPE_PATHGRADIENT: {
3386
+ const brush = parsePathGradient(view, b, end);
3387
+ return brush ?? { kind: "plus-brush", color: "rgba(0,0,0,1)" };
3388
+ }
3389
+ default:
3390
+ return { kind: "plus-brush", color: "rgba(0,0,0,1)" };
3391
+ }
3392
+ }
3393
+
2704
3394
  // src/emf-plus-bitmap-decoder.ts
2705
3395
  var PIXELFORMAT_24BPP_RGB = 137224;
2706
3396
  var PIXELFORMAT_32BPP_RGB = 139273;
@@ -2810,7 +3500,8 @@ function parseEmfPlusPenObject(view, dataOff, recDataSize) {
2810
3500
  if (recDataSize < 20) {
2811
3501
  return null;
2812
3502
  }
2813
- const penFlags = view.getUint32(dataOff + 4, true);
3503
+ const hasVersion = looksLikeGraphicsVersion(view.getUint32(dataOff, true));
3504
+ const penFlags = view.getUint32(dataOff + (hasVersion ? 8 : 4), true);
2814
3505
  const penWidth = view.getFloat32(dataOff + 16, true);
2815
3506
  let brushOff = dataOff + 20;
2816
3507
  const flagSizes = [
@@ -2870,9 +3561,9 @@ function parseEmfPlusPenObject(view, dataOff, recDataSize) {
2870
3561
  }
2871
3562
  let penColor = "rgba(0,0,0,1)";
2872
3563
  if (brushOff + 8 <= dataOff + recDataSize) {
2873
- const penBrushType = view.getUint32(brushOff, true);
2874
- if (penBrushType === EMFPLUS_BRUSHTYPE_SOLID && brushOff + 8 <= dataOff + recDataSize) {
2875
- penColor = argbToRgba(view.getUint32(brushOff + 4, true));
3564
+ const brush = parseEmfPlusBrushObject(view, brushOff, dataOff + recDataSize - brushOff);
3565
+ if (brush) {
3566
+ penColor = brush.color;
2876
3567
  }
2877
3568
  }
2878
3569
  return { kind: "plus-pen", color: penColor, width: penWidth || 1, dashStyle };
@@ -2968,19 +3659,9 @@ function handleEmfPlusObjectRecord(rCtx, recFlags, dataOff, recDataSize) {
2968
3659
  // Brush
2969
3660
  // ---------------------------------------------------------------
2970
3661
  case EMFPLUS_OBJECTTYPE_BRUSH: {
2971
- if (recDataSize >= 8) {
2972
- const brushType = view.getUint32(dataOff, true);
2973
- let color = "rgba(0,0,0,1)";
2974
- if (brushType === EMFPLUS_BRUSHTYPE_SOLID && recDataSize >= 8) {
2975
- color = argbToRgba(view.getUint32(dataOff + 4, true));
2976
- } else if (brushType === EMFPLUS_BRUSHTYPE_LINEARGRADIENT && recDataSize >= 48) {
2977
- color = argbToRgba(view.getUint32(dataOff + 40, true));
2978
- } else if (brushType === EMFPLUS_BRUSHTYPE_PATHGRADIENT && recDataSize >= 12) {
2979
- color = argbToRgba(view.getUint32(dataOff + 8, true));
2980
- } else if (brushType === EMFPLUS_BRUSHTYPE_HATCHFILL && recDataSize >= 12) {
2981
- color = argbToRgba(view.getUint32(dataOff + 8, true));
2982
- }
2983
- objectTable.set(objectId, { kind: "plus-brush", color });
3662
+ const brush = parseEmfPlusBrushObject(view, dataOff, recDataSize);
3663
+ if (brush) {
3664
+ objectTable.set(objectId, brush);
2984
3665
  }
2985
3666
  break;
2986
3667
  }
@@ -3076,7 +3757,7 @@ function parseRegionNode(view, off, endOff, depth = 0) {
3076
3757
  }
3077
3758
  const nodeType = view.getUint32(off, true);
3078
3759
  let cursor = off + 4;
3079
- if (nodeType <= 4) {
3760
+ if (nodeType <= 5) {
3080
3761
  const leftResult = parseRegionNode(view, cursor, endOff, depth + 1);
3081
3762
  if (!leftResult) {
3082
3763
  return null;
@@ -3165,7 +3846,7 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
3165
3846
  const pathId = recFlags & 255;
3166
3847
  const pathObj = objectTable.get(pathId);
3167
3848
  if (pathObj && pathObj.kind === "plus-path") {
3168
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
3849
+ ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
3169
3850
  applyPlusWorldTransform(rCtx);
3170
3851
  replayEmfPlusPath(ctx, pathObj);
3171
3852
  ctx.fill();
@@ -3210,7 +3891,7 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
3210
3891
  const italic = font.flags & 2 ? "italic " : "";
3211
3892
  const family = mapFontFamily(font.family, rCtx.fontFamilyMap);
3212
3893
  ctx.font = `${italic}${bold}${font.emSize}px ${family}`;
3213
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
3894
+ ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
3214
3895
  ctx.textBaseline = "top";
3215
3896
  const sf = objectTable.get(formatId);
3216
3897
  if (sf && sf.kind === "plus-stringformat") {
@@ -3250,7 +3931,7 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
3250
3931
  const italic = font.flags & 2 ? "italic " : "";
3251
3932
  const family = mapFontFamily(font.family, rCtx.fontFamilyMap);
3252
3933
  ctx.font = `${italic}${bold}${font.emSize}px ${family}`;
3253
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
3934
+ ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
3254
3935
  ctx.textBaseline = "alphabetic";
3255
3936
  ctx.textAlign = "left";
3256
3937
  applyPlusWorldTransform(rCtx);
@@ -3427,7 +4108,8 @@ function replayEmfPlusRecords(view, offset, length, ctx, _canvasW, _canvasH, sta
3427
4108
  saveIdMap: s.saveIdMap,
3428
4109
  totalImageObjects: 0,
3429
4110
  totalDrawImageCalls: 0,
3430
- clipSaveDepth: 0,
4111
+ clipSaveDepth: s.clipSaveDepth,
4112
+ clipRegion: s.clipRegion,
3431
4113
  pageUnit: 2,
3432
4114
  pageScale: 1,
3433
4115
  continuationBuffer: null,
@@ -3561,6 +4243,8 @@ function replayEmfPlusRecords(view, offset, length, ctx, _canvasW, _canvasH, sta
3561
4243
  if (state) {
3562
4244
  state.worldTransform = rCtx.worldTransform;
3563
4245
  state.saveIdMap = rCtx.saveIdMap;
4246
+ state.clipRegion = rCtx.clipRegion ?? null;
4247
+ state.clipSaveDepth = rCtx.clipSaveDepth;
3564
4248
  }
3565
4249
  ctx.setTransform(1, 0, 0, 1, 0, 0);
3566
4250
  return rCtx.deferredImages;
@@ -4180,19 +4864,7 @@ async function processDeferredImages(ctx, deferredImages, recursionDepth = 0) {
4180
4864
  continue;
4181
4865
  }
4182
4866
  emfLog(` Deferred image [${idx}]: recursively converting embedded metafile...`);
4183
- const metafileDataUrl = await convertEmfToDataUrl(
4184
- plainBuffer,
4185
- void 0,
4186
- void 0,
4187
- void 0,
4188
- recursionDepth + 1
4189
- ) ?? await convertWmfToDataUrl(
4190
- plainBuffer,
4191
- void 0,
4192
- void 0,
4193
- void 0,
4194
- recursionDepth + 1
4195
- );
4867
+ const metafileDataUrl = await convertEmfToDataUrl(plainBuffer, void 0, recursionDepth + 1) ?? await convertWmfToDataUrl(plainBuffer, void 0, recursionDepth + 1);
4196
4868
  if (metafileDataUrl) {
4197
4869
  emfLog(
4198
4870
  ` Deferred image [${idx}]: metafile converted, dataUrl length=${metafileDataUrl.length}`
@@ -4237,14 +4909,14 @@ async function processDeferredImages(ctx, deferredImages, recursionDepth = 0) {
4237
4909
  }
4238
4910
  ctx.setTransform(1, 0, 0, 1, 0, 0);
4239
4911
  }
4240
- async function convertEmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScale, recursionDepth = 0) {
4912
+ async function convertEmfToDataUrl(buffer, options, recursionDepth = 0) {
4241
4913
  if (recursionDepth > MAX_METAFILE_RECURSION) {
4242
4914
  return null;
4243
4915
  }
4244
- const opts = typeof optionsOrDpiScale === "number" ? { dpiScale: optionsOrDpiScale } : optionsOrDpiScale ?? {};
4916
+ const opts = options ?? {};
4245
4917
  const dpiScale = opts.dpiScale ?? DEFAULT_DPI_SCALE;
4246
- const effectiveMaxWidth = maxWidth ?? opts.maxWidth;
4247
- const effectiveMaxHeight = maxHeight ?? opts.maxHeight;
4918
+ const effectiveMaxWidth = opts.maxWidth;
4919
+ const effectiveMaxHeight = opts.maxHeight;
4248
4920
  const replayOptions = {
4249
4921
  maxRecords: opts.maxRecords,
4250
4922
  maxRecordsEmfPlus: opts.maxRecords,
@@ -4322,14 +4994,14 @@ async function convertEmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScal
4322
4994
  return null;
4323
4995
  }
4324
4996
  }
4325
- async function convertWmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScale, recursionDepth = 0) {
4997
+ async function convertWmfToDataUrl(buffer, options, recursionDepth = 0) {
4326
4998
  if (recursionDepth > MAX_METAFILE_RECURSION) {
4327
4999
  return null;
4328
5000
  }
4329
- const opts = typeof optionsOrDpiScale === "number" ? { dpiScale: optionsOrDpiScale } : optionsOrDpiScale ?? {};
5001
+ const opts = options ?? {};
4330
5002
  const dpiScale = opts.dpiScale ?? DEFAULT_DPI_SCALE;
4331
- const effectiveMaxWidth = maxWidth ?? opts.maxWidth;
4332
- const effectiveMaxHeight = maxHeight ?? opts.maxHeight;
5003
+ const effectiveMaxWidth = opts.maxWidth;
5004
+ const effectiveMaxHeight = opts.maxHeight;
4333
5005
  const replayOptions = {
4334
5006
  maxRecords: opts.maxRecords,
4335
5007
  fontFamilyMap: opts.fontFamilyMap