emf-converter 1.4.3 → 1.6.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/dist/index.js CHANGED
@@ -1,6 +1,229 @@
1
1
  'use strict';
2
2
 
3
- // src/emf-logging.ts
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
+
55
+ // src/emf-constants.ts
56
+ var EMR_HEADER = 1;
57
+ var EMR_POLYBEZIER = 2;
58
+ var EMR_POLYGON = 3;
59
+ var EMR_POLYLINE = 4;
60
+ var EMR_POLYBEZIERTO = 5;
61
+ var EMR_POLYLINETO = 6;
62
+ var EMR_POLYPOLYLINE = 7;
63
+ var EMR_POLYPOLYGON = 8;
64
+ var EMR_SETWINDOWEXTEX = 9;
65
+ var EMR_SETWINDOWORGEX = 10;
66
+ var EMR_SETVIEWPORTEXTEX = 11;
67
+ var EMR_SETVIEWPORTORGEX = 12;
68
+ var EMR_SETBRUSHORGEX = 13;
69
+ var EMR_EOF = 14;
70
+ var EMR_SETPIXELV = 15;
71
+ var EMR_SETMAPMODE = 17;
72
+ var EMR_SETBKMODE = 18;
73
+ var EMR_SETPOLYFILLMODE = 19;
74
+ var EMR_SETROP2 = 20;
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;
81
+ var R2_NOT = 6;
82
+ var R2_XORPEN = 7;
83
+ var R2_NOTMASKPEN = 8;
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;
89
+ var R2_MERGEPEN = 15;
90
+ var R2_WHITE = 16;
91
+ var MAX_CANVAS_DIMENSION = 8192;
92
+ var MAX_RECORDS_DEFAULT = 2e5;
93
+ var MAX_RECORDS_EMFPLUS_DEFAULT = 5e5;
94
+ var EMR_SETTEXTALIGN = 22;
95
+ var EMR_SETTEXTCOLOR = 24;
96
+ var EMR_SETBKCOLOR = 25;
97
+ var EMR_OFFSETCLIPRGN = 26;
98
+ var EMR_MOVETOEX = 27;
99
+ var EMR_SETMETARGN = 28;
100
+ var EMR_EXCLUDECLIPRECT = 29;
101
+ var EMR_INTERSECTCLIPRECT = 30;
102
+ var EMR_SCALEVIEWPORTEXTEX = 31;
103
+ var EMR_SCALEWINDOWEXTEX = 32;
104
+ var EMR_SAVEDC = 33;
105
+ var EMR_RESTOREDC = 34;
106
+ var EMR_SETWORLDTRANSFORM = 35;
107
+ var EMR_MODIFYWORLDTRANSFORM = 36;
108
+ var EMR_SELECTOBJECT = 37;
109
+ var EMR_CREATEPEN = 38;
110
+ var EMR_CREATEBRUSHINDIRECT = 39;
111
+ var EMR_DELETEOBJECT = 40;
112
+ var EMR_ELLIPSE = 42;
113
+ var EMR_RECTANGLE = 43;
114
+ var EMR_ROUNDRECT = 44;
115
+ var EMR_ARC = 45;
116
+ var EMR_CHORD = 46;
117
+ var EMR_PIE = 47;
118
+ var EMR_LINETO = 54;
119
+ var EMR_ARCTO = 55;
120
+ var EMR_SETMITERLIMIT = 58;
121
+ var EMR_BEGINPATH = 59;
122
+ var EMR_ENDPATH = 60;
123
+ var EMR_CLOSEFIGURE = 61;
124
+ var EMR_FILLPATH = 62;
125
+ var EMR_STROKEANDFILLPATH = 63;
126
+ var EMR_STROKEPATH = 64;
127
+ var EMR_SELECTCLIPPATH = 67;
128
+ var EMR_COMMENT = 70;
129
+ var EMR_EXTSELECTCLIPRGN = 75;
130
+ var EMR_BITBLT = 76;
131
+ var EMR_STRETCHDIBITS = 81;
132
+ var EMR_EXTCREATEFONTINDIRECTW = 82;
133
+ var EMR_EXTTEXTOUTW = 84;
134
+ var EMR_POLYBEZIER16 = 85;
135
+ var EMR_POLYGON16 = 86;
136
+ var EMR_POLYLINE16 = 87;
137
+ var EMR_POLYBEZIERTO16 = 88;
138
+ var EMR_POLYLINETO16 = 89;
139
+ var EMR_POLYPOLYGON16 = 91;
140
+ var EMR_EXTCREATEPEN = 95;
141
+ var EMR_SETICMMODE = 98;
142
+ var EMR_SETLAYOUT = 115;
143
+ var STOCK_OBJECT_BASE = 2147483648;
144
+ var EMFPLUS_SIGNATURE = 726027589;
145
+ var EMR_COMMENT_PUBLIC_SIGNATURE = 1128875079;
146
+ var EMFPLUS_HEADER = 16385;
147
+ var EMFPLUS_ENDOFFILE = 16386;
148
+ var EMFPLUS_GETDC = 16388;
149
+ var EMFPLUS_OBJECT = 16392;
150
+ var EMFPLUS_FILLRECTS = 16394;
151
+ var EMFPLUS_DRAWRECTS = 16395;
152
+ var EMFPLUS_FILLPOLYGON = 16396;
153
+ var EMFPLUS_DRAWLINES = 16397;
154
+ var EMFPLUS_FILLELLIPSE = 16398;
155
+ var EMFPLUS_DRAWELLIPSE = 16399;
156
+ var EMFPLUS_FILLPIE = 16400;
157
+ var EMFPLUS_DRAWPIE = 16401;
158
+ var EMFPLUS_DRAWARC = 16402;
159
+ var EMFPLUS_FILLPATH = 16404;
160
+ var EMFPLUS_DRAWPATH = 16405;
161
+ var EMFPLUS_DRAWIMAGE = 16410;
162
+ var EMFPLUS_DRAWIMAGEPOINTS = 16411;
163
+ var EMFPLUS_DRAWSTRING = 16412;
164
+ var EMFPLUS_SETANTIALIASMODE = 16414;
165
+ var EMFPLUS_SETTEXTRENDERINGHINT = 16415;
166
+ var EMFPLUS_SETINTERPOLATIONMODE = 16417;
167
+ var EMFPLUS_SETPIXELOFFSETMODE = 16418;
168
+ var EMFPLUS_SETCOMPOSITINGQUALITY = 16420;
169
+ var EMFPLUS_SAVE = 16421;
170
+ var EMFPLUS_RESTORE = 16422;
171
+ var EMFPLUS_BEGINCONTAINERNOPARAMS = 16424;
172
+ var EMFPLUS_ENDCONTAINER = 16425;
173
+ var EMFPLUS_SETWORLDTRANSFORM = 16426;
174
+ var EMFPLUS_RESETWORLDTRANSFORM = 16427;
175
+ var EMFPLUS_MULTIPLYWORLDTRANSFORM = 16428;
176
+ var EMFPLUS_TRANSLATEWORLDTRANSFORM = 16429;
177
+ var EMFPLUS_SCALEWORLDTRANSFORM = 16430;
178
+ var EMFPLUS_ROTATEWORLDTRANSFORM = 16431;
179
+ var EMFPLUS_SETPAGETRANSFORM = 16432;
180
+ var EMFPLUS_RESETCLIP = 16433;
181
+ var EMFPLUS_SETCLIPRECT = 16434;
182
+ var EMFPLUS_SETCLIPPATH = 16435;
183
+ var EMFPLUS_SETCLIPREGION = 16436;
184
+ var EMFPLUS_DRAWDRIVERSTRING = 16438;
185
+ var EMFPLUS_OFFSETCLIP = 16437;
186
+ var EMFPLUS_OBJECTTYPE_BRUSH = 1;
187
+ var EMFPLUS_OBJECTTYPE_PEN = 2;
188
+ var EMFPLUS_OBJECTTYPE_PATH = 3;
189
+ var EMFPLUS_OBJECTTYPE_IMAGEATTRIBUTES = 4;
190
+ var EMFPLUS_OBJECTTYPE_IMAGE = 5;
191
+ var EMFPLUS_OBJECTTYPE_FONT = 6;
192
+ var EMFPLUS_OBJECTTYPE_STRINGFORMAT = 7;
193
+ var EMFPLUS_OBJECTTYPE_REGION = 8;
194
+ var EMFPLUS_BRUSHTYPE_SOLID = 0;
195
+ var EMFPLUS_BRUSHTYPE_HATCHFILL = 1;
196
+ var EMFPLUS_BRUSHTYPE_PATHGRADIENT = 3;
197
+ var EMFPLUS_BRUSHTYPE_LINEARGRADIENT = 4;
198
+ var META_EOF = 0;
199
+ var META_SETBKCOLOR = 513;
200
+ var META_SETBKMODE = 258;
201
+ var META_SETROP2 = 260;
202
+ var META_SETPOLYFILLMODE = 262;
203
+ var META_SETTEXTCOLOR = 521;
204
+ var META_SETTEXTALIGN = 302;
205
+ var META_SETWINDOWORG = 523;
206
+ var META_SETWINDOWEXT = 524;
207
+ var META_MOVETO = 532;
208
+ var META_LINETO = 531;
209
+ var META_RECTANGLE = 1051;
210
+ var META_ROUNDRECT = 1564;
211
+ var META_ELLIPSE = 1048;
212
+ var META_ARC = 2071;
213
+ var META_PIE = 2074;
214
+ var META_CHORD = 2096;
215
+ var META_POLYGON = 804;
216
+ var META_POLYLINE = 805;
217
+ var META_SELECTOBJECT = 301;
218
+ var META_DELETEOBJECT = 496;
219
+ var META_CREATEPENINDIRECT = 762;
220
+ var META_CREATEBRUSHINDIRECT = 764;
221
+ var META_CREATEFONTINDIRECT = 763;
222
+ var META_TEXTOUT = 1313;
223
+ var META_EXTTEXTOUT = 2610;
224
+ var META_SAVEDC = 30;
225
+ var META_RESTOREDC = 295;
226
+ var META_POLYPOLYGON = 1336;
4
227
  var emfLog = (...args) => {
5
228
  };
