emf-converter 3.0.1 → 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.
- package/README.md +4 -2
- package/dist/index.js +1488 -410
- package/dist/index.mjs +1488 -410
- 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;
|
|
@@ -244,7 +288,11 @@ async function ensureNodeCanvasModule() {
|
|
|
244
288
|
return nodeCanvasModule;
|
|
245
289
|
}
|
|
246
290
|
try {
|
|
247
|
-
nodeCanvasModule = await import(
|
|
291
|
+
nodeCanvasModule = await import(
|
|
292
|
+
/* webpackIgnore: true */
|
|
293
|
+
/* @vite-ignore */
|
|
294
|
+
'@napi-rs/canvas'
|
|
295
|
+
);
|
|
248
296
|
} catch {
|
|
249
297
|
nodeCanvasModule = null;
|
|
250
298
|
}
|
|
@@ -466,7 +514,7 @@ function mapFontFamily(face, map) {
|
|
|
466
514
|
return resolved;
|
|
467
515
|
}
|
|
468
516
|
function fontSizePx(state, scale = 1) {
|
|
469
|
-
return Math.max(
|
|
517
|
+
return Math.max(resolveFontPixelHeight(state.fontHeight) * Math.abs(scale || 1), 8);
|
|
470
518
|
}
|
|
471
519
|
function applyFont(ctx, state, scale = 1) {
|
|
472
520
|
const italic = state.fontItalic ? "italic " : "";
|
|
@@ -618,6 +666,28 @@ function canvasDrawImage(ctx, image, dx, dy, dw, dh) {
|
|
|
618
666
|
const draw = ctx.drawImage;
|
|
619
667
|
draw.call(ctx, image, dx, dy, dw, dh);
|
|
620
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
|
+
}
|
|
621
691
|
|
|
622
692
|
// src/emf-header-parser.ts
|
|
623
693
|
function parseEmfHeader(view) {
|
|
@@ -936,218 +1006,6 @@ function handleEmfGdiShapeRecord(rCtx, recType, dataOff, recSize) {
|
|
|
936
1006
|
}
|
|
937
1007
|
}
|
|
938
1008
|
|
|
939
|
-
// src/emf-clip-region.ts
|
|
940
|
-
var CLIP_HUGE = 1 << 24;
|
|
941
|
-
function rectClipShape(x, y, w, h) {
|
|
942
|
-
return { cmds: [{ op: "rect", x, y, w, h }], fillRule: "nonzero", simple: true };
|
|
943
|
-
}
|
|
944
|
-
function rectsClipShape(rects) {
|
|
945
|
-
return {
|
|
946
|
-
cmds: rects.map((r) => ({ op: "rect", x: r.x, y: r.y, w: r.w, h: r.h })),
|
|
947
|
-
fillRule: "nonzero",
|
|
948
|
-
simple: true
|
|
949
|
-
};
|
|
950
|
-
}
|
|
951
|
-
function emptyClipShape() {
|
|
952
|
-
return { cmds: [{ op: "rect", x: 0, y: 0, w: 0, h: 0 }], fillRule: "nonzero", simple: true };
|
|
953
|
-
}
|
|
954
|
-
function translateClipShape(shape, dx, dy) {
|
|
955
|
-
return {
|
|
956
|
-
...shape,
|
|
957
|
-
cmds: shape.cmds.map((c) => {
|
|
958
|
-
switch (c.op) {
|
|
959
|
-
case "rect":
|
|
960
|
-
return { ...c, x: c.x + dx, y: c.y + dy };
|
|
961
|
-
case "moveTo":
|
|
962
|
-
case "lineTo":
|
|
963
|
-
return { ...c, x: c.x + dx, y: c.y + dy };
|
|
964
|
-
case "bezierCurveTo":
|
|
965
|
-
return {
|
|
966
|
-
...c,
|
|
967
|
-
cp1x: c.cp1x + dx,
|
|
968
|
-
cp1y: c.cp1y + dy,
|
|
969
|
-
cp2x: c.cp2x + dx,
|
|
970
|
-
cp2y: c.cp2y + dy,
|
|
971
|
-
x: c.x + dx,
|
|
972
|
-
y: c.y + dy
|
|
973
|
-
};
|
|
974
|
-
case "closePath":
|
|
975
|
-
return c;
|
|
976
|
-
}
|
|
977
|
-
})
|
|
978
|
-
};
|
|
979
|
-
}
|
|
980
|
-
function translateClipRegion(region, dx, dy) {
|
|
981
|
-
if (!region) {
|
|
982
|
-
return null;
|
|
983
|
-
}
|
|
984
|
-
return region.map((s) => translateClipShape(s, dx, dy));
|
|
985
|
-
}
|
|
986
|
-
function isComposable(shape) {
|
|
987
|
-
return shape.simple && shape.fillRule === "nonzero";
|
|
988
|
-
}
|
|
989
|
-
function invertClipShape(shape) {
|
|
990
|
-
return {
|
|
991
|
-
cmds: [
|
|
992
|
-
{ op: "rect", x: -CLIP_HUGE, y: -CLIP_HUGE, w: 2 * CLIP_HUGE, h: 2 * CLIP_HUGE },
|
|
993
|
-
...shape.cmds
|
|
994
|
-
],
|
|
995
|
-
fillRule: "evenodd",
|
|
996
|
-
simple: false
|
|
997
|
-
};
|
|
998
|
-
}
|
|
999
|
-
function combineClip(current, shape, op) {
|
|
1000
|
-
switch (op) {
|
|
1001
|
-
case "replace":
|
|
1002
|
-
return { region: [shape], exact: true };
|
|
1003
|
-
case "intersect":
|
|
1004
|
-
return { region: current ? [...current, shape] : [shape], exact: true };
|
|
1005
|
-
case "exclude": {
|
|
1006
|
-
if (!isComposable(shape)) {
|
|
1007
|
-
return { region: current ? [...current, shape] : [shape], exact: false };
|
|
1008
|
-
}
|
|
1009
|
-
const inv = invertClipShape(shape);
|
|
1010
|
-
return { region: current ? [...current, inv] : [inv], exact: true };
|
|
1011
|
-
}
|
|
1012
|
-
case "union": {
|
|
1013
|
-
if (!current) {
|
|
1014
|
-
return { region: null, exact: true };
|
|
1015
|
-
}
|
|
1016
|
-
if (current.length === 1 && isComposable(current[0]) && isComposable(shape)) {
|
|
1017
|
-
return {
|
|
1018
|
-
region: [
|
|
1019
|
-
{ cmds: [...current[0].cmds, ...shape.cmds], fillRule: "nonzero", simple: false }
|
|
1020
|
-
],
|
|
1021
|
-
exact: false
|
|
1022
|
-
};
|
|
1023
|
-
}
|
|
1024
|
-
return { region: current, exact: false };
|
|
1025
|
-
}
|
|
1026
|
-
case "xor": {
|
|
1027
|
-
if (!current) {
|
|
1028
|
-
if (isComposable(shape)) {
|
|
1029
|
-
return { region: [invertClipShape(shape)], exact: true };
|
|
1030
|
-
}
|
|
1031
|
-
return { region: [shape], exact: false };
|
|
1032
|
-
}
|
|
1033
|
-
if (current.length === 1 && isComposable(current[0]) && isComposable(shape)) {
|
|
1034
|
-
return {
|
|
1035
|
-
region: [
|
|
1036
|
-
{ cmds: [...current[0].cmds, ...shape.cmds], fillRule: "evenodd", simple: false }
|
|
1037
|
-
],
|
|
1038
|
-
exact: true
|
|
1039
|
-
};
|
|
1040
|
-
}
|
|
1041
|
-
if (isComposable(shape)) {
|
|
1042
|
-
return { region: [...current, invertClipShape(shape)], exact: false };
|
|
1043
|
-
}
|
|
1044
|
-
return { region: [...current, shape], exact: false };
|
|
1045
|
-
}
|
|
1046
|
-
case "complement": {
|
|
1047
|
-
if (!current) {
|
|
1048
|
-
return { region: [emptyClipShape()], exact: true };
|
|
1049
|
-
}
|
|
1050
|
-
if (current.length === 1 && isComposable(current[0])) {
|
|
1051
|
-
return { region: [shape, invertClipShape(current[0])], exact: true };
|
|
1052
|
-
}
|
|
1053
|
-
return { region: [shape], exact: false };
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
function combineClipRegions(current, incoming, op) {
|
|
1058
|
-
if (op === "replace") {
|
|
1059
|
-
return { region: incoming, exact: true };
|
|
1060
|
-
}
|
|
1061
|
-
if (incoming && incoming.length === 1) {
|
|
1062
|
-
return combineClip(current, incoming[0], op);
|
|
1063
|
-
}
|
|
1064
|
-
if (!incoming) {
|
|
1065
|
-
switch (op) {
|
|
1066
|
-
case "intersect":
|
|
1067
|
-
return { region: current, exact: true };
|
|
1068
|
-
case "union":
|
|
1069
|
-
return { region: null, exact: true };
|
|
1070
|
-
case "exclude":
|
|
1071
|
-
return { region: [emptyClipShape()], exact: true };
|
|
1072
|
-
case "xor":
|
|
1073
|
-
case "complement": {
|
|
1074
|
-
if (!current) {
|
|
1075
|
-
return { region: [emptyClipShape()], exact: true };
|
|
1076
|
-
}
|
|
1077
|
-
if (current.length === 1) {
|
|
1078
|
-
return combineClip(null, current[0], "exclude");
|
|
1079
|
-
}
|
|
1080
|
-
return { region: current, exact: false };
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
switch (op) {
|
|
1085
|
-
case "intersect":
|
|
1086
|
-
return { region: current ? [...current, ...incoming] : incoming, exact: true };
|
|
1087
|
-
case "union":
|
|
1088
|
-
return { region: current, exact: false };
|
|
1089
|
-
case "xor":
|
|
1090
|
-
return { region: current ?? incoming, exact: false };
|
|
1091
|
-
case "exclude":
|
|
1092
|
-
return { region: current, exact: false };
|
|
1093
|
-
case "complement": {
|
|
1094
|
-
if (!current) {
|
|
1095
|
-
return { region: [emptyClipShape()], exact: true };
|
|
1096
|
-
}
|
|
1097
|
-
if (current.length === 1) {
|
|
1098
|
-
return combineClip(incoming, current[0], "exclude");
|
|
1099
|
-
}
|
|
1100
|
-
return { region: incoming, exact: false };
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
function replayClipCmds(ctx, cmds) {
|
|
1105
|
-
for (const c of cmds) {
|
|
1106
|
-
switch (c.op) {
|
|
1107
|
-
case "rect":
|
|
1108
|
-
ctx.rect(c.x, c.y, c.w, c.h);
|
|
1109
|
-
break;
|
|
1110
|
-
case "moveTo":
|
|
1111
|
-
ctx.moveTo(c.x, c.y);
|
|
1112
|
-
break;
|
|
1113
|
-
case "lineTo":
|
|
1114
|
-
ctx.lineTo(c.x, c.y);
|
|
1115
|
-
break;
|
|
1116
|
-
case "bezierCurveTo":
|
|
1117
|
-
ctx.bezierCurveTo(c.cp1x, c.cp1y, c.cp2x, c.cp2y, c.x, c.y);
|
|
1118
|
-
break;
|
|
1119
|
-
case "closePath":
|
|
1120
|
-
ctx.closePath();
|
|
1121
|
-
break;
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
function applyClipShapes(ctx, shapes) {
|
|
1126
|
-
for (const s of shapes) {
|
|
1127
|
-
ctx.beginPath();
|
|
1128
|
-
replayClipCmds(ctx, s.cmds);
|
|
1129
|
-
try {
|
|
1130
|
-
ctx.clip(s.fillRule);
|
|
1131
|
-
} catch {
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
function reapplyClipRegion(holder, region, identityTransform = false) {
|
|
1136
|
-
const { ctx } = holder;
|
|
1137
|
-
while (holder.clipSaveDepth > 0) {
|
|
1138
|
-
ctx.restore();
|
|
1139
|
-
holder.clipSaveDepth--;
|
|
1140
|
-
}
|
|
1141
|
-
if (region) {
|
|
1142
|
-
ctx.save();
|
|
1143
|
-
holder.clipSaveDepth = 1;
|
|
1144
|
-
if (identityTransform) {
|
|
1145
|
-
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
1146
|
-
}
|
|
1147
|
-
applyClipShapes(ctx, region);
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
1009
|
// src/emf-dib-rle-decoder.ts
|
|
1152
1010
|
function decodeRleBitmap(view, bitsOffset, bitsSize, width, height, _topDown, isRle4, colorTable, out, setPixel) {
|
|
1153
1011
|
let x = 0;
|
|
@@ -1243,7 +1101,7 @@ function decodeRleBitmap(view, bitsOffset, bitsSize, width, height, _topDown, is
|
|
|
1243
1101
|
}
|
|
1244
1102
|
}
|
|
1245
1103
|
}
|
|
1246
|
-
return
|
|
1104
|
+
return createImageDataCompat(
|
|
1247
1105
|
new Uint8ClampedArray(out.buffer, out.byteOffset, out.byteLength),
|
|
1248
1106
|
width,
|
|
1249
1107
|
height
|
|
@@ -1453,138 +1311,855 @@ function decodeDibToImageData(view, bmiOffset, bitsOffset, bitsSize) {
|
|
|
1453
1311
|
masks,
|
|
1454
1312
|
out
|
|
1455
1313
|
);
|
|
1456
|
-
return
|
|
1314
|
+
return createImageDataCompat(out, width, height);
|
|
1457
1315
|
}
|
|
1458
1316
|
|
|
1459
|
-
// src/emf-gdi-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
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 };
|
|
1491
2087
|
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
const w = ctx.measureText(text).width;
|
|
1495
|
-
const baseX = gmx(rCtx, refX);
|
|
1496
|
-
const startX = alignHoriz === "center" ? baseX - w / 2 : alignHoriz === "right" ? baseX - w : baseX;
|
|
1497
|
-
drawTextDecorations(ctx, state, startX, gmy(rCtx, refY), w, fontScale);
|
|
2088
|
+
if (current.length === 1) {
|
|
2089
|
+
return combineClip(null, current[0], "exclude");
|
|
1498
2090
|
}
|
|
2091
|
+
return { region: current, exact: false };
|
|
1499
2092
|
}
|
|
1500
2093
|
}
|
|
1501
2094
|
}
|
|
1502
|
-
|
|
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
|
+
}
|
|
1503
2114
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
ctx.fillRect(gmx(rCtx, dstX), gmy(rCtx, dstY), gmw(rCtx, dstW), gmh(rCtx, dstH));
|
|
1523
|
-
}
|
|
1524
|
-
ctx.fillStyle = prevFill;
|
|
1525
|
-
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;
|
|
1526
2133
|
}
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
if (temp) {
|
|
1537
|
-
temp.ctx.putImageData(imageData, 0, 0);
|
|
1538
|
-
canvasDrawImage(
|
|
1539
|
-
ctx,
|
|
1540
|
-
temp.canvas,
|
|
1541
|
-
gmx(rCtx, dstX),
|
|
1542
|
-
gmy(rCtx, dstY),
|
|
1543
|
-
gmw(rCtx, dstW),
|
|
1544
|
-
gmh(rCtx, dstH)
|
|
1545
|
-
);
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
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 {
|
|
1548
2143
|
}
|
|
1549
2144
|
}
|
|
1550
|
-
return true;
|
|
1551
2145
|
}
|
|
1552
|
-
function
|
|
1553
|
-
const { ctx
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
if (offBmiSrc > 0 && cbBmiSrc > 0 && offBitsSrc > 0 && cbBitsSrc > 0) {
|
|
1564
|
-
const imageData = decodeDibToImageData(
|
|
1565
|
-
view,
|
|
1566
|
-
offset + offBmiSrc,
|
|
1567
|
-
offset + offBitsSrc,
|
|
1568
|
-
cbBitsSrc
|
|
1569
|
-
);
|
|
1570
|
-
if (imageData) {
|
|
1571
|
-
const temp = createTempCanvas(imageData.width, imageData.height);
|
|
1572
|
-
if (temp) {
|
|
1573
|
-
temp.ctx.putImageData(imageData, 0, 0);
|
|
1574
|
-
canvasDrawImage(
|
|
1575
|
-
ctx,
|
|
1576
|
-
temp.canvas,
|
|
1577
|
-
gmx(rCtx, dstX),
|
|
1578
|
-
gmy(rCtx, dstY),
|
|
1579
|
-
gmw(rCtx, dstW),
|
|
1580
|
-
gmh(rCtx, dstH)
|
|
1581
|
-
);
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
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);
|
|
1584
2157
|
}
|
|
2158
|
+
applyClipShapes(ctx, region);
|
|
1585
2159
|
}
|
|
1586
|
-
return true;
|
|
1587
2160
|
}
|
|
2161
|
+
|
|
2162
|
+
// src/emf-gdi-clip-records.ts
|
|
1588
2163
|
function gdiCombineClip(rCtx, shape, op) {
|
|
1589
2164
|
const { ctx } = rCtx;
|
|
1590
2165
|
if (rCtx.clipUntracked && op !== "replace") {
|
|
@@ -1716,14 +2291,8 @@ function handleOffsetClipRgn(rCtx, dataOff, recSize) {
|
|
|
1716
2291
|
}
|
|
1717
2292
|
return true;
|
|
1718
2293
|
}
|
|
1719
|
-
function
|
|
2294
|
+
function handleEmfGdiClipRecord(rCtx, recType, dataOff, recSize) {
|
|
1720
2295
|
switch (recType) {
|
|
1721
|
-
case EMR_EXTTEXTOUTW:
|
|
1722
|
-
return handleExtTextOutW(rCtx, offset, dataOff, recSize);
|
|
1723
|
-
case EMR_BITBLT:
|
|
1724
|
-
return handleBitBlt(rCtx, offset, dataOff, recSize);
|
|
1725
|
-
case EMR_STRETCHDIBITS:
|
|
1726
|
-
return handleStretchDibits(rCtx, offset, dataOff, recSize);
|
|
1727
2296
|
case EMR_INTERSECTCLIPRECT:
|
|
1728
2297
|
return handleIntersectClipRect(rCtx, dataOff, recSize);
|
|
1729
2298
|
case EMR_EXTSELECTCLIPRGN:
|
|
@@ -1737,6 +2306,11 @@ function handleEmfGdiTextBitmapRecord(rCtx, recType, offset, dataOff, recSize) {
|
|
|
1737
2306
|
}
|
|
1738
2307
|
}
|
|
1739
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
|
+
|
|
1740
2314
|
// src/emf-gdi-draw-handlers.ts
|
|
1741
2315
|
function handleEmfGdiDrawRecord(rCtx, recType, offset, dataOff, recSize) {
|
|
1742
2316
|
return handleEmfGdiShapeRecord(rCtx, recType, dataOff, recSize) || handleEmfGdiTextBitmapRecord(rCtx, recType, offset, dataOff, recSize);
|
|
@@ -2089,10 +2663,28 @@ function handleEmfObjectRecord(rCtx, recType, dataOff, recSize) {
|
|
|
2089
2663
|
const ihBrush = view.getUint32(dataOff, true);
|
|
2090
2664
|
const brushStyle = view.getUint32(dataOff + 4, true);
|
|
2091
2665
|
const color = readColorRef(view, dataOff + 8);
|
|
2666
|
+
const hatch = view.getUint32(dataOff + 12, true);
|
|
2092
2667
|
rCtx.objectTable.set(ihBrush, {
|
|
2093
2668
|
kind: "brush",
|
|
2094
2669
|
style: brushStyle,
|
|
2095
|
-
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 } : {}
|
|
2096
2688
|
});
|
|
2097
2689
|
}
|
|
2098
2690
|
return true;
|
|
@@ -2101,19 +2693,23 @@ function handleEmfObjectRecord(rCtx, recType, dataOff, recSize) {
|
|
|
2101
2693
|
if (recSize >= 332) {
|
|
2102
2694
|
const ihFont = view.getUint32(dataOff, true);
|
|
2103
2695
|
const height = view.getInt32(dataOff + 4, true);
|
|
2696
|
+
const escapementTenthDeg = view.getInt32(dataOff + 12, true);
|
|
2104
2697
|
const weight = view.getInt32(dataOff + 20, true);
|
|
2105
2698
|
const italic = view.getUint8(dataOff + 24);
|
|
2106
2699
|
const underline = view.getUint8(dataOff + 25);
|
|
2107
2700
|
const strikeOut = view.getUint8(dataOff + 26);
|
|
2108
|
-
const family = readUtf16LE(view, dataOff +
|
|
2701
|
+
const family = readUtf16LE(view, dataOff + 32, 32) || "sans-serif";
|
|
2109
2702
|
rCtx.objectTable.set(ihFont, {
|
|
2110
2703
|
kind: "font",
|
|
2111
|
-
|
|
2704
|
+
// Sign kept (not Math.abs'd): resolveFontPixelHeight() needs it
|
|
2705
|
+
// to distinguish GDI's cell-height vs character-height convention.
|
|
2706
|
+
height,
|
|
2112
2707
|
weight,
|
|
2113
2708
|
italic: italic !== 0,
|
|
2114
2709
|
underline: underline !== 0,
|
|
2115
2710
|
strikeOut: strikeOut !== 0,
|
|
2116
|
-
family
|
|
2711
|
+
family,
|
|
2712
|
+
escapementTenthDeg
|
|
2117
2713
|
});
|
|
2118
2714
|
}
|
|
2119
2715
|
return true;
|
|
@@ -2132,6 +2728,7 @@ function handleEmfObjectRecord(rCtx, recType, dataOff, recSize) {
|
|
|
2132
2728
|
case "brush":
|
|
2133
2729
|
state.brushStyle = obj.style;
|
|
2134
2730
|
state.brushColor = obj.color;
|
|
2731
|
+
state.brushPattern = obj.pattern ?? null;
|
|
2135
2732
|
break;
|
|
2136
2733
|
case "font":
|
|
2137
2734
|
state.fontHeight = obj.height;
|
|
@@ -2140,6 +2737,7 @@ function handleEmfObjectRecord(rCtx, recType, dataOff, recSize) {
|
|
|
2140
2737
|
state.fontUnderline = obj.underline;
|
|
2141
2738
|
state.fontStrikeOut = obj.strikeOut;
|
|
2142
2739
|
state.fontFamily = obj.family;
|
|
2740
|
+
state.fontEscapementTenthDeg = obj.escapementTenthDeg ?? 0;
|
|
2143
2741
|
break;
|
|
2144
2742
|
}
|
|
2145
2743
|
}
|
|
@@ -2308,15 +2906,22 @@ function defaultState() {
|
|
|
2308
2906
|
penStyle: 0,
|
|
2309
2907
|
brushColor: "#ffffff",
|
|
2310
2908
|
brushStyle: 0,
|
|
2909
|
+
brushPattern: null,
|
|
2910
|
+
brushOrgX: 0,
|
|
2911
|
+
brushOrgY: 0,
|
|
2912
|
+
stretchBltMode: 1,
|
|
2311
2913
|
textColor: "#000000",
|
|
2312
2914
|
bkColor: "#ffffff",
|
|
2313
2915
|
bkMode: 1,
|
|
2314
|
-
|
|
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,
|
|
2315
2919
|
fontWeight: 400,
|
|
2316
2920
|
fontItalic: false,
|
|
2317
2921
|
fontFamily: "sans-serif",
|
|
2318
2922
|
fontUnderline: false,
|
|
2319
2923
|
fontStrikeOut: false,
|
|
2924
|
+
fontEscapementTenthDeg: 0,
|
|
2320
2925
|
rop2: 13,
|
|
2321
2926
|
curX: 0,
|
|
2322
2927
|
curY: 0,
|
|
@@ -2433,7 +3038,19 @@ function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
|
|
|
2433
3038
|
}
|
|
2434
3039
|
return true;
|
|
2435
3040
|
}
|
|
2436
|
-
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
|
+
}
|
|
2437
3054
|
case EMR_SETMITERLIMIT:
|
|
2438
3055
|
case EMR_SETTEXTALIGN: {
|
|
2439
3056
|
if (recType === EMR_SETTEXTALIGN && recSize >= 12) {
|
|
@@ -2476,6 +3093,375 @@ function readPointFromView(view, offset, compressed) {
|
|
|
2476
3093
|
};
|
|
2477
3094
|
}
|
|
2478
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
|
+
|
|
2479
3465
|
// src/emf-plus-path.ts
|
|
2480
3466
|
function parseEmfPlusPath(data, off, maxLen) {
|
|
2481
3467
|
if (maxLen < 12) {
|
|
@@ -2610,32 +3596,6 @@ function multiplyMatrix(m1, m2) {
|
|
|
2610
3596
|
m1[4] * m2[1] + m1[5] * m2[3] + m2[5]
|
|
2611
3597
|
];
|
|
2612
3598
|
}
|
|
2613
|
-
function createBrushGradient(rCtx, grad) {
|
|
2614
|
-
const ctx = rCtx.ctx;
|
|
2615
|
-
try {
|
|
2616
|
-
let g = null;
|
|
2617
|
-
if (grad.type === "linear" && typeof ctx.createLinearGradient === "function") {
|
|
2618
|
-
if (grad.x1 === grad.x2 && grad.y1 === grad.y2) {
|
|
2619
|
-
return null;
|
|
2620
|
-
}
|
|
2621
|
-
g = ctx.createLinearGradient(grad.x1, grad.y1, grad.x2, grad.y2);
|
|
2622
|
-
} else if (grad.type === "radial" && typeof ctx.createRadialGradient === "function") {
|
|
2623
|
-
if (!(grad.r > 0)) {
|
|
2624
|
-
return null;
|
|
2625
|
-
}
|
|
2626
|
-
g = ctx.createRadialGradient(grad.cx, grad.cy, 0, grad.cx, grad.cy, grad.r);
|
|
2627
|
-
}
|
|
2628
|
-
if (!g) {
|
|
2629
|
-
return null;
|
|
2630
|
-
}
|
|
2631
|
-
for (const stop of grad.stops) {
|
|
2632
|
-
g.addColorStop(stop.offset, stop.color);
|
|
2633
|
-
}
|
|
2634
|
-
return g;
|
|
2635
|
-
} catch {
|
|
2636
|
-
return null;
|
|
2637
|
-
}
|
|
2638
|
-
}
|
|
2639
3599
|
function resolveBrushPaint(rCtx, flags, brushIdOrColor) {
|
|
2640
3600
|
if (flags & 32768) {
|
|
2641
3601
|
return argbToRgba(brushIdOrColor);
|
|
@@ -2643,7 +3603,7 @@ function resolveBrushPaint(rCtx, flags, brushIdOrColor) {
|
|
|
2643
3603
|
const obj = rCtx.objectTable.get(brushIdOrColor & 255);
|
|
2644
3604
|
if (obj && obj.kind === "plus-brush") {
|
|
2645
3605
|
if (obj.gradient) {
|
|
2646
|
-
const g = createBrushGradient(rCtx, obj.gradient);
|
|
3606
|
+
const g = createBrushGradient(rCtx.ctx, obj.gradient, plusWorldMatrix(rCtx));
|
|
2647
3607
|
if (g) {
|
|
2648
3608
|
return g;
|
|
2649
3609
|
}
|
|
@@ -2678,18 +3638,14 @@ function getPageUnitMultiplier(pageUnit, pageScale) {
|
|
|
2678
3638
|
}
|
|
2679
3639
|
return unitToPixel * pageScale;
|
|
2680
3640
|
}
|
|
2681
|
-
function
|
|
3641
|
+
function plusWorldMatrix(rCtx) {
|
|
2682
3642
|
const wt = rCtx.worldTransform;
|
|
2683
|
-
const
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
wt[3] * m * d,
|
|
2690
|
-
wt[4] * m * d,
|
|
2691
|
-
wt[5] * m * d
|
|
2692
|
-
);
|
|
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]);
|
|
2693
3649
|
}
|
|
2694
3650
|
function pushState(rCtx, stackId) {
|
|
2695
3651
|
rCtx.saveStack.push({
|
|
@@ -3212,6 +4168,7 @@ var BRUSH_DATA_PATH = 1;
|
|
|
3212
4168
|
var BRUSH_DATA_TRANSFORM = 2;
|
|
3213
4169
|
var BRUSH_DATA_PRESET_COLORS = 4;
|
|
3214
4170
|
var BRUSH_DATA_BLEND_FACTORS_H = 8;
|
|
4171
|
+
var BRUSH_DATA_FOCUS_SCALES = 64;
|
|
3215
4172
|
var MAX_GRADIENT_ELEMENTS = 4096;
|
|
3216
4173
|
function looksLikeGraphicsVersion(v) {
|
|
3217
4174
|
return v >>> 12 === 900097;
|
|
@@ -3232,8 +4189,22 @@ function applyMatrix(m, x, y) {
|
|
|
3232
4189
|
function clamp01(v) {
|
|
3233
4190
|
return Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : 0;
|
|
3234
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
|
+
}
|
|
3235
4206
|
function normaliseStops(stops) {
|
|
3236
|
-
return stops.map((s) => ({ offset: clamp01(s.offset)
|
|
4207
|
+
return stops.map((s) => ({ ...s, offset: clamp01(s.offset) })).sort((a, b) => a.offset - b.offset);
|
|
3237
4208
|
}
|
|
3238
4209
|
function readPresetColors(view, off, end) {
|
|
3239
4210
|
if (off + 4 > end) {
|
|
@@ -3251,9 +4222,11 @@ function readPresetColors(view, off, end) {
|
|
|
3251
4222
|
}
|
|
3252
4223
|
const stops = [];
|
|
3253
4224
|
for (let i = 0; i < count; i++) {
|
|
4225
|
+
const argb = view.getUint32(colOff + i * 4, true);
|
|
3254
4226
|
stops.push({
|
|
3255
4227
|
offset: view.getFloat32(posOff + i * 4, true),
|
|
3256
|
-
color: argbToRgba(
|
|
4228
|
+
color: argbToRgba(argb),
|
|
4229
|
+
argb
|
|
3257
4230
|
});
|
|
3258
4231
|
}
|
|
3259
4232
|
return { stops, next };
|
|
@@ -3286,6 +4259,7 @@ function parseLinearGradient(view, b, end) {
|
|
|
3286
4259
|
return null;
|
|
3287
4260
|
}
|
|
3288
4261
|
const flags = view.getUint32(b, true);
|
|
4262
|
+
const wrapMode = readWrapMode(view.getUint32(b + 4, true));
|
|
3289
4263
|
const rx = view.getFloat32(b + 8, true);
|
|
3290
4264
|
const ry = view.getFloat32(b + 12, true);
|
|
3291
4265
|
const rw = view.getFloat32(b + 16, true);
|
|
@@ -3299,8 +4273,8 @@ function parseLinearGradient(view, b, end) {
|
|
|
3299
4273
|
o += 24;
|
|
3300
4274
|
}
|
|
3301
4275
|
let stops = [
|
|
3302
|
-
{ offset: 0, color: argbToRgba(startArgb) },
|
|
3303
|
-
{ offset: 1, color: argbToRgba(endArgb) }
|
|
4276
|
+
{ offset: 0, color: argbToRgba(startArgb), argb: startArgb },
|
|
4277
|
+
{ offset: 1, color: argbToRgba(endArgb), argb: endArgb }
|
|
3304
4278
|
];
|
|
3305
4279
|
if (flags & BRUSH_DATA_PRESET_COLORS) {
|
|
3306
4280
|
const preset = readPresetColors(view, o, end);
|
|
@@ -3312,7 +4286,8 @@ function parseLinearGradient(view, b, end) {
|
|
|
3312
4286
|
if (blend) {
|
|
3313
4287
|
stops = blend.entries.map((e) => ({
|
|
3314
4288
|
offset: e.pos,
|
|
3315
|
-
color: lerpArgbToRgba(startArgb, endArgb, e.factor)
|
|
4289
|
+
color: lerpArgbToRgba(startArgb, endArgb, e.factor),
|
|
4290
|
+
argb: lerpArgb2(startArgb, endArgb, e.factor)
|
|
3316
4291
|
}));
|
|
3317
4292
|
}
|
|
3318
4293
|
}
|
|
@@ -3334,7 +4309,10 @@ function parseLinearGradient(view, b, end) {
|
|
|
3334
4309
|
y1: p1.y,
|
|
3335
4310
|
x2: p2.x,
|
|
3336
4311
|
y2: p2.y,
|
|
3337
|
-
stops: normaliseStops(stops)
|
|
4312
|
+
stops: normaliseStops(stops),
|
|
4313
|
+
wrapMode,
|
|
4314
|
+
rect: { x: rx, y: ry, w: rw, h: rh },
|
|
4315
|
+
transform
|
|
3338
4316
|
}
|
|
3339
4317
|
};
|
|
3340
4318
|
}
|
|
@@ -3343,6 +4321,7 @@ function parsePathGradient(view, b, end) {
|
|
|
3343
4321
|
return null;
|
|
3344
4322
|
}
|
|
3345
4323
|
const flags = view.getUint32(b, true);
|
|
4324
|
+
const wrapMode = readWrapMode(view.getUint32(b + 4, true));
|
|
3346
4325
|
const centerArgb = view.getUint32(b + 8, true);
|
|
3347
4326
|
let cx = view.getFloat32(b + 12, true);
|
|
3348
4327
|
let cy = view.getFloat32(b + 16, true);
|
|
@@ -3357,6 +4336,8 @@ function parsePathGradient(view, b, end) {
|
|
|
3357
4336
|
o += 4;
|
|
3358
4337
|
}
|
|
3359
4338
|
let boundaryPts = [];
|
|
4339
|
+
let boundaryArgb = [];
|
|
4340
|
+
const surroundAt = (k) => surround.length > 0 ? surround[Math.min(k, surround.length - 1)] : centerArgb;
|
|
3360
4341
|
if (flags & BRUSH_DATA_PATH) {
|
|
3361
4342
|
if (o + 4 <= end) {
|
|
3362
4343
|
const pathSize = view.getInt32(o, true);
|
|
@@ -3364,7 +4345,9 @@ function parsePathGradient(view, b, end) {
|
|
|
3364
4345
|
if (pathSize > 0 && o + pathSize <= end) {
|
|
3365
4346
|
const path = parseEmfPlusPath(view, o, pathSize);
|
|
3366
4347
|
if (path) {
|
|
3367
|
-
|
|
4348
|
+
const flat = flattenFirstFigure(path.points, path.types, surroundAt);
|
|
4349
|
+
boundaryPts = flat.points;
|
|
4350
|
+
boundaryArgb = flat.argb;
|
|
3368
4351
|
}
|
|
3369
4352
|
o += pathSize;
|
|
3370
4353
|
}
|
|
@@ -3378,6 +4361,7 @@ function parsePathGradient(view, b, end) {
|
|
|
3378
4361
|
x: view.getFloat32(o + i * 8, true),
|
|
3379
4362
|
y: view.getFloat32(o + i * 8 + 4, true)
|
|
3380
4363
|
});
|
|
4364
|
+
boundaryArgb.push(surroundAt(i));
|
|
3381
4365
|
}
|
|
3382
4366
|
o += ptCount * 8;
|
|
3383
4367
|
}
|
|
@@ -3387,31 +4371,50 @@ function parsePathGradient(view, b, end) {
|
|
|
3387
4371
|
transform = readTransform(view, o);
|
|
3388
4372
|
o += 24;
|
|
3389
4373
|
}
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
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));
|
|
3393
4379
|
}
|
|
3394
4380
|
const surroundArgb = surround.length > 0 ? surround[0] : centerArgb;
|
|
3395
4381
|
let stops = [
|
|
3396
|
-
{ offset: 0, color: argbToRgba(centerArgb) },
|
|
3397
|
-
{ offset: 1, color: argbToRgba(surroundArgb) }
|
|
4382
|
+
{ offset: 0, color: argbToRgba(centerArgb), argb: centerArgb },
|
|
4383
|
+
{ offset: 1, color: argbToRgba(surroundArgb), argb: surroundArgb }
|
|
3398
4384
|
];
|
|
4385
|
+
let blend = null;
|
|
4386
|
+
let preset = null;
|
|
3399
4387
|
if (flags & BRUSH_DATA_PRESET_COLORS) {
|
|
3400
|
-
const
|
|
3401
|
-
if (
|
|
3402
|
-
stops =
|
|
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;
|
|
3403
4396
|
}
|
|
3404
4397
|
} else if (flags & BRUSH_DATA_BLEND_FACTORS_H) {
|
|
3405
|
-
const
|
|
3406
|
-
if (
|
|
3407
|
-
stops =
|
|
4398
|
+
const blendData = readBlendFactors(view, o, end);
|
|
4399
|
+
if (blendData) {
|
|
4400
|
+
stops = blendData.entries.map((e) => ({
|
|
3408
4401
|
offset: 1 - e.pos,
|
|
3409
|
-
color: lerpArgbToRgba(surroundArgb, centerArgb, e.factor)
|
|
4402
|
+
color: lerpArgbToRgba(surroundArgb, centerArgb, e.factor),
|
|
4403
|
+
argb: lerpArgb2(surroundArgb, centerArgb, e.factor)
|
|
3410
4404
|
}));
|
|
4405
|
+
blend = {
|
|
4406
|
+
positions: blendData.entries.map((e) => e.pos),
|
|
4407
|
+
factors: blendData.entries.map((e) => e.factor)
|
|
4408
|
+
};
|
|
4409
|
+
o = blendData.next;
|
|
3411
4410
|
}
|
|
3412
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
|
+
}
|
|
3413
4416
|
let r = 0;
|
|
3414
|
-
for (const p of
|
|
4417
|
+
for (const p of worldBoundary) {
|
|
3415
4418
|
const d = Math.hypot(p.x - cx, p.y - cy);
|
|
3416
4419
|
if (d > r) {
|
|
3417
4420
|
r = d;
|
|
@@ -3421,14 +4424,85 @@ function parsePathGradient(view, b, end) {
|
|
|
3421
4424
|
return { kind: "plus-brush", color: argbToRgba(centerArgb) };
|
|
3422
4425
|
}
|
|
3423
4426
|
emfLog(
|
|
3424
|
-
`parseEmfPlusBrushObject: path gradient centre=(${cx.toFixed(1)},${cy.toFixed(1)}),
|
|
4427
|
+
`parseEmfPlusBrushObject: path gradient centre=(${cx.toFixed(1)},${cy.toFixed(1)}), ${boundaryPts.length} boundary point(s)`
|
|
3425
4428
|
);
|
|
3426
4429
|
return {
|
|
3427
4430
|
kind: "plus-brush",
|
|
3428
4431
|
color: argbToRgba(centerArgb),
|
|
3429
|
-
gradient: {
|
|
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
|
+
}
|
|
3430
4450
|
};
|
|
3431
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
|
+
}
|
|
3432
4506
|
function parseEmfPlusBrushObject(view, dataOff, recDataSize) {
|
|
3433
4507
|
if (recDataSize < 8) {
|
|
3434
4508
|
return null;
|
|
@@ -4449,7 +5523,7 @@ function replayEmfRecords(view, ctx, bounds, canvasW, canvasH, dpiScale = 1, rep
|
|
|
4449
5523
|
);
|
|
4450
5524
|
break;
|
|
4451
5525
|
}
|
|
4452
|
-
if (recType ===
|
|
5526
|
+
if (recType === EMR_SETMETARGN || recType === EMR_SETICMMODE || recType === EMR_SETLAYOUT || recType === EMR_HEADER) {
|
|
4453
5527
|
offset += recSize;
|
|
4454
5528
|
continue;
|
|
4455
5529
|
}
|
|
@@ -4844,8 +5918,8 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH, replayOptions = {
|
|
|
4844
5918
|
case META_CREATEFONTINDIRECT:
|
|
4845
5919
|
if (recSize >= 24) {
|
|
4846
5920
|
let family = "";
|
|
4847
|
-
for (let i = 0; i < 32 && dataOff +
|
|
4848
|
-
const ch = view.getUint8(dataOff +
|
|
5921
|
+
for (let i = 0; i < 32 && dataOff + 18 + i < offset + recSize; i++) {
|
|
5922
|
+
const ch = view.getUint8(dataOff + 18 + i);
|
|
4849
5923
|
if (ch === 0) {
|
|
4850
5924
|
break;
|
|
4851
5925
|
}
|
|
@@ -4854,12 +5928,15 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH, replayOptions = {
|
|
|
4854
5928
|
const slot = allocObjectSlot();
|
|
4855
5929
|
objectTable.set(slot, {
|
|
4856
5930
|
kind: "font",
|
|
4857
|
-
|
|
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),
|
|
4858
5934
|
weight: view.getInt16(dataOff + 8, true),
|
|
4859
5935
|
italic: view.getUint8(dataOff + 10) !== 0,
|
|
4860
5936
|
underline: view.getUint8(dataOff + 11) !== 0,
|
|
4861
5937
|
strikeOut: view.getUint8(dataOff + 12) !== 0,
|
|
4862
|
-
family: family || "sans-serif"
|
|
5938
|
+
family: family || "sans-serif",
|
|
5939
|
+
escapementTenthDeg: view.getInt16(dataOff + 4, true)
|
|
4863
5940
|
});
|
|
4864
5941
|
}
|
|
4865
5942
|
break;
|
|
@@ -4884,6 +5961,7 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH, replayOptions = {
|
|
|
4884
5961
|
state.fontUnderline = obj.underline;
|
|
4885
5962
|
state.fontStrikeOut = obj.strikeOut;
|
|
4886
5963
|
state.fontFamily = obj.family;
|
|
5964
|
+
state.fontEscapementTenthDeg = obj.escapementTenthDeg ?? 0;
|
|
4887
5965
|
break;
|
|
4888
5966
|
}
|
|
4889
5967
|
}
|