emf-converter 4.3.1 → 4.3.2

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/README.md CHANGED
@@ -196,7 +196,7 @@ A three-phase pipeline: **parse → replay → export**. The header parser reads
196
196
 
197
197
  Everything below is verified against output painted by Windows itself; `src/gdi-parity.fixture.test.ts` holds the per-fixture bounds.
198
198
 
199
- - **GDI shapes** (`gdi-raster.ts`, `gdi-raster-widen.ts`): 28.4 fixed-point geometry; GDI's fill rule (ALTERNATE/WINDING); one-pixel lines by GDI's diamond rule with its tie-breaks; GDI's own Bezier flattener, ellipse, rounded-rectangle, arc (GDI's trigonometry table and `SetArcDirection`), chord and pie construction; cosmetic dash styles (dash 18/6, dot 3/3, ...) and geometric dashes; wide pens widened from GDI's own pen polygons with every cap and join and the miter limit; rotated and skewed world transforms. Pixel-exact on the shape fixtures.
199
+ - **GDI shapes** (`gdi-raster.ts`, `gdi-raster-widen.ts`): 28.4 fixed-point geometry; GDI's fill rule (ALTERNATE/WINDING); one-pixel lines by GDI's diamond rule with its tie-breaks; GDI's own Bezier flattener, ellipse, rounded-rectangle, arc (GDI's trigonometry table and `SetArcDirection`), chord and pie construction; cosmetic dash styles (dash 18/6, dot 3/3, ...) and geometric dashes; wide pens widened from GDI's own pen polygons with every cap and join and the miter limit; rotated and skewed world transforms. Pixel-exact on the shape fixtures. A path bracket's GDI geometry has the points, point types, figure starts and direction that Windows' `GetPath` reports (checked against the Windows data in Wine's `gdi32` path tests).
200
200
  - **Raster operations**: all 256 ROP3 codes for `BitBlt`/`StretchBlt`/`StretchDIBits`/`PatBlt`, exact per bit against the destination, brush and source, with GDI's stretch modes, mirrored rectangles and rotated destinations (each device pixel mapped back to one source texel). Every `SetROP2` mode, including the bitwise AND/OR/XOR family, for shapes, paths and pattern-brush fills.
