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