emf-converter 1.5.0 → 2.0.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 +18 -14
- package/dist/index.d.mts +8 -12
- package/dist/index.d.ts +8 -12
- package/dist/index.js +933 -261
- package/dist/index.mjs +933 -261
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,55 @@
|
|
|
1
|
+
// src/emf-color-helpers.ts
|
|
2
|
+
function colorRefToHex(r, g, b) {
|
|
3
|
+
const toHex = (v) => v.toString(16).padStart(2, "0");
|
|
4
|
+
return `#${toHex(r & 255)}${toHex(g & 255)}${toHex(b & 255)}`;
|
|
5
|
+
}
|
|
6
|
+
function readColorRef(view, offset) {
|
|
7
|
+
const r = view.getUint8(offset);
|
|
8
|
+
const g = view.getUint8(offset + 1);
|
|
9
|
+
const b = view.getUint8(offset + 2);
|
|
10
|
+
return colorRefToHex(r, g, b);
|
|
11
|
+
}
|
|
12
|
+
function argbToRgba(argb) {
|
|
13
|
+
const a = (argb >>> 24 & 255) / 255;
|
|
14
|
+
const r = argb >>> 16 & 255;
|
|
15
|
+
const g = argb >>> 8 & 255;
|
|
16
|
+
const b = argb & 255;
|
|
17
|
+
return `rgba(${r},${g},${b},${a.toFixed(3)})`;
|
|
18
|
+
}
|
|
19
|
+
function lerpArgbToRgba(argbA, argbB, t) {
|
|
20
|
+
const tc = Math.min(1, Math.max(0, t));
|
|
21
|
+
const mix = (a2, b2) => Math.round(a2 + (b2 - a2) * tc);
|
|
22
|
+
const aA = argbA >>> 24 & 255;
|
|
23
|
+
const aB = argbB >>> 24 & 255;
|
|
24
|
+
const r = mix(argbA >>> 16 & 255, argbB >>> 16 & 255);
|
|
25
|
+
const g = mix(argbA >>> 8 & 255, argbB >>> 8 & 255);
|
|
26
|
+
const b = mix(argbA & 255, argbB & 255);
|
|
27
|
+
const a = mix(aA, aB) / 255;
|
|
28
|
+
return `rgba(${r},${g},${b},${a.toFixed(3)})`;
|
|
29
|
+
}
|
|
30
|
+
function invertCssColor(color) {
|
|
31
|
+
const hex = /^#([0-9a-f]{6})$/i.exec(color);
|
|
32
|
+
if (hex) {
|
|
33
|
+
const v = parseInt(hex[1], 16);
|
|
34
|
+
const inv = 16777215 ^ v;
|
|
35
|
+
return `#${inv.toString(16).padStart(6, "0")}`;
|
|
36
|
+
}
|
|
37
|
+
const shortHex = /^#([0-9a-f]{3})$/i.exec(color);
|
|
38
|
+
if (shortHex) {
|
|
39
|
+
const [r, g, b] = shortHex[1].split("").map((c) => parseInt(c + c, 16));
|
|
40
|
+
const toHex = (v) => (255 - v).toString(16).padStart(2, "0");
|
|
41
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
42
|
+
}
|
|
43
|
+
const rgba = /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+)\s*)?\)$/i.exec(color);
|
|
44
|
+
if (rgba) {
|
|
45
|
+
const r = 255 - Math.min(255, parseInt(rgba[1], 10));
|
|
46
|
+
const g = 255 - Math.min(255, parseInt(rgba[2], 10));
|
|
47
|
+
const b = 255 - Math.min(255, parseInt(rgba[3], 10));
|
|
48
|
+
return rgba[4] !== void 0 ? `rgba(${r},${g},${b},${rgba[4]})` : `rgb(${r},${g},${b})`;
|
|
49
|
+
}
|
|
50
|
+
return color;
|
|
51
|
+
}
|
|
52
|
+
|
|
1
53
|
// src/emf-constants.ts
|
|
2
54
|
var EMR_HEADER = 1;
|
|
3
55
|
var EMR_POLYBEZIER = 2;
|
|
@@ -19,10 +71,21 @@ var EMR_SETBKMODE = 18;
|
|
|
19
71
|
var EMR_SETPOLYFILLMODE = 19;
|
|
20
72
|
var EMR_SETROP2 = 20;
|
|
21
73
|
var EMR_SETSTRETCHBLTMODE = 21;
|
|
74
|
+
var R2_BLACK = 1;
|
|
75
|
+
var R2_NOTMERGEPEN = 2;
|
|
76
|
+
var R2_MASKNOTPEN = 3;
|
|
77
|
+
var R2_NOTCOPYPEN = 4;
|
|
78
|
+
var R2_MASKPENNOT = 5;
|
|
22
79
|
var R2_NOT = 6;
|
|
23
80
|
var R2_XORPEN = 7;
|
|
81
|
+
var R2_NOTMASKPEN = 8;
|
|
24
82
|
var R2_MASKPEN = 9;
|
|
83
|
+
var R2_NOTXORPEN = 10;
|
|
84
|
+
var R2_NOP = 11;
|
|
85
|
+
var R2_MERGENOTPEN = 12;
|
|
86
|
+
var R2_MERGEPENNOT = 14;
|
|
25
87
|
var R2_MERGEPEN = 15;
|
|
88
|
+
var R2_WHITE = 16;
|
|
26
89
|
var MAX_CANVAS_DIMENSION = 8192;
|
|
27
90
|
var MAX_RECORDS_DEFAULT = 2e5;
|
|
28
91
|
var MAX_RECORDS_EMFPLUS_DEFAULT = 5e5;
|
|
@@ -255,28 +318,61 @@ function createTempCanvas(width, height) {
|
|
|
255
318
|
}
|
|
256
319
|
return null;
|
|
257
320
|
}
|
|
258
|
-
function
|
|
321
|
+
function rop2Paint(rop2) {
|
|
259
322
|
switch (rop2) {
|
|
323
|
+
case R2_BLACK:
|
|
324
|
+
return { gco: "source-over", colorTransform: "black", exact: true };
|
|
325
|
+
case R2_WHITE:
|
|
326
|
+
return { gco: "source-over", colorTransform: "white", exact: true };
|
|
327
|
+
case R2_NOP:
|
|
328
|
+
return { gco: "source-over", colorTransform: "skip", exact: true };
|
|
329
|
+
case R2_NOTCOPYPEN:
|
|
330
|
+
return { gco: "source-over", colorTransform: "invert", exact: true };
|
|
331
|
+
case R2_NOT:
|
|
332
|
+
return { gco: "difference", colorTransform: "white", exact: true };
|
|
260
333
|
case R2_XORPEN:
|
|
261
|
-
|
|
334
|
+
case R2_MASKPENNOT:
|
|
335
|
+
return { gco: "difference", colorTransform: "none", exact: false };
|
|
336
|
+
case R2_NOTXORPEN:
|
|
337
|
+
return { gco: "difference", colorTransform: "invert", exact: false };
|
|
262
338
|
case R2_MASKPEN:
|
|
263
|
-
return "
|
|
339
|
+
return { gco: "darken", colorTransform: "none", exact: false };
|
|
340
|
+
case R2_MASKNOTPEN:
|
|
341
|
+
case R2_NOTMERGEPEN:
|
|
342
|
+
return { gco: "darken", colorTransform: "invert", exact: false };
|
|
264
343
|
case R2_MERGEPEN:
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
344
|
+
case R2_MERGEPENNOT:
|
|
345
|
+
return { gco: "lighten", colorTransform: "none", exact: false };
|
|
346
|
+
case R2_MERGENOTPEN:
|
|
347
|
+
case R2_NOTMASKPEN:
|
|
348
|
+
return { gco: "lighten", colorTransform: "invert", exact: false };
|
|
349
|
+
default:
|
|
350
|
+
return { gco: "source-over", colorTransform: "none", exact: true };
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function rop2TransformColor(color, transform) {
|
|
354
|
+
switch (transform) {
|
|
355
|
+
case "invert":
|
|
356
|
+
return invertCssColor(color);
|
|
357
|
+
case "black":
|
|
358
|
+
return "#000000";
|
|
359
|
+
case "white":
|
|
360
|
+
return "#ffffff";
|
|
361
|
+
case "skip":
|
|
362
|
+
return "rgba(0,0,0,0)";
|
|
268
363
|
default:
|
|
269
|
-
return
|
|
364
|
+
return color;
|
|
270
365
|
}
|
|
271
366
|
}
|
|
272
367
|
function applyPen(ctx, state) {
|
|
273
|
-
|
|
368
|
+
const paint = rop2Paint(state.rop2);
|
|
369
|
+
ctx.globalCompositeOperation = paint.gco;
|
|
274
370
|
if (state.penStyle === 5) {
|
|
275
371
|
ctx.strokeStyle = "rgba(0,0,0,0)";
|
|
276
372
|
ctx.lineWidth = 0;
|
|
277
373
|
return;
|
|
278
374
|
}
|
|
279
|
-
ctx.strokeStyle = state.penColor;
|
|
375
|
+
ctx.strokeStyle = rop2TransformColor(state.penColor, paint.colorTransform);
|
|
280
376
|
ctx.lineWidth = Math.max(state.penWidth, 1);
|
|
281
377
|
switch (state.penStyle) {
|
|
282
378
|
case 1:
|
|
@@ -297,12 +393,13 @@ function applyPen(ctx, state) {
|
|
|
297
393
|
}
|
|
298
394
|
}
|
|
299
395
|
function applyBrush(ctx, state) {
|
|
300
|
-
|
|
396
|
+
const paint = rop2Paint(state.rop2);
|
|
397
|
+
ctx.globalCompositeOperation = paint.gco;
|
|
301
398
|
if (state.brushStyle === 1) {
|
|
302
399
|
ctx.fillStyle = "rgba(0,0,0,0)";
|
|
303
400
|
return;
|
|
304
401
|
}
|
|
305
|
-
ctx.fillStyle = state.brushColor;
|
|
402
|
+
ctx.fillStyle = rop2TransformColor(state.brushColor, paint.colorTransform);
|
|
306
403
|
}
|
|
307
404
|
function cssFontWeight(weight) {
|
|
308
405
|
if (!weight || weight === 400) {
|
|
@@ -536,49 +633,36 @@ function parseWmfHeader(view) {
|
|
|
536
633
|
};
|
|
537
634
|
}
|
|
538
635
|
|
|
539
|
-
// src/emf-color-helpers.ts
|
|
540
|
-
function colorRefToHex(r, g, b) {
|
|
541
|
-
const toHex = (v) => v.toString(16).padStart(2, "0");
|
|
542
|
-
return `#${toHex(r & 255)}${toHex(g & 255)}${toHex(b & 255)}`;
|
|
543
|
-
}
|
|
544
|
-
function readColorRef(view, offset) {
|
|
545
|
-
const r = view.getUint8(offset);
|
|
546
|
-
const g = view.getUint8(offset + 1);
|
|
547
|
-
const b = view.getUint8(offset + 2);
|
|
548
|
-
return colorRefToHex(r, g, b);
|
|
549
|
-
}
|
|
550
|
-
function argbToRgba(argb) {
|
|
551
|
-
const a = (argb >>> 24 & 255) / 255;
|
|
552
|
-
const r = argb >>> 16 & 255;
|
|
553
|
-
const g = argb >>> 8 & 255;
|
|
554
|
-
const b = argb & 255;
|
|
555
|
-
return `rgba(${r},${g},${b},${a.toFixed(3)})`;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
636
|
// src/emf-gdi-coord.ts
|
|
559
637
|
function gmx(r, x) {
|
|
638
|
+
const wt = r.state.worldTransform;
|
|
639
|
+
const px = wt[0] * x + wt[4];
|
|
560
640
|
if (r.useMappingMode) {
|
|
561
|
-
return (
|
|
641
|
+
return (px - r.windowOrg.x) / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1) + r.viewportOrg.x;
|
|
562
642
|
}
|
|
563
|
-
return (
|
|
643
|
+
return (px - r.bounds.left) * r.sx;
|
|
564
644
|
}
|
|
565
645
|
function gmy(r, y) {
|
|
646
|
+
const wt = r.state.worldTransform;
|
|
647
|
+
const py = wt[3] * y + wt[5];
|
|
566
648
|
if (r.useMappingMode) {
|
|
567
|
-
return (
|
|
649
|
+
return (py - r.windowOrg.y) / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1) + r.viewportOrg.y;
|
|
568
650
|
}
|
|
569
|
-
return (
|
|
651
|
+
return (py - r.bounds.top) * r.sy;
|
|
570
652
|
}
|
|
571
653
|
function gmw(r, w) {
|
|
654
|
+
const pw = r.state.worldTransform[0] * w;
|
|
572
655
|
if (r.useMappingMode) {
|
|
573
|
-
return
|
|
656
|
+
return pw / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1);
|
|
574
657
|
}
|
|
575
|
-
return
|
|
658
|
+
return pw * r.sx;
|
|
576
659
|
}
|
|
577
660
|
function gmh(r, h) {
|
|
661
|
+
const ph = r.state.worldTransform[3] * h;
|
|
578
662
|
if (r.useMappingMode) {
|
|
579
|
-
return
|
|
663
|
+
return ph / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1);
|
|
580
664
|
}
|
|
581
|
-
return
|
|
665
|
+
return ph * r.sy;
|
|
582
666
|
}
|
|
583
667
|
function activateGdiMappingMode(r) {
|
|
584
668
|
r.useMappingMode = true;
|
|
@@ -782,6 +866,218 @@ function handleEmfGdiShapeRecord(rCtx, recType, dataOff, recSize) {
|
|
|
782
866
|
}
|
|
783
867
|
}
|
|
784
868
|
|
|
869
|
+
// src/emf-clip-region.ts
|
|
870
|
+
var CLIP_HUGE = 1 << 24;
|
|
871
|
+
function rectClipShape(x, y, w, h) {
|
|
872
|
+
return { cmds: [{ op: "rect", x, y, w, h }], fillRule: "nonzero", simple: true };
|
|
873
|
+
}
|
|
874
|
+
function rectsClipShape(rects) {
|
|
875
|
+
return {
|
|
876
|
+
cmds: rects.map((r) => ({ op: "rect", x: r.x, y: r.y, w: r.w, h: r.h })),
|
|
877
|
+
fillRule: "nonzero",
|
|
878
|
+
simple: true
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
function emptyClipShape() {
|
|
882
|
+
return { cmds: [{ op: "rect", x: 0, y: 0, w: 0, h: 0 }], fillRule: "nonzero", simple: true };
|
|
883
|
+
}
|
|
884
|
+
function translateClipShape(shape, dx, dy) {
|
|
885
|
+
return {
|
|
886
|
+
...shape,
|
|
887
|
+
cmds: shape.cmds.map((c) => {
|
|
888
|
+
switch (c.op) {
|
|
889
|
+
case "rect":
|
|
890
|
+
return { ...c, x: c.x + dx, y: c.y + dy };
|
|
891
|
+
case "moveTo":
|
|
892
|
+
case "lineTo":
|
|
893
|
+
return { ...c, x: c.x + dx, y: c.y + dy };
|
|
894
|
+
case "bezierCurveTo":
|
|
895
|
+
return {
|
|
896
|
+
...c,
|
|
897
|
+
cp1x: c.cp1x + dx,
|
|
898
|
+
cp1y: c.cp1y + dy,
|
|
899
|
+
cp2x: c.cp2x + dx,
|
|
900
|
+
cp2y: c.cp2y + dy,
|
|
901
|
+
x: c.x + dx,
|
|
902
|
+
y: c.y + dy
|
|
903
|
+
};
|
|
904
|
+
case "closePath":
|
|
905
|
+
return c;
|
|
906
|
+
}
|
|
907
|
+
})
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
function translateClipRegion(region, dx, dy) {
|
|
911
|
+
if (!region) {
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
return region.map((s) => translateClipShape(s, dx, dy));
|
|
915
|
+
}
|
|
916
|
+
function isComposable(shape) {
|
|
917
|
+
return shape.simple && shape.fillRule === "nonzero";
|
|
918
|
+
}
|
|
919
|
+
function invertClipShape(shape) {
|
|
920
|
+
return {
|
|
921
|
+
cmds: [
|
|
922
|
+
{ op: "rect", x: -CLIP_HUGE, y: -CLIP_HUGE, w: 2 * CLIP_HUGE, h: 2 * CLIP_HUGE },
|
|
923
|
+
...shape.cmds
|
|
924
|
+
],
|
|
925
|
+
fillRule: "evenodd",
|
|
926
|
+
simple: false
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
function combineClip(current, shape, op) {
|
|
930
|
+
switch (op) {
|
|
931
|
+
case "replace":
|
|
932
|
+
return { region: [shape], exact: true };
|
|
933
|
+
case "intersect":
|
|
934
|
+
return { region: current ? [...current, shape] : [shape], exact: true };
|
|
935
|
+
case "exclude": {
|
|
936
|
+
if (!isComposable(shape)) {
|
|
937
|
+
return { region: current ? [...current, shape] : [shape], exact: false };
|
|
938
|
+
}
|
|
939
|
+
const inv = invertClipShape(shape);
|
|
940
|
+
return { region: current ? [...current, inv] : [inv], exact: true };
|
|
941
|
+
}
|
|
942
|
+
case "union": {
|
|
943
|
+
if (!current) {
|
|
944
|
+
return { region: null, exact: true };
|
|
945
|
+
}
|
|
946
|
+
if (current.length === 1 && isComposable(current[0]) && isComposable(shape)) {
|
|
947
|
+
return {
|
|
948
|
+
region: [
|
|
949
|
+
{ cmds: [...current[0].cmds, ...shape.cmds], fillRule: "nonzero", simple: false }
|
|
950
|
+
],
|
|
951
|
+
exact: false
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
return { region: current, exact: false };
|
|
955
|
+
}
|
|
956
|
+
case "xor": {
|
|
957
|
+
if (!current) {
|
|
958
|
+
if (isComposable(shape)) {
|
|
959
|
+
return { region: [invertClipShape(shape)], exact: true };
|
|
960
|
+
}
|
|
961
|
+
return { region: [shape], exact: false };
|
|
962
|
+
}
|
|
963
|
+
if (current.length === 1 && isComposable(current[0]) && isComposable(shape)) {
|
|
964
|
+
return {
|
|
965
|
+
region: [
|
|
966
|
+
{ cmds: [...current[0].cmds, ...shape.cmds], fillRule: "evenodd", simple: false }
|
|
967
|
+
],
|
|
968
|
+
exact: true
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
if (isComposable(shape)) {
|
|
972
|
+
return { region: [...current, invertClipShape(shape)], exact: false };
|
|
973
|
+
}
|
|
974
|
+
return { region: [...current, shape], exact: false };
|
|
975
|
+
}
|
|
976
|
+
case "complement": {
|
|
977
|
+
if (!current) {
|
|
978
|
+
return { region: [emptyClipShape()], exact: true };
|
|
979
|
+
}
|
|
980
|
+
if (current.length === 1 && isComposable(current[0])) {
|
|
981
|
+
return { region: [shape, invertClipShape(current[0])], exact: true };
|
|
982
|
+
}
|
|
983
|
+
return { region: [shape], exact: false };
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
function combineClipRegions(current, incoming, op) {
|
|
988
|
+
if (op === "replace") {
|
|
989
|
+
return { region: incoming, exact: true };
|
|
990
|
+
}
|
|
991
|
+
if (incoming && incoming.length === 1) {
|
|
992
|
+
return combineClip(current, incoming[0], op);
|
|
993
|
+
}
|
|
994
|
+
if (!incoming) {
|
|
995
|
+
switch (op) {
|
|
996
|
+
case "intersect":
|
|
997
|
+
return { region: current, exact: true };
|
|
998
|
+
case "union":
|
|
999
|
+
return { region: null, exact: true };
|
|
1000
|
+
case "exclude":
|
|
1001
|
+
return { region: [emptyClipShape()], exact: true };
|
|
1002
|
+
case "xor":
|
|
1003
|
+
case "complement": {
|
|
1004
|
+
if (!current) {
|
|
1005
|
+
return { region: [emptyClipShape()], exact: true };
|
|
1006
|
+
}
|
|
1007
|
+
if (current.length === 1) {
|
|
1008
|
+
return combineClip(null, current[0], "exclude");
|
|
1009
|
+
}
|
|
1010
|
+
return { region: current, exact: false };
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
switch (op) {
|
|
1015
|
+
case "intersect":
|
|
1016
|
+
return { region: current ? [...current, ...incoming] : incoming, exact: true };
|
|
1017
|
+
case "union":
|
|
1018
|
+
return { region: current, exact: false };
|
|
1019
|
+
case "xor":
|
|
1020
|
+
return { region: current ?? incoming, exact: false };
|
|
1021
|
+
case "exclude":
|
|
1022
|
+
return { region: current, exact: false };
|
|
1023
|
+
case "complement": {
|
|
1024
|
+
if (!current) {
|
|
1025
|
+
return { region: [emptyClipShape()], exact: true };
|
|
1026
|
+
}
|
|
1027
|
+
if (current.length === 1) {
|
|
1028
|
+
return combineClip(incoming, current[0], "exclude");
|
|
1029
|
+
}
|
|
1030
|
+
return { region: incoming, exact: false };
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
function replayClipCmds(ctx, cmds) {
|
|
1035
|
+
for (const c of cmds) {
|
|
1036
|
+
switch (c.op) {
|
|
1037
|
+
case "rect":
|
|
1038
|
+
ctx.rect(c.x, c.y, c.w, c.h);
|
|
1039
|
+
break;
|
|
1040
|
+
case "moveTo":
|
|
1041
|
+
ctx.moveTo(c.x, c.y);
|
|
1042
|
+
break;
|
|
1043
|
+
case "lineTo":
|
|
1044
|
+
ctx.lineTo(c.x, c.y);
|
|
1045
|
+
break;
|
|
1046
|
+
case "bezierCurveTo":
|
|
1047
|
+
ctx.bezierCurveTo(c.cp1x, c.cp1y, c.cp2x, c.cp2y, c.x, c.y);
|
|
1048
|
+
break;
|
|
1049
|
+
case "closePath":
|
|
1050
|
+
ctx.closePath();
|
|
1051
|
+
break;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
function applyClipShapes(ctx, shapes) {
|
|
1056
|
+
for (const s of shapes) {
|
|
1057
|
+
ctx.beginPath();
|
|
1058
|
+
replayClipCmds(ctx, s.cmds);
|
|
1059
|
+
try {
|
|
1060
|
+
ctx.clip(s.fillRule);
|
|
1061
|
+
} catch {
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
function reapplyClipRegion(holder, region, identityTransform = false) {
|
|
1066
|
+
const { ctx } = holder;
|
|
1067
|
+
while (holder.clipSaveDepth > 0) {
|
|
1068
|
+
ctx.restore();
|
|
1069
|
+
holder.clipSaveDepth--;
|
|
1070
|
+
}
|
|
1071
|
+
if (region) {
|
|
1072
|
+
ctx.save();
|
|
1073
|
+
holder.clipSaveDepth = 1;
|
|
1074
|
+
if (identityTransform) {
|
|
1075
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
1076
|
+
}
|
|
1077
|
+
applyClipShapes(ctx, region);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
785
1081
|
// src/emf-dib-rle-decoder.ts
|
|
786
1082
|
function decodeRleBitmap(view, bitsOffset, bitsSize, width, height, _topDown, isRle4, colorTable, out, setPixel) {
|
|
787
1083
|
let x = 0;
|
|
@@ -1104,14 +1400,9 @@ function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
|
|
|
1104
1400
|
if (text.length > 0) {
|
|
1105
1401
|
applyFont(ctx, state);
|
|
1106
1402
|
ctx.fillStyle = state.textColor;
|
|
1107
|
-
|
|
1403
|
+
const vAlign = state.textAlign & 24;
|
|
1404
|
+
const alignBaseline = vAlign === 24 ? "alphabetic" : vAlign === 8 ? "bottom" : "top";
|
|
1108
1405
|
let alignHoriz = "left";
|
|
1109
|
-
if (state.textAlign & 8) {
|
|
1110
|
-
alignBaseline = "bottom";
|
|
1111
|
-
}
|
|
1112
|
-
if (state.textAlign & 24) {
|
|
1113
|
-
alignBaseline = "alphabetic";
|
|
1114
|
-
}
|
|
1115
1406
|
if (state.textAlign & 6) {
|
|
1116
1407
|
alignHoriz = "center";
|
|
1117
1408
|
}
|
|
@@ -1139,17 +1430,29 @@ function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
|
|
|
1139
1430
|
}
|
|
1140
1431
|
return true;
|
|
1141
1432
|
}
|
|
1433
|
+
var ROP_PATCOPY = 15728673;
|
|
1434
|
+
var BS_NULL = 1;
|
|
1142
1435
|
function handleBitBlt(rCtx, offset, dataOff, recSize) {
|
|
1143
|
-
const { ctx, view } = rCtx;
|
|
1436
|
+
const { ctx, view, state } = rCtx;
|
|
1144
1437
|
if (recSize >= 96) {
|
|
1145
1438
|
const dstX = view.getInt32(dataOff + 16, true);
|
|
1146
1439
|
const dstY = view.getInt32(dataOff + 20, true);
|
|
1147
1440
|
const dstW = view.getInt32(dataOff + 24, true);
|
|
1148
1441
|
const dstH = view.getInt32(dataOff + 28, true);
|
|
1442
|
+
const rop = view.getUint32(dataOff + 32, true);
|
|
1149
1443
|
const offBmiSrc = view.getUint32(dataOff + 76, true);
|
|
1150
1444
|
const cbBmiSrc = view.getUint32(dataOff + 80, true);
|
|
1151
1445
|
const offBitsSrc = view.getUint32(dataOff + 84, true);
|
|
1152
1446
|
const cbBitsSrc = view.getUint32(dataOff + 88, true);
|
|
1447
|
+
if (offBmiSrc === 0 && rop === ROP_PATCOPY) {
|
|
1448
|
+
const prevFill = ctx.fillStyle;
|
|
1449
|
+
if (state.brushStyle !== BS_NULL) {
|
|
1450
|
+
ctx.fillStyle = state.brushColor;
|
|
1451
|
+
ctx.fillRect(gmx(rCtx, dstX), gmy(rCtx, dstY), gmw(rCtx, dstW), gmh(rCtx, dstH));
|
|
1452
|
+
}
|
|
1453
|
+
ctx.fillStyle = prevFill;
|
|
1454
|
+
return true;
|
|
1455
|
+
}
|
|
1153
1456
|
if (offBmiSrc > 0 && cbBmiSrc > 0 && offBitsSrc > 0 && cbBitsSrc > 0) {
|
|
1154
1457
|
const imageData = decodeDibToImageData(
|
|
1155
1458
|
view,
|
|
@@ -1209,86 +1512,134 @@ function handleStretchDibits(rCtx, offset, dataOff, recSize) {
|
|
|
1209
1512
|
}
|
|
1210
1513
|
return true;
|
|
1211
1514
|
}
|
|
1515
|
+
function gdiCombineClip(rCtx, shape, op) {
|
|
1516
|
+
const { ctx } = rCtx;
|
|
1517
|
+
if (rCtx.clipUntracked && op !== "replace") {
|
|
1518
|
+
switch (op) {
|
|
1519
|
+
case "intersect":
|
|
1520
|
+
case "complement": {
|
|
1521
|
+
ctx.save();
|
|
1522
|
+
rCtx.clipSaveDepth++;
|
|
1523
|
+
applyClipShapes(ctx, [shape]);
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
case "exclude":
|
|
1527
|
+
case "xor": {
|
|
1528
|
+
const inv = combineClip(null, shape, "exclude");
|
|
1529
|
+
ctx.save();
|
|
1530
|
+
rCtx.clipSaveDepth++;
|
|
1531
|
+
applyClipShapes(ctx, inv.region ?? [shape]);
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
case "union":
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
const res = combineClip(op === "replace" ? null : rCtx.clipRegion ?? null, shape, op);
|
|
1539
|
+
if (!res.exact) ;
|
|
1540
|
+
rCtx.clipRegion = res.region;
|
|
1541
|
+
rCtx.clipUntracked = false;
|
|
1542
|
+
reapplyClipRegion(rCtx, res.region);
|
|
1543
|
+
}
|
|
1544
|
+
function readClipRectShape(rCtx, dataOff) {
|
|
1545
|
+
const { view } = rCtx;
|
|
1546
|
+
const left = view.getInt32(dataOff, true);
|
|
1547
|
+
const top = view.getInt32(dataOff + 4, true);
|
|
1548
|
+
const right = view.getInt32(dataOff + 8, true);
|
|
1549
|
+
const bottom = view.getInt32(dataOff + 12, true);
|
|
1550
|
+
return rectClipShape(
|
|
1551
|
+
gmx(rCtx, left),
|
|
1552
|
+
gmy(rCtx, top),
|
|
1553
|
+
gmw(rCtx, right - left),
|
|
1554
|
+
gmh(rCtx, bottom - top)
|
|
1555
|
+
);
|
|
1556
|
+
}
|
|
1212
1557
|
function handleIntersectClipRect(rCtx, dataOff, recSize) {
|
|
1213
|
-
const { ctx, view } = rCtx;
|
|
1214
1558
|
if (recSize >= 24) {
|
|
1215
|
-
|
|
1216
|
-
const top = view.getInt32(dataOff + 4, true);
|
|
1217
|
-
const right = view.getInt32(dataOff + 8, true);
|
|
1218
|
-
const bottom = view.getInt32(dataOff + 12, true);
|
|
1219
|
-
ctx.save();
|
|
1220
|
-
rCtx.clipSaveDepth++;
|
|
1221
|
-
ctx.beginPath();
|
|
1222
|
-
ctx.rect(gmx(rCtx, left), gmy(rCtx, top), gmw(rCtx, right - left), gmh(rCtx, bottom - top));
|
|
1223
|
-
try {
|
|
1224
|
-
ctx.clip();
|
|
1225
|
-
} catch {
|
|
1226
|
-
}
|
|
1559
|
+
gdiCombineClip(rCtx, readClipRectShape(rCtx, dataOff), "intersect");
|
|
1227
1560
|
}
|
|
1228
1561
|
return true;
|
|
1229
1562
|
}
|
|
1563
|
+
function handleExcludeClipRect(rCtx, dataOff, recSize) {
|
|
1564
|
+
if (recSize >= 24) {
|
|
1565
|
+
gdiCombineClip(rCtx, readClipRectShape(rCtx, dataOff), "exclude");
|
|
1566
|
+
}
|
|
1567
|
+
return true;
|
|
1568
|
+
}
|
|
1569
|
+
var RGN_MODE_OPS = {
|
|
1570
|
+
1: "intersect",
|
|
1571
|
+
// RGN_AND
|
|
1572
|
+
2: "union",
|
|
1573
|
+
// RGN_OR
|
|
1574
|
+
3: "xor",
|
|
1575
|
+
// RGN_XOR
|
|
1576
|
+
4: "exclude",
|
|
1577
|
+
// RGN_DIFF
|
|
1578
|
+
5: "replace"
|
|
1579
|
+
// RGN_COPY
|
|
1580
|
+
};
|
|
1230
1581
|
function handleExtSelectClipRgn(rCtx, dataOff, recSize) {
|
|
1231
|
-
const {
|
|
1582
|
+
const { view } = rCtx;
|
|
1232
1583
|
if (recSize < 16) {
|
|
1233
1584
|
return true;
|
|
1234
1585
|
}
|
|
1235
1586
|
const cbRgnData = view.getUint32(dataOff, true);
|
|
1236
1587
|
const iMode = view.getUint32(dataOff + 4, true);
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
if (cbRgnData < 32) {
|
|
1247
|
-
return true;
|
|
1248
|
-
}
|
|
1249
|
-
const nCount = view.getUint32(rgnStart + 8, true);
|
|
1250
|
-
if (nCount === 0) {
|
|
1251
|
-
return true;
|
|
1252
|
-
}
|
|
1253
|
-
while (rCtx.clipSaveDepth > 0) {
|
|
1254
|
-
ctx.restore();
|
|
1255
|
-
rCtx.clipSaveDepth--;
|
|
1256
|
-
}
|
|
1257
|
-
ctx.save();
|
|
1258
|
-
rCtx.clipSaveDepth++;
|
|
1259
|
-
ctx.beginPath();
|
|
1260
|
-
const rectsStart = rgnStart + 32;
|
|
1261
|
-
for (let i = 0; i < nCount; i++) {
|
|
1262
|
-
const rOff = rectsStart + i * 16;
|
|
1263
|
-
if (rOff + 16 > dataOff + 8 + cbRgnData) {
|
|
1264
|
-
break;
|
|
1265
|
-
}
|
|
1266
|
-
const left = view.getInt32(rOff, true);
|
|
1267
|
-
const top = view.getInt32(rOff + 4, true);
|
|
1268
|
-
const right = view.getInt32(rOff + 8, true);
|
|
1269
|
-
const bottom = view.getInt32(rOff + 12, true);
|
|
1270
|
-
ctx.rect(gmx(rCtx, left), gmy(rCtx, top), gmw(rCtx, right - left), gmh(rCtx, bottom - top));
|
|
1271
|
-
}
|
|
1272
|
-
try {
|
|
1273
|
-
ctx.clip();
|
|
1274
|
-
} catch {
|
|
1588
|
+
const op = RGN_MODE_OPS[iMode];
|
|
1589
|
+
if (!op) {
|
|
1590
|
+
return true;
|
|
1591
|
+
}
|
|
1592
|
+
if (cbRgnData === 0) {
|
|
1593
|
+
if (op === "replace") {
|
|
1594
|
+
rCtx.clipRegion = null;
|
|
1595
|
+
rCtx.clipUntracked = false;
|
|
1596
|
+
reapplyClipRegion(rCtx, null);
|
|
1275
1597
|
}
|
|
1598
|
+
return true;
|
|
1276
1599
|
}
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
if (recSize >= 24) {
|
|
1281
|
-
rCtx.view.getInt32(dataOff, true);
|
|
1282
|
-
rCtx.view.getInt32(dataOff + 4, true);
|
|
1283
|
-
rCtx.view.getInt32(dataOff + 8, true);
|
|
1284
|
-
rCtx.view.getInt32(dataOff + 12, true);
|
|
1600
|
+
const rgnStart = dataOff + 8;
|
|
1601
|
+
if (cbRgnData < 32) {
|
|
1602
|
+
return true;
|
|
1285
1603
|
}
|
|
1604
|
+
const nCount = view.getUint32(rgnStart + 8, true);
|
|
1605
|
+
if (nCount === 0) {
|
|
1606
|
+
return true;
|
|
1607
|
+
}
|
|
1608
|
+
const rects = [];
|
|
1609
|
+
const rectsStart = rgnStart + 32;
|
|
1610
|
+
for (let i = 0; i < nCount; i++) {
|
|
1611
|
+
const rOff = rectsStart + i * 16;
|
|
1612
|
+
if (rOff + 16 > dataOff + 8 + cbRgnData) {
|
|
1613
|
+
break;
|
|
1614
|
+
}
|
|
1615
|
+
const left = view.getInt32(rOff, true);
|
|
1616
|
+
const top = view.getInt32(rOff + 4, true);
|
|
1617
|
+
const right = view.getInt32(rOff + 8, true);
|
|
1618
|
+
const bottom = view.getInt32(rOff + 12, true);
|
|
1619
|
+
rects.push({
|
|
1620
|
+
x: gmx(rCtx, left),
|
|
1621
|
+
y: gmy(rCtx, top),
|
|
1622
|
+
w: gmw(rCtx, right - left),
|
|
1623
|
+
h: gmh(rCtx, bottom - top)
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
if (rects.length === 0) {
|
|
1627
|
+
return true;
|
|
1628
|
+
}
|
|
1629
|
+
gdiCombineClip(rCtx, rectsClipShape(rects), op);
|
|
1286
1630
|
return true;
|
|
1287
1631
|
}
|
|
1288
1632
|
function handleOffsetClipRgn(rCtx, dataOff, recSize) {
|
|
1289
1633
|
if (recSize >= 16) {
|
|
1290
|
-
rCtx.view.getInt32(dataOff, true);
|
|
1291
|
-
rCtx.view.getInt32(dataOff + 4, true);
|
|
1634
|
+
const dx = rCtx.view.getInt32(dataOff, true);
|
|
1635
|
+
const dy = rCtx.view.getInt32(dataOff + 4, true);
|
|
1636
|
+
if (rCtx.clipUntracked) {
|
|
1637
|
+
return true;
|
|
1638
|
+
}
|
|
1639
|
+
if (rCtx.clipRegion) {
|
|
1640
|
+
rCtx.clipRegion = translateClipRegion(rCtx.clipRegion, gmw(rCtx, dx), gmh(rCtx, dy));
|
|
1641
|
+
reapplyClipRegion(rCtx, rCtx.clipRegion);
|
|
1642
|
+
}
|
|
1292
1643
|
}
|
|
1293
1644
|
return true;
|
|
1294
1645
|
}
|
|
@@ -1611,14 +1962,12 @@ function handleEmfGdiPolyPathRecord(rCtx, recType, offset, dataOff, recSize) {
|
|
|
1611
1962
|
ctx.restore();
|
|
1612
1963
|
rCtx.clipSaveDepth--;
|
|
1613
1964
|
}
|
|
1614
|
-
ctx.save();
|
|
1615
|
-
rCtx.clipSaveDepth++;
|
|
1616
|
-
ctx.clip();
|
|
1617
|
-
} else {
|
|
1618
|
-
ctx.save();
|
|
1619
|
-
rCtx.clipSaveDepth++;
|
|
1620
|
-
ctx.clip();
|
|
1621
1965
|
}
|
|
1966
|
+
ctx.save();
|
|
1967
|
+
rCtx.clipSaveDepth++;
|
|
1968
|
+
ctx.clip(state.polyFillMode === 2 ? "nonzero" : "evenodd");
|
|
1969
|
+
rCtx.clipRegion = null;
|
|
1970
|
+
rCtx.clipUntracked = true;
|
|
1622
1971
|
} catch {
|
|
1623
1972
|
}
|
|
1624
1973
|
return true;
|
|
@@ -1914,7 +2263,9 @@ function createEmfPlusState() {
|
|
|
1914
2263
|
objectTable: /* @__PURE__ */ new Map(),
|
|
1915
2264
|
worldTransform: [1, 0, 0, 1, 0, 0],
|
|
1916
2265
|
saveStack: [],
|
|
1917
|
-
saveIdMap: /* @__PURE__ */ new Map()
|
|
2266
|
+
saveIdMap: /* @__PURE__ */ new Map(),
|
|
2267
|
+
clipRegion: null,
|
|
2268
|
+
clipSaveDepth: 0
|
|
1918
2269
|
};
|
|
1919
2270
|
}
|
|
1920
2271
|
|
|
@@ -1934,8 +2285,19 @@ function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
|
|
|
1934
2285
|
ctx.restore();
|
|
1935
2286
|
rCtx.clipSaveDepth--;
|
|
1936
2287
|
}
|
|
2288
|
+
rCtx.clipStack ?? (rCtx.clipStack = []);
|
|
2289
|
+
rCtx.clipStack.push({
|
|
2290
|
+
region: rCtx.clipRegion ?? null,
|
|
2291
|
+
untracked: rCtx.clipUntracked ?? false
|
|
2292
|
+
});
|
|
1937
2293
|
rCtx.stateStack.push(cloneState(state));
|
|
1938
2294
|
ctx.save();
|
|
2295
|
+
if (rCtx.clipUntracked) {
|
|
2296
|
+
rCtx.clipUntracked = false;
|
|
2297
|
+
rCtx.clipRegion = null;
|
|
2298
|
+
} else if (rCtx.clipRegion) {
|
|
2299
|
+
reapplyClipRegion(rCtx, rCtx.clipRegion);
|
|
2300
|
+
}
|
|
1939
2301
|
return true;
|
|
1940
2302
|
}
|
|
1941
2303
|
case EMR_RESTOREDC: {
|
|
@@ -1946,16 +2308,23 @@ function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
|
|
|
1946
2308
|
}
|
|
1947
2309
|
let rel = view.getInt32(dataOff, true);
|
|
1948
2310
|
if (rel < 0) {
|
|
1949
|
-
rel = rCtx.stateStack.length + rel;
|
|
2311
|
+
rel = rCtx.stateStack.length + rel + 1;
|
|
1950
2312
|
}
|
|
1951
2313
|
while (rCtx.stateStack.length > rel && rCtx.stateStack.length > 0) {
|
|
1952
2314
|
rCtx.stateStack.pop();
|
|
2315
|
+
rCtx.clipStack?.pop();
|
|
1953
2316
|
ctx.restore();
|
|
1954
2317
|
}
|
|
1955
2318
|
const restored = rCtx.stateStack.pop();
|
|
1956
2319
|
if (restored) {
|
|
2320
|
+
const clipSnapshot = rCtx.clipStack?.pop();
|
|
1957
2321
|
Object.assign(state, restored);
|
|
1958
2322
|
ctx.restore();
|
|
2323
|
+
rCtx.clipRegion = clipSnapshot?.region ?? null;
|
|
2324
|
+
rCtx.clipUntracked = false;
|
|
2325
|
+
if (rCtx.clipRegion) {
|
|
2326
|
+
reapplyClipRegion(rCtx, rCtx.clipRegion);
|
|
2327
|
+
}
|
|
1959
2328
|
}
|
|
1960
2329
|
}
|
|
1961
2330
|
return true;
|
|
@@ -2074,6 +2443,47 @@ function parseEmfPlusPath(data, off, maxLen) {
|
|
|
2074
2443
|
const types = new Uint8Array(data.buffer, data.byteOffset + alignedPOff, pointCount);
|
|
2075
2444
|
return { kind: "plus-path", points, types: new Uint8Array(types) };
|
|
2076
2445
|
}
|
|
2446
|
+
function emfPlusPathToClipCmds(path, m) {
|
|
2447
|
+
const tx = (x, y) => m[0] * x + m[2] * y + m[4];
|
|
2448
|
+
const ty = (x, y) => m[1] * x + m[3] * y + m[5];
|
|
2449
|
+
const cmds = [];
|
|
2450
|
+
const pts = path.points;
|
|
2451
|
+
const types = path.types;
|
|
2452
|
+
let i = 0;
|
|
2453
|
+
while (i < pts.length) {
|
|
2454
|
+
const t = types[i] & 15;
|
|
2455
|
+
const close = (types[i] & 128) !== 0;
|
|
2456
|
+
if (t === 0) {
|
|
2457
|
+
cmds.push({ op: "moveTo", x: tx(pts[i].x, pts[i].y), y: ty(pts[i].x, pts[i].y) });
|
|
2458
|
+
i++;
|
|
2459
|
+
} else if (t === 3) {
|
|
2460
|
+
if (i + 2 < pts.length) {
|
|
2461
|
+
cmds.push({
|
|
2462
|
+
op: "bezierCurveTo",
|
|
2463
|
+
cp1x: tx(pts[i].x, pts[i].y),
|
|
2464
|
+
cp1y: ty(pts[i].x, pts[i].y),
|
|
2465
|
+
cp2x: tx(pts[i + 1].x, pts[i + 1].y),
|
|
2466
|
+
cp2y: ty(pts[i + 1].x, pts[i + 1].y),
|
|
2467
|
+
x: tx(pts[i + 2].x, pts[i + 2].y),
|
|
2468
|
+
y: ty(pts[i + 2].x, pts[i + 2].y)
|
|
2469
|
+
});
|
|
2470
|
+
if ((types[i + 2] & 128) !== 0) {
|
|
2471
|
+
cmds.push({ op: "closePath" });
|
|
2472
|
+
}
|
|
2473
|
+
i += 3;
|
|
2474
|
+
continue;
|
|
2475
|
+
}
|
|
2476
|
+
break;
|
|
2477
|
+
} else {
|
|
2478
|
+
cmds.push({ op: "lineTo", x: tx(pts[i].x, pts[i].y), y: ty(pts[i].x, pts[i].y) });
|
|
2479
|
+
i++;
|
|
2480
|
+
}
|
|
2481
|
+
if (close) {
|
|
2482
|
+
cmds.push({ op: "closePath" });
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
return cmds;
|
|
2486
|
+
}
|
|
2077
2487
|
function replayEmfPlusPath(ctx, path) {
|
|
2078
2488
|
ctx.beginPath();
|
|
2079
2489
|
const pts = path.points;
|
|
@@ -2127,12 +2537,44 @@ function multiplyMatrix(m1, m2) {
|
|
|
2127
2537
|
m1[4] * m2[1] + m1[5] * m2[3] + m2[5]
|
|
2128
2538
|
];
|
|
2129
2539
|
}
|
|
2130
|
-
function
|
|
2540
|
+
function createBrushGradient(rCtx, grad) {
|
|
2541
|
+
const ctx = rCtx.ctx;
|
|
2542
|
+
try {
|
|
2543
|
+
let g = null;
|
|
2544
|
+
if (grad.type === "linear" && typeof ctx.createLinearGradient === "function") {
|
|
2545
|
+
if (grad.x1 === grad.x2 && grad.y1 === grad.y2) {
|
|
2546
|
+
return null;
|
|
2547
|
+
}
|
|
2548
|
+
g = ctx.createLinearGradient(grad.x1, grad.y1, grad.x2, grad.y2);
|
|
2549
|
+
} else if (grad.type === "radial" && typeof ctx.createRadialGradient === "function") {
|
|
2550
|
+
if (!(grad.r > 0)) {
|
|
2551
|
+
return null;
|
|
2552
|
+
}
|
|
2553
|
+
g = ctx.createRadialGradient(grad.cx, grad.cy, 0, grad.cx, grad.cy, grad.r);
|
|
2554
|
+
}
|
|
2555
|
+
if (!g) {
|
|
2556
|
+
return null;
|
|
2557
|
+
}
|
|
2558
|
+
for (const stop of grad.stops) {
|
|
2559
|
+
g.addColorStop(stop.offset, stop.color);
|
|
2560
|
+
}
|
|
2561
|
+
return g;
|
|
2562
|
+
} catch {
|
|
2563
|
+
return null;
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
function resolveBrushPaint(rCtx, flags, brushIdOrColor) {
|
|
2131
2567
|
if (flags & 32768) {
|
|
2132
2568
|
return argbToRgba(brushIdOrColor);
|
|
2133
2569
|
}
|
|
2134
2570
|
const obj = rCtx.objectTable.get(brushIdOrColor & 255);
|
|
2135
2571
|
if (obj && obj.kind === "plus-brush") {
|
|
2572
|
+
if (obj.gradient) {
|
|
2573
|
+
const g = createBrushGradient(rCtx, obj.gradient);
|
|
2574
|
+
if (g) {
|
|
2575
|
+
return g;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2136
2578
|
return obj.color;
|
|
2137
2579
|
}
|
|
2138
2580
|
return "rgba(0,0,0,1)";
|
|
@@ -2196,88 +2638,83 @@ function popState(rCtx, stackId) {
|
|
|
2196
2638
|
rCtx.saveIdMap = newMap;
|
|
2197
2639
|
}
|
|
2198
2640
|
}
|
|
2199
|
-
function
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
}
|
|
2641
|
+
function plusDeviceMatrix(rCtx) {
|
|
2642
|
+
const wt = rCtx.worldTransform;
|
|
2643
|
+
const s = getPageUnitMultiplier(rCtx.pageUnit, rCtx.pageScale) * rCtx.dpiScale;
|
|
2644
|
+
return [wt[0] * s, wt[1] * s, wt[2] * s, wt[3] * s, wt[4] * s, wt[5] * s];
|
|
2204
2645
|
}
|
|
2205
|
-
function
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2646
|
+
function transformedRectShape(x, y, w, h, m) {
|
|
2647
|
+
const tx = (px, py) => m[0] * px + m[2] * py + m[4];
|
|
2648
|
+
const ty = (px, py) => m[1] * px + m[3] * py + m[5];
|
|
2649
|
+
const cmds = [
|
|
2650
|
+
{ op: "moveTo", x: tx(x, y), y: ty(x, y) },
|
|
2651
|
+
{ op: "lineTo", x: tx(x + w, y), y: ty(x + w, y) },
|
|
2652
|
+
{ op: "lineTo", x: tx(x + w, y + h), y: ty(x + w, y + h) },
|
|
2653
|
+
{ op: "lineTo", x: tx(x, y + h), y: ty(x, y + h) },
|
|
2654
|
+
{ op: "closePath" }
|
|
2655
|
+
];
|
|
2656
|
+
return { cmds, fillRule: "nonzero", simple: true };
|
|
2210
2657
|
}
|
|
2211
|
-
function
|
|
2212
|
-
|
|
2213
|
-
case 0:
|
|
2214
|
-
resetClipState(rCtx);
|
|
2215
|
-
ensureClipSave(rCtx);
|
|
2216
|
-
clipFn();
|
|
2217
|
-
return true;
|
|
2218
|
-
case 1:
|
|
2219
|
-
ensureClipSave(rCtx);
|
|
2220
|
-
clipFn();
|
|
2221
|
-
return true;
|
|
2222
|
-
case 2:
|
|
2223
|
-
ensureClipSave(rCtx);
|
|
2224
|
-
clipFn();
|
|
2225
|
-
return true;
|
|
2226
|
-
case 3:
|
|
2227
|
-
return false;
|
|
2228
|
-
case 4:
|
|
2229
|
-
return false;
|
|
2230
|
-
case 5:
|
|
2231
|
-
return false;
|
|
2232
|
-
default:
|
|
2233
|
-
ensureClipSave(rCtx);
|
|
2234
|
-
clipFn();
|
|
2235
|
-
return true;
|
|
2236
|
-
}
|
|
2658
|
+
function pathClipShape(path, m) {
|
|
2659
|
+
return { cmds: emfPlusPathToClipCmds(path.path, m), fillRule: "nonzero", simple: true };
|
|
2237
2660
|
}
|
|
2238
|
-
var
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2661
|
+
var REGION_NODE_OPS = {
|
|
2662
|
+
0: "intersect",
|
|
2663
|
+
// legacy/lenient: treat 0 as And
|
|
2664
|
+
1: "intersect",
|
|
2665
|
+
// RegionNodeDataTypeAnd
|
|
2666
|
+
2: "union",
|
|
2667
|
+
// RegionNodeDataTypeOr
|
|
2668
|
+
3: "xor",
|
|
2669
|
+
// RegionNodeDataTypeXor
|
|
2670
|
+
4: "exclude",
|
|
2671
|
+
// RegionNodeDataTypeExclude
|
|
2672
|
+
5: "complement"
|
|
2673
|
+
// RegionNodeDataTypeComplement
|
|
2674
|
+
};
|
|
2675
|
+
var MAX_REGION_FLATTEN_DEPTH = 64;
|
|
2676
|
+
function flattenRegionNode(node, m, depth = 0) {
|
|
2677
|
+
if (depth > MAX_REGION_FLATTEN_DEPTH) {
|
|
2678
|
+
return { region: [emptyClipShape()], exact: false };
|
|
2243
2679
|
}
|
|
2244
2680
|
switch (node.type) {
|
|
2245
2681
|
case "rect":
|
|
2246
|
-
|
|
2247
|
-
break;
|
|
2682
|
+
return { region: [transformedRectShape(node.x, node.y, node.width, node.height, m)], exact: true };
|
|
2248
2683
|
case "path":
|
|
2249
|
-
|
|
2250
|
-
break;
|
|
2684
|
+
return { region: [pathClipShape(node, m)], exact: true };
|
|
2251
2685
|
case "infinite":
|
|
2252
|
-
|
|
2253
|
-
break;
|
|
2686
|
+
return { region: null, exact: true };
|
|
2254
2687
|
case "empty":
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
} else {
|
|
2264
|
-
try {
|
|
2265
|
-
ctx.clip();
|
|
2266
|
-
} catch {
|
|
2267
|
-
}
|
|
2268
|
-
ctx.beginPath();
|
|
2269
|
-
traceRegionNodePath(ctx, node.right, depth + 1);
|
|
2270
|
-
}
|
|
2271
|
-
break;
|
|
2688
|
+
return { region: [emptyClipShape()], exact: true };
|
|
2689
|
+
case "combine": {
|
|
2690
|
+
const left = flattenRegionNode(node.left, m, depth + 1);
|
|
2691
|
+
const right = flattenRegionNode(node.right, m, depth + 1);
|
|
2692
|
+
const op = REGION_NODE_OPS[node.combineMode] ?? "intersect";
|
|
2693
|
+
const combined = combineClipRegions(left.region, right.region, op);
|
|
2694
|
+
return { region: combined.region, exact: combined.exact && left.exact && right.exact };
|
|
2695
|
+
}
|
|
2272
2696
|
}
|
|
2273
2697
|
}
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2698
|
+
var PLUS_COMBINE_OPS = {
|
|
2699
|
+
0: "replace",
|
|
2700
|
+
1: "intersect",
|
|
2701
|
+
2: "union",
|
|
2702
|
+
3: "xor",
|
|
2703
|
+
4: "exclude",
|
|
2704
|
+
5: "complement"
|
|
2705
|
+
};
|
|
2706
|
+
function reapplyPlusClip(rCtx) {
|
|
2707
|
+
reapplyClipRegion(rCtx, rCtx.clipRegion ?? null, true);
|
|
2708
|
+
}
|
|
2709
|
+
function applyPlusClipRegion(rCtx, incoming, combineMode, opName) {
|
|
2710
|
+
const op = PLUS_COMBINE_OPS[combineMode];
|
|
2711
|
+
const res = combineClipRegions(rCtx.clipRegion ?? null, incoming, op ?? "intersect");
|
|
2712
|
+
if (!res.exact) ;
|
|
2713
|
+
rCtx.clipRegion = res.region;
|
|
2714
|
+
reapplyPlusClip(rCtx);
|
|
2715
|
+
}
|
|
2716
|
+
function applyPlusClipShape(rCtx, shape, combineMode, opName) {
|
|
2717
|
+
applyPlusClipRegion(rCtx, [shape], combineMode);
|
|
2281
2718
|
}
|
|
2282
2719
|
function handleEmfPlusStateRecord(rCtx, recType, recFlags, dataOff, recDataSize) {
|
|
2283
2720
|
const { view } = rCtx;
|
|
@@ -2379,30 +2816,24 @@ function handleEmfPlusStateRecord(rCtx, recType, recFlags, dataOff, recDataSize)
|
|
|
2379
2816
|
const cy = view.getFloat32(dataOff + 4, true);
|
|
2380
2817
|
const cw = view.getFloat32(dataOff + 8, true);
|
|
2381
2818
|
const ch = view.getFloat32(dataOff + 12, true);
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
rCtx.ctx.beginPath();
|
|
2385
|
-
rCtx.ctx.rect(cx, cy, cw, ch);
|
|
2386
|
-
try {
|
|
2387
|
-
rCtx.ctx.clip();
|
|
2388
|
-
} catch {
|
|
2389
|
-
}
|
|
2390
|
-
});
|
|
2819
|
+
const shape = transformedRectShape(cx, cy, cw, ch, plusDeviceMatrix(rCtx));
|
|
2820
|
+
applyPlusClipShape(rCtx, shape, combineMode);
|
|
2391
2821
|
}
|
|
2392
2822
|
return true;
|
|
2393
2823
|
}
|
|
2394
2824
|
case EMFPLUS_RESETCLIP: {
|
|
2395
|
-
|
|
2396
|
-
|
|
2825
|
+
rCtx.clipRegion = null;
|
|
2826
|
+
reapplyPlusClip(rCtx);
|
|
2397
2827
|
return true;
|
|
2398
2828
|
}
|
|
2399
2829
|
case EMFPLUS_SETCLIPREGION: {
|
|
2400
2830
|
const regionId = recFlags & 255;
|
|
2831
|
+
const combineMode = recFlags >> 8 & 15;
|
|
2401
2832
|
const regionObj = rCtx.objectTable.get(regionId);
|
|
2402
2833
|
if (regionObj && regionObj.kind === "plus-region" && regionObj.nodes.length > 0) {
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2834
|
+
const flattened = flattenRegionNode(regionObj.nodes[0], plusDeviceMatrix(rCtx));
|
|
2835
|
+
if (!flattened.exact) ;
|
|
2836
|
+
applyPlusClipRegion(rCtx, flattened.region, combineMode);
|
|
2406
2837
|
}
|
|
2407
2838
|
return true;
|
|
2408
2839
|
}
|
|
@@ -2411,22 +2842,26 @@ function handleEmfPlusStateRecord(rCtx, recType, recFlags, dataOff, recDataSize)
|
|
|
2411
2842
|
const combineMode = recFlags >> 8 & 15;
|
|
2412
2843
|
const pathObj = rCtx.objectTable.get(pathId);
|
|
2413
2844
|
if (pathObj && pathObj.kind === "plus-path") {
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
} catch {
|
|
2421
|
-
}
|
|
2422
|
-
});
|
|
2845
|
+
const shape = {
|
|
2846
|
+
cmds: emfPlusPathToClipCmds(pathObj, plusDeviceMatrix(rCtx)),
|
|
2847
|
+
fillRule: "nonzero",
|
|
2848
|
+
simple: true
|
|
2849
|
+
};
|
|
2850
|
+
applyPlusClipShape(rCtx, shape, combineMode);
|
|
2423
2851
|
}
|
|
2424
2852
|
return true;
|
|
2425
2853
|
}
|
|
2426
2854
|
case EMFPLUS_OFFSETCLIP: {
|
|
2427
2855
|
if (recDataSize >= 8) {
|
|
2428
|
-
view.getFloat32(dataOff, true);
|
|
2429
|
-
view.getFloat32(dataOff + 4, true);
|
|
2856
|
+
const dx = view.getFloat32(dataOff, true);
|
|
2857
|
+
const dy = view.getFloat32(dataOff + 4, true);
|
|
2858
|
+
if (rCtx.clipRegion) {
|
|
2859
|
+
const m = plusDeviceMatrix(rCtx);
|
|
2860
|
+
const ddx = m[0] * dx + m[2] * dy;
|
|
2861
|
+
const ddy = m[1] * dx + m[3] * dy;
|
|
2862
|
+
rCtx.clipRegion = translateClipRegion(rCtx.clipRegion, ddx, ddy);
|
|
2863
|
+
reapplyPlusClip(rCtx);
|
|
2864
|
+
}
|
|
2430
2865
|
}
|
|
2431
2866
|
return true;
|
|
2432
2867
|
}
|
|
@@ -2499,7 +2934,7 @@ function handleEmfPlusDrawRecord(rCtx, recType, recFlags, dataOff, recDataSize)
|
|
|
2499
2934
|
const count = view.getUint32(dataOff + 4, true);
|
|
2500
2935
|
const compressed = (recFlags & 16384) !== 0;
|
|
2501
2936
|
const rectSize = compressed ? 8 : 16;
|
|
2502
|
-
ctx.fillStyle =
|
|
2937
|
+
ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
|
|
2503
2938
|
applyPlusWorldTransform(rCtx);
|
|
2504
2939
|
let rOff = dataOff + 8;
|
|
2505
2940
|
for (let i = 0; i < count && rOff + rectSize <= dataOff + recDataSize; i++) {
|
|
@@ -2549,7 +2984,7 @@ function handleEmfPlusDrawRecord(rCtx, recType, recFlags, dataOff, recDataSize)
|
|
|
2549
2984
|
w = view.getFloat32(dataOff + 12, true);
|
|
2550
2985
|
h = view.getFloat32(dataOff + 16, true);
|
|
2551
2986
|
}
|
|
2552
|
-
ctx.fillStyle =
|
|
2987
|
+
ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
|
|
2553
2988
|
applyPlusWorldTransform(rCtx);
|
|
2554
2989
|
ctx.beginPath();
|
|
2555
2990
|
ctx.ellipse(x + w / 2, y + h / 2, Math.abs(w) / 2, Math.abs(h) / 2, 0, 0, Math.PI * 2);
|
|
@@ -2595,7 +3030,7 @@ function handleEmfPlusDrawRecord(rCtx, recType, recFlags, dataOff, recDataSize)
|
|
|
2595
3030
|
let aOff = dataOff;
|
|
2596
3031
|
if (isFill) {
|
|
2597
3032
|
const brushVal = view.getUint32(aOff, true);
|
|
2598
|
-
ctx.fillStyle =
|
|
3033
|
+
ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
|
|
2599
3034
|
aOff += 4;
|
|
2600
3035
|
}
|
|
2601
3036
|
const startAngle = view.getFloat32(aOff, true) * Math.PI / 180;
|
|
@@ -2676,7 +3111,7 @@ function handleEmfPlusDrawRecord(rCtx, recType, recFlags, dataOff, recDataSize)
|
|
|
2676
3111
|
const count = view.getUint32(dataOff + 4, true);
|
|
2677
3112
|
const compressed = (recFlags & 16384) !== 0;
|
|
2678
3113
|
const ptSize = compressed ? 4 : 8;
|
|
2679
|
-
ctx.fillStyle =
|
|
3114
|
+
ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
|
|
2680
3115
|
applyPlusWorldTransform(rCtx);
|
|
2681
3116
|
ctx.beginPath();
|
|
2682
3117
|
let pOff = dataOff + 8;
|
|
@@ -2699,6 +3134,261 @@ function handleEmfPlusDrawRecord(rCtx, recType, recFlags, dataOff, recDataSize)
|
|
|
2699
3134
|
}
|
|
2700
3135
|
}
|
|
2701
3136
|
|
|
3137
|
+
// src/emf-plus-brush-parser.ts
|
|
3138
|
+
var BRUSH_DATA_PATH = 1;
|
|
3139
|
+
var BRUSH_DATA_TRANSFORM = 2;
|
|
3140
|
+
var BRUSH_DATA_PRESET_COLORS = 4;
|
|
3141
|
+
var BRUSH_DATA_BLEND_FACTORS_H = 8;
|
|
3142
|
+
var MAX_GRADIENT_ELEMENTS = 4096;
|
|
3143
|
+
function looksLikeGraphicsVersion(v) {
|
|
3144
|
+
return v >>> 12 === 900097;
|
|
3145
|
+
}
|
|
3146
|
+
function readTransform(view, off) {
|
|
3147
|
+
return [
|
|
3148
|
+
view.getFloat32(off, true),
|
|
3149
|
+
view.getFloat32(off + 4, true),
|
|
3150
|
+
view.getFloat32(off + 8, true),
|
|
3151
|
+
view.getFloat32(off + 12, true),
|
|
3152
|
+
view.getFloat32(off + 16, true),
|
|
3153
|
+
view.getFloat32(off + 20, true)
|
|
3154
|
+
];
|
|
3155
|
+
}
|
|
3156
|
+
function applyMatrix(m, x, y) {
|
|
3157
|
+
return { x: m[0] * x + m[2] * y + m[4], y: m[1] * x + m[3] * y + m[5] };
|
|
3158
|
+
}
|
|
3159
|
+
function clamp01(v) {
|
|
3160
|
+
return Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : 0;
|
|
3161
|
+
}
|
|
3162
|
+
function normaliseStops(stops) {
|
|
3163
|
+
return stops.map((s) => ({ offset: clamp01(s.offset), color: s.color })).sort((a, b) => a.offset - b.offset);
|
|
3164
|
+
}
|
|
3165
|
+
function readPresetColors(view, off, end) {
|
|
3166
|
+
if (off + 4 > end) {
|
|
3167
|
+
return null;
|
|
3168
|
+
}
|
|
3169
|
+
const count = view.getUint32(off, true);
|
|
3170
|
+
if (count === 0 || count > MAX_GRADIENT_ELEMENTS) {
|
|
3171
|
+
return null;
|
|
3172
|
+
}
|
|
3173
|
+
const posOff = off + 4;
|
|
3174
|
+
const colOff = posOff + count * 4;
|
|
3175
|
+
const next = colOff + count * 4;
|
|
3176
|
+
if (next > end) {
|
|
3177
|
+
return null;
|
|
3178
|
+
}
|
|
3179
|
+
const stops = [];
|
|
3180
|
+
for (let i = 0; i < count; i++) {
|
|
3181
|
+
stops.push({
|
|
3182
|
+
offset: view.getFloat32(posOff + i * 4, true),
|
|
3183
|
+
color: argbToRgba(view.getUint32(colOff + i * 4, true))
|
|
3184
|
+
});
|
|
3185
|
+
}
|
|
3186
|
+
return { stops, next };
|
|
3187
|
+
}
|
|
3188
|
+
function readBlendFactors(view, off, end) {
|
|
3189
|
+
if (off + 4 > end) {
|
|
3190
|
+
return null;
|
|
3191
|
+
}
|
|
3192
|
+
const count = view.getUint32(off, true);
|
|
3193
|
+
if (count === 0 || count > MAX_GRADIENT_ELEMENTS) {
|
|
3194
|
+
return null;
|
|
3195
|
+
}
|
|
3196
|
+
const posOff = off + 4;
|
|
3197
|
+
const facOff = posOff + count * 4;
|
|
3198
|
+
const next = facOff + count * 4;
|
|
3199
|
+
if (next > end) {
|
|
3200
|
+
return null;
|
|
3201
|
+
}
|
|
3202
|
+
const entries = [];
|
|
3203
|
+
for (let i = 0; i < count; i++) {
|
|
3204
|
+
entries.push({
|
|
3205
|
+
pos: view.getFloat32(posOff + i * 4, true),
|
|
3206
|
+
factor: view.getFloat32(facOff + i * 4, true)
|
|
3207
|
+
});
|
|
3208
|
+
}
|
|
3209
|
+
return { entries, next };
|
|
3210
|
+
}
|
|
3211
|
+
function parseLinearGradient(view, b, end) {
|
|
3212
|
+
if (b + 40 > end) {
|
|
3213
|
+
return null;
|
|
3214
|
+
}
|
|
3215
|
+
const flags = view.getUint32(b, true);
|
|
3216
|
+
const rx = view.getFloat32(b + 8, true);
|
|
3217
|
+
const ry = view.getFloat32(b + 12, true);
|
|
3218
|
+
const rw = view.getFloat32(b + 16, true);
|
|
3219
|
+
const rh = view.getFloat32(b + 20, true);
|
|
3220
|
+
const startArgb = view.getUint32(b + 24, true);
|
|
3221
|
+
const endArgb = view.getUint32(b + 28, true);
|
|
3222
|
+
let o = b + 40;
|
|
3223
|
+
let transform = null;
|
|
3224
|
+
if (flags & BRUSH_DATA_TRANSFORM && o + 24 <= end) {
|
|
3225
|
+
transform = readTransform(view, o);
|
|
3226
|
+
o += 24;
|
|
3227
|
+
}
|
|
3228
|
+
let stops = [
|
|
3229
|
+
{ offset: 0, color: argbToRgba(startArgb) },
|
|
3230
|
+
{ offset: 1, color: argbToRgba(endArgb) }
|
|
3231
|
+
];
|
|
3232
|
+
if (flags & BRUSH_DATA_PRESET_COLORS) {
|
|
3233
|
+
const preset = readPresetColors(view, o, end);
|
|
3234
|
+
if (preset) {
|
|
3235
|
+
stops = preset.stops;
|
|
3236
|
+
}
|
|
3237
|
+
} else if (flags & BRUSH_DATA_BLEND_FACTORS_H) {
|
|
3238
|
+
const blend = readBlendFactors(view, o, end);
|
|
3239
|
+
if (blend) {
|
|
3240
|
+
stops = blend.entries.map((e) => ({
|
|
3241
|
+
offset: e.pos,
|
|
3242
|
+
color: lerpArgbToRgba(startArgb, endArgb, e.factor)
|
|
3243
|
+
}));
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
let p1 = { x: rx, y: ry + rh / 2 };
|
|
3247
|
+
let p2 = { x: rx + rw, y: ry + rh / 2 };
|
|
3248
|
+
if (transform) {
|
|
3249
|
+
p1 = applyMatrix(transform, p1.x, p1.y);
|
|
3250
|
+
p2 = applyMatrix(transform, p2.x, p2.y);
|
|
3251
|
+
}
|
|
3252
|
+
emfLog(
|
|
3253
|
+
`parseEmfPlusBrushObject: linear gradient (${p1.x.toFixed(1)},${p1.y.toFixed(1)})\u2192(${p2.x.toFixed(1)},${p2.y.toFixed(1)}), ${stops.length} stop(s)`
|
|
3254
|
+
);
|
|
3255
|
+
return {
|
|
3256
|
+
kind: "plus-brush",
|
|
3257
|
+
color: argbToRgba(startArgb),
|
|
3258
|
+
gradient: {
|
|
3259
|
+
type: "linear",
|
|
3260
|
+
x1: p1.x,
|
|
3261
|
+
y1: p1.y,
|
|
3262
|
+
x2: p2.x,
|
|
3263
|
+
y2: p2.y,
|
|
3264
|
+
stops: normaliseStops(stops)
|
|
3265
|
+
}
|
|
3266
|
+
};
|
|
3267
|
+
}
|
|
3268
|
+
function parsePathGradient(view, b, end) {
|
|
3269
|
+
if (b + 24 > end) {
|
|
3270
|
+
return null;
|
|
3271
|
+
}
|
|
3272
|
+
const flags = view.getUint32(b, true);
|
|
3273
|
+
const centerArgb = view.getUint32(b + 8, true);
|
|
3274
|
+
let cx = view.getFloat32(b + 12, true);
|
|
3275
|
+
let cy = view.getFloat32(b + 16, true);
|
|
3276
|
+
const surroundCount = view.getUint32(b + 20, true);
|
|
3277
|
+
if (surroundCount > MAX_GRADIENT_ELEMENTS) {
|
|
3278
|
+
return { kind: "plus-brush", color: argbToRgba(centerArgb) };
|
|
3279
|
+
}
|
|
3280
|
+
const surround = [];
|
|
3281
|
+
let o = b + 24;
|
|
3282
|
+
for (let i = 0; i < surroundCount && o + 4 <= end; i++) {
|
|
3283
|
+
surround.push(view.getUint32(o, true));
|
|
3284
|
+
o += 4;
|
|
3285
|
+
}
|
|
3286
|
+
let boundaryPts = [];
|
|
3287
|
+
if (flags & BRUSH_DATA_PATH) {
|
|
3288
|
+
if (o + 4 <= end) {
|
|
3289
|
+
const pathSize = view.getInt32(o, true);
|
|
3290
|
+
o += 4;
|
|
3291
|
+
if (pathSize > 0 && o + pathSize <= end) {
|
|
3292
|
+
const path = parseEmfPlusPath(view, o, pathSize);
|
|
3293
|
+
if (path) {
|
|
3294
|
+
boundaryPts = path.points;
|
|
3295
|
+
}
|
|
3296
|
+
o += pathSize;
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
} else if (o + 4 <= end) {
|
|
3300
|
+
const ptCount = view.getUint32(o, true);
|
|
3301
|
+
o += 4;
|
|
3302
|
+
if (ptCount > 0 && ptCount <= MAX_GRADIENT_ELEMENTS && o + ptCount * 8 <= end) {
|
|
3303
|
+
for (let i = 0; i < ptCount; i++) {
|
|
3304
|
+
boundaryPts.push({
|
|
3305
|
+
x: view.getFloat32(o + i * 8, true),
|
|
3306
|
+
y: view.getFloat32(o + i * 8 + 4, true)
|
|
3307
|
+
});
|
|
3308
|
+
}
|
|
3309
|
+
o += ptCount * 8;
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
let transform = null;
|
|
3313
|
+
if (flags & BRUSH_DATA_TRANSFORM && o + 24 <= end) {
|
|
3314
|
+
transform = readTransform(view, o);
|
|
3315
|
+
o += 24;
|
|
3316
|
+
}
|
|
3317
|
+
if (transform) {
|
|
3318
|
+
({ x: cx, y: cy } = applyMatrix(transform, cx, cy));
|
|
3319
|
+
boundaryPts = boundaryPts.map((p) => applyMatrix(transform, p.x, p.y));
|
|
3320
|
+
}
|
|
3321
|
+
const surroundArgb = surround.length > 0 ? surround[0] : centerArgb;
|
|
3322
|
+
let stops = [
|
|
3323
|
+
{ offset: 0, color: argbToRgba(centerArgb) },
|
|
3324
|
+
{ offset: 1, color: argbToRgba(surroundArgb) }
|
|
3325
|
+
];
|
|
3326
|
+
if (flags & BRUSH_DATA_PRESET_COLORS) {
|
|
3327
|
+
const preset = readPresetColors(view, o, end);
|
|
3328
|
+
if (preset) {
|
|
3329
|
+
stops = preset.stops.map((s) => ({ offset: 1 - s.offset, color: s.color }));
|
|
3330
|
+
}
|
|
3331
|
+
} else if (flags & BRUSH_DATA_BLEND_FACTORS_H) {
|
|
3332
|
+
const blend = readBlendFactors(view, o, end);
|
|
3333
|
+
if (blend) {
|
|
3334
|
+
stops = blend.entries.map((e) => ({
|
|
3335
|
+
offset: 1 - e.pos,
|
|
3336
|
+
color: lerpArgbToRgba(surroundArgb, centerArgb, e.factor)
|
|
3337
|
+
}));
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
let r = 0;
|
|
3341
|
+
for (const p of boundaryPts) {
|
|
3342
|
+
const d = Math.hypot(p.x - cx, p.y - cy);
|
|
3343
|
+
if (d > r) {
|
|
3344
|
+
r = d;
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
if (!(r > 0)) {
|
|
3348
|
+
return { kind: "plus-brush", color: argbToRgba(centerArgb) };
|
|
3349
|
+
}
|
|
3350
|
+
emfLog(
|
|
3351
|
+
`parseEmfPlusBrushObject: path gradient centre=(${cx.toFixed(1)},${cy.toFixed(1)}), r=${r.toFixed(1)}, ${stops.length} stop(s)`
|
|
3352
|
+
);
|
|
3353
|
+
return {
|
|
3354
|
+
kind: "plus-brush",
|
|
3355
|
+
color: argbToRgba(centerArgb),
|
|
3356
|
+
gradient: { type: "radial", cx, cy, r, stops: normaliseStops(stops) }
|
|
3357
|
+
};
|
|
3358
|
+
}
|
|
3359
|
+
function parseEmfPlusBrushObject(view, dataOff, recDataSize) {
|
|
3360
|
+
if (recDataSize < 8) {
|
|
3361
|
+
return null;
|
|
3362
|
+
}
|
|
3363
|
+
const end = dataOff + recDataSize;
|
|
3364
|
+
const hasVersion = looksLikeGraphicsVersion(view.getUint32(dataOff, true));
|
|
3365
|
+
const typeOff = dataOff + (hasVersion ? 4 : 0);
|
|
3366
|
+
if (typeOff + 8 > end) {
|
|
3367
|
+
return null;
|
|
3368
|
+
}
|
|
3369
|
+
const brushType = view.getUint32(typeOff, true);
|
|
3370
|
+
const b = typeOff + 4;
|
|
3371
|
+
switch (brushType) {
|
|
3372
|
+
case EMFPLUS_BRUSHTYPE_SOLID:
|
|
3373
|
+
return { kind: "plus-brush", color: argbToRgba(view.getUint32(b, true)) };
|
|
3374
|
+
case EMFPLUS_BRUSHTYPE_HATCHFILL:
|
|
3375
|
+
if (b + 8 <= end) {
|
|
3376
|
+
return { kind: "plus-brush", color: argbToRgba(view.getUint32(b + 4, true)) };
|
|
3377
|
+
}
|
|
3378
|
+
return { kind: "plus-brush", color: "rgba(0,0,0,1)" };
|
|
3379
|
+
case EMFPLUS_BRUSHTYPE_LINEARGRADIENT: {
|
|
3380
|
+
const brush = parseLinearGradient(view, b, end);
|
|
3381
|
+
return brush ?? { kind: "plus-brush", color: "rgba(0,0,0,1)" };
|
|
3382
|
+
}
|
|
3383
|
+
case EMFPLUS_BRUSHTYPE_PATHGRADIENT: {
|
|
3384
|
+
const brush = parsePathGradient(view, b, end);
|
|
3385
|
+
return brush ?? { kind: "plus-brush", color: "rgba(0,0,0,1)" };
|
|
3386
|
+
}
|
|
3387
|
+
default:
|
|
3388
|
+
return { kind: "plus-brush", color: "rgba(0,0,0,1)" };
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
|
|
2702
3392
|
// src/emf-plus-bitmap-decoder.ts
|
|
2703
3393
|
var PIXELFORMAT_24BPP_RGB = 137224;
|
|
2704
3394
|
var PIXELFORMAT_32BPP_RGB = 139273;
|
|
@@ -2808,7 +3498,8 @@ function parseEmfPlusPenObject(view, dataOff, recDataSize) {
|
|
|
2808
3498
|
if (recDataSize < 20) {
|
|
2809
3499
|
return null;
|
|
2810
3500
|
}
|
|
2811
|
-
const
|
|
3501
|
+
const hasVersion = looksLikeGraphicsVersion(view.getUint32(dataOff, true));
|
|
3502
|
+
const penFlags = view.getUint32(dataOff + (hasVersion ? 8 : 4), true);
|
|
2812
3503
|
const penWidth = view.getFloat32(dataOff + 16, true);
|
|
2813
3504
|
let brushOff = dataOff + 20;
|
|
2814
3505
|
const flagSizes = [
|
|
@@ -2868,9 +3559,9 @@ function parseEmfPlusPenObject(view, dataOff, recDataSize) {
|
|
|
2868
3559
|
}
|
|
2869
3560
|
let penColor = "rgba(0,0,0,1)";
|
|
2870
3561
|
if (brushOff + 8 <= dataOff + recDataSize) {
|
|
2871
|
-
const
|
|
2872
|
-
if (
|
|
2873
|
-
penColor =
|
|
3562
|
+
const brush = parseEmfPlusBrushObject(view, brushOff, dataOff + recDataSize - brushOff);
|
|
3563
|
+
if (brush) {
|
|
3564
|
+
penColor = brush.color;
|
|
2874
3565
|
}
|
|
2875
3566
|
}
|
|
2876
3567
|
return { kind: "plus-pen", color: penColor, width: penWidth || 1, dashStyle };
|
|
@@ -2966,19 +3657,9 @@ function handleEmfPlusObjectRecord(rCtx, recFlags, dataOff, recDataSize) {
|
|
|
2966
3657
|
// Brush
|
|
2967
3658
|
// ---------------------------------------------------------------
|
|
2968
3659
|
case EMFPLUS_OBJECTTYPE_BRUSH: {
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
if (brushType === EMFPLUS_BRUSHTYPE_SOLID && recDataSize >= 8) {
|
|
2973
|
-
color = argbToRgba(view.getUint32(dataOff + 4, true));
|
|
2974
|
-
} else if (brushType === EMFPLUS_BRUSHTYPE_LINEARGRADIENT && recDataSize >= 48) {
|
|
2975
|
-
color = argbToRgba(view.getUint32(dataOff + 40, true));
|
|
2976
|
-
} else if (brushType === EMFPLUS_BRUSHTYPE_PATHGRADIENT && recDataSize >= 12) {
|
|
2977
|
-
color = argbToRgba(view.getUint32(dataOff + 8, true));
|
|
2978
|
-
} else if (brushType === EMFPLUS_BRUSHTYPE_HATCHFILL && recDataSize >= 12) {
|
|
2979
|
-
color = argbToRgba(view.getUint32(dataOff + 8, true));
|
|
2980
|
-
}
|
|
2981
|
-
objectTable.set(objectId, { kind: "plus-brush", color });
|
|
3660
|
+
const brush = parseEmfPlusBrushObject(view, dataOff, recDataSize);
|
|
3661
|
+
if (brush) {
|
|
3662
|
+
objectTable.set(objectId, brush);
|
|
2982
3663
|
}
|
|
2983
3664
|
break;
|
|
2984
3665
|
}
|
|
@@ -3074,7 +3755,7 @@ function parseRegionNode(view, off, endOff, depth = 0) {
|
|
|
3074
3755
|
}
|
|
3075
3756
|
const nodeType = view.getUint32(off, true);
|
|
3076
3757
|
let cursor = off + 4;
|
|
3077
|
-
if (nodeType <=
|
|
3758
|
+
if (nodeType <= 5) {
|
|
3078
3759
|
const leftResult = parseRegionNode(view, cursor, endOff, depth + 1);
|
|
3079
3760
|
if (!leftResult) {
|
|
3080
3761
|
return null;
|
|
@@ -3163,7 +3844,7 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
|
|
|
3163
3844
|
const pathId = recFlags & 255;
|
|
3164
3845
|
const pathObj = objectTable.get(pathId);
|
|
3165
3846
|
if (pathObj && pathObj.kind === "plus-path") {
|
|
3166
|
-
ctx.fillStyle =
|
|
3847
|
+
ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
|
|
3167
3848
|
applyPlusWorldTransform(rCtx);
|
|
3168
3849
|
replayEmfPlusPath(ctx, pathObj);
|
|
3169
3850
|
ctx.fill();
|
|
@@ -3208,7 +3889,7 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
|
|
|
3208
3889
|
const italic = font.flags & 2 ? "italic " : "";
|
|
3209
3890
|
const family = mapFontFamily(font.family, rCtx.fontFamilyMap);
|
|
3210
3891
|
ctx.font = `${italic}${bold}${font.emSize}px ${family}`;
|
|
3211
|
-
ctx.fillStyle =
|
|
3892
|
+
ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
|
|
3212
3893
|
ctx.textBaseline = "top";
|
|
3213
3894
|
const sf = objectTable.get(formatId);
|
|
3214
3895
|
if (sf && sf.kind === "plus-stringformat") {
|
|
@@ -3248,7 +3929,7 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
|
|
|
3248
3929
|
const italic = font.flags & 2 ? "italic " : "";
|
|
3249
3930
|
const family = mapFontFamily(font.family, rCtx.fontFamilyMap);
|
|
3250
3931
|
ctx.font = `${italic}${bold}${font.emSize}px ${family}`;
|
|
3251
|
-
ctx.fillStyle =
|
|
3932
|
+
ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
|
|
3252
3933
|
ctx.textBaseline = "alphabetic";
|
|
3253
3934
|
ctx.textAlign = "left";
|
|
3254
3935
|
applyPlusWorldTransform(rCtx);
|
|
@@ -3425,7 +4106,8 @@ function replayEmfPlusRecords(view, offset, length, ctx, _canvasW, _canvasH, sta
|
|
|
3425
4106
|
saveIdMap: s.saveIdMap,
|
|
3426
4107
|
totalImageObjects: 0,
|
|
3427
4108
|
totalDrawImageCalls: 0,
|
|
3428
|
-
clipSaveDepth:
|
|
4109
|
+
clipSaveDepth: s.clipSaveDepth,
|
|
4110
|
+
clipRegion: s.clipRegion,
|
|
3429
4111
|
pageUnit: 2,
|
|
3430
4112
|
pageScale: 1,
|
|
3431
4113
|
continuationBuffer: null,
|
|
@@ -3559,6 +4241,8 @@ function replayEmfPlusRecords(view, offset, length, ctx, _canvasW, _canvasH, sta
|
|
|
3559
4241
|
if (state) {
|
|
3560
4242
|
state.worldTransform = rCtx.worldTransform;
|
|
3561
4243
|
state.saveIdMap = rCtx.saveIdMap;
|
|
4244
|
+
state.clipRegion = rCtx.clipRegion ?? null;
|
|
4245
|
+
state.clipSaveDepth = rCtx.clipSaveDepth;
|
|
3562
4246
|
}
|
|
3563
4247
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
3564
4248
|
return rCtx.deferredImages;
|
|
@@ -4178,19 +4862,7 @@ async function processDeferredImages(ctx, deferredImages, recursionDepth = 0) {
|
|
|
4178
4862
|
continue;
|
|
4179
4863
|
}
|
|
4180
4864
|
emfLog(` Deferred image [${idx}]: recursively converting embedded metafile...`);
|
|
4181
|
-
const metafileDataUrl = await convertEmfToDataUrl(
|
|
4182
|
-
plainBuffer,
|
|
4183
|
-
void 0,
|
|
4184
|
-
void 0,
|
|
4185
|
-
void 0,
|
|
4186
|
-
recursionDepth + 1
|
|
4187
|
-
) ?? await convertWmfToDataUrl(
|
|
4188
|
-
plainBuffer,
|
|
4189
|
-
void 0,
|
|
4190
|
-
void 0,
|
|
4191
|
-
void 0,
|
|
4192
|
-
recursionDepth + 1
|
|
4193
|
-
);
|
|
4865
|
+
const metafileDataUrl = await convertEmfToDataUrl(plainBuffer, void 0, recursionDepth + 1) ?? await convertWmfToDataUrl(plainBuffer, void 0, recursionDepth + 1);
|
|
4194
4866
|
if (metafileDataUrl) {
|
|
4195
4867
|
emfLog(
|
|
4196
4868
|
` Deferred image [${idx}]: metafile converted, dataUrl length=${metafileDataUrl.length}`
|
|
@@ -4235,14 +4907,14 @@ async function processDeferredImages(ctx, deferredImages, recursionDepth = 0) {
|
|
|
4235
4907
|
}
|
|
4236
4908
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
4237
4909
|
}
|
|
4238
|
-
async function convertEmfToDataUrl(buffer,
|
|
4910
|
+
async function convertEmfToDataUrl(buffer, options, recursionDepth = 0) {
|
|
4239
4911
|
if (recursionDepth > MAX_METAFILE_RECURSION) {
|
|
4240
4912
|
return null;
|
|
4241
4913
|
}
|
|
4242
|
-
const opts =
|
|
4914
|
+
const opts = options ?? {};
|
|
4243
4915
|
const dpiScale = opts.dpiScale ?? DEFAULT_DPI_SCALE;
|
|
4244
|
-
const effectiveMaxWidth =
|
|
4245
|
-
const effectiveMaxHeight =
|
|
4916
|
+
const effectiveMaxWidth = opts.maxWidth;
|
|
4917
|
+
const effectiveMaxHeight = opts.maxHeight;
|
|
4246
4918
|
const replayOptions = {
|
|
4247
4919
|
maxRecords: opts.maxRecords,
|
|
4248
4920
|
maxRecordsEmfPlus: opts.maxRecords,
|
|
@@ -4320,14 +4992,14 @@ async function convertEmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScal
|
|
|
4320
4992
|
return null;
|
|
4321
4993
|
}
|
|
4322
4994
|
}
|
|
4323
|
-
async function convertWmfToDataUrl(buffer,
|
|
4995
|
+
async function convertWmfToDataUrl(buffer, options, recursionDepth = 0) {
|
|
4324
4996
|
if (recursionDepth > MAX_METAFILE_RECURSION) {
|
|
4325
4997
|
return null;
|
|
4326
4998
|
}
|
|
4327
|
-
const opts =
|
|
4999
|
+
const opts = options ?? {};
|
|
4328
5000
|
const dpiScale = opts.dpiScale ?? DEFAULT_DPI_SCALE;
|
|
4329
|
-
const effectiveMaxWidth =
|
|
4330
|
-
const effectiveMaxHeight =
|
|
5001
|
+
const effectiveMaxWidth = opts.maxWidth;
|
|
5002
|
+
const effectiveMaxHeight = opts.maxHeight;
|
|
4331
5003
|
const replayOptions = {
|
|
4332
5004
|
maxRecords: opts.maxRecords,
|
|
4333
5005
|
fontFamilyMap: opts.fontFamilyMap
|