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