6
229
  var emfWarn = (...args) => {
@@ -8,7 +231,7 @@ var emfWarn = (...args) => {
8
231
 
9
232
  // src/emf-canvas-helpers.ts
10
233
  var DEFAULT_DPI_SCALE = 1;
11
- function createCanvas(width, height, maxWidth, maxHeight, dpiScale = DEFAULT_DPI_SCALE) {
234
+ function createCanvas(width, height, maxWidth, maxHeight, dpiScale = DEFAULT_DPI_SCALE, maxCanvasDimension = MAX_CANVAS_DIMENSION) {
12
235
  const effectiveScale = Math.max(1, Math.min(dpiScale, 4));
13
236
  let w = Math.round(width * effectiveScale);
14
237
  let h = Math.round(height * effectiveScale);
@@ -28,8 +251,9 @@ function createCanvas(width, height, maxWidth, maxHeight, dpiScale = DEFAULT_DPI
28
251
  scaleX *= factor;
29
252
  scaleY *= factor;
30
253
  }
31
- const clampedW = Math.max(1, Math.min(w, 8192));
32
- const clampedH = Math.max(1, Math.min(h, 8192));
254
+ const dimCap = Math.max(1, Math.floor(maxCanvasDimension));
255
+ const clampedW = Math.max(1, Math.min(w, dimCap));
256
+ const clampedH = Math.max(1, Math.min(h, dimCap));
33
257
  if (clampedW !== w || clampedH !== h) {
34
258
  console.warn(
35
259
  `[emf-converter] Canvas size clamped from ${w}\xD7${h} to ${clampedW}\xD7${clampedH}. Output may lose detail.`
@@ -74,8 +298,8 @@ function createTempCanvas(width, height) {
74
298
  if (width <= 0 || height <= 0) {
75
299
  return null;
76
300
  }
77
- width = Math.max(1, Math.min(Math.floor(width), 8192));
78
- height = Math.max(1, Math.min(Math.floor(height), 8192));
301
+ width = Math.max(1, Math.min(Math.floor(width), MAX_CANVAS_DIMENSION));
302
+ height = Math.max(1, Math.min(Math.floor(height), MAX_CANVAS_DIMENSION));
79
303
  if (typeof OffscreenCanvas !== "undefined") {
80
304
  const canvas = new OffscreenCanvas(width, height);
81
305
  const ctx = canvas.getContext("2d");
@@ -96,13 +320,61 @@ function createTempCanvas(width, height) {
96
320
  }
97
321
  return null;
98
322
  }
323
+ function rop2Paint(rop2) {
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 };
335
+ case R2_XORPEN:
336
+ case R2_MASKPENNOT:
337
+ return { gco: "difference", colorTransform: "none", exact: false };
338
+ case R2_NOTXORPEN:
339
+ return { gco: "difference", colorTransform: "invert", exact: false };
340
+ case R2_MASKPEN:
341
+ return { gco: "darken", colorTransform: "none", exact: false };
342
+ case R2_MASKNOTPEN:
343
+ case R2_NOTMERGEPEN:
344
+ return { gco: "darken", colorTransform: "invert", exact: false };
345
+ case R2_MERGEPEN:
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)";
365
+ default:
366
+ return color;
367
+ }
368
+ }
99
369
  function applyPen(ctx, state) {
370
+ const paint = rop2Paint(state.rop2);
371
+ ctx.globalCompositeOperation = paint.gco;
100
372
  if (state.penStyle === 5) {
101
373
  ctx.strokeStyle = "rgba(0,0,0,0)";
102
374
  ctx.lineWidth = 0;
103
375
  return;
104
376
  }
105
- ctx.strokeStyle = state.penColor;
377
+ ctx.strokeStyle = rop2TransformColor(state.penColor, paint.colorTransform);
106
378
  ctx.lineWidth = Math.max(state.penWidth, 1);
107
379
  switch (state.penStyle) {
108
380
  case 1:
@@ -123,17 +395,57 @@ function applyPen(ctx, state) {
123
395
  }
124
396
  }
125
397
  function applyBrush(ctx, state) {
398
+ const paint = rop2Paint(state.rop2);
399
+ ctx.globalCompositeOperation = paint.gco;
126
400
  if (state.brushStyle === 1) {
127
401
  ctx.fillStyle = "rgba(0,0,0,0)";
128
402
  return;
129
403
  }
130
- ctx.fillStyle = state.brushColor;
404
+ ctx.fillStyle = rop2TransformColor(state.brushColor, paint.colorTransform);
405
+ }
406
+ function cssFontWeight(weight) {
407
+ if (!weight || weight === 400) {
408
+ return "";
409
+ }
410
+ const rounded = Math.round(weight / 100) * 100;
411
+ if (rounded === 700) {
412
+ return "bold";
413
+ }
414
+ if (rounded >= 100 && rounded <= 900) {
415
+ return String(rounded);
416
+ }
417
+ return weight >= 700 ? "bold" : "";
418
+ }
419
+ function mapFontFamily(face, map) {
420
+ const resolved = map?.[face.toLowerCase().trim()] ?? face;
421
+ if (/[\s,]/.test(resolved) && !/^["']/.test(resolved)) {
422
+ return `"${resolved}"`;
423
+ }
424
+ return resolved;
131
425
  }
132
426
  function applyFont(ctx, state) {
133
427
  const italic = state.fontItalic ? "italic " : "";
134
- const weight = state.fontWeight >= 700 ? "bold " : "";
428
+ const weight = cssFontWeight(state.fontWeight);
429
+ const weightPart = weight ? `${weight} ` : "";
430
+ const size = Math.max(Math.abs(state.fontHeight), 8);
431
+ const family = mapFontFamily(state.fontFamily, state.fontFamilyMap);
432
+ ctx.font = `${italic}${weightPart}${size}px ${family}`;
433
+ }
434
+ function drawTextDecorations(ctx, state, x, y, width) {
435
+ if (!state.fontUnderline && !state.fontStrikeOut) {
436
+ return;
437
+ }
135
438
  const size = Math.max(Math.abs(state.fontHeight), 8);
136
- ctx.font = `${italic}${weight}${size}px ${state.fontFamily}`;
439
+ const thickness = Math.max(1, Math.round(size / 14));
440
+ const prevFill = ctx.fillStyle;
441
+ ctx.fillStyle = state.textColor;
442
+ if (state.fontUnderline) {
443
+ ctx.fillRect(x, y + Math.round(size * 0.12), width, thickness);
444
+ }
445
+ if (state.fontStrikeOut) {
446
+ ctx.fillRect(x, y - Math.round(size * 0.3), width, thickness);
447
+ }
448
+ ctx.fillStyle = prevFill;
137
449
  }
138
450
  function readUtf16LE(view, offset, charCount) {
139
451
  if (charCount <= 0) {
@@ -192,6 +504,8 @@ function getStockObject(index) {
192
504
  height: 12,
193
505
  weight: 400,
194
506
  italic: false,
507
+ underline: false,
508
+ strikeOut: false,
195
509
  family: "monospace"
196
510
  };
197
511
  case 12:
@@ -203,6 +517,8 @@ function getStockObject(index) {
203
517
  height: 12,
204
518
  weight: 400,
205
519
  italic: false,
520
+ underline: false,
521
+ strikeOut: false,
206
522
  family: "sans-serif"
207
523
  };
208
524
  default:
@@ -235,161 +551,6 @@ async function exportCanvasToPngDataUrl(canvas) {
235
551
  return null;
236
552
  }
237
553
 
238
- // src/emf-constants.ts
239
- var EMR_HEADER = 1;
240
- var EMR_POLYBEZIER = 2;
241
- var EMR_POLYGON = 3;
242
- var EMR_POLYLINE = 4;
243
- var EMR_POLYBEZIERTO = 5;
244
- var EMR_POLYLINETO = 6;
245
- var EMR_POLYPOLYLINE = 7;
246
- var EMR_POLYPOLYGON = 8;
247
- var EMR_SETWINDOWEXTEX = 9;
248
- var EMR_SETWINDOWORGEX = 10;
249
- var EMR_SETVIEWPORTEXTEX = 11;
250
- var EMR_SETVIEWPORTORGEX = 12;
251
- var EMR_SETBRUSHORGEX = 13;
252
- var EMR_EOF = 14;
253
- var EMR_SETPIXELV = 15;
254
- var EMR_SETMAPMODE = 17;
255
- var EMR_SETBKMODE = 18;
256
- var EMR_SETPOLYFILLMODE = 19;
257
- var EMR_SETROP2 = 20;
258
- var EMR_SETSTRETCHBLTMODE = 21;
259
- var EMR_SETTEXTALIGN = 22;
260
- var EMR_SETTEXTCOLOR = 24;
261
- var EMR_SETBKCOLOR = 25;
262
- var EMR_OFFSETCLIPRGN = 26;
263
- var EMR_MOVETOEX = 27;
264
- var EMR_SETMETARGN = 28;
265
- var EMR_EXCLUDECLIPRECT = 29;
266
- var EMR_INTERSECTCLIPRECT = 30;
267
- var EMR_SCALEVIEWPORTEXTEX = 31;
268
- var EMR_SCALEWINDOWEXTEX = 32;
269
- var EMR_SAVEDC = 33;
270
- var EMR_RESTOREDC = 34;
271
- var EMR_SETWORLDTRANSFORM = 35;
272
- var EMR_MODIFYWORLDTRANSFORM = 36;
273
- var EMR_SELECTOBJECT = 37;
274
- var EMR_CREATEPEN = 38;
275
- var EMR_CREATEBRUSHINDIRECT = 39;
276
- var EMR_DELETEOBJECT = 40;
277
- var EMR_ELLIPSE = 42;
278
- var EMR_RECTANGLE = 43;
279
- var EMR_ROUNDRECT = 44;
280
- var EMR_ARC = 45;
281
- var EMR_CHORD = 46;
282
- var EMR_PIE = 47;
283
- var EMR_LINETO = 54;
284
- var EMR_ARCTO = 55;
285
- var EMR_SETMITERLIMIT = 58;
286
- var EMR_BEGINPATH = 59;
287
- var EMR_ENDPATH = 60;
288
- var EMR_CLOSEFIGURE = 61;
289
- var EMR_FILLPATH = 62;
290
- var EMR_STROKEANDFILLPATH = 63;
291
- var EMR_STROKEPATH = 64;
292
- var EMR_SELECTCLIPPATH = 67;
293
- var EMR_COMMENT = 70;
294
- var EMR_EXTSELECTCLIPRGN = 75;
295
- var EMR_BITBLT = 76;
296
- var EMR_STRETCHDIBITS = 81;
297
- var EMR_EXTCREATEFONTINDIRECTW = 82;
298
- var EMR_EXTTEXTOUTW = 84;
299
- var EMR_POLYBEZIER16 = 85;
300
- var EMR_POLYGON16 = 86;
301
- var EMR_POLYLINE16 = 87;
302
- var EMR_POLYBEZIERTO16 = 88;
303
- var EMR_POLYLINETO16 = 89;
304
- var EMR_POLYPOLYGON16 = 91;
305
- var EMR_EXTCREATEPEN = 95;
306
- var EMR_SETICMMODE = 98;
307
- var EMR_SETLAYOUT = 115;
308
- var STOCK_OBJECT_BASE = 2147483648;
309
- var EMFPLUS_SIGNATURE = 726027589;
310
- var EMR_COMMENT_PUBLIC_SIGNATURE = 1128875079;
311
- var EMFPLUS_HEADER = 16385;
312
- var EMFPLUS_ENDOFFILE = 16386;
313
- var EMFPLUS_GETDC = 16388;
314
- var EMFPLUS_OBJECT = 16392;
315
- var EMFPLUS_FILLRECTS = 16394;
316
- var EMFPLUS_DRAWRECTS = 16395;
317
- var EMFPLUS_FILLPOLYGON = 16396;
318
- var EMFPLUS_DRAWLINES = 16397;
319
- var EMFPLUS_FILLELLIPSE = 16398;
320
- var EMFPLUS_DRAWELLIPSE = 16399;
321
- var EMFPLUS_FILLPIE = 16400;
322
- var EMFPLUS_DRAWPIE = 16401;
323
- var EMFPLUS_DRAWARC = 16402;
324
- var EMFPLUS_FILLPATH = 16404;
325
- var EMFPLUS_DRAWPATH = 16405;
326
- var EMFPLUS_DRAWIMAGE = 16410;
327
- var EMFPLUS_DRAWIMAGEPOINTS = 16411;
328
- var EMFPLUS_DRAWSTRING = 16412;
329
- var EMFPLUS_SETANTIALIASMODE = 16414;
330
- var EMFPLUS_SETTEXTRENDERINGHINT = 16415;
331
- var EMFPLUS_SETINTERPOLATIONMODE = 16417;
332
- var EMFPLUS_SETPIXELOFFSETMODE = 16418;
333
- var EMFPLUS_SETCOMPOSITINGQUALITY = 16420;
334
- var EMFPLUS_SAVE = 16421;
335
- var EMFPLUS_RESTORE = 16422;
336
- var EMFPLUS_BEGINCONTAINERNOPARAMS = 16424;
337
- var EMFPLUS_ENDCONTAINER = 16425;
338
- var EMFPLUS_SETWORLDTRANSFORM = 16426;
339
- var EMFPLUS_RESETWORLDTRANSFORM = 16427;
340
- var EMFPLUS_MULTIPLYWORLDTRANSFORM = 16428;
341
- var EMFPLUS_TRANSLATEWORLDTRANSFORM = 16429;
342
- var EMFPLUS_SCALEWORLDTRANSFORM = 16430;
343
- var EMFPLUS_ROTATEWORLDTRANSFORM = 16431;
344
- var EMFPLUS_SETPAGETRANSFORM = 16432;
345
- var EMFPLUS_RESETCLIP = 16433;
346
- var EMFPLUS_SETCLIPRECT = 16434;
347
- var EMFPLUS_SETCLIPPATH = 16435;
348
- var EMFPLUS_SETCLIPREGION = 16436;
349
- var EMFPLUS_DRAWDRIVERSTRING = 16438;
350
- var EMFPLUS_OFFSETCLIP = 16437;
351
- var EMFPLUS_OBJECTTYPE_BRUSH = 1;
352
- var EMFPLUS_OBJECTTYPE_PEN = 2;
353
- var EMFPLUS_OBJECTTYPE_PATH = 3;
354
- var EMFPLUS_OBJECTTYPE_IMAGEATTRIBUTES = 4;
355
- var EMFPLUS_OBJECTTYPE_IMAGE = 5;
356
- var EMFPLUS_OBJECTTYPE_FONT = 6;
357
- var EMFPLUS_OBJECTTYPE_STRINGFORMAT = 7;
358
- var EMFPLUS_OBJECTTYPE_REGION = 8;
359
- var EMFPLUS_BRUSHTYPE_SOLID = 0;
360
- var EMFPLUS_BRUSHTYPE_HATCHFILL = 1;
361
- var EMFPLUS_BRUSHTYPE_PATHGRADIENT = 3;
362
- var EMFPLUS_BRUSHTYPE_LINEARGRADIENT = 4;
363
- var META_EOF = 0;
364
- var META_SETBKCOLOR = 513;
365
- var META_SETBKMODE = 258;
366
- var META_SETROP2 = 260;
367
- var META_SETPOLYFILLMODE = 262;
368
- var META_SETTEXTCOLOR = 521;
369
- var META_SETTEXTALIGN = 302;
370
- var META_SETWINDOWORG = 523;
371
- var META_SETWINDOWEXT = 524;
372
- var META_MOVETO = 532;
373
- var META_LINETO = 531;
374
- var META_RECTANGLE = 1051;
375
- var META_ROUNDRECT = 1564;
376
- var META_ELLIPSE = 1048;
377
- var META_ARC = 2071;
378
- var META_PIE = 2074;
379
- var META_CHORD = 2096;
380
- var META_POLYGON = 804;
381
- var META_POLYLINE = 805;
382
- var META_SELECTOBJECT = 301;
383
- var META_DELETEOBJECT = 496;
384
- var META_CREATEPENINDIRECT = 762;
385
- var META_CREATEBRUSHINDIRECT = 764;
386
- var META_CREATEFONTINDIRECT = 763;
387
- var META_TEXTOUT = 1313;
388
- var META_EXTTEXTOUT = 2610;
389
- var META_SAVEDC = 30;
390
- var META_RESTOREDC = 295;
391
- var META_POLYPOLYGON = 1336;
392
-
393
554
  // src/emf-header-parser.ts
394
555
  function parseEmfHeader(view) {
395
556
  emfLog("parseEmfHeader: byteLength =", view.byteLength);
@@ -474,49 +635,36 @@ function parseWmfHeader(view) {
474
635
  };
475
636
  }
476
637
 
477
- // src/emf-color-helpers.ts
478
- function colorRefToHex(r, g, b) {
479
- const toHex = (v) => v.toString(16).padStart(2, "0");
480
- return `#${toHex(r & 255)}${toHex(g & 255)}${toHex(b & 255)}`;
481
- }
482
- function readColorRef(view, offset) {
483
- const r = view.getUint8(offset);
484
- const g = view.getUint8(offset + 1);
485
- const b = view.getUint8(offset + 2);
486
- return colorRefToHex(r, g, b);
487
- }
488
- function argbToRgba(argb) {
489
- const a = (argb >>> 24 & 255) / 255;
490
- const r = argb >>> 16 & 255;
491
- const g = argb >>> 8 & 255;
492
- const b = argb & 255;
493
- return `rgba(${r},${g},${b},${a.toFixed(3)})`;
494
- }
495
-
496
638
  // src/emf-gdi-coord.ts
497
639
  function gmx(r, x) {
640
+ const wt = r.state.worldTransform;
641
+ const px = wt[0] * x + wt[4];
498
642
  if (r.useMappingMode) {
499
- return (x - r.windowOrg.x) / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1) + r.viewportOrg.x;
643
+ return (px - r.windowOrg.x) / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1) + r.viewportOrg.x;
500
644
  }
501
- return (x - r.bounds.left) * r.sx;
645
+ return (px - r.bounds.left) * r.sx;
502
646
  }
503
647
  function gmy(r, y) {
648
+ const wt = r.state.worldTransform;
649
+ const py = wt[3] * y + wt[5];
504
650
  if (r.useMappingMode) {
505
- return (y - r.windowOrg.y) / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1) + r.viewportOrg.y;
651
+ return (py - r.windowOrg.y) / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1) + r.viewportOrg.y;
506
652
  }
507
- return (y - r.bounds.top) * r.sy;
653
+ return (py - r.bounds.top) * r.sy;
508
654
  }
509
655
  function gmw(r, w) {
656
+ const pw = r.state.worldTransform[0] * w;
510
657
  if (r.useMappingMode) {
511
- return w / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1);
658
+ return pw / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1);
512
659
  }
513
- return w * r.sx;
660
+ return pw * r.sx;
514
661
  }
515
662
  function gmh(r, h) {
663
+ const ph = r.state.worldTransform[3] * h;
516
664
  if (r.useMappingMode) {
517
- return h / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1);
665
+ return ph / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1);
518
666
  }
519
- return h * r.sy;
667
+ return ph * r.sy;
520
668
  }
