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