emf-converter 4.1.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -22,9 +22,9 @@ function lerpArgbToRgba(argbA, argbB, t) {
22
22
  return `rgba(${r},${g},${b},${a.toFixed(3)})`;
23
23
  }
24
24
  function invertCssColor(color) {
25
- const hex4 = /^#([0-9a-f]{6})$/i.exec(color);
26
- if (hex4) {
27
- const v = parseInt(hex4[1], 16);
25
+ const hex5 = /^#([0-9a-f]{6})$/i.exec(color);
26
+ if (hex5) {
27
+ const v = parseInt(hex5[1], 16);
28
28
  const inv2 = 16777215 ^ v;
29
29
  return `#${inv2.toString(16).padStart(6, "0")}`;
30
30
  }
@@ -384,8 +384,8 @@ function cssHexToRgb(color) {
384
384
  if (!m) {
385
385
  return 0;
386
386
  }
387
- const hex4 = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];
388
- return parseInt(hex4, 16);
387
+ const hex5 = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];
388
+ return parseInt(hex5, 16);
389
389
  }
390
390
  function decodeMonoBits(view, bmiOffset, bitsOffset) {
391
391
  if (bmiOffset + 16 > view.byteLength) {
@@ -728,6 +728,7 @@ var EMFPLUS_SETRENDERINGORIGIN = 16413;
728
728
  var EMFPLUS_SETTEXTCONTRAST = 16416;
729
729
  var EMFPLUS_SETCOMPOSITINGMODE = 16419;
730
730
  var EMFPLUS_STROKEFILLPATH = 16439;
731
+ var EMFPLUS_SERIALIZABLEOBJECT = 16440;
731
732
  var EMFPLUS_SETTSGRAPHICS = 16441;
732
733
  var EMFPLUS_SETTSCLIP = 16442;
733
734
  var EMFPLUS_OBJECTTYPE_BRUSH = 1;
@@ -13738,7 +13739,8 @@ function gmx(r, x) {
13738
13739
  const wt = r.state.worldTransform;
13739
13740
  const px = wt[0] * x + wt[4];
13740
13741
  if (r.useMappingMode) {
13741
- return (px - r.windowOrg.x) / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1) + r.viewportOrg.x;
13742
+ const dx = (px - r.windowOrg.x) / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1) + r.viewportOrg.x;
13743
+ return r.deviceToCanvas ? (dx - r.bounds.left) * r.sx : dx;
13742
13744
  }
13743
13745
  return (px - r.bounds.left) * r.sx;
13744
13746
  }
@@ -13746,24 +13748,31 @@ function gmy(r, y) {
13746
13748
  const wt = r.state.worldTransform;
13747
13749
  const py = wt[3] * y + wt[5];
13748
13750
  if (r.useMappingMode) {
13749
- return (py - r.windowOrg.y) / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1) + r.viewportOrg.y;
13751
+ const dy = (py - r.windowOrg.y) / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1) + r.viewportOrg.y;
13752
+ return r.deviceToCanvas ? (dy - r.bounds.top) * r.sy : dy;
13750
13753
  }
13751
13754
  return (py - r.bounds.top) * r.sy;
13752
13755
  }
13753
13756
  function gmw(r, w) {
13754
13757
  const pw = r.state.worldTransform[0] * w;
13755
13758
  if (r.useMappingMode) {
13756
- return pw / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1);
13759
+ return pw / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1) * (r.deviceToCanvas ? r.sx : 1);
13757
13760
  }
13758
13761
  return pw * r.sx;
13759
13762
  }
13760
13763
  function gmh(r, h) {
13761
13764
  const ph = r.state.worldTransform[3] * h;
13762
13765
  if (r.useMappingMode) {
13763
- return ph / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1);
13766
+ return ph / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1) * (r.deviceToCanvas ? r.sy : 1);
13764
13767
  }
13765
13768
  return ph * r.sy;
13766
13769
  }
13770
+ function gdiDevicePixelX(r) {
13771
+ return !r.useMappingMode || r.deviceToCanvas ? r.sx : 1;
13772
+ }
13773
+ function gdiDevicePixelY(r) {
13774
+ return !r.useMappingMode || r.deviceToCanvas ? r.sy : 1;
13775
+ }
13767
13776
  function activateGdiMappingMode(r) {
13768
13777
  r.useMappingMode = true;
13769
13778
  }
