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