201
201
  - **Brushes**: hatch, monochrome and DIB pattern brushes anchored to the brush origin, with the background mode; GDI+ solid, hatch, texture (bilinear, WrapMode-aware, as GDI+ samples them), linear gradients (GDI+'s own interpolation table: preset colours, blend shapes, gamma correction, every WrapMode) and path gradients (true boundary-shaped falloff, every WrapMode).
202
202
  - **Clipping**: every GDI and GDI+ region combine mode exact for every clip (vector where possible, otherwise scan-converted to pixel regions, which is how GDI stores them), path clips with their fill mode, and region offsets.
package/dist/index.js CHANGED
@@ -1737,7 +1737,21 @@ var KAPPA = 0.5522847498307936;
1737
1737
  function axisBox(l, t, r, b) {
1738
1738
  return { ax: l, ay: t, exx: r - l, exy: 0, eyx: 0, eyy: b - t };
1739
1739
  }
1740
- function ellipseBeziersBox(box) {
1740
+ function ellipseBeziersBox(box, clockwise = false) {
1741
+ if (!clockwise) {
1742
+ return ellipsePoints(box, false);
1743
+ }
1744
+ const e = clockwiseEllipseBeziersBox(box);
1745
+ const out = [];
1746
+ for (let i = e.length - 2; i >= 0; i -= 2) {
1747
+ out.push(e[i], e[i + 1]);
1748
+ }
1749
+ return out;
1750
+ }
1751
+ function clockwiseEllipseBeziersBox(box) {
1752
+ return ellipsePoints(box, true);
1753
+ }
1754
+ function ellipsePoints(box, cwControls) {
1741
1755
  const half = (v, up) => up ? Math.ceil(v / 2) : Math.floor(v / 2);
1742
1756
  const hMidLo = (v) => half(v, false);
1743
1757
  const hMidHi = (v) => half(v, true);
@@ -1747,6 +1761,11 @@ function ellipseBeziersBox(box) {
1747
1761
  const vMidHi = (v) => half(v, true);
1748
1762
  const vCtl = (v) => Math.floor(v / 2) - Math.floor(KAPPA * Math.floor(v / 2));
1749
1763
  const vCtlR = (v) => v - vCtl(v);
1764
+ const vDist = (v) => Math.ceil(KAPPA * Math.floor(v / 2));
1765
+ const rightUpper = cwControls ? (v) => vMidHi(v) - vDist(v) : vCtl;
1766
+ const leftUpper = cwControls ? (v) => vMidLo(v) - vDist(v) : vCtl;
1767
+ const leftLower = cwControls ? (v) => vMidLo(v) + vDist(v) : vCtlR;
1768
+ const rightLower = cwControls ? (v) => vMidHi(v) + vDist(v) : vCtlR;
1750
1769
  const zero = () => 0;
1751
1770
  const full = (v) => v;
1752
1771
  const { ax, ay, exx, exy, eyx, eyy } = box;
@@ -1755,17 +1774,17 @@ function ellipseBeziersBox(box) {
1755
1774
  out.push(ax + gx(exx) + gy(eyx), ay + gx(exy) + gy(eyy));
1756
1775
  };
1757
1776
  P(full, vMidHi);
1758
- P(full, vCtl);
1777
+ P(full, rightUpper);
1759
1778
  P(hCtl, zero);
1760
1779
  P(hMidLo, zero);
1761
1780
  P(hCtl2, zero);
1762
- P(zero, vCtl);
1781
+ P(zero, leftUpper);
1763
1782
  P(zero, vMidLo);
1764
- P(zero, vCtlR);
1783
+ P(zero, leftLower);
1765
1784
  P(hCtl2, full);
1766
1785
  P(hMidHi, full);
1767
1786
  P(hCtl, full);
1768
- P(full, vCtlR);
1787
+ P(full, rightLower);
1769
1788
  P(full, vMidHi);
1770
1789
  return out;
1771
1790
  }
@@ -1782,8 +1801,12 @@ function flattenBezierPath(pts) {
1782
1801
  function roundRectCorners(l, t, r, b, cw, ch) {
1783
1802
  const w = r - l;
1784
1803
  const h = b - t;
1785
- const ew = Math.min(Math.abs(cw), w);
1786
- const eh = Math.min(Math.abs(ch), h);
1804
+ let ew = Math.min(Math.abs(cw), w);
1805
+ let eh = Math.min(Math.abs(ch), h);
1806
+ if (ew === 0 || eh === 0) {
1807
+ ew = 0;
1808
+ eh = 0;
1809
+ }
1787
1810
  const hy = Math.floor(h / 2) - Math.floor((h - eh) / 2);
1788
1811
  const topX = Math.floor(r - ew / 2);
1789
1812
  const hx = r - topX;
@@ -1892,12 +1915,7 @@ function angleArcFix(box, startDeg, sweepDeg) {
1892
1915
  const cx = l + w / 2;
1893
1916
  const cy = t + h / 2;
1894
1917
  const E = ellipseBeziers(l, t, l + w, t + h);
1895
- const Ecw = E.slice();
1896
- const d = Math.ceil(KAPPA * Math.floor(h / 2));
1897
- Ecw[3] = Ecw[1] - d;
1898
- Ecw[11] = Ecw[13] - d;
1899
- Ecw[15] = Ecw[13] + d;
1900
- Ecw[23] = Ecw[1] + d;
1918
+ const Ecw = clockwiseEllipseBeziersBox(axisBox(l, t, l + w, t + h));
1901
1919
  const start = startDeg * Math.PI / 180;
1902
1920
  const out = [Math.round(cx + w / 2 * tableCos(start)), Math.round(cy - h / 2 * tableSin(start))];
1903
1921
  for (const p of angleArcPieces(startDeg, sweepDeg)) {
@@ -1939,15 +1957,7 @@ function arcBeziers(l, t, r, b, xs, ys, xe, ye, clockwise) {
1939
1957
  }
1940
1958
  const px = (a2) => cx + rx * tableCos(a2);
1941
1959
  const py = (a2) => cy - ry * tableSin(a2);
1942
- const E = ellipseBeziers(l, t, r, b);
1943
- if (clockwise) {
1944
- const fh = Math.floor((b - t) / 2);
1945
- const d = Math.ceil(KAPPA * fh);
1946
- E[3] = E[1] - d;
1947
- E[11] = E[13] - d;
1948
- E[15] = E[13] + d;
1949
- E[23] = E[1] + d;
1950
- }
1960
+ const E = clockwise ? clockwiseEllipseBeziersBox(axisBox(l, t, r, b)) : ellipseBeziers(l, t, r, b);
1951
1961
  const out = [Math.round(px(a0)), Math.round(py(a0))];
1952
1962
  let a = a0;
1953
1963
  for (let guard = 0; guard < 8 && (s > 0 ? a < a1 - 1e-12 : a > a1 + 1e-12); guard++) {
@@ -1980,12 +1990,45 @@ function arcBeziers(l, t, r, b, xs, ys, xe, ye, clockwise) {
1980
1990
  var GdiRasterPath = class {
1981
1991
  constructor() {
1982
1992
  this.figures = [];
1993
+ /**
1994
+ * The path as `GetPath` reports it, before flattening: FIX points
1995
+ * (`[x0, y0, ...]`) and their `PT_*` types (`PT_MOVETO` 6, `PT_LINETO`
1996
+ * 2, `PT_BEZIERTO` 4, `PT_CLOSEFIGURE` 1 or-ed onto a figure's last
1997
+ * point). Used to check the geometry against Windows' own paths.
1998
+ */
1999
+ this.getPath = { pts: [], types: [] };
1983
2000
  this.current = null;
1984
2001
  }
1985
2002
  /** Starts a new figure at (`x`, `y`). */
1986
2003
  moveTo(x, y) {
1987
2004
  this.current = { pts: [x, y], closed: false };
1988
2005
  this.figures.push(this.current);
2006
+ this.log(x, y, 6);
2007
+ }
2008
+ /**
2009
+ * Starts a new figure at the current position (`x`, `y`) unless the open
2010
+ * figure already ends there. GDI opens a new figure for a drawing call
2011
+ * that continues from the current position (`LineTo`, `PolylineTo`,
2012
+ * `ArcTo`, ...) when the path is empty, its last figure is closed, or
2013
+ * the current position has moved away from the path's last point (a
2014
+ * `MoveTo`, or a call such as `Polyline`, `Arc` or `PolyPolygon` that
2015
+ * adds to the path without moving the current position; Windows'
2016
+ * `GetPath`, Wine `gdi32/tests/path.c`, `test_all_functions`).
2017
+ */
2018
+ continueAt(x, y) {
2019
+ const f = this.current;
2020
+ if (f && f.pts[f.pts.length - 2] === x && f.pts[f.pts.length - 1] === y) {
2021
+ return;
2022
+ }
2023
+ this.moveTo(x, y);
2024
+ }
2025
+ /** True when a figure is open (not closed and not ended by an appended path). */
2026
+ hasOpenFigure() {
2027
+ return this.current !== null;
2028
+ }
2029
+ log(x, y, type) {
2030
+ this.getPath.pts.push(x, y);
2031
+ this.getPath.types.push(type);
1989
2032
  }
1990
2033
  /** Adds a line to (`x`, `y`), starting a figure there when none is open. */
1991
2034
  lineTo(x, y) {
@@ -1994,6 +2037,7 @@ var GdiRasterPath = class {
1994
2037
  this.moveTo(last ? last[0] : x, last ? last[1] : y);
1995
2038
  }
1996
2039
  this.current.pts.push(x, y);
2040
+ this.log(x, y, 2);
1997
2041
  }
1998
2042
  /** Adds a cubic Bezier from the current point. */
1999
2043
  bezierTo(c1x, c1y, c2x, c2y, x, y) {
@@ -2003,6 +2047,9 @@ var GdiRasterPath = class {
2003
2047
  }
2004
2048
  const p = this.current.pts;
2005
2049
  this.flattenInto(p[p.length - 2], p[p.length - 1], c1x, c1y, c2x, c2y, x, y);
2050
+ this.log(c1x, c1y, 4);
2051
+ this.log(c2x, c2y, 4);
2052
+ this.log(x, y, 4);
2006
2053
  }
2007
2054
  /** Flattens one Bezier onto the open figure, recording its end tangents. */
2008
2055
  flattenInto(x0, y0, c1x, c1y, c2x, c2y, x3, y3) {
@@ -2037,6 +2084,9 @@ var GdiRasterPath = class {
2037
2084
  }
2038
2085
  for (let i = 2; i + 5 < pts.length; i += 6) {
2039
2086
  this.flattenInto(pts[i - 2], pts[i - 1], pts[i], pts[i + 1], pts[i + 2], pts[i + 3], pts[i + 4], pts[i + 5]);
2087
+ for (let k = i; k < i + 6; k += 2) {
2088
+ this.log(pts[k], pts[k + 1], 4);
2089
+ }
2040
2090
  }
2041
2091
  }
2042
2092
  /** Closes the open figure (the next drawing call starts a new one). */
@@ -2044,6 +2094,8 @@ var GdiRasterPath = class {
2044
2094
  if (this.current) {
2045
2095
  this.current.closed = true;
2046
2096
  this.current = null;
2097
+ const t = this.getPath.types;
2098
+ t[t.length - 1] |= 1;
2047
2099
  }
2048
2100
  }
2049
2101
  /** Appends every figure of `other`. */
@@ -2051,6 +2103,9 @@ var GdiRasterPath = class {
2051
2103
  for (const f of other.figures) {
2052
2104
  this.figures.push({ pts: f.pts.slice(), closed: f.closed, tangents: f.tangents && new Map(f.tangents) });
2053
2105
  }
2106
+ for (let i = 0; i < other.getPath.types.length; i++) {
2107
+ this.log(other.getPath.pts[2 * i], other.getPath.pts[2 * i + 1], other.getPath.types[i]);
2108
+ }
2054
2109
  this.current = null;
2055
2110
  }
2056
2111
  lastPoint() {
@@ -16653,8 +16708,11 @@ function boxCorners(box) {
16653
16708
  const { ax, ay, exx, exy, eyx, eyy } = box;
16654
16709
  return [ax + exx, ay + exy, ax, ay, ax + eyx, ay + eyy, ax + exx + eyx, ay + exy + eyy];
16655
16710
  }
16656
- function rectRasterPath(box) {
16657
- const c = boxCorners(box);
16711
+ function flipBox(box) {
16712
+ return { ax: box.ax + box.eyx, ay: box.ay + box.eyy, exx: box.exx, exy: box.exy, eyx: -box.eyx, eyy: -box.eyy };
16713
+ }
16714
+ function rectRasterPath(box, clockwise = false) {
16715
+ const c = boxCorners(clockwise ? flipBox(box) : box);
16658
16716
  const path = new GdiRasterPath();
16659
16717
  path.moveTo(c[0], c[1]);
16660
16718
  path.lineTo(c[2], c[3]);
@@ -16663,9 +16721,9 @@ function rectRasterPath(box) {
16663
16721
  path.closeFigure();
16664
16722
  return path;
16665
16723
  }
16666
- function ellipseRasterPath(box) {
16724
+ function ellipseRasterPath(box, clockwise = false) {
16667
16725
  const path = new GdiRasterPath();
16668
- path.addBeziers(ellipseBeziersBox(box), true);
16726
+ path.addBeziers(ellipseBeziersBox(box, clockwise), true);
16669
16727
  path.closeFigure();
16670
16728
  return path;
16671
16729
  }
@@ -16691,13 +16749,16 @@ function boxFrame(box) {
16691
16749
  const h = Math.round(Math.hypot(box.eyx, box.eyy));
16692
16750
  return { l: 0, t: 0, r: w, b: h };
16693
16751
  }
16694
- function roundRectRasterPath(box, cw, ch) {
16752
+ function roundRectRasterPath(box, cw, ch, clockwise = false, rectangle = false) {
16753
+ if (rectangle) {
16754
+ return rectRasterPath(box, clockwise);
16755
+ }
16695
16756
  const f = boxFrame(box);
16696
16757
  const q = roundRectCorners(f.l, f.t, f.r, f.b, cw, ch);
16697
16758
  const map = frameMapper(box, f.l, f.t, f.r - f.l, f.b - f.t);
16698
16759
  const pts = [];
16699
16760
  for (let i = 0; i < q.length; i += 2) {
16700
- pts.push(...map(q[i], q[i + 1]));
16761
+ pts.push(...map(q[i], clockwise ? f.t + f.b - q[i + 1] : q[i + 1]));
16701
16762
  }
16702
16763
  const path = new GdiRasterPath();
16703
16764
  for (let c = 0; c < 4; c++) {
@@ -16740,7 +16801,7 @@ function arcRasterPath(box, s, e, clockwise, kind, from, path = new GdiRasterPat
16740
16801
  pts.push(...map(bz[i], bz[i + 1]));
16741
16802
  }
16742
16803
  if (kind === "arcto" && from) {
16743
- path.lineTo(from[0], from[1]);
16804
+ path.continueAt(from[0], from[1]);
16744
16805
  path.addBeziers(pts, false);
16745
16806
  } else {
16746
16807
  path.addBeziers(pts, true);
@@ -19460,6 +19521,15 @@ function rasterPathOf(rCtx) {
19460
19521
  rCtx.rasterPath ?? (rCtx.rasterPath = new GdiRasterPath());
19461
19522
  return rCtx.rasterPath;
19462
19523
  }
19524
+ function continueFigure(rCtx) {
19525
+ const path = rasterPathOf(rCtx);
19526
+ const [x, y] = currentFix(rCtx);
19527
+ const before = path.figures.length;
19528
+ path.continueAt(x, y);
19529
+ if (path.figures.length !== before) {
19530
+ gdiPathRecorder(rCtx).moveTo(x / 16, y / 16);
19531
+ }
19532
+ }
19463
19533
  function lineStyleOf(rCtx) {
19464
19534
  rCtx.lineStyle ?? (rCtx.lineStyle = { pos: 0 });
19465
19535
  return rCtx.lineStyle;
@@ -19475,6 +19545,9 @@ function currentFix(rCtx) {
19475
19545
  }
19476
19546
  return fixPoint(rCtx, state.curX, state.curY);
19477
19547
  }
19548
+ function uprightFixBox(rCtx, l, t, r, b) {
19549
+ return fixBox(rCtx, Math.min(l, r), Math.min(t, b), Math.max(l, r), Math.max(t, b));
19550
+ }
19478
19551
  function fixExtent(rCtx, v, axis) {
19479
19552
  const m = gdiDeviceMatrix(rCtx);
19480
19553
  const k = axis === 0 ? Math.hypot(m[0], m[1]) : Math.hypot(m[2], m[3]);
@@ -19517,6 +19590,7 @@ function handleLineTo(rCtx, dataOff, recSize) {
19517
19590
  const to = rotated ? gmapPoint(rCtx, lx, ly) : { x: gmx(rCtx, lx), y: gmy(rCtx, ly) };
19518
19591
  const fixTo = fixPoint(rCtx, lx, ly);
19519
19592
  if (inPath) {
19593
+ continueFigure(rCtx);
19520
19594
  gdiPathRecorder(rCtx).lineTo(to.x, to.y);
19521
19595
  rasterPathOf(rCtx).lineTo(fixTo[0], fixTo[1]);
19522
19596
  } else {
@@ -19551,7 +19625,8 @@ function handleRectangle(rCtx, dataOff, recSize) {
19551
19625
  const t = view.getInt32(dataOff + 4, true);
19552
19626
  const r = view.getInt32(dataOff + 8, true);
19553
19627
  const b = view.getInt32(dataOff + 12, true);
19554
- const box = fixBox(rCtx, l, t, r, b);
19628
+ const box = uprightFixBox(rCtx, l, t, r, b);
19629
+ const clockwise = rCtx.state.arcDirection === 2;
19555
19630
  if (!inPath) {
19556
19631
  resetLineStyle(rCtx);
19557
19632
  }
@@ -19570,14 +19645,14 @@ function handleRectangle(rCtx, dataOff, recSize) {
19570
19645
  };
19571
19646
  if (inPath) {
19572
19647
  appendRect(gdiPathRecorder(rCtx));
19573
- rasterPathOf(rCtx).append(rectRasterPath(box));
19648
+ rasterPathOf(rCtx).append(rectRasterPath(box, clockwise));
19574
19649
  } else {
19575
19650
  paintGdiShape(rCtx, {
19576
19651
  build: (c) => {
19577
19652
  c.beginPath();
19578
19653
  appendRect(c);
19579
19654
  },
19580
- raster: () => rectRasterPath(box),
19655
+ raster: () => rectRasterPath(box, clockwise),
19581
19656
  rectangle: true,
19582
19657
  fill: true,
19583
19658
  stroke: true,
@@ -19592,14 +19667,14 @@ function handleRectangle(rCtx, dataOff, recSize) {
19592
19667
  const h = gmh(rCtx, b - t);
19593
19668
  if (inPath) {
19594
19669
  gdiPathRecorder(rCtx).rect(x, y, w, h);
19595
- rasterPathOf(rCtx).append(rectRasterPath(box));
19670
+ rasterPathOf(rCtx).append(rectRasterPath(box, clockwise));
19596
19671
  } else if (needsPathBasedRectangle(rCtx)) {
19597
19672
  paintGdiShape(rCtx, {
19598
19673
  build: (c) => {
19599
19674
  c.beginPath();
19600
19675
  c.rect(x, y, w, h);
19601
19676
  },
19602
- raster: () => rectRasterPath(box),
19677
+ raster: () => rectRasterPath(box, clockwise),
19603
19678
  rectangle: true,
19604
19679
  fill: true,
19605
19680
  stroke: true,
@@ -19712,7 +19787,8 @@ function handleRoundRect(rCtx, dataOff, recSize) {
19712
19787
  gmh(rCtx, cornerH) / 2,
19713
19788
  null
19714
19789
  );
19715
- const raster = (box = fixBox(rCtx, l, t, r, b)) => roundRectRasterPath(box, fixExtent(rCtx, cornerW, 0), fixExtent(rCtx, cornerH, 1));
19790
+ const clockwise = rCtx.state.arcDirection === 2;
19791
+ const raster = (box = uprightFixBox(rCtx, l, t, r, b)) => roundRectRasterPath(box, fixExtent(rCtx, cornerW, 0), fixExtent(rCtx, cornerH, 1), clockwise, cornerW === 0 || cornerH === 0);
19716
19792
  if (inPath) {
19717
19793
  drawRoundRect(gdiPathRecorder(rCtx));
19718
19794
  rasterPathOf(rCtx).append(raster());
@@ -19723,7 +19799,7 @@ function handleRoundRect(rCtx, dataOff, recSize) {
19723
19799
  c.beginPath();
19724
19800
  drawRoundRect(c);
19725
19801
  },
19726
- raster: () => raster(curvedFixBox(rCtx, l, t, r, b)),
19802
+ raster: () => raster(curvedFixBox(rCtx, l, t, r, b, true)),
19727
19803
  roundPen: true,
19728
19804
  fill: true,
19729
19805
  stroke: true
@@ -19732,8 +19808,8 @@ function handleRoundRect(rCtx, dataOff, recSize) {
19732
19808
  }
19733
19809
  return true;
19734
19810
  }
19735
- function curvedFixBox(rCtx, l, t, r, b) {
19736
- const box = fixBox(rCtx, l, t, r, b);
19811
+ function curvedFixBox(rCtx, l, t, r, b, upright = false) {
19812
+ const box = upright ? uprightFixBox(rCtx, l, t, r, b) : fixBox(rCtx, l, t, r, b);
19737
19813
  if (rCtx.state.penStyle !== 5) {
19738
19814
  return box;
19739
19815
  }
@@ -19759,6 +19835,7 @@ function handleEllipse(rCtx, dataOff, recSize) {
19759
19835
  const t = view.getInt32(dataOff + 4, true);
19760
19836
  const r = view.getInt32(dataOff + 8, true);
19761
19837
  const b = view.getInt32(dataOff + 12, true);
19838
+ const clockwise = rCtx.state.arcDirection === 2;
19762
19839
  const params = hasWorldRotation(rCtx) ? gdiEllipseParams(rCtx, (l + r) / 2, (t + b) / 2, Math.abs(r - l) / 2, Math.abs(b - t) / 2) : {
19763
19840
  cx: gmx(rCtx, (l + r) / 2),
19764
19841
  cy: gmy(rCtx, (t + b) / 2),
@@ -19768,7 +19845,7 @@ function handleEllipse(rCtx, dataOff, recSize) {
19768
19845
  };
19769
19846
  if (inPath) {
19770
19847
  gdiPathRecorder(rCtx).ellipse(params.cx, params.cy, params.rx, params.ry, params.rotation, 0, Math.PI * 2);
19771
- rasterPathOf(rCtx).append(ellipseRasterPath(fixBox(rCtx, l, t, r, b)));
19848
+ rasterPathOf(rCtx).append(ellipseRasterPath(uprightFixBox(rCtx, l, t, r, b), clockwise));
19772
19849
  } else {
19773
19850
  resetLineStyle(rCtx);
19774
19851
  paintGdiShape(rCtx, {
@@ -19776,7 +19853,7 @@ function handleEllipse(rCtx, dataOff, recSize) {
19776
19853
  c.beginPath();
19777
19854
  c.ellipse(params.cx, params.cy, params.rx, params.ry, params.rotation, 0, Math.PI * 2);
19778
19855
  },
19779
- raster: () => ellipseRasterPath(curvedFixBox(rCtx, l, t, r, b)),
19856
+ raster: () => ellipseRasterPath(curvedFixBox(rCtx, l, t, r, b, true), clockwise),
19780
19857
  roundPen: true,
19781
19858
  fill: true,
19782
19859
  stroke: true
@@ -19834,6 +19911,9 @@ function handleArcFamily(rCtx, recType, dataOff, recSize) {
19834
19911
  from: currentFix(rCtx)
19835
19912
  });
19836
19913
  if (inPath) {
19914
+ if (isArcTo) {
19915
+ continueFigure(rCtx);
19916
+ }
19837
19917
  build(gdiPathRecorder(rCtx));
19838
19918
  const a = rasterArgs();
19839
19919
  arcRasterPath(a.box, a.s, a.e, clockwise, kind, a.from, rasterPathOf(rCtx));
@@ -19910,9 +19990,7 @@ function handleAngleArc(rCtx, dataOff, recSize) {
19910
19990
  }
19911
19991
  const e = [fixPts[fixPts.length - 2], fixPts[fixPts.length - 1]];
19912
19992
  const buildRaster = (path, from) => {
19913
- if (from) {
19914
- path.moveTo(from[0], from[1]);
19915
- }
19993
+ path.continueAt(from[0], from[1]);
19916
19994
  path.addBeziers(fixPts, false);
19917
19995
  };
19918
19996
  const params = gdiEllipseParams(rCtx, cx, cy, radius, radius);
@@ -19926,9 +20004,9 @@ function handleAngleArc(rCtx, dataOff, recSize) {
19926
20004
  c.ellipse(params.cx, params.cy, params.rx, params.ry, params.rotation, -a0, clockwise ? -a0 + span : -a0 - span, !clockwise);
19927
20005
  };
19928
20006
  if (inPath) {
19929
- const rec = gdiPathRecorder(rCtx);
19930
- buildCanvas(rec);
19931
- buildRaster(rasterPathOf(rCtx), rasterPathOf(rCtx).figures.length === 0 ? currentFix(rCtx) : null);
20007
+ continueFigure(rCtx);
20008
+ buildCanvas(gdiPathRecorder(rCtx));
20009
+ buildRaster(rasterPathOf(rCtx), currentFix(rCtx));
19932
20010
  } else {
19933
20011
  resetLineStyle(rCtx);
19934
20012
  const from = currentFix(rCtx);
@@ -22775,9 +22853,12 @@ function handlePoly(rCtx, recType, count, readPt, kinds) {
22775
22853
  }
22776
22854
  };
22777
22855
  if (inPath) {
22856
+ if (isTo) {
22857
+ continueFigure(rCtx);
22858
+ }
22778
22859
  build(gdiPathRecorder(rCtx));
22779
22860
  rCtx.rasterPath ?? (rCtx.rasterPath = new GdiRasterPath());
22780
- buildRaster(rCtx.rasterPath, isTo && rCtx.rasterPath.figures.length === 0 ? currentFix(rCtx) : null);
22861
+ buildRaster(rCtx.rasterPath, null);
22781
22862
  } else {
22782
22863
  const from = isTo ? currentFix(rCtx) : null;
22783
22864
  const start = isTo ? gmapPoint(rCtx, state.curX, state.curY) : null;
@@ -22800,10 +22881,11 @@ function handlePoly(rCtx, recType, count, readPt, kinds) {
22800
22881
  fillRule: state.polyFillMode === 2 ? "nonzero" : "evenodd"
22801
22882
  });
22802
22883
  }
22803
- if (count > 0) {
22884
+ if (isTo && count > 0) {
22804
22885
  const [x, y] = readPt(count - 1);
22805
22886
  state.curX = x;
22806
22887
  state.curY = y;
22888
+ rCtx.curFix = void 0;
22807
22889
  }
22808
22890
  }
22809
22891
  function handlePoly32(rCtx, recType, offset, dataOff, recSize) {
@@ -22908,9 +22990,11 @@ function handlePolyDraw(rCtx, offset, dataOff, recSize, pointSize) {
22908
22990
  };
22909
22991
  const startsWithMove = (typeAt(0) & ~PT_CLOSEFIGURE) === PT_MOVETO;
22910
22992
  if (inPath) {
22993
+ if (!startsWithMove) {
22994
+ continueFigure(rCtx);
22995
+ }
22911
22996
  rCtx.rasterPath ?? (rCtx.rasterPath = new GdiRasterPath());
22912
- const fresh = rCtx.rasterPath.figures.length === 0;
22913
- walk2(gdiPathRecorder(rCtx), rCtx.rasterPath, !startsWithMove && fresh ? currentFix(rCtx) : null, null);
22997
+ walk2(gdiPathRecorder(rCtx), rCtx.rasterPath, null, null);
22914
22998
  } else {
22915
22999
  const from = startsWithMove ? null : currentFix(rCtx);
22916
23000
  const fromPx = startsWithMove ? null : gmapPoint(rCtx, state.curX, state.curY);
@@ -27366,7 +27450,8 @@ function wmfSetDibToDev(p, d, end) {
27366
27450
  const hv = new DataView(bmi.buffer);
27367
27451
  const fullH = hv.getInt32(8, true);
27368
27452
  hv.setInt32(8, fullH < 0 ? -scanCount : scanCount, true);
27369
- const [ox, oy] = deviceDest(p, dx, dy, 0, 0);
27453
+ const [mx, oy] = deviceDest(p, dx, dy, 0, 0);
27454
+ const ox = p.layout & 1 ? mx - w : mx;
27370
27455
  playStretchDibits(p, 13369376, ox, oy + (sy + h - hi), w, hi - lo, sx, lo - startScan, w, hi - lo, { bmi, bits: dib.bits }, true);
27371
27456
  }
27372
27457
 
@@ -27713,25 +27798,30 @@ function wmfRectangle(p, l, t, r, b) {
27713
27798
  c.beginPath();
27714
27799
  c.rect(cr.x, cr.y, cr.w, cr.h);
27715
27800
  },
27716
- raster: () => rectRasterPath(fixBoxOf(box)),
27801
+ raster: () => rectRasterPath(fixBoxOf(box), clockwiseOf(p)),
27717
27802
  rectangle: true,
27718
27803
  fill: true,
27719
27804
  stroke: true,
27720
27805
  axisRect: { interior: rectInterior(p, box) }
27721
27806
  });
27722
27807
  }
27808
+ function compatCorner(p, w, h) {
27809
+ const m = gdiDeviceMatrix(p.rCtx);
27810
+ const cw = Math.round(Math.abs(w * m[0]) * 16);
27811
+ const ch = Math.round(Math.abs(h * m[3]) * 16);
27812
+ const ux = 16 * p.kx;
27813
+ const uy = 16 * p.ky;
27814
+ if (!penIsNull(p) && !penIsCosmetic(p.rCtx)) {
27815
+ return [Math.round(Math.floor(cw / (2 * ux)) * 2 * ux), Math.round(Math.floor(ch / (2 * uy)) * 2 * uy)];
27816
+ }
27817
+ return [Math.round(Math.floor(Math.round(cw / ux) / 2) * 2 * ux), Math.round(Math.floor(Math.round(ch / uy) / 2) * 2 * uy)];
27818
+ }
27723
27819
  function wmfRoundRect(p, l, t, r, b, w, h) {
27724
27820
  const box = compatBox(p, l, t, r, b, { curved: true });
27725
27821
  if (box.x1 < box.x0 || box.y1 < box.y0) {
27726
27822
  return;
27727
27823
  }
27728
- const m = gdiDeviceMatrix(p.rCtx);
27729
- let cw = Math.round(Math.abs(w * m[0]) * 16);
27730
- let ch = Math.round(Math.abs(h * m[3]) * 16);
27731
- if (!penIsNull(p) && !penIsCosmetic(p.rCtx)) {
27732
- cw = Math.floor(cw / 32) * 32;
27733
- ch = Math.floor(ch / 32) * 32;
27734
- }
27824
+ const [cw, ch] = compatCorner(p, w, h);
27735
27825
  const cr = canvasRect(box);
27736
27826
  paintGdiShape(p.rCtx, {
27737
27827
  build: (c) => {
@@ -27756,7 +27846,7 @@ function wmfRoundRect(p, l, t, r, b, w, h) {
27756
27846
  c.ellipse(cr.x + ex, cr.y + ey, ex, ey, 0, 2 * q, 3 * q);
27757
27847
  c.closePath();
27758
27848
  },
27759
- raster: () => roundRectRasterPath(fixBoxOf(box), cw, ch),
27849
+ raster: () => roundRectRasterPath(fixBoxOf(box), cw, ch, clockwiseOf(p), w === 0 || h === 0),
27760
27850
  roundPen: true,
27761
27851
  fill: true,
27762
27852
  stroke: true
@@ -27773,24 +27863,31 @@ function wmfEllipse(p, l, t, r, b) {
27773
27863
  c.beginPath();
27774
27864
  c.ellipse(cr.x + cr.w / 2, cr.y + cr.h / 2, cr.w / 2, cr.h / 2, 0, 0, Math.PI * 2);
27775
27865
  },
27776
- raster: () => ellipseRasterPath(fixBoxOf(box)),
27866
+ raster: () => ellipseRasterPath(fixBoxOf(box), clockwiseOf(p)),
27777
27867
  roundPen: true,
27778
27868
  fill: true,
27779
27869
  stroke: true
27780
27870
  });
27781
27871
  }
27782
- function wmfArcFamily(p, kind, l, t, r, b, xs, ys, xe, ye) {
27783
- const box = compatBox(p, l, t, r, b, { curved: kind !== "arc" });
27872
+ function compatArc(p, kind, l, t, r, b, xs, ys, xe, ye) {
27873
+ const box = compatBox(p, l, t, r, b, { curved: kind === "chord" || kind === "pie" });
27784
27874
  if (box.x1 < box.x0 || box.y1 < box.y0) {
27785
- return;
27875
+ return null;
27786
27876
  }
27787
27877
  const raw = compatBox(p, l, t, r, b, { exclusive: false });
27788
- const rdx = (box.x0 + box.x1 - raw.x0 - raw.x1) / 2;
27789
- const rdy = (box.y0 + box.y1 - raw.y0 - raw.y1) / 2;
27790
- const s0 = fixPoint(p.rCtx, xs, ys);
27791
- const e0 = fixPoint(p.rCtx, xe, ye);
27792
- const s = [s0[0] + rdx, s0[1] + rdy];
27793
- const e = [e0[0] + rdx, e0[1] + rdy];
27878
+ const onBox = (v, r0, r1, b0, b1) => (b0 + b1) / 2 + (r1 !== r0 ? (v - (r0 + r1) / 2) * (b1 - b0) / (r1 - r0) : v - (r0 + r1) / 2);
27879
+ const radial = (x, y) => {
27880
+ const [fx, fy] = fixPoint(p.rCtx, x, y);
27881
+ return [onBox(fx, raw.x0, raw.x1, box.x0, box.x1), onBox(fy, raw.y0, raw.y1, box.y0, box.y1)];
27882
+ };
27883
+ return { box, s: radial(xs, ys), e: radial(xe, ye) };
27884
+ }
27885
+ function wmfArcFamily(p, kind, l, t, r, b, xs, ys, xe, ye) {
27886
+ const geom = compatArc(p, kind, l, t, r, b, xs, ys, xe, ye);
27887
+ if (!geom) {
27888
+ return;
27889
+ }
27890
+ const { box, s, e } = geom;
27794
27891
  const clockwise = p.rCtx.state.arcDirection === 2;
27795
27892
  const cr = canvasRect(box);
27796
27893
  const cx = cr.x + cr.w / 2;
@@ -27816,6 +27913,9 @@ function wmfArcFamily(p, kind, l, t, r, b, xs, ys, xe, ye) {
27816
27913
  stroke: true
27817
27914
  });
27818
27915
  }
27916
+ function clockwiseOf(p) {
27917
+ return p.rCtx.state.arcDirection === 2;
27918
+ }
27819
27919
  function penIsNull(p) {
27820
27920
  return (p.rCtx.state.penStyle & 15) === PS_NULL3;
27821
27921
  }
package/dist/index.mjs CHANGED
@@ -1735,7 +1735,21 @@ var KAPPA = 0.5522847498307936;
1735
1735
  function axisBox(l, t, r, b) {
1736
1736
  return { ax: l, ay: t, exx: r - l, exy: 0, eyx: 0, eyy: b - t };
1737
1737
  }
1738
- function ellipseBeziersBox(box) {
1738
+ function ellipseBeziersBox(box, clockwise = false) {
1739
+ if (!clockwise) {
1740
+ return ellipsePoints(box, false);
1741
+ }
1742
+ const e = clockwiseEllipseBeziersBox(box);
1743
+ const out = [];
1744
+ for (let i = e.length - 2; i >= 0; i -= 2) {
1745
+ out.push(e[i], e[i + 1]);
1746
+ }
1747
+ return out;
1748
+ }
1749
+ function clockwiseEllipseBeziersBox(box) {
1750
+ return ellipsePoints(box, true);
1751
+ }
1752
+ function ellipsePoints(box, cwControls) {
1739
1753
  const half = (v, up) => up ? Math.ceil(v / 2) : Math.floor(v / 2);
1740
1754
  const hMidLo = (v) => half(v, false);
1741
1755
  const hMidHi = (v) => half(v, true);
@@ -1745,6 +1759,11 @@ function ellipseBeziersBox(box) {
1745
1759
  const vMidHi = (v) => half(v, true);
1746
1760
  const vCtl = (v) => Math.floor(v / 2) - Math.floor(KAPPA * Math.floor(v / 2));
1747
1761
  const vCtlR = (v) => v - vCtl(v);
1762
+ const vDist = (v) => Math.ceil(KAPPA * Math.floor(v / 2));
1763
+ const rightUpper = cwControls ? (v) => vMidHi(v) - vDist(v) : vCtl;
1764
+ const leftUpper = cwControls ? (v) => vMidLo(v) - vDist(v) : vCtl;
1765
+ const leftLower = cwControls ? (v) => vMidLo(v) + vDist(v) : vCtlR;
1766
+ const rightLower = cwControls ? (v) => vMidHi(v) + vDist(v) : vCtlR;
1748
1767
  const zero = () => 0;
1749
1768
  const full = (v) => v;
1750
1769
  const { ax, ay, exx, exy, eyx, eyy } = box;
@@ -1753,17 +1772,17 @@ function ellipseBeziersBox(box) {
1753
1772
  out.push(ax + gx(exx) + gy(eyx), ay + gx(exy) + gy(eyy));
1754
1773
  };
1755
1774
  P(full, vMidHi);
1756
- P(full, vCtl);
1775
+ P(full, rightUpper);
1757
1776
  P(hCtl, zero);
1758
1777
  P(hMidLo, zero);
1759
1778
  P(hCtl2, zero);
1760
- P(zero, vCtl);
1779
+ P(zero, leftUpper);
1761
1780
  P(zero, vMidLo);
1762
- P(zero, vCtlR);
1781
+ P(zero, leftLower);
1763
1782
  P(hCtl2, full);
1764
1783
  P(hMidHi, full);
1765
1784
  P(hCtl, full);
1766
- P(full, vCtlR);
1785
+ P(full, rightLower);
1767
1786
  P(full, vMidHi);
1768
1787
  return out;
1769
1788
  }
@@ -1780,8 +1799,12 @@ function flattenBezierPath(pts) {
1780
1799
  function roundRectCorners(l, t, r, b, cw, ch) {
1781
1800
  const w = r - l;
1782
1801
  const h = b - t;
1783
- const ew = Math.min(Math.abs(cw), w);
1784
- const eh = Math.min(Math.abs(ch), h);
1802
+ let ew = Math.min(Math.abs(cw), w);
1803
+ let eh = Math.min(Math.abs(ch), h);
1804
+ if (ew === 0 || eh === 0) {
1805
+ ew = 0;
1806
+ eh = 0;
1807
+ }
1785
1808
  const hy = Math.floor(h / 2) - Math.floor((h - eh) / 2);
1786
1809
  const topX = Math.floor(r - ew / 2);
1787
1810
  const hx = r - topX;
@@ -1890,12 +1913,7 @@ function angleArcFix(box, startDeg, sweepDeg) {
1890
1913
  const cx = l + w / 2;
1891
1914
  const cy = t + h / 2;
1892
1915
  const E = ellipseBeziers(l, t, l + w, t + h);
1893
- const Ecw = E.slice();
1894
- const d = Math.ceil(KAPPA * Math.floor(h / 2));
1895
- Ecw[3] = Ecw[1] - d;
1896
- Ecw[11] = Ecw[13] - d;
1897
- Ecw[15] = Ecw[13] + d;
1898
- Ecw[23] = Ecw[1] + d;
1916
+ const Ecw = clockwiseEllipseBeziersBox(axisBox(l, t, l + w, t + h));
1899
1917
  const start = startDeg * Math.PI / 180;
1900
1918
  const out = [Math.round(cx + w / 2 * tableCos(start)), Math.round(cy - h / 2 * tableSin(start))];
1901
1919
  for (const p of angleArcPieces(startDeg, sweepDeg)) {
@@ -1937,15 +1955,7 @@ function arcBeziers(l, t, r, b, xs, ys, xe, ye, clockwise) {
1937
1955
  }
1938
1956
  const px = (a2) => cx + rx * tableCos(a2);
1939
1957
  const py = (a2) => cy - ry * tableSin(a2);
1940
- const E = ellipseBeziers(l, t, r, b);
1941
- if (clockwise) {
1942
- const fh = Math.floor((b - t) / 2);
1943
- const d = Math.ceil(KAPPA * fh);
1944
- E[3] = E[1] - d;
1945
- E[11] = E[13] - d;
1946
- E[15] = E[13] + d;
1947
- E[23] = E[1] + d;
1948
- }
1958
+ const E = clockwise ? clockwiseEllipseBeziersBox(axisBox(l, t, r, b)) : ellipseBeziers(l, t, r, b);
1949
1959
  const out = [Math.round(px(a0)), Math.round(py(a0))];
1950
1960
  let a = a0;
1951
1961
  for (let guard = 0; guard < 8 && (s > 0 ? a < a1 - 1e-12 : a > a1 + 1e-12); guard++) {
@@ -1978,12 +1988,45 @@ function arcBeziers(l, t, r, b, xs, ys, xe, ye, clockwise) {
1978
1988
  var GdiRasterPath = class {
1979
1989
  constructor() {
1980
1990
  this.figures = [];
1991
+ /**
1992
+ * The path as `GetPath` reports it, before flattening: FIX points
1993
+ * (`[x0, y0, ...]`) and their `PT_*` types (`PT_MOVETO` 6, `PT_LINETO`
1994
+ * 2, `PT_BEZIERTO` 4, `PT_CLOSEFIGURE` 1 or-ed onto a figure's last
1995
+ * point). Used to check the geometry against Windows' own paths.
1996
+ */
1997
+ this.getPath = { pts: [], types: [] };
1981
1998
  this.current = null;
1982
1999
  }
1983
2000
  /** Starts a new figure at (`x`, `y`). */
1984
2001
  moveTo(x, y) {
1985
2002
  this.current = { pts: [x, y], closed: false };
1986
2003
  this.figures.push(this.current);
2004
+ this.log(x, y, 6);
2005
+ }
2006
+ /**
2007
+ * Starts a new figure at the current position (`x`, `y`) unless the open
2008
+ * figure already ends there. GDI opens a new figure for a drawing call
2009
+ * that continues from the current position (`LineTo`, `PolylineTo`,
2010
+ * `ArcTo`, ...) when the path is empty, its last figure is closed, or
2011
+ * the current position has moved away from the path's last point (a
2012
+ * `MoveTo`, or a call such as `Polyline`, `Arc` or `PolyPolygon` that
2013
+ * adds to the path without moving the current position; Windows'
2014
+ * `GetPath`, Wine `gdi32/tests/path.c`, `test_all_functions`).
2015
+ */
2016
+ continueAt(x, y) {
2017
+ const f = this.current;
2018
+ if (f && f.pts[f.pts.length - 2] === x && f.pts[f.pts.length - 1] === y) {
2019
+ return;
2020
+ }
2021
+ this.moveTo(x, y);
2022
+ }
2023
+ /** True when a figure is open (not closed and not ended by an appended path). */
2024
+ hasOpenFigure() {
2025
+ return this.current !== null;
2026
+ }
2027
+ log(x, y, type) {
2028
+ this.getPath.pts.push(x, y);
2029
+ this.getPath.types.push(type);
1987
2030
  }
1988
2031
  /** Adds a line to (`x`, `y`), starting a figure there when none is open. */
1989
2032
  lineTo(x, y) {
@@ -1992,6 +2035,7 @@ var GdiRasterPath = class {
1992
2035
  this.moveTo(last ? last[0] : x, last ? last[1] : y);
1993
2036
  }
1994
2037
  this.current.pts.push(x, y);
2038
+ this.log(x, y, 2);
1995
2039
  }
1996
2040
  /** Adds a cubic Bezier from the current point. */
1997
2041
  bezierTo(c1x, c1y, c2x, c2y, x, y) {
@@ -2001,6 +2045,9 @@ var GdiRasterPath = class {
2001
2045
  }
2002
2046
  const p = this.current.pts;
2003
2047
  this.flattenInto(p[p.length - 2], p[p.length - 1], c1x, c1y, c2x, c2y, x, y);
2048
+ this.log(c1x, c1y, 4);
2049
+ this.log(c2x, c2y, 4);
2050
+ this.log(x, y, 4);
2004
2051
  }
2005
2052
  /** Flattens one Bezier onto the open figure, recording its end tangents. */
2006
2053
  flattenInto(x0, y0, c1x, c1y, c2x, c2y, x3, y3) {
@@ -2035,6 +2082,9 @@ var GdiRasterPath = class {
2035
2082
  }
2036
2083
  for (let i = 2; i + 5 < pts.length; i += 6) {
2037
2084
  this.flattenInto(pts[i - 2], pts[i - 1], pts[i], pts[i + 1], pts[i + 2], pts[i + 3], pts[i + 4], pts[i + 5]);
2085
+ for (let k = i; k < i + 6; k += 2) {
2086
+ this.log(pts[k], pts[k + 1], 4);
2087
+ }
2038
2088
  }
2039
2089
  }
2040
2090
  /** Closes the open figure (the next drawing call starts a new one). */
@@ -2042,6 +2092,8 @@ var GdiRasterPath = class {
2042
2092
  if (this.current) {
2043
2093
  this.current.closed = true;
2044
2094
  this.current = null;
2095
+ const t = this.getPath.types;
2096
+ t[t.length - 1] |= 1;
2045
2097
  }
2046
2098
  }
2047
2099
  /** Appends every figure of `other`. */
@@ -2049,6 +2101,9 @@ var GdiRasterPath = class {
2049
2101
  for (const f of other.figures) {
2050
2102
  this.figures.push({ pts: f.pts.slice(), closed: f.closed, tangents: f.tangents && new Map(f.tangents) });
2051
2103
  }
2104
+ for (let i = 0; i < other.getPath.types.length; i++) {
2105
+ this.log(other.getPath.pts[2 * i], other.getPath.pts[2 * i + 1], other.getPath.types[i]);
2106
+ }
2052
2107
  this.current = null;
2053
2108
  }
2054
2109
  lastPoint() {
@@ -16651,8 +16706,11 @@ function boxCorners(box) {
16651
16706
  const { ax, ay, exx, exy, eyx, eyy } = box;
16652
16707
  return [ax + exx, ay + exy, ax, ay, ax + eyx, ay + eyy, ax + exx + eyx, ay + exy + eyy];
16653
16708
  }
16654
- function rectRasterPath(box) {
16655
- const c = boxCorners(box);
16709
+ function flipBox(box) {
16710
+ return { ax: box.ax + box.eyx, ay: box.ay + box.eyy, exx: box.exx, exy: box.exy, eyx: -box.eyx, eyy: -box.eyy };
16711
+ }
16712
+ function rectRasterPath(box, clockwise = false) {
16713
+ const c = boxCorners(clockwise ? flipBox(box) : box);
16656
16714
  const path = new GdiRasterPath();
16657
16715
  path.moveTo(c[0], c[1]);
16658
16716
  path.lineTo(c[2], c[3]);
@@ -16661,9 +16719,9 @@ function rectRasterPath(box) {
16661
16719
  path.closeFigure();
16662
16720
  return path;
16663
16721
  }
16664
- function ellipseRasterPath(box) {
16722
+ function ellipseRasterPath(box, clockwise = false) {
16665
16723
  const path = new GdiRasterPath();
16666
- path.addBeziers(ellipseBeziersBox(box), true);
16724
+ path.addBeziers(ellipseBeziersBox(box, clockwise), true);
16667
16725
  path.closeFigure();
16668
16726
  return path;
16669
16727
  }
@@ -16689,13 +16747,16 @@ function boxFrame(box) {
16689
16747
  const h = Math.round(Math.hypot(box.eyx, box.eyy));
16690
16748
  return { l: 0, t: 0, r: w, b: h };
16691
16749
  }
16692
- function roundRectRasterPath(box, cw, ch) {
16750
+ function roundRectRasterPath(box, cw, ch, clockwise = false, rectangle = false) {
16751
+ if (rectangle) {
16752
+ return rectRasterPath(box, clockwise);
16753
+ }
16693
16754
  const f = boxFrame(box);
16694
16755
  const q = roundRectCorners(f.l, f.t, f.r, f.b, cw, ch);
16695
16756
  const map = frameMapper(box, f.l, f.t, f.r - f.l, f.b - f.t);
16696
16757
  const pts = [];
16697
16758
  for (let i = 0; i < q.length; i += 2) {
16698
- pts.push(...map(q[i], q[i + 1]));
16759
+ pts.push(...map(q[i], clockwise ? f.t + f.b - q[i + 1] : q[i + 1]));
16699
16760
  }
16700
16761
  const path = new GdiRasterPath();
16701
16762
  for (let c = 0; c < 4; c++) {
@@ -16738,7 +16799,7 @@ function arcRasterPath(box, s, e, clockwise, kind, from, path = new GdiRasterPat
16738
16799
  pts.push(...map(bz[i], bz[i + 1]));
16739
16800
  }
16740
16801
  if (kind === "arcto" && from) {
16741
- path.lineTo(from[0], from[1]);
16802
+ path.continueAt(from[0], from[1]);
16742
16803
  path.addBeziers(pts, false);
16743
16804
  } else {
16744
16805
  path.addBeziers(pts, true);
@@ -19458,6 +19519,15 @@ function rasterPathOf(rCtx) {
19458
19519
  rCtx.rasterPath ?? (rCtx.rasterPath = new GdiRasterPath());
19459
19520
  return rCtx.rasterPath;
19460
19521
  }
19522
+ function continueFigure(rCtx) {
19523
+ const path = rasterPathOf(rCtx);
19524
+ const [x, y] = currentFix(rCtx);
19525
+ const before = path.figures.length;
19526
+ path.continueAt(x, y);
19527
+ if (path.figures.length !== before) {
19528
+ gdiPathRecorder(rCtx).moveTo(x / 16, y / 16);
19529
+ }
19530
+ }
19461
19531
  function lineStyleOf(rCtx) {
19462
19532
  rCtx.lineStyle ?? (rCtx.lineStyle = { pos: 0 });
19463
19533
  return rCtx.lineStyle;
@@ -19473,6 +19543,9 @@ function currentFix(rCtx) {
19473
19543
  }
19474
19544
  return fixPoint(rCtx, state.curX, state.curY);
19475
19545
  }
19546
+ function uprightFixBox(rCtx, l, t, r, b) {
19547
+ return fixBox(rCtx, Math.min(l, r), Math.min(t, b), Math.max(l, r), Math.max(t, b));
19548
+ }
19476
19549
  function fixExtent(rCtx, v, axis) {
19477
19550
  const m = gdiDeviceMatrix(rCtx);
19478
19551
  const k = axis === 0 ? Math.hypot(m[0], m[1]) : Math.hypot(m[2], m[3]);
@@ -19515,6 +19588,7 @@ function handleLineTo(rCtx, dataOff, recSize) {
19515
19588
  const to = rotated ? gmapPoint(rCtx, lx, ly) : { x: gmx(rCtx, lx), y: gmy(rCtx, ly) };
19516
19589
  const fixTo = fixPoint(rCtx, lx, ly);
19517
19590
  if (inPath) {
19591
+ continueFigure(rCtx);
19518
19592
  gdiPathRecorder(rCtx).lineTo(to.x, to.y);
19519
19593
  rasterPathOf(rCtx).lineTo(fixTo[0], fixTo[1]);
19520
19594
  } else {
@@ -19549,7 +19623,8 @@ function handleRectangle(rCtx, dataOff, recSize) {
19549
19623
  const t = view.getInt32(dataOff + 4, true);
19550
19624
  const r = view.getInt32(dataOff + 8, true);
19551
19625
  const b = view.getInt32(dataOff + 12, true);
19552
- const box = fixBox(rCtx, l, t, r, b);
19626
+ const box = uprightFixBox(rCtx, l, t, r, b);
19627
+ const clockwise = rCtx.state.arcDirection === 2;
19553
19628
  if (!inPath) {
19554
19629
  resetLineStyle(rCtx);
19555
19630
  }
@@ -19568,14 +19643,14 @@ function handleRectangle(rCtx, dataOff, recSize) {
19568
19643
  };
19569
19644
  if (inPath) {
19570
19645
  appendRect(gdiPathRecorder(rCtx));
19571
- rasterPathOf(rCtx).append(rectRasterPath(box));
19646
+ rasterPathOf(rCtx).append(rectRasterPath(box, clockwise));
19572
19647
  } else {
19573
19648
  paintGdiShape(rCtx, {
19574
19649
  build: (c) => {
19575
19650
  c.beginPath();
19576
19651
  appendRect(c);
19577
19652
  },
19578
- raster: () => rectRasterPath(box),
19653
+ raster: () => rectRasterPath(box, clockwise),
19579
19654
  rectangle: true,
19580
19655
  fill: true,
19581
19656
  stroke: true,
@@ -19590,14 +19665,14 @@ function handleRectangle(rCtx, dataOff, recSize) {
19590
19665
  const h = gmh(rCtx, b - t);
19591
19666
  if (inPath) {
19592
19667
  gdiPathRecorder(rCtx).rect(x, y, w, h);
19593
- rasterPathOf(rCtx).append(rectRasterPath(box));
19668
+ rasterPathOf(rCtx).append(rectRasterPath(box, clockwise));
19594
19669
  } else if (needsPathBasedRectangle(rCtx)) {
19595
19670
  paintGdiShape(rCtx, {
19596
19671
  build: (c) => {
19597
19672
  c.beginPath();
19598
19673
  c.rect(x, y, w, h);
19599
19674
  },
19600
- raster: () => rectRasterPath(box),
19675
+ raster: () => rectRasterPath(box, clockwise),
19601
19676
  rectangle: true,
19602
19677
  fill: true,
19603
19678
  stroke: true,
@@ -19710,7 +19785,8 @@ function handleRoundRect(rCtx, dataOff, recSize) {
19710
19785
  gmh(rCtx, cornerH) / 2,
19711
19786
  null
19712
19787
  );
19713
- const raster = (box = fixBox(rCtx, l, t, r, b)) => roundRectRasterPath(box, fixExtent(rCtx, cornerW, 0), fixExtent(rCtx, cornerH, 1));
19788
+ const clockwise = rCtx.state.arcDirection === 2;
19789
+ const raster = (box = uprightFixBox(rCtx, l, t, r, b)) => roundRectRasterPath(box, fixExtent(rCtx, cornerW, 0), fixExtent(rCtx, cornerH, 1), clockwise, cornerW === 0 || cornerH === 0);
19714
19790
  if (inPath) {
19715
19791
  drawRoundRect(gdiPathRecorder(rCtx));
19716
19792
  rasterPathOf(rCtx).append(raster());
@@ -19721,7 +19797,7 @@ function handleRoundRect(rCtx, dataOff, recSize) {
19721
19797
  c.beginPath();
19722
19798
  drawRoundRect(c);
19723
19799
  },
19724
- raster: () => raster(curvedFixBox(rCtx, l, t, r, b)),
19800
+ raster: () => raster(curvedFixBox(rCtx, l, t, r, b, true)),
19725
19801
  roundPen: true,
19726
19802
  fill: true,
19727
19803
  stroke: true
@@ -19730,8 +19806,8 @@ function handleRoundRect(rCtx, dataOff, recSize) {
19730
19806
  }
19731
19807
  return true;
19732
19808
  }
19733
- function curvedFixBox(rCtx, l, t, r, b) {
19734
- const box = fixBox(rCtx, l, t, r, b);
19809
+ function curvedFixBox(rCtx, l, t, r, b, upright = false) {
19810
+ const box = upright ? uprightFixBox(rCtx, l, t, r, b) : fixBox(rCtx, l, t, r, b);
19735
19811
  if (rCtx.state.penStyle !== 5) {
19736
19812
  return box;
19737
19813
  }
@@ -19757,6 +19833,7 @@ function handleEllipse(rCtx, dataOff, recSize) {
19757
19833
  const t = view.getInt32(dataOff + 4, true);
19758
19834
  const r = view.getInt32(dataOff + 8, true);
19759
19835
  const b = view.getInt32(dataOff + 12, true);
19836
+ const clockwise = rCtx.state.arcDirection === 2;
19760
19837
  const params = hasWorldRotation(rCtx) ? gdiEllipseParams(rCtx, (l + r) / 2, (t + b) / 2, Math.abs(r - l) / 2, Math.abs(b - t) / 2) : {
19761
19838
  cx: gmx(rCtx, (l + r) / 2),
19762
19839
  cy: gmy(rCtx, (t + b) / 2),
@@ -19766,7 +19843,7 @@ function handleEllipse(rCtx, dataOff, recSize) {
19766
19843
  };
19767
19844
  if (inPath) {
19768
19845
  gdiPathRecorder(rCtx).ellipse(params.cx, params.cy, params.rx, params.ry, params.rotation, 0, Math.PI * 2);
19769
- rasterPathOf(rCtx).append(ellipseRasterPath(fixBox(rCtx, l, t, r, b)));
19846
+ rasterPathOf(rCtx).append(ellipseRasterPath(uprightFixBox(rCtx, l, t, r, b), clockwise));
19770
19847
  } else {
19771
19848
  resetLineStyle(rCtx);
19772
19849
  paintGdiShape(rCtx, {
@@ -19774,7 +19851,7 @@ function handleEllipse(rCtx, dataOff, recSize) {
19774
19851
  c.beginPath();
19775
19852
  c.ellipse(params.cx, params.cy, params.rx, params.ry, params.rotation, 0, Math.PI * 2);
19776
19853
  },
19777
- raster: () => ellipseRasterPath(curvedFixBox(rCtx, l, t, r, b)),
19854
+ raster: () => ellipseRasterPath(curvedFixBox(rCtx, l, t, r, b, true), clockwise),
19778
19855
  roundPen: true,
19779
19856
  fill: true,
19780
19857
  stroke: true
@@ -19832,6 +19909,9 @@ function handleArcFamily(rCtx, recType, dataOff, recSize) {
19832
19909
  from: currentFix(rCtx)
19833
19910
  });
19834
19911
  if (inPath) {
19912
+ if (isArcTo) {
19913
+ continueFigure(rCtx);
19914
+ }
19835
19915
  build(gdiPathRecorder(rCtx));
19836
19916
  const a = rasterArgs();
19837
19917
  arcRasterPath(a.box, a.s, a.e, clockwise, kind, a.from, rasterPathOf(rCtx));
@@ -19908,9 +19988,7 @@ function handleAngleArc(rCtx, dataOff, recSize) {
19908
19988
  }
19909
19989
  const e = [fixPts[fixPts.length - 2], fixPts[fixPts.length - 1]];
19910
19990
  const buildRaster = (path, from) => {
19911
- if (from) {
19912
- path.moveTo(from[0], from[1]);
19913
- }
19991
+ path.continueAt(from[0], from[1]);
19914
19992
  path.addBeziers(fixPts, false);
19915
19993
  };
19916
19994
  const params = gdiEllipseParams(rCtx, cx, cy, radius, radius);
@@ -19924,9 +20002,9 @@ function handleAngleArc(rCtx, dataOff, recSize) {
19924
20002
  c.ellipse(params.cx, params.cy, params.rx, params.ry, params.rotation, -a0, clockwise ? -a0 + span : -a0 - span, !clockwise);
19925
20003
  };
19926
20004
  if (inPath) {
19927
- const rec = gdiPathRecorder(rCtx);
19928
- buildCanvas(rec);
19929
- buildRaster(rasterPathOf(rCtx), rasterPathOf(rCtx).figures.length === 0 ? currentFix(rCtx) : null);
20005
+ continueFigure(rCtx);
20006
+ buildCanvas(gdiPathRecorder(rCtx));
20007
+ buildRaster(rasterPathOf(rCtx), currentFix(rCtx));
19930
20008
  } else {
19931
20009
  resetLineStyle(rCtx);
19932
20010
  const from = currentFix(rCtx);
@@ -22773,9 +22851,12 @@ function handlePoly(rCtx, recType, count, readPt, kinds) {
22773
22851
  }
22774
22852
  };
22775
22853
  if (inPath) {
22854
+ if (isTo) {
22855
+ continueFigure(rCtx);
22856
+ }
22776
22857
  build(gdiPathRecorder(rCtx));
22777
22858
  rCtx.rasterPath ?? (rCtx.rasterPath = new GdiRasterPath());
22778
- buildRaster(rCtx.rasterPath, isTo && rCtx.rasterPath.figures.length === 0 ? currentFix(rCtx) : null);
22859
+ buildRaster(rCtx.rasterPath, null);
22779
22860
  } else {
22780
22861
  const from = isTo ? currentFix(rCtx) : null;
22781
22862
  const start = isTo ? gmapPoint(rCtx, state.curX, state.curY) : null;
@@ -22798,10 +22879,11 @@ function handlePoly(rCtx, recType, count, readPt, kinds) {
22798
22879
  fillRule: state.polyFillMode === 2 ? "nonzero" : "evenodd"
22799
22880
  });
22800
22881
  }
22801
- if (count > 0) {
22882
+ if (isTo && count > 0) {
22802
22883
  const [x, y] = readPt(count - 1);
22803
22884
  state.curX = x;
22804
22885
  state.curY = y;
22886
+ rCtx.curFix = void 0;
22805
22887
  }
22806
22888
  }
22807
22889
  function handlePoly32(rCtx, recType, offset, dataOff, recSize) {
@@ -22906,9 +22988,11 @@ function handlePolyDraw(rCtx, offset, dataOff, recSize, pointSize) {
22906
22988
  };
22907
22989
  const startsWithMove = (typeAt(0) & ~PT_CLOSEFIGURE) === PT_MOVETO;
22908
22990
  if (inPath) {
22991
+ if (!startsWithMove) {
22992
+ continueFigure(rCtx);
22993
+ }
22909
22994
  rCtx.rasterPath ?? (rCtx.rasterPath = new GdiRasterPath());
22910
- const fresh = rCtx.rasterPath.figures.length === 0;
22911
- walk2(gdiPathRecorder(rCtx), rCtx.rasterPath, !startsWithMove && fresh ? currentFix(rCtx) : null, null);
22995
+ walk2(gdiPathRecorder(rCtx), rCtx.rasterPath, null, null);
22912
22996
  } else {
22913
22997
  const from = startsWithMove ? null : currentFix(rCtx);
22914
22998
  const fromPx = startsWithMove ? null : gmapPoint(rCtx, state.curX, state.curY);
@@ -27364,7 +27448,8 @@ function wmfSetDibToDev(p, d, end) {
27364
27448
  const hv = new DataView(bmi.buffer);
27365
27449
  const fullH = hv.getInt32(8, true);
27366
27450
  hv.setInt32(8, fullH < 0 ? -scanCount : scanCount, true);
27367
- const [ox, oy] = deviceDest(p, dx, dy, 0, 0);
27451
+ const [mx, oy] = deviceDest(p, dx, dy, 0, 0);
27452
+ const ox = p.layout & 1 ? mx - w : mx;
27368
27453
  playStretchDibits(p, 13369376, ox, oy + (sy + h - hi), w, hi - lo, sx, lo - startScan, w, hi - lo, { bmi, bits: dib.bits }, true);
27369
27454
  }
27370
27455
 
@@ -27711,25 +27796,30 @@ function wmfRectangle(p, l, t, r, b) {
27711
27796
  c.beginPath();
27712
27797
  c.rect(cr.x, cr.y, cr.w, cr.h);
27713
27798
  },
27714
- raster: () => rectRasterPath(fixBoxOf(box)),
27799
+ raster: () => rectRasterPath(fixBoxOf(box), clockwiseOf(p)),
27715
27800
  rectangle: true,
27716
27801
  fill: true,
27717
27802
  stroke: true,
27718
27803
  axisRect: { interior: rectInterior(p, box) }
27719
27804
  });
27720
27805
  }
27806
+ function compatCorner(p, w, h) {
27807
+ const m = gdiDeviceMatrix(p.rCtx);
27808
+ const cw = Math.round(Math.abs(w * m[0]) * 16);
27809
+ const ch = Math.round(Math.abs(h * m[3]) * 16);
27810
+ const ux = 16 * p.kx;
27811
+ const uy = 16 * p.ky;
27812
+ if (!penIsNull(p) && !penIsCosmetic(p.rCtx)) {
27813
+ return [Math.round(Math.floor(cw / (2 * ux)) * 2 * ux), Math.round(Math.floor(ch / (2 * uy)) * 2 * uy)];
27814
+ }
27815
+ return [Math.round(Math.floor(Math.round(cw / ux) / 2) * 2 * ux), Math.round(Math.floor(Math.round(ch / uy) / 2) * 2 * uy)];
27816
+ }
27721
27817
  function wmfRoundRect(p, l, t, r, b, w, h) {
27722
27818
  const box = compatBox(p, l, t, r, b, { curved: true });
27723
27819
  if (box.x1 < box.x0 || box.y1 < box.y0) {
27724
27820
  return;
27725
27821
  }
27726
- const m = gdiDeviceMatrix(p.rCtx);
27727
- let cw = Math.round(Math.abs(w * m[0]) * 16);
27728
- let ch = Math.round(Math.abs(h * m[3]) * 16);
27729
- if (!penIsNull(p) && !penIsCosmetic(p.rCtx)) {
27730
- cw = Math.floor(cw / 32) * 32;
27731
- ch = Math.floor(ch / 32) * 32;
27732
- }
27822
+ const [cw, ch] = compatCorner(p, w, h);
27733
27823
  const cr = canvasRect(box);
27734
27824
  paintGdiShape(p.rCtx, {
27735
27825
  build: (c) => {
@@ -27754,7 +27844,7 @@ function wmfRoundRect(p, l, t, r, b, w, h) {
27754
27844
  c.ellipse(cr.x + ex, cr.y + ey, ex, ey, 0, 2 * q, 3 * q);
27755
27845
  c.closePath();
27756
27846
  },
27757
- raster: () => roundRectRasterPath(fixBoxOf(box), cw, ch),
27847
+ raster: () => roundRectRasterPath(fixBoxOf(box), cw, ch, clockwiseOf(p), w === 0 || h === 0),
27758
27848
  roundPen: true,
27759
27849
  fill: true,
27760
27850
  stroke: true
@@ -27771,24 +27861,31 @@ function wmfEllipse(p, l, t, r, b) {
27771
27861
  c.beginPath();
27772
27862
  c.ellipse(cr.x + cr.w / 2, cr.y + cr.h / 2, cr.w / 2, cr.h / 2, 0, 0, Math.PI * 2);
27773
27863
  },
27774
- raster: () => ellipseRasterPath(fixBoxOf(box)),
27864
+ raster: () => ellipseRasterPath(fixBoxOf(box), clockwiseOf(p)),
27775
27865
  roundPen: true,
27776
27866
  fill: true,
27777
27867
  stroke: true
27778
27868
  });
27779
27869
  }
27780
- function wmfArcFamily(p, kind, l, t, r, b, xs, ys, xe, ye) {
27781
- const box = compatBox(p, l, t, r, b, { curved: kind !== "arc" });
27870
+ function compatArc(p, kind, l, t, r, b, xs, ys, xe, ye) {
27871
+ const box = compatBox(p, l, t, r, b, { curved: kind === "chord" || kind === "pie" });
27782
27872
  if (box.x1 < box.x0 || box.y1 < box.y0) {
27783
- return;
27873
+ return null;
27784
27874
  }
27785
27875
  const raw = compatBox(p, l, t, r, b, { exclusive: false });
27786
- const rdx = (box.x0 + box.x1 - raw.x0 - raw.x1) / 2;
27787
- const rdy = (box.y0 + box.y1 - raw.y0 - raw.y1) / 2;
27788
- const s0 = fixPoint(p.rCtx, xs, ys);
27789
- const e0 = fixPoint(p.rCtx, xe, ye);
27790
- const s = [s0[0] + rdx, s0[1] + rdy];
27791
- const e = [e0[0] + rdx, e0[1] + rdy];
27876
+ const onBox = (v, r0, r1, b0, b1) => (b0 + b1) / 2 + (r1 !== r0 ? (v - (r0 + r1) / 2) * (b1 - b0) / (r1 - r0) : v - (r0 + r1) / 2);
27877
+ const radial = (x, y) => {
27878
+ const [fx, fy] = fixPoint(p.rCtx, x, y);
27879
+ return [onBox(fx, raw.x0, raw.x1, box.x0, box.x1), onBox(fy, raw.y0, raw.y1, box.y0, box.y1)];
27880
+ };
27881
+ return { box, s: radial(xs, ys), e: radial(xe, ye) };
27882
+ }
27883
+ function wmfArcFamily(p, kind, l, t, r, b, xs, ys, xe, ye) {
27884
+ const geom = compatArc(p, kind, l, t, r, b, xs, ys, xe, ye);
27885
+ if (!geom) {
27886
+ return;
27887
+ }
27888
+ const { box, s, e } = geom;
27792
27889
  const clockwise = p.rCtx.state.arcDirection === 2;
27793
27890
  const cr = canvasRect(box);
27794
27891
  const cx = cr.x + cr.w / 2;
@@ -27814,6 +27911,9 @@ function wmfArcFamily(p, kind, l, t, r, b, xs, ys, xe, ye) {
27814
27911
  stroke: true
27815
27912
  });
27816
27913
  }
27914
+ function clockwiseOf(p) {
27915
+ return p.rCtx.state.arcDirection === 2;
27916
+ }
27817
27917
  function penIsNull(p) {
27818
27918
  return (p.rCtx.state.penStyle & 15) === PS_NULL3;
27819
27919
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emf-converter",
3
- "version": "4.3.1",
3
+ "version": "4.3.2",
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",