emf-converter 3.0.2 → 3.1.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.
Files changed (4) hide show
  1. package/README.md +4 -2
  2. package/dist/index.js +1483 -409
  3. package/dist/index.mjs +1483 -409
  4. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -50,6 +50,47 @@ function invertCssColor(color) {
50
50
  return color;
51
51
  }
52
52
 
53
+ // src/emf-gdi-text-layout.ts
54
+ function cumulativeGlyphOffsets(dx) {
55
+ const offsets = [];
56
+ let acc = 0;
57
+ for (let i = 0; i < dx.length; i++) {
58
+ offsets.push(acc);
59
+ acc += dx[i];
60
+ }
61
+ return offsets;
62
+ }
63
+ function totalGlyphAdvance(dx) {
64
+ let sum = 0;
65
+ for (const d of dx) {
66
+ sum += d;
67
+ }
68
+ return sum;
69
+ }
70
+ function alignmentStartOffset(totalWidth, align) {
71
+ if (align === "center") {
72
+ return -totalWidth / 2;
73
+ }
74
+ if (align === "right") {
75
+ return -totalWidth;
76
+ }
77
+ return 0;
78
+ }
79
+ var DEG_TO_RAD = Math.PI / 180;
80
+ function escapementToCanvasRadians(tenthsOfDegree) {
81
+ return -(tenthsOfDegree / 10) * DEG_TO_RAD;
82
+ }
83
+ var CELL_TO_CHAR_HEIGHT_RATIO = 1.15;
84
+ function resolveFontPixelHeight(lfHeight) {
85
+ if (lfHeight === 0) {
86
+ return 0;
87
+ }
88
+ if (lfHeight < 0) {
89
+ return -lfHeight;
90
+ }
91
+ return lfHeight / CELL_TO_CHAR_HEIGHT_RATIO;
92
+ }
93
+
53
94
  // src/emf-constants.ts
54
95
  var EMR_HEADER = 1;
55
96
  var EMR_POLYBEZIER = 2;
@@ -64,6 +105,9 @@ var EMR_SETWINDOWORGEX = 10;
64
105
  var EMR_SETVIEWPORTEXTEX = 11;
65
106
  var EMR_SETVIEWPORTORGEX = 12;
66
107
  var EMR_SETBRUSHORGEX = 13;
108
+ var EMR_CREATEMONOBRUSH = 93;
109
+ var EMR_CREATEDIBPATTERNBRUSHPT = 94;
110
+ var EMR_STRETCHBLT = 77;
67
111
  var EMR_EOF = 14;
68
112
  var EMR_SETPIXELV = 15;
69
113
  var EMR_SETMAPMODE = 17;
@@ -468,7 +512,7 @@ function mapFontFamily(face, map) {
468
512
  return resolved;
469
513
  }
470
514
  function fontSizePx(state, scale = 1) {
471
- return Math.max(Math.abs(state.fontHeight) * Math.abs(scale || 1), 8);
515
+ return Math.max(resolveFontPixelHeight(state.fontHeight) * Math.abs(scale || 1), 8);
472
516
  }
473
517
  function applyFont(ctx, state, scale = 1) {
474
518
  const italic = state.fontItalic ? "italic " : "";
@@ -620,6 +664,28 @@ function canvasDrawImage(ctx, image, dx, dy, dw, dh) {
620
664
  const draw = ctx.drawImage;
621
665
  draw.call(ctx, image, dx, dy, dw, dh);
622
666
  }
667
+ function canvasGetImageData(ctx, x, y, w, h) {
668
+ const get = ctx.getImageData;
669
+ return get.call(ctx, x, y, w, h);
670
+ }
671
+ function canvasPutImageData(ctx, data, x, y) {
672
+ const put = ctx.putImageData;
673
+ put.call(ctx, data, x, y);
674
+ }
675
+ function canvasCreatePattern(ctx, image, repetition) {
676
+ const create = ctx.createPattern;
677
+ return create.call(ctx, image, repetition);
678
+ }
679
+ function createImageDataCompat(data, width, height) {
680
+ if (typeof ImageData !== "undefined") {
681
+ return new ImageData(data, width, height);
682
+ }
683
+ if (nodeCanvasModule) {
684
+ const Ctor = nodeCanvasModule.ImageData;
685
+ return new Ctor(data, width, height);
686
+ }
687
+ throw new ReferenceError("ImageData is not defined");
688
+ }
623
689
 
624
690
  // src/emf-header-parser.ts
625
691
  function parseEmfHeader(view) {
@@ -938,218 +1004,6 @@ function handleEmfGdiShapeRecord(rCtx, recType, dataOff, recSize) {
938
1004
  }
939
1005
  }
940
1006
 
941
- // src/emf-clip-region.ts
942
- var CLIP_HUGE = 1 << 24;
943
- function rectClipShape(x, y, w, h) {
944
- return { cmds: [{ op: "rect", x, y, w, h }], fillRule: "nonzero", simple: true };
945
- }
946
- function rectsClipShape(rects) {
947
- return {
948
- cmds: rects.map((r) => ({ op: "rect", x: r.x, y: r.y, w: r.w, h: r.h })),
949
- fillRule: "nonzero",
950
- simple: true
951
- };
952
- }
953
- function emptyClipShape() {
954
- return { cmds: [{ op: "rect", x: 0, y: 0, w: 0, h: 0 }], fillRule: "nonzero", simple: true };
955
- }
956
- function translateClipShape(shape, dx, dy) {
957
- return {
958
- ...shape,
959
- cmds: shape.cmds.map((c) => {
960
- switch (c.op) {
961
- case "rect":
962
- return { ...c, x: c.x + dx, y: c.y + dy };
963
- case "moveTo":
964
- case "lineTo":
965
- return { ...c, x: c.x + dx, y: c.y + dy };
966
- case "bezierCurveTo":
967
- return {
968
- ...c,
969
- cp1x: c.cp1x + dx,
970
- cp1y: c.cp1y + dy,
971
- cp2x: c.cp2x + dx,
972
- cp2y: c.cp2y + dy,
973
- x: c.x + dx,
974
- y: c.y + dy
975
- };
976
- case "closePath":
977
- return c;
978
- }
979
- })
980
- };
981
- }
982
- function translateClipRegion(region, dx, dy) {
983
- if (!region) {
984
- return null;
985
- }
986
- return region.map((s) => translateClipShape(s, dx, dy));
987
- }
988
- function isComposable(shape) {
989
- return shape.simple && shape.fillRule === "nonzero";
990
- }
991
- function invertClipShape(shape) {
992
- return {
993
- cmds: [
994
- { op: "rect", x: -CLIP_HUGE, y: -CLIP_HUGE, w: 2 * CLIP_HUGE, h: 2 * CLIP_HUGE },
995
- ...shape.cmds
996
- ],
997
- fillRule: "evenodd",
998
- simple: false
999
- };
1000
- }
1001
- function combineClip(current, shape, op) {
1002
- switch (op) {
1003
- case "replace":
1004
- return { region: [shape], exact: true };
1005
- case "intersect":
1006
- return { region: current ? [...current, shape] : [shape], exact: true };
1007
- case "exclude": {
1008
- if (!isComposable(shape)) {
1009
- return { region: current ? [...current, shape] : [shape], exact: false };
1010
- }
1011
- const inv = invertClipShape(shape);
1012
- return { region: current ? [...current, inv] : [inv], exact: true };
1013
- }
1014
- case "union": {
1015
- if (!current) {
1016
- return { region: null, exact: true };
1017
- }
1018
- if (current.length === 1 && isComposable(current[0]) && isComposable(shape)) {
1019
- return {
1020
- region: [
1021
- { cmds: [...current[0].cmds, ...shape.cmds], fillRule: "nonzero", simple: false }
1022
- ],
1023
- exact: false
1024
- };
1025
- }
1026
- return { region: current, exact: false };
1027
- }
1028
- case "xor": {
1029
- if (!current) {
1030
- if (isComposable(shape)) {
1031
- return { region: [invertClipShape(shape)], exact: true };
1032
- }
1033
- return { region: [shape], exact: false };
1034
- }
1035
- if (current.length === 1 && isComposable(current[0]) && isComposable(shape)) {
1036
- return {
1037
- region: [
1038
- { cmds: [...current[0].cmds, ...shape.cmds], fillRule: "evenodd", simple: false }
1039
- ],
1040
- exact: true
1041
- };
1042
- }
1043
- if (isComposable(shape)) {
1044
- return { region: [...current, invertClipShape(shape)], exact: false };
1045
- }
1046
- return { region: [...current, shape], exact: false };
1047
- }
1048
- case "complement": {
1049
- if (!current) {
1050
- return { region: [emptyClipShape()], exact: true };
1051
- }
1052
- if (current.length === 1 && isComposable(current[0])) {
1053
- return { region: [shape, invertClipShape(current[0])], exact: true };
1054
- }
1055
- return { region: [shape], exact: false };
1056
- }
1057
- }
1058
- }
1059
- function combineClipRegions(current, incoming, op) {
1060
- if (op === "replace") {
1061
- return { region: incoming, exact: true };
1062
- }
1063
- if (incoming && incoming.length === 1) {
1064
- return combineClip(current, incoming[0], op);
1065
- }
1066
- if (!incoming) {
1067
- switch (op) {
1068
- case "intersect":
1069
- return { region: current, exact: true };
1070
- case "union":
1071
- return { region: null, exact: true };
1072
- case "exclude":
1073
- return { region: [emptyClipShape()], exact: true };
1074
- case "xor":
1075
- case "complement": {
1076
- if (!current) {
1077
- return { region: [emptyClipShape()], exact: true };
1078
- }
1079
- if (current.length === 1) {
1080
- return combineClip(null, current[0], "exclude");
1081
- }
1082
- return { region: current, exact: false };
1083
- }
1084
- }
1085
- }
1086
- switch (op) {
1087
- case "intersect":
1088
- return { region: current ? [...current, ...incoming] : incoming, exact: true };
1089
- case "union":
1090
- return { region: current, exact: false };
1091
- case "xor":
1092
- return { region: current ?? incoming, exact: false };
1093
- case "exclude":
1094
- return { region: current, exact: false };
1095
- case "complement": {
1096
- if (!current) {
1097
- return { region: [emptyClipShape()], exact: true };
1098
- }
1099
- if (current.length === 1) {
1100
- return combineClip(incoming, current[0], "exclude");
1101
- }
1102
- return { region: incoming, exact: false };
1103
- }
1104
- }
1105
- }
1106
- function replayClipCmds(ctx, cmds) {
1107
- for (const c of cmds) {
1108
- switch (c.op) {
1109
- case "rect":
1110
- ctx.rect(c.x, c.y, c.w, c.h);
1111
- break;
1112
- case "moveTo":
1113
- ctx.moveTo(c.x, c.y);
1114
- break;
1115
- case "lineTo":
1116
- ctx.lineTo(c.x, c.y);
1117
- break;
1118
- case "bezierCurveTo":
1119
- ctx.bezierCurveTo(c.cp1x, c.cp1y, c.cp2x, c.cp2y, c.x, c.y);
1120
- break;
1121
- case "closePath":
1122
- ctx.closePath();
1123
- break;
1124
- }
1125
- }
1126
- }
1127
- function applyClipShapes(ctx, shapes) {
1128
- for (const s of shapes) {
1129
- ctx.beginPath();
1130
- replayClipCmds(ctx, s.cmds);
1131
- try {
1132
- ctx.clip(s.fillRule);
1133
- } catch {
1134
- }
1135
- }
1136
- }
1137
- function reapplyClipRegion(holder, region, identityTransform = false) {
1138
- const { ctx } = holder;
1139
- while (holder.clipSaveDepth > 0) {
1140
- ctx.restore();
1141
- holder.clipSaveDepth--;
1142
- }
1143
- if (region) {
1144
- ctx.save();
1145
- holder.clipSaveDepth = 1;
1146
- if (identityTransform) {
1147
- ctx.setTransform(1, 0, 0, 1, 0, 0);
1148
- }
1149
- applyClipShapes(ctx, region);
1150
- }
1151
- }
1152
-
1153
1007
  // src/emf-dib-rle-decoder.ts
1154
1008
  function decodeRleBitmap(view, bitsOffset, bitsSize, width, height, _topDown, isRle4, colorTable, out, setPixel) {
1155
1009
  let x = 0;
@@ -1245,7 +1099,7 @@ function decodeRleBitmap(view, bitsOffset, bitsSize, width, height, _topDown, is
1245
1099
  }
1246
1100
  }
1247
1101
  }
