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