@@ -13776,13 +13785,14 @@ function gdiDeviceMatrix(r) {
13776
13785
  if (r.useMappingMode) {
13777
13786
  const kx = (r.viewportExt.cx || 1) / (r.windowExt.cx || 1);
13778
13787
  const ky = (r.viewportExt.cy || 1) / (r.windowExt.cy || 1);
13788
+ const [cx, cy, ox, oy] = r.deviceToCanvas ? [r.sx, r.sy, r.bounds.left, r.bounds.top] : [1, 1, 0, 0];
13779
13789
  return [
13780
- wt[0] * kx,
13781
- wt[1] * ky,
13782
- wt[2] * kx,
13783
- wt[3] * ky,
13784
- (wt[4] - r.windowOrg.x) * kx + r.viewportOrg.x,
13785
- (wt[5] - r.windowOrg.y) * ky + r.viewportOrg.y
13790
+ wt[0] * kx * cx,
13791
+ wt[1] * ky * cy,
13792
+ wt[2] * kx * cx,
13793
+ wt[3] * ky * cy,
13794
+ ((wt[4] - r.windowOrg.x) * kx + r.viewportOrg.x - ox) * cx,
13795
+ ((wt[5] - r.windowOrg.y) * ky + r.viewportOrg.y - oy) * cy
13786
13796
  ];
13787
13797
  }
13788
13798
  const sx = r.sx || 1;
@@ -15657,8 +15667,8 @@ function paintWithRop2PerPixel(ctx, rop2, color, draw, pad = 2, fillRule) {
15657
15667
  sc.restore();
15658
15668
  }
15659
15669
  const pointTest = fillRule !== void 0 && typeof sc.isPointInPath === "function";
15660
- const hex4 = /^#([0-9a-f]{6})$/i.exec(color.trim());
15661
- const fixedP = hex4 ? parseInt(hex4[1], 16) : -1;
15670
+ const hex5 = /^#([0-9a-f]{6})$/i.exec(color.trim());
15671
+ const fixedP = hex5 ? parseInt(hex5[1], 16) : -1;
15662
15672
  const paint = canvasGetImageData(sc, 0, 0, box.w, box.h);
15663
15673
  const pd = paint.data;
15664
15674
  const dd = index === 240 ? null : canvasGetImageData(ctx, box.x, box.y, box.w, box.h).data;
@@ -17957,7 +17967,7 @@ function writeTextureColor(inv2, width, height, rgba, wrap, halfPixel2, dx, dy,
17957
17967
  const iv = Math.floor(cv + 1e-9);
17958
17968
  const fu = Math.max(0, cu - iu);
17959
17969
  const fv = Math.max(0, cv - iv);
17960
- const clamp = wrap === "clamp";
17970
+ const clamp2 = wrap === "clamp";
17961
17971
  const mirrorX = wrap === "tile-flip-x" || wrap === "tile-flip-xy";
17962
17972
  const mirrorY = wrap === "tile-flip-y" || wrap === "tile-flip-xy";
17963
17973
  let r = 0;
@@ -17973,7 +17983,7 @@ function writeTextureColor(inv2, width, height, rgba, wrap, halfPixel2, dx, dy,
17973
17983
  }
17974
17984
  let x;
17975
17985
  let y;
17976
- if (clamp) {
17986
+ if (clamp2) {
17977
17987
  if (tu < 0 || tv < 0 || tu >= width || tv >= height) {
17978
17988
  continue;
17979
17989
  }
@@ -20355,8 +20365,8 @@ function paintParallelogram(rCtx, a, b, q, sw, sh, pixel) {
20355
20365
  });
20356
20366
  }
20357
20367
  function alignMirroredDest(rCtx, req) {
20358
- const pxX = rCtx.useMappingMode ? 1 : rCtx.sx;
20359
- const pxY = rCtx.useMappingMode ? 1 : rCtx.sy;
20368
+ const pxX = gdiDevicePixelX(rCtx);
20369
+ const pxY = gdiDevicePixelY(rCtx);
20360
20370
  return {
20361
20371
  ...req,
20362
20372
  dx: req.dw < 0 ? req.dx + pxX : req.dx,
@@ -20591,10 +20601,10 @@ function axisMap(rCtx, xDest, yDest, cxDest, cyDest, sx, sy, sw, sh) {
20591
20601
  const dw = gmw(rCtx, cxDest);
20592
20602
  const dh = gmh(rCtx, cyDest);
20593
20603
  if (dw < 0) {
20594
- dx += rCtx.useMappingMode ? 1 : rCtx.sx;
20604
+ dx += gdiDevicePixelX(rCtx);
20595
20605
  }
20596
20606
  if (dh < 0) {
20597
- dy += rCtx.useMappingMode ? 1 : rCtx.sy;
20607
+ dy += gdiDevicePixelY(rCtx);
20598
20608
  }
20599
20609
  const W = Math.round(Math.abs(dw));
20600
20610
  const H = Math.round(Math.abs(dh));
@@ -23523,6 +23533,412 @@ function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
23523
23533
  }
23524
23534
  }
23525
23535
 
23536
+ // src/emf-plus-image-effects.ts
23537
+ var EFFECT_GUIDS = {
23538
+ "633C80A4-1843-482B-9EF2-BE2834C5FDD4": "blur",
23539
+ "D3A1DBE1-8EC4-4C17-9F4C-EA97AD1C343D": "brightnessContrast",
23540
+ "537E597D-251E-48DA-9664-29CA496B70F8": "colorBalance",
23541
+ "DD6A0022-58E4-4A67-9D9B-D48EB881A53D": "colorCurve",
23542
+ "A7CE72A9-0F7F-40D7-B3CC-D0C02D5C3212": "colorLookupTable",
23543
+ "718F2615-7933-40E3-A511-5F68FE14DD74": "colorMatrix",
23544
+ "8B2DD6C3-EB07-4D87-A5F0-7108E26A9C5F": "hueSaturationLightness",
23545
+ "99C354EC-2A31-4F3A-8C34-17A803B33A25": "levels",
23546
+ "74D29D05-69A4-4266-9549-3CC52836B632": "redEyeCorrection",
23547
+ "63CBF3EE-C526-402C-8F71-62C540BF5142": "sharpen",
23548
+ "1077AF00-2848-4441-9489-44AD4C2D7A2C": "tint"
23549
+ };
23550
+ var DRAWIMAGE_EFFECT_FLAG = 8192;
23551
+ var CurveAdjustment = {
23552
+ Exposure: 0,
23553
+ Density: 1,
23554
+ Contrast: 2,
23555
+ Highlight: 3,
23556
+ Shadow: 4,
23557
+ Midtone: 5,
23558
+ WhiteSaturation: 6,
23559
+ BlackSaturation: 7
23560
+ };
23561
+ var CurveChannel = { All: 0, Red: 1, Green: 2, Blue: 3 };
23562
+ var hex4 = (v, digits) => v.toString(16).toUpperCase().padStart(digits, "0");
23563
+ function readGuid(view, off) {
23564
+ const tail = [];
23565
+ for (let i = 0; i < 8; i++) {
23566
+ tail.push(hex4(view.getUint8(off + 8 + i), 2));
23567
+ }
23568
+ return [
23569
+ hex4(view.getUint32(off, true), 8),
23570
+ hex4(view.getUint16(off + 4, true), 4),
23571
+ hex4(view.getUint16(off + 6, true), 4),
23572
+ tail.slice(0, 2).join(""),
23573
+ tail.slice(2).join("")
23574
+ ].join("-");
23575
+ }
23576
+ var clamp = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
23577
+ function parseSerializableObject(view, dataOff, dataSize) {
23578
+ if (dataSize < 20) {
23579
+ return null;
23580
+ }
23581
+ const kind = EFFECT_GUIDS[readGuid(view, dataOff)];
23582
+ const size = view.getUint32(dataOff + 16, true);
23583
+ if (!kind || 20 + size > dataSize) {
23584
+ return null;
23585
+ }
23586
+ const p = dataOff + 20;
23587
+ const i32 = (k, lo, hi) => clamp(view.getInt32(p + 4 * k, true), lo, hi);
23588
+ const f323 = (k, lo, hi) => {
23589
+ const v = view.getFloat32(p + 4 * k, true);
23590
+ return Number.isFinite(v) ? clamp(v, lo, hi) : 0;
23591
+ };
23592
+ const need = (n) => size >= n;
23593
+ switch (kind) {
23594
+ case "blur":
23595
+ return need(8) ? { kind, radius: f323(0, 0, 255), expandEdge: view.getUint32(p + 4, true) !== 0 } : null;
23596
+ case "brightnessContrast":
23597
+ return need(8) ? { kind, brightness: i32(0, -255, 255), contrast: i32(1, -100, 100) } : null;
23598
+ case "colorBalance":
23599
+ return need(12) ? { kind, cyanRed: i32(0, -100, 100), magentaGreen: i32(1, -100, 100), yellowBlue: i32(2, -100, 100) } : null;
23600
+ case "colorCurve":
23601
+ return need(12) ? {
23602
+ kind,
23603
+ adjustment: view.getInt32(p, true),
23604
+ channel: view.getInt32(p + 4, true),
23605
+ intensity: i32(2, -255, 255)
23606
+ } : null;
23607
+ case "colorLookupTable": {
23608
+ if (!need(1024)) {
23609
+ return null;
23610
+ }
23611
+ const lut = (k) => new Uint8Array(view.buffer.slice(view.byteOffset + p + 256 * k, view.byteOffset + p + 256 * (k + 1)));
23612
+ return { kind, b: lut(0), g: lut(1), r: lut(2), a: lut(3) };
23613
+ }
23614
+ case "colorMatrix": {
23615
+ if (!need(100)) {
23616
+ return null;
23617
+ }
23618
+ const matrix = [];
23619
+ for (let k = 0; k < 25; k++) {
23620
+ const v = view.getFloat32(p + 4 * k, true);
23621
+ matrix.push(Number.isFinite(v) ? v : 0);
23622
+ }
23623
+ return { kind, matrix };
23624
+ }
23625
+ case "hueSaturationLightness":
23626
+ return need(12) ? { kind, hue: i32(0, -180, 180), saturation: i32(1, -100, 100), lightness: i32(2, -100, 100) } : null;
23627
+ case "levels":
23628
+ return need(12) ? { kind, highlight: i32(0, 0, 100), midtone: i32(1, -100, 100), shadow: i32(2, 0, 100) } : null;
23629
+ case "redEyeCorrection": {
23630
+ if (!need(4)) {
23631
+ return null;
23632
+ }
23633
+ const count = view.getInt32(p, true);
23634
+ if (count < 0 || 4 + 16 * count > size) {
23635
+ return null;
23636
+ }
23637
+ const areas = [];
23638
+ for (let k = 0; k < count; k++) {
23639
+ const o = p + 4 + 16 * k;
23640
+ areas.push({
23641
+ left: view.getInt32(o, true),
23642
+ top: view.getInt32(o + 4, true),
23643
+ right: view.getInt32(o + 8, true),
23644
+ bottom: view.getInt32(o + 12, true)
23645
+ });
23646
+ }
23647
+ return { kind, areas };
23648
+ }
23649
+ case "sharpen":
23650
+ return need(8) ? { kind, radius: f323(0, 0, 255), amount: f323(1, 0, 100) } : null;
23651
+ case "tint":
23652
+ return need(8) ? { kind, hue: i32(0, -180, 180), amount: i32(1, -100, 100) } : null;
23653
+ }
23654
+ }
23655
+ function buildLut(f) {
23656
+ const lut = new Uint8Array(256);
23657
+ for (let v = 0; v < 256; v++) {
23658
+ lut[v] = clamp(Math.round(f(v)), 0, 255);
23659
+ }
23660
+ return lut;
23661
+ }
23662
+ function contrastCurve(c) {
23663
+ const gain = c >= 100 ? 1e6 : c >= 0 ? 100 / (100 - c) : (100 + c) / 100;
23664
+ return (v) => (v - 127.5) * gain + 127.5;
23665
+ }
23666
+ function balanceCurve(t) {
23667
+ const f = t / 100;
23668
+ return (v) => f >= 0 ? v + (255 - v) * f : v * (1 + f);
23669
+ }
23670
+ function midtoneCurve(t) {
23671
+ const gamma = 2 ** (-t / 100);
23672
+ return (v) => 255 * (v / 255) ** gamma;
23673
+ }
23674
+ function curveAdjustmentLut(adjustment, intensity) {
23675
+ const t = intensity;
23676
+ switch (adjustment) {
23677
+ case CurveAdjustment.Exposure:
23678
+ return buildLut((v) => v * (1 + t / 255));
23679
+ case CurveAdjustment.Density:
23680
+ return buildLut((v) => v * (1 - t / 255));
23681
+ case CurveAdjustment.Contrast:
23682
+ return buildLut(contrastCurve(clamp(t, -100, 100)));
23683
+ case CurveAdjustment.Highlight:
23684
+ case CurveAdjustment.Shadow: {
23685
+ const high = adjustment === CurveAdjustment.Highlight;
23686
+ const amp = clamp(t, -100, 100) * 0.32;
23687
+ return buildLut((v) => {
23688
+ const inside = high ? v > 128 : v < 128;
23689
+ if (!inside) {
23690
+ return v;
23691
+ }
23692
+ const u = high ? (v - 128) / 127 : v / 128;
23693
+ return v + amp * Math.sin(Math.PI * u);
23694
+ });
23695
+ }
23696
+ case CurveAdjustment.Midtone:
23697
+ return buildLut(midtoneCurve(clamp(t, -100, 100)));
23698
+ case CurveAdjustment.WhiteSaturation: {
23699
+ const w = Math.max(1, clamp(t, 0, 255));
23700
+ return buildLut((v) => v * 255 / w);
23701
+ }
23702
+ case CurveAdjustment.BlackSaturation: {
23703
+ const b = Math.min(254, clamp(t, 0, 255));
23704
+ return buildLut((v) => (v - b) * 255 / (255 - b));
23705
+ }
23706
+ default:
23707
+ return null;
23708
+ }
23709
+ }
23710
+ function levelsLut(highlight, midtone, shadow) {
23711
+ const black = shadow / 100 * 255;
23712
+ const white = Math.max(black + 1, highlight / 100 * 255);
23713
+ const gamma = midtoneCurve(midtone);
23714
+ return buildLut((v) => gamma(clamp((v - black) / (white - black), 0, 1) * 255));
23715
+ }
23716
+ function applyLuts(src, r, g, b, a = null) {
23717
+ const out = new Uint8ClampedArray(src);
23718
+ for (let i = 0; i < out.length; i += 4) {
23719
+ if (r) out[i] = r[src[i]];
23720
+ if (g) out[i + 1] = g[src[i + 1]];
23721
+ if (b) out[i + 2] = b[src[i + 2]];
23722
+ if (a) out[i + 3] = a[src[i + 3]];
23723
+ }
23724
+ return out;
23725
+ }
23726
+ function applyColorMatrix(src, m) {
23727
+ const out = new Uint8ClampedArray(src.length);
23728
+ for (let i = 0; i < src.length; i += 4) {
23729
+ const r = src[i] / 255;
23730
+ const g = src[i + 1] / 255;
23731
+ const b = src[i + 2] / 255;
23732
+ const a = src[i + 3] / 255;
23733
+ for (let j = 0; j < 4; j++) {
23734
+ const v = r * m[j] + g * m[5 + j] + b * m[10 + j] + a * m[15 + j] + m[20 + j];
23735
+ out[i + j] = Math.round(clamp(v, 0, 1) * 255);
23736
+ }
23737
+ }
23738
+ return out;
23739
+ }
23740
+ function rgbToHsl(r, g, b) {
23741
+ const max = Math.max(r, g, b);
23742
+ const min = Math.min(r, g, b);
23743
+ const l = (max + min) / 2;
23744
+ const d = max - min;
23745
+ if (d === 0) {
23746
+ return [0, 0, l];
23747
+ }
23748
+ const s = d / (1 - Math.abs(2 * l - 1));
23749
+ let h;
23750
+ if (max === r) {
23751
+ h = (g - b) / d % 6;
23752
+ } else if (max === g) {
23753
+ h = (b - r) / d + 2;
23754
+ } else {
23755
+ h = (r - g) / d + 4;
23756
+ }
23757
+ h *= 60;
23758
+ return [h < 0 ? h + 360 : h, s, l];
23759
+ }
23760
+ function hslToRgb(h, s, l) {
23761
+ const c = (1 - Math.abs(2 * l - 1)) * s;
23762
+ const hp = (h % 360 + 360) % 360 / 60;
23763
+ const x = c * (1 - Math.abs(hp % 2 - 1));
23764
+ const m = l - c / 2;
23765
+ const [r, g, b] = hp < 1 ? [c, x, 0] : hp < 2 ? [x, c, 0] : hp < 3 ? [0, c, x] : hp < 4 ? [0, x, c] : hp < 5 ? [x, 0, c] : [c, 0, x];
23766
+ return [r + m, g + m, b + m];
23767
+ }
23768
+ function applyHueSaturationLightness(src, hue, saturation, lightness) {
23769
+ const out = new Uint8ClampedArray(src);
23770
+ const push = (v, t) => t >= 0 ? v + (1 - v) * (t / 100) : v * (1 + t / 100);
23771
+ for (let i = 0; i < src.length; i += 4) {
23772
+ const [h, s, l] = rgbToHsl(src[i] / 255, src[i + 1] / 255, src[i + 2] / 255);
23773
+ const [r, g, b] = hslToRgb(h + hue, push(s, saturation), push(l, lightness));
23774
+ out[i] = Math.round(r * 255);
23775
+ out[i + 1] = Math.round(g * 255);
23776
+ out[i + 2] = Math.round(b * 255);
23777
+ }
23778
+ return out;
23779
+ }
23780
+ var LUMA = [0.299, 0.587, 0.114];
23781
+ function applyTint(src, hue, amount) {
23782
+ const tint = hslToRgb(hue, 1, 0.5);
23783
+ const y = tint[0] * LUMA[0] + tint[1] * LUMA[1] + tint[2] * LUMA[2];
23784
+ const k = amount / 100 * 255;
23785
+ const add = tint.map((c) => (c - y) * k);
23786
+ const out = new Uint8ClampedArray(src);
23787
+ for (let i = 0; i < src.length; i += 4) {
23788
+ out[i] = Math.round(src[i] + add[0]);
23789
+ out[i + 1] = Math.round(src[i + 1] + add[1]);
23790
+ out[i + 2] = Math.round(src[i + 2] + add[2]);
23791
+ }
23792
+ return out;
23793
+ }
23794
+ function applyRedEyeCorrection(src, width, height, areas) {
23795
+ const out = new Uint8ClampedArray(src);
23796
+ for (const a of areas) {
23797
+ const x0 = clamp(a.left, 0, width);
23798
+ const x1 = clamp(a.right, 0, width);
23799
+ const y0 = clamp(a.top, 0, height);
23800
+ const y1 = clamp(a.bottom, 0, height);
23801
+ for (let y = y0; y < y1; y++) {
23802
+ for (let x = x0; x < x1; x++) {
23803
+ const i = (y * width + x) * 4;
23804
+ const r = out[i];
23805
+ const g = out[i + 1];
23806
+ const b = out[i + 2];
23807
+ if (r > g && r > b) {
23808
+ out[i] = Math.round((g + b) / 2);
23809
+ }
23810
+ }
23811
+ }
23812
+ }
23813
+ return out;
23814
+ }
23815
+ function boxRadiiForSigma(sigma) {
23816
+ const target = sigma * sigma;
23817
+ let k = 0;
23818
+ while ((k + 1) * (k + 2) <= target) {
23819
+ k++;
23820
+ }
23821
+ const v = (n) => n * (n + 1) / 3;
23822
+ let best = 0;
23823
+ for (let m = 1; m <= 3; m++) {
23824
+ if (Math.abs((3 - m) * v(k) + m * v(k + 1) - target) < Math.abs((3 - best) * v(k) + best * v(k + 1) - target)) {
23825
+ best = m;
23826
+ }
23827
+ }
23828
+ return [0, 1, 2].map((i) => i < best ? k + 1 : k);
23829
+ }
23830
+ function boxPass(src, w, h, r, horizontal) {
23831
+ if (r <= 0) {
23832
+ return src;
23833
+ }
23834
+ const out = new Float64Array(src.length);
23835
+ const lines = horizontal ? h : w;
23836
+ const len = horizontal ? w : h;
23837
+ const step = horizontal ? 4 : w * 4;
23838
+ const norm = 1 / (2 * r + 1);
23839
+ for (let line = 0; line < lines; line++) {
23840
+ const base = horizontal ? line * w * 4 : line * 4;
23841
+ for (let c = 0; c < 4; c++) {
23842
+ const at = (p) => src[base + clamp(p, 0, len - 1) * step + c];
23843
+ let sum = 0;
23844
+ for (let p = -r; p <= r; p++) {
23845
+ sum += at(p);
23846
+ }
23847
+ for (let p = 0; p < len; p++) {
23848
+ out[base + p * step + c] = sum * norm;
23849
+ sum += at(p + r + 1) - at(p - r);
23850
+ }
23851
+ }
23852
+ }
23853
+ return out;
23854
+ }
23855
+ function blurFloat(src, w, h, radius) {
23856
+ let buf = new Float64Array(src.length);
23857
+ for (let i = 0; i < src.length; i += 4) {
23858
+ const a = src[i + 3] / 255;
23859
+ buf[i] = src[i] * a;
23860
+ buf[i + 1] = src[i + 1] * a;
23861
+ buf[i + 2] = src[i + 2] * a;
23862
+ buf[i + 3] = src[i + 3];
23863
+ }
23864
+ for (const r of boxRadiiForSigma(radius / 2)) {
23865
+ buf = boxPass(buf, w, h, r, true);
23866
+ buf = boxPass(buf, w, h, r, false);
23867
+ }
23868
+ for (let i = 0; i < buf.length; i += 4) {
23869
+ const a = buf[i + 3] / 255;
23870
+ for (let c = 0; c < 3; c++) {
23871
+ buf[i + c] = a > 0 ? buf[i + c] / a : 0;
23872
+ }
23873
+ }
23874
+ return buf;
23875
+ }
23876
+ function applyBlur(src, w, h, radius) {
23877
+ const blurred = blurFloat(src, w, h, radius);
23878
+ const out = new Uint8ClampedArray(src.length);
23879
+ for (let i = 0; i < src.length; i++) {
23880
+ out[i] = Math.round(blurred[i]);
23881
+ }
23882
+ return out;
23883
+ }
23884
+ function applySharpen(src, w, h, radius, amount) {
23885
+ const blurred = blurFloat(src, w, h, radius);
23886
+ const k = amount / 100;
23887
+ const out = new Uint8ClampedArray(src);
23888
+ for (let i = 0; i < src.length; i += 4) {
23889
+ for (let c = 0; c < 3; c++) {
23890
+ out[i + c] = Math.round(src[i + c] + k * (src[i + c] - blurred[i + c]));
23891
+ }
23892
+ }
23893
+ return out;
23894
+ }
23895
+ function applyImageEffect(rgba, width, height, effect) {
23896
+ if (width <= 0 || height <= 0 || rgba.length !== width * height * 4) {
23897
+ return null;
23898
+ }
23899
+ switch (effect.kind) {
23900
+ case "blur":
23901
+ return applyBlur(rgba, width, height, effect.radius);
23902
+ case "sharpen":
23903
+ return applySharpen(rgba, width, height, effect.radius, effect.amount);
23904
+ case "brightnessContrast": {
23905
+ const contrast = contrastCurve(effect.contrast);
23906
+ const lut = buildLut((v) => contrast(v) + effect.brightness);
23907
+ return applyLuts(rgba, lut, lut, lut);
23908
+ }
23909
+ case "colorBalance":
23910
+ return applyLuts(
23911
+ rgba,
23912
+ buildLut(balanceCurve(effect.cyanRed)),
23913
+ buildLut(balanceCurve(effect.magentaGreen)),
23914
+ buildLut(balanceCurve(effect.yellowBlue))
23915
+ );
23916
+ case "colorCurve": {
23917
+ const lut = curveAdjustmentLut(effect.adjustment, effect.intensity);
23918
+ const ch = effect.channel;
23919
+ if (!lut || ch < CurveChannel.All || ch > CurveChannel.Blue) {
23920
+ return null;
23921
+ }
23922
+ const pick = (c) => ch === CurveChannel.All || ch === c ? lut : null;
23923
+ return applyLuts(rgba, pick(CurveChannel.Red), pick(CurveChannel.Green), pick(CurveChannel.Blue));
23924
+ }
23925
+ case "colorLookupTable":
23926
+ return applyLuts(rgba, effect.r, effect.g, effect.b, effect.a);
23927
+ case "colorMatrix":
23928
+ return applyColorMatrix(rgba, effect.matrix);
23929
+ case "hueSaturationLightness":
23930
+ return applyHueSaturationLightness(rgba, effect.hue, effect.saturation, effect.lightness);
23931
+ case "levels": {
23932
+ const lut = levelsLut(effect.highlight, effect.midtone, effect.shadow);
23933
+ return applyLuts(rgba, lut, lut, lut);
23934
+ }
23935
+ case "redEyeCorrection":
23936
+ return applyRedEyeCorrection(rgba, width, height, effect.areas);
23937
+ case "tint":
23938
+ return applyTint(rgba, effect.hue, effect.amount);
23939
+ }
23940
+ }
23941
+
23526
23942
  // src/emf-plus-draw-image.ts
23527
23943
  function surfaceSize4(ctx) {
23528
23944
  const canvas = ctx.canvas;
@@ -23585,8 +24001,8 @@ function svgImagePayload(img, bitmap) {
23585
24001
  }
23586
24002
  return decodeBmpFile(bytes.buffer);
23587
24003
  }
23588
- function paintBitmapSvg(svg, img, bitmap, draw) {
23589
- const payload = svgImagePayload(img, bitmap);
24004
+ function paintBitmapSvg(svg, img, bitmap, draw, pixelsOnly = false) {
24005
+ const payload = pixelsOnly && bitmap ? { kind: "rgba", data: bitmap.rgba, width: bitmap.width, height: bitmap.height } : svgImagePayload(img, bitmap);
23590
24006
  if (!payload) {
23591
24007
  return false;
23592
24008
  }
@@ -23658,14 +24074,32 @@ function drawNestedMetafile(rCtx, img, cached, source) {
23658
24074
  rCtx.deferredImages.push(...deferred);
23659
24075
  return true;
23660
24076
  }
23661
- function drawEmfPlusImageNow(rCtx, img, draw, source) {
24077
+ function effectedBitmap(img, bitmap, effect) {
24078
+ let src = bitmap;
24079
+ if (!src && img.data) {
24080
+ const bmp = decodeBmpFile(img.data.slice(0));
24081
+ if (bmp && bmp.kind === "rgba") {
24082
+ src = { rgba: bmp.data, width: bmp.width, height: bmp.height };
24083
+ }
24084
+ }
24085
+ if (!src) {
24086
+ return null;
24087
+ }
24088
+ const rgba = applyImageEffect(src.rgba, src.width, src.height, effect);
24089
+ return rgba ? { kind: "bitmap", width: src.width, height: src.height, rgba } : null;
24090
+ }
24091
+ function drawEmfPlusImageNow(rCtx, img, draw, source, effect = null) {
23662
24092
  const cached = img.cacheKey !== void 0 ? rCtx.imageCache?.get(img.cacheKey) : void 0;
23663
24093
  if (img.type === 2) {
23664
24094
  return drawNestedMetafile(rCtx, img, cached, source);
23665
24095
  }
23666
- const bitmap = cached && cached.kind === "bitmap" ? cached : null;
24096
+ let bitmap = cached && cached.kind === "bitmap" ? cached : null;
24097
+ const effected = effect ? effectedBitmap(img, bitmap, effect) : null;
24098
+ if (effected) {
24099
+ bitmap = effected;
24100
+ }
23667
24101
  if (isSvgContext(rCtx.ctx)) {
23668
- return paintBitmapSvg(rCtx.ctx, img, bitmap, draw);
24102
+ return paintBitmapSvg(rCtx.ctx, img, bitmap, draw, effected !== null);
23669
24103
  }
23670
24104
  return bitmap ? paintBitmapRaster(rCtx.ctx, bitmap, draw) : false;
23671
24105
  }
@@ -23918,20 +24352,27 @@ function applyPlusPenStyle(ctx, pen, widthScale = 1) {
23918
24352
  }
23919
24353
  function strokePlusGeometry(rCtx, pen, buildPath, points, closedFigure = false) {
23920
24354
  const { ctx } = rCtx;
23921
- pen = applySimilarityPenTransform(pen);
24355
+ const resolved = resolvePenTransform(pen);
24356
+ pen = resolved.pen;
24357
+ const nib = resolved.nib;
23922
24358
  const inset = !!pen && pen.alignment === 1 && closedFigure;
23923
24359
  const widthScale = inset ? 2 : 1;
23924
24360
  const stroke = (c) => {
23925
- if (inset) {
24361
+ if (inset || nib) {
23926
24362
  c.save();
24363
+ }
24364
+ if (inset) {
23927
24365
  c.beginPath();
23928
24366
  buildPath(c);
23929
24367
  c.clip();
23930
24368
  }
23931
24369
  c.beginPath();
23932
24370
  buildPath(c);
24371
+ if (nib) {
24372
+ c.transform(nib[0], nib[1], nib[2], nib[3], 0, 0);
24373
+ }
23933
24374
  c.stroke();
23934
- if (inset) {
24375
+ if (inset || nib) {
23935
24376
  c.restore();
23936
24377
  }
23937
24378
  };
@@ -23946,8 +24387,8 @@ function strokePlusGeometry(rCtx, pen, buildPath, points, closedFigure = false)
23946
24387
  if (sampler) {
23947
24388
  const size = surfaceSize5(ctx);
23948
24389
  const device = plusWorldMatrix(rCtx);
23949
- const scale = Math.max(Math.hypot(device[0], device[1]), Math.hypot(device[2], device[3]));
23950
- if (mode !== "canvas" && size && strokeGdiplus(rCtx, pen, sampler, buildPath, device, size, mode, closedFigure)) {
24390
+ const scale = maxStretch(nib ? mulMatrix(device, nib) : device);
24391
+ if (mode !== "canvas" && size && strokeGdiplus(rCtx, pen, nib, sampler, buildPath, device, size, mode, closedFigure)) {
23951
24392
  return;
23952
24393
  }
23953
24394
  const miter = Math.max(1, ctx.miterLimit || 10);
@@ -23965,6 +24406,9 @@ function strokePlusGeometry(rCtx, pen, buildPath, points, closedFigure = false)
23965
24406
  stroke(c);
23966
24407
  c.beginPath();
23967
24408
  buildPath(c);
24409
+ if (nib) {
24410
+ c.transform(nib[0], nib[1], nib[2], nib[3], 0, 0);
24411
+ }
23968
24412
  },
23969
24413
  mode,
23970
24414
  (c, x, y) => c.isPointInStroke(x, y) && (!inset || c.isPointInPath(x, y))
@@ -23972,71 +24416,105 @@ function strokePlusGeometry(rCtx, pen, buildPath, points, closedFigure = false)
23972
24416
  return;
23973
24417
  }
23974
24418
  }
23975
- if (hasCustomCap(pen) && !pen.transform && fillCustomCapStroke(rCtx, pen, buildPath, closedFigure)) {
24419
+ const paint = pen.brush ? brushPaint2(rCtx, pen.brush) : pen.color;
24420
+ if ((hasCustomCap(pen) || nib && typeof paint !== "string") && fillPenOutline(rCtx, pen, nib, paint, buildPath, closedFigure)) {
23976
24421
  return;
23977
24422
  }
23978
- ctx.strokeStyle = pen.brush ? brushPaint2(rCtx, pen.brush) : pen.color;
24423
+ ctx.strokeStyle = paint;
23979
24424
  }
23980
24425
  applyPlusWorldTransform(rCtx);
23981
24426
  stroke(ctx);
23982
24427
  }
23983
- function applySimilarityPenTransform(pen) {
24428
+ function resolvePenTransform(pen) {
23984
24429
  if (!pen?.transform) {
23985
- return pen;
23986
- }
23987
- if (isIdentity2(pen.transform)) {
23988
- return { ...pen, transform: null };
24430
+ return { pen, nib: null };
23989
24431
  }
23990
24432
  const [a, b, c, d] = pen.transform;
23991
24433
  const sx = Math.hypot(a, b);
23992
24434
  const sy = Math.hypot(c, d);
23993
24435
  const dot = a * c + b * d;
23994
- if (!Number.isFinite(sx + sy + dot) || !(sx > 0) || Math.abs(sx - sy) > 1e-6 * sx || Math.abs(dot) > 1e-6 * sx * sy) {
23995
- return pen;
24436
+ const det = a * d - b * c;
24437
+ const plain = { ...pen, transform: null };
24438
+ if (pen.width === 0 || isIdentity2(pen.transform) || !Number.isFinite(sx + sy + dot + det) || Math.abs(det) < 1e-12) {
24439
+ return { pen: { ...plain, width: pen.width === 0 ? 1 : pen.width }, nib: null };
23996
24440
  }
23997
- return { ...pen, width: pen.width === 0 ? 1 : pen.width * sx, transform: null };
23998
- }
23999
- function strokeGdiplus(rCtx, pen, sampler, buildPath, device, size, mode, closedFigure) {
24000
- if (pen.transform && !isIdentity2(pen.transform)) {
24001
- return false;
24441
+ if (Math.abs(sx - sy) <= 1e-6 * sx && Math.abs(dot) <= 1e-6 * sx * sy) {
24442
+ return { pen: { ...plain, width: pen.width * sx }, nib: null };
24002
24443
  }
24003
- const sx = Math.hypot(device[0], device[1]);
24004
- const sy = Math.hypot(device[2], device[3]);
24005
- const dot = device[0] * device[2] + device[1] * device[3];
24006
- if (!(sx > 0) || Math.abs(sx - sy) > 1e-6 * sx || Math.abs(dot) > 1e-6 * sx * sy) {
24007
- return false;
24008
- }
24009
- let figures;
24010
- try {
24011
- figures = recordDeviceFigures(buildPath, device);
24012
- } catch {
24013
- return false;
24444
+ return { pen: plain, nib: [a, b, c, d, 0, 0] };
24445
+ }
24446
+ function maxStretch(m) {
24447
+ const s = m[0] * m[0] + m[1] * m[1] + m[2] * m[2] + m[3] * m[3];
24448
+ const det = m[0] * m[3] - m[1] * m[2];
24449
+ return Math.sqrt((s + Math.sqrt(Math.max(0, s * s - 4 * det * det))) / 2);
24450
+ }
24451
+ function penFrame(device, nib) {
24452
+ if (!nib) {
24453
+ return { record: device, unit: Math.hypot(device[0], device[1]), toDevice: IDENTITY5 };
24014
24454
  }
24015
- if (closedFigure) {
24016
- figures = figures.map((f) => ({ ...f, closed: true }));
24455
+ const full = mulMatrix(device, nib);
24456
+ const unit = maxStretch(full);
24457
+ const inv2 = invertAffine(nib);
24458
+ if (!inv2 || !(unit > 0) || !Number.isFinite(unit)) {
24459
+ return null;
24017
24460
  }
24018
- const width = (pen.width || 1) * sx;
24019
- const dash = penDashArray(pen).map((v) => v * sx);
24020
- const devicePen = {
24021
- half: width / 2,
24461
+ return {
24462
+ record: [inv2[0] * unit, inv2[1] * unit, inv2[2] * unit, inv2[3] * unit, 0, 0],
24463
+ unit,
24464
+ toDevice: mulMatrix(full, [1 / unit, 0, 0, 1 / unit, 0, 0])
24465
+ };
24466
+ }
24467
+ function framePen(pen, unit) {
24468
+ const dash = penDashArray(pen).map((v) => v * unit);
24469
+ return {
24470
+ half: (pen.width || 1) * unit / 2,
24022
24471
  join: pen.lineJoin ?? 0,
24023
24472
  miterLimit: pen.miterLimit && pen.miterLimit >= 1 ? pen.miterLimit : 10,
24024
24473
  startCap: pen.startCap ?? 0,
24025
24474
  endCap: pen.endCap ?? 0,
24026
24475
  dashCap: pen.dashCap ?? 0,
24027
24476
  dash: dash.length > 0 ? dash : null,
24028
- dashOffset: (pen.dashOffset ?? 0) * (pen.width || 1) * sx,
24477
+ dashOffset: (pen.dashOffset ?? 0) * (pen.width || 1) * unit,
24029
24478
  compound: pen.compound ?? null,
24030
24479
  inset: pen.alignment === 1
24031
24480
  };
24481
+ }
24482
+ function strokeGdiplus(rCtx, pen, nib, sampler, buildPath, device, size, mode, closedFigure) {
24483
+ if (!nib) {
24484
+ const sx = Math.hypot(device[0], device[1]);
24485
+ const sy = Math.hypot(device[2], device[3]);
24486
+ const dot = device[0] * device[2] + device[1] * device[3];
24487
+ if (!(sx > 0) || Math.abs(sx - sy) > 1e-6 * sx || Math.abs(dot) > 1e-6 * sx * sy) {
24488
+ return false;
24489
+ }
24490
+ }
24491
+ const frame2 = penFrame(device, nib);
24492
+ if (!frame2) {
24493
+ return false;
24494
+ }
24495
+ let figures;
24496
+ try {
24497
+ figures = recordDeviceFigures(buildPath, frame2.record);
24498
+ } catch {
24499
+ return false;
24500
+ }
24501
+ if (closedFigure) {
24502
+ figures = figures.map((f) => ({ ...f, closed: true }));
24503
+ }
24504
+ const width = (pen.width || 1) * frame2.unit;
24505
+ const devicePen = framePen(pen, frame2.unit);
24032
24506
  const antialias = mode === "gdiplus-aa";
24507
+ const toFix = (poly) => {
24508
+ const m = frame2.toDevice;
24509
+ return poly.flatMap((p) => [toPlusFix(m[0] * p.x + m[2] * p.y + m[4]), toPlusFix(m[1] * p.x + m[3] * p.y + m[5])]);
24510
+ };
24033
24511
  let fix;
24034
24512
  if (hasCustomCap(pen)) {
24035
- fix = customCapOutline(figures, width, devicePen, pen).map((poly) => poly.flatMap((p) => [toPlusFix(p.x), toPlusFix(p.y)]));
24513
+ fix = customCapOutline(figures, width, devicePen, pen).map(toFix);
24036
24514
  } else if (width <= NOMINAL_PEN_MAX && !devicePen.dash && !(((pen.startCap ?? 0) | (pen.endCap ?? 0) | (pen.dashCap ?? 0)) & 240)) {
24037
24515
  return strokeNominal(rCtx, pen, sampler, buildPath, device, size, antialias, closedFigure);
24038
24516
  } else {
24039
- fix = widenFigures(figures, devicePen).map((poly) => poly.flatMap((p) => [toPlusFix(p.x), toPlusFix(p.y)]));
24517
+ fix = widenFigures(figures, devicePen).map(toFix);
24040
24518
  }
24041
24519
  const box = figuresBox(fix, size);
24042
24520
  if (!box) {
@@ -24099,54 +24577,36 @@ function customCapOutline(figures, width, devicePen, pen) {
24099
24577
  outline.push(...geo.polygons);
24100
24578
  return outline;
24101
24579
  }
24102
- function fillCustomCapStroke(rCtx, pen, buildPath, closedFigure) {
24580
+ function fillPenOutline(rCtx, pen, nib, paint, buildPath, closedFigure) {
24103
24581
  const device = plusWorldMatrix(rCtx);
24104
- const sx = Math.hypot(device[0], device[1]);
24105
- const det = device[0] * device[3] - device[1] * device[2];
24106
- if (!(sx > 0) || !Number.isFinite(det) || Math.abs(det) < 1e-12) {
24582
+ const frame2 = penFrame(device, nib);
24583
+ const deviceInv = invertAffine(device);
24584
+ if (!frame2 || !deviceInv || !(frame2.unit > 0)) {
24107
24585
  return false;
24108
24586
  }
24109
24587
  let figures;
24110
24588
  try {
24111
- figures = recordDeviceFigures(buildPath, device);
24589
+ figures = recordDeviceFigures(buildPath, frame2.record);
24112
24590
  } catch {
24113
24591
  return false;
24114
24592
  }
24115
24593
  if (closedFigure) {
24116
24594
  figures = figures.map((f) => ({ ...f, closed: true }));
24117
24595
  }
24118
- const width = (pen.width || 1) * sx;
24119
- const dash = penDashArray(pen).map((v) => v * sx);
24120
- const devicePen = {
24121
- half: width / 2,
24122
- join: pen.lineJoin ?? 0,
24123
- miterLimit: pen.miterLimit && pen.miterLimit >= 1 ? pen.miterLimit : 10,
24124
- startCap: pen.startCap ?? 0,
24125
- endCap: pen.endCap ?? 0,
24126
- dashCap: pen.dashCap ?? 0,
24127
- dash: dash.length > 0 ? dash : null,
24128
- dashOffset: (pen.dashOffset ?? 0) * (pen.width || 1) * sx,
24129
- compound: pen.compound ?? null,
24130
- inset: pen.alignment === 1
24131
- };
24132
- const inv2 = [
24133
- device[3] / det,
24134
- -device[1] / det,
24135
- -device[2] / det,
24136
- device[0] / det,
24137
- (device[2] * device[5] - device[3] * device[4]) / det,
24138
- (device[1] * device[4] - device[0] * device[5]) / det
24139
- ];
24596
+ const width = (pen.width || 1) * frame2.unit;
24597
+ const devicePen = framePen(pen, frame2.unit);
24598
+ const outline = hasCustomCap(pen) ? customCapOutline(figures, width, devicePen, pen) : widenFigures(figures, devicePen);
24599
+ const toWorld = mulMatrix(deviceInv, frame2.toDevice);
24140
24600
  const { ctx } = rCtx;
24141
24601
  ctx.save();
24142
24602
  try {
24143
- ctx.fillStyle = pen.brush ? brushPaint2(rCtx, pen.brush) : pen.color;
24603
+ ctx.fillStyle = paint;
24144
24604
  applyPlusWorldTransform(rCtx);
24145
24605
  ctx.beginPath();
24146
- for (const poly of customCapOutline(figures, width, devicePen, pen)) {
24606
+ for (const poly of outline) {
24147
24607
  poly.forEach((p, i) => {
24148
- const x = inv2[0] * p.x + inv2[2] * p.y + inv2[4];
24149
- const y = inv2[1] * p.x + inv2[3] * p.y + inv2[5];
24608
+ const x = toWorld[0] * p.x + toWorld[2] * p.y + toWorld[4];
24609
+ const y = toWorld[1] * p.x + toWorld[3] * p.y + toWorld[5];
24150
24610
  if (i === 0) {
24151
24611
  ctx.moveTo(x, y);
24152
24612
  } else {
@@ -24161,6 +24621,7 @@ function fillCustomCapStroke(rCtx, pen, buildPath, closedFigure) {
24161
24621
  }
24162
24622
  return true;
24163
24623
  }
24624
+ var IDENTITY5 = [1, 0, 0, 1, 0, 0];
24164
24625
  function isIdentity2(m) {
24165
24626
  return m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0;
24166
24627
  }
@@ -24213,7 +24674,7 @@ function imageResampleSpec(rCtx, dataOff, isMetafile, toWorld) {
24213
24674
  ...imageAttributesWrap(rCtx, view.getUint32(dataOff, true))
24214
24675
  };
24215
24676
  }
24216
- function drawOrDeferImage(rCtx, imgObj, dataOff, dx, dy, dw, dh, toWorld) {
24677
+ function drawOrDeferImage(rCtx, imgObj, dataOff, dx, dy, dw, dh, toWorld, effect = null) {
24217
24678
  if (!imgObj.data) {
24218
24679
  return;
24219
24680
  }
@@ -24237,7 +24698,7 @@ function drawOrDeferImage(rCtx, imgObj, dataOff, dx, dy, dw, dh, toWorld) {
24237
24698
  srcH: view.getFloat32(dataOff + 20, true),
24238
24699
  toWorld
24239
24700
  };
24240
- if (drawEmfPlusImageNow(rCtx, imgObj, draw, source)) {
24701
+ if (drawEmfPlusImageNow(rCtx, imgObj, draw, source, effect)) {
24241
24702
  return;
24242
24703
  }
24243
24704
  if (isSvgContext(rCtx.ctx)) {
@@ -24601,6 +25062,10 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
24601
25062
  const imgObj = objectTable.get(imgId);
24602
25063
  const count = view.getUint32(dataOff + 24, true);
24603
25064
  const ptOff = dataOff + 28;
25065
+ const effect = (recFlags & DRAWIMAGE_EFFECT_FLAG) !== 0 ? rCtx.ext?.pendingEffect ?? null : null;
25066
+ if (rCtx.ext) {
25067
+ rCtx.ext.pendingEffect = null;
25068
+ }
24604
25069
  if (count >= 3 && imgObj && imgObj.kind === "plus-image" && imgObj.data) {
24605
25070
  const pts = readPlusPoints(view, ptOff, dataOff + recDataSize, 3, recFlags);
24606
25071
  if (!pts) {
@@ -24624,7 +25089,7 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
24624
25089
  const c = (p3x - p1x) / sh;
24625
25090
  const d = (p3y - p1y) / sh;
24626
25091
  return [a, b, c, d, p1x - a * sx - c * sy, p1y - b * sx - d * sy];
24627
- });
25092
+ }, effect);
24628
25093
  } else {
24629
25094
  imgObj && imgObj.kind === "plus-image" && imgObj.data;
24630
25095
  }
@@ -25454,12 +25919,14 @@ function replayEmfPlusRecords(view, offset, length, ctx, canvasW, canvasH, state
25454
25919
  recordCount++;
25455
25920
  emfPlusRecordTypes.set(recType, (emfPlusRecordTypes.get(recType) ?? 0) + 1);
25456
25921
  const dataOff = offset + 12;
25922
+ s.gdiPassthrough = recType === EMFPLUS_GETDC;
25457
25923
  if (rCtx.ext?.multiFormatSkip && recType !== EMFPLUS_ENDOFFILE) {
25458
25924
  offset += recSize;
25459
25925
  continue;
25460
25926
  }
25461
25927
  switch (recType) {
25462
25928
  case EMFPLUS_HEADER: {
25929
+ s.dualMode = (recFlags & 1) !== 0;
25463
25930
  if (recDataSize >= 16) {
25464
25931
  view.getFloat32(dataOff + 8, true);
25465
25932
  view.getFloat32(dataOff + 12, true);
@@ -25478,6 +25945,12 @@ function replayEmfPlusRecords(view, offset, length, ctx, canvasW, canvasH, state
25478
25945
  case EMFPLUS_MULTIFORMATSECTION:
25479
25946
  case EMFPLUS_MULTIFORMATEND:
25480
25947
  break;
25948
+ // An image effect for the next DrawImagePoints with flag E
25949
+ // (`emf-plus-image-effects.ts`); an unknown or malformed effect
25950
+ // replaces any earlier one with none.
25951
+ case EMFPLUS_SERIALIZABLEOBJECT:
25952
+ (rCtx.ext ?? (rCtx.ext = {})).pendingEffect = parseSerializableObject(view, dataOff, recDataSize);
25953
+ break;
25481
25954
  case EMFPLUS_CLEAR: {
25482
25955
  if (recDataSize >= 4) {
25483
25956
  const argb = view.getUint32(dataOff, true);
@@ -25624,11 +26097,16 @@ function replayEmfRecords(view, ctx, bounds, canvasW, canvasH, dpiScale = 1, rep
25624
26097
  state: { ...defaultState(), fontFamilyMap: replayOptions.fontFamilyMap },
25625
26098
  stateStack: [],
25626
26099
  inPath: false,
25627
- windowOrg: { x: bounds.left, y: bounds.top },
26100
+ // GDI's defaults: both origins at 0 and a 1:1 window/viewport ratio.
26101
+ // The window/viewport mapping yields device units, placed on the
26102
+ // canvas like every other device coordinate (bounds' top-left to the
26103
+ // canvas origin, scaled by sx/sy); see `deviceToCanvas`.
26104
+ windowOrg: { x: 0, y: 0 },
25628
26105
  windowExt: { cx: logicalW, cy: logicalH },
25629
26106
  viewportOrg: { x: 0, y: 0 },
25630
- viewportExt: { cx: canvasW, cy: canvasH },
26107
+ viewportExt: { cx: logicalW, cy: logicalH },
25631
26108
  useMappingMode: false,
26109
+ deviceToCanvas: true,
25632
26110
  clipSaveDepth: 0,
25633
26111
  bounds,
25634
26112
  canvasW,
@@ -25715,6 +26193,13 @@ function replayEmfRecords(view, ctx, bounds, canvasW, canvasH, dpiScale = 1, rep
25715
26193
  offset += recSize;
25716
26194
  continue;
25717
26195
  }
26196
+ if (emfPlusState.dualMode && !emfPlusState.gdiPassthrough) {
26197
+ if (recType !== EMR_SAVEDC && recType !== EMR_RESTOREDC) {
26198
+ handleEmfGdiStateRecord(rCtx, recType, offset, dataOff, recSize);
26199
+ }
26200
+ offset += recSize;
26201
+ continue;
26202
+ }
25718
26203
  const handled = handleEmfGdiStateRecord(rCtx, recType, offset, dataOff, recSize) || handleEmfGdiDrawRecord(rCtx, recType, offset, dataOff, recSize) || handleEmfGdiPolyPathRecord(rCtx, recType, offset, dataOff, recSize);
25719
26204
  if (!handled) {
25720
26205
  console.warn(`[emf-converter] Unhandled EMR record type: ${recType}`);