521
669
  function activateGdiMappingMode(r) {
522
670
  r.useMappingMode = true;
@@ -696,27 +844,239 @@ function handleArcFamily(rCtx, recType, dataOff, recSize) {
696
844
  }
697
845
  return true;
698
846
  }
699
- function handleEmfGdiShapeRecord(rCtx, recType, dataOff, recSize) {
700
- switch (recType) {
701
- case EMR_SETPIXELV:
702
- return handleSetPixelV(rCtx, dataOff, recSize);
703
- case EMR_MOVETOEX:
704
- return handleMoveToEx(rCtx, dataOff, recSize);
705
- case EMR_LINETO:
706
- return handleLineTo(rCtx, dataOff, recSize);
707
- case EMR_RECTANGLE:
708
- return handleRectangle(rCtx, dataOff, recSize);
709
- case EMR_ROUNDRECT:
710
- return handleRoundRect(rCtx, dataOff, recSize);
711
- case EMR_ELLIPSE:
712
- return handleEllipse(rCtx, dataOff, recSize);
713
- case EMR_ARC:
714
- case EMR_ARCTO:
715
- case EMR_CHORD:
716
- case EMR_PIE:
717
- return handleArcFamily(rCtx, recType, dataOff, recSize);
718
- default:
719
- return false;
847
+ function handleEmfGdiShapeRecord(rCtx, recType, dataOff, recSize) {
848
+ switch (recType) {
849
+ case EMR_SETPIXELV:
850
+ return handleSetPixelV(rCtx, dataOff, recSize);
851
+ case EMR_MOVETOEX:
852
+ return handleMoveToEx(rCtx, dataOff, recSize);
853
+ case EMR_LINETO:
854
+ return handleLineTo(rCtx, dataOff, recSize);
855
+ case EMR_RECTANGLE:
856
+ return handleRectangle(rCtx, dataOff, recSize);
857
+ case EMR_ROUNDRECT:
858
+ return handleRoundRect(rCtx, dataOff, recSize);
859
+ case EMR_ELLIPSE:
860
+ return handleEllipse(rCtx, dataOff, recSize);
861
+ case EMR_ARC:
862
+ case EMR_ARCTO:
863
+ case EMR_CHORD:
864
+ case EMR_PIE:
865
+ return handleArcFamily(rCtx, recType, dataOff, recSize);
866
+ default:
867
+ return false;
868
+ }
869
+ }
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);
720
1080
  }
721
1081
  }
722
1082
 
@@ -1066,6 +1426,12 @@ function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
1066
1426
  ctx.fillStyle = state.textColor;
1067
1427
  }
1068
1428
  ctx.fillText(text, gmx(rCtx, refX), gmy(rCtx, refY));
1429
+ if (state.fontUnderline || state.fontStrikeOut) {
1430
+ const w = ctx.measureText(text).width;
1431
+ const baseX = gmx(rCtx, refX);
1432
+ const startX = alignHoriz === "center" ? baseX - w / 2 : alignHoriz === "right" ? baseX - w : baseX;
1433
+ drawTextDecorations(ctx, state, startX, gmy(rCtx, refY), w);
1434
+ }
1069
1435
  }
1070
1436
  }
1071
1437
  }
@@ -1141,86 +1507,134 @@ function handleStretchDibits(rCtx, offset, dataOff, recSize) {
1141
1507
  }
1142
1508
  return true;
1143
1509
  }
1510
+ function gdiCombineClip(rCtx, shape, op) {
1511
+ const { ctx } = rCtx;
1512
+ if (rCtx.clipUntracked && op !== "replace") {
1513
+ switch (op) {
1514
+ case "intersect":
1515
+ case "complement": {
1516
+ ctx.save();
1517
+ rCtx.clipSaveDepth++;
1518
+ applyClipShapes(ctx, [shape]);
1519
+ return;
1520
+ }
1521
+ case "exclude":
1522
+ case "xor": {
1523
+ const inv = combineClip(null, shape, "exclude");
1524
+ ctx.save();
1525
+ rCtx.clipSaveDepth++;
1526
+ applyClipShapes(ctx, inv.region ?? [shape]);
1527
+ return;
1528
+ }
1529
+ case "union":
1530
+ return;
1531
+ }
1532
+ }
1533
+ const res = combineClip(op === "replace" ? null : rCtx.clipRegion ?? null, shape, op);
1534
+ if (!res.exact) ;
1535
+ rCtx.clipRegion = res.region;
1536
+ rCtx.clipUntracked = false;
1537
+ reapplyClipRegion(rCtx, res.region);
1538
+ }
1539
+ function readClipRectShape(rCtx, dataOff) {
1540
+ const { view } = rCtx;
1541
+ const left = view.getInt32(dataOff, true);
1542
+ const top = view.getInt32(dataOff + 4, true);
1543
+ const right = view.getInt32(dataOff + 8, true);
1544
+ const bottom = view.getInt32(dataOff + 12, true);
1545
+ return rectClipShape(
1546
+ gmx(rCtx, left),
1547
+ gmy(rCtx, top),
1548
+ gmw(rCtx, right - left),
1549
+ gmh(rCtx, bottom - top)
1550
+ );
1551
+ }
1144
1552
  function handleIntersectClipRect(rCtx, dataOff, recSize) {
1145
- const { ctx, view } = rCtx;
1146
1553
  if (recSize >= 24) {
1147
- const left = view.getInt32(dataOff, true);
1148
- const top = view.getInt32(dataOff + 4, true);
1149
- const right = view.getInt32(dataOff + 8, true);
1150
- const bottom = view.getInt32(dataOff + 12, true);
1151
- ctx.save();
1152
- rCtx.clipSaveDepth++;
1153
- ctx.beginPath();
1154
- ctx.rect(gmx(rCtx, left), gmy(rCtx, top), gmw(rCtx, right - left), gmh(rCtx, bottom - top));
1155
- try {
1156
- ctx.clip();
1157
- } catch {
1158
- }
1554
+ gdiCombineClip(rCtx, readClipRectShape(rCtx, dataOff), "intersect");
1159
1555
  }
1160
1556
  return true;
1161
1557
  }