1248
- return new ImageData(
1102
+ return createImageDataCompat(
1249
1103
  new Uint8ClampedArray(out.buffer, out.byteOffset, out.byteLength),
1250
1104
  width,
1251
1105
  height
@@ -1455,138 +1309,855 @@ function decodeDibToImageData(view, bmiOffset, bitsOffset, bitsSize) {
1455
1309
  masks,
1456
1310
  out
1457
1311
  );
1458
- return new ImageData(out, width, height);
1312
+ return createImageDataCompat(out, width, height);
1459
1313
  }
1460
1314
 
1461
- // src/emf-gdi-draw-text-bitmap.ts
1462
- function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
1463
- const { ctx, view, state } = rCtx;
1464
- if (recSize >= 76) {
1465
- const refX = view.getInt32(dataOff + 28, true);
1466
- const refY = view.getInt32(dataOff + 32, true);
1467
- const nChars = view.getUint32(dataOff + 36, true);
1468
- const offString = view.getUint32(dataOff + 40, true);
1469
- const maxOffset = view.byteLength;
1470
- if (nChars > 0 && offString > 0 && offset + offString + nChars * 2 <= maxOffset) {
1471
- const text = readUtf16LE(view, offset + offString, nChars);
1472
- if (text.length > 0) {
1473
- const fontScale = Math.abs(gmh(rCtx, 1));
1474
- applyFont(ctx, state, fontScale);
1475
- ctx.fillStyle = state.textColor;
1476
- const vAlign = state.textAlign & 24;
1477
- const alignBaseline = vAlign === 24 ? "alphabetic" : vAlign === 8 ? "bottom" : "top";
1478
- let alignHoriz = "left";
1479
- if (state.textAlign & 6) {
1480
- alignHoriz = "center";
1481
- }
1482
- if (state.textAlign & 2) {
1483
- alignHoriz = "right";
1484
- }
1485
- ctx.textBaseline = alignBaseline;
1486
- ctx.textAlign = alignHoriz;
1487
- if (state.bkMode === 2) {
1488
- const measured = ctx.measureText(text);
1489
- const bgH = fontSizePx(state, fontScale);
1490
- ctx.fillStyle = state.bkColor;
1491
- ctx.fillRect(gmx(rCtx, refX), gmy(rCtx, refY) - bgH, measured.width, bgH);
1492
- ctx.fillStyle = state.textColor;
1315
+ // src/emf-gdi-brush-pattern.ts
1316
+ var HATCH_ROWS = [
1317
+ [0, 0, 0, 255, 0, 0, 0, 0],
1318
+ // HS_HORIZONTAL
1319
+ [8, 8, 8, 8, 8, 8, 8, 8],
1320
+ // HS_VERTICAL
1321
+ [128, 64, 32, 16, 8, 4, 2, 1],
1322
+ // HS_FDIAGONAL
1323
+ [1, 2, 4, 8, 16, 32, 64, 128],
1324
+ // HS_BDIAGONAL
1325
+ [8, 8, 8, 255, 8, 8, 8, 8],
1326
+ // HS_CROSS
1327
+ [129, 66, 36, 24, 24, 36, 66, 129]
1328
+ // HS_DIAGCROSS
1329
+ ];
1330
+ function hatchBit(hatch, x, y) {
1331
+ const rows = HATCH_ROWS[hatch] ?? HATCH_ROWS[0];
1332
+ return (rows[y & 7] >> 7 - (x & 7) & 1) === 1;
1333
+ }
1334
+ function cssHexToRgb(color) {
1335
+ const m = /^#([0-9a-f]{6}|[0-9a-f]{3})$/i.exec(color.trim());
1336
+ if (!m) {
1337
+ return 0;
1338
+ }
1339
+ const hex = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];
1340
+ return parseInt(hex, 16);
1341
+ }
1342
+ function decodeMonoBits(view, bmiOffset, bitsOffset) {
1343
+ if (bmiOffset + 16 > view.byteLength) {
1344
+ return null;
1345
+ }
1346
+ const width = view.getInt32(bmiOffset + 4, true);
1347
+ const heightRaw = view.getInt32(bmiOffset + 8, true);
1348
+ const bitCount = view.getUint16(bmiOffset + 14, true);
1349
+ const height = Math.abs(heightRaw);
1350
+ if (bitCount !== 1 || width <= 0 || height === 0 || width > 256 || height > 256) {
1351
+ return null;
1352
+ }
1353
+ const stride = (width + 31 >> 5) * 4;
1354
+ if (bitsOffset + stride * height > view.byteLength) {
1355
+ return null;
1356
+ }
1357
+ const bits = new Uint8Array(width * height);
1358
+ for (let row = 0; row < height; row++) {
1359
+ const y = heightRaw > 0 ? height - 1 - row : row;
1360
+ const rowOff = bitsOffset + row * stride;
1361
+ for (let x = 0; x < width; x++) {
1362
+ bits[y * width + x] = view.getUint8(rowOff + (x >> 3)) >> 7 - (x & 7) & 1;
1363
+ }
1364
+ }
1365
+ return { width, height, bits };
1366
+ }
1367
+ function parsePatternBrush(view, offset, dataOff, mono) {
1368
+ if (dataOff + 24 > view.byteLength) {
1369
+ return null;
1370
+ }
1371
+ const offBmi = view.getUint32(dataOff + 8, true);
1372
+ const offBits = view.getUint32(dataOff + 16, true);
1373
+ const cbBits = view.getUint32(dataOff + 20, true);
1374
+ const bmi = offset + offBmi;
1375
+ const bitsAt = offset + offBits;
1376
+ const bitCount = bmi + 16 <= view.byteLength ? view.getUint16(bmi + 14, true) : 0;
1377
+ if (mono || bitCount === 1) {
1378
+ const decoded = decodeMonoBits(view, bmi, bitsAt);
1379
+ if (decoded && mono) {
1380
+ return { kind: "mono", ...decoded };
1381
+ }
1382
+ }
1383
+ const image = decodeDibToImageData(view, bmi, bitsAt, cbBits);
1384
+ if (!image) {
1385
+ return null;
1386
+ }
1387
+ const rgb = new Uint32Array(image.width * image.height);
1388
+ const d = image.data;
1389
+ for (let i = 0; i < rgb.length; i++) {
1390
+ rgb[i] = d[i * 4] << 16 | d[i * 4 + 1] << 8 | d[i * 4 + 2];
1391
+ }
1392
+ return { kind: "bitmap", width: image.width, height: image.height, rgb };
1393
+ }
1394
+ function realizeBrush(state) {
1395
+ if (state.brushStyle === 1) {
1396
+ return { kind: "none" };
1397
+ }
1398
+ const pattern = state.brushPattern;
1399
+ if (!pattern) {
1400
+ return { kind: "solid", rgb: cssHexToRgb(state.brushColor) };
1401
+ }
1402
+ if (pattern.kind === "bitmap") {
1403
+ return { kind: "tile", width: pattern.width, height: pattern.height, rgb: pattern.rgb };
1404
+ }
1405
+ const bk = cssHexToRgb(state.bkColor);
1406
+ if (pattern.kind === "hatch") {
1407
+ const fg = cssHexToRgb(state.brushColor);
1408
+ const rgb2 = new Uint32Array(64);
1409
+ for (let y = 0; y < 8; y++) {
1410
+ for (let x = 0; x < 8; x++) {
1411
+ rgb2[y * 8 + x] = hatchBit(pattern.hatch, x, y) ? fg : bk;
1412
+ }
1413
+ }
1414
+ return { kind: "tile", width: 8, height: 8, rgb: rgb2 };
1415
+ }
1416
+ const text = cssHexToRgb(state.textColor);
1417
+ const rgb = new Uint32Array(pattern.width * pattern.height);
1418
+ for (let i = 0; i < rgb.length; i++) {
1419
+ rgb[i] = pattern.bits[i] ? bk : text;
1420
+ }
1421
+ return { kind: "tile", width: pattern.width, height: pattern.height, rgb };
1422
+ }
1423
+ function sampleTile(tile, devX, devY, orgX, orgY) {
1424
+ const tx = ((devX - orgX) % tile.width + tile.width) % tile.width;
1425
+ const ty = ((devY - orgY) % tile.height + tile.height) % tile.height;
1426
+ return tile.rgb[ty * tile.width + tx];
1427
+ }
1428
+
1429
+ // src/emf-gdi-stretch.ts
1430
+ var BLACKONWHITE = 1;
1431
+ var WHITEONBLACK = 2;
1432
+ var HALFTONE = 4;
1433
+ function gdiNearest(i, srcLen, dstLen) {
1434
+ const k = Math.floor((2 * i + 1) * srcLen / (2 * dstLen));
1435
+ return Math.max(0, Math.min(srcLen - 1, k));
1436
+ }
1437
+ function axisSamples(srcStart, srcLen, dstLen, reverse, combine) {
1438
+ const out = [];
1439
+ const at = (k) => reverse ? srcStart + srcLen - 1 - k : srcStart + k;
1440
+ if (dstLen <= 0 || srcLen <= 0) {
1441
+ return out;
1442
+ }
1443
+ let prev = -1;
1444
+ for (let i = 0; i < dstLen; i++) {
1445
+ const c = gdiNearest(i, srcLen, dstLen);
1446
+ const run = [];
1447
+ const from = combine && dstLen < srcLen ? prev + 1 : c;
1448
+ for (let k = from; k <= c; k++) {
1449
+ run.push(at(k));
1450
+ }
1451
+ out.push(run);
1452
+ prev = c;
1453
+ }
1454
+ return out;
1455
+ }
1456
+ function stretchGdi(src, sx, sy, sw, sh, dw, dh, mode) {
1457
+ const W = Math.max(0, Math.round(Math.abs(dw)));
1458
+ const H = Math.max(0, Math.round(Math.abs(dh)));
1459
+ const flipX = dw < 0 !== sw < 0;
1460
+ const flipY = dh < 0 !== sh < 0;
1461
+ const combine = mode === BLACKONWHITE || mode === WHITEONBLACK;
1462
+ const cols = axisSamples(Math.round(Math.min(sx, sx + sw)), Math.round(Math.abs(sw)), W, flipX, combine);
1463
+ const rows = axisSamples(Math.round(Math.min(sy, sy + sh)), Math.round(Math.abs(sh)), H, flipY, combine);
1464
+ const data = new Uint8ClampedArray(W * H * 4);
1465
+ const s = src.data;
1466
+ const read = (x, y) => {
1467
+ if (x < 0 || y < 0 || x >= src.width || y >= src.height) {
1468
+ return 0;
1469
+ }
1470
+ const i = (y * src.width + x) * 4;
1471
+ return s[i] << 16 | s[i + 1] << 8 | s[i + 2];
1472
+ };
1473
+ for (let y = 0; y < H; y++) {
1474
+ const ys = rows[y] ?? [];
1475
+ for (let x = 0; x < W; x++) {
1476
+ const xs = cols[x] ?? [];
1477
+ let v = mode === BLACKONWHITE ? 16777215 : 0;
1478
+ let first = true;
1479
+ for (const yy of ys) {
1480
+ for (const xx of xs) {
1481
+ const p = read(xx, yy);
1482
+ if (mode === BLACKONWHITE) {
1483
+ v &= p;
1484
+ } else if (mode === WHITEONBLACK) {
1485
+ v |= p;
1486
+ } else if (first) {
1487
+ v = p;
1488
+ }
1489
+ first = false;
1490
+ }
1491
+ }
1492
+ const o = (y * W + x) * 4;
1493
+ data[o] = v >> 16 & 255;
1494
+ data[o + 1] = v >> 8 & 255;
1495
+ data[o + 2] = v & 255;
1496
+ data[o + 3] = 255;
1497
+ }
1498
+ }
1499
+ return { width: W, height: H, data };
1500
+ }
1501
+
1502
+ // src/emf-rop3.ts
1503
+ function rop3Index(rop) {
1504
+ return rop >>> 16 & 255;
1505
+ }
1506
+ function rop3Operands(index) {
1507
+ const t = index & 255;
1508
+ return {
1509
+ usesP: (t >> 4 & 15) !== (t & 15),
1510
+ usesS: (t >> 2 & 51) !== (t & 51),
1511
+ usesD: (t >> 1 & 85) !== (t & 85)
1512
+ };
1513
+ }
1514
+ function evalRop3(index, p, s, d, mask = 16777215) {
1515
+ let out = 0;
1516
+ const np = ~p;
1517
+ const ns = ~s;
1518
+ const nd = ~d;
1519
+ for (let i = 0; i < 8; i++) {
1520
+ if (index & 1 << i) {
1521
+ out |= (i & 4 ? p : np) & (i & 2 ? s : ns) & (i & 1 ? d : nd);
1522
+ }
1523
+ }
1524
+ return out & mask;
1525
+ }
1526
+ function classifyRop3(rop) {
1527
+ const index = rop3Index(rop);
1528
+ switch (index) {
1529
+ case 204:
1530
+ return { kind: "copy" };
1531
+ case 0:
1532
+ return { kind: "solid", color: "black" };
1533
+ case 255:
1534
+ return { kind: "solid", color: "white" };
1535
+ case 170:
1536
+ return { kind: "noop" };
1537
+ case 85:
1538
+ return { kind: "invert-dest" };
1539
+ default:
1540
+ return { kind: "ternary", index, operands: rop3Operands(index) };
1541
+ }
1542
+ }
1543
+ function applyRop3(dst, src, pattern, index, originX, originY) {
1544
+ const d = dst.data;
1545
+ const s = src ? src.data : null;
1546
+ const w = dst.width;
1547
+ const h = dst.height;
1548
+ const solidP = typeof pattern === "number" ? pattern : 0;
1549
+ const sampleP = typeof pattern === "function" ? pattern : null;
1550
+ for (let y = 0; y < h; y++) {
1551
+ for (let x = 0; x < w; x++) {
1552
+ const i = (y * w + x) * 4;
1553
+ const dv = d[i] << 16 | d[i + 1] << 8 | d[i + 2];
1554
+ const sv = s ? s[i] << 16 | s[i + 1] << 8 | s[i + 2] : 0;
1555
+ const pv = sampleP ? sampleP(originX + x, originY + y) : solidP;
1556
+ const r = evalRop3(index, pv, sv, dv);
1557
+ d[i] = r >> 16 & 255;
1558
+ d[i + 1] = r >> 8 & 255;
1559
+ d[i + 2] = r & 255;
1560
+ d[i + 3] = 255;
1561
+ }
1562
+ }
1563
+ }
1564
+ function clampPositiveRect(x, y, w, h, canvasW, canvasH) {
1565
+ let left = w < 0 ? x + w : x;
1566
+ let top = h < 0 ? y + h : y;
1567
+ let width = Math.abs(w);
1568
+ let height = Math.abs(h);
1569
+ left = Math.round(left);
1570
+ top = Math.round(top);
1571
+ width = Math.round(width);
1572
+ height = Math.round(height);
1573
+ const right = Math.min(left + width, Math.round(canvasW));
1574
+ const bottom = Math.min(top + height, Math.round(canvasH));
1575
+ left = Math.max(left, 0);
1576
+ top = Math.max(top, 0);
1577
+ width = right - left;
1578
+ height = bottom - top;
1579
+ if (width <= 0 || height <= 0) {
1580
+ return null;
1581
+ }
1582
+ return { x: left, y: top, w: width, h: height };
1583
+ }
1584
+
1585
+ // src/emf-gdi-draw-bitmap.ts
1586
+ function patternOperand(rCtx, brush) {
1587
+ if (brush.kind === "solid") {
1588
+ return brush.rgb;
1589
+ }
1590
+ if (brush.kind === "none") {
1591
+ return 0;
1592
+ }
1593
+ const { state, bounds } = rCtx;
1594
+ const sx = rCtx.sx || 1;
1595
+ const sy = rCtx.sy || 1;
1596
+ return (x, y) => sampleTile(
1597
+ brush,
1598
+ Math.floor(bounds.left + (x + 0.5) / sx),
1599
+ Math.floor(bounds.top + (y + 0.5) / sy),
1600
+ state.brushOrgX,
1601
+ state.brushOrgY
1602
+ );
1603
+ }
1604
+ function drawSourceMapped(target, decoded, req, offsetX, offsetY, mode) {
1605
+ const dLeft = Math.min(req.dx, req.dx + req.dw) - offsetX;
1606
+ const dTop = Math.min(req.dy, req.dy + req.dh) - offsetY;
1607
+ if (mode !== HALFTONE) {
1608
+ const px = stretchGdi(decoded.pixels, req.sx, req.sy, req.sw, req.sh, req.dw, req.dh, mode);
1609
+ const tile = createTempCanvas(px.width, px.height);
1610
+ if (!tile) {
1611
+ return;
1612
+ }
1613
+ canvasPutImageData(tile.ctx, createImageDataCompat(px.data, px.width, px.height), 0, 0);
1614
+ target.save();
1615
+ target.globalCompositeOperation = "source-over";
1616
+ canvasDrawImage(target, tile.canvas, Math.round(dLeft), Math.round(dTop), px.width, px.height);
1617
+ target.restore();
1618
+ return;
1619
+ }
1620
+ const flipX = req.dw < 0 !== req.sw < 0;
1621
+ const flipY = req.dh < 0 !== req.sh < 0;
1622
+ const adw = Math.abs(req.dw);
1623
+ const adh = Math.abs(req.dh);
1624
+ target.save();
1625
+ target.imageSmoothingEnabled = true;
1626
+ target.transform(
1627
+ flipX ? -1 : 1,
1628
+ 0,
1629
+ 0,
1630
+ flipY ? -1 : 1,
1631
+ flipX ? dLeft * 2 + adw : 0,
1632
+ flipY ? dTop * 2 + adh : 0
1633
+ );
1634
+ const draw = target.drawImage;
1635
+ draw.call(
1636
+ target,
1637
+ decoded.canvas,
1638
+ Math.min(req.sx, req.sx + req.sw),
1639
+ Math.min(req.sy, req.sy + req.sh),
1640
+ Math.abs(req.sw),
1641
+ Math.abs(req.sh),
1642
+ dLeft,
1643
+ dTop,
1644
+ adw,
1645
+ adh
1646
+ );
1647
+ target.restore();
1648
+ }
1649
+ function decodeSource(rCtx, req) {
1650
+ if (!req.source) {
1651
+ return null;
1652
+ }
1653
+ const image = decodeDibToImageData(rCtx.view, req.source.bmi, req.source.bits, req.source.cbBits);
1654
+ if (!image) {
1655
+ return null;
1656
+ }
1657
+ const temp = createTempCanvas(image.width, image.height);
1658
+ if (!temp) {
1659
+ return null;
1660
+ }
1661
+ canvasPutImageData(temp.ctx, image, 0, 0);
1662
+ let { sy } = req;
1663
+ if (req.dibOrigin === "bottom-left" && rCtx.view.getInt32(req.source.bmi + 8, true) > 0) {
1664
+ sy = image.height - sy - req.sh;
1665
+ }
1666
+ return {
1667
+ decoded: { pixels: image, canvas: temp.canvas },
1668
+ req: { ...req, sy }
1669
+ };
1670
+ }
1671
+ function fillRectWith(ctx, style, gco, req) {
1672
+ const prevGco = ctx.globalCompositeOperation;
1673
+ const prevFill = ctx.fillStyle;
1674
+ ctx.globalCompositeOperation = gco;
1675
+ ctx.fillStyle = style;
1676
+ ctx.fillRect(req.dx, req.dy, req.dw, req.dh);
1677
+ ctx.globalCompositeOperation = prevGco;
1678
+ ctx.fillStyle = prevFill;
1679
+ }
1680
+ function runTernary(rCtx, req, index, usesP, usesS) {
1681
+ const { ctx } = rCtx;
1682
+ const rect = clampPositiveRect(req.dx, req.dy, req.dw, req.dh, rCtx.canvasW, rCtx.canvasH);
1683
+ if (!rect) {
1684
+ return;
1685
+ }
1686
+ if (typeof ctx.getImageData !== "function") {
1687
+ return;
1688
+ }
1689
+ const brush = realizeBrush(rCtx.state);
1690
+ if (usesP && brush.kind === "none") {
1691
+ return;
1692
+ }
1693
+ const layer = createTempCanvas(rect.w, rect.h);
1694
+ if (!layer) {
1695
+ return;
1696
+ }
1697
+ let src = null;
1698
+ if (usesS) {
1699
+ const decoded = decodeSource(rCtx, req);
1700
+ if (!decoded) {
1701
+ return;
1702
+ }
1703
+ drawSourceMapped(layer.ctx, decoded.decoded, decoded.req, rect.x, rect.y, rCtx.state.stretchBltMode);
1704
+ src = canvasGetImageData(layer.ctx, 0, 0, rect.w, rect.h);
1705
+ }
1706
+ const dst = canvasGetImageData(ctx, rect.x, rect.y, rect.w, rect.h);
1707
+ applyRop3(dst, src, patternOperand(rCtx, brush), index, rect.x, rect.y);
1708
+ canvasPutImageData(layer.ctx, dst, 0, 0);
1709
+ ctx.save();
1710
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
1711
+ ctx.globalCompositeOperation = "source-over";
1712
+ ctx.globalAlpha = 1;
1713
+ canvasDrawImage(ctx, layer.canvas, rect.x, rect.y, rect.w, rect.h);
1714
+ ctx.restore();
1715
+ }
1716
+ function alignMirroredDest(rCtx, req) {
1717
+ const pxX = rCtx.useMappingMode ? 1 : rCtx.sx;
1718
+ const pxY = rCtx.useMappingMode ? 1 : rCtx.sy;
1719
+ return {
1720
+ ...req,
1721
+ dx: req.dw < 0 ? req.dx + pxX : req.dx,
1722
+ dy: req.dh < 0 ? req.dy + pxY : req.dy
1723
+ };
1724
+ }
1725
+ function executeBlit(rCtx, request) {
1726
+ const { ctx } = rCtx;
1727
+ const req = alignMirroredDest(rCtx, request);
1728
+ const plan = req.plan;
1729
+ switch (plan.kind) {
1730
+ case "noop":
1731
+ return;
1732
+ case "solid":
1733
+ fillRectWith(ctx, plan.color === "black" ? "#000000" : "#ffffff", "source-over", req);
1734
+ return;
1735
+ case "invert-dest":
1736
+ fillRectWith(ctx, "#ffffff", "difference", req);
1737
+ return;
1738
+ case "copy": {
1739
+ const decoded = decodeSource(rCtx, req);
1740
+ if (decoded) {
1741
+ drawSourceMapped(ctx, decoded.decoded, decoded.req, 0, 0, rCtx.state.stretchBltMode);
1742
+ }
1743
+ return;
1744
+ }
1745
+ case "ternary": {
1746
+ const brush = realizeBrush(rCtx.state);
1747
+ if (plan.index === 240 && brush.kind !== "tile") {
1748
+ if (brush.kind === "solid") {
1749
+ fillRectWith(ctx, rCtx.state.brushColor, "source-over", req);
1750
+ }
1751
+ return;
1752
+ }
1753
+ runTernary(rCtx, req, plan.index, plan.operands.usesP, plan.operands.usesS);
1754
+ }
1755
+ }
1756
+ }
1757
+ function sourceOf(offset, offBmi, cbBmi, offBits, cbBits) {
1758
+ return offBmi > 0 && cbBmi > 0 && offBits > 0 && cbBits > 0 ? { bmi: offset + offBmi, bits: offset + offBits, cbBits } : null;
1759
+ }
1760
+ function handleBlt(rCtx, offset, dataOff, recSize, stretch) {
1761
+ const { view } = rCtx;
1762
+ if (recSize < (stretch ? 108 : 96)) {
1763
+ return true;
1764
+ }
1765
+ const xDest = view.getInt32(dataOff + 16, true);
1766
+ const yDest = view.getInt32(dataOff + 20, true);
1767
+ const cxDest = view.getInt32(dataOff + 24, true);
1768
+ const cyDest = view.getInt32(dataOff + 28, true);
1769
+ const rop = view.getUint32(dataOff + 32, true);
1770
+ const xSrc = view.getInt32(dataOff + 36, true);
1771
+ const ySrc = view.getInt32(dataOff + 40, true);
1772
+ const m11 = view.getFloat32(dataOff + 44, true);
1773
+ const m12 = view.getFloat32(dataOff + 48, true);
1774
+ const m21 = view.getFloat32(dataOff + 52, true);
1775
+ const m22 = view.getFloat32(dataOff + 56, true);
1776
+ const mdx = view.getFloat32(dataOff + 60, true);
1777
+ const mdy = view.getFloat32(dataOff + 64, true);
1778
+ const cxSrc = stretch ? view.getInt32(dataOff + 92, true) : cxDest;
1779
+ const cySrc = stretch ? view.getInt32(dataOff + 96, true) : cyDest;
1780
+ const source = recSize >= 100 ? sourceOf(
1781
+ offset,
1782
+ view.getUint32(dataOff + 76, true),
1783
+ view.getUint32(dataOff + 80, true),
1784
+ view.getUint32(dataOff + 84, true),
1785
+ view.getUint32(dataOff + 88, true)
1786
+ ) : null;
1787
+ const identity = m11 === 0 && m22 === 0 && m12 === 0 && m21 === 0;
1788
+ const a = identity ? 1 : m11;
1789
+ const d = identity ? 1 : m22;
1790
+ executeBlit(rCtx, {
1791
+ plan: classifyRop3(rop),
1792
+ dx: gmx(rCtx, xDest),
1793
+ dy: gmy(rCtx, yDest),
1794
+ dw: gmw(rCtx, cxDest),
1795
+ dh: gmh(rCtx, cyDest),
1796
+ source,
1797
+ sx: a * xSrc + (identity ? 0 : m21 * ySrc + mdx),
1798
+ sy: d * ySrc + (identity ? 0 : m12 * xSrc + mdy),
1799
+ sw: a * cxSrc,
1800
+ sh: d * cySrc,
1801
+ dibOrigin: "top-left"
1802
+ });
1803
+ return true;
1804
+ }
1805
+ function handleStretchDibits(rCtx, offset, dataOff, recSize) {
1806
+ const { view } = rCtx;
1807
+ if (recSize < 80) {
1808
+ return true;
1809
+ }
1810
+ executeBlit(rCtx, {
1811
+ plan: classifyRop3(view.getUint32(dataOff + 60, true)),
1812
+ dx: gmx(rCtx, view.getInt32(dataOff + 16, true)),
1813
+ dy: gmy(rCtx, view.getInt32(dataOff + 20, true)),
1814
+ dw: gmw(rCtx, view.getInt32(dataOff + 64, true)),
1815
+ dh: gmh(rCtx, view.getInt32(dataOff + 68, true)),
1816
+ source: sourceOf(
1817
+ offset,
1818
+ view.getUint32(dataOff + 40, true),
1819
+ view.getUint32(dataOff + 44, true),
1820
+ view.getUint32(dataOff + 48, true),
1821
+ view.getUint32(dataOff + 52, true)
1822
+ ),
1823
+ sx: view.getInt32(dataOff + 24, true),
1824
+ sy: view.getInt32(dataOff + 28, true),
1825
+ sw: view.getInt32(dataOff + 32, true),
1826
+ sh: view.getInt32(dataOff + 36, true),
1827
+ dibOrigin: "bottom-left"
1828
+ });
1829
+ return true;
1830
+ }
1831
+ function handleEmfGdiBitmapRecord(rCtx, recType, offset, dataOff, recSize) {
1832
+ switch (recType) {
1833
+ case EMR_BITBLT:
1834
+ return handleBlt(rCtx, offset, dataOff, recSize, false);
1835
+ case EMR_STRETCHBLT:
1836
+ return handleBlt(rCtx, offset, dataOff, recSize, true);
1837
+ case EMR_STRETCHDIBITS:
1838
+ return handleStretchDibits(rCtx, offset, dataOff, recSize);
1839
+ default:
1840
+ return false;
1841
+ }
1842
+ }
1843
+
1844
+ // src/emf-gdi-draw-text.ts
1845
+ function readDxArray(view, offset, dataOff, nChars, viewEnd) {
1846
+ const offDx = view.getUint32(dataOff + 64, true);
1847
+ if (offDx === 0) {
1848
+ return null;
1849
+ }
1850
+ const start = offset + offDx;
1851
+ const end = start + nChars * 4;
1852
+ if (start < 0 || end > viewEnd) {
1853
+ return null;
1854
+ }
1855
+ const dx = [];
1856
+ for (let i = 0; i < nChars; i++) {
1857
+ dx.push(view.getUint32(start + i * 4, true));
1858
+ }
1859
+ return dx;
1860
+ }
1861
+ function horizontalAlign(textAlign) {
1862
+ if (textAlign & 2) {
1863
+ return "right";
1864
+ }
1865
+ if (textAlign & 6) {
1866
+ return "center";
1867
+ }
1868
+ return "left";
1869
+ }
1870
+ function verticalBaseline(textAlign) {
1871
+ const vAlign = textAlign & 24;
1872
+ return vAlign === 24 ? "alphabetic" : vAlign === 8 ? "bottom" : "top";
1873
+ }
1874
+ function paintRun(ctx, state, text, dxDevice, startX, y, align, fontScale) {
1875
+ const totalWidth = dxDevice ? totalGlyphAdvance(dxDevice) : ctx.measureText(text).width;
1876
+ const runStartX = dxDevice ? startX + alignmentStartOffset(totalWidth, align) : startX;
1877
+ if (state.bkMode === 2) {
1878
+ const bgH = fontSizePx(state, fontScale);
1879
+ const prevFill = ctx.fillStyle;
1880
+ ctx.fillStyle = state.bkColor;
1881
+ ctx.fillRect(runStartX, y - bgH, totalWidth, bgH);
1882
+ ctx.fillStyle = prevFill;
1883
+ }
1884
+ ctx.fillStyle = state.textColor;
1885
+ if (dxDevice) {
1886
+ const offsets = cumulativeGlyphOffsets(dxDevice);
1887
+ const prevAlign = ctx.textAlign;
1888
+ ctx.textAlign = "left";
1889
+ for (let i = 0; i < text.length && i < offsets.length; i++) {
1890
+ ctx.fillText(text[i], runStartX + offsets[i], y);
1891
+ }
1892
+ ctx.textAlign = prevAlign;
1893
+ } else {
1894
+ ctx.fillText(text, startX, y);
1895
+ }
1896
+ if (state.fontUnderline || state.fontStrikeOut) {
1897
+ drawTextDecorations(ctx, state, runStartX, y, totalWidth, fontScale);
1898
+ }
1899
+ }
1900
+ function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
1901
+ const { ctx, view, state } = rCtx;
1902
+ if (recSize < 76) {
1903
+ return true;
1904
+ }
1905
+ const refX = view.getInt32(dataOff + 28, true);
1906
+ const refY = view.getInt32(dataOff + 32, true);
1907
+ const nChars = view.getUint32(dataOff + 36, true);
1908
+ const offString = view.getUint32(dataOff + 40, true);
1909
+ const viewEnd = view.byteLength;
1910
+ if (nChars === 0 || offString === 0 || offset + offString + nChars * 2 > viewEnd) {
1911
+ return true;
1912
+ }
1913
+ const text = readUtf16LE(view, offset + offString, nChars);
1914
+ if (text.length === 0) {
1915
+ return true;
1916
+ }
1917
+ const fontScale = Math.abs(gmh(rCtx, 1));
1918
+ applyFont(ctx, state, fontScale);
1919
+ const align = horizontalAlign(state.textAlign);
1920
+ ctx.textBaseline = verticalBaseline(state.textAlign);
1921
+ ctx.textAlign = align === "center" ? "center" : align === "right" ? "right" : "left";
1922
+ const dxLogical = readDxArray(view, offset, dataOff, nChars, viewEnd);
1923
+ const dxDevice = dxLogical ? dxLogical.map((v) => gmw(rCtx, v)) : null;
1924
+ if (dxDevice) {
1925
+ ctx.textAlign = "left";
1926
+ }
1927
+ const baseX = gmx(rCtx, refX);
1928
+ const baseY = gmy(rCtx, refY);
1929
+ const radians = escapementToCanvasRadians(state.fontEscapementTenthDeg);
1930
+ if (radians !== 0) {
1931
+ ctx.save();
1932
+ ctx.translate(baseX, baseY);
1933
+ ctx.rotate(radians);
1934
+ paintRun(ctx, state, text, dxDevice, 0, 0, align, fontScale);
1935
+ ctx.restore();
1936
+ } else {
1937
+ paintRun(ctx, state, text, dxDevice, baseX, baseY, align, fontScale);
1938
+ }
1939
+ return true;
1940
+ }
1941
+ function handleEmfGdiDrawTextRecord(rCtx, recType, offset, dataOff, recSize) {
1942
+ if (recType === EMR_EXTTEXTOUTW) {
1943
+ return handleExtTextOutW(rCtx, offset, dataOff, recSize);
1944
+ }
1945
+ return false;
1946
+ }
1947
+
1948
+ // src/emf-clip-region.ts
1949
+ var CLIP_HUGE = 1 << 24;
1950
+ function rectClipShape(x, y, w, h) {
1951
+ return { cmds: [{ op: "rect", x, y, w, h }], fillRule: "nonzero", simple: true };
1952
+ }
1953
+ function rectsClipShape(rects) {
1954
+ return {
1955
+ cmds: rects.map((r) => ({ op: "rect", x: r.x, y: r.y, w: r.w, h: r.h })),
1956
+ fillRule: "nonzero",
1957
+ simple: true
1958
+ };
1959
+ }
1960
+ function emptyClipShape() {
1961
+ return { cmds: [{ op: "rect", x: 0, y: 0, w: 0, h: 0 }], fillRule: "nonzero", simple: true };
1962
+ }
1963
+ function translateClipShape(shape, dx, dy) {
1964
+ return {
1965
+ ...shape,
1966
+ cmds: shape.cmds.map((c) => {
1967
+ switch (c.op) {
1968
+ case "rect":
1969
+ return { ...c, x: c.x + dx, y: c.y + dy };
1970
+ case "moveTo":
1971
+ case "lineTo":
1972
+ return { ...c, x: c.x + dx, y: c.y + dy };
1973
+ case "bezierCurveTo":
1974
+ return {
1975
+ ...c,
1976
+ cp1x: c.cp1x + dx,
1977
+ cp1y: c.cp1y + dy,
1978
+ cp2x: c.cp2x + dx,
1979
+ cp2y: c.cp2y + dy,
1980
+ x: c.x + dx,
1981
+ y: c.y + dy
1982
+ };
1983
+ case "closePath":
1984
+ return c;
1985
+ }
1986
+ })
1987
+ };
1988
+ }
1989
+ function translateClipRegion(region, dx, dy) {
1990
+ if (!region) {
1991
+ return null;
1992
+ }
1993
+ return region.map((s) => translateClipShape(s, dx, dy));
1994
+ }
1995
+ function isComposable(shape) {
1996
+ return shape.simple && shape.fillRule === "nonzero";
1997
+ }
1998
+ function invertClipShape(shape) {
1999
+ return {
2000
+ cmds: [
2001
+ { op: "rect", x: -CLIP_HUGE, y: -CLIP_HUGE, w: 2 * CLIP_HUGE, h: 2 * CLIP_HUGE },
2002
+ ...shape.cmds
2003
+ ],
2004
+ fillRule: "evenodd",
2005
+ simple: false
2006
+ };
2007
+ }
2008
+ function combineClip(current, shape, op) {
2009
+ switch (op) {
2010
+ case "replace":
2011
+ return { region: [shape], exact: true };
2012
+ case "intersect":
2013
+ return { region: current ? [...current, shape] : [shape], exact: true };
2014
+ case "exclude": {
2015
+ if (!isComposable(shape)) {
2016
+ return { region: current ? [...current, shape] : [shape], exact: false };
2017
+ }
2018
+ const inv = invertClipShape(shape);
2019
+ return { region: current ? [...current, inv] : [inv], exact: true };
2020
+ }
2021
+ case "union": {
2022
+ if (!current) {
2023
+ return { region: null, exact: true };
2024
+ }
2025
+ if (current.length === 1 && isComposable(current[0]) && isComposable(shape)) {
2026
+ return {
2027
+ region: [
2028
+ { cmds: [...current[0].cmds, ...shape.cmds], fillRule: "nonzero", simple: false }
2029
+ ],
2030
+ exact: false
2031
+ };
2032
+ }
2033
+ return { region: current, exact: false };
2034
+ }
2035
+ case "xor": {
2036
+ if (!current) {
2037
+ if (isComposable(shape)) {
2038
+ return { region: [invertClipShape(shape)], exact: true };
2039
+ }
2040
+ return { region: [shape], exact: false };
2041
+ }
2042
+ if (current.length === 1 && isComposable(current[0]) && isComposable(shape)) {
2043
+ return {
2044
+ region: [
2045
+ { cmds: [...current[0].cmds, ...shape.cmds], fillRule: "evenodd", simple: false }
2046
+ ],
2047
+ exact: true
2048
+ };
2049
+ }
2050
+ if (isComposable(shape)) {
2051
+ return { region: [...current, invertClipShape(shape)], exact: false };
2052
+ }
2053
+ return { region: [...current, shape], exact: false };
2054
+ }
2055
+ case "complement": {
2056
+ if (!current) {
2057
+ return { region: [emptyClipShape()], exact: true };
2058
+ }
2059
+ if (current.length === 1 && isComposable(current[0])) {
2060
+ return { region: [shape, invertClipShape(current[0])], exact: true };
2061
+ }
2062
+ return { region: [shape], exact: false };
2063
+ }
2064
+ }
2065
+ }
2066
+ function combineClipRegions(current, incoming, op) {
2067
+ if (op === "replace") {
2068
+ return { region: incoming, exact: true };
2069
+ }
2070
+ if (incoming && incoming.length === 1) {
2071
+ return combineClip(current, incoming[0], op);
2072
+ }
2073
+ if (!incoming) {
2074
+ switch (op) {
2075
+ case "intersect":
2076
+ return { region: current, exact: true };
2077
+ case "union":
2078
+ return { region: null, exact: true };
2079
+ case "exclude":
2080
+ return { region: [emptyClipShape()], exact: true };
2081
+ case "xor":
2082
+ case "complement": {
2083
+ if (!current) {
2084
+ return { region: [emptyClipShape()], exact: true };
1493
2085
  }
1494
- ctx.fillText(text, gmx(rCtx, refX), gmy(rCtx, refY));
1495
- if (state.fontUnderline || state.fontStrikeOut) {
1496
- const w = ctx.measureText(text).width;
1497
- const baseX = gmx(rCtx, refX);
1498
- const startX = alignHoriz === "center" ? baseX - w / 2 : alignHoriz === "right" ? baseX - w : baseX;
1499
- drawTextDecorations(ctx, state, startX, gmy(rCtx, refY), w, fontScale);
2086
+ if (current.length === 1) {
2087
+ return combineClip(null, current[0], "exclude");
1500
2088
  }
2089
+ return { region: current, exact: false };
1501
2090
  }
1502
2091
  }
1503
2092
  }
1504
- return true;
2093
+ switch (op) {
2094
+ case "intersect":
2095
+ return { region: current ? [...current, ...incoming] : incoming, exact: true };
2096
+ case "union":
2097
+ return { region: current, exact: false };
2098
+ case "xor":
2099
+ return { region: current ?? incoming, exact: false };
2100
+ case "exclude":
2101
+ return { region: current, exact: false };
2102
+ case "complement": {
2103
+ if (!current) {
2104
+ return { region: [emptyClipShape()], exact: true };
2105
+ }
2106
+ if (current.length === 1) {
2107
+ return combineClip(incoming, current[0], "exclude");
2108
+ }
2109
+ return { region: incoming, exact: false };
2110
+ }
2111
+ }
1505
2112
  }
1506
- var ROP_PATCOPY = 15728673;
1507
- var BS_NULL = 1;
1508
- function handleBitBlt(rCtx, offset, dataOff, recSize) {
1509
- const { ctx, view, state } = rCtx;
1510
- if (recSize >= 96) {
1511
- const dstX = view.getInt32(dataOff + 16, true);
1512
- const dstY = view.getInt32(dataOff + 20, true);
1513
- const dstW = view.getInt32(dataOff + 24, true);
1514
- const dstH = view.getInt32(dataOff + 28, true);
1515
- const rop = view.getUint32(dataOff + 32, true);
1516
- const offBmiSrc = view.getUint32(dataOff + 76, true);
1517
- const cbBmiSrc = view.getUint32(dataOff + 80, true);
1518
- const offBitsSrc = view.getUint32(dataOff + 84, true);
1519
- const cbBitsSrc = view.getUint32(dataOff + 88, true);
1520
- if (offBmiSrc === 0 && rop === ROP_PATCOPY) {
1521
- const prevFill = ctx.fillStyle;
1522
- if (state.brushStyle !== BS_NULL) {
1523
- ctx.fillStyle = state.brushColor;
1524
- ctx.fillRect(gmx(rCtx, dstX), gmy(rCtx, dstY), gmw(rCtx, dstW), gmh(rCtx, dstH));
1525
- }
1526
- ctx.fillStyle = prevFill;
1527
- return true;
2113
+ function replayClipCmds(ctx, cmds) {
2114
+ for (const c of cmds) {
2115
+ switch (c.op) {
2116
+ case "rect":
2117
+ ctx.rect(c.x, c.y, c.w, c.h);
2118
+ break;
2119
+ case "moveTo":
2120
+ ctx.moveTo(c.x, c.y);
2121
+ break;
2122
+ case "lineTo":
2123
+ ctx.lineTo(c.x, c.y);
2124
+ break;
2125
+ case "bezierCurveTo":
2126
+ ctx.bezierCurveTo(c.cp1x, c.cp1y, c.cp2x, c.cp2y, c.x, c.y);
2127
+ break;
2128
+ case "closePath":
2129
+ ctx.closePath();
2130
+ break;
1528
2131
  }
1529
- if (offBmiSrc > 0 && cbBmiSrc > 0 && offBitsSrc > 0 && cbBitsSrc > 0) {
1530
- const imageData = decodeDibToImageData(
1531
- view,
1532
- offset + offBmiSrc,
1533
- offset + offBitsSrc,
1534
- cbBitsSrc
1535
- );
1536
- if (imageData) {
1537
- const temp = createTempCanvas(imageData.width, imageData.height);
1538
- if (temp) {
1539
- temp.ctx.putImageData(imageData, 0, 0);
1540
- canvasDrawImage(
1541
- ctx,
1542
- temp.canvas,
1543
- gmx(rCtx, dstX),
1544
- gmy(rCtx, dstY),
1545
- gmw(rCtx, dstW),
1546
- gmh(rCtx, dstH)
1547
- );
1548
- }
1549
- }
2132
+ }
2133
+ }
2134
+ function applyClipShapes(ctx, shapes) {
2135
+ for (const s of shapes) {
2136
+ ctx.beginPath();
2137
+ replayClipCmds(ctx, s.cmds);
2138
+ try {
2139
+ ctx.clip(s.fillRule);
2140
+ } catch {
1550
2141
  }
1551
2142
  }
1552
- return true;
1553
2143
  }
1554
- function handleStretchDibits(rCtx, offset, dataOff, recSize) {
1555
- const { ctx, view } = rCtx;
1556
- if (recSize >= 80) {
1557
- const dstX = view.getInt32(dataOff + 16, true);
1558
- const dstY = view.getInt32(dataOff + 20, true);
1559
- const dstW = view.getInt32(dataOff + 64, true);
1560
- const dstH = view.getInt32(dataOff + 68, true);
1561
- const offBmiSrc = view.getUint32(dataOff + 40, true);
1562
- const cbBmiSrc = view.getUint32(dataOff + 44, true);
1563
- const offBitsSrc = view.getUint32(dataOff + 48, true);
1564
- const cbBitsSrc = view.getUint32(dataOff + 52, true);
1565
- if (offBmiSrc > 0 && cbBmiSrc > 0 && offBitsSrc > 0 && cbBitsSrc > 0) {
1566
- const imageData = decodeDibToImageData(
1567
- view,
1568
- offset + offBmiSrc,
1569
- offset + offBitsSrc,
1570
- cbBitsSrc
1571
- );
1572
- if (imageData) {
1573
- const temp = createTempCanvas(imageData.width, imageData.height);
1574
- if (temp) {
1575
- temp.ctx.putImageData(imageData, 0, 0);
1576
- canvasDrawImage(
1577
- ctx,
1578
- temp.canvas,
1579
- gmx(rCtx, dstX),
1580
- gmy(rCtx, dstY),
1581
- gmw(rCtx, dstW),
1582
- gmh(rCtx, dstH)
1583
- );
1584
- }
1585
- }
2144
+ function reapplyClipRegion(holder, region, identityTransform = false) {
2145
+ const { ctx } = holder;
2146
+ while (holder.clipSaveDepth > 0) {
2147
+ ctx.restore();
2148
+ holder.clipSaveDepth--;
2149
+ }
2150
+ if (region) {
2151
+ ctx.save();
2152
+ holder.clipSaveDepth = 1;
2153
+ if (identityTransform) {
2154
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
1586
2155
  }
2156
+ applyClipShapes(ctx, region);
1587
2157
  }
1588
- return true;
1589
2158
  }
2159
+
2160
+ // src/emf-gdi-clip-records.ts
1590
2161
  function gdiCombineClip(rCtx, shape, op) {
1591
2162
  const { ctx } = rCtx;
1592
2163
  if (rCtx.clipUntracked && op !== "replace") {
@@ -1718,14 +2289,8 @@ function handleOffsetClipRgn(rCtx, dataOff, recSize) {
1718
2289
  }
1719
2290
  return true;
1720
2291
  }
1721
- function handleEmfGdiTextBitmapRecord(rCtx, recType, offset, dataOff, recSize) {
2292
+ function handleEmfGdiClipRecord(rCtx, recType, dataOff, recSize) {
1722
2293
  switch (recType) {
1723
- case EMR_EXTTEXTOUTW:
1724
- return handleExtTextOutW(rCtx, offset, dataOff, recSize);
1725
- case EMR_BITBLT:
1726
- return handleBitBlt(rCtx, offset, dataOff, recSize);
1727
- case EMR_STRETCHDIBITS:
1728
- return handleStretchDibits(rCtx, offset, dataOff, recSize);
1729
2294
  case EMR_INTERSECTCLIPRECT:
1730
2295
  return handleIntersectClipRect(rCtx, dataOff, recSize);
1731
2296
  case EMR_EXTSELECTCLIPRGN:
@@ -1739,6 +2304,11 @@ function handleEmfGdiTextBitmapRecord(rCtx, recType, offset, dataOff, recSize) {
1739
2304
  }
1740
2305
  }
1741
2306
 
2307
+ // src/emf-gdi-draw-text-bitmap.ts
2308
+ function handleEmfGdiTextBitmapRecord(rCtx, recType, offset, dataOff, recSize) {
2309
+ return handleEmfGdiDrawTextRecord(rCtx, recType, offset, dataOff, recSize) || handleEmfGdiBitmapRecord(rCtx, recType, offset, dataOff, recSize) || handleEmfGdiClipRecord(rCtx, recType, dataOff, recSize);
2310
+ }
2311
+
1742
2312
  // src/emf-gdi-draw-handlers.ts
1743
2313
  function handleEmfGdiDrawRecord(rCtx, recType, offset, dataOff, recSize) {
1744
2314
  return handleEmfGdiShapeRecord(rCtx, recType, dataOff, recSize) || handleEmfGdiTextBitmapRecord(rCtx, recType, offset, dataOff, recSize);
@@ -2091,10 +2661,28 @@ function handleEmfObjectRecord(rCtx, recType, dataOff, recSize) {
2091
2661
  const ihBrush = view.getUint32(dataOff, true);
2092
2662
  const brushStyle = view.getUint32(dataOff + 4, true);
2093
2663
  const color = readColorRef(view, dataOff + 8);
2664
+ const hatch = view.getUint32(dataOff + 12, true);
2094
2665
  rCtx.objectTable.set(ihBrush, {
2095
2666
  kind: "brush",
2096
2667
  style: brushStyle,
2097
- color
2668
+ color,
2669
+ ...brushStyle === 2 && hatch <= 5 ? { pattern: { kind: "hatch", hatch } } : {}
2670
+ });
2671
+ }
2672
+ return true;
2673
+ }
2674
+ case EMR_CREATEMONOBRUSH:
2675
+ case EMR_CREATEDIBPATTERNBRUSHPT: {
2676
+ if (recSize >= 32) {
2677
+ const ihBrush = view.getUint32(dataOff, true);
2678
+ const mono = recType === EMR_CREATEMONOBRUSH;
2679
+ const pattern = parsePatternBrush(view, dataOff - 8, dataOff, mono);
2680
+ rCtx.objectTable.set(ihBrush, {
2681
+ kind: "brush",
2682
+ style: pattern ? mono ? 3 : 6 : 0,
2683
+ // Flat stand-in for fills that do not realise the pattern.
2684
+ color: "#808080",
2685
+ ...pattern ? { pattern } : {}
2098
2686
  });
2099
2687
  }
2100
2688
  return true;
@@ -2103,19 +2691,23 @@ function handleEmfObjectRecord(rCtx, recType, dataOff, recSize) {
2103
2691
  if (recSize >= 332) {
2104
2692
  const ihFont = view.getUint32(dataOff, true);
2105
2693
  const height = view.getInt32(dataOff + 4, true);
2694
+ const escapementTenthDeg = view.getInt32(dataOff + 12, true);
2106
2695
  const weight = view.getInt32(dataOff + 20, true);
2107
2696
  const italic = view.getUint8(dataOff + 24);
2108
2697
  const underline = view.getUint8(dataOff + 25);
2109
2698
  const strikeOut = view.getUint8(dataOff + 26);
2110
- const family = readUtf16LE(view, dataOff + 28, 32) || "sans-serif";
2699
+ const family = readUtf16LE(view, dataOff + 32, 32) || "sans-serif";
2111
2700
  rCtx.objectTable.set(ihFont, {
2112
2701
  kind: "font",
2113
- height: Math.abs(height),
2702
+ // Sign kept (not Math.abs'd): resolveFontPixelHeight() needs it
2703
+ // to distinguish GDI's cell-height vs character-height convention.
2704
+ height,
2114
2705
  weight,
2115
2706
  italic: italic !== 0,
2116
2707
  underline: underline !== 0,
2117
2708
  strikeOut: strikeOut !== 0,
2118
- family
2709
+ family,
2710
+ escapementTenthDeg
2119
2711
  });
2120
2712
  }
2121
2713
  return true;
@@ -2134,6 +2726,7 @@ function handleEmfObjectRecord(rCtx, recType, dataOff, recSize) {
2134
2726
  case "brush":
2135
2727
  state.brushStyle = obj.style;
2136
2728
  state.brushColor = obj.color;
2729
+ state.brushPattern = obj.pattern ?? null;
2137
2730
  break;
2138
2731
  case "font":
2139
2732
  state.fontHeight = obj.height;
@@ -2142,6 +2735,7 @@ function handleEmfObjectRecord(rCtx, recType, dataOff, recSize) {
2142
2735
  state.fontUnderline = obj.underline;
2143
2736
  state.fontStrikeOut = obj.strikeOut;
2144
2737
  state.fontFamily = obj.family;
2738
+ state.fontEscapementTenthDeg = obj.escapementTenthDeg ?? 0;
2145
2739
  break;
2146
2740
  }
2147
2741
  }
@@ -2310,15 +2904,22 @@ function defaultState() {
2310
2904
  penStyle: 0,
2311
2905
  brushColor: "#ffffff",
2312
2906
  brushStyle: 0,
2907
+ brushPattern: null,
2908
+ brushOrgX: 0,
2909
+ brushOrgY: 0,
2910
+ stretchBltMode: 1,
2313
2911
  textColor: "#000000",
2314
2912
  bkColor: "#ffffff",
2315
2913
  bkMode: 1,
2316
- fontHeight: 12,
2914
+ // Negative (character-height convention) so the no-font-selected default
2915
+ // resolves to exactly 12px, matching this library's historical fallback.
2916
+ fontHeight: -12,
2317
2917
  fontWeight: 400,
2318
2918
  fontItalic: false,
2319
2919
  fontFamily: "sans-serif",
2320
2920
  fontUnderline: false,
2321
2921
  fontStrikeOut: false,
2922
+ fontEscapementTenthDeg: 0,
2322
2923
  rop2: 13,
2323
2924
  curX: 0,
2324
2925
  curY: 0,
@@ -2435,7 +3036,19 @@ function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
2435
3036
  }
2436
3037
  return true;
2437
3038
  }
2438
- case EMR_SETSTRETCHBLTMODE:
3039
+ case EMR_SETSTRETCHBLTMODE: {
3040
+ if (recSize >= 12) {
3041
+ state.stretchBltMode = view.getUint32(dataOff, true);
3042
+ }
3043
+ return true;
3044
+ }
3045
+ case EMR_SETBRUSHORGEX: {
3046
+ if (recSize >= 16) {
3047
+ state.brushOrgX = view.getInt32(dataOff, true);
3048
+ state.brushOrgY = view.getInt32(dataOff + 4, true);
3049
+ }
3050
+ return true;
3051
+ }
2439
3052
  case EMR_SETMITERLIMIT:
2440
3053
  case EMR_SETTEXTALIGN: {
2441
3054
  if (recType === EMR_SETTEXTALIGN && recSize >= 12) {
@@ -2478,6 +3091,375 @@ function readPointFromView(view, offset, compressed) {
2478
3091
  };
2479
3092
  }
2480
3093
 
3094
+ // src/emf-plus-brush-gradient.ts
3095
+ var IDENTITY = [1, 0, 0, 1, 0, 0];
3096
+ function mulMatrix(a, b) {
3097
+ return [
3098
+ a[0] * b[0] + a[2] * b[1],
3099
+ a[1] * b[0] + a[3] * b[1],
3100
+ a[0] * b[2] + a[2] * b[3],
3101
+ a[1] * b[2] + a[3] * b[3],
3102
+ a[0] * b[4] + a[2] * b[5] + a[4],
3103
+ a[1] * b[4] + a[3] * b[5] + a[5]
3104
+ ];
3105
+ }
3106
+ function invertLinear(m) {
3107
+ const det = m[0] * m[3] - m[1] * m[2];
3108
+ if (!Number.isFinite(det) || Math.abs(det) < 1e-12) {
3109
+ return null;
3110
+ }
3111
+ return [m[3] / det, -m[1] / det, -m[2] / det, m[0] / det];
3112
+ }
3113
+ function lerpArgb(a, b, t) {
3114
+ const ch = (shift) => {
3115
+ const ca = a >>> shift & 255;
3116
+ const cb = b >>> shift & 255;
3117
+ return Math.round(ca + (cb - ca) * t);
3118
+ };
3119
+ return (ch(24) << 24 | ch(16) << 16 | ch(8) << 8 | ch(0)) >>> 0;
3120
+ }
3121
+ function piecewise(xs, ys, x) {
3122
+ const n = Math.min(xs.length, ys.length);
3123
+ if (n === 0) {
3124
+ return x;
3125
+ }
3126
+ if (x <= xs[0]) {
3127
+ return ys[0];
3128
+ }
3129
+ for (let i = 1; i < n; i++) {
3130
+ if (x <= xs[i]) {
3131
+ const span = xs[i] - xs[i - 1];
3132
+ const f = span > 0 ? (x - xs[i - 1]) / span : 1;
3133
+ return ys[i - 1] + (ys[i] - ys[i - 1]) * f;
3134
+ }
3135
+ }
3136
+ return ys[n - 1];
3137
+ }
3138
+ function stopColorAt(stops, t) {
3139
+ if (stops.length === 0 || stops.some((s) => s.argb === void 0)) {
3140
+ return null;
3141
+ }
3142
+ if (t <= stops[0].offset) {
3143
+ return stops[0].argb;
3144
+ }
3145
+ for (let i = 1; i < stops.length; i++) {
3146
+ const b = stops[i];
3147
+ if (t <= b.offset) {
3148
+ const a = stops[i - 1];
3149
+ const span = b.offset - a.offset;
3150
+ return lerpArgb(a.argb, b.argb, span > 0 ? (t - a.offset) / span : 1);
3151
+ }
3152
+ }
3153
+ return stops[stops.length - 1].argb;
3154
+ }
3155
+ function pathGradientColorAt(shape, x, y) {
3156
+ const { center, boundary, boundaryArgb } = shape;
3157
+ const n = boundary.length;
3158
+ const px = x - center.x;
3159
+ const py = y - center.y;
3160
+ let found = -1;
3161
+ let bestS = 0;
3162
+ let bestU = 0;
3163
+ const eps = 1e-9;
3164
+ for (let i = 0; i < n; i++) {
3165
+ const v0 = boundary[i];
3166
+ const v1 = boundary[(i + 1) % n];
3167
+ const ax = v0.x - center.x;
3168
+ const ay = v0.y - center.y;
3169
+ const ex = v1.x - v0.x;
3170
+ const ey = v1.y - v0.y;
3171
+ const det = ax * ey - ay * ex;
3172
+ if (Math.abs(det) < 1e-12) {
3173
+ continue;
3174
+ }
3175
+ const alpha = (px * ey - py * ex) / det;
3176
+ const beta = (ax * py - ay * px) / det;
3177
+ if (alpha < -eps || beta < -eps || beta > alpha + eps || alpha > 1 + eps) {
3178
+ continue;
3179
+ }
3180
+ found = i;
3181
+ bestS = Math.max(0, alpha);
3182
+ bestU = alpha > 0 ? Math.min(1, Math.max(0, beta / alpha)) : 0;
3183
+ }
3184
+ if (found < 0) {
3185
+ return null;
3186
+ }
3187
+ let s = bestS;
3188
+ if (shape.focus) {
3189
+ const f = Math.min(0.999, Math.max(0, (shape.focus.x + shape.focus.y) / 2));
3190
+ s = s <= f ? 0 : (s - f) / (1 - f);
3191
+ }
3192
+ const pos = 1 - s;
3193
+ if (shape.preset && shape.preset.positions.length > 0) {
3194
+ const { positions, argb } = shape.preset;
3195
+ if (pos <= positions[0]) {
3196
+ return argb[0];
3197
+ }
3198
+ for (let k = 1; k < positions.length; k++) {
3199
+ if (pos <= positions[k]) {
3200
+ const span = positions[k] - positions[k - 1];
3201
+ return lerpArgb(argb[k - 1], argb[k], span > 0 ? (pos - positions[k - 1]) / span : 1);
3202
+ }
3203
+ }
3204
+ return argb[positions.length - 1];
3205
+ }
3206
+ const c0 = boundaryArgb[found] ?? shape.centerArgb;
3207
+ const c1 = boundaryArgb[(found + 1) % n] ?? c0;
3208
+ const surround = lerpArgb(c0, c1, bestU);
3209
+ const factor = shape.blend ? piecewise(shape.blend.positions, shape.blend.factors, pos) : pos;
3210
+ return lerpArgb(surround, shape.centerArgb, Math.min(1, Math.max(0, factor)));
3211
+ }
3212
+ var MAX_TILE = 2048;
3213
+ function mirrorFlags(wrap) {
3214
+ return {
3215
+ x: wrap === "tile-flip-x" || wrap === "tile-flip-xy",
3216
+ y: wrap === "tile-flip-y" || wrap === "tile-flip-xy"
3217
+ };
3218
+ }
3219
+ function buildPattern(ctx, rect, tw, th, wrap, brush, delta, sampling, colorAt) {
3220
+ if (typeof ctx.createPattern !== "function") {
3221
+ return null;
3222
+ }
3223
+ const mirror = wrap === "clamp" ? { x: false, y: false } : mirrorFlags(wrap);
3224
+ const w = tw * (mirror.x ? 2 : 1);
3225
+ const h = th * (mirror.y ? 2 : 1);
3226
+ const temp = createTempCanvas(w, h);
3227
+ if (!temp) {
3228
+ return null;
3229
+ }
3230
+ const stepX = rect.w / tw;
3231
+ const stepY = rect.h / th;
3232
+ const offX = (i) => {
3233
+ const d = i % tw * stepX + (0.5 + sampling.phaseX) * stepX;
3234
+ return i < tw ? d : rect.w - sampling.lagX - (d - (0.5 + sampling.phaseX) * stepX) - 0.5 * stepX;
3235
+ };
3236
+ const offY = (j) => {
3237
+ const d = (j % th + 0.5) * stepY;
3238
+ return j < th ? d : rect.h - sampling.lagY - d;
3239
+ };
3240
+ const data = new Uint8ClampedArray(w * h * 4);
3241
+ for (let j = 0; j < h; j++) {
3242
+ const by = rect.y + offY(j);
3243
+ for (let i = 0; i < w; i++) {
3244
+ const c = colorAt(rect.x + offX(i), by);
3245
+ if (c === null) {
3246
+ continue;
3247
+ }
3248
+ const o = (j * w + i) * 4;
3249
+ data[o] = c >>> 16 & 255;
3250
+ data[o + 1] = c >>> 8 & 255;
3251
+ data[o + 2] = c & 255;
3252
+ data[o + 3] = c >>> 24 & 255;
3253
+ }
3254
+ }
3255
+ canvasPutImageData(temp.ctx, createImageDataCompat(data, w, h), 0, 0);
3256
+ const pattern = canvasCreatePattern(ctx, temp.canvas, wrap === "clamp" ? "no-repeat" : "repeat");
3257
+ if (!pattern || typeof pattern.setTransform !== "function") {
3258
+ return null;
3259
+ }
3260
+ const place = [stepX, 0, 0, stepY, rect.x + sampling.phaseX * stepX, rect.y];
3261
+ const m = mulMatrix([1, 0, 0, 1, delta.x, delta.y], mulMatrix(brush, place));
3262
+ try {
3263
+ pattern.setTransform({ a: m[0], b: m[1], c: m[2], d: m[3], e: m[4], f: m[5] });
3264
+ } catch {
3265
+ return null;
3266
+ }
3267
+ return pattern;
3268
+ }
3269
+ function halfPixelDelta(device) {
3270
+ const inv = invertLinear(device);
3271
+ if (!inv) {
3272
+ return { x: 0, y: 0 };
3273
+ }
3274
+ return { x: inv[0] * 0.5 + inv[2] * 0.5, y: inv[1] * 0.5 + inv[3] * 0.5 };
3275
+ }
3276
+ var SUPERSAMPLE = 4;
3277
+ function texelsFor(full, axis, length) {
3278
+ const scale = axis === "x" ? Math.hypot(full[0], full[1]) : Math.hypot(full[2], full[3]);
3279
+ return Math.max(2, Math.min(MAX_TILE, Math.ceil(Math.abs(length) * scale * SUPERSAMPLE)));
3280
+ }
3281
+ function linearGradientEndpoints(rect, transform) {
3282
+ const m = transform ?? IDENTITY;
3283
+ const map = (x, y) => ({
3284
+ x: m[0] * x + m[2] * y + m[4],
3285
+ y: m[1] * x + m[3] * y + m[5]
3286
+ });
3287
+ const p1 = map(rect.x, rect.y + rect.h / 2);
3288
+ const pe = map(rect.x + rect.w, rect.y + rect.h / 2);
3289
+ const nx = m[3];
3290
+ const ny = -m[2];
3291
+ const nn = nx * nx + ny * ny;
3292
+ if (nn < 1e-12) {
3293
+ return { x1: p1.x, y1: p1.y, x2: pe.x, y2: pe.y };
3294
+ }
3295
+ const t = ((pe.x - p1.x) * nx + (pe.y - p1.y) * ny) / nn;
3296
+ return { x1: p1.x, y1: p1.y, x2: p1.x + nx * t, y2: p1.y + ny * t };
3297
+ }
3298
+ function plainLinear(ctx, grad) {
3299
+ if (typeof ctx.createLinearGradient !== "function") {
3300
+ return null;
3301
+ }
3302
+ const pts = grad.rect ? linearGradientEndpoints(grad.rect, grad.transform) : grad;
3303
+ if (pts.x1 === pts.x2 && pts.y1 === pts.y2) {
3304
+ return null;
3305
+ }
3306
+ const g = ctx.createLinearGradient(pts.x1, pts.y1, pts.x2, pts.y2);
3307
+ for (const stop of grad.stops) {
3308
+ g.addColorStop(stop.offset, stop.color);
3309
+ }
3310
+ return g;
3311
+ }
3312
+ var SEAM_BIAS = 1e-5;
3313
+ var MAX_PERIODS = 2048;
3314
+ function surfaceSize(ctx) {
3315
+ const canvas = ctx.canvas;
3316
+ const w = canvas?.width;
3317
+ const h = canvas?.height;
3318
+ return typeof w === "number" && typeof h === "number" && w > 0 && h > 0 ? { w, h } : null;
3319
+ }
3320
+ function tiledLinear(ctx, grad, rect, device) {
3321
+ const size = surfaceSize(ctx);
3322
+ const inv = invertLinear(device);
3323
+ if (!size || !inv || typeof ctx.createLinearGradient !== "function") {
3324
+ return null;
3325
+ }
3326
+ const e = linearGradientEndpoints(rect, grad.transform);
3327
+ const delta = halfPixelDelta(device);
3328
+ const dx = e.x2 - e.x1;
3329
+ const dy = e.y2 - e.y1;
3330
+ const x1 = e.x1 + delta.x + dx * SEAM_BIAS;
3331
+ const y1 = e.y1 + delta.y + dy * SEAM_BIAS;
3332
+ const len2 = dx * dx + dy * dy;
3333
+ if (!(len2 > 1e-12)) {
3334
+ return null;
3335
+ }
3336
+ let tMin = Infinity;
3337
+ let tMax = -Infinity;
3338
+ for (const [cx, cy] of [
3339
+ [0, 0],
3340
+ [size.w, 0],
3341
+ [0, size.h],
3342
+ [size.w, size.h]
3343
+ ]) {
3344
+ const px = cx - device[4];
3345
+ const py = cy - device[5];
3346
+ const wx = inv[0] * px + inv[2] * py;
3347
+ const wy = inv[1] * px + inv[3] * py;
3348
+ const t = ((wx - x1) * dx + (wy - y1) * dy) / len2;
3349
+ tMin = Math.min(tMin, t);
3350
+ tMax = Math.max(tMax, t);
3351
+ }
3352
+ const k0 = Math.floor(tMin);
3353
+ const k1 = Math.max(k0 + 1, Math.ceil(tMax));
3354
+ if (k1 - k0 > MAX_PERIODS) {
3355
+ return null;
3356
+ }
3357
+ const g = ctx.createLinearGradient(x1 + dx * k0, y1 + dy * k0, x1 + dx * k1, y1 + dy * k1);
3358
+ const span = k1 - k0;
3359
+ const mirrorX = mirrorFlags(grad.wrapMode).x;
3360
+ const reversed = [...grad.stops].reverse();
3361
+ for (let k = k0; k < k1; k++) {
3362
+ const mirrored = mirrorX && (k % 2 + 2) % 2 === 1;
3363
+ for (const stop of mirrored ? reversed : grad.stops) {
3364
+ const local = mirrored ? 1 - stop.offset : stop.offset;
3365
+ g.addColorStop(Math.min(1, Math.max(0, (k - k0 + local) / span)), stop.color);
3366
+ }
3367
+ }
3368
+ return g;
3369
+ }
3370
+ function linearPaint(ctx, grad, device) {
3371
+ const rect = grad.rect;
3372
+ if (grad.wrapMode !== "clamp" && rect && rect.w !== 0) {
3373
+ const unrolled = tiledLinear(ctx, grad, rect, device);
3374
+ if (unrolled) {
3375
+ return unrolled;
3376
+ }
3377
+ if (stopColorAt(grad.stops, 0) !== null) {
3378
+ const brush = grad.transform ?? IDENTITY;
3379
+ const tw = texelsFor(mulMatrix(device, brush), "x", rect.w);
3380
+ const pattern = buildPattern(
3381
+ ctx,
3382
+ rect,
3383
+ tw,
3384
+ 1,
3385
+ grad.wrapMode,
3386
+ brush,
3387
+ halfPixelDelta(device),
3388
+ { phaseX: 0.5, lagX: 0, lagY: 0 },
3389
+ (bx) => stopColorAt(grad.stops, (bx - rect.x) / rect.w)
3390
+ );
3391
+ if (pattern) {
3392
+ return pattern;
3393
+ }
3394
+ }
3395
+ }
3396
+ return plainLinear(ctx, grad);
3397
+ }
3398
+ function boundaryBox(points) {
3399
+ if (points.length < 3) {
3400
+ return null;
3401
+ }
3402
+ let x0 = Infinity;
3403
+ let y0 = Infinity;
3404
+ let x1 = -Infinity;
3405
+ let y1 = -Infinity;
3406
+ for (const p of points) {
3407
+ x0 = Math.min(x0, p.x);
3408
+ y0 = Math.min(y0, p.y);
3409
+ x1 = Math.max(x1, p.x);
3410
+ y1 = Math.max(y1, p.y);
3411
+ }
3412
+ return x1 > x0 && y1 > y0 ? { x: x0, y: y0, w: x1 - x0, h: y1 - y0 } : null;
3413
+ }
3414
+ function pathPaint(ctx, shape, wrap, device) {
3415
+ const box = boundaryBox(shape.boundary);
3416
+ if (!box) {
3417
+ return null;
3418
+ }
3419
+ const brush = shape.transform ?? IDENTITY;
3420
+ const full = mulMatrix(device, brush);
3421
+ const pxX = Math.hypot(full[0], full[1]);
3422
+ const pxY = Math.hypot(full[2], full[3]);
3423
+ return buildPattern(
3424
+ ctx,
3425
+ box,
3426
+ texelsFor(full, "x", box.w),
3427
+ texelsFor(full, "y", box.h),
3428
+ wrap,
3429
+ brush,
3430
+ halfPixelDelta(device),
3431
+ { phaseX: 0, lagX: pxX > 0 ? 1 / pxX : 0, lagY: pxY > 0 ? 1 / pxY : 0 },
3432
+ (bx, by) => pathGradientColorAt(shape, bx, by)
3433
+ );
3434
+ }
3435
+ function radialFallback(ctx, grad) {
3436
+ if (!(grad.r > 0) || typeof ctx.createRadialGradient !== "function") {
3437
+ return null;
3438
+ }
3439
+ const g = ctx.createRadialGradient(grad.cx, grad.cy, 0, grad.cx, grad.cy, grad.r);
3440
+ for (const stop of grad.stops) {
3441
+ g.addColorStop(stop.offset, stop.color);
3442
+ }
3443
+ return g;
3444
+ }
3445
+ function createBrushGradient(ctx, grad, device = IDENTITY) {
3446
+ try {
3447
+ if (grad.type === "linear") {
3448
+ return linearPaint(ctx, grad, device);
3449
+ }
3450
+ if (grad.shape) {
3451
+ const pattern = pathPaint(ctx, grad.shape, grad.wrapMode, device);
3452
+ if (pattern) {
3453
+ return pattern;
3454
+ }
3455
+ emfLog("createBrushGradient: path gradient pattern unavailable, using the radial approximation");
3456
+ }
3457
+ return radialFallback(ctx, grad);
3458
+ } catch {
3459
+ return null;
3460
+ }
3461
+ }
3462
+
2481
3463
  // src/emf-plus-path.ts
2482
3464
  function parseEmfPlusPath(data, off, maxLen) {
2483
3465
  if (maxLen < 12) {
@@ -2612,32 +3594,6 @@ function multiplyMatrix(m1, m2) {
2612
3594
  m1[4] * m2[1] + m1[5] * m2[3] + m2[5]
2613
3595
  ];
2614
3596
  }
2615
- function createBrushGradient(rCtx, grad) {
2616
- const ctx = rCtx.ctx;
2617
- try {
2618
- let g = null;
2619
- if (grad.type === "linear" && typeof ctx.createLinearGradient === "function") {
2620
- if (grad.x1 === grad.x2 && grad.y1 === grad.y2) {
2621
- return null;
2622
- }
2623
- g = ctx.createLinearGradient(grad.x1, grad.y1, grad.x2, grad.y2);
2624
- } else if (grad.type === "radial" && typeof ctx.createRadialGradient === "function") {
2625
- if (!(grad.r > 0)) {
2626
- return null;
2627
- }
2628
- g = ctx.createRadialGradient(grad.cx, grad.cy, 0, grad.cx, grad.cy, grad.r);
2629
- }
2630
- if (!g) {
2631
- return null;
2632
- }
2633
- for (const stop of grad.stops) {
2634
- g.addColorStop(stop.offset, stop.color);
2635
- }
2636
- return g;
2637
- } catch {
2638
- return null;
2639
- }
2640
- }
2641
3597
  function resolveBrushPaint(rCtx, flags, brushIdOrColor) {
2642
3598
  if (flags & 32768) {
2643
3599
  return argbToRgba(brushIdOrColor);
@@ -2645,7 +3601,7 @@ function resolveBrushPaint(rCtx, flags, brushIdOrColor) {
2645
3601
  const obj = rCtx.objectTable.get(brushIdOrColor & 255);
2646
3602
  if (obj && obj.kind === "plus-brush") {
2647
3603
  if (obj.gradient) {
2648
- const g = createBrushGradient(rCtx, obj.gradient);
3604
+ const g = createBrushGradient(rCtx.ctx, obj.gradient, plusWorldMatrix(rCtx));
2649
3605
  if (g) {
2650
3606
  return g;
2651
3607
  }
@@ -2680,18 +3636,14 @@ function getPageUnitMultiplier(pageUnit, pageScale) {
2680
3636
  }
2681
3637
  return unitToPixel * pageScale;
2682
3638
  }
2683
- function applyPlusWorldTransform(rCtx) {
3639
+ function plusWorldMatrix(rCtx) {
2684
3640
  const wt = rCtx.worldTransform;
2685
- const m = getPageUnitMultiplier(rCtx.pageUnit, rCtx.pageScale);
2686
- const d = rCtx.dpiScale;
2687
- rCtx.ctx.setTransform(
2688
- wt[0] * m * d,
2689
- wt[1] * m * d,
2690
- wt[2] * m * d,
2691
- wt[3] * m * d,
2692
- wt[4] * m * d,
2693
- wt[5] * m * d
2694
- );
3641
+ const k = getPageUnitMultiplier(rCtx.pageUnit, rCtx.pageScale) * rCtx.dpiScale;
3642
+ return [wt[0] * k, wt[1] * k, wt[2] * k, wt[3] * k, wt[4] * k, wt[5] * k];
3643
+ }
3644
+ function applyPlusWorldTransform(rCtx) {
3645
+ const m = plusWorldMatrix(rCtx);
3646
+ rCtx.ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]);
2695
3647
  }
2696
3648
  function pushState(rCtx, stackId) {
2697
3649
  rCtx.saveStack.push({
@@ -3214,6 +4166,7 @@ var BRUSH_DATA_PATH = 1;
3214
4166
  var BRUSH_DATA_TRANSFORM = 2;
3215
4167
  var BRUSH_DATA_PRESET_COLORS = 4;
3216
4168
  var BRUSH_DATA_BLEND_FACTORS_H = 8;
4169
+ var BRUSH_DATA_FOCUS_SCALES = 64;
3217
4170
  var MAX_GRADIENT_ELEMENTS = 4096;
3218
4171
  function looksLikeGraphicsVersion(v) {
3219
4172
  return v >>> 12 === 900097;
@@ -3234,8 +4187,22 @@ function applyMatrix(m, x, y) {
3234
4187
  function clamp01(v) {
3235
4188
  return Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : 0;
3236
4189
  }
4190
+ function readWrapMode(raw) {
4191
+ switch (raw) {
4192
+ case 0:
4193
+ return "tile";
4194
+ case 1:
4195
+ return "tile-flip-x";
4196
+ case 2:
4197
+ return "tile-flip-y";
4198
+ case 3:
4199
+ return "tile-flip-xy";
4200
+ default:
4201
+ return "clamp";
4202
+ }
4203
+ }
3237
4204
  function normaliseStops(stops) {
3238
- return stops.map((s) => ({ offset: clamp01(s.offset), color: s.color })).sort((a, b) => a.offset - b.offset);
4205
+ return stops.map((s) => ({ ...s, offset: clamp01(s.offset) })).sort((a, b) => a.offset - b.offset);
3239
4206
  }
3240
4207
  function readPresetColors(view, off, end) {
3241
4208
  if (off + 4 > end) {
@@ -3253,9 +4220,11 @@ function readPresetColors(view, off, end) {
3253
4220
  }
3254
4221
  const stops = [];
3255
4222
  for (let i = 0; i < count; i++) {
4223
+ const argb = view.getUint32(colOff + i * 4, true);
3256
4224
  stops.push({
3257
4225
  offset: view.getFloat32(posOff + i * 4, true),
3258
- color: argbToRgba(view.getUint32(colOff + i * 4, true))
4226
+ color: argbToRgba(argb),
4227
+ argb
3259
4228
  });
3260
4229
  }
3261
4230
  return { stops, next };
@@ -3288,6 +4257,7 @@ function parseLinearGradient(view, b, end) {
3288
4257
  return null;
3289
4258
  }
3290
4259
  const flags = view.getUint32(b, true);
4260
+ const wrapMode = readWrapMode(view.getUint32(b + 4, true));
3291
4261
  const rx = view.getFloat32(b + 8, true);
3292
4262
  const ry = view.getFloat32(b + 12, true);
3293
4263
  const rw = view.getFloat32(b + 16, true);
@@ -3301,8 +4271,8 @@ function parseLinearGradient(view, b, end) {
3301
4271
  o += 24;
3302
4272
  }
3303
4273
  let stops = [
3304
- { offset: 0, color: argbToRgba(startArgb) },
3305
- { offset: 1, color: argbToRgba(endArgb) }
4274
+ { offset: 0, color: argbToRgba(startArgb), argb: startArgb },
4275
+ { offset: 1, color: argbToRgba(endArgb), argb: endArgb }
3306
4276
  ];
3307
4277
  if (flags & BRUSH_DATA_PRESET_COLORS) {
3308
4278
  const preset = readPresetColors(view, o, end);
@@ -3314,7 +4284,8 @@ function parseLinearGradient(view, b, end) {
3314
4284
  if (blend) {
3315
4285
  stops = blend.entries.map((e) => ({
3316
4286
  offset: e.pos,
3317
- color: lerpArgbToRgba(startArgb, endArgb, e.factor)
4287
+ color: lerpArgbToRgba(startArgb, endArgb, e.factor),
4288
+ argb: lerpArgb2(startArgb, endArgb, e.factor)
3318
4289
  }));
3319
4290
  }
3320
4291
  }
@@ -3336,7 +4307,10 @@ function parseLinearGradient(view, b, end) {
3336
4307
  y1: p1.y,
3337
4308
  x2: p2.x,
3338
4309
  y2: p2.y,
3339
- stops: normaliseStops(stops)
4310
+ stops: normaliseStops(stops),
4311
+ wrapMode,
4312
+ rect: { x: rx, y: ry, w: rw, h: rh },
4313
+ transform
3340
4314
  }
3341
4315
  };
3342
4316
  }
@@ -3345,6 +4319,7 @@ function parsePathGradient(view, b, end) {
3345
4319
  return null;
3346
4320
  }
3347
4321
  const flags = view.getUint32(b, true);
4322
+ const wrapMode = readWrapMode(view.getUint32(b + 4, true));
3348
4323
  const centerArgb = view.getUint32(b + 8, true);
3349
4324
  let cx = view.getFloat32(b + 12, true);
3350
4325
  let cy = view.getFloat32(b + 16, true);
@@ -3359,6 +4334,8 @@ function parsePathGradient(view, b, end) {
3359
4334
  o += 4;
3360
4335
  }
3361
4336
  let boundaryPts = [];
4337
+ let boundaryArgb = [];
4338
+ const surroundAt = (k) => surround.length > 0 ? surround[Math.min(k, surround.length - 1)] : centerArgb;
3362
4339
  if (flags & BRUSH_DATA_PATH) {
3363
4340
  if (o + 4 <= end) {
3364
4341
  const pathSize = view.getInt32(o, true);
@@ -3366,7 +4343,9 @@ function parsePathGradient(view, b, end) {
3366
4343
  if (pathSize > 0 && o + pathSize <= end) {
3367
4344
  const path = parseEmfPlusPath(view, o, pathSize);
3368
4345
  if (path) {
3369
- boundaryPts = path.points;
4346
+ const flat = flattenFirstFigure(path.points, path.types, surroundAt);
4347
+ boundaryPts = flat.points;
4348
+ boundaryArgb = flat.argb;
3370
4349
  }
3371
4350
  o += pathSize;
3372
4351
  }
@@ -3380,6 +4359,7 @@ function parsePathGradient(view, b, end) {
3380
4359
  x: view.getFloat32(o + i * 8, true),
3381
4360
  y: view.getFloat32(o + i * 8 + 4, true)
3382
4361
  });
4362
+ boundaryArgb.push(surroundAt(i));
3383
4363
  }
3384
4364
  o += ptCount * 8;
3385
4365
  }
@@ -3389,31 +4369,50 @@ function parsePathGradient(view, b, end) {
3389
4369
  transform = readTransform(view, o);
3390
4370
  o += 24;
3391
4371
  }
3392
- if (transform) {
3393
- ({ x: cx, y: cy } = applyMatrix(transform, cx, cy));
3394
- boundaryPts = boundaryPts.map((p) => applyMatrix(transform, p.x, p.y));
4372
+ const center = { x: cx, y: cy };
4373
+ const m = transform;
4374
+ const worldBoundary = m ? boundaryPts.map((p) => applyMatrix(m, p.x, p.y)) : boundaryPts;
4375
+ if (m) {
4376
+ ({ x: cx, y: cy } = applyMatrix(m, cx, cy));
3395
4377
  }
3396
4378
  const surroundArgb = surround.length > 0 ? surround[0] : centerArgb;
3397
4379
  let stops = [
3398
- { offset: 0, color: argbToRgba(centerArgb) },
3399
- { offset: 1, color: argbToRgba(surroundArgb) }
4380
+ { offset: 0, color: argbToRgba(centerArgb), argb: centerArgb },
4381
+ { offset: 1, color: argbToRgba(surroundArgb), argb: surroundArgb }
3400
4382
  ];
4383
+ let blend = null;
4384
+ let preset = null;
3401
4385
  if (flags & BRUSH_DATA_PRESET_COLORS) {
3402
- const preset = readPresetColors(view, o, end);
3403
- if (preset) {
3404
- stops = preset.stops.map((s) => ({ offset: 1 - s.offset, color: s.color }));
4386
+ const presetData = readPresetColors(view, o, end);
4387
+ if (presetData) {
4388
+ stops = presetData.stops.map((s) => ({ ...s, offset: 1 - s.offset }));
4389
+ preset = {
4390
+ positions: presetData.stops.map((s) => s.offset),
4391
+ argb: presetData.stops.map((s) => s.argb ?? 0)
4392
+ };
4393
+ o = presetData.next;
3405
4394
  }
3406
4395
  } else if (flags & BRUSH_DATA_BLEND_FACTORS_H) {
3407
- const blend = readBlendFactors(view, o, end);
3408
- if (blend) {
3409
- stops = blend.entries.map((e) => ({
4396
+ const blendData = readBlendFactors(view, o, end);
4397
+ if (blendData) {
4398
+ stops = blendData.entries.map((e) => ({
3410
4399
  offset: 1 - e.pos,
3411
- color: lerpArgbToRgba(surroundArgb, centerArgb, e.factor)
4400
+ color: lerpArgbToRgba(surroundArgb, centerArgb, e.factor),
4401
+ argb: lerpArgb2(surroundArgb, centerArgb, e.factor)
3412
4402
  }));
4403
+ blend = {
4404
+ positions: blendData.entries.map((e) => e.pos),
4405
+ factors: blendData.entries.map((e) => e.factor)
4406
+ };
4407
+ o = blendData.next;
3413
4408
  }
3414
4409
  }
4410
+ let focus = null;
4411
+ if (flags & BRUSH_DATA_FOCUS_SCALES && o + 12 <= end && view.getUint32(o, true) === 2) {
4412
+ focus = { x: view.getFloat32(o + 4, true), y: view.getFloat32(o + 8, true) };
4413
+ }
3415
4414
  let r = 0;
3416
- for (const p of boundaryPts) {
4415
+ for (const p of worldBoundary) {
3417
4416
  const d = Math.hypot(p.x - cx, p.y - cy);
3418
4417
  if (d > r) {
3419
4418
  r = d;
@@ -3423,14 +4422,85 @@ function parsePathGradient(view, b, end) {
3423
4422
  return { kind: "plus-brush", color: argbToRgba(centerArgb) };
3424
4423
  }
3425
4424
  emfLog(
3426
- `parseEmfPlusBrushObject: path gradient centre=(${cx.toFixed(1)},${cy.toFixed(1)}), r=${r.toFixed(1)}, ${stops.length} stop(s)`
4425
+ `parseEmfPlusBrushObject: path gradient centre=(${cx.toFixed(1)},${cy.toFixed(1)}), ${boundaryPts.length} boundary point(s)`
3427
4426
  );
3428
4427
  return {
3429
4428
  kind: "plus-brush",
3430
4429
  color: argbToRgba(centerArgb),
3431
- gradient: { type: "radial", cx, cy, r, stops: normaliseStops(stops) }
4430
+ gradient: {
4431
+ type: "radial",
4432
+ cx,
4433
+ cy,
4434
+ r,
4435
+ stops: normaliseStops(stops),
4436
+ wrapMode,
4437
+ shape: {
4438
+ center,
4439
+ centerArgb,
4440
+ boundary: boundaryPts,
4441
+ boundaryArgb,
4442
+ blend,
4443
+ preset,
4444
+ focus,
4445
+ transform
4446
+ }
4447
+ }
3432
4448
  };
3433
4449
  }
4450
+ function lerpArgb2(a, b, t) {
4451
+ const tc = Math.min(1, Math.max(0, t));
4452
+ let out = 0;
4453
+ for (let shift = 24; shift >= 0; shift -= 8) {
4454
+ const ca = a >>> shift & 255;
4455
+ const cb = b >>> shift & 255;
4456
+ out = out * 256 + Math.round(ca + (cb - ca) * tc);
4457
+ }
4458
+ return out >>> 0;
4459
+ }
4460
+ var BEZIER_STEPS = 24;
4461
+ function flattenFirstFigure(points, types, colorAt) {
4462
+ const outPts = [];
4463
+ const outArgb = [];
4464
+ for (let k = 0; k < points.length; k++) {
4465
+ const kind = types[k] & 7;
4466
+ if (k > 0 && kind === 0) {
4467
+ break;
4468
+ }
4469
+ if (kind === 3 && k > 0 && k + 2 < points.length) {
4470
+ const p0 = points[k - 1];
4471
+ const p1 = points[k];
4472
+ const p2 = points[k + 1];
4473
+ const p3 = points[k + 2];
4474
+ const c0 = colorAt(k - 1);
4475
+ const c3 = colorAt(k + 2);
4476
+ for (let i = 1; i <= BEZIER_STEPS; i++) {
4477
+ const t = i / BEZIER_STEPS;
4478
+ const u = 1 - t;
4479
+ outPts.push({
4480
+ x: u * u * u * p0.x + 3 * u * u * t * p1.x + 3 * u * t * t * p2.x + t * t * t * p3.x,
4481
+ y: u * u * u * p0.y + 3 * u * u * t * p1.y + 3 * u * t * t * p2.y + t * t * t * p3.y
4482
+ });
4483
+ outArgb.push(lerpArgb2(c0, c3, t));
4484
+ }
4485
+ k += 2;
4486
+ if (types[k] & 128) {
4487
+ break;
4488
+ }
4489
+ continue;
4490
+ }
4491
+ outPts.push(points[k]);
4492
+ outArgb.push(colorAt(k));
4493
+ if (types[k] & 128) {
4494
+ break;
4495
+ }
4496
+ }
4497
+ const n = outPts.length;
4498
+ if (n > 1 && outPts[0].x === outPts[n - 1].x && outPts[0].y === outPts[n - 1].y) {
4499
+ outPts.pop();
4500
+ outArgb.pop();
4501
+ }
4502
+ return { points: outPts, argb: outArgb };
4503
+ }
3434
4504
  function parseEmfPlusBrushObject(view, dataOff, recDataSize) {
3435
4505
  if (recDataSize < 8) {
3436
4506
  return null;
@@ -4451,7 +5521,7 @@ function replayEmfRecords(view, ctx, bounds, canvasW, canvasH, dpiScale = 1, rep
4451
5521
  );
4452
5522
  break;
4453
5523
  }
4454
- if (recType === EMR_SETBRUSHORGEX || recType === EMR_SETMETARGN || recType === EMR_SETICMMODE || recType === EMR_SETLAYOUT || recType === EMR_HEADER) {
5524
+ if (recType === EMR_SETMETARGN || recType === EMR_SETICMMODE || recType === EMR_SETLAYOUT || recType === EMR_HEADER) {
4455
5525
  offset += recSize;
4456
5526
  continue;
4457
5527
  }
@@ -4846,8 +5916,8 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH, replayOptions = {
4846
5916
  case META_CREATEFONTINDIRECT:
4847
5917
  if (recSize >= 24) {
4848
5918
  let family = "";
4849
- for (let i = 0; i < 32 && dataOff + 14 + i < offset + recSize; i++) {
4850
- const ch = view.getUint8(dataOff + 14 + i);
5919
+ for (let i = 0; i < 32 && dataOff + 18 + i < offset + recSize; i++) {
5920
+ const ch = view.getUint8(dataOff + 18 + i);
4851
5921
  if (ch === 0) {
4852
5922
  break;
4853
5923
  }
@@ -4856,12 +5926,15 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH, replayOptions = {
4856
5926
  const slot = allocObjectSlot();
4857
5927
  objectTable.set(slot, {
4858
5928
  kind: "font",
4859
- height: Math.abs(view.getInt16(dataOff, true)),
5929
+ // Sign kept (not Math.abs'd): resolveFontPixelHeight() needs it
5930
+ // to distinguish GDI's cell-height vs character-height convention.
5931
+ height: view.getInt16(dataOff, true),
4860
5932
  weight: view.getInt16(dataOff + 8, true),
4861
5933
  italic: view.getUint8(dataOff + 10) !== 0,
4862
5934
  underline: view.getUint8(dataOff + 11) !== 0,
4863
5935
  strikeOut: view.getUint8(dataOff + 12) !== 0,
4864
- family: family || "sans-serif"
5936
+ family: family || "sans-serif",
5937
+ escapementTenthDeg: view.getInt16(dataOff + 4, true)
4865
5938
  });
4866
5939
  }
4867
5940
  break;
@@ -4886,6 +5959,7 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH, replayOptions = {
4886
5959
  state.fontUnderline = obj.underline;
4887
5960
  state.fontStrikeOut = obj.strikeOut;
4888
5961
  state.fontFamily = obj.family;
5962
+ state.fontEscapementTenthDeg = obj.escapementTenthDeg ?? 0;
4889
5963
  break;
4890
5964
  }
4891
5965
  }