emf-converter 4.8.7 → 4.8.8

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/browser.js CHANGED
@@ -27798,10 +27798,10 @@ function parseEmfHeader(view) {
27798
27798
  };
27799
27799
  }
27800
27800
  function getRenderableEmfBounds(header) {
27801
- const boundsW = header.bounds.right - header.bounds.left;
27802
- const boundsH = header.bounds.bottom - header.bounds.top;
27801
+ const boundsW = header.bounds.right - header.bounds.left + 1;
27802
+ const boundsH = header.bounds.bottom - header.bounds.top + 1;
27803
27803
  if (boundsW > 0 && boundsH > 0) {
27804
- return header.bounds;
27804
+ return { ...header.bounds, right: header.bounds.right + 1, bottom: header.bounds.bottom + 1 };
27805
27805
  }
27806
27806
  if (header.frameW > 0 && header.frameH > 0) {
27807
27807
  emfLog(
@@ -30833,6 +30833,13 @@ function insetFixBox(box, inset) {
30833
30833
  }
30834
30834
  return framed;
30835
30835
  }
30836
+ function sameEllipseRay(l, t, r, b, sx, sy, ex, ey) {
30837
+ const ax = BigInt(2 * sx - l - r);
30838
+ const ay = BigInt(2 * sy - t - b);
30839
+ const bx = BigInt(2 * ex - l - r);
30840
+ const by = BigInt(2 * ey - t - b);
30841
+ return ax * by === ay * bx && ax * bx + ay * by > 0n;
30842
+ }
30836
30843
  function handleSetPixelV(rCtx, dataOff, recSize) {
30837
30844
  const { ctx, view } = rCtx;
30838
30845
  if (recSize >= 20) {
@@ -31190,7 +31197,9 @@ function handleArcFamily(rCtx, recType, dataOff, recSize) {
31190
31197
  const rx = Math.abs(r - l) / 2;
31191
31198
  const ry = Math.abs(b - t) / 2;
31192
31199
  const startAngle = Math.atan2((startY - cyA) / (ry || 1), (startX - cxA) / (rx || 1));
31193
- const endAngle = Math.atan2((endY - cyA) / (ry || 1), (endX - cxA) / (rx || 1));
31200
+ const clockwise = state.arcDirection === 2;
31201
+ const fullEllipse = recType !== EMR_PIE && rx > 0 && ry > 0 && sameEllipseRay(l, t, r, b, startX, startY, endX, endY);
31202
+ const endAngle = fullEllipse ? startAngle + (clockwise ? 2 : -2) * Math.PI : Math.atan2((endY - cyA) / (ry || 1), (endX - cxA) / (rx || 1));
31194
31203
  const rotated = hasWorldRotation(rCtx);
31195
31204
  const unframed = fixBox(rCtx, l, t, r, b);
31196
31205
  const inset = insideFrameInset(rCtx, unframed);
@@ -31204,13 +31213,18 @@ function handleArcFamily(rCtx, recType, dataOff, recSize) {
31204
31213
  };
31205
31214
  const isArcTo = recType === EMR_ARCTO;
31206
31215
  const needsFill = recType === EMR_PIE || recType === EMR_CHORD;
31207
- const clockwise = state.arcDirection === 2;
31208
31216
  const startPoint = rotated ? gmapPoint(rCtx, cxA + rx * Math.cos(startAngle), cyA + ry * Math.sin(startAngle)) : { x: params.cx + params.rx * Math.cos(startAngle), y: params.cy + params.ry * Math.sin(startAngle) };
31209
31217
  const build = (c) => {
31210
31218
  if (recType === EMR_PIE) {
31211
31219
  c.moveTo(params.cx, params.cy);
31220
+ } else if (!isArcTo && inPath) {
31221
+ c.moveTo(startPoint.x, startPoint.y);
31212
31222
  }
31213
31223
  if (isArcTo) {
31224
+ if (!inPath) {
31225
+ const from = currentFix(rCtx);
31226
+ c.moveTo(from[0] / 16, from[1] / 16);
31227
+ }
31214
31228
  c.lineTo(startPoint.x, startPoint.y);
31215
31229
  }
31216
31230
  c.ellipse(params.cx, params.cy, params.rx, params.ry, params.rotation, startAngle, endAngle, !clockwise);
@@ -31230,7 +31244,9 @@ function handleArcFamily(rCtx, recType, dataOff, recSize) {
31230
31244
  const rasterArgs = (immediate = false) => ({
31231
31245
  box: inset ? framed : immediate && needsFill ? curvedFixBox(rCtx, l, t, r, b) : unframed,
31232
31246
  s: radialOnBox(fixPoint(rCtx, startX, startY)),
31233
- e: radialOnBox(fixPoint(rCtx, endX, endY)),
31247
+ // Device FIX rounding can separate distinct radial points on the
31248
+ // same logical ray. Preserve their proven endpoint identity.
31249
+ e: fullEllipse ? radialOnBox(fixPoint(rCtx, startX, startY)) : radialOnBox(fixPoint(rCtx, endX, endY)),
31234
31250
  from: currentFix(rCtx)
31235
31251
  });
31236
31252
  if (inPath) {
@@ -35696,14 +35712,20 @@ function handleEmfObjectRecord(rCtx, recType, dataOff, recSize) {
35696
35712
  }
35697
35713
 
35698
35714
  // src/emf-gdi-transform-handlers.ts
35715
+ function extentsSettable(rCtx) {
35716
+ return rCtx.mapMode === 7 || rCtx.mapMode === 8;
35717
+ }
35699
35718
  function handleCoordinateRecord(rCtx, recType, dataOff, recSize) {
35700
35719
  const { view } = rCtx;
35701
35720
  switch (recType) {
35702
35721
  case EMR_SETWINDOWEXTEX: {
35703
- if (recSize >= 16) {
35704
- rCtx.windowExt.cx = view.getInt32(dataOff, true);
35705
- rCtx.windowExt.cy = view.getInt32(dataOff + 4, true);
35706
- activateGdiMappingMode(rCtx);
35722
+ if (recSize >= 16 && extentsSettable(rCtx)) {
35723
+ const cx = view.getInt32(dataOff, true);
35724
+ const cy = view.getInt32(dataOff + 4, true);
35725
+ if (cx !== 0 && cy !== 0) {
35726
+ rCtx.windowExt = { cx, cy };
35727
+ activateGdiMappingMode(rCtx);
35728
+ }
35707
35729
  }
35708
35730
  return true;
35709
35731
  }
@@ -35716,10 +35738,13 @@ function handleCoordinateRecord(rCtx, recType, dataOff, recSize) {
35716
35738
  return true;
35717
35739
  }
35718
35740
  case EMR_SETVIEWPORTEXTEX: {
35719
- if (recSize >= 16) {
35720
- rCtx.viewportExt.cx = view.getInt32(dataOff, true);
35721
- rCtx.viewportExt.cy = view.getInt32(dataOff + 4, true);
35722
- activateGdiMappingMode(rCtx);
35741
+ if (recSize >= 16 && extentsSettable(rCtx)) {
35742
+ const cx = view.getInt32(dataOff, true);
35743
+ const cy = view.getInt32(dataOff + 4, true);
35744
+ if (cx !== 0 && cy !== 0) {
35745
+ rCtx.viewportExt = { cx, cy };
35746
+ activateGdiMappingMode(rCtx);
35747
+ }
35723
35748
  }
35724
35749
  return true;
35725
35750
  }
@@ -35734,14 +35759,21 @@ function handleCoordinateRecord(rCtx, recType, dataOff, recSize) {
35734
35759
  case EMR_SETMAPMODE: {
35735
35760
  if (recSize >= 12) {
35736
35761
  const mode = view.getUint32(dataOff, true);
35737
- if (mode === 8 || mode === 7) {
35738
- activateGdiMappingMode(rCtx);
35762
+ if (mode >= 1 && mode <= 8) {
35763
+ rCtx.mapMode = mode;
35764
+ if (mode === 1) {
35765
+ rCtx.windowExt = { cx: 1, cy: 1 };
35766
+ rCtx.viewportExt = { cx: 1, cy: 1 };
35767
+ }
35768
+ if (mode === 1 || mode === 8 || mode === 7) {
35769
+ activateGdiMappingMode(rCtx);
35770
+ }
35739
35771
  }
35740
35772
  }
35741
35773
  return true;
35742
35774
  }
35743
35775
  case EMR_SCALEVIEWPORTEXTEX: {
35744
- if (recSize >= 24) {
35776
+ if (recSize >= 24 && extentsSettable(rCtx)) {
35745
35777
  const xNum = view.getInt32(dataOff, true);
35746
35778
  const xDenom = view.getInt32(dataOff + 4, true);
35747
35779
  const yNum = view.getInt32(dataOff + 8, true);
@@ -35757,7 +35789,7 @@ function handleCoordinateRecord(rCtx, recType, dataOff, recSize) {
35757
35789
  return true;
35758
35790
  }
35759
35791
  case EMR_SCALEWINDOWEXTEX: {
35760
- if (recSize >= 24) {
35792
+ if (recSize >= 24 && extentsSettable(rCtx)) {
35761
35793
  const xNum = view.getInt32(dataOff, true);
35762
35794
  const xDenom = view.getInt32(dataOff + 4, true);
35763
35795
  const yNum = view.getInt32(dataOff + 8, true);
@@ -35908,6 +35940,15 @@ function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
35908
35940
  }
35909
35941
  rCtx.clipStack ?? (rCtx.clipStack = []);
35910
35942
  rCtx.clipStack.push({ region: rCtx.clipRegion ?? null });
35943
+ rCtx.mappingStack ?? (rCtx.mappingStack = []);
35944
+ rCtx.mappingStack.push({
35945
+ mapMode: rCtx.mapMode,
35946
+ windowOrg: { ...rCtx.windowOrg },
35947
+ windowExt: { ...rCtx.windowExt },
35948
+ viewportOrg: { ...rCtx.viewportOrg },
35949
+ viewportExt: { ...rCtx.viewportExt },
35950
+ useMappingMode: rCtx.useMappingMode
35951
+ });
35911
35952
  rCtx.stateStack.push(cloneState(state));
35912
35953
  ctx.save();
35913
35954
  if (rCtx.clipRegion) {
@@ -35917,23 +35958,31 @@ function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
35917
35958
  }
35918
35959
  case EMR_RESTOREDC: {
35919
35960
  if (recSize >= 12) {
35920
- while (rCtx.clipSaveDepth > 0) {
35921
- ctx.restore();
35922
- rCtx.clipSaveDepth--;
35923
- }
35924
35961
  let rel = view.getInt32(dataOff, true);
35925
35962
  if (rel < 0) {
35926
35963
  rel = rCtx.stateStack.length + rel + 1;
35927
35964
  }
35928
- while (rCtx.stateStack.length > rel && rCtx.stateStack.length > 0) {
35965
+ if (rel < 1 || rel > rCtx.stateStack.length) {
35966
+ return true;
35967
+ }
35968
+ while (rCtx.clipSaveDepth > 0) {
35969
+ ctx.restore();
35970
+ rCtx.clipSaveDepth--;
35971
+ }
35972
+ while (rCtx.stateStack.length > rel) {
35929
35973
  rCtx.stateStack.pop();
35930
35974
  rCtx.clipStack?.pop();
35975
+ rCtx.mappingStack?.pop();
35931
35976
  ctx.restore();
35932
35977
  }
35933
35978
  const restored = rCtx.stateStack.pop();
35934
35979
  if (restored) {
35935
35980
  const clipSnapshot = rCtx.clipStack?.pop();
35981
+ const mappingSnapshot = rCtx.mappingStack?.pop();
35936
35982
  Object.assign(state, restored);
35983
+ if (mappingSnapshot) {
35984
+ Object.assign(rCtx, mappingSnapshot);
35985
+ }
35937
35986
  ctx.restore();
35938
35987
  rCtx.clipRegion = clipSnapshot?.region ?? null;
35939
35988
  if (rCtx.clipRegion) {
@@ -38032,9 +38081,11 @@ function replayEmfRecords(view, ctx, bounds, canvasW, canvasH, dpiScale = 1, rep
38032
38081
  // canvas like every other device coordinate (bounds' top-left to the
38033
38082
  // canvas origin, scaled by sx/sy); see `deviceToCanvas`.
38034
38083
  windowOrg: { x: 0, y: 0 },
38035
- windowExt: { cx: logicalW, cy: logicalH },
38084
+ windowExt: { cx: 1, cy: 1 },
38036
38085
  viewportOrg: { x: 0, y: 0 },
38037
- viewportExt: { cx: logicalW, cy: logicalH },
38086
+ viewportExt: { cx: 1, cy: 1 },
38087
+ mapMode: 1,
38088
+ // MM_TEXT: the default DC uses one-to-one page/device units.
38038
38089
  useMappingMode: false,
38039
38090
  deviceToCanvas: true,
38040
38091
  clipSaveDepth: 0,
@@ -38286,18 +38337,18 @@ function setMapMode(m, mode) {
38286
38337
  }
38287
38338
  m.mode = mode;
38288
38339
  }
38289
- function extentsSettable(m) {
38340
+ function extentsSettable2(m) {
38290
38341
  return m.mode === MM_ISOTROPIC || m.mode === MM_ANISOTROPIC;
38291
38342
  }
38292
38343
  function setWindowExt(m, cx, cy) {
38293
- if (!extentsSettable(m) || cx === 0 || cy === 0) {
38344
+ if (!extentsSettable2(m) || cx === 0 || cy === 0) {
38294
38345
  return;
38295
38346
  }
38296
38347
  m.winExt = { cx, cy };
38297
38348
  fixIsotropic(m);
38298
38349
  }
38299
38350
  function setViewportExt(m, cx, cy) {
38300
- if (!extentsSettable(m) || cx === 0 || cy === 0) {
38351
+ if (!extentsSettable2(m) || cx === 0 || cy === 0) {
38301
38352
  return;
38302
38353
  }
38303
38354
  m.vpExt = { cx, cy };
@@ -38307,7 +38358,7 @@ function scaled(v, num, den) {
38307
38358
  return Math.trunc(v * num / den);
38308
38359
  }
38309
38360
  function scaleWindowExt(m, xNum, xDen, yNum, yDen) {
38310
- if (!extentsSettable(m) || !xDen || !yDen) {
38361
+ if (!extentsSettable2(m) || !xDen || !yDen) {
38311
38362
  return;
38312
38363
  }
38313
38364
  const cx = scaled(m.winExt.cx, xNum, xDen);
@@ -38319,7 +38370,7 @@ function scaleWindowExt(m, xNum, xDen, yNum, yDen) {
38319
38370
  fixIsotropic(m);
38320
38371
  }
38321
38372
  function scaleViewportExt(m, xNum, xDen, yNum, yDen) {
38322
- if (!extentsSettable(m) || !xDen || !yDen) {
38373
+ if (!extentsSettable2(m) || !xDen || !yDen) {
38323
38374
  return;
38324
38375
  }
38325
38376
  const cx = scaled(m.vpExt.cx, xNum, xDen);
package/dist/browser.mjs CHANGED
@@ -27796,10 +27796,10 @@ function parseEmfHeader(view) {
27796
27796
  };
27797
27797
  }
27798
27798
  function getRenderableEmfBounds(header) {
27799
- const boundsW = header.bounds.right - header.bounds.left;
27800
- const boundsH = header.bounds.bottom - header.bounds.top;
27799
+ const boundsW = header.bounds.right - header.bounds.left + 1;
27800
+ const boundsH = header.bounds.bottom - header.bounds.top + 1;
27801
27801
  if (boundsW > 0 && boundsH > 0) {
27802
- return header.bounds;
27802
+ return { ...header.bounds, right: header.bounds.right + 1, bottom: header.bounds.bottom + 1 };
27803
27803
  }
27804
27804
  if (header.frameW > 0 && header.frameH > 0) {
27805
27805
  emfLog(
@@ -30831,6 +30831,13 @@ function insetFixBox(box, inset) {
30831
30831
  }
30832
30832
  return framed;
30833
30833
  }
30834
+ function sameEllipseRay(l, t, r, b, sx, sy, ex, ey) {
30835
+ const ax = BigInt(2 * sx - l - r);
30836
+ const ay = BigInt(2 * sy - t - b);
30837
+ const bx = BigInt(2 * ex - l - r);
30838
+ const by = BigInt(2 * ey - t - b);
30839
+ return ax * by === ay * bx && ax * bx + ay * by > 0n;
30840
+ }
30834
30841
  function handleSetPixelV(rCtx, dataOff, recSize) {
30835
30842
  const { ctx, view } = rCtx;
30836
30843
  if (recSize >= 20) {
@@ -31188,7 +31195,9 @@ function handleArcFamily(rCtx, recType, dataOff, recSize) {
31188
31195
  const rx = Math.abs(r - l) / 2;
31189
31196
  const ry = Math.abs(b - t) / 2;
31190
31197
  const startAngle = Math.atan2((startY - cyA) / (ry || 1), (startX - cxA) / (rx || 1));
31191
- const endAngle = Math.atan2((endY - cyA) / (ry || 1), (endX - cxA) / (rx || 1));
31198
+ const clockwise = state.arcDirection === 2;
31199
+ const fullEllipse = recType !== EMR_PIE && rx > 0 && ry > 0 && sameEllipseRay(l, t, r, b, startX, startY, endX, endY);
31200
+ const endAngle = fullEllipse ? startAngle + (clockwise ? 2 : -2) * Math.PI : Math.atan2((endY - cyA) / (ry || 1), (endX - cxA) / (rx || 1));
31192
31201
  const rotated = hasWorldRotation(rCtx);
31193
31202
  const unframed = fixBox(rCtx, l, t, r, b);
31194
31203
  const inset = insideFrameInset(rCtx, unframed);
@@ -31202,13 +31211,18 @@ function handleArcFamily(rCtx, recType, dataOff, recSize) {
31202
31211
  };
31203
31212
  const isArcTo = recType === EMR_ARCTO;
31204
31213
  const needsFill = recType === EMR_PIE || recType === EMR_CHORD;
31205
- const clockwise = state.arcDirection === 2;
31206
31214
  const startPoint = rotated ? gmapPoint(rCtx, cxA + rx * Math.cos(startAngle), cyA + ry * Math.sin(startAngle)) : { x: params.cx + params.rx * Math.cos(startAngle), y: params.cy + params.ry * Math.sin(startAngle) };
31207
31215
  const build = (c) => {
31208
31216
  if (recType === EMR_PIE) {
31209
31217
  c.moveTo(params.cx, params.cy);
31218
+ } else if (!isArcTo && inPath) {
31219
+ c.moveTo(startPoint.x, startPoint.y);
31210
31220
  }
31211
31221
  if (isArcTo) {
31222
+ if (!inPath) {
31223
+ const from = currentFix(rCtx);
31224
+ c.moveTo(from[0] / 16, from[1] / 16);
31225
+ }
31212
31226
  c.lineTo(startPoint.x, startPoint.y);
31213
31227
  }
31214
31228
  c.ellipse(params.cx, params.cy, params.rx, params.ry, params.rotation, startAngle, endAngle, !clockwise);
@@ -31228,7 +31242,9 @@ function handleArcFamily(rCtx, recType, dataOff, recSize) {
31228
31242
  const rasterArgs = (immediate = false) => ({
31229
31243
  box: inset ? framed : immediate && needsFill ? curvedFixBox(rCtx, l, t, r, b) : unframed,
31230
31244
  s: radialOnBox(fixPoint(rCtx, startX, startY)),
31231
- e: radialOnBox(fixPoint(rCtx, endX, endY)),
31245
+ // Device FIX rounding can separate distinct radial points on the
31246
+ // same logical ray. Preserve their proven endpoint identity.
31247
+ e: fullEllipse ? radialOnBox(fixPoint(rCtx, startX, startY)) : radialOnBox(fixPoint(rCtx, endX, endY)),
31232
31248
  from: currentFix(rCtx)
31233
31249
  });
31234
31250
  if (inPath) {
@@ -35694,14 +35710,20 @@ function handleEmfObjectRecord(rCtx, recType, dataOff, recSize) {
35694
35710
  }
35695
35711
 
35696
35712
  // src/emf-gdi-transform-handlers.ts
35713
+ function extentsSettable(rCtx) {
35714
+ return rCtx.mapMode === 7 || rCtx.mapMode === 8;
35715
+ }
35697
35716
  function handleCoordinateRecord(rCtx, recType, dataOff, recSize) {
35698
35717
  const { view } = rCtx;
35699
35718
  switch (recType) {
35700
35719
  case EMR_SETWINDOWEXTEX: {
35701
- if (recSize >= 16) {
35702
- rCtx.windowExt.cx = view.getInt32(dataOff, true);
35703
- rCtx.windowExt.cy = view.getInt32(dataOff + 4, true);
35704
- activateGdiMappingMode(rCtx);
35720
+ if (recSize >= 16 && extentsSettable(rCtx)) {
35721
+ const cx = view.getInt32(dataOff, true);
35722
+ const cy = view.getInt32(dataOff + 4, true);
35723
+ if (cx !== 0 && cy !== 0) {
35724
+ rCtx.windowExt = { cx, cy };
35725
+ activateGdiMappingMode(rCtx);
35726
+ }
35705
35727
  }
35706
35728
  return true;
35707
35729
  }
@@ -35714,10 +35736,13 @@ function handleCoordinateRecord(rCtx, recType, dataOff, recSize) {
35714
35736
  return true;
35715
35737
  }
35716
35738
  case EMR_SETVIEWPORTEXTEX: {
35717
- if (recSize >= 16) {
35718
- rCtx.viewportExt.cx = view.getInt32(dataOff, true);
35719
- rCtx.viewportExt.cy = view.getInt32(dataOff + 4, true);
35720
- activateGdiMappingMode(rCtx);
35739
+ if (recSize >= 16 && extentsSettable(rCtx)) {
35740
+ const cx = view.getInt32(dataOff, true);
35741
+ const cy = view.getInt32(dataOff + 4, true);
35742
+ if (cx !== 0 && cy !== 0) {
35743
+ rCtx.viewportExt = { cx, cy };
35744
+ activateGdiMappingMode(rCtx);
35745
+ }
35721
35746
  }
35722
35747
  return true;
35723
35748
  }
@@ -35732,14 +35757,21 @@ function handleCoordinateRecord(rCtx, recType, dataOff, recSize) {
35732
35757
  case EMR_SETMAPMODE: {
35733
35758
  if (recSize >= 12) {
35734
35759
  const mode = view.getUint32(dataOff, true);
35735
- if (mode === 8 || mode === 7) {
35736
- activateGdiMappingMode(rCtx);
35760
+ if (mode >= 1 && mode <= 8) {
35761
+ rCtx.mapMode = mode;
35762
+ if (mode === 1) {
35763
+ rCtx.windowExt = { cx: 1, cy: 1 };
35764
+ rCtx.viewportExt = { cx: 1, cy: 1 };
35765
+ }
35766
+ if (mode === 1 || mode === 8 || mode === 7) {
35767
+ activateGdiMappingMode(rCtx);
35768
+ }
35737
35769
  }
35738
35770
  }
35739
35771
  return true;
35740
35772
  }
35741
35773
  case EMR_SCALEVIEWPORTEXTEX: {
35742
- if (recSize >= 24) {
35774
+ if (recSize >= 24 && extentsSettable(rCtx)) {
35743
35775
  const xNum = view.getInt32(dataOff, true);
35744
35776
  const xDenom = view.getInt32(dataOff + 4, true);
35745
35777
  const yNum = view.getInt32(dataOff + 8, true);
@@ -35755,7 +35787,7 @@ function handleCoordinateRecord(rCtx, recType, dataOff, recSize) {
35755
35787
  return true;
35756
35788
  }
35757
35789
  case EMR_SCALEWINDOWEXTEX: {
35758
- if (recSize >= 24) {
35790
+ if (recSize >= 24 && extentsSettable(rCtx)) {
35759
35791
  const xNum = view.getInt32(dataOff, true);
35760
35792
  const xDenom = view.getInt32(dataOff + 4, true);
35761
35793
  const yNum = view.getInt32(dataOff + 8, true);
@@ -35906,6 +35938,15 @@ function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
35906
35938
  }
35907
35939
  rCtx.clipStack ?? (rCtx.clipStack = []);
35908
35940
  rCtx.clipStack.push({ region: rCtx.clipRegion ?? null });
35941
+ rCtx.mappingStack ?? (rCtx.mappingStack = []);
35942
+ rCtx.mappingStack.push({
35943
+ mapMode: rCtx.mapMode,
35944
+ windowOrg: { ...rCtx.windowOrg },
35945
+ windowExt: { ...rCtx.windowExt },
35946
+ viewportOrg: { ...rCtx.viewportOrg },
35947
+ viewportExt: { ...rCtx.viewportExt },
35948
+ useMappingMode: rCtx.useMappingMode
35949
+ });
35909
35950
  rCtx.stateStack.push(cloneState(state));
35910
35951
  ctx.save();
35911
35952
  if (rCtx.clipRegion) {
@@ -35915,23 +35956,31 @@ function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
35915
35956
  }
35916
35957
  case EMR_RESTOREDC: {
35917
35958
  if (recSize >= 12) {
35918
- while (rCtx.clipSaveDepth > 0) {
35919
- ctx.restore();
35920
- rCtx.clipSaveDepth--;
35921
- }
35922
35959
  let rel = view.getInt32(dataOff, true);
35923
35960
  if (rel < 0) {
35924
35961
  rel = rCtx.stateStack.length + rel + 1;
35925
35962
  }
35926
- while (rCtx.stateStack.length > rel && rCtx.stateStack.length > 0) {
35963
+ if (rel < 1 || rel > rCtx.stateStack.length) {
35964
+ return true;
35965
+ }
35966
+ while (rCtx.clipSaveDepth > 0) {
35967
+ ctx.restore();
35968
+ rCtx.clipSaveDepth--;
35969
+ }
35970
+ while (rCtx.stateStack.length > rel) {
35927
35971
  rCtx.stateStack.pop();
35928
35972
  rCtx.clipStack?.pop();
35973
+ rCtx.mappingStack?.pop();
35929
35974
  ctx.restore();
35930
35975
  }
35931
35976
  const restored = rCtx.stateStack.pop();
35932
35977
  if (restored) {
35933
35978
  const clipSnapshot = rCtx.clipStack?.pop();
35979
+ const mappingSnapshot = rCtx.mappingStack?.pop();
35934
35980
  Object.assign(state, restored);
35981
+ if (mappingSnapshot) {
35982
+ Object.assign(rCtx, mappingSnapshot);
35983
+ }
35935
35984
  ctx.restore();
35936
35985
  rCtx.clipRegion = clipSnapshot?.region ?? null;
35937
35986
  if (rCtx.clipRegion) {
@@ -38030,9 +38079,11 @@ function replayEmfRecords(view, ctx, bounds, canvasW, canvasH, dpiScale = 1, rep
38030
38079
  // canvas like every other device coordinate (bounds' top-left to the
38031
38080
  // canvas origin, scaled by sx/sy); see `deviceToCanvas`.
38032
38081
  windowOrg: { x: 0, y: 0 },
38033
- windowExt: { cx: logicalW, cy: logicalH },
38082
+ windowExt: { cx: 1, cy: 1 },
38034
38083
  viewportOrg: { x: 0, y: 0 },
38035
- viewportExt: { cx: logicalW, cy: logicalH },
38084
+ viewportExt: { cx: 1, cy: 1 },
38085
+ mapMode: 1,
38086
+ // MM_TEXT: the default DC uses one-to-one page/device units.
38036
38087
  useMappingMode: false,
38037
38088
  deviceToCanvas: true,
38038
38089
  clipSaveDepth: 0,
@@ -38284,18 +38335,18 @@ function setMapMode(m, mode) {
38284
38335
  }
38285
38336
  m.mode = mode;
38286
38337
  }
38287
- function extentsSettable(m) {
38338
+ function extentsSettable2(m) {
38288
38339
  return m.mode === MM_ISOTROPIC || m.mode === MM_ANISOTROPIC;
38289
38340
  }
38290
38341
  function setWindowExt(m, cx, cy) {
38291
- if (!extentsSettable(m) || cx === 0 || cy === 0) {
38342
+ if (!extentsSettable2(m) || cx === 0 || cy === 0) {
38292
38343
  return;
38293
38344
  }
38294
38345
  m.winExt = { cx, cy };
38295
38346
  fixIsotropic(m);
38296
38347
  }
38297
38348
  function setViewportExt(m, cx, cy) {
38298
- if (!extentsSettable(m) || cx === 0 || cy === 0) {
38349
+ if (!extentsSettable2(m) || cx === 0 || cy === 0) {
38299
38350
  return;
38300
38351
  }
38301
38352
  m.vpExt = { cx, cy };
@@ -38305,7 +38356,7 @@ function scaled(v, num, den) {
38305
38356
  return Math.trunc(v * num / den);
38306
38357
  }
38307
38358
  function scaleWindowExt(m, xNum, xDen, yNum, yDen) {
38308
- if (!extentsSettable(m) || !xDen || !yDen) {
38359
+ if (!extentsSettable2(m) || !xDen || !yDen) {
38309
38360
  return;
38310
38361
  }
38311
38362
  const cx = scaled(m.winExt.cx, xNum, xDen);
@@ -38317,7 +38368,7 @@ function scaleWindowExt(m, xNum, xDen, yNum, yDen) {
38317
38368
  fixIsotropic(m);
38318
38369
  }
38319
38370
  function scaleViewportExt(m, xNum, xDen, yNum, yDen) {
38320
- if (!extentsSettable(m) || !xDen || !yDen) {
38371
+ if (!extentsSettable2(m) || !xDen || !yDen) {
38321
38372
  return;
38322
38373
  }
38323
38374
  const cx = scaled(m.vpExt.cx, xNum, xDen);
package/dist/index.js CHANGED
@@ -27799,10 +27799,10 @@ function parseEmfHeader(view) {
27799
27799
  };
27800
27800
  }
27801
27801
  function getRenderableEmfBounds(header) {
27802
- const boundsW = header.bounds.right - header.bounds.left;
27803
- const boundsH = header.bounds.bottom - header.bounds.top;
27802
+ const boundsW = header.bounds.right - header.bounds.left + 1;
27803
+ const boundsH = header.bounds.bottom - header.bounds.top + 1;
27804
27804
  if (boundsW > 0 && boundsH > 0) {
27805
- return header.bounds;
27805
+ return { ...header.bounds, right: header.bounds.right + 1, bottom: header.bounds.bottom + 1 };
27806
27806
  }
27807
27807
  if (header.frameW > 0 && header.frameH > 0) {
27808
27808
  emfLog(
@@ -30834,6 +30834,13 @@ function insetFixBox(box, inset) {
30834
30834
  }
30835
30835
  return framed;
30836
30836
  }
30837
+ function sameEllipseRay(l, t, r, b, sx, sy, ex, ey) {
30838
+ const ax = BigInt(2 * sx - l - r);
30839
+ const ay = BigInt(2 * sy - t - b);
30840
+ const bx = BigInt(2 * ex - l - r);
30841
+ const by = BigInt(2 * ey - t - b);
30842
+ return ax * by === ay * bx && ax * bx + ay * by > 0n;
30843
+ }
30837
30844
  function handleSetPixelV(rCtx, dataOff, recSize) {
30838
30845
  const { ctx, view } = rCtx;
30839
30846
  if (recSize >= 20) {
@@ -31191,7 +31198,9 @@ function handleArcFamily(rCtx, recType, dataOff, recSize) {
31191
31198
  const rx = Math.abs(r - l) / 2;
31192
31199
  const ry = Math.abs(b - t) / 2;
31193
31200
  const startAngle = Math.atan2((startY - cyA) / (ry || 1), (startX - cxA) / (rx || 1));
31194
- const endAngle = Math.atan2((endY - cyA) / (ry || 1), (endX - cxA) / (rx || 1));
31201
+ const clockwise = state.arcDirection === 2;
31202
+ const fullEllipse = recType !== EMR_PIE && rx > 0 && ry > 0 && sameEllipseRay(l, t, r, b, startX, startY, endX, endY);
31203
+ const endAngle = fullEllipse ? startAngle + (clockwise ? 2 : -2) * Math.PI : Math.atan2((endY - cyA) / (ry || 1), (endX - cxA) / (rx || 1));
31195
31204
  const rotated = hasWorldRotation(rCtx);
31196
31205
  const unframed = fixBox(rCtx, l, t, r, b);
31197
31206
  const inset = insideFrameInset(rCtx, unframed);
@@ -31205,13 +31214,18 @@ function handleArcFamily(rCtx, recType, dataOff, recSize) {
31205
31214
  };
31206
31215
  const isArcTo = recType === EMR_ARCTO;
31207
31216
  const needsFill = recType === EMR_PIE || recType === EMR_CHORD;
31208
- const clockwise = state.arcDirection === 2;
31209
31217
  const startPoint = rotated ? gmapPoint(rCtx, cxA + rx * Math.cos(startAngle), cyA + ry * Math.sin(startAngle)) : { x: params.cx + params.rx * Math.cos(startAngle), y: params.cy + params.ry * Math.sin(startAngle) };
31210
31218
  const build = (c) => {
31211
31219
  if (recType === EMR_PIE) {
31212
31220
  c.moveTo(params.cx, params.cy);
31221
+ } else if (!isArcTo && inPath) {
31222
+ c.moveTo(startPoint.x, startPoint.y);
31213
31223
  }
31214
31224
  if (isArcTo) {
31225
+ if (!inPath) {
31226
+ const from = currentFix(rCtx);
31227
+ c.moveTo(from[0] / 16, from[1] / 16);
31228
+ }
31215
31229
  c.lineTo(startPoint.x, startPoint.y);
31216
31230
  }
31217
31231
  c.ellipse(params.cx, params.cy, params.rx, params.ry, params.rotation, startAngle, endAngle, !clockwise);
@@ -31231,7 +31245,9 @@ function handleArcFamily(rCtx, recType, dataOff, recSize) {
31231
31245
  const rasterArgs = (immediate = false) => ({
31232
31246
  box: inset ? framed : immediate && needsFill ? curvedFixBox(rCtx, l, t, r, b) : unframed,
31233
31247
  s: radialOnBox(fixPoint(rCtx, startX, startY)),
31234
- e: radialOnBox(fixPoint(rCtx, endX, endY)),
31248
+ // Device FIX rounding can separate distinct radial points on the
31249
+ // same logical ray. Preserve their proven endpoint identity.
31250
+ e: fullEllipse ? radialOnBox(fixPoint(rCtx, startX, startY)) : radialOnBox(fixPoint(rCtx, endX, endY)),
31235
31251
  from: currentFix(rCtx)
31236
31252
  });
31237
31253
  if (inPath) {
@@ -35697,14 +35713,20 @@ function handleEmfObjectRecord(rCtx, recType, dataOff, recSize) {
35697
35713
  }
35698
35714
 
35699
35715
  // src/emf-gdi-transform-handlers.ts
35716
+ function extentsSettable(rCtx) {
35717
+ return rCtx.mapMode === 7 || rCtx.mapMode === 8;
35718
+ }
35700
35719
  function handleCoordinateRecord(rCtx, recType, dataOff, recSize) {
35701
35720
  const { view } = rCtx;
35702
35721
  switch (recType) {
35703
35722
  case EMR_SETWINDOWEXTEX: {
35704
- if (recSize >= 16) {
35705
- rCtx.windowExt.cx = view.getInt32(dataOff, true);
35706
- rCtx.windowExt.cy = view.getInt32(dataOff + 4, true);
35707
- activateGdiMappingMode(rCtx);
35723
+ if (recSize >= 16 && extentsSettable(rCtx)) {
35724
+ const cx = view.getInt32(dataOff, true);
35725
+ const cy = view.getInt32(dataOff + 4, true);
35726
+ if (cx !== 0 && cy !== 0) {
35727
+ rCtx.windowExt = { cx, cy };
35728
+ activateGdiMappingMode(rCtx);
35729
+ }
35708
35730
  }
35709
35731
  return true;
35710
35732
  }
@@ -35717,10 +35739,13 @@ function handleCoordinateRecord(rCtx, recType, dataOff, recSize) {
35717
35739
  return true;
35718
35740
  }
35719
35741
  case EMR_SETVIEWPORTEXTEX: {
35720
- if (recSize >= 16) {
35721
- rCtx.viewportExt.cx = view.getInt32(dataOff, true);
35722
- rCtx.viewportExt.cy = view.getInt32(dataOff + 4, true);
35723
- activateGdiMappingMode(rCtx);
35742
+ if (recSize >= 16 && extentsSettable(rCtx)) {
35743
+ const cx = view.getInt32(dataOff, true);
35744
+ const cy = view.getInt32(dataOff + 4, true);
35745
+ if (cx !== 0 && cy !== 0) {
35746
+ rCtx.viewportExt = { cx, cy };
35747
+ activateGdiMappingMode(rCtx);
35748
+ }
35724
35749
  }
35725
35750
  return true;
35726
35751
  }
@@ -35735,14 +35760,21 @@ function handleCoordinateRecord(rCtx, recType, dataOff, recSize) {
35735
35760
  case EMR_SETMAPMODE: {
35736
35761
  if (recSize >= 12) {
35737
35762
  const mode = view.getUint32(dataOff, true);
35738
- if (mode === 8 || mode === 7) {
35739
- activateGdiMappingMode(rCtx);
35763
+ if (mode >= 1 && mode <= 8) {
35764
+ rCtx.mapMode = mode;
35765
+ if (mode === 1) {
35766
+ rCtx.windowExt = { cx: 1, cy: 1 };
35767
+ rCtx.viewportExt = { cx: 1, cy: 1 };
35768
+ }
35769
+ if (mode === 1 || mode === 8 || mode === 7) {
35770
+ activateGdiMappingMode(rCtx);
35771
+ }
35740
35772
  }
35741
35773
  }
35742
35774
  return true;
35743
35775
  }
35744
35776
  case EMR_SCALEVIEWPORTEXTEX: {
35745
- if (recSize >= 24) {
35777
+ if (recSize >= 24 && extentsSettable(rCtx)) {
35746
35778
  const xNum = view.getInt32(dataOff, true);
35747
35779
  const xDenom = view.getInt32(dataOff + 4, true);
35748
35780
  const yNum = view.getInt32(dataOff + 8, true);
@@ -35758,7 +35790,7 @@ function handleCoordinateRecord(rCtx, recType, dataOff, recSize) {
35758
35790
  return true;
35759
35791
  }
35760
35792
  case EMR_SCALEWINDOWEXTEX: {
35761
- if (recSize >= 24) {
35793
+ if (recSize >= 24 && extentsSettable(rCtx)) {
35762
35794
  const xNum = view.getInt32(dataOff, true);
35763
35795
  const xDenom = view.getInt32(dataOff + 4, true);
35764
35796
  const yNum = view.getInt32(dataOff + 8, true);
@@ -35909,6 +35941,15 @@ function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
35909
35941
  }
35910
35942
  rCtx.clipStack ?? (rCtx.clipStack = []);
35911
35943
  rCtx.clipStack.push({ region: rCtx.clipRegion ?? null });
35944
+ rCtx.mappingStack ?? (rCtx.mappingStack = []);
35945
+ rCtx.mappingStack.push({
35946
+ mapMode: rCtx.mapMode,
35947
+ windowOrg: { ...rCtx.windowOrg },
35948
+ windowExt: { ...rCtx.windowExt },
35949
+ viewportOrg: { ...rCtx.viewportOrg },
35950
+ viewportExt: { ...rCtx.viewportExt },
35951
+ useMappingMode: rCtx.useMappingMode
35952
+ });
35912
35953
  rCtx.stateStack.push(cloneState(state));
35913
35954
  ctx.save();
35914
35955
  if (rCtx.clipRegion) {
@@ -35918,23 +35959,31 @@ function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
35918
35959
  }
35919
35960
  case EMR_RESTOREDC: {
35920
35961
  if (recSize >= 12) {
35921
- while (rCtx.clipSaveDepth > 0) {
35922
- ctx.restore();
35923
- rCtx.clipSaveDepth--;
35924
- }
35925
35962
  let rel = view.getInt32(dataOff, true);
35926
35963
  if (rel < 0) {
35927
35964
  rel = rCtx.stateStack.length + rel + 1;
35928
35965
  }
35929
- while (rCtx.stateStack.length > rel && rCtx.stateStack.length > 0) {
35966
+ if (rel < 1 || rel > rCtx.stateStack.length) {
35967
+ return true;
35968
+ }
35969
+ while (rCtx.clipSaveDepth > 0) {
35970
+ ctx.restore();
35971
+ rCtx.clipSaveDepth--;
35972
+ }
35973
+ while (rCtx.stateStack.length > rel) {
35930
35974
  rCtx.stateStack.pop();
35931
35975
  rCtx.clipStack?.pop();
35976
+ rCtx.mappingStack?.pop();
35932
35977
  ctx.restore();
35933
35978
  }
35934
35979
  const restored = rCtx.stateStack.pop();
35935
35980
  if (restored) {
35936
35981
  const clipSnapshot = rCtx.clipStack?.pop();
35982
+ const mappingSnapshot = rCtx.mappingStack?.pop();
35937
35983
  Object.assign(state, restored);
35984
+ if (mappingSnapshot) {
35985
+ Object.assign(rCtx, mappingSnapshot);
35986
+ }
35938
35987
  ctx.restore();
35939
35988
  rCtx.clipRegion = clipSnapshot?.region ?? null;
35940
35989
  if (rCtx.clipRegion) {
@@ -38033,9 +38082,11 @@ function replayEmfRecords(view, ctx, bounds, canvasW, canvasH, dpiScale = 1, rep
38033
38082
  // canvas like every other device coordinate (bounds' top-left to the
38034
38083
  // canvas origin, scaled by sx/sy); see `deviceToCanvas`.
38035
38084
  windowOrg: { x: 0, y: 0 },
38036
- windowExt: { cx: logicalW, cy: logicalH },
38085
+ windowExt: { cx: 1, cy: 1 },
38037
38086
  viewportOrg: { x: 0, y: 0 },
38038
- viewportExt: { cx: logicalW, cy: logicalH },
38087
+ viewportExt: { cx: 1, cy: 1 },
38088
+ mapMode: 1,
38089
+ // MM_TEXT: the default DC uses one-to-one page/device units.
38039
38090
  useMappingMode: false,
38040
38091
  deviceToCanvas: true,
38041
38092
  clipSaveDepth: 0,
@@ -38287,18 +38338,18 @@ function setMapMode(m, mode) {
38287
38338
  }
38288
38339
  m.mode = mode;
38289
38340
  }
38290
- function extentsSettable(m) {
38341
+ function extentsSettable2(m) {
38291
38342
  return m.mode === MM_ISOTROPIC || m.mode === MM_ANISOTROPIC;
38292
38343
  }
38293
38344
  function setWindowExt(m, cx, cy) {
38294
- if (!extentsSettable(m) || cx === 0 || cy === 0) {
38345
+ if (!extentsSettable2(m) || cx === 0 || cy === 0) {
38295
38346
  return;
38296
38347
  }
38297
38348
  m.winExt = { cx, cy };
38298
38349
  fixIsotropic(m);
38299
38350
  }
38300
38351
  function setViewportExt(m, cx, cy) {
38301
- if (!extentsSettable(m) || cx === 0 || cy === 0) {
38352
+ if (!extentsSettable2(m) || cx === 0 || cy === 0) {
38302
38353
  return;
38303
38354
  }
38304
38355
  m.vpExt = { cx, cy };
@@ -38308,7 +38359,7 @@ function scaled(v, num, den) {
38308
38359
  return Math.trunc(v * num / den);
38309
38360
  }
38310
38361
  function scaleWindowExt(m, xNum, xDen, yNum, yDen) {
38311
- if (!extentsSettable(m) || !xDen || !yDen) {
38362
+ if (!extentsSettable2(m) || !xDen || !yDen) {
38312
38363
  return;
38313
38364
  }
38314
38365
  const cx = scaled(m.winExt.cx, xNum, xDen);
@@ -38320,7 +38371,7 @@ function scaleWindowExt(m, xNum, xDen, yNum, yDen) {
38320
38371
  fixIsotropic(m);
38321
38372
  }
38322
38373
  function scaleViewportExt(m, xNum, xDen, yNum, yDen) {
38323
- if (!extentsSettable(m) || !xDen || !yDen) {
38374
+ if (!extentsSettable2(m) || !xDen || !yDen) {
38324
38375
  return;
38325
38376
  }
38326
38377
  const cx = scaled(m.vpExt.cx, xNum, xDen);
package/dist/index.mjs CHANGED
@@ -27797,10 +27797,10 @@ function parseEmfHeader(view) {
27797
27797
  };
27798
27798
  }
27799
27799
  function getRenderableEmfBounds(header) {
27800
- const boundsW = header.bounds.right - header.bounds.left;
27801
- const boundsH = header.bounds.bottom - header.bounds.top;
27800
+ const boundsW = header.bounds.right - header.bounds.left + 1;
27801
+ const boundsH = header.bounds.bottom - header.bounds.top + 1;
27802
27802
  if (boundsW > 0 && boundsH > 0) {
27803
- return header.bounds;
27803
+ return { ...header.bounds, right: header.bounds.right + 1, bottom: header.bounds.bottom + 1 };
27804
27804
  }
27805
27805
  if (header.frameW > 0 && header.frameH > 0) {
27806
27806
  emfLog(
@@ -30832,6 +30832,13 @@ function insetFixBox(box, inset) {
30832
30832
  }
30833
30833
  return framed;
30834
30834
  }
30835
+ function sameEllipseRay(l, t, r, b, sx, sy, ex, ey) {
30836
+ const ax = BigInt(2 * sx - l - r);
30837
+ const ay = BigInt(2 * sy - t - b);
30838
+ const bx = BigInt(2 * ex - l - r);
30839
+ const by = BigInt(2 * ey - t - b);
30840
+ return ax * by === ay * bx && ax * bx + ay * by > 0n;
30841
+ }
30835
30842
  function handleSetPixelV(rCtx, dataOff, recSize) {
30836
30843
  const { ctx, view } = rCtx;
30837
30844
  if (recSize >= 20) {
@@ -31189,7 +31196,9 @@ function handleArcFamily(rCtx, recType, dataOff, recSize) {
31189
31196
  const rx = Math.abs(r - l) / 2;
31190
31197
  const ry = Math.abs(b - t) / 2;
31191
31198
  const startAngle = Math.atan2((startY - cyA) / (ry || 1), (startX - cxA) / (rx || 1));
31192
- const endAngle = Math.atan2((endY - cyA) / (ry || 1), (endX - cxA) / (rx || 1));
31199
+ const clockwise = state.arcDirection === 2;
31200
+ const fullEllipse = recType !== EMR_PIE && rx > 0 && ry > 0 && sameEllipseRay(l, t, r, b, startX, startY, endX, endY);
31201
+ const endAngle = fullEllipse ? startAngle + (clockwise ? 2 : -2) * Math.PI : Math.atan2((endY - cyA) / (ry || 1), (endX - cxA) / (rx || 1));
31193
31202
  const rotated = hasWorldRotation(rCtx);
31194
31203
  const unframed = fixBox(rCtx, l, t, r, b);
31195
31204
  const inset = insideFrameInset(rCtx, unframed);
@@ -31203,13 +31212,18 @@ function handleArcFamily(rCtx, recType, dataOff, recSize) {
31203
31212
  };
31204
31213
  const isArcTo = recType === EMR_ARCTO;
31205
31214
  const needsFill = recType === EMR_PIE || recType === EMR_CHORD;
31206
- const clockwise = state.arcDirection === 2;
31207
31215
  const startPoint = rotated ? gmapPoint(rCtx, cxA + rx * Math.cos(startAngle), cyA + ry * Math.sin(startAngle)) : { x: params.cx + params.rx * Math.cos(startAngle), y: params.cy + params.ry * Math.sin(startAngle) };
31208
31216
  const build = (c) => {
31209
31217
  if (recType === EMR_PIE) {
31210
31218
  c.moveTo(params.cx, params.cy);
31219
+ } else if (!isArcTo && inPath) {
31220
+ c.moveTo(startPoint.x, startPoint.y);
31211
31221
  }
31212
31222
  if (isArcTo) {
31223
+ if (!inPath) {
31224
+ const from = currentFix(rCtx);
31225
+ c.moveTo(from[0] / 16, from[1] / 16);
31226
+ }
31213
31227
  c.lineTo(startPoint.x, startPoint.y);
31214
31228
  }
31215
31229
  c.ellipse(params.cx, params.cy, params.rx, params.ry, params.rotation, startAngle, endAngle, !clockwise);
@@ -31229,7 +31243,9 @@ function handleArcFamily(rCtx, recType, dataOff, recSize) {
31229
31243
  const rasterArgs = (immediate = false) => ({
31230
31244
  box: inset ? framed : immediate && needsFill ? curvedFixBox(rCtx, l, t, r, b) : unframed,
31231
31245
  s: radialOnBox(fixPoint(rCtx, startX, startY)),
31232
- e: radialOnBox(fixPoint(rCtx, endX, endY)),
31246
+ // Device FIX rounding can separate distinct radial points on the
31247
+ // same logical ray. Preserve their proven endpoint identity.
31248
+ e: fullEllipse ? radialOnBox(fixPoint(rCtx, startX, startY)) : radialOnBox(fixPoint(rCtx, endX, endY)),
31233
31249
  from: currentFix(rCtx)
31234
31250
  });
31235
31251
  if (inPath) {
@@ -35695,14 +35711,20 @@ function handleEmfObjectRecord(rCtx, recType, dataOff, recSize) {
35695
35711
  }
35696
35712
 
35697
35713
  // src/emf-gdi-transform-handlers.ts
35714
+ function extentsSettable(rCtx) {
35715
+ return rCtx.mapMode === 7 || rCtx.mapMode === 8;
35716
+ }
35698
35717
  function handleCoordinateRecord(rCtx, recType, dataOff, recSize) {
35699
35718
  const { view } = rCtx;
35700
35719
  switch (recType) {
35701
35720
  case EMR_SETWINDOWEXTEX: {
35702
- if (recSize >= 16) {
35703
- rCtx.windowExt.cx = view.getInt32(dataOff, true);
35704
- rCtx.windowExt.cy = view.getInt32(dataOff + 4, true);
35705
- activateGdiMappingMode(rCtx);
35721
+ if (recSize >= 16 && extentsSettable(rCtx)) {
35722
+ const cx = view.getInt32(dataOff, true);
35723
+ const cy = view.getInt32(dataOff + 4, true);
35724
+ if (cx !== 0 && cy !== 0) {
35725
+ rCtx.windowExt = { cx, cy };
35726
+ activateGdiMappingMode(rCtx);
35727
+ }
35706
35728
  }
35707
35729
  return true;
35708
35730
  }
@@ -35715,10 +35737,13 @@ function handleCoordinateRecord(rCtx, recType, dataOff, recSize) {
35715
35737
  return true;
35716
35738
  }
35717
35739
  case EMR_SETVIEWPORTEXTEX: {
35718
- if (recSize >= 16) {
35719
- rCtx.viewportExt.cx = view.getInt32(dataOff, true);
35720
- rCtx.viewportExt.cy = view.getInt32(dataOff + 4, true);
35721
- activateGdiMappingMode(rCtx);
35740
+ if (recSize >= 16 && extentsSettable(rCtx)) {
35741
+ const cx = view.getInt32(dataOff, true);
35742
+ const cy = view.getInt32(dataOff + 4, true);
35743
+ if (cx !== 0 && cy !== 0) {
35744
+ rCtx.viewportExt = { cx, cy };
35745
+ activateGdiMappingMode(rCtx);
35746
+ }
35722
35747
  }
35723
35748
  return true;
35724
35749
  }
@@ -35733,14 +35758,21 @@ function handleCoordinateRecord(rCtx, recType, dataOff, recSize) {
35733
35758
  case EMR_SETMAPMODE: {
35734
35759
  if (recSize >= 12) {
35735
35760
  const mode = view.getUint32(dataOff, true);
35736
- if (mode === 8 || mode === 7) {
35737
- activateGdiMappingMode(rCtx);
35761
+ if (mode >= 1 && mode <= 8) {
35762
+ rCtx.mapMode = mode;
35763
+ if (mode === 1) {
35764
+ rCtx.windowExt = { cx: 1, cy: 1 };
35765
+ rCtx.viewportExt = { cx: 1, cy: 1 };
35766
+ }
35767
+ if (mode === 1 || mode === 8 || mode === 7) {
35768
+ activateGdiMappingMode(rCtx);
35769
+ }
35738
35770
  }
35739
35771
  }
35740
35772
  return true;
35741
35773
  }
35742
35774
  case EMR_SCALEVIEWPORTEXTEX: {
35743
- if (recSize >= 24) {
35775
+ if (recSize >= 24 && extentsSettable(rCtx)) {
35744
35776
  const xNum = view.getInt32(dataOff, true);
35745
35777
  const xDenom = view.getInt32(dataOff + 4, true);
35746
35778
  const yNum = view.getInt32(dataOff + 8, true);
@@ -35756,7 +35788,7 @@ function handleCoordinateRecord(rCtx, recType, dataOff, recSize) {
35756
35788
  return true;
35757
35789
  }
35758
35790
  case EMR_SCALEWINDOWEXTEX: {
35759
- if (recSize >= 24) {
35791
+ if (recSize >= 24 && extentsSettable(rCtx)) {
35760
35792
  const xNum = view.getInt32(dataOff, true);
35761
35793
  const xDenom = view.getInt32(dataOff + 4, true);
35762
35794
  const yNum = view.getInt32(dataOff + 8, true);
@@ -35907,6 +35939,15 @@ function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
35907
35939
  }
35908
35940
  rCtx.clipStack ?? (rCtx.clipStack = []);
35909
35941
  rCtx.clipStack.push({ region: rCtx.clipRegion ?? null });
35942
+ rCtx.mappingStack ?? (rCtx.mappingStack = []);
35943
+ rCtx.mappingStack.push({
35944
+ mapMode: rCtx.mapMode,
35945
+ windowOrg: { ...rCtx.windowOrg },
35946
+ windowExt: { ...rCtx.windowExt },
35947
+ viewportOrg: { ...rCtx.viewportOrg },
35948
+ viewportExt: { ...rCtx.viewportExt },
35949
+ useMappingMode: rCtx.useMappingMode
35950
+ });
35910
35951
  rCtx.stateStack.push(cloneState(state));
35911
35952
  ctx.save();
35912
35953
  if (rCtx.clipRegion) {
@@ -35916,23 +35957,31 @@ function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
35916
35957
  }
35917
35958
  case EMR_RESTOREDC: {
35918
35959
  if (recSize >= 12) {
35919
- while (rCtx.clipSaveDepth > 0) {
35920
- ctx.restore();
35921
- rCtx.clipSaveDepth--;
35922
- }
35923
35960
  let rel = view.getInt32(dataOff, true);
35924
35961
  if (rel < 0) {
35925
35962
  rel = rCtx.stateStack.length + rel + 1;
35926
35963
  }
35927
- while (rCtx.stateStack.length > rel && rCtx.stateStack.length > 0) {
35964
+ if (rel < 1 || rel > rCtx.stateStack.length) {
35965
+ return true;
35966
+ }
35967
+ while (rCtx.clipSaveDepth > 0) {
35968
+ ctx.restore();
35969
+ rCtx.clipSaveDepth--;
35970
+ }
35971
+ while (rCtx.stateStack.length > rel) {
35928
35972
  rCtx.stateStack.pop();
35929
35973
  rCtx.clipStack?.pop();
35974
+ rCtx.mappingStack?.pop();
35930
35975
  ctx.restore();
35931
35976
  }
35932
35977
  const restored = rCtx.stateStack.pop();
35933
35978
  if (restored) {
35934
35979
  const clipSnapshot = rCtx.clipStack?.pop();
35980
+ const mappingSnapshot = rCtx.mappingStack?.pop();
35935
35981
  Object.assign(state, restored);
35982
+ if (mappingSnapshot) {
35983
+ Object.assign(rCtx, mappingSnapshot);
35984
+ }
35936
35985
  ctx.restore();
35937
35986
  rCtx.clipRegion = clipSnapshot?.region ?? null;
35938
35987
  if (rCtx.clipRegion) {
@@ -38031,9 +38080,11 @@ function replayEmfRecords(view, ctx, bounds, canvasW, canvasH, dpiScale = 1, rep
38031
38080
  // canvas like every other device coordinate (bounds' top-left to the
38032
38081
  // canvas origin, scaled by sx/sy); see `deviceToCanvas`.
38033
38082
  windowOrg: { x: 0, y: 0 },
38034
- windowExt: { cx: logicalW, cy: logicalH },
38083
+ windowExt: { cx: 1, cy: 1 },
38035
38084
  viewportOrg: { x: 0, y: 0 },
38036
- viewportExt: { cx: logicalW, cy: logicalH },
38085
+ viewportExt: { cx: 1, cy: 1 },
38086
+ mapMode: 1,
38087
+ // MM_TEXT: the default DC uses one-to-one page/device units.
38037
38088
  useMappingMode: false,
38038
38089
  deviceToCanvas: true,
38039
38090
  clipSaveDepth: 0,
@@ -38285,18 +38336,18 @@ function setMapMode(m, mode) {
38285
38336
  }
38286
38337
  m.mode = mode;
38287
38338
  }
38288
- function extentsSettable(m) {
38339
+ function extentsSettable2(m) {
38289
38340
  return m.mode === MM_ISOTROPIC || m.mode === MM_ANISOTROPIC;
38290
38341
  }
38291
38342
  function setWindowExt(m, cx, cy) {
38292
- if (!extentsSettable(m) || cx === 0 || cy === 0) {
38343
+ if (!extentsSettable2(m) || cx === 0 || cy === 0) {
38293
38344
  return;
38294
38345
  }
38295
38346
  m.winExt = { cx, cy };
38296
38347
  fixIsotropic(m);
38297
38348
  }
38298
38349
  function setViewportExt(m, cx, cy) {
38299
- if (!extentsSettable(m) || cx === 0 || cy === 0) {
38350
+ if (!extentsSettable2(m) || cx === 0 || cy === 0) {
38300
38351
  return;
38301
38352
  }
38302
38353
  m.vpExt = { cx, cy };
@@ -38306,7 +38357,7 @@ function scaled(v, num, den) {
38306
38357
  return Math.trunc(v * num / den);
38307
38358
  }
38308
38359
  function scaleWindowExt(m, xNum, xDen, yNum, yDen) {
38309
- if (!extentsSettable(m) || !xDen || !yDen) {
38360
+ if (!extentsSettable2(m) || !xDen || !yDen) {
38310
38361
  return;
38311
38362
  }
38312
38363
  const cx = scaled(m.winExt.cx, xNum, xDen);
@@ -38318,7 +38369,7 @@ function scaleWindowExt(m, xNum, xDen, yNum, yDen) {
38318
38369
  fixIsotropic(m);
38319
38370
  }
38320
38371
  function scaleViewportExt(m, xNum, xDen, yNum, yDen) {
38321
- if (!extentsSettable(m) || !xDen || !yDen) {
38372
+ if (!extentsSettable2(m) || !xDen || !yDen) {
38322
38373
  return;
38323
38374
  }
38324
38375
  const cx = scaled(m.vpExt.cx, xNum, xDen);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emf-converter",
3
- "version": "4.8.7",
3
+ "version": "4.8.8",
4
4
  "description": "Convert EMF/WMF metafile binaries to PNG data URLs or SVG (markup, data URL, React/JSX)",
5
5
  "keywords": [
6
6
  "canvas",