emf-converter 4.0.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) {
@@ -491,6 +491,23 @@ function totalGlyphAdvance(dx) {
491
491
  }
492
492
  return sum;
493
493
  }
494
+ function applyTextJustification(advances, characters, extra, count) {
495
+ const result = advances.slice();
496
+ if (extra === 0 || count <= 0) {
497
+ return result;
498
+ }
499
+ let seen = 0;
500
+ for (let i = 0; i < result.length && i < characters.length; i++) {
501
+ if (characters[i] !== 32) {
502
+ continue;
503
+ }
504
+ const before = Math.round(seen * extra / count);
505
+ seen++;
506
+ const after = Math.round(seen * extra / count);
507
+ result[i] += after - before;
508
+ }
509
+ return result;
510
+ }
494
511
  function alignmentStartOffset(totalWidth, align) {
495
512
  if (align === "center") {
496
513
  return -totalWidth / 2;
@@ -627,8 +644,11 @@ var EMR_PAINTRGN = 74;
627
644
  var EMR_MASKBLT = 78;
628
645
  var EMR_PLGBLT = 79;
629
646
  var EMR_SETDIBITSTODEVICE = 80;
647
+ var EMR_EXTTEXTOUTA = 83;
630
648
  var EMR_POLYPOLYLINE16 = 90;
631
649
  var EMR_POLYDRAW16 = 92;
650
+ var EMR_POLYTEXTOUTA = 96;
651
+ var EMR_POLYTEXTOUTW = 97;
632
652
  var EMR_CREATECOLORSPACE = 99;
633
653
  var EMR_SETCOLORSPACE = 100;
634
654
  var EMR_DELETECOLORSPACE = 101;
@@ -637,6 +657,7 @@ var EMR_GLSBOUNDEDRECORD = 103;
637
657
  var EMR_PIXELFORMAT = 104;
638
658
  var EMR_DRAWESCAPE = 105;
639
659
  var EMR_EXTESCAPE = 106;
660
+ var EMR_SMALLTEXTOUT = 108;
640
661
  var EMR_FORCEUFIMAPPING = 109;
641
662
  var EMR_NAMEDESCAPE = 110;
642
663
  var EMR_COLORCORRECTPALETTE = 111;
@@ -707,6 +728,7 @@ var EMFPLUS_SETRENDERINGORIGIN = 16413;
707
728
  var EMFPLUS_SETTEXTCONTRAST = 16416;
708
729
  var EMFPLUS_SETCOMPOSITINGMODE = 16419;
709
730
  var EMFPLUS_STROKEFILLPATH = 16439;
731
+ var EMFPLUS_SERIALIZABLEOBJECT = 16440;
710
732
  var EMFPLUS_SETTSGRAPHICS = 16441;
711
733
  var EMFPLUS_SETTSCLIP = 16442;
712
734
  var EMFPLUS_OBJECTTYPE_BRUSH = 1;
@@ -13100,7 +13122,7 @@ function parseEmfPlusPenObject(view, dataOff, recDataSize, textureCache, cacheKe
13100
13122
  o += 4;
13101
13123
  return v;
13102
13124
  };
13103
- const pen = { kind: "plus-pen", color: "rgba(0,0,0,1)", width: penWidth || 1, dashStyle: 0 };
13125
+ const pen = { kind: "plus-pen", color: "rgba(0,0,0,1)", width: penWidth, dashStyle: 0 };
13104
13126
  if (penFlags & PEN_TRANSFORM) {
13105
13127
  if (o + 24 <= end) {
13106
13128
  pen.transform = [0, 4, 8, 12, 16, 20].map((k) => view.getFloat32(o + k, true));
@@ -13717,7 +13739,8 @@ function gmx(r, x) {
13717
13739
  const wt = r.state.worldTransform;
13718
13740
  const px = wt[0] * x + wt[4];
13719
13741
  if (r.useMappingMode) {
13720
- 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;
13721
13744
  }
13722
13745
  return (px - r.bounds.left) * r.sx;
13723
13746
  }
@@ -13725,24 +13748,31 @@ function gmy(r, y) {
13725
13748
  const wt = r.state.worldTransform;
13726
13749
  const py = wt[3] * y + wt[5];
13727
13750
  if (r.useMappingMode) {
13728
- 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;
13729
13753
  }
13730
13754
  return (py - r.bounds.top) * r.sy;
13731
13755
  }
13732
13756
  function gmw(r, w) {
13733
13757
  const pw = r.state.worldTransform[0] * w;
13734
13758
  if (r.useMappingMode) {
13735
- 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);
13736
13760
  }
13737
13761
  return pw * r.sx;
13738
13762
  }
13739
13763
  function gmh(r, h) {
13740
13764
  const ph = r.state.worldTransform[3] * h;
13741
13765
  if (r.useMappingMode) {
13742
- 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);
13743
13767
  }
13744
13768
  return ph * r.sy;
13745
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
+ }
13746
13776
  function activateGdiMappingMode(r) {
13747
13777
  r.useMappingMode = true;
13748
13778
  }
@@ -13755,13 +13785,14 @@ function gdiDeviceMatrix(r) {
13755
13785
  if (r.useMappingMode) {
13756
13786
  const kx = (r.viewportExt.cx || 1) / (r.windowExt.cx || 1);
13757
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];
13758
13789
  return [
13759
- wt[0] * kx,
13760
- wt[1] * ky,
13761
- wt[2] * kx,
13762
- wt[3] * ky,
13763
- (wt[4] - r.windowOrg.x) * kx + r.viewportOrg.x,
13764
- (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
13765
13796
  ];
13766
13797
  }
13767
13798
  const sx = r.sx || 1;
@@ -14936,19 +14967,7 @@ var SvgContext = class {
14936
14967
  }
14937
14968
  }
14938
14969
  // ---- text ---------------------------------------------------------------
14939
- measurer() {
14940
- if (this.measureCtx === void 0) {
14941
- const temp = createTempCanvas(1, 1);
14942
- this.measureCtx = temp && !isSoftwareRaster(temp.canvas) ? temp.ctx : null;
14943
- }
14944
- return this.measureCtx;
14945
- }
14946
14970
  measureText(text) {
14947
- const real = this.shadow ?? this.measurer();
14948
- if (real) {
14949
- real.font = this.state.font;
14950
- return real.measureText(text);
14951
- }
14952
14971
  const size = parseFont(this.state.font).size;
14953
14972
  return { width: estimateTextWidth(text, size) };
14954
14973
  }
@@ -15648,8 +15667,8 @@ function paintWithRop2PerPixel(ctx, rop2, color, draw, pad = 2, fillRule) {
15648
15667
  sc.restore();
15649
15668
  }
15650
15669
  const pointTest = fillRule !== void 0 && typeof sc.isPointInPath === "function";
15651
- const hex4 = /^#([0-9a-f]{6})$/i.exec(color.trim());
15652
- 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;
15653
15672
  const paint = canvasGetImageData(sc, 0, 0, box.w, box.h);
15654
15673
  const pd = paint.data;
15655
15674
  const dd = index === 240 ? null : canvasGetImageData(ctx, box.x, box.y, box.w, box.h).data;
@@ -17948,7 +17967,7 @@ function writeTextureColor(inv2, width, height, rgba, wrap, halfPixel2, dx, dy,
17948
17967
  const iv = Math.floor(cv + 1e-9);
17949
17968
  const fu = Math.max(0, cu - iu);
17950
17969
  const fv = Math.max(0, cv - iv);
17951
- const clamp = wrap === "clamp";
17970
+ const clamp2 = wrap === "clamp";
17952
17971
  const mirrorX = wrap === "tile-flip-x" || wrap === "tile-flip-xy";
17953
17972
  const mirrorY = wrap === "tile-flip-y" || wrap === "tile-flip-xy";
17954
17973
  let r = 0;
@@ -17964,7 +17983,7 @@ function writeTextureColor(inv2, width, height, rgba, wrap, halfPixel2, dx, dy,
17964
17983
  }
17965
17984
  let x;
17966
17985
  let y;
17967
- if (clamp) {
17986
+ if (clamp2) {
17968
17987
  if (tu < 0 || tv < 0 || tu >= width || tv >= height) {
17969
17988
  continue;
17970
17989
  }
@@ -20346,8 +20365,8 @@ function paintParallelogram(rCtx, a, b, q, sw, sh, pixel) {
20346
20365
  });
20347
20366
  }
20348
20367
  function alignMirroredDest(rCtx, req) {
20349
- const pxX = rCtx.useMappingMode ? 1 : rCtx.sx;
20350
- const pxY = rCtx.useMappingMode ? 1 : rCtx.sy;
20368
+ const pxX = gdiDevicePixelX(rCtx);
20369
+ const pxY = gdiDevicePixelY(rCtx);
20351
20370
  return {
20352
20371
  ...req,
20353
20372
  dx: req.dw < 0 ? req.dx + pxX : req.dx,
@@ -20582,10 +20601,10 @@ function axisMap(rCtx, xDest, yDest, cxDest, cyDest, sx, sy, sw, sh) {
20582
20601
  const dw = gmw(rCtx, cxDest);
20583
20602
  const dh = gmh(rCtx, cyDest);
20584
20603
  if (dw < 0) {
20585
- dx += rCtx.useMappingMode ? 1 : rCtx.sx;
20604
+ dx += gdiDevicePixelX(rCtx);
20586
20605
  }
20587
20606
  if (dh < 0) {
20588
- dy += rCtx.useMappingMode ? 1 : rCtx.sy;
20607
+ dy += gdiDevicePixelY(rCtx);
20589
20608
  }
20590
20609
  const W = Math.round(Math.abs(dw));
20591
20610
  const H = Math.round(Math.abs(dh));
@@ -20953,10 +20972,109 @@ function handleEmfGdiBlendBlitRecord(rCtx, recType, offset, dataOff, recSize) {
20953
20972
  }
20954
20973
  }
20955
20974
 
20975
+ // src/emf-ansi.ts
20976
+ var CP1252_HIGH2 = [
20977
+ 8364,
20978
+ 129,
20979
+ 8218,
20980
+ 402,
20981
+ 8222,
20982
+ 8230,
20983
+ 8224,
20984
+ 8225,
20985
+ 710,
20986
+ 8240,
20987
+ 352,
20988
+ 8249,
20989
+ 338,
20990
+ 141,
20991
+ 381,
20992
+ 143,
20993
+ 144,
20994
+ 8216,
20995
+ 8217,
20996
+ 8220,
20997
+ 8221,
20998
+ 8226,
20999
+ 8211,
21000
+ 8212,
21001
+ 732,
21002
+ 8482,
21003
+ 353,
21004
+ 8250,
21005
+ 339,
21006
+ 157,
21007
+ 382,
21008
+ 376
21009
+ ];
21010
+ function ansiToCode(b, charSet) {
21011
+ return charSet === 2 ? b : b >= 128 && b <= 159 ? CP1252_HIGH2[b - 128] : b;
21012
+ }
21013
+ var CHARSET_ENCODING = {
21014
+ 0: "windows-1252",
21015
+ 1: "windows-1252",
21016
+ 128: "shift_jis",
21017
+ 129: "euc-kr",
21018
+ 130: "johab",
21019
+ 134: "gbk",
21020
+ 136: "big5",
21021
+ 161: "windows-1253",
21022
+ 162: "windows-1254",
21023
+ 163: "windows-1258",
21024
+ 177: "windows-1255",
21025
+ 178: "windows-1256",
21026
+ 186: "windows-1257",
21027
+ 204: "windows-1251",
21028
+ 222: "windows-874",
21029
+ 238: "windows-1250",
21030
+ 255: "ibm437"
21031
+ };
21032
+ function decodeAnsiRecord(bytes, charSet) {
21033
+ if (charSet === 2) {
21034
+ return { codes: [...bytes], byteLengths: bytes.map(() => 1) };
21035
+ }
21036
+ const label = CHARSET_ENCODING[charSet] ?? "windows-1252";
21037
+ try {
21038
+ const decoder = new TextDecoder(label, { fatal: true });
21039
+ const replacementDecoder = new TextDecoder(label);
21040
+ const source = new Uint8Array(bytes);
21041
+ const codes = [];
21042
+ const byteLengths = [];
21043
+ let i = 0;
21044
+ while (i < bytes.length) {
21045
+ let decoded = null;
21046
+ let consumed = 1;
21047
+ for (let n = 1; n <= Math.min(4, bytes.length - i); n++) {
21048
+ try {
21049
+ const candidate = decoder.decode(source.subarray(i, i + n));
21050
+ if (candidate.length > 0) {
21051
+ decoded = candidate;
21052
+ consumed = n;
21053
+ break;
21054
+ }
21055
+ } catch {
21056
+ }
21057
+ }
21058
+ if (decoded === null) {
21059
+ decoded = replacementDecoder.decode(source.subarray(i, i + 1));
21060
+ }
21061
+ for (let j = 0; j < decoded.length; j++) {
21062
+ codes.push(decoded.charCodeAt(j));
21063
+ byteLengths.push(j === decoded.length - 1 ? consumed : 0);
21064
+ }
21065
+ i += consumed;
21066
+ }
21067
+ return { codes, byteLengths };
21068
+ } catch {
21069
+ return { codes: bytes.map((b) => ansiToCode(b, charSet)), byteLengths: bytes.map(() => 1) };
21070
+ }
21071
+ }
21072
+
20956
21073
  // src/gdi-text-render.ts
20957
21074
  var ETO_OPAQUE = 2;
20958
21075
  var ETO_CLIPPED = 4;
20959
21076
  var ETO_GLYPH_INDEX = 16;
21077
+ var ETO_IGNORELANGUAGE = 4096;
20960
21078
  var ETO_PDY = 8192;
20961
21079
  var GRAY_INV_GAMMA = 0.43;
20962
21080
  var GRAY_BLACK_ON_WHITE = [255, 255, 240, 233, 225, 217, 208, 199, 189, 178, 167, 154, 140, 124, 104, 77, 0];
@@ -21037,15 +21155,24 @@ function fillDeviceQuad(ctx, pts, color, clip) {
21037
21155
  ctx.fill();
21038
21156
  ctx.restore();
21039
21157
  }
21158
+ function isC1Control(code) {
21159
+ return code >= 128 && code <= 159;
21160
+ }
21161
+ function suppressC1Glyph(run, index) {
21162
+ return !run.glyphIndices && !(run.options & ETO_IGNORELANGUAGE) && isC1Control(run.codes[index]);
21163
+ }
21040
21164
  function layoutGdiRun(font, run) {
21041
21165
  const n = run.codes.length;
21042
21166
  const glyphs = run.codes.map((c) => run.glyphIndices ? c : font.glyphIndex(c));
21043
- const adv = [];
21167
+ let adv = [];
21044
21168
  for (let i = 0; i < n; i++) {
21045
21169
  adv.push(
21046
- run.dx && i < run.dx.length ? run.dx[i] : run.matrix ? font.rotatedAdvance(glyphs[i]) : font.advance(glyphs[i])
21170
+ suppressC1Glyph(run, i) && !run.dx ? 0 : run.dx && i < run.dx.length ? run.dx[i] : run.matrix ? font.rotatedAdvance(glyphs[i]) : font.advance(glyphs[i])
21047
21171
  );
21048
21172
  }
21173
+ if (!run.dx && run.textJustification && !run.glyphIndices) {
21174
+ adv = applyTextJustification(adv, run.codes, run.textJustification.extra, run.textJustification.count);
21175
+ }
21049
21176
  const advY = [];
21050
21177
  for (let i = 0; i < n; i++) {
21051
21178
  advY.push(run.dy && i < run.dy.length ? run.dy[i] : 0);
@@ -21088,9 +21215,11 @@ function layoutGdiRun(font, run) {
21088
21215
  const subX = font.gridFit ? 0 : Math.round((origin.x - ox) * 64);
21089
21216
  const oy = Math.round(origin.y);
21090
21217
  origins.push({ x: ox + subX / 64, y: oy, along, down });
21091
- const g = font.glyph(glyphs[i], gm, subX);
21092
- if (g.bitmap) {
21093
- placed.push({ x: ox + g.bitmap.left, y: oy - g.bitmap.top, bitmap: g.bitmap });
21218
+ if (!suppressC1Glyph(run, i)) {
21219
+ const g = font.glyph(glyphs[i], gm, subX);
21220
+ if (g.bitmap) {
21221
+ placed.push({ x: ox + g.bitmap.left, y: oy - g.bitmap.top, bitmap: g.bitmap });
21222
+ }
21094
21223
  }
21095
21224
  along += adv[i];
21096
21225
  down += advY[i];
@@ -21175,21 +21304,25 @@ function paintGdiTextRun(ctx, font, run, fontFamilyMap) {
21175
21304
  }
21176
21305
  function emitSvgRun(ctx, font, run, glyphs, origins, fontFamilyMap) {
21177
21306
  let text = "";
21307
+ const visibleOrigins = [];
21178
21308
  for (let i = 0; i < origins.length; i++) {
21179
21309
  const code = run.glyphIndices ? font.charForGlyph(glyphs[i]) : run.codes[i];
21310
+ if (!run.glyphIndices && !(run.options & ETO_IGNORELANGUAGE) && isC1Control(code)) continue;
21180
21311
  text += String.fromCharCode(code);
21312
+ visibleOrigins.push(origins[i]);
21181
21313
  }
21314
+ if (text.length === 0) return;
21182
21315
  const m = run.matrix;
21183
21316
  const ttf = font.ttf;
21184
21317
  const bold = font.syntheticBold || ttf.weightClass >= 600;
21185
21318
  const italic = font.syntheticItalic || (ttf.fsSelection & 1) !== 0 || (ttf.macStyle & 2) !== 0;
21186
- const first = origins[0];
21319
+ const first = visibleOrigins[0];
21187
21320
  ctx.fillGlyphRun({
21188
21321
  text,
21189
21322
  // Upright runs: device positions. Rotated runs: offsets along/across
21190
21323
  // the baseline from the first glyph, under the frame matrix.
21191
- xs: origins.map((o) => m ? o.along - first.along : o.x),
21192
- ys: origins.map((o) => m ? o.down - first.down : o.y),
21324
+ xs: visibleOrigins.map((o) => m ? o.along - first.along : o.x),
21325
+ ys: visibleOrigins.map((o) => m ? o.down - first.down : o.y),
21193
21326
  matrix: m ? [m[0], m[1], m[2], m[3], first.x, first.y] : null,
21194
21327
  scaleX: font.ppemX !== font.ppem ? font.ppemX / font.ppem : 1,
21195
21328
  fontFamily: mapFontFamily(ttf.family, fontFamilyMap),
@@ -21360,6 +21493,7 @@ function drawGdiTextCall(ctx, fonts, state, call) {
21360
21493
  y: ref.y,
21361
21494
  dx: call.dx ? call.dx.map((v) => v * advanceScale) : null,
21362
21495
  dy: call.dy ? call.dy.map((v) => -v * fontScale) : null,
21496
+ textJustification: call.textJustification ? { ...call.textJustification, extra: call.textJustification.extra * advanceScale } : void 0,
21363
21497
  textAlign: state.textAlign,
21364
21498
  textColor: state.textColor,
21365
21499
  bkColor: state.bkColor,
@@ -21496,11 +21630,11 @@ function readDxArray(view, offset, dataOff, nChars, viewEnd) {
21496
21630
  }
21497
21631
  const dx = [];
21498
21632
  for (let i = 0; i < nChars; i++) {
21499
- dx.push(view.getUint32(start + i * 4, true));
21633
+ dx.push(view.getInt32(start + i * 4, true));
21500
21634
  }
21501
21635
  return dx;
21502
21636
  }
21503
- function drawWithFontEngine(rCtx, offset, dataOff, nChars, offString) {
21637
+ function drawWithFontEngine(rCtx, offset, dataOff, nChars, offString, viewEnd) {
21504
21638
  const { ctx, view, state } = rCtx;
21505
21639
  const fonts = rCtx.fonts;
21506
21640
  if (!fonts) {
@@ -21512,7 +21646,7 @@ function drawWithFontEngine(rCtx, offset, dataOff, nChars, offString) {
21512
21646
  codes.push(view.getUint16(offset + offString + i * 2, true));
21513
21647
  }
21514
21648
  const pdy = (options & ETO_PDY) !== 0;
21515
- const raw = readDxArray(view, offset, dataOff, pdy ? nChars * 2 : nChars, view.byteLength);
21649
+ const raw = readDxArray(view, offset, dataOff, pdy ? nChars * 2 : nChars, viewEnd);
21516
21650
  let dx = raw;
21517
21651
  let dy = null;
21518
21652
  if (raw && pdy) {
@@ -21537,7 +21671,8 @@ function drawWithFontEngine(rCtx, offset, dataOff, nChars, offString) {
21537
21671
  },
21538
21672
  dx,
21539
21673
  dy,
21540
- matrix: gdiDeviceMatrix(rCtx)
21674
+ matrix: gdiDeviceMatrix(rCtx),
21675
+ textJustification: state.textJustification
21541
21676
  });
21542
21677
  if (!adv) {
21543
21678
  return false;
@@ -21549,12 +21684,12 @@ function drawWithFontEngine(rCtx, offset, dataOff, nChars, offString) {
21549
21684
  return true;
21550
21685
  }
21551
21686
  function horizontalAlign(textAlign) {
21687
+ if ((textAlign & 6) === 6) {
21688
+ return "center";
21689
+ }
21552
21690
  if (textAlign & 2) {
21553
21691
  return "right";
21554
21692
  }
21555
- if (textAlign & 6) {
21556
- return "center";
21557
- }
21558
21693
  return "left";
21559
21694
  }
21560
21695
  function verticalBaseline(textAlign) {
@@ -21596,11 +21731,11 @@ function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
21596
21731
  const refY = view.getInt32(dataOff + 32, true);
21597
21732
  const nChars = view.getUint32(dataOff + 36, true);
21598
21733
  const offString = view.getUint32(dataOff + 40, true);
21599
- const viewEnd = view.byteLength;
21600
- if (nChars === 0 || offString === 0 || offset + offString + nChars * 2 > viewEnd) {
21734
+ const viewEnd = Math.min(view.byteLength, offset + recSize);
21735
+ if (nChars === 0 || offString === 0 || nChars > Math.floor((viewEnd - offset - offString) / 2)) {
21601
21736
  return true;
21602
21737
  }
21603
- if (drawWithFontEngine(rCtx, offset, dataOff, nChars, offString)) {
21738
+ if (drawWithFontEngine(rCtx, offset, dataOff, nChars, offString, viewEnd)) {
21604
21739
  return true;
21605
21740
  }
21606
21741
  const text = readUtf16LE(view, offset + offString, nChars);
@@ -21615,8 +21750,16 @@ function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
21615
21750
  const align = horizontalAlign(state.textAlign);
21616
21751
  ctx.textBaseline = verticalBaseline(state.textAlign);
21617
21752
  ctx.textAlign = align === "center" ? "center" : align === "right" ? "right" : "left";
21618
- const dxLogical = readDxArray(view, offset, dataOff, nChars, viewEnd);
21619
- const dxDevice = dxLogical ? dxLogical.map((v) => advanceScale !== null ? v * advanceScale : gmw(rCtx, v)) : null;
21753
+ const pdy = (view.getUint32(dataOff + 44, true) & ETO_PDY) !== 0;
21754
+ const dxRaw = readDxArray(view, offset, dataOff, pdy ? nChars * 2 : nChars, viewEnd);
21755
+ const dxLogical = dxRaw && pdy ? dxRaw.filter((_, i) => i % 2 === 0) : dxRaw;
21756
+ let dxDevice = dxLogical ? dxLogical.map((v) => advanceScale !== null ? v * advanceScale : gmw(rCtx, v)) : null;
21757
+ if (!dxDevice && state.textJustification && !pdy) {
21758
+ const units = text.split("");
21759
+ const natural = units.map((ch) => ctx.measureText(ch).width);
21760
+ const scale = advanceScale ?? gmw(rCtx, 1);
21761
+ dxDevice = applyTextJustification(natural, units.map((ch) => ch.charCodeAt(0)), state.textJustification.extra * scale, state.textJustification.count);
21762
+ }
21620
21763
  if (dxDevice) {
21621
21764
  ctx.textAlign = "left";
21622
21765
  }
@@ -21649,10 +21792,118 @@ function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
21649
21792
  }
21650
21793
  return true;
21651
21794
  }
21795
+ function drawTextCall(rCtx, x, y, codes, options, rect, dx) {
21796
+ const n = codes.length;
21797
+ const offString = 76;
21798
+ const offDx = offString + n * 2 + 3 & -4;
21799
+ const dxCount = dx ? dx.length : 0;
21800
+ const size = offDx + dxCount * 4;
21801
+ const view = new DataView(new ArrayBuffer(size));
21802
+ view.setUint32(44, n, true);
21803
+ view.setUint32(48, offString, true);
21804
+ view.setUint32(52, options, true);
21805
+ view.setInt32(36, x, true);
21806
+ view.setInt32(40, y, true);
21807
+ view.setInt32(56, rect[0], true);
21808
+ view.setInt32(60, rect[1], true);
21809
+ view.setInt32(64, rect[2], true);
21810
+ view.setInt32(68, rect[3], true);
21811
+ view.setUint32(72, dx ? offDx : 0, true);
21812
+ for (let i = 0; i < n; i++) view.setUint16(offString + i * 2, codes[i], true);
21813
+ if (dx) for (let i = 0; i < dxCount; i++) view.setInt32(offDx + i * 4, dx[i], true);
21814
+ const child = { ...rCtx, view };
21815
+ handleExtTextOutW(child, 0, 8, size);
21816
+ }
21817
+ function readAnsiRecord(view, start, count, charSet) {
21818
+ return decodeAnsiRecord(Array.from({ length: count }, (_, i) => view.getUint8(start + i)), charSet);
21819
+ }
21820
+ function collapseAnsiDx(dx, byteLengths, pdy) {
21821
+ if (!dx) return null;
21822
+ const result = [];
21823
+ let at = 0;
21824
+ for (const bytes of byteLengths) {
21825
+ let x = 0, y = 0;
21826
+ for (let i = 0; i < bytes; i++) {
21827
+ x += dx[at + i * (pdy ? 2 : 1)] ?? 0;
21828
+ if (pdy) y += dx[at + i * 2 + 1] ?? 0;
21829
+ }
21830
+ result.push(x);
21831
+ if (pdy) result.push(y);
21832
+ at += bytes * (pdy ? 2 : 1);
21833
+ }
21834
+ return result;
21835
+ }
21836
+ function handlePolyText(rCtx, offset, dataOff, recSize, wide) {
21837
+ const { view, state } = rCtx;
21838
+ const end = Math.min(view.byteLength, offset + recSize);
21839
+ if (recSize < 40) return true;
21840
+ const count = view.getUint32(dataOff + 28, true);
21841
+ const arrayStart = dataOff + 32;
21842
+ if (count > Math.floor((end - arrayStart) / 40)) return true;
21843
+ const charSet = state.fontDetails?.charSet ?? 1;
21844
+ for (let i = 0; i < count; i++) {
21845
+ const e = arrayStart + i * 40;
21846
+ const x = view.getInt32(e, true), y = view.getInt32(e + 4, true);
21847
+ const n = view.getUint32(e + 8, true), offString = view.getUint32(e + 12, true);
21848
+ const options = view.getUint32(e + 16, true);
21849
+ const rect = [view.getInt32(e + 20, true), view.getInt32(e + 24, true), view.getInt32(e + 28, true), view.getInt32(e + 32, true)];
21850
+ const offDx = view.getUint32(e + 36, true);
21851
+ const unit = wide ? 2 : 1;
21852
+ if (!offString || offString > end - offset || n > Math.floor((end - (offset + offString)) / unit)) continue;
21853
+ const ansi = wide ? null : readAnsiRecord(view, offset + offString, n, charSet);
21854
+ const codes = wide ? Array.from({ length: n }, (_, j) => view.getUint16(offset + offString + j * 2, true)) : ansi.codes;
21855
+ let dx = null;
21856
+ const pdy = (options & ETO_PDY) !== 0;
21857
+ const dxCount = n * (pdy ? 2 : 1);
21858
+ if (offDx && offDx <= end - offset && dxCount <= Math.floor((end - (offset + offDx)) / 4)) {
21859
+ dx = Array.from({ length: dxCount }, (_, j) => view.getInt32(offset + offDx + j * 4, true));
21860
+ if (!wide) dx = collapseAnsiDx(dx, ansi.byteLengths, pdy);
21861
+ }
21862
+ drawTextCall(rCtx, x, y, codes, options, rect, dx);
21863
+ }
21864
+ return true;
21865
+ }
21866
+ function handleSmallTextOut(rCtx, offset, dataOff, recSize) {
21867
+ const { view } = rCtx;
21868
+ const end = Math.min(view.byteLength, offset + recSize);
21869
+ if (recSize < 36) return true;
21870
+ const x = view.getInt32(dataOff, true), y = view.getInt32(dataOff + 4, true);
21871
+ const n = view.getUint32(dataOff + 8, true), options = view.getUint32(dataOff + 12, true);
21872
+ const small = (options & 512) !== 0, noRect = (options & 256) !== 0;
21873
+ const base = dataOff + 28 + (noRect ? 0 : 16);
21874
+ if (base > end || n > Math.floor((end - base) / (small ? 1 : 2))) return true;
21875
+ const rect = noRect ? [0, 0, 0, 0] : [view.getInt32(dataOff + 28, true), view.getInt32(dataOff + 32, true), view.getInt32(dataOff + 36, true), view.getInt32(dataOff + 40, true)];
21876
+ const codes = small ? Array.from({ length: n }, (_, i) => view.getUint8(base + i)) : Array.from({ length: n }, (_, i) => view.getUint16(base + i * 2, true));
21877
+ drawTextCall(rCtx, x, y, codes, options, rect, null);
21878
+ return true;
21879
+ }
21652
21880
  function handleEmfGdiDrawTextRecord(rCtx, recType, offset, dataOff, recSize) {
21653
21881
  if (recType === EMR_EXTTEXTOUTW) {
21654
21882
  return handleExtTextOutW(rCtx, offset, dataOff, recSize);
21655
21883
  }
21884
+ if (recType === EMR_EXTTEXTOUTA || recType === EMR_POLYTEXTOUTA || recType === EMR_POLYTEXTOUTW || recType === EMR_SMALLTEXTOUT) {
21885
+ if (recType === EMR_POLYTEXTOUTA || recType === EMR_POLYTEXTOUTW) return handlePolyText(rCtx, offset, dataOff, recSize, recType === EMR_POLYTEXTOUTW);
21886
+ if (recType === EMR_SMALLTEXTOUT) return handleSmallTextOut(rCtx, offset, dataOff, recSize);
21887
+ const { view, state } = rCtx;
21888
+ const end = Math.min(view.byteLength, offset + recSize);
21889
+ if (recSize < 76) return true;
21890
+ const x = view.getInt32(dataOff + 28, true), y = view.getInt32(dataOff + 32, true);
21891
+ const n = view.getUint32(dataOff + 36, true), strOff = view.getUint32(dataOff + 40, true), options = view.getUint32(dataOff + 44, true);
21892
+ if (!strOff || strOff > end - offset) return true;
21893
+ if (n > end - (offset + strOff)) return true;
21894
+ const ansi = readAnsiRecord(view, offset + strOff, n, state.fontDetails?.charSet ?? 1);
21895
+ const rect = [view.getInt32(dataOff + 48, true), view.getInt32(dataOff + 52, true), view.getInt32(dataOff + 56, true), view.getInt32(dataOff + 60, true)];
21896
+ const offDx = view.getUint32(dataOff + 64, true);
21897
+ let dx = null;
21898
+ const pdy = (options & ETO_PDY) !== 0;
21899
+ const dxCount = n * (pdy ? 2 : 1);
21900
+ if (offDx && offDx <= end - offset && dxCount <= Math.floor((end - (offset + offDx)) / 4)) {
21901
+ dx = Array.from({ length: dxCount }, (_, i) => view.getInt32(offset + offDx + i * 4, true));
21902
+ dx = collapseAnsiDx(dx, ansi.byteLengths, pdy);
21903
+ }
21904
+ drawTextCall(rCtx, x, y, ansi.codes, options, rect, dx);
21905
+ return true;
21906
+ }
21656
21907
  return false;
21657
21908
  }
21658
21909
 
@@ -23282,6 +23533,412 @@ function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
23282
23533
  }
23283
23534
  }
23284
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
+
23285
23942
  // src/emf-plus-draw-image.ts
23286
23943
  function surfaceSize4(ctx) {
23287
23944
  const canvas = ctx.canvas;
@@ -23344,8 +24001,8 @@ function svgImagePayload(img, bitmap) {
23344
24001
  }
23345
24002
  return decodeBmpFile(bytes.buffer);
23346
24003
  }
23347
- function paintBitmapSvg(svg, img, bitmap, draw) {
23348
- 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);
23349
24006
  if (!payload) {
23350
24007
  return false;
23351
24008
  }
@@ -23417,14 +24074,32 @@ function drawNestedMetafile(rCtx, img, cached, source) {
23417
24074
  rCtx.deferredImages.push(...deferred);
23418
24075
  return true;
23419
24076
  }
23420
- 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) {
23421
24092
  const cached = img.cacheKey !== void 0 ? rCtx.imageCache?.get(img.cacheKey) : void 0;
23422
24093
  if (img.type === 2) {
23423
24094
  return drawNestedMetafile(rCtx, img, cached, source);
23424
24095
  }
23425
- 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
+ }
23426
24101
  if (isSvgContext(rCtx.ctx)) {
23427
- return paintBitmapSvg(rCtx.ctx, img, bitmap, draw);
24102
+ return paintBitmapSvg(rCtx.ctx, img, bitmap, draw, effected !== null);
23428
24103
  }
23429
24104
  return bitmap ? paintBitmapRaster(rCtx.ctx, bitmap, draw) : false;
23430
24105
  }
@@ -23665,7 +24340,7 @@ function penDashArray(pen) {
23665
24340
  return pattern.map((v) => v * w);
23666
24341
  }
23667
24342
  function applyPlusPenStyle(ctx, pen, widthScale = 1) {
23668
- ctx.lineWidth = pen.width * widthScale;
24343
+ ctx.lineWidth = (pen.width || 1) * widthScale;
23669
24344
  const dashes = penDashArray(pen);
23670
24345
  ctx.lineCap = canvasLineCap(dashes.length > 0 ? pen.dashCap ?? 0 : pen.startCap ?? pen.endCap);
23671
24346
  ctx.lineJoin = canvasLineJoin(pen.lineJoin);
@@ -23677,19 +24352,27 @@ function applyPlusPenStyle(ctx, pen, widthScale = 1) {
23677
24352
  }
23678
24353
  function strokePlusGeometry(rCtx, pen, buildPath, points, closedFigure = false) {
23679
24354
  const { ctx } = rCtx;
24355
+ const resolved = resolvePenTransform(pen);
24356
+ pen = resolved.pen;
24357
+ const nib = resolved.nib;
23680
24358
  const inset = !!pen && pen.alignment === 1 && closedFigure;
23681
24359
  const widthScale = inset ? 2 : 1;
23682
24360
  const stroke = (c) => {
23683
- if (inset) {
24361
+ if (inset || nib) {
23684
24362
  c.save();
24363
+ }
24364
+ if (inset) {
23685
24365
  c.beginPath();
23686
24366
  buildPath(c);
23687
24367
  c.clip();
23688
24368
  }
23689
24369
  c.beginPath();
23690
24370
  buildPath(c);
24371
+ if (nib) {
24372
+ c.transform(nib[0], nib[1], nib[2], nib[3], 0, 0);
24373
+ }
23691
24374
  c.stroke();
23692
- if (inset) {
24375
+ if (inset || nib) {
23693
24376
  c.restore();
23694
24377
  }
23695
24378
  };
@@ -23704,8 +24387,8 @@ function strokePlusGeometry(rCtx, pen, buildPath, points, closedFigure = false)
23704
24387
  if (sampler) {
23705
24388
  const size = surfaceSize5(ctx);
23706
24389
  const device = plusWorldMatrix(rCtx);
23707
- const scale = Math.max(Math.hypot(device[0], device[1]), Math.hypot(device[2], device[3]));
23708
- 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)) {
23709
24392
  return;
23710
24393
  }
23711
24394
  const miter = Math.max(1, ctx.miterLimit || 10);
@@ -23723,6 +24406,9 @@ function strokePlusGeometry(rCtx, pen, buildPath, points, closedFigure = false)
23723
24406
  stroke(c);
23724
24407
  c.beginPath();
23725
24408
  buildPath(c);
24409
+ if (nib) {
24410
+ c.transform(nib[0], nib[1], nib[2], nib[3], 0, 0);
24411
+ }
23726
24412
  },
23727
24413
  mode,
23728
24414
  (c, x, y) => c.isPointInStroke(x, y) && (!inset || c.isPointInPath(x, y))
@@ -23730,55 +24416,105 @@ function strokePlusGeometry(rCtx, pen, buildPath, points, closedFigure = false)
23730
24416
  return;
23731
24417
  }
23732
24418
  }
23733
- 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)) {
23734
24421
  return;
23735
24422
  }
23736
- ctx.strokeStyle = pen.brush ? brushPaint2(rCtx, pen.brush) : pen.color;
24423
+ ctx.strokeStyle = paint;
23737
24424
  }
23738
24425
  applyPlusWorldTransform(rCtx);
23739
24426
  stroke(ctx);
23740
24427
  }
23741
- function strokeGdiplus(rCtx, pen, sampler, buildPath, device, size, mode, closedFigure) {
23742
- if (pen.transform && !isIdentity2(pen.transform)) {
23743
- return false;
24428
+ function resolvePenTransform(pen) {
24429
+ if (!pen?.transform) {
24430
+ return { pen, nib: null };
23744
24431
  }
23745
- const sx = Math.hypot(device[0], device[1]);
23746
- const sy = Math.hypot(device[2], device[3]);
23747
- const dot = device[0] * device[2] + device[1] * device[3];
23748
- if (!(sx > 0) || Math.abs(sx - sy) > 1e-6 * sx || Math.abs(dot) > 1e-6 * sx * sy) {
23749
- return false;
24432
+ const [a, b, c, d] = pen.transform;
24433
+ const sx = Math.hypot(a, b);
24434
+ const sy = Math.hypot(c, d);
24435
+ const dot = a * c + b * d;
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 };
23750
24440
  }
23751
- let figures;
23752
- try {
23753
- figures = recordDeviceFigures(buildPath, device);
23754
- } catch {
23755
- 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 };
23756
24443
  }
23757
- if (closedFigure) {
23758
- figures = figures.map((f) => ({ ...f, closed: true }));
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 };
23759
24454
  }
23760
- const width = (pen.width || 1) * sx;
23761
- const dash = penDashArray(pen).map((v) => v * sx);
23762
- const devicePen = {
23763
- half: width / 2,
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;
24460
+ }
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,
23764
24471
  join: pen.lineJoin ?? 0,
23765
24472
  miterLimit: pen.miterLimit && pen.miterLimit >= 1 ? pen.miterLimit : 10,
23766
24473
  startCap: pen.startCap ?? 0,
23767
24474
  endCap: pen.endCap ?? 0,
23768
24475
  dashCap: pen.dashCap ?? 0,
23769
24476
  dash: dash.length > 0 ? dash : null,
23770
- dashOffset: (pen.dashOffset ?? 0) * (pen.width || 1) * sx,
24477
+ dashOffset: (pen.dashOffset ?? 0) * (pen.width || 1) * unit,
23771
24478
  compound: pen.compound ?? null,
23772
24479
  inset: pen.alignment === 1
23773
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);
23774
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
+ };
23775
24511
  let fix;
23776
24512
  if (hasCustomCap(pen)) {
23777
- 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);
23778
24514
  } else if (width <= NOMINAL_PEN_MAX && !devicePen.dash && !(((pen.startCap ?? 0) | (pen.endCap ?? 0) | (pen.dashCap ?? 0)) & 240)) {
23779
24515
  return strokeNominal(rCtx, pen, sampler, buildPath, device, size, antialias, closedFigure);
23780
24516
  } else {
23781
- fix = widenFigures(figures, devicePen).map((poly) => poly.flatMap((p) => [toPlusFix(p.x), toPlusFix(p.y)]));
24517
+ fix = widenFigures(figures, devicePen).map(toFix);
23782
24518
  }
23783
24519
  const box = figuresBox(fix, size);
23784
24520
  if (!box) {
@@ -23841,54 +24577,36 @@ function customCapOutline(figures, width, devicePen, pen) {
23841
24577
  outline.push(...geo.polygons);
23842
24578
  return outline;
23843
24579
  }
23844
- function fillCustomCapStroke(rCtx, pen, buildPath, closedFigure) {
24580
+ function fillPenOutline(rCtx, pen, nib, paint, buildPath, closedFigure) {
23845
24581
  const device = plusWorldMatrix(rCtx);
23846
- const sx = Math.hypot(device[0], device[1]);
23847
- const det = device[0] * device[3] - device[1] * device[2];
23848
- 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)) {
23849
24585
  return false;
23850
24586
  }
23851
24587
  let figures;
23852
24588
  try {
23853
- figures = recordDeviceFigures(buildPath, device);
24589
+ figures = recordDeviceFigures(buildPath, frame2.record);
23854
24590
  } catch {
23855
24591
  return false;
23856
24592
  }
23857
24593
  if (closedFigure) {
23858
24594
  figures = figures.map((f) => ({ ...f, closed: true }));
23859
24595
  }
23860
- const width = (pen.width || 1) * sx;
23861
- const dash = penDashArray(pen).map((v) => v * sx);
23862
- const devicePen = {
23863
- half: width / 2,
23864
- join: pen.lineJoin ?? 0,
23865
- miterLimit: pen.miterLimit && pen.miterLimit >= 1 ? pen.miterLimit : 10,
23866
- startCap: pen.startCap ?? 0,
23867
- endCap: pen.endCap ?? 0,
23868
- dashCap: pen.dashCap ?? 0,
23869
- dash: dash.length > 0 ? dash : null,
23870
- dashOffset: (pen.dashOffset ?? 0) * (pen.width || 1) * sx,
23871
- compound: pen.compound ?? null,
23872
- inset: pen.alignment === 1
23873
- };
23874
- const inv2 = [
23875
- device[3] / det,
23876
- -device[1] / det,
23877
- -device[2] / det,
23878
- device[0] / det,
23879
- (device[2] * device[5] - device[3] * device[4]) / det,
23880
- (device[1] * device[4] - device[0] * device[5]) / det
23881
- ];
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);
23882
24600
  const { ctx } = rCtx;
23883
24601
  ctx.save();
23884
24602
  try {
23885
- ctx.fillStyle = pen.brush ? brushPaint2(rCtx, pen.brush) : pen.color;
24603
+ ctx.fillStyle = paint;
23886
24604
  applyPlusWorldTransform(rCtx);
23887
24605
  ctx.beginPath();
23888
- for (const poly of customCapOutline(figures, width, devicePen, pen)) {
24606
+ for (const poly of outline) {
23889
24607
  poly.forEach((p, i) => {
23890
- const x = inv2[0] * p.x + inv2[2] * p.y + inv2[4];
23891
- 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];
23892
24610
  if (i === 0) {
23893
24611
  ctx.moveTo(x, y);
23894
24612
  } else {
@@ -23903,6 +24621,7 @@ function fillCustomCapStroke(rCtx, pen, buildPath, closedFigure) {
23903
24621
  }
23904
24622
  return true;
23905
24623
  }
24624
+ var IDENTITY5 = [1, 0, 0, 1, 0, 0];
23906
24625
  function isIdentity2(m) {
23907
24626
  return m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0;
23908
24627
  }
@@ -23955,7 +24674,7 @@ function imageResampleSpec(rCtx, dataOff, isMetafile, toWorld) {
23955
24674
  ...imageAttributesWrap(rCtx, view.getUint32(dataOff, true))
23956
24675
  };
23957
24676
  }
23958
- function drawOrDeferImage(rCtx, imgObj, dataOff, dx, dy, dw, dh, toWorld) {
24677
+ function drawOrDeferImage(rCtx, imgObj, dataOff, dx, dy, dw, dh, toWorld, effect = null) {
23959
24678
  if (!imgObj.data) {
23960
24679
  return;
23961
24680
  }
@@ -23979,7 +24698,7 @@ function drawOrDeferImage(rCtx, imgObj, dataOff, dx, dy, dw, dh, toWorld) {
23979
24698
  srcH: view.getFloat32(dataOff + 20, true),
23980
24699
  toWorld
23981
24700
  };
23982
- if (drawEmfPlusImageNow(rCtx, imgObj, draw, source)) {
24701
+ if (drawEmfPlusImageNow(rCtx, imgObj, draw, source, effect)) {
23983
24702
  return;
23984
24703
  }
23985
24704
  if (isSvgContext(rCtx.ctx)) {
@@ -24151,6 +24870,7 @@ function drawPlusStringWithEngine(rCtx, font, text, layoutX, layoutY, paint, ali
24151
24870
  dx,
24152
24871
  dy: null,
24153
24872
  textAlign: 24,
24873
+ options: 0,
24154
24874
  matrix: null});
24155
24875
  if (!cov) {
24156
24876
  return true;
@@ -24342,6 +25062,10 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
24342
25062
  const imgObj = objectTable.get(imgId);
24343
25063
  const count = view.getUint32(dataOff + 24, true);
24344
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
+ }
24345
25069
  if (count >= 3 && imgObj && imgObj.kind === "plus-image" && imgObj.data) {
24346
25070
  const pts = readPlusPoints(view, ptOff, dataOff + recDataSize, 3, recFlags);
24347
25071
  if (!pts) {
@@ -24365,7 +25089,7 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
24365
25089
  const c = (p3x - p1x) / sh;
24366
25090
  const d = (p3y - p1y) / sh;
24367
25091
  return [a, b, c, d, p1x - a * sx - c * sy, p1y - b * sx - d * sy];
24368
- });
25092
+ }, effect);
24369
25093
  } else {
24370
25094
  imgObj && imgObj.kind === "plus-image" && imgObj.data;
24371
25095
  }
@@ -25195,12 +25919,14 @@ function replayEmfPlusRecords(view, offset, length, ctx, canvasW, canvasH, state
25195
25919
  recordCount++;
25196
25920
  emfPlusRecordTypes.set(recType, (emfPlusRecordTypes.get(recType) ?? 0) + 1);
25197
25921
  const dataOff = offset + 12;
25922
+ s.gdiPassthrough = recType === EMFPLUS_GETDC;
25198
25923
  if (rCtx.ext?.multiFormatSkip && recType !== EMFPLUS_ENDOFFILE) {
25199
25924
  offset += recSize;
25200
25925
  continue;
25201
25926
  }
25202
25927
  switch (recType) {
25203
25928
  case EMFPLUS_HEADER: {
25929
+ s.dualMode = (recFlags & 1) !== 0;
25204
25930
  if (recDataSize >= 16) {
25205
25931
  view.getFloat32(dataOff + 8, true);
25206
25932
  view.getFloat32(dataOff + 12, true);
@@ -25219,6 +25945,12 @@ function replayEmfPlusRecords(view, offset, length, ctx, canvasW, canvasH, state
25219
25945
  case EMFPLUS_MULTIFORMATSECTION:
25220
25946
  case EMFPLUS_MULTIFORMATEND:
25221
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;
25222
25954
  case EMFPLUS_CLEAR: {
25223
25955
  if (recDataSize >= 4) {
25224
25956
  const argb = view.getUint32(dataOff, true);
@@ -25365,11 +26097,16 @@ function replayEmfRecords(view, ctx, bounds, canvasW, canvasH, dpiScale = 1, rep
25365
26097
  state: { ...defaultState(), fontFamilyMap: replayOptions.fontFamilyMap },
25366
26098
  stateStack: [],
25367
26099
  inPath: false,
25368
- 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 },
25369
26105
  windowExt: { cx: logicalW, cy: logicalH },
25370
26106
  viewportOrg: { x: 0, y: 0 },
25371
- viewportExt: { cx: canvasW, cy: canvasH },
26107
+ viewportExt: { cx: logicalW, cy: logicalH },
25372
26108
  useMappingMode: false,
26109
+ deviceToCanvas: true,
25373
26110
  clipSaveDepth: 0,
25374
26111
  bounds,
25375
26112
  canvasW,
@@ -25456,6 +26193,13 @@ function replayEmfRecords(view, ctx, bounds, canvasW, canvasH, dpiScale = 1, rep
25456
26193
  offset += recSize;
25457
26194
  continue;
25458
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
+ }
25459
26203
  const handled = handleEmfGdiStateRecord(rCtx, recType, offset, dataOff, recSize) || handleEmfGdiDrawRecord(rCtx, recType, offset, dataOff, recSize) || handleEmfGdiPolyPathRecord(rCtx, recType, offset, dataOff, recSize);
25460
26204
  if (!handled) {
25461
26205
  console.warn(`[emf-converter] Unhandled EMR record type: ${recType}`);
@@ -26865,46 +27609,6 @@ function penIsNull(p) {
26865
27609
  }
26866
27610
 
26867
27611
  // src/wmf-text.ts
26868
- var CP1252_HIGH2 = [
26869
- 8364,
26870
- 129,
26871
- 8218,
26872
- 402,
26873
- 8222,
26874
- 8230,
26875
- 8224,
26876
- 8225,
26877
- 710,
26878
- 8240,
26879
- 352,
26880
- 8249,
26881
- 338,
26882
- 141,
26883
- 381,
26884
- 143,
26885
- 144,
26886
- 8216,
26887
- 8217,
26888
- 8220,
26889
- 8221,
26890
- 8226,
26891
- 8211,
26892
- 8212,
26893
- 732,
26894
- 8482,
26895
- 353,
26896
- 8250,
26897
- 339,
26898
- 157,
26899
- 382,
26900
- 376
26901
- ];
26902
- function ansiToCode(b, charSet) {
26903
- if (charSet === 2) {
26904
- return b;
26905
- }
26906
- return b >= 128 && b <= 159 ? CP1252_HIGH2[b - 128] : b;
26907
- }
26908
27612
  function fontAdvances(p, codes) {
26909
27613
  const fonts = p.rCtx.fonts;
26910
27614
  if (!fonts) {