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