1558
+ function handleExcludeClipRect(rCtx, dataOff, recSize) {
1559
+ if (recSize >= 24) {
1560
+ gdiCombineClip(rCtx, readClipRectShape(rCtx, dataOff), "exclude");
1561
+ }
1562
+ return true;
1563
+ }
1564
+ var RGN_MODE_OPS = {
1565
+ 1: "intersect",
1566
+ // RGN_AND
1567
+ 2: "union",
1568
+ // RGN_OR
1569
+ 3: "xor",
1570
+ // RGN_XOR
1571
+ 4: "exclude",
1572
+ // RGN_DIFF
1573
+ 5: "replace"
1574
+ // RGN_COPY
1575
+ };
1162
1576
  function handleExtSelectClipRgn(rCtx, dataOff, recSize) {
1163
- const { ctx, view } = rCtx;
1577
+ const { view } = rCtx;
1164
1578
  if (recSize < 16) {
1165
1579
  return true;
1166
1580
  }
1167
1581
  const cbRgnData = view.getUint32(dataOff, true);
1168
1582
  const iMode = view.getUint32(dataOff + 4, true);
1169
- if (iMode === 5) {
1170
- if (cbRgnData === 0) {
1171
- while (rCtx.clipSaveDepth > 0) {
1172
- ctx.restore();
1173
- rCtx.clipSaveDepth--;
1174
- }
1175
- return true;
1176
- }
1177
- const rgnStart = dataOff + 8;
1178
- if (cbRgnData < 32) {
1179
- return true;
1180
- }
1181
- const nCount = view.getUint32(rgnStart + 8, true);
1182
- if (nCount === 0) {
1183
- return true;
1184
- }
1185
- while (rCtx.clipSaveDepth > 0) {
1186
- ctx.restore();
1187
- rCtx.clipSaveDepth--;
1188
- }
1189
- ctx.save();
1190
- rCtx.clipSaveDepth++;
1191
- ctx.beginPath();
1192
- const rectsStart = rgnStart + 32;
1193
- for (let i = 0; i < nCount; i++) {
1194
- const rOff = rectsStart + i * 16;
1195
- if (rOff + 16 > dataOff + 8 + cbRgnData) {
1196
- break;
1197
- }
1198
- const left = view.getInt32(rOff, true);
1199
- const top = view.getInt32(rOff + 4, true);
1200
- const right = view.getInt32(rOff + 8, true);
1201
- const bottom = view.getInt32(rOff + 12, true);
1202
- ctx.rect(gmx(rCtx, left), gmy(rCtx, top), gmw(rCtx, right - left), gmh(rCtx, bottom - top));
1203
- }
1204
- try {
1205
- ctx.clip();
1206
- } catch {
1583
+ const op = RGN_MODE_OPS[iMode];
1584
+ if (!op) {
1585
+ return true;
1586
+ }
1587
+ if (cbRgnData === 0) {
1588
+ if (op === "replace") {
1589
+ rCtx.clipRegion = null;
1590
+ rCtx.clipUntracked = false;
1591
+ reapplyClipRegion(rCtx, null);
1207
1592
  }
1593
+ return true;
1208
1594
  }
1209
- return true;
1210
- }
1211
- function handleExcludeClipRect(rCtx, dataOff, recSize) {
1212
- if (recSize >= 24) {
1213
- rCtx.view.getInt32(dataOff, true);
1214
- rCtx.view.getInt32(dataOff + 4, true);
1215
- rCtx.view.getInt32(dataOff + 8, true);
1216
- rCtx.view.getInt32(dataOff + 12, true);
1595
+ const rgnStart = dataOff + 8;
1596
+ if (cbRgnData < 32) {
1597
+ return true;
1598
+ }
1599
+ const nCount = view.getUint32(rgnStart + 8, true);
1600
+ if (nCount === 0) {
1601
+ return true;
1602
+ }
1603
+ const rects = [];
1604
+ const rectsStart = rgnStart + 32;
1605
+ for (let i = 0; i < nCount; i++) {
1606
+ const rOff = rectsStart + i * 16;
1607
+ if (rOff + 16 > dataOff + 8 + cbRgnData) {
1608
+ break;
1609
+ }
1610
+ const left = view.getInt32(rOff, true);
1611
+ const top = view.getInt32(rOff + 4, true);
1612
+ const right = view.getInt32(rOff + 8, true);
1613
+ const bottom = view.getInt32(rOff + 12, true);
1614
+ rects.push({
1615
+ x: gmx(rCtx, left),
1616
+ y: gmy(rCtx, top),
1617
+ w: gmw(rCtx, right - left),
1618
+ h: gmh(rCtx, bottom - top)
1619
+ });
1620
+ }
1621
+ if (rects.length === 0) {
1622
+ return true;
1217
1623
  }
1624
+ gdiCombineClip(rCtx, rectsClipShape(rects), op);
1218
1625
  return true;
1219
1626
  }
1220
1627
  function handleOffsetClipRgn(rCtx, dataOff, recSize) {
1221
1628
  if (recSize >= 16) {
1222
- rCtx.view.getInt32(dataOff, true);
1223
- rCtx.view.getInt32(dataOff + 4, true);
1629
+ const dx = rCtx.view.getInt32(dataOff, true);
1630
+ const dy = rCtx.view.getInt32(dataOff + 4, true);
1631
+ if (rCtx.clipUntracked) {
1632
+ return true;
1633
+ }
1634
+ if (rCtx.clipRegion) {
1635
+ rCtx.clipRegion = translateClipRegion(rCtx.clipRegion, gmw(rCtx, dx), gmh(rCtx, dy));
1636
+ reapplyClipRegion(rCtx, rCtx.clipRegion);
1637
+ }
1224
1638
  }
1225
1639
  return true;
1226
1640
  }
@@ -1543,14 +1957,12 @@ function handleEmfGdiPolyPathRecord(rCtx, recType, offset, dataOff, recSize) {
1543
1957
  ctx.restore();
1544
1958
  rCtx.clipSaveDepth--;
1545
1959
  }
1546
- ctx.save();
1547
- rCtx.clipSaveDepth++;
1548
- ctx.clip();
1549
- } else {
1550
- ctx.save();
1551
- rCtx.clipSaveDepth++;
1552
- ctx.clip();
1553
1960
  }
1961
+ ctx.save();
1962
+ rCtx.clipSaveDepth++;
1963
+ ctx.clip(state.polyFillMode === 2 ? "nonzero" : "evenodd");
1964
+ rCtx.clipRegion = null;
1965
+ rCtx.clipUntracked = true;
1554
1966
  } catch {
1555
1967
  }
1556
1968
  return true;
@@ -1613,12 +2025,16 @@ function handleEmfObjectRecord(rCtx, recType, dataOff, recSize) {
1613
2025
  const height = view.getInt32(dataOff + 4, true);
1614
2026
  const weight = view.getInt32(dataOff + 20, true);
1615
2027
  const italic = view.getUint8(dataOff + 24);
2028
+ const underline = view.getUint8(dataOff + 25);
2029
+ const strikeOut = view.getUint8(dataOff + 26);
1616
2030
  const family = readUtf16LE(view, dataOff + 28, 32) || "sans-serif";
1617
2031
  rCtx.objectTable.set(ihFont, {
1618
2032
  kind: "font",
1619
2033
  height: Math.abs(height),
1620
2034
  weight,
1621
2035
  italic: italic !== 0,
2036
+ underline: underline !== 0,
2037
+ strikeOut: strikeOut !== 0,
1622
2038
  family
1623
2039
  });
1624
2040
  }
@@ -1643,6 +2059,8 @@ function handleEmfObjectRecord(rCtx, recType, dataOff, recSize) {
1643
2059
  state.fontHeight = obj.height;
1644
2060
  state.fontWeight = obj.weight;
1645
2061
  state.fontItalic = obj.italic;
2062
+ state.fontUnderline = obj.underline;
2063
+ state.fontStrikeOut = obj.strikeOut;
1646
2064
  state.fontFamily = obj.family;
1647
2065
  break;
1648
2066
  }
@@ -1819,6 +2237,9 @@ function defaultState() {
1819
2237
  fontWeight: 400,
1820
2238
  fontItalic: false,
1821
2239
  fontFamily: "sans-serif",
2240
+ fontUnderline: false,
2241
+ fontStrikeOut: false,
2242
+ rop2: 13,
1822
2243
  curX: 0,
1823
2244
  curY: 0,
1824
2245
  polyFillMode: 1,
@@ -1837,7 +2258,9 @@ function createEmfPlusState() {
1837
2258
  objectTable: /* @__PURE__ */ new Map(),
1838
2259
  worldTransform: [1, 0, 0, 1, 0, 0],
1839
2260
  saveStack: [],
1840
- saveIdMap: /* @__PURE__ */ new Map()
2261
+ saveIdMap: /* @__PURE__ */ new Map(),
2262
+ clipRegion: null,
2263
+ clipSaveDepth: 0
1841
2264
  };
1842
2265
  }
1843
2266
 
@@ -1857,8 +2280,19 @@ function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
1857
2280
  ctx.restore();
1858
2281
  rCtx.clipSaveDepth--;
1859
2282
  }
2283
+ rCtx.clipStack ?? (rCtx.clipStack = []);
2284
+ rCtx.clipStack.push({
2285
+ region: rCtx.clipRegion ?? null,
2286
+ untracked: rCtx.clipUntracked ?? false
2287
+ });
1860
2288
  rCtx.stateStack.push(cloneState(state));
1861
2289
  ctx.save();
2290
+ if (rCtx.clipUntracked) {
2291
+ rCtx.clipUntracked = false;
2292
+ rCtx.clipRegion = null;
2293
+ } else if (rCtx.clipRegion) {
2294
+ reapplyClipRegion(rCtx, rCtx.clipRegion);
2295
+ }
1862
2296
  return true;
1863
2297
  }
1864
2298
  case EMR_RESTOREDC: {
@@ -1869,16 +2303,23 @@ function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
1869
2303
  }
1870
2304
  let rel = view.getInt32(dataOff, true);
1871
2305
  if (rel < 0) {
1872
- rel = rCtx.stateStack.length + rel;
2306
+ rel = rCtx.stateStack.length + rel + 1;
1873
2307
  }
1874
2308
  while (rCtx.stateStack.length > rel && rCtx.stateStack.length > 0) {
1875
2309
  rCtx.stateStack.pop();
2310
+ rCtx.clipStack?.pop();
1876
2311
  ctx.restore();
1877
2312
  }
1878
2313
  const restored = rCtx.stateStack.pop();
1879
2314
  if (restored) {
2315
+ const clipSnapshot = rCtx.clipStack?.pop();
1880
2316
  Object.assign(state, restored);
1881
2317
  ctx.restore();
2318
+ rCtx.clipRegion = clipSnapshot?.region ?? null;
2319
+ rCtx.clipUntracked = false;
2320
+ if (rCtx.clipRegion) {
2321
+ reapplyClipRegion(rCtx, rCtx.clipRegion);
2322
+ }
1882
2323
  }
1883
2324
  }
1884
2325
  return true;
@@ -1908,7 +2349,12 @@ function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
1908
2349
  }
1909
2350
  return true;
1910
2351
  }
1911
- case EMR_SETROP2:
2352
+ case EMR_SETROP2: {
2353
+ if (recSize >= 12) {
2354
+ state.rop2 = view.getUint32(dataOff, true);
2355
+ }
2356
+ return true;
2357
+ }
1912
2358
  case EMR_SETSTRETCHBLTMODE:
1913
2359
  case EMR_SETMITERLIMIT:
1914
2360
  case EMR_SETTEXTALIGN: {
@@ -1992,6 +2438,47 @@ function parseEmfPlusPath(data, off, maxLen) {
1992
2438
  const types = new Uint8Array(data.buffer, data.byteOffset + alignedPOff, pointCount);
1993
2439
  return { kind: "plus-path", points, types: new Uint8Array(types) };
1994
2440
  }
2441
+ function emfPlusPathToClipCmds(path, m) {
2442
+ const tx = (x, y) => m[0] * x + m[2] * y + m[4];
2443
+ const ty = (x, y) => m[1] * x + m[3] * y + m[5];
2444
+ const cmds = [];
2445
+ const pts = path.points;
2446
+ const types = path.types;
2447
+ let i = 0;
2448
+ while (i < pts.length) {
2449
+ const t = types[i] & 15;
2450
+ const close = (types[i] & 128) !== 0;
2451
+ if (t === 0) {
2452
+ cmds.push({ op: "moveTo", x: tx(pts[i].x, pts[i].y), y: ty(pts[i].x, pts[i].y) });
2453
+ i++;
2454
+ } else if (t === 3) {
2455
+ if (i + 2 < pts.length) {
2456
+ cmds.push({
2457
+ op: "bezierCurveTo",
2458
+ cp1x: tx(pts[i].x, pts[i].y),
2459
+ cp1y: ty(pts[i].x, pts[i].y),
2460
+ cp2x: tx(pts[i + 1].x, pts[i + 1].y),
2461
+ cp2y: ty(pts[i + 1].x, pts[i + 1].y),
2462
+ x: tx(pts[i + 2].x, pts[i + 2].y),
2463
+ y: ty(pts[i + 2].x, pts[i + 2].y)
2464
+ });
2465
+ if ((types[i + 2] & 128) !== 0) {
2466
+ cmds.push({ op: "closePath" });
2467
+ }
2468
+ i += 3;
2469
+ continue;
2470
+ }
2471
+ break;
2472
+ } else {
2473
+ cmds.push({ op: "lineTo", x: tx(pts[i].x, pts[i].y), y: ty(pts[i].x, pts[i].y) });
2474
+ i++;
2475
+ }
2476
+ if (close) {
2477
+ cmds.push({ op: "closePath" });
2478
+ }
2479
+ }
2480
+ return cmds;
2481
+ }
1995
2482
  function replayEmfPlusPath(ctx, path) {
1996
2483
  ctx.beginPath();
1997
2484
  const pts = path.points;
@@ -2045,12 +2532,44 @@ function multiplyMatrix(m1, m2) {
2045
2532
  m1[4] * m2[1] + m1[5] * m2[3] + m2[5]
2046
2533
  ];
2047
2534
  }
2048
- function resolveBrushColor(rCtx, flags, brushIdOrColor) {
2535
+ function createBrushGradient(rCtx, grad) {
2536
+ const ctx = rCtx.ctx;
2537
+ try {
2538
+ let g = null;
2539
+ if (grad.type === "linear" && typeof ctx.createLinearGradient === "function") {
2540
+ if (grad.x1 === grad.x2 && grad.y1 === grad.y2) {
2541
+ return null;
2542
+ }
2543
+ g = ctx.createLinearGradient(grad.x1, grad.y1, grad.x2, grad.y2);
2544
+ } else if (grad.type === "radial" && typeof ctx.createRadialGradient === "function") {
2545
+ if (!(grad.r > 0)) {
2546
+ return null;
2547
+ }
2548
+ g = ctx.createRadialGradient(grad.cx, grad.cy, 0, grad.cx, grad.cy, grad.r);
2549
+ }
2550
+ if (!g) {
2551
+ return null;
2552
+ }
2553
+ for (const stop of grad.stops) {
2554
+ g.addColorStop(stop.offset, stop.color);
2555
+ }
2556
+ return g;
2557
+ } catch {
2558
+ return null;
2559
+ }
2560
+ }
2561
+ function resolveBrushPaint(rCtx, flags, brushIdOrColor) {
2049
2562
  if (flags & 32768) {
2050
2563
  return argbToRgba(brushIdOrColor);
2051
2564
  }
2052
2565
  const obj = rCtx.objectTable.get(brushIdOrColor & 255);
2053
2566
  if (obj && obj.kind === "plus-brush") {
2567
+ if (obj.gradient) {
2568
+ const g = createBrushGradient(rCtx, obj.gradient);
2569
+ if (g) {
2570
+ return g;
2571
+ }
2572
+ }
2054
2573
  return obj.color;
2055
2574
  }
2056
2575
  return "rgba(0,0,0,1)";
@@ -2114,88 +2633,83 @@ function popState(rCtx, stackId) {
2114
2633
  rCtx.saveIdMap = newMap;
2115
2634
  }
2116
2635
  }
2117
- function ensureClipSave(rCtx) {
2118
- if (rCtx.clipSaveDepth === 0) {
2119
- rCtx.ctx.save();
2120
- rCtx.clipSaveDepth = 1;
2121
- }
2636
+ function plusDeviceMatrix(rCtx) {
2637
+ const wt = rCtx.worldTransform;
2638
+ const s = getPageUnitMultiplier(rCtx.pageUnit, rCtx.pageScale) * rCtx.dpiScale;
2639
+ return [wt[0] * s, wt[1] * s, wt[2] * s, wt[3] * s, wt[4] * s, wt[5] * s];
2122
2640
  }
2123
- function resetClipState(rCtx) {
2124
- if (rCtx.clipSaveDepth > 0) {
2125
- rCtx.ctx.restore();
2126
- rCtx.clipSaveDepth = 0;
2127
- }
2641
+ function transformedRectShape(x, y, w, h, m) {
2642
+ const tx = (px, py) => m[0] * px + m[2] * py + m[4];
2643
+ const ty = (px, py) => m[1] * px + m[3] * py + m[5];
2644
+ const cmds = [
2645
+ { op: "moveTo", x: tx(x, y), y: ty(x, y) },
2646
+ { op: "lineTo", x: tx(x + w, y), y: ty(x + w, y) },
2647
+ { op: "lineTo", x: tx(x + w, y + h), y: ty(x + w, y + h) },
2648
+ { op: "lineTo", x: tx(x, y + h), y: ty(x, y + h) },
2649
+ { op: "closePath" }
2650
+ ];
2651
+ return { cmds, fillRule: "nonzero", simple: true };
2128
2652
  }
2129
- function applyClipCombineMode(rCtx, combineMode, opName, clipFn) {
2130
- switch (combineMode) {
2131
- case 0:
2132
- resetClipState(rCtx);
2133
- ensureClipSave(rCtx);
2134
- clipFn();
2135
- return true;
2136
- case 1:
2137
- ensureClipSave(rCtx);
2138
- clipFn();
2139
- return true;
2140
- case 2:
2141
- ensureClipSave(rCtx);
2142
- clipFn();
2143
- return true;
2144
- case 3:
2145
- return false;
2146
- case 4:
2147
- return false;
2148
- case 5:
2149
- return false;
2150
- default:
2151
- ensureClipSave(rCtx);
2152
- clipFn();
2153
- return true;
2154
- }
2653
+ function pathClipShape(path, m) {
2654
+ return { cmds: emfPlusPathToClipCmds(path.path, m), fillRule: "nonzero", simple: true };
2155
2655
  }
2156
- var MAX_REGION_TRACE_DEPTH = 64;
2157
- function traceRegionNodePath(ctx, node, depth = 0) {
2158
- if (depth > MAX_REGION_TRACE_DEPTH) {
2159
- ctx.rect(0, 0, 0, 0);
2160
- return;
2656
+ var REGION_NODE_OPS = {
2657
+ 0: "intersect",
2658
+ // legacy/lenient: treat 0 as And
2659
+ 1: "intersect",
2660
+ // RegionNodeDataTypeAnd
2661
+ 2: "union",
2662
+ // RegionNodeDataTypeOr
2663
+ 3: "xor",
2664
+ // RegionNodeDataTypeXor
2665
+ 4: "exclude",
2666
+ // RegionNodeDataTypeExclude
2667
+ 5: "complement"
2668
+ // RegionNodeDataTypeComplement
2669
+ };
2670
+ var MAX_REGION_FLATTEN_DEPTH = 64;
2671
+ function flattenRegionNode(node, m, depth = 0) {
2672
+ if (depth > MAX_REGION_FLATTEN_DEPTH) {
2673
+ return { region: [emptyClipShape()], exact: false };
2161
2674
  }
2162
2675
  switch (node.type) {
2163
2676
  case "rect":
2164
- ctx.rect(node.x, node.y, node.width, node.height);
2165
- break;
2677
+ return { region: [transformedRectShape(node.x, node.y, node.width, node.height, m)], exact: true };
2166
2678
  case "path":
2167
- replayEmfPlusPath(ctx, node.path);
2168
- break;
2679
+ return { region: [pathClipShape(node, m)], exact: true };
2169
2680
  case "infinite":
2170
- ctx.rect(-1e6, -1e6, 2e6, 2e6);
2171
- break;
2681
+ return { region: null, exact: true };
2172
2682
  case "empty":
2173
- ctx.rect(0, 0, 0, 0);
2174
- break;
2175
- case "combine":
2176
- traceRegionNodePath(ctx, node.left, depth + 1);
2177
- if (node.combineMode !== 0) {
2178
- emfWarn(
2179
- `traceRegionNodePath: combine mode ${node.combineMode} not fully supported, using left subtree only`
2180
- );
2181
- } else {
2182
- try {
2183
- ctx.clip();
2184
- } catch {
2185
- }
2186
- ctx.beginPath();
2187
- traceRegionNodePath(ctx, node.right, depth + 1);
2188
- }
2189
- break;
2683
+ return { region: [emptyClipShape()], exact: true };
2684
+ case "combine": {
2685
+ const left = flattenRegionNode(node.left, m, depth + 1);
2686
+ const right = flattenRegionNode(node.right, m, depth + 1);
2687
+ const op = REGION_NODE_OPS[node.combineMode] ?? "intersect";
2688
+ const combined = combineClipRegions(left.region, right.region, op);
2689
+ return { region: combined.region, exact: combined.exact && left.exact && right.exact };
2690
+ }
2190
2691
  }
2191
2692
  }
2192
- function applyRegionNodeClip(ctx, rootNode, combineMode) {
2193
- ctx.beginPath();
2194
- traceRegionNodePath(ctx, rootNode);
2195
- try {
2196
- ctx.clip();
2197
- } catch {
2198
- }
2693
+ var PLUS_COMBINE_OPS = {
2694
+ 0: "replace",
2695
+ 1: "intersect",
2696
+ 2: "union",
2697
+ 3: "xor",
2698
+ 4: "exclude",
2699
+ 5: "complement"
2700
+ };
2701
+ function reapplyPlusClip(rCtx) {
2702
+ reapplyClipRegion(rCtx, rCtx.clipRegion ?? null, true);
2703
+ }
2704
+ function applyPlusClipRegion(rCtx, incoming, combineMode, opName) {
2705
+ const op = PLUS_COMBINE_OPS[combineMode];
2706
+ const res = combineClipRegions(rCtx.clipRegion ?? null, incoming, op ?? "intersect");
2707
+ if (!res.exact) ;
2708
+ rCtx.clipRegion = res.region;
2709
+ reapplyPlusClip(rCtx);
2710
+ }
2711
+ function applyPlusClipShape(rCtx, shape, combineMode, opName) {
2712
+ applyPlusClipRegion(rCtx, [shape], combineMode);
2199
2713
  }
2200
2714
  function handleEmfPlusStateRecord(rCtx, recType, recFlags, dataOff, recDataSize) {
2201
2715
  const { view } = rCtx;
@@ -2297,30 +2811,24 @@ function handleEmfPlusStateRecord(rCtx, recType, recFlags, dataOff, recDataSize)
2297
2811
  const cy = view.getFloat32(dataOff + 4, true);
2298
2812
  const cw = view.getFloat32(dataOff + 8, true);
2299
2813
  const ch = view.getFloat32(dataOff + 12, true);
2300
- applyClipCombineMode(rCtx, combineMode, "SetClipRect", () => {
2301
- applyPlusWorldTransform(rCtx);
2302
- rCtx.ctx.beginPath();
2303
- rCtx.ctx.rect(cx, cy, cw, ch);
2304
- try {
2305
- rCtx.ctx.clip();
2306
- } catch {
2307
- }
2308
- });
2814
+ const shape = transformedRectShape(cx, cy, cw, ch, plusDeviceMatrix(rCtx));
2815
+ applyPlusClipShape(rCtx, shape, combineMode);
2309
2816
  }
2310
2817
  return true;
2311
2818
  }
2312
2819
  case EMFPLUS_RESETCLIP: {
2313
- resetClipState(rCtx);
2314
- ensureClipSave(rCtx);
2820
+ rCtx.clipRegion = null;
2821
+ reapplyPlusClip(rCtx);
2315
2822
  return true;
2316
2823
  }
2317
2824
  case EMFPLUS_SETCLIPREGION: {
2318
2825
  const regionId = recFlags & 255;
2826
+ const combineMode = recFlags >> 8 & 15;
2319
2827
  const regionObj = rCtx.objectTable.get(regionId);
2320
2828
  if (regionObj && regionObj.kind === "plus-region" && regionObj.nodes.length > 0) {
2321
- applyPlusWorldTransform(rCtx);
2322
- const rootNode = regionObj.nodes[0];
2323
- applyRegionNodeClip(rCtx.ctx, rootNode);
2829
+ const flattened = flattenRegionNode(regionObj.nodes[0], plusDeviceMatrix(rCtx));
2830
+ if (!flattened.exact) ;
2831
+ applyPlusClipRegion(rCtx, flattened.region, combineMode);
2324
2832
  }
2325
2833
  return true;
2326
2834
  }
@@ -2329,22 +2837,26 @@ function handleEmfPlusStateRecord(rCtx, recType, recFlags, dataOff, recDataSize)
2329
2837
  const combineMode = recFlags >> 8 & 15;
2330
2838
  const pathObj = rCtx.objectTable.get(pathId);
2331
2839
  if (pathObj && pathObj.kind === "plus-path") {
2332
- applyClipCombineMode(rCtx, combineMode, "SetClipPath", () => {
2333
- applyPlusWorldTransform(rCtx);
2334
- rCtx.ctx.beginPath();
2335
- replayEmfPlusPath(rCtx.ctx, pathObj);
2336
- try {
2337
- rCtx.ctx.clip();
2338
- } catch {
2339
- }
2340
- });
2840
+ const shape = {
2841
+ cmds: emfPlusPathToClipCmds(pathObj, plusDeviceMatrix(rCtx)),
2842
+ fillRule: "nonzero",
2843
+ simple: true
2844
+ };
2845
+ applyPlusClipShape(rCtx, shape, combineMode);
2341
2846
  }
2342
2847
  return true;
2343
2848
  }
2344
2849
  case EMFPLUS_OFFSETCLIP: {
2345
2850
  if (recDataSize >= 8) {
2346
- view.getFloat32(dataOff, true);
2347
- view.getFloat32(dataOff + 4, true);
2851
+ const dx = view.getFloat32(dataOff, true);
2852
+ const dy = view.getFloat32(dataOff + 4, true);
2853
+ if (rCtx.clipRegion) {
2854
+ const m = plusDeviceMatrix(rCtx);
2855
+ const ddx = m[0] * dx + m[2] * dy;
2856
+ const ddy = m[1] * dx + m[3] * dy;
2857
+ rCtx.clipRegion = translateClipRegion(rCtx.clipRegion, ddx, ddy);
2858
+ reapplyPlusClip(rCtx);
2859
+ }
2348
2860
  }
2349
2861
  return true;
2350
2862
  }
@@ -2417,7 +2929,7 @@ function handleEmfPlusDrawRecord(rCtx, recType, recFlags, dataOff, recDataSize)
2417
2929
  const count = view.getUint32(dataOff + 4, true);
2418
2930
  const compressed = (recFlags & 16384) !== 0;
2419
2931
  const rectSize = compressed ? 8 : 16;
2420
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
2932
+ ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
2421
2933
  applyPlusWorldTransform(rCtx);
2422
2934
  let rOff = dataOff + 8;
2423
2935
  for (let i = 0; i < count && rOff + rectSize <= dataOff + recDataSize; i++) {
@@ -2467,7 +2979,7 @@ function handleEmfPlusDrawRecord(rCtx, recType, recFlags, dataOff, recDataSize)
2467
2979
  w = view.getFloat32(dataOff + 12, true);
2468
2980
  h = view.getFloat32(dataOff + 16, true);
2469
2981
  }
2470
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
2982
+ ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
2471
2983
  applyPlusWorldTransform(rCtx);
2472
2984
  ctx.beginPath();
2473
2985
  ctx.ellipse(x + w / 2, y + h / 2, Math.abs(w) / 2, Math.abs(h) / 2, 0, 0, Math.PI * 2);
@@ -2513,7 +3025,7 @@ function handleEmfPlusDrawRecord(rCtx, recType, recFlags, dataOff, recDataSize)
2513
3025
  let aOff = dataOff;
2514
3026
  if (isFill) {
2515
3027
  const brushVal = view.getUint32(aOff, true);
2516
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
3028
+ ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
2517
3029
  aOff += 4;
2518
3030
  }
2519
3031
  const startAngle = view.getFloat32(aOff, true) * Math.PI / 180;
@@ -2594,7 +3106,7 @@ function handleEmfPlusDrawRecord(rCtx, recType, recFlags, dataOff, recDataSize)
2594
3106
  const count = view.getUint32(dataOff + 4, true);
2595
3107
  const compressed = (recFlags & 16384) !== 0;
2596
3108
  const ptSize = compressed ? 4 : 8;
2597
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
3109
+ ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
2598
3110
  applyPlusWorldTransform(rCtx);
2599
3111
  ctx.beginPath();
2600
3112
  let pOff = dataOff + 8;
@@ -2617,6 +3129,261 @@ function handleEmfPlusDrawRecord(rCtx, recType, recFlags, dataOff, recDataSize)
2617
3129
  }
2618
3130
  }
2619
3131
 
3132
+ // src/emf-plus-brush-parser.ts
3133
+ var BRUSH_DATA_PATH = 1;
3134
+ var BRUSH_DATA_TRANSFORM = 2;
3135
+ var BRUSH_DATA_PRESET_COLORS = 4;
3136
+ var BRUSH_DATA_BLEND_FACTORS_H = 8;
3137
+ var MAX_GRADIENT_ELEMENTS = 4096;
3138
+ function looksLikeGraphicsVersion(v) {
3139
+ return v >>> 12 === 900097;
3140
+ }
3141
+ function readTransform(view, off) {
3142
+ return [
3143
+ view.getFloat32(off, true),
3144
+ view.getFloat32(off + 4, true),
3145
+ view.getFloat32(off + 8, true),
3146
+ view.getFloat32(off + 12, true),
3147
+ view.getFloat32(off + 16, true),
3148
+ view.getFloat32(off + 20, true)
3149
+ ];
3150
+ }
3151
+ function applyMatrix(m, x, y) {
3152
+ return { x: m[0] * x + m[2] * y + m[4], y: m[1] * x + m[3] * y + m[5] };
3153
+ }
3154
+ function clamp01(v) {
3155
+ return Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : 0;
3156
+ }
3157
+ function normaliseStops(stops) {
3158
+ return stops.map((s) => ({ offset: clamp01(s.offset), color: s.color })).sort((a, b) => a.offset - b.offset);
3159
+ }
3160
+ function readPresetColors(view, off, end) {
3161
+ if (off + 4 > end) {
3162
+ return null;
3163
+ }
3164
+ const count = view.getUint32(off, true);
3165
+ if (count === 0 || count > MAX_GRADIENT_ELEMENTS) {
3166
+ return null;
3167
+ }
3168
+ const posOff = off + 4;
3169
+ const colOff = posOff + count * 4;
3170
+ const next = colOff + count * 4;
3171
+ if (next > end) {
3172
+ return null;
3173
+ }
3174
+ const stops = [];
3175
+ for (let i = 0; i < count; i++) {
3176
+ stops.push({
3177
+ offset: view.getFloat32(posOff + i * 4, true),
3178
+ color: argbToRgba(view.getUint32(colOff + i * 4, true))
3179
+ });
3180
+ }
3181
+ return { stops, next };
3182
+ }
3183
+ function readBlendFactors(view, off, end) {
3184
+ if (off + 4 > end) {
3185
+ return null;
3186
+ }
3187
+ const count = view.getUint32(off, true);
3188
+ if (count === 0 || count > MAX_GRADIENT_ELEMENTS) {
3189
+ return null;
3190
+ }
3191
+ const posOff = off + 4;
3192
+ const facOff = posOff + count * 4;
3193
+ const next = facOff + count * 4;
3194
+ if (next > end) {
3195
+ return null;
3196
+ }
3197
+ const entries = [];
3198
+ for (let i = 0; i < count; i++) {
3199
+ entries.push({
3200
+ pos: view.getFloat32(posOff + i * 4, true),
3201
+ factor: view.getFloat32(facOff + i * 4, true)
3202
+ });
3203
+ }
3204
+ return { entries, next };
3205
+ }
3206
+ function parseLinearGradient(view, b, end) {
3207
+ if (b + 40 > end) {
3208
+ return null;
3209
+ }
3210
+ const flags = view.getUint32(b, true);
3211
+ const rx = view.getFloat32(b + 8, true);
3212
+ const ry = view.getFloat32(b + 12, true);
3213
+ const rw = view.getFloat32(b + 16, true);
3214
+ const rh = view.getFloat32(b + 20, true);
3215
+ const startArgb = view.getUint32(b + 24, true);
3216
+ const endArgb = view.getUint32(b + 28, true);
3217
+ let o = b + 40;
3218
+ let transform = null;
3219
+ if (flags & BRUSH_DATA_TRANSFORM && o + 24 <= end) {
3220
+ transform = readTransform(view, o);
3221
+ o += 24;
3222
+ }
3223
+ let stops = [
3224
+ { offset: 0, color: argbToRgba(startArgb) },
3225
+ { offset: 1, color: argbToRgba(endArgb) }
3226
+ ];
3227
+ if (flags & BRUSH_DATA_PRESET_COLORS) {
3228
+ const preset = readPresetColors(view, o, end);
3229
+ if (preset) {
3230
+ stops = preset.stops;
3231
+ }
3232
+ } else if (flags & BRUSH_DATA_BLEND_FACTORS_H) {
3233
+ const blend = readBlendFactors(view, o, end);
3234
+ if (blend) {
3235
+ stops = blend.entries.map((e) => ({
3236
+ offset: e.pos,
3237
+ color: lerpArgbToRgba(startArgb, endArgb, e.factor)
3238
+ }));
3239
+ }
3240
+ }
3241
+ let p1 = { x: rx, y: ry + rh / 2 };
3242
+ let p2 = { x: rx + rw, y: ry + rh / 2 };
3243
+ if (transform) {
3244
+ p1 = applyMatrix(transform, p1.x, p1.y);
3245
+ p2 = applyMatrix(transform, p2.x, p2.y);
3246
+ }
3247
+ emfLog(
3248
+ `parseEmfPlusBrushObject: linear gradient (${p1.x.toFixed(1)},${p1.y.toFixed(1)})\u2192(${p2.x.toFixed(1)},${p2.y.toFixed(1)}), ${stops.length} stop(s)`
3249
+ );
3250
+ return {
3251
+ kind: "plus-brush",
3252
+ color: argbToRgba(startArgb),
3253
+ gradient: {
3254
+ type: "linear",
3255
+ x1: p1.x,
3256
+ y1: p1.y,
3257
+ x2: p2.x,
3258
+ y2: p2.y,
3259
+ stops: normaliseStops(stops)
3260
+ }
3261
+ };
3262
+ }
3263
+ function parsePathGradient(view, b, end) {
3264
+ if (b + 24 > end) {
3265
+ return null;
3266
+ }
3267
+ const flags = view.getUint32(b, true);
3268
+ const centerArgb = view.getUint32(b + 8, true);
3269
+ let cx = view.getFloat32(b + 12, true);
3270
+ let cy = view.getFloat32(b + 16, true);
3271
+ const surroundCount = view.getUint32(b + 20, true);
3272
+ if (surroundCount > MAX_GRADIENT_ELEMENTS) {
3273
+ return { kind: "plus-brush", color: argbToRgba(centerArgb) };
3274
+ }
3275
+ const surround = [];
3276
+ let o = b + 24;
3277
+ for (let i = 0; i < surroundCount && o + 4 <= end; i++) {
3278
+ surround.push(view.getUint32(o, true));
3279
+ o += 4;
3280
+ }
3281
+ let boundaryPts = [];
3282
+ if (flags & BRUSH_DATA_PATH) {
3283
+ if (o + 4 <= end) {
3284
+ const pathSize = view.getInt32(o, true);
3285
+ o += 4;
3286
+ if (pathSize > 0 && o + pathSize <= end) {
3287
+ const path = parseEmfPlusPath(view, o, pathSize);
3288
+ if (path) {
3289
+ boundaryPts = path.points;
3290
+ }
3291
+ o += pathSize;
3292
+ }
3293
+ }
3294
+ } else if (o + 4 <= end) {
3295
+ const ptCount = view.getUint32(o, true);
3296
+ o += 4;
3297
+ if (ptCount > 0 && ptCount <= MAX_GRADIENT_ELEMENTS && o + ptCount * 8 <= end) {
3298
+ for (let i = 0; i < ptCount; i++) {
3299
+ boundaryPts.push({
3300
+ x: view.getFloat32(o + i * 8, true),
3301
+ y: view.getFloat32(o + i * 8 + 4, true)
3302
+ });
3303
+ }
3304
+ o += ptCount * 8;
3305
+ }
3306
+ }
3307
+ let transform = null;
3308
+ if (flags & BRUSH_DATA_TRANSFORM && o + 24 <= end) {
3309
+ transform = readTransform(view, o);
3310
+ o += 24;
3311
+ }
3312
+ if (transform) {
3313
+ ({ x: cx, y: cy } = applyMatrix(transform, cx, cy));
3314
+ boundaryPts = boundaryPts.map((p) => applyMatrix(transform, p.x, p.y));
3315
+ }
3316
+ const surroundArgb = surround.length > 0 ? surround[0] : centerArgb;
3317
+ let stops = [
3318
+ { offset: 0, color: argbToRgba(centerArgb) },
3319
+ { offset: 1, color: argbToRgba(surroundArgb) }
3320
+ ];
3321
+ if (flags & BRUSH_DATA_PRESET_COLORS) {
3322
+ const preset = readPresetColors(view, o, end);
3323
+ if (preset) {
3324
+ stops = preset.stops.map((s) => ({ offset: 1 - s.offset, color: s.color }));
3325
+ }
3326
+ } else if (flags & BRUSH_DATA_BLEND_FACTORS_H) {
3327
+ const blend = readBlendFactors(view, o, end);
3328
+ if (blend) {
3329
+ stops = blend.entries.map((e) => ({
3330
+ offset: 1 - e.pos,
3331
+ color: lerpArgbToRgba(surroundArgb, centerArgb, e.factor)
3332
+ }));
3333
+ }
3334
+ }
3335
+ let r = 0;
3336
+ for (const p of boundaryPts) {
3337
+ const d = Math.hypot(p.x - cx, p.y - cy);
3338
+ if (d > r) {
3339
+ r = d;
3340
+ }
3341
+ }
3342
+ if (!(r > 0)) {
3343
+ return { kind: "plus-brush", color: argbToRgba(centerArgb) };
3344
+ }
3345
+ emfLog(
3346
+ `parseEmfPlusBrushObject: path gradient centre=(${cx.toFixed(1)},${cy.toFixed(1)}), r=${r.toFixed(1)}, ${stops.length} stop(s)`
3347
+ );
3348
+ return {
3349
+ kind: "plus-brush",
3350
+ color: argbToRgba(centerArgb),
3351
+ gradient: { type: "radial", cx, cy, r, stops: normaliseStops(stops) }
3352
+ };
3353
+ }
3354
+ function parseEmfPlusBrushObject(view, dataOff, recDataSize) {
3355
+ if (recDataSize < 8) {
3356
+ return null;
3357
+ }
3358
+ const end = dataOff + recDataSize;
3359
+ const hasVersion = looksLikeGraphicsVersion(view.getUint32(dataOff, true));
3360
+ const typeOff = dataOff + (hasVersion ? 4 : 0);
3361
+ if (typeOff + 8 > end) {
3362
+ return null;
3363
+ }
3364
+ const brushType = view.getUint32(typeOff, true);
3365
+ const b = typeOff + 4;
3366
+ switch (brushType) {
3367
+ case EMFPLUS_BRUSHTYPE_SOLID:
3368
+ return { kind: "plus-brush", color: argbToRgba(view.getUint32(b, true)) };
3369
+ case EMFPLUS_BRUSHTYPE_HATCHFILL:
3370
+ if (b + 8 <= end) {
3371
+ return { kind: "plus-brush", color: argbToRgba(view.getUint32(b + 4, true)) };
3372
+ }
3373
+ return { kind: "plus-brush", color: "rgba(0,0,0,1)" };
3374
+ case EMFPLUS_BRUSHTYPE_LINEARGRADIENT: {
3375
+ const brush = parseLinearGradient(view, b, end);
3376
+ return brush ?? { kind: "plus-brush", color: "rgba(0,0,0,1)" };
3377
+ }
3378
+ case EMFPLUS_BRUSHTYPE_PATHGRADIENT: {
3379
+ const brush = parsePathGradient(view, b, end);
3380
+ return brush ?? { kind: "plus-brush", color: "rgba(0,0,0,1)" };
3381
+ }
3382
+ default:
3383
+ return { kind: "plus-brush", color: "rgba(0,0,0,1)" };
3384
+ }
3385
+ }
3386
+
2620
3387
  // src/emf-plus-bitmap-decoder.ts
2621
3388
  var PIXELFORMAT_24BPP_RGB = 137224;
2622
3389
  var PIXELFORMAT_32BPP_RGB = 139273;
@@ -2726,7 +3493,8 @@ function parseEmfPlusPenObject(view, dataOff, recDataSize) {
2726
3493
  if (recDataSize < 20) {
2727
3494
  return null;
2728
3495
  }
2729
- const penFlags = view.getUint32(dataOff + 4, true);
3496
+ const hasVersion = looksLikeGraphicsVersion(view.getUint32(dataOff, true));
3497
+ const penFlags = view.getUint32(dataOff + (hasVersion ? 8 : 4), true);
2730
3498
  const penWidth = view.getFloat32(dataOff + 16, true);
2731
3499
  let brushOff = dataOff + 20;
2732
3500
  const flagSizes = [
@@ -2786,9 +3554,9 @@ function parseEmfPlusPenObject(view, dataOff, recDataSize) {
2786
3554
  }
2787
3555
  let penColor = "rgba(0,0,0,1)";
2788
3556
  if (brushOff + 8 <= dataOff + recDataSize) {
2789
- const penBrushType = view.getUint32(brushOff, true);
2790
- if (penBrushType === EMFPLUS_BRUSHTYPE_SOLID && brushOff + 8 <= dataOff + recDataSize) {
2791
- penColor = argbToRgba(view.getUint32(brushOff + 4, true));
3557
+ const brush = parseEmfPlusBrushObject(view, brushOff, dataOff + recDataSize - brushOff);
3558
+ if (brush) {
3559
+ penColor = brush.color;
2792
3560
  }
2793
3561
  }
2794
3562
  return { kind: "plus-pen", color: penColor, width: penWidth || 1, dashStyle };
@@ -2884,19 +3652,9 @@ function handleEmfPlusObjectRecord(rCtx, recFlags, dataOff, recDataSize) {
2884
3652
  // Brush
2885
3653
  // ---------------------------------------------------------------
2886
3654
  case EMFPLUS_OBJECTTYPE_BRUSH: {
2887
- if (recDataSize >= 8) {
2888
- const brushType = view.getUint32(dataOff, true);
2889
- let color = "rgba(0,0,0,1)";
2890
- if (brushType === EMFPLUS_BRUSHTYPE_SOLID && recDataSize >= 8) {
2891
- color = argbToRgba(view.getUint32(dataOff + 4, true));
2892
- } else if (brushType === EMFPLUS_BRUSHTYPE_LINEARGRADIENT && recDataSize >= 48) {
2893
- color = argbToRgba(view.getUint32(dataOff + 40, true));
2894
- } else if (brushType === EMFPLUS_BRUSHTYPE_PATHGRADIENT && recDataSize >= 12) {
2895
- color = argbToRgba(view.getUint32(dataOff + 8, true));
2896
- } else if (brushType === EMFPLUS_BRUSHTYPE_HATCHFILL && recDataSize >= 12) {
2897
- color = argbToRgba(view.getUint32(dataOff + 8, true));
2898
- }
2899
- objectTable.set(objectId, { kind: "plus-brush", color });
3655
+ const brush = parseEmfPlusBrushObject(view, dataOff, recDataSize);
3656
+ if (brush) {
3657
+ objectTable.set(objectId, brush);
2900
3658
  }
2901
3659
  break;
2902
3660
  }
@@ -2992,7 +3750,7 @@ function parseRegionNode(view, off, endOff, depth = 0) {
2992
3750
  }
2993
3751
  const nodeType = view.getUint32(off, true);
2994
3752
  let cursor = off + 4;
2995
- if (nodeType <= 4) {
3753
+ if (nodeType <= 5) {
2996
3754
  const leftResult = parseRegionNode(view, cursor, endOff, depth + 1);
2997
3755
  if (!leftResult) {
2998
3756
  return null;
@@ -3081,7 +3839,7 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
3081
3839
  const pathId = recFlags & 255;
3082
3840
  const pathObj = objectTable.get(pathId);
3083
3841
  if (pathObj && pathObj.kind === "plus-path") {
3084
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
3842
+ ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
3085
3843
  applyPlusWorldTransform(rCtx);
3086
3844
  replayEmfPlusPath(ctx, pathObj);
3087
3845
  ctx.fill();
@@ -3124,8 +3882,9 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
3124
3882
  if (text.length > 0 && font && font.kind === "plus-font") {
3125
3883
  const bold = font.flags & 1 ? "bold " : "";
3126
3884
  const italic = font.flags & 2 ? "italic " : "";
3127
- ctx.font = `${italic}${bold}${font.emSize}px ${font.family}`;
3128
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
3885
+ const family = mapFontFamily(font.family, rCtx.fontFamilyMap);
3886
+ ctx.font = `${italic}${bold}${font.emSize}px ${family}`;
3887
+ ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
3129
3888
  ctx.textBaseline = "top";
3130
3889
  const sf = objectTable.get(formatId);
3131
3890
  if (sf && sf.kind === "plus-stringformat") {
@@ -3163,8 +3922,9 @@ function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataS
3163
3922
  if (text.length > 0) {
3164
3923
  const bold = font.flags & 1 ? "bold " : "";
3165
3924
  const italic = font.flags & 2 ? "italic " : "";
3166
- ctx.font = `${italic}${bold}${font.emSize}px ${font.family}`;
3167
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
3925
+ const family = mapFontFamily(font.family, rCtx.fontFamilyMap);
3926
+ ctx.font = `${italic}${bold}${font.emSize}px ${family}`;
3927
+ ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
3168
3928
  ctx.textBaseline = "alphabetic";
3169
3929
  ctx.textAlign = "left";
3170
3930
  applyPlusWorldTransform(rCtx);
@@ -3329,7 +4089,7 @@ var EMFPLUS_REC_NAMES = {
3329
4089
  16424: "BeginContainerNoParams",
3330
4090
  16425: "EndContainer"
3331
4091
  };
3332
- function replayEmfPlusRecords(view, offset, length, ctx, _canvasW, _canvasH, state, dpiScale = 1) {
4092
+ function replayEmfPlusRecords(view, offset, length, ctx, _canvasW, _canvasH, state, dpiScale = 1, maxRecords = MAX_RECORDS_EMFPLUS_DEFAULT, fontFamilyMap) {
3333
4093
  const s = state ?? createEmfPlusState();
3334
4094
  const rCtx = {
3335
4095
  ctx,
@@ -3341,7 +4101,8 @@ function replayEmfPlusRecords(view, offset, length, ctx, _canvasW, _canvasH, sta
3341
4101
  saveIdMap: s.saveIdMap,
3342
4102
  totalImageObjects: 0,
3343
4103
  totalDrawImageCalls: 0,
3344
- clipSaveDepth: 0,
4104
+ clipSaveDepth: s.clipSaveDepth,
4105
+ clipRegion: s.clipRegion,
3345
4106
  pageUnit: 2,
3346
4107
  pageScale: 1,
3347
4108
  continuationBuffer: null,
@@ -3349,10 +4110,10 @@ function replayEmfPlusRecords(view, offset, length, ctx, _canvasW, _canvasH, sta
3349
4110
  continuationObjectType: 0,
3350
4111
  continuationTotalSize: 0,
3351
4112
  continuationOffset: 0,
3352
- dpiScale
4113
+ dpiScale,
4114
+ fontFamilyMap
3353
4115
  };
3354
4116
  const end = offset + length;
3355
- const maxRecords = 5e5;
3356
4117
  let recordCount = 0;
3357
4118
  const emfPlusRecordTypes = /* @__PURE__ */ new Map();
3358
4119
  emfLog(`replayEmfPlusRecords: offset=0x${offset.toString(16)}, length=${length}`);
@@ -3475,6 +4236,8 @@ function replayEmfPlusRecords(view, offset, length, ctx, _canvasW, _canvasH, sta
3475
4236
  if (state) {
3476
4237
  state.worldTransform = rCtx.worldTransform;
3477
4238
  state.saveIdMap = rCtx.saveIdMap;
4239
+ state.clipRegion = rCtx.clipRegion ?? null;
4240
+ state.clipSaveDepth = rCtx.clipSaveDepth;
3478
4241
  }
3479
4242
  ctx.setTransform(1, 0, 0, 1, 0, 0);
3480
4243
  return rCtx.deferredImages;
@@ -3512,12 +4275,14 @@ var GDI_NAMES = {
3512
4275
  88: "EMR_POLYBEZIERTO16",
3513
4276
  91: "EMR_POLYPOLYGON16"
3514
4277
  };
3515
- function replayEmfRecords(view, ctx, bounds, canvasW, canvasH, dpiScale = 1) {
4278
+ function replayEmfRecords(view, ctx, bounds, canvasW, canvasH, dpiScale = 1, replayOptions = {}) {
3516
4279
  emfLog(
3517
4280
  `replayEmfRecords: bounds=(${bounds.left},${bounds.top})\u2192(${bounds.right},${bounds.bottom}), canvas=${canvasW}\xD7${canvasH}`
3518
4281
  );
3519
4282
  const allDeferredImages = [];
3520
4283
  const emfPlusState = createEmfPlusState();
4284
+ const maxRecords = replayOptions.maxRecords ?? MAX_RECORDS_DEFAULT;
4285
+ const maxRecordsEmfPlus = replayOptions.maxRecordsEmfPlus ?? MAX_RECORDS_EMFPLUS_DEFAULT;
3521
4286
  const logicalW = bounds.right - bounds.left || 1;
3522
4287
  const logicalH = bounds.bottom - bounds.top || 1;
3523
4288
  const sx = canvasW / logicalW;
@@ -3529,7 +4294,7 @@ function replayEmfRecords(view, ctx, bounds, canvasW, canvasH, dpiScale = 1) {
3529
4294
  ctx,
3530
4295
  view,
3531
4296
  objectTable: /* @__PURE__ */ new Map(),
3532
- state: defaultState(),
4297
+ state: { ...defaultState(), fontFamilyMap: replayOptions.fontFamilyMap },
3533
4298
  stateStack: [],
3534
4299
  inPath: false,
3535
4300
  windowOrg: { x: bounds.left, y: bounds.top },
@@ -3546,7 +4311,6 @@ function replayEmfRecords(view, ctx, bounds, canvasW, canvasH, dpiScale = 1) {
3546
4311
  };
3547
4312
  let offset = 0;
3548
4313
  const maxOffset = view.byteLength;
3549
- const maxRecords = 2e5;
3550
4314
  let recordCount = 0;
3551
4315
  let emfPlusCommentCount = 0;
3552
4316
  const gdiRecordTypes = /* @__PURE__ */ new Map();
@@ -3576,7 +4340,9 @@ function replayEmfRecords(view, ctx, bounds, canvasW, canvasH, dpiScale = 1) {
3576
4340
  canvasW,
3577
4341
  canvasH,
3578
4342
  emfPlusState,
3579
- dpiScale
4343
+ dpiScale,
4344
+ maxRecordsEmfPlus,
4345
+ replayOptions.fontFamilyMap
3580
4346
  );
3581
4347
  emfLog(
3582
4348
  `replayEmfRecords: EMF+ comment #${emfPlusCommentCount} returned ${deferred.length} deferred images`
@@ -3886,7 +4652,7 @@ function createWmfCoord(windowOrg, windowExt, canvasW, canvasH) {
3886
4652
  mh: (h) => h / (windowExt.cy || 1) * canvasH
3887
4653
  };
3888
4654
  }
3889
- function replayWmfRecords(view, ctx, header, canvasW, canvasH) {
4655
+ function replayWmfRecords(view, ctx, header, canvasW, canvasH, replayOptions = {}) {
3890
4656
  const logicalW = header.boundsRight - header.boundsLeft || 1;
3891
4657
  const logicalH = header.boundsBottom - header.boundsTop || 1;
3892
4658
  const windowOrg = { x: header.boundsLeft, y: header.boundsTop };
@@ -3900,12 +4666,12 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH) {
3900
4666
  }
3901
4667
  return slot;
3902
4668
  };
3903
- const state = defaultState();
4669
+ const state = { ...defaultState(), fontFamilyMap: replayOptions.fontFamilyMap };
3904
4670
  const stateStack = [];
3905
4671
  const wCtx = { view, ctx, state, coord };
3906
4672
  let offset = header.headerSize;
3907
4673
  const maxOffset = view.byteLength;
3908
- const maxRecords = 2e5;
4674
+ const maxRecords = replayOptions.maxRecords ?? MAX_RECORDS_DEFAULT;
3909
4675
  let recordCount = 0;
3910
4676
  while (offset + 6 <= maxOffset && recordCount < maxRecords) {
3911
4677
  const recSizeWords = view.getUint32(offset, true);
@@ -3962,6 +4728,9 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH) {
3962
4728
  }
3963
4729
  break;
3964
4730
  case META_SETROP2:
4731
+ if (recSize >= 8) {
4732
+ state.rop2 = view.getUint16(dataOff, true);
4733
+ }
3965
4734
  break;
3966
4735
  case META_SETPOLYFILLMODE:
3967
4736
  if (recSize >= 8) {
@@ -4010,6 +4779,8 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH) {
4010
4779
  height: Math.abs(view.getInt16(dataOff, true)),
4011
4780
  weight: view.getInt16(dataOff + 8, true),
4012
4781
  italic: view.getUint8(dataOff + 10) !== 0,
4782
+ underline: view.getUint8(dataOff + 11) !== 0,
4783
+ strikeOut: view.getUint8(dataOff + 12) !== 0,
4013
4784
  family: family || "sans-serif"
4014
4785
  });
4015
4786
  }
@@ -4032,6 +4803,8 @@ function replayWmfRecords(view, ctx, header, canvasW, canvasH) {
4032
4803
  state.fontHeight = obj.height;
4033
4804
  state.fontWeight = obj.weight;
4034
4805
  state.fontItalic = obj.italic;
4806
+ state.fontUnderline = obj.underline;
4807
+ state.fontStrikeOut = obj.strikeOut;
4035
4808
  state.fontFamily = obj.family;
4036
4809
  break;
4037
4810
  }
@@ -4149,6 +4922,11 @@ async function convertEmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScal
4149
4922
  const dpiScale = opts.dpiScale ?? DEFAULT_DPI_SCALE;
4150
4923
  const effectiveMaxWidth = maxWidth ?? opts.maxWidth;
4151
4924
  const effectiveMaxHeight = maxHeight ?? opts.maxHeight;
4925
+ const replayOptions = {
4926
+ maxRecords: opts.maxRecords,
4927
+ maxRecordsEmfPlus: opts.maxRecords,
4928
+ fontFamilyMap: opts.fontFamilyMap
4929
+ };
4152
4930
  try {
4153
4931
  emfLog("=== convertEmfToDataUrl START ===");
4154
4932
  emfLog(
@@ -4174,7 +4952,14 @@ async function convertEmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScal
4174
4952
  const logicalW = renderBounds.right - renderBounds.left;
4175
4953
  const logicalH = renderBounds.bottom - renderBounds.top;
4176
4954
  emfLog(`convertEmfToDataUrl: logicalSize=${logicalW}\xD7${logicalH}`);
4177
- const setup = createCanvas(logicalW, logicalH, effectiveMaxWidth, effectiveMaxHeight, dpiScale);
4955
+ const setup = createCanvas(
4956
+ logicalW,
4957
+ logicalH,
4958
+ effectiveMaxWidth,
4959
+ effectiveMaxHeight,
4960
+ dpiScale,
4961
+ opts.maxCanvasDimension
4962
+ );
4178
4963
  if (!setup) {
4179
4964
  emfLog("convertEmfToDataUrl: createCanvas returned null \u2014 returning null");
4180
4965
  return null;
@@ -4191,7 +4976,8 @@ async function convertEmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScal
4191
4976
  renderBounds,
4192
4977
  canvas.width,
4193
4978
  canvas.height,
4194
- dpiScale
4979
+ dpiScale,
4980
+ replayOptions
4195
4981
  );
4196
4982
  emfLog(
4197
4983
  `convertEmfToDataUrl: replayEmfRecords returned ${deferredImages.length} deferred images`
@@ -4221,6 +5007,10 @@ async function convertWmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScal
4221
5007
  const dpiScale = opts.dpiScale ?? DEFAULT_DPI_SCALE;
4222
5008
  const effectiveMaxWidth = maxWidth ?? opts.maxWidth;
4223
5009
  const effectiveMaxHeight = maxHeight ?? opts.maxHeight;
5010
+ const replayOptions = {
5011
+ maxRecords: opts.maxRecords,
5012
+ fontFamilyMap: opts.fontFamilyMap
5013
+ };
4224
5014
  try {
4225
5015
  emfLog(
4226
5016
  "=== convertWmfToDataUrl START ===",
@@ -4239,13 +5029,20 @@ async function convertWmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScal
4239
5029
  emfLog("convertWmfToDataUrl: invalid dimensions \u2014 returning null");
4240
5030
  return null;
4241
5031
  }
4242
- const setup = createCanvas(logicalW, logicalH, effectiveMaxWidth, effectiveMaxHeight, dpiScale);
5032
+ const setup = createCanvas(
5033
+ logicalW,
5034
+ logicalH,
5035
+ effectiveMaxWidth,
5036
+ effectiveMaxHeight,
5037
+ dpiScale,
5038
+ opts.maxCanvasDimension
5039
+ );
4243
5040
  if (!setup) {
4244
5041
  return null;
4245
5042
  }
4246
5043
  const { canvas, ctx } = setup;
4247
5044
  ctx.save();
4248
- replayWmfRecords(view, ctx, header, canvas.width, canvas.height);
5045
+ replayWmfRecords(view, ctx, header, canvas.width, canvas.height, replayOptions);
4249
5046
  ctx.restore();
4250
5047
  const result = await exportCanvasToPngDataUrl(canvas);
4251
5048
  emfLog(`convertWmfToDataUrl: result=${result ? `dataUrl len=${result.length}` : "null"}`);