@mkabatek/pptx-viewer 1.5.4 → 1.5.11

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.
Files changed (47) hide show
  1. package/dist/index.js +3 -1
  2. package/dist/index.mjs +3 -1
  3. package/dist/viewer/index.js +3 -1
  4. package/dist/viewer/index.mjs +3 -1
  5. package/package.json +2 -9
  6. package/node_modules/emf-converter/LICENSE +0 -21
  7. package/node_modules/emf-converter/README.md +0 -629
  8. package/node_modules/emf-converter/dist/index.d.mts +0 -86
  9. package/node_modules/emf-converter/dist/index.d.ts +0 -86
  10. package/node_modules/emf-converter/dist/index.js +0 -4257
  11. package/node_modules/emf-converter/dist/index.mjs +0 -4253
  12. package/node_modules/emf-converter/package.json +0 -53
  13. package/node_modules/mtx-decompressor/LICENSE +0 -373
  14. package/node_modules/mtx-decompressor/README.md +0 -271
  15. package/node_modules/mtx-decompressor/dist/index.d.mts +0 -84
  16. package/node_modules/mtx-decompressor/dist/index.d.ts +0 -84
  17. package/node_modules/mtx-decompressor/dist/index.js +0 -1532
  18. package/node_modules/mtx-decompressor/dist/index.mjs +0 -1528
  19. package/node_modules/mtx-decompressor/package.json +0 -44
  20. package/node_modules/pptx-viewer-core/LICENSE +0 -21
  21. package/node_modules/pptx-viewer-core/NOTICE +0 -16
  22. package/node_modules/pptx-viewer-core/README.md +0 -1294
  23. package/node_modules/pptx-viewer-core/dist/SvgExporter-BtZczTlB.d.ts +0 -557
  24. package/node_modules/pptx-viewer-core/dist/SvgExporter-D4mBWJHE.d.mts +0 -557
  25. package/node_modules/pptx-viewer-core/dist/cli/index.d.mts +0 -150
  26. package/node_modules/pptx-viewer-core/dist/cli/index.d.ts +0 -150
  27. package/node_modules/pptx-viewer-core/dist/cli/index.js +0 -0
  28. package/node_modules/pptx-viewer-core/dist/cli/index.mjs +0 -0
  29. package/node_modules/pptx-viewer-core/dist/converter/index.d.mts +0 -48
  30. package/node_modules/pptx-viewer-core/dist/converter/index.d.ts +0 -48
  31. package/node_modules/pptx-viewer-core/dist/converter/index.js +0 -0
  32. package/node_modules/pptx-viewer-core/dist/converter/index.mjs +0 -0
  33. package/node_modules/pptx-viewer-core/dist/index.d.mts +0 -12744
  34. package/node_modules/pptx-viewer-core/dist/index.d.ts +0 -12744
  35. package/node_modules/pptx-viewer-core/dist/index.js +0 -66894
  36. package/node_modules/pptx-viewer-core/dist/index.mjs +0 -66420
  37. package/node_modules/pptx-viewer-core/dist/presentation-nZxgWvXq.d.mts +0 -5645
  38. package/node_modules/pptx-viewer-core/dist/presentation-nZxgWvXq.d.ts +0 -5645
  39. package/node_modules/pptx-viewer-core/dist/signature-inspection-status-BCUpfCQh.d.mts +0 -220
  40. package/node_modules/pptx-viewer-core/dist/signature-inspection-status-BCUpfCQh.d.ts +0 -220
  41. package/node_modules/pptx-viewer-core/dist/signature-node/index.d.mts +0 -177
  42. package/node_modules/pptx-viewer-core/dist/signature-node/index.d.ts +0 -177
  43. package/node_modules/pptx-viewer-core/dist/signature-node/index.js +0 -1206
  44. package/node_modules/pptx-viewer-core/dist/signature-node/index.mjs +0 -1143
  45. package/node_modules/pptx-viewer-core/dist/text-operations-DCTGMltY.d.mts +0 -134
  46. package/node_modules/pptx-viewer-core/dist/text-operations-DYmhoi7U.d.ts +0 -134
  47. package/node_modules/pptx-viewer-core/package.json +0 -96
@@ -1,4257 +0,0 @@
1
- 'use strict';
2
-
3
- // src/emf-logging.ts
4
- var emfLog = (...args) => {
5
- };
6
- var emfWarn = (...args) => {
7
- };
8
-
9
- // src/emf-canvas-helpers.ts
10
- var DEFAULT_DPI_SCALE = 1;
11
- function createCanvas(width, height, maxWidth, maxHeight, dpiScale = DEFAULT_DPI_SCALE) {
12
- const effectiveScale = Math.max(1, Math.min(dpiScale, 4));
13
- let w = Math.round(width * effectiveScale);
14
- let h = Math.round(height * effectiveScale);
15
- let scaleX = effectiveScale;
16
- let scaleY = effectiveScale;
17
- if (maxWidth && w > maxWidth) {
18
- const factor = maxWidth / w;
19
- w = maxWidth;
20
- h = Math.round(h * factor);
21
- scaleX *= factor;
22
- scaleY *= factor;
23
- }
24
- if (maxHeight && h > maxHeight) {
25
- const factor = maxHeight / h;
26
- w = Math.round(w * factor);
27
- h = maxHeight;
28
- scaleX *= factor;
29
- scaleY *= factor;
30
- }
31
- const clampedW = Math.max(1, Math.min(w, 8192));
32
- const clampedH = Math.max(1, Math.min(h, 8192));
33
- if (clampedW !== w || clampedH !== h) {
34
- console.warn(
35
- `[emf-converter] Canvas size clamped from ${w}\xD7${h} to ${clampedW}\xD7${clampedH}. Output may lose detail.`
36
- );
37
- }
38
- w = clampedW;
39
- h = clampedH;
40
- try {
41
- if (typeof OffscreenCanvas !== "undefined") {
42
- emfLog(
43
- `createCanvas: using OffscreenCanvas ${w}\xD7${h}, scale=(${scaleX.toFixed(3)},${scaleY.toFixed(3)})`
44
- );
45
- const canvas2 = new OffscreenCanvas(w, h);
46
- const ctx2 = canvas2.getContext("2d");
47
- if (!ctx2) {
48
- emfWarn('createCanvas: OffscreenCanvas.getContext("2d") returned null');
49
- return null;
50
- }
51
- return { canvas: canvas2, ctx: ctx2, scaleX, scaleY };
52
- }
53
- if (typeof document === "undefined") {
54
- emfWarn("createCanvas: no OffscreenCanvas and no document \u2014 cannot create canvas");
55
- return null;
56
- }
57
- emfLog(
58
- `createCanvas: using HTMLCanvasElement ${w}\xD7${h}, scale=(${scaleX.toFixed(3)},${scaleY.toFixed(3)})`
59
- );
60
- const canvas = document.createElement("canvas");
61
- canvas.width = w;
62
- canvas.height = h;
63
- const ctx = canvas.getContext("2d");
64
- if (!ctx) {
65
- emfWarn('createCanvas: HTMLCanvasElement.getContext("2d") returned null');
66
- return null;
67
- }
68
- return { canvas, ctx, scaleX, scaleY };
69
- } catch (err) {
70
- return null;
71
- }
72
- }
73
- function createTempCanvas(width, height) {
74
- if (width <= 0 || height <= 0) {
75
- return null;
76
- }
77
- width = Math.max(1, Math.min(Math.floor(width), 8192));
78
- height = Math.max(1, Math.min(Math.floor(height), 8192));
79
- if (typeof OffscreenCanvas !== "undefined") {
80
- const canvas = new OffscreenCanvas(width, height);
81
- const ctx = canvas.getContext("2d");
82
- if (!ctx) {
83
- return null;
84
- }
85
- return { canvas, ctx };
86
- }
87
- if (typeof document !== "undefined") {
88
- const canvas = document.createElement("canvas");
89
- canvas.width = width;
90
- canvas.height = height;
91
- const ctx = canvas.getContext("2d");
92
- if (!ctx) {
93
- return null;
94
- }
95
- return { canvas, ctx };
96
- }
97
- return null;
98
- }
99
- function applyPen(ctx, state) {
100
- if (state.penStyle === 5) {
101
- ctx.strokeStyle = "rgba(0,0,0,0)";
102
- ctx.lineWidth = 0;
103
- return;
104
- }
105
- ctx.strokeStyle = state.penColor;
106
- ctx.lineWidth = Math.max(state.penWidth, 1);
107
- switch (state.penStyle) {
108
- case 1:
109
- ctx.setLineDash([8, 4]);
110
- break;
111
- case 2:
112
- ctx.setLineDash([2, 2]);
113
- break;
114
- case 3:
115
- ctx.setLineDash([8, 4, 2, 4]);
116
- break;
117
- case 4:
118
- ctx.setLineDash([8, 4, 2, 4, 2, 4]);
119
- break;
120
- default:
121
- ctx.setLineDash([]);
122
- break;
123
- }
124
- }
125
- function applyBrush(ctx, state) {
126
- if (state.brushStyle === 1) {
127
- ctx.fillStyle = "rgba(0,0,0,0)";
128
- return;
129
- }
130
- ctx.fillStyle = state.brushColor;
131
- }
132
- function applyFont(ctx, state) {
133
- const italic = state.fontItalic ? "italic " : "";
134
- const weight = state.fontWeight >= 700 ? "bold " : "";
135
- const size = Math.max(Math.abs(state.fontHeight), 8);
136
- ctx.font = `${italic}${weight}${size}px ${state.fontFamily}`;
137
- }
138
- function readUtf16LE(view, offset, charCount) {
139
- if (charCount <= 0) {
140
- return "";
141
- }
142
- const maxBytes = view.byteLength - offset;
143
- if (maxBytes <= 0) {
144
- return "";
145
- }
146
- const usableChars = Math.min(charCount, Math.floor(maxBytes / 2));
147
- if (usableChars <= 0) {
148
- return "";
149
- }
150
- let decoded;
151
- try {
152
- const bytes = new Uint8Array(view.buffer, view.byteOffset + offset, usableChars * 2);
153
- decoded = new TextDecoder("utf-16le").decode(bytes);
154
- } catch {
155
- const chars = [];
156
- for (let i = 0; i < usableChars; i++) {
157
- const code = view.getUint16(offset + i * 2, true);
158
- if (code === 0) {
159
- return chars.join("");
160
- }
161
- chars.push(String.fromCharCode(code));
162
- }
163
- return chars.join("");
164
- }
165
- const nul = decoded.indexOf(String.fromCharCode(0));
166
- return nul === -1 ? decoded : decoded.slice(0, nul);
167
- }
168
- function getStockObject(index) {
169
- switch (index) {
170
- case 0:
171
- return { kind: "brush", style: 0, color: "#ffffff" };
172
- case 1:
173
- return { kind: "brush", style: 0, color: "#c0c0c0" };
174
- case 2:
175
- return { kind: "brush", style: 0, color: "#808080" };
176
- case 3:
177
- return { kind: "brush", style: 0, color: "#404040" };
178
- case 4:
179
- return { kind: "brush", style: 0, color: "#000000" };
180
- case 5:
181
- return { kind: "brush", style: 1, color: "#000000" };
182
- case 6:
183
- return { kind: "pen", style: 0, widthX: 1, color: "#ffffff" };
184
- case 7:
185
- return { kind: "pen", style: 0, widthX: 1, color: "#000000" };
186
- case 8:
187
- return { kind: "pen", style: 5, widthX: 0, color: "#000000" };
188
- case 10:
189
- case 11:
190
- return {
191
- kind: "font",
192
- height: 12,
193
- weight: 400,
194
- italic: false,
195
- family: "monospace"
196
- };
197
- case 12:
198
- case 13:
199
- case 14:
200
- case 17:
201
- return {
202
- kind: "font",
203
- height: 12,
204
- weight: 400,
205
- italic: false,
206
- family: "sans-serif"
207
- };
208
- default:
209
- return null;
210
- }
211
- }
212
- async function blobToDataUrl(blob) {
213
- return new Promise((resolve, reject) => {
214
- const reader = new FileReader();
215
- reader.onload = () => resolve(reader.result);
216
- reader.onerror = () => reject(reader.error);
217
- reader.readAsDataURL(blob);
218
- });
219
- }
220
- async function exportCanvasToPngDataUrl(canvas) {
221
- if (typeof OffscreenCanvas !== "undefined" && canvas instanceof OffscreenCanvas) {
222
- emfLog(
223
- `exportCanvasToPngDataUrl: using OffscreenCanvas.convertToBlob (${canvas.width}\xD7${canvas.height})`
224
- );
225
- const blob = await canvas.convertToBlob({ type: "image/png" });
226
- emfLog(`exportCanvasToPngDataUrl: blob size=${blob.size} bytes, type=${blob.type}`);
227
- return blobToDataUrl(blob);
228
- }
229
- if (typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement) {
230
- emfLog(
231
- `exportCanvasToPngDataUrl: using HTMLCanvasElement.toDataURL (${canvas.width}\xD7${canvas.height})`
232
- );
233
- return canvas.toDataURL("image/png");
234
- }
235
- return null;
236
- }
237
-
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
- // src/emf-header-parser.ts
394
- function parseEmfHeader(view) {
395
- emfLog("parseEmfHeader: byteLength =", view.byteLength);
396
- if (view.byteLength < 88) {
397
- return null;
398
- }
399
- const recordType = view.getUint32(0, true);
400
- if (recordType !== EMR_HEADER) {
401
- return null;
402
- }
403
- const boundsLeft = view.getInt32(8, true);
404
- const boundsTop = view.getInt32(12, true);
405
- const boundsRight = view.getInt32(16, true);
406
- const boundsBottom = view.getInt32(20, true);
407
- const frameLeft = view.getInt32(24, true);
408
- const frameTop = view.getInt32(28, true);
409
- const frameRight = view.getInt32(32, true);
410
- const frameBottom = view.getInt32(36, true);
411
- const frameW = frameRight - frameLeft;
412
- const frameH = frameBottom - frameTop;
413
- return {
414
- bounds: {
415
- left: boundsLeft,
416
- top: boundsTop,
417
- right: boundsRight,
418
- bottom: boundsBottom
419
- },
420
- frameW,
421
- frameH
422
- };
423
- }
424
- function getRenderableEmfBounds(header) {
425
- const boundsW = header.bounds.right - header.bounds.left;
426
- const boundsH = header.bounds.bottom - header.bounds.top;
427
- if (boundsW > 0 && boundsH > 0) {
428
- return header.bounds;
429
- }
430
- if (header.frameW > 0 && header.frameH > 0) {
431
- emfLog(
432
- `getRenderableEmfBounds: bounds invalid (${boundsW}\xD7${boundsH}), falling back to frame ${header.frameW}\xD7${header.frameH}`
433
- );
434
- return { left: 0, top: 0, right: header.frameW, bottom: header.frameH };
435
- }
436
- return null;
437
- }
438
- function parseWmfHeader(view) {
439
- if (view.byteLength < 22) {
440
- return null;
441
- }
442
- const magic = view.getUint32(0, true);
443
- let headerOffset = 0;
444
- let boundsLeft = 0;
445
- let boundsTop = 0;
446
- let boundsRight = 800;
447
- let boundsBottom = 600;
448
- let unitsPerInch = 96;
449
- if (magic === 2596720087) {
450
- boundsLeft = view.getInt16(6, true);
451
- boundsTop = view.getInt16(8, true);
452
- boundsRight = view.getInt16(10, true);
453
- boundsBottom = view.getInt16(12, true);
454
- unitsPerInch = view.getUint16(14, true) || 96;
455
- headerOffset = 22;
456
- }
457
- if (headerOffset + 18 > view.byteLength) {
458
- return null;
459
- }
460
- const fileType = view.getUint16(headerOffset, true);
461
- if (fileType !== 1 && fileType !== 2) {
462
- return null;
463
- }
464
- const headerSize = view.getUint16(headerOffset + 2, true) * 2;
465
- const maxRecordSize = view.getUint32(headerOffset + 8, true) * 2;
466
- return {
467
- headerSize: headerOffset + headerSize,
468
- maxRecordSize,
469
- boundsLeft,
470
- boundsTop,
471
- boundsRight,
472
- boundsBottom,
473
- unitsPerInch
474
- };
475
- }
476
-
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
- // src/emf-gdi-coord.ts
497
- function gmx(r, x) {
498
- if (r.useMappingMode) {
499
- return (x - r.windowOrg.x) / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1) + r.viewportOrg.x;
500
- }
501
- return (x - r.bounds.left) * r.sx;
502
- }
503
- function gmy(r, y) {
504
- if (r.useMappingMode) {
505
- return (y - r.windowOrg.y) / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1) + r.viewportOrg.y;
506
- }
507
- return (y - r.bounds.top) * r.sy;
508
- }
509
- function gmw(r, w) {
510
- if (r.useMappingMode) {
511
- return w / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1);
512
- }
513
- return w * r.sx;
514
- }
515
- function gmh(r, h) {
516
- if (r.useMappingMode) {
517
- return h / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1);
518
- }
519
- return h * r.sy;
520
- }
521
- function activateGdiMappingMode(r) {
522
- r.useMappingMode = true;
523
- }
524
-
525
- // src/emf-gdi-draw-shapes.ts
526
- function handleSetPixelV(rCtx, dataOff, recSize) {
527
- const { ctx, view } = rCtx;
528
- if (recSize >= 20) {
529
- const x = view.getInt32(dataOff, true);
530
- const y = view.getInt32(dataOff + 4, true);
531
- const color = readColorRef(view, dataOff + 8);
532
- ctx.fillStyle = color;
533
- ctx.fillRect(gmx(rCtx, x), gmy(rCtx, y), 1, 1);
534
- }
535
- return true;
536
- }
537
- function handleMoveToEx(rCtx, dataOff, recSize) {
538
- const { ctx, view, state, inPath } = rCtx;
539
- if (recSize >= 16) {
540
- state.curX = view.getInt32(dataOff, true);
541
- state.curY = view.getInt32(dataOff + 4, true);
542
- if (inPath) {
543
- ctx.moveTo(gmx(rCtx, state.curX), gmy(rCtx, state.curY));
544
- }
545
- }
546
- return true;
547
- }
548
- function handleLineTo(rCtx, dataOff, recSize) {
549
- const { ctx, view, state, inPath } = rCtx;
550
- if (recSize >= 16) {
551
- const lx = view.getInt32(dataOff, true);
552
- const ly = view.getInt32(dataOff + 4, true);
553
- if (inPath) {
554
- ctx.lineTo(gmx(rCtx, lx), gmy(rCtx, ly));
555
- } else {
556
- applyPen(ctx, state);
557
- ctx.beginPath();
558
- ctx.moveTo(gmx(rCtx, state.curX), gmy(rCtx, state.curY));
559
- ctx.lineTo(gmx(rCtx, lx), gmy(rCtx, ly));
560
- ctx.stroke();
561
- }
562
- state.curX = lx;
563
- state.curY = ly;
564
- }
565
- return true;
566
- }
567
- function handleRectangle(rCtx, dataOff, recSize) {
568
- const { ctx, view, state, inPath } = rCtx;
569
- if (recSize >= 24) {
570
- const l = view.getInt32(dataOff, true);
571
- const t = view.getInt32(dataOff + 4, true);
572
- const r = view.getInt32(dataOff + 8, true);
573
- const b = view.getInt32(dataOff + 12, true);
574
- if (inPath) {
575
- ctx.rect(gmx(rCtx, l), gmy(rCtx, t), gmw(rCtx, r - l), gmh(rCtx, b - t));
576
- } else {
577
- applyBrush(ctx, state);
578
- ctx.fillRect(gmx(rCtx, l), gmy(rCtx, t), gmw(rCtx, r - l), gmh(rCtx, b - t));
579
- applyPen(ctx, state);
580
- ctx.strokeRect(gmx(rCtx, l), gmy(rCtx, t), gmw(rCtx, r - l), gmh(rCtx, b - t));
581
- }
582
- }
583
- return true;
584
- }
585
- function handleRoundRect(rCtx, dataOff, recSize) {
586
- const { ctx, view, state, inPath } = rCtx;
587
- if (recSize >= 32) {
588
- const l = view.getInt32(dataOff, true);
589
- const t = view.getInt32(dataOff + 4, true);
590
- const r = view.getInt32(dataOff + 8, true);
591
- const b = view.getInt32(dataOff + 12, true);
592
- const rw = Math.abs(gmw(rCtx, view.getInt32(dataOff + 16, true))) / 2;
593
- const rh = Math.abs(gmh(rCtx, view.getInt32(dataOff + 20, true))) / 2;
594
- const x1 = gmx(rCtx, l);
595
- const y1 = gmy(rCtx, t);
596
- const w = gmw(rCtx, r - l);
597
- const h = gmh(rCtx, b - t);
598
- const drawRoundRect = () => {
599
- const radius = Math.min(rw, rh, w / 2, h / 2);
600
- ctx.moveTo(x1 + radius, y1);
601
- ctx.lineTo(x1 + w - radius, y1);
602
- ctx.arcTo(x1 + w, y1, x1 + w, y1 + radius, radius);
603
- ctx.lineTo(x1 + w, y1 + h - radius);
604
- ctx.arcTo(x1 + w, y1 + h, x1 + w - radius, y1 + h, radius);
605
- ctx.lineTo(x1 + radius, y1 + h);
606
- ctx.arcTo(x1, y1 + h, x1, y1 + h - radius, radius);
607
- ctx.lineTo(x1, y1 + radius);
608
- ctx.arcTo(x1, y1, x1 + radius, y1, radius);
609
- ctx.closePath();
610
- };
611
- if (inPath) {
612
- drawRoundRect();
613
- } else {
614
- ctx.beginPath();
615
- drawRoundRect();
616
- applyBrush(ctx, state);
617
- ctx.fill();
618
- applyPen(ctx, state);
619
- ctx.stroke();
620
- }
621
- }
622
- return true;
623
- }
624
- function handleEllipse(rCtx, dataOff, recSize) {
625
- const { ctx, view, state, inPath } = rCtx;
626
- if (recSize >= 24) {
627
- const l = view.getInt32(dataOff, true);
628
- const t = view.getInt32(dataOff + 4, true);
629
- const r = view.getInt32(dataOff + 8, true);
630
- const b = view.getInt32(dataOff + 12, true);
631
- const cx = gmx(rCtx, (l + r) / 2);
632
- const cy = gmy(rCtx, (t + b) / 2);
633
- const rx = Math.abs(gmw(rCtx, r - l)) / 2;
634
- const ry = Math.abs(gmh(rCtx, b - t)) / 2;
635
- if (inPath) {
636
- ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
637
- } else {
638
- ctx.beginPath();
639
- ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
640
- applyBrush(ctx, state);
641
- ctx.fill();
642
- applyPen(ctx, state);
643
- ctx.stroke();
644
- }
645
- }
646
- return true;
647
- }
648
- function handleArcFamily(rCtx, recType, dataOff, recSize) {
649
- const { ctx, view, state, inPath } = rCtx;
650
- if (recSize >= 40) {
651
- const l = view.getInt32(dataOff, true);
652
- const t = view.getInt32(dataOff + 4, true);
653
- const r = view.getInt32(dataOff + 8, true);
654
- const b = view.getInt32(dataOff + 12, true);
655
- const startX = view.getInt32(dataOff + 16, true);
656
- const startY = view.getInt32(dataOff + 20, true);
657
- const endX = view.getInt32(dataOff + 24, true);
658
- const endY = view.getInt32(dataOff + 28, true);
659
- const cxA = (l + r) / 2;
660
- const cyA = (t + b) / 2;
661
- const rx = Math.abs(r - l) / 2;
662
- const ry = Math.abs(b - t) / 2;
663
- const startAngle = Math.atan2((startY - cyA) / (ry || 1), (startX - cxA) / (rx || 1));
664
- const endAngle = Math.atan2((endY - cyA) / (ry || 1), (endX - cxA) / (rx || 1));
665
- const mcx = gmx(rCtx, cxA);
666
- const mcy = gmy(rCtx, cyA);
667
- const mrx = Math.abs(gmw(rCtx, rx));
668
- const mry = Math.abs(gmh(rCtx, ry));
669
- const isArcTo = recType === EMR_ARCTO;
670
- const needsFill = recType === EMR_PIE || recType === EMR_CHORD;
671
- if (!inPath) {
672
- ctx.beginPath();
673
- }
674
- if (recType === EMR_PIE) {
675
- ctx.moveTo(mcx, mcy);
676
- }
677
- if (isArcTo) {
678
- ctx.lineTo(mcx + mrx * Math.cos(startAngle), mcy + mry * Math.sin(startAngle));
679
- }
680
- ctx.ellipse(mcx, mcy, mrx, mry, 0, startAngle, endAngle, false);
681
- if (recType === EMR_PIE || recType === EMR_CHORD) {
682
- ctx.closePath();
683
- }
684
- if (!inPath) {
685
- if (needsFill) {
686
- applyBrush(ctx, state);
687
- ctx.fill();
688
- }
689
- applyPen(ctx, state);
690
- ctx.stroke();
691
- }
692
- if (isArcTo) {
693
- state.curX = endX;
694
- state.curY = endY;
695
- }
696
- }
697
- return true;
698
- }
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;
720
- }
721
- }
722
-
723
- // src/emf-dib-rle-decoder.ts
724
- function decodeRleBitmap(view, bitsOffset, bitsSize, width, height, _topDown, isRle4, colorTable, out, setPixel) {
725
- let x = 0;
726
- let y = height - 1;
727
- let off = bitsOffset;
728
- const endOff = bitsOffset + bitsSize;
729
- while (off + 1 < endOff && y >= 0) {
730
- const first = view.getUint8(off);
731
- const second = view.getUint8(off + 1);
732
- off += 2;
733
- if (first === 0) {
734
- if (second === 0) {
735
- x = 0;
736
- y--;
737
- } else if (second === 1) {
738
- break;
739
- } else if (second === 2) {
740
- if (off + 1 >= endOff) {
741
- break;
742
- }
743
- x += view.getUint8(off);
744
- y -= view.getUint8(off + 1);
745
- off += 2;
746
- } else {
747
- const count = second;
748
- if (!isRle4) {
749
- for (let i = 0; i < count && off < endOff && x < width; i++) {
750
- const idx = view.getUint8(off++);
751
- if (idx < colorTable.length) {
752
- setPixel(
753
- x,
754
- height - 1 - y,
755
- colorTable[idx][0],
756
- colorTable[idx][1],
757
- colorTable[idx][2],
758
- 255
759
- );
760
- }
761
- x++;
762
- }
763
- if (count & 1) {
764
- off++;
765
- }
766
- } else {
767
- const bytes = Math.ceil(count / 2);
768
- let pi = 0;
769
- for (let i = 0; i < bytes && off < endOff; i++) {
770
- const byte = view.getUint8(off++);
771
- for (let nibble = 0; nibble < 2 && pi < count; nibble++) {
772
- const idx = nibble === 0 ? byte >> 4 & 15 : byte & 15;
773
- if (idx < colorTable.length && x < width) {
774
- setPixel(
775
- x,
776
- height - 1 - y,
777
- colorTable[idx][0],
778
- colorTable[idx][1],
779
- colorTable[idx][2],
780
- 255
781
- );
782
- }
783
- x++;
784
- pi++;
785
- }
786
- }
787
- if (bytes & 1) {
788
- off++;
789
- }
790
- }
791
- }
792
- } else if (!isRle4) {
793
- const idx = second;
794
- const c = idx < colorTable.length ? colorTable[idx] : [0, 0, 0];
795
- for (let i = 0; i < first && x < width; i++) {
796
- setPixel(x, height - 1 - y, c[0], c[1], c[2], 255);
797
- x++;
798
- }
799
- } else {
800
- const hi = second >> 4 & 15;
801
- const lo = second & 15;
802
- for (let i = 0; i < first && x < width; i++) {
803
- const idx = (i & 1) === 0 ? hi : lo;
804
- if (idx < colorTable.length) {
805
- setPixel(
806
- x,
807
- height - 1 - y,
808
- colorTable[idx][0],
809
- colorTable[idx][1],
810
- colorTable[idx][2],
811
- 255
812
- );
813
- }
814
- x++;
815
- }
816
- }
817
- }
818
- return new ImageData(
819
- new Uint8ClampedArray(out.buffer, out.byteOffset, out.byteLength),
820
- width,
821
- height
822
- );
823
- }
824
-
825
- // src/emf-dib-uncompressed.ts
826
- function countTrailingZeros(v) {
827
- if (v === 0) {
828
- return 0;
829
- }
830
- let c = 0;
831
- let val = v;
832
- while ((val & 1) === 0) {
833
- val >>>= 1;
834
- c++;
835
- }
836
- return c;
837
- }
838
- var BI_BITFIELDS = 3;
839
- function parseBitfieldMasks(view, bmiOffset, headerSize, compression, bitCount) {
840
- let rMask = 0, gMask = 0, bMask = 0;
841
- let rShift = 0, gShift = 0, bShift = 0;
842
- let rMax = 1, gMax = 1, bMax = 1;
843
- if (compression === BI_BITFIELDS) {
844
- const bfOff = bmiOffset + headerSize;
845
- if (bfOff + 12 > view.byteLength) {
846
- return null;
847
- }
848
- rMask = view.getUint32(bfOff, true);
849
- gMask = view.getUint32(bfOff + 4, true);
850
- bMask = view.getUint32(bfOff + 8, true);
851
- rShift = countTrailingZeros(rMask);
852
- gShift = countTrailingZeros(gMask);
853
- bShift = countTrailingZeros(bMask);
854
- rMax = rMask >>> rShift || 1;
855
- gMax = gMask >>> gShift || 1;
856
- bMax = bMask >>> bShift || 1;
857
- } else if (bitCount === 16) {
858
- rMask = 31744;
859
- gMask = 992;
860
- bMask = 31;
861
- rShift = 10;
862
- gShift = 5;
863
- bShift = 0;
864
- rMax = 31;
865
- gMax = 31;
866
- bMax = 31;
867
- }
868
- return { rMask, gMask, bMask, rShift, gShift, bShift, rMax, gMax, bMax };
869
- }
870
- function decodeUncompressedRows(view, bitsOffset, width, height, topDown, bitCount, colorTable, masks, out) {
871
- const rowStride = Math.floor((bitCount * width + 31) / 32) * 4;
872
- const { rMask, gMask, bMask, rShift, gShift, bShift, rMax, gMax, bMax } = masks;
873
- for (let y = 0; y < height; y++) {
874
- const srcY = topDown ? y : height - 1 - y;
875
- const rowStart = bitsOffset + srcY * rowStride;
876
- if (rowStart + rowStride > view.byteLength) {
877
- continue;
878
- }
879
- for (let x = 0; x < width; x++) {
880
- const dstPx = (y * width + x) * 4;
881
- if (bitCount === 1) {
882
- const byteIdx = rowStart + (x >> 3);
883
- const bit = view.getUint8(byteIdx) >> 7 - (x & 7) & 1;
884
- if (bit < colorTable.length) {
885
- out[dstPx] = colorTable[bit][0];
886
- out[dstPx + 1] = colorTable[bit][1];
887
- out[dstPx + 2] = colorTable[bit][2];
888
- }
889
- out[dstPx + 3] = 255;
890
- } else if (bitCount === 4) {
891
- const byteIdx = rowStart + (x >> 1);
892
- const nibble = (x & 1) === 0 ? view.getUint8(byteIdx) >> 4 & 15 : view.getUint8(byteIdx) & 15;
893
- if (nibble < colorTable.length) {
894
- out[dstPx] = colorTable[nibble][0];
895
- out[dstPx + 1] = colorTable[nibble][1];
896
- out[dstPx + 2] = colorTable[nibble][2];
897
- }
898
- out[dstPx + 3] = 255;
899
- } else if (bitCount === 8) {
900
- const idx = view.getUint8(rowStart + x);
901
- if (idx < colorTable.length) {
902
- out[dstPx] = colorTable[idx][0];
903
- out[dstPx + 1] = colorTable[idx][1];
904
- out[dstPx + 2] = colorTable[idx][2];
905
- }
906
- out[dstPx + 3] = 255;
907
- } else if (bitCount === 16) {
908
- const val = view.getUint16(rowStart + x * 2, true);
909
- out[dstPx] = Math.round(((val & rMask) >>> rShift) * 255 / rMax);
910
- out[dstPx + 1] = Math.round(((val & gMask) >>> gShift) * 255 / gMax);
911
- out[dstPx + 2] = Math.round(((val & bMask) >>> bShift) * 255 / bMax);
912
- out[dstPx + 3] = 255;
913
- } else if (bitCount === 24) {
914
- const srcPx = rowStart + x * 3;
915
- out[dstPx] = view.getUint8(srcPx + 2);
916
- out[dstPx + 1] = view.getUint8(srcPx + 1);
917
- out[dstPx + 2] = view.getUint8(srcPx);
918
- out[dstPx + 3] = 255;
919
- } else {
920
- const srcPx = rowStart + x * 4;
921
- const bb = view.getUint8(srcPx);
922
- const gg = view.getUint8(srcPx + 1);
923
- const rr = view.getUint8(srcPx + 2);
924
- const aa = view.getUint8(srcPx + 3);
925
- out[dstPx] = rr;
926
- out[dstPx + 1] = gg;
927
- out[dstPx + 2] = bb;
928
- out[dstPx + 3] = aa === 0 ? 255 : aa;
929
- }
930
- }
931
- }
932
- }
933
-
934
- // src/emf-dib-decoder.ts
935
- function decodeDibToImageData(view, bmiOffset, bitsOffset, bitsSize) {
936
- if (bmiOffset < 0 || bitsOffset < 0 || bmiOffset + 40 > view.byteLength || bitsOffset + bitsSize > view.byteLength) {
937
- return null;
938
- }
939
- const headerSize = view.getUint32(bmiOffset, true);
940
- if (headerSize < 40 || bmiOffset + headerSize > view.byteLength) {
941
- return null;
942
- }
943
- const width = view.getInt32(bmiOffset + 4, true);
944
- const heightRaw = view.getInt32(bmiOffset + 8, true);
945
- const planes = view.getUint16(bmiOffset + 12, true);
946
- const bitCount = view.getUint16(bmiOffset + 14, true);
947
- const compression = view.getUint32(bmiOffset + 16, true);
948
- if (planes !== 1 || width <= 0 || heightRaw === 0) {
949
- return null;
950
- }
951
- if (width > 8192 || Math.abs(heightRaw) > 8192) {
952
- return null;
953
- }
954
- const BI_RGB = 0;
955
- const BI_RLE8 = 1;
956
- const BI_RLE4 = 2;
957
- const BI_BITFIELDS2 = 3;
958
- if (bitCount !== 1 && bitCount !== 4 && bitCount !== 8 && bitCount !== 16 && bitCount !== 24 && bitCount !== 32) {
959
- return null;
960
- }
961
- if (compression === BI_RLE8 && bitCount !== 8) {
962
- return null;
963
- }
964
- if (compression === BI_RLE4 && bitCount !== 4) {
965
- return null;
966
- }
967
- if (compression === BI_BITFIELDS2 && bitCount !== 16 && bitCount !== 32) {
968
- return null;
969
- }
970
- if (compression !== BI_RGB && compression !== BI_RLE8 && compression !== BI_RLE4 && compression !== BI_BITFIELDS2) {
971
- return null;
972
- }
973
- const height = Math.abs(heightRaw);
974
- const topDown = heightRaw < 0;
975
- const colorTable = [];
976
- if (bitCount <= 8) {
977
- const maxColors = 1 << bitCount;
978
- const colorsUsed = view.getUint32(bmiOffset + 32, true) || maxColors;
979
- const numColors = Math.min(colorsUsed, maxColors);
980
- const ctOffset = bmiOffset + headerSize;
981
- if (ctOffset + numColors * 4 > view.byteLength) {
982
- return null;
983
- }
984
- for (let i = 0; i < numColors; i++) {
985
- const b = view.getUint8(ctOffset + i * 4);
986
- const g = view.getUint8(ctOffset + i * 4 + 1);
987
- const r = view.getUint8(ctOffset + i * 4 + 2);
988
- colorTable.push([r, g, b]);
989
- }
990
- }
991
- const masks = parseBitfieldMasks(view, bmiOffset, headerSize, compression, bitCount);
992
- if (!masks) {
993
- return null;
994
- }
995
- const out = new Uint8ClampedArray(width * height * 4);
996
- if (compression === BI_RLE8 || compression === BI_RLE4) {
997
- const setPixel = (x, y, r, g, b, a) => {
998
- const dstPx = (y * width + x) * 4;
999
- out[dstPx] = r;
1000
- out[dstPx + 1] = g;
1001
- out[dstPx + 2] = b;
1002
- out[dstPx + 3] = a;
1003
- };
1004
- return decodeRleBitmap(
1005
- view,
1006
- bitsOffset,
1007
- bitsSize,
1008
- width,
1009
- height,
1010
- topDown,
1011
- compression === BI_RLE4,
1012
- colorTable,
1013
- out,
1014
- setPixel
1015
- );
1016
- }
1017
- decodeUncompressedRows(
1018
- view,
1019
- bitsOffset,
1020
- width,
1021
- height,
1022
- topDown,
1023
- bitCount,
1024
- colorTable,
1025
- masks,
1026
- out
1027
- );
1028
- return new ImageData(out, width, height);
1029
- }
1030
-
1031
- // src/emf-gdi-draw-text-bitmap.ts
1032
- function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
1033
- const { ctx, view, state } = rCtx;
1034
- if (recSize >= 76) {
1035
- const refX = view.getInt32(dataOff + 28, true);
1036
- const refY = view.getInt32(dataOff + 32, true);
1037
- const nChars = view.getUint32(dataOff + 36, true);
1038
- const offString = view.getUint32(dataOff + 40, true);
1039
- const maxOffset = view.byteLength;
1040
- if (nChars > 0 && offString > 0 && offset + offString + nChars * 2 <= maxOffset) {
1041
- const text = readUtf16LE(view, offset + offString, nChars);
1042
- if (text.length > 0) {
1043
- applyFont(ctx, state);
1044
- ctx.fillStyle = state.textColor;
1045
- let alignBaseline = "alphabetic";
1046
- let alignHoriz = "left";
1047
- if (state.textAlign & 8) {
1048
- alignBaseline = "bottom";
1049
- }
1050
- if (state.textAlign & 24) {
1051
- alignBaseline = "alphabetic";
1052
- }
1053
- if (state.textAlign & 6) {
1054
- alignHoriz = "center";
1055
- }
1056
- if (state.textAlign & 2) {
1057
- alignHoriz = "right";
1058
- }
1059
- ctx.textBaseline = alignBaseline;
1060
- ctx.textAlign = alignHoriz;
1061
- if (state.bkMode === 2) {
1062
- const measured = ctx.measureText(text);
1063
- const bgH = state.fontHeight || 12;
1064
- ctx.fillStyle = state.bkColor;
1065
- ctx.fillRect(gmx(rCtx, refX), gmy(rCtx, refY) - bgH, measured.width, bgH);
1066
- ctx.fillStyle = state.textColor;
1067
- }
1068
- ctx.fillText(text, gmx(rCtx, refX), gmy(rCtx, refY));
1069
- }
1070
- }
1071
- }
1072
- return true;
1073
- }
1074
- function handleBitBlt(rCtx, offset, dataOff, recSize) {
1075
- const { ctx, view } = rCtx;
1076
- if (recSize >= 96) {
1077
- const dstX = view.getInt32(dataOff + 16, true);
1078
- const dstY = view.getInt32(dataOff + 20, true);
1079
- const dstW = view.getInt32(dataOff + 24, true);
1080
- const dstH = view.getInt32(dataOff + 28, true);
1081
- const offBmiSrc = view.getUint32(dataOff + 76, true);
1082
- const cbBmiSrc = view.getUint32(dataOff + 80, true);
1083
- const offBitsSrc = view.getUint32(dataOff + 84, true);
1084
- const cbBitsSrc = view.getUint32(dataOff + 88, true);
1085
- if (offBmiSrc > 0 && cbBmiSrc > 0 && offBitsSrc > 0 && cbBitsSrc > 0) {
1086
- const imageData = decodeDibToImageData(
1087
- view,
1088
- offset + offBmiSrc,
1089
- offset + offBitsSrc,
1090
- cbBitsSrc
1091
- );
1092
- if (imageData) {
1093
- const temp = createTempCanvas(imageData.width, imageData.height);
1094
- if (temp) {
1095
- temp.ctx.putImageData(imageData, 0, 0);
1096
- ctx.drawImage(
1097
- temp.canvas,
1098
- gmx(rCtx, dstX),
1099
- gmy(rCtx, dstY),
1100
- gmw(rCtx, dstW),
1101
- gmh(rCtx, dstH)
1102
- );
1103
- }
1104
- }
1105
- }
1106
- }
1107
- return true;
1108
- }
1109
- function handleStretchDibits(rCtx, offset, dataOff, recSize) {
1110
- const { ctx, view } = rCtx;
1111
- if (recSize >= 80) {
1112
- const dstX = view.getInt32(dataOff + 16, true);
1113
- const dstY = view.getInt32(dataOff + 20, true);
1114
- const dstW = view.getInt32(dataOff + 64, true);
1115
- const dstH = view.getInt32(dataOff + 68, true);
1116
- const offBmiSrc = view.getUint32(dataOff + 40, true);
1117
- const cbBmiSrc = view.getUint32(dataOff + 44, true);
1118
- const offBitsSrc = view.getUint32(dataOff + 48, true);
1119
- const cbBitsSrc = view.getUint32(dataOff + 52, true);
1120
- if (offBmiSrc > 0 && cbBmiSrc > 0 && offBitsSrc > 0 && cbBitsSrc > 0) {
1121
- const imageData = decodeDibToImageData(
1122
- view,
1123
- offset + offBmiSrc,
1124
- offset + offBitsSrc,
1125
- cbBitsSrc
1126
- );
1127
- if (imageData) {
1128
- const temp = createTempCanvas(imageData.width, imageData.height);
1129
- if (temp) {
1130
- temp.ctx.putImageData(imageData, 0, 0);
1131
- ctx.drawImage(
1132
- temp.canvas,
1133
- gmx(rCtx, dstX),
1134
- gmy(rCtx, dstY),
1135
- gmw(rCtx, dstW),
1136
- gmh(rCtx, dstH)
1137
- );
1138
- }
1139
- }
1140
- }
1141
- }
1142
- return true;
1143
- }
1144
- function handleIntersectClipRect(rCtx, dataOff, recSize) {
1145
- const { ctx, view } = rCtx;
1146
- 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
- }
1159
- }
1160
- return true;
1161
- }
1162
- function handleExtSelectClipRgn(rCtx, dataOff, recSize) {
1163
- const { ctx, view } = rCtx;
1164
- if (recSize < 16) {
1165
- return true;
1166
- }
1167
- const cbRgnData = view.getUint32(dataOff, true);
1168
- 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 {
1207
- }
1208
- }
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);
1217
- }
1218
- return true;
1219
- }
1220
- function handleOffsetClipRgn(rCtx, dataOff, recSize) {
1221
- if (recSize >= 16) {
1222
- rCtx.view.getInt32(dataOff, true);
1223
- rCtx.view.getInt32(dataOff + 4, true);
1224
- }
1225
- return true;
1226
- }
1227
- function handleEmfGdiTextBitmapRecord(rCtx, recType, offset, dataOff, recSize) {
1228
- switch (recType) {
1229
- case EMR_EXTTEXTOUTW:
1230
- return handleExtTextOutW(rCtx, offset, dataOff, recSize);
1231
- case EMR_BITBLT:
1232
- return handleBitBlt(rCtx, offset, dataOff, recSize);
1233
- case EMR_STRETCHDIBITS:
1234
- return handleStretchDibits(rCtx, offset, dataOff, recSize);
1235
- case EMR_INTERSECTCLIPRECT:
1236
- return handleIntersectClipRect(rCtx, dataOff, recSize);
1237
- case EMR_EXTSELECTCLIPRGN:
1238
- return handleExtSelectClipRgn(rCtx, dataOff, recSize);
1239
- case EMR_EXCLUDECLIPRECT:
1240
- return handleExcludeClipRect(rCtx, dataOff, recSize);
1241
- case EMR_OFFSETCLIPRGN:
1242
- return handleOffsetClipRgn(rCtx, dataOff, recSize);
1243
- default:
1244
- return false;
1245
- }
1246
- }
1247
-
1248
- // src/emf-gdi-draw-handlers.ts
1249
- function handleEmfGdiDrawRecord(rCtx, recType, offset, dataOff, recSize) {
1250
- return handleEmfGdiShapeRecord(rCtx, recType, dataOff, recSize) || handleEmfGdiTextBitmapRecord(rCtx, recType, offset, dataOff, recSize);
1251
- }
1252
-
1253
- // src/emf-gdi-polypolygon-helpers.ts
1254
- function handlePolyPolygon32(rCtx, offset, dataOff, recSize) {
1255
- const { ctx, view, state, inPath } = rCtx;
1256
- const numPolys = view.getUint32(dataOff + 16, true);
1257
- const totalPoints = view.getUint32(dataOff + 20, true);
1258
- if (numPolys === 0 || numPolys >= 1e4 || totalPoints >= 1e5) {
1259
- return;
1260
- }
1261
- const countsOff = dataOff + 24;
1262
- const ptOff = countsOff + numPolys * 4;
1263
- if (ptOff + totalPoints * 8 > offset + recSize) {
1264
- return;
1265
- }
1266
- if (!inPath) {
1267
- ctx.beginPath();
1268
- }
1269
- let pIdx = 0;
1270
- for (let p = 0; p < numPolys; p++) {
1271
- const count = view.getUint32(countsOff + p * 4, true);
1272
- for (let i = 0; i < count && pIdx < totalPoints; i++) {
1273
- const px = view.getInt32(ptOff + pIdx * 8, true);
1274
- const py = view.getInt32(ptOff + pIdx * 8 + 4, true);
1275
- if (i === 0) {
1276
- ctx.moveTo(gmx(rCtx, px), gmy(rCtx, py));
1277
- } else {
1278
- ctx.lineTo(gmx(rCtx, px), gmy(rCtx, py));
1279
- }
1280
- pIdx++;
1281
- }
1282
- ctx.closePath();
1283
- }
1284
- if (!inPath) {
1285
- applyBrush(ctx, state);
1286
- ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
1287
- applyPen(ctx, state);
1288
- ctx.stroke();
1289
- }
1290
- }
1291
- function handlePolyPolyline32(rCtx, offset, dataOff, recSize) {
1292
- const { ctx, view, state, inPath } = rCtx;
1293
- const numPolys = view.getUint32(dataOff + 16, true);
1294
- const totalPoints = view.getUint32(dataOff + 20, true);
1295
- if (numPolys === 0 || numPolys >= 1e4 || totalPoints >= 1e5) {
1296
- return;
1297
- }
1298
- const countsOff = dataOff + 24;
1299
- const ptOff = countsOff + numPolys * 4;
1300
- if (ptOff + totalPoints * 8 > offset + recSize) {
1301
- return;
1302
- }
1303
- if (!inPath) {
1304
- ctx.beginPath();
1305
- }
1306
- let pIdx = 0;
1307
- for (let p = 0; p < numPolys; p++) {
1308
- const count = view.getUint32(countsOff + p * 4, true);
1309
- for (let i = 0; i < count && pIdx < totalPoints; i++) {
1310
- const px = view.getInt32(ptOff + pIdx * 8, true);
1311
- const py = view.getInt32(ptOff + pIdx * 8 + 4, true);
1312
- if (i === 0) {
1313
- ctx.moveTo(gmx(rCtx, px), gmy(rCtx, py));
1314
- } else {
1315
- ctx.lineTo(gmx(rCtx, px), gmy(rCtx, py));
1316
- }
1317
- pIdx++;
1318
- }
1319
- }
1320
- if (!inPath) {
1321
- applyPen(ctx, state);
1322
- ctx.stroke();
1323
- }
1324
- }
1325
- function handlePolyPolygon16(rCtx, offset, dataOff, recSize) {
1326
- const { ctx, view, state, inPath } = rCtx;
1327
- const numPolys = view.getUint32(dataOff + 16, true);
1328
- const totalPoints = view.getUint32(dataOff + 20, true);
1329
- if (numPolys === 0 || numPolys >= 1e4 || totalPoints >= 1e5) {
1330
- return;
1331
- }
1332
- const countsOff = dataOff + 24;
1333
- const ptOff = countsOff + numPolys * 4;
1334
- if (ptOff + totalPoints * 4 > offset + recSize) {
1335
- return;
1336
- }
1337
- if (!inPath) {
1338
- ctx.beginPath();
1339
- }
1340
- let pIdx = 0;
1341
- for (let p = 0; p < numPolys; p++) {
1342
- const count = view.getUint32(countsOff + p * 4, true);
1343
- for (let i = 0; i < count && pIdx < totalPoints; i++) {
1344
- const px = view.getInt16(ptOff + pIdx * 4, true);
1345
- const py = view.getInt16(ptOff + pIdx * 4 + 2, true);
1346
- if (i === 0) {
1347
- ctx.moveTo(gmx(rCtx, px), gmy(rCtx, py));
1348
- } else {
1349
- ctx.lineTo(gmx(rCtx, px), gmy(rCtx, py));
1350
- }
1351
- pIdx++;
1352
- }
1353
- ctx.closePath();
1354
- }
1355
- if (!inPath) {
1356
- applyBrush(ctx, state);
1357
- ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
1358
- applyPen(ctx, state);
1359
- ctx.stroke();
1360
- }
1361
- }
1362
-
1363
- // src/emf-gdi-poly-path-handlers.ts
1364
- function handlePoly32(rCtx, recType, offset, dataOff, recSize) {
1365
- const { ctx, view, state, inPath } = rCtx;
1366
- if (recSize < 28) {
1367
- return true;
1368
- }
1369
- const count = view.getUint32(dataOff + 16, true);
1370
- const ptOff = dataOff + 20;
1371
- if (count === 0 || ptOff + count * 8 > offset + recSize) {
1372
- return true;
1373
- }
1374
- const isPolygon = recType === EMR_POLYGON;
1375
- const isBezier = recType === EMR_POLYBEZIER || recType === EMR_POLYBEZIERTO;
1376
- const isTo = recType === EMR_POLYBEZIERTO || recType === EMR_POLYLINETO;
1377
- if (!inPath) {
1378
- ctx.beginPath();
1379
- }
1380
- if (!isTo) {
1381
- ctx.moveTo(gmx(rCtx, view.getInt32(ptOff, true)), gmy(rCtx, view.getInt32(ptOff + 4, true)));
1382
- }
1383
- let i = isTo ? 0 : 1;
1384
- if (isBezier) {
1385
- while (i + 2 < count) {
1386
- ctx.bezierCurveTo(
1387
- gmx(rCtx, view.getInt32(ptOff + i * 8, true)),
1388
- gmy(rCtx, view.getInt32(ptOff + i * 8 + 4, true)),
1389
- gmx(rCtx, view.getInt32(ptOff + (i + 1) * 8, true)),
1390
- gmy(rCtx, view.getInt32(ptOff + (i + 1) * 8 + 4, true)),
1391
- gmx(rCtx, view.getInt32(ptOff + (i + 2) * 8, true)),
1392
- gmy(rCtx, view.getInt32(ptOff + (i + 2) * 8 + 4, true))
1393
- );
1394
- i += 3;
1395
- }
1396
- } else {
1397
- for (; i < count; i++) {
1398
- ctx.lineTo(
1399
- gmx(rCtx, view.getInt32(ptOff + i * 8, true)),
1400
- gmy(rCtx, view.getInt32(ptOff + i * 8 + 4, true))
1401
- );
1402
- }
1403
- }
1404
- if (isPolygon) {
1405
- ctx.closePath();
1406
- }
1407
- if (!inPath) {
1408
- if (isPolygon) {
1409
- applyBrush(ctx, state);
1410
- ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
1411
- }
1412
- applyPen(ctx, state);
1413
- ctx.stroke();
1414
- }
1415
- if (count > 0) {
1416
- const last = count - 1;
1417
- state.curX = view.getInt32(ptOff + last * 8, true);
1418
- state.curY = view.getInt32(ptOff + last * 8 + 4, true);
1419
- }
1420
- return true;
1421
- }
1422
- function handlePoly16(rCtx, recType, offset, dataOff, recSize) {
1423
- const { ctx, view, state, inPath } = rCtx;
1424
- if (recSize < 28) {
1425
- return true;
1426
- }
1427
- const count = view.getUint32(dataOff + 16, true);
1428
- const ptOff = dataOff + 20;
1429
- if (count === 0 || ptOff + count * 4 > offset + recSize) {
1430
- return true;
1431
- }
1432
- const isPolygon = recType === EMR_POLYGON16;
1433
- const isBezier = recType === EMR_POLYBEZIER16 || recType === EMR_POLYBEZIERTO16;
1434
- const isTo = recType === EMR_POLYBEZIERTO16 || recType === EMR_POLYLINETO16;
1435
- if (!inPath) {
1436
- ctx.beginPath();
1437
- }
1438
- if (!isTo) {
1439
- ctx.moveTo(gmx(rCtx, view.getInt16(ptOff, true)), gmy(rCtx, view.getInt16(ptOff + 2, true)));
1440
- }
1441
- let i = isTo ? 0 : 1;
1442
- if (isBezier) {
1443
- while (i + 2 < count) {
1444
- ctx.bezierCurveTo(
1445
- gmx(rCtx, view.getInt16(ptOff + i * 4, true)),
1446
- gmy(rCtx, view.getInt16(ptOff + i * 4 + 2, true)),
1447
- gmx(rCtx, view.getInt16(ptOff + (i + 1) * 4, true)),
1448
- gmy(rCtx, view.getInt16(ptOff + (i + 1) * 4 + 2, true)),
1449
- gmx(rCtx, view.getInt16(ptOff + (i + 2) * 4, true)),
1450
- gmy(rCtx, view.getInt16(ptOff + (i + 2) * 4 + 2, true))
1451
- );
1452
- i += 3;
1453
- }
1454
- } else {
1455
- for (; i < count; i++) {
1456
- ctx.lineTo(
1457
- gmx(rCtx, view.getInt16(ptOff + i * 4, true)),
1458
- gmy(rCtx, view.getInt16(ptOff + i * 4 + 2, true))
1459
- );
1460
- }
1461
- }
1462
- if (isPolygon) {
1463
- ctx.closePath();
1464
- }
1465
- if (!inPath) {
1466
- if (isPolygon) {
1467
- applyBrush(ctx, state);
1468
- ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
1469
- }
1470
- applyPen(ctx, state);
1471
- ctx.stroke();
1472
- }
1473
- if (count > 0) {
1474
- const last = count - 1;
1475
- state.curX = view.getInt16(ptOff + last * 4, true);
1476
- state.curY = view.getInt16(ptOff + last * 4 + 2, true);
1477
- }
1478
- return true;
1479
- }
1480
- function handleEmfGdiPolyPathRecord(rCtx, recType, offset, dataOff, recSize) {
1481
- const { ctx, state } = rCtx;
1482
- switch (recType) {
1483
- // ---- 32-bit polys ----
1484
- case EMR_POLYLINE:
1485
- case EMR_POLYGON:
1486
- case EMR_POLYBEZIER:
1487
- case EMR_POLYBEZIERTO:
1488
- case EMR_POLYLINETO:
1489
- return handlePoly32(rCtx, recType, offset, dataOff, recSize);
1490
- // ---- 16-bit polys ----
1491
- case EMR_POLYLINE16:
1492
- case EMR_POLYGON16:
1493
- case EMR_POLYBEZIER16:
1494
- case EMR_POLYBEZIERTO16:
1495
- case EMR_POLYLINETO16:
1496
- return handlePoly16(rCtx, recType, offset, dataOff, recSize);
1497
- // ---- polypolyline / polypolygon ----
1498
- case EMR_POLYPOLYLINE:
1499
- if (recSize >= 28) {
1500
- handlePolyPolyline32(rCtx, offset, dataOff, recSize);
1501
- }
1502
- return true;
1503
- case EMR_POLYPOLYGON:
1504
- if (recSize >= 28) {
1505
- handlePolyPolygon32(rCtx, offset, dataOff, recSize);
1506
- }
1507
- return true;
1508
- case EMR_POLYPOLYGON16:
1509
- if (recSize >= 28) {
1510
- handlePolyPolygon16(rCtx, offset, dataOff, recSize);
1511
- }
1512
- return true;
1513
- // ---- path operations ----
1514
- case EMR_BEGINPATH:
1515
- rCtx.inPath = true;
1516
- ctx.beginPath();
1517
- return true;
1518
- case EMR_ENDPATH:
1519
- rCtx.inPath = false;
1520
- return true;
1521
- case EMR_CLOSEFIGURE:
1522
- ctx.closePath();
1523
- return true;
1524
- case EMR_FILLPATH:
1525
- applyBrush(ctx, state);
1526
- ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
1527
- return true;
1528
- case EMR_STROKEANDFILLPATH:
1529
- applyBrush(ctx, state);
1530
- ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
1531
- applyPen(ctx, state);
1532
- ctx.stroke();
1533
- return true;
1534
- case EMR_STROKEPATH:
1535
- applyPen(ctx, state);
1536
- ctx.stroke();
1537
- return true;
1538
- case EMR_SELECTCLIPPATH: {
1539
- const clipMode = recSize >= 12 ? rCtx.view.getUint32(dataOff, true) : 5;
1540
- try {
1541
- if (clipMode === 5) {
1542
- while (rCtx.clipSaveDepth > 0) {
1543
- ctx.restore();
1544
- rCtx.clipSaveDepth--;
1545
- }
1546
- ctx.save();
1547
- rCtx.clipSaveDepth++;
1548
- ctx.clip();
1549
- } else {
1550
- ctx.save();
1551
- rCtx.clipSaveDepth++;
1552
- ctx.clip();
1553
- }
1554
- } catch {
1555
- }
1556
- return true;
1557
- }
1558
- default:
1559
- return false;
1560
- }
1561
- }
1562
-
1563
- // src/emf-gdi-object-handlers.ts
1564
- function handleEmfObjectRecord(rCtx, recType, dataOff, recSize) {
1565
- const { view, state } = rCtx;
1566
- switch (recType) {
1567
- case EMR_CREATEPEN: {
1568
- if (recSize >= 28) {
1569
- const ihPen = view.getUint32(dataOff, true);
1570
- const penStyle = view.getUint32(dataOff + 4, true);
1571
- const widthX = view.getInt32(dataOff + 8, true);
1572
- const color = readColorRef(view, dataOff + 16);
1573
- rCtx.objectTable.set(ihPen, {
1574
- kind: "pen",
1575
- style: penStyle & 255,
1576
- widthX,
1577
- color
1578
- });
1579
- }
1580
- return true;
1581
- }
1582
- case EMR_EXTCREATEPEN: {
1583
- if (recSize >= 52) {
1584
- const ihPen = view.getUint32(dataOff, true);
1585
- const penStyle = view.getUint32(dataOff + 12, true);
1586
- const widthX = view.getInt32(dataOff + 16, true);
1587
- const color = readColorRef(view, dataOff + 24);
1588
- rCtx.objectTable.set(ihPen, {
1589
- kind: "pen",
1590
- style: penStyle & 255,
1591
- widthX,
1592
- color
1593
- });
1594
- }
1595
- return true;
1596
- }
1597
- case EMR_CREATEBRUSHINDIRECT: {
1598
- if (recSize >= 24) {
1599
- const ihBrush = view.getUint32(dataOff, true);
1600
- const brushStyle = view.getUint32(dataOff + 4, true);
1601
- const color = readColorRef(view, dataOff + 8);
1602
- rCtx.objectTable.set(ihBrush, {
1603
- kind: "brush",
1604
- style: brushStyle,
1605
- color
1606
- });
1607
- }
1608
- return true;
1609
- }
1610
- case EMR_EXTCREATEFONTINDIRECTW: {
1611
- if (recSize >= 332) {
1612
- const ihFont = view.getUint32(dataOff, true);
1613
- const height = view.getInt32(dataOff + 4, true);
1614
- const weight = view.getInt32(dataOff + 20, true);
1615
- const italic = view.getUint8(dataOff + 24);
1616
- const family = readUtf16LE(view, dataOff + 28, 32) || "sans-serif";
1617
- rCtx.objectTable.set(ihFont, {
1618
- kind: "font",
1619
- height: Math.abs(height),
1620
- weight,
1621
- italic: italic !== 0,
1622
- family
1623
- });
1624
- }
1625
- return true;
1626
- }
1627
- case EMR_SELECTOBJECT: {
1628
- if (recSize >= 12) {
1629
- const ihObject = view.getUint32(dataOff, true);
1630
- const obj = ihObject >= STOCK_OBJECT_BASE ? getStockObject(ihObject - STOCK_OBJECT_BASE) : rCtx.objectTable.get(ihObject) ?? null;
1631
- if (obj) {
1632
- switch (obj.kind) {
1633
- case "pen":
1634
- state.penStyle = obj.style;
1635
- state.penWidth = obj.widthX;
1636
- state.penColor = obj.color;
1637
- break;
1638
- case "brush":
1639
- state.brushStyle = obj.style;
1640
- state.brushColor = obj.color;
1641
- break;
1642
- case "font":
1643
- state.fontHeight = obj.height;
1644
- state.fontWeight = obj.weight;
1645
- state.fontItalic = obj.italic;
1646
- state.fontFamily = obj.family;
1647
- break;
1648
- }
1649
- }
1650
- }
1651
- return true;
1652
- }
1653
- case EMR_DELETEOBJECT: {
1654
- if (recSize >= 12) {
1655
- rCtx.objectTable.delete(view.getUint32(dataOff, true));
1656
- }
1657
- return true;
1658
- }
1659
- default:
1660
- return false;
1661
- }
1662
- }
1663
-
1664
- // src/emf-gdi-transform-handlers.ts
1665
- function handleCoordinateRecord(rCtx, recType, dataOff, recSize) {
1666
- const { view } = rCtx;
1667
- switch (recType) {
1668
- case EMR_SETWINDOWEXTEX: {
1669
- if (recSize >= 16) {
1670
- rCtx.windowExt.cx = view.getInt32(dataOff, true);
1671
- rCtx.windowExt.cy = view.getInt32(dataOff + 4, true);
1672
- activateGdiMappingMode(rCtx);
1673
- }
1674
- return true;
1675
- }
1676
- case EMR_SETWINDOWORGEX: {
1677
- if (recSize >= 16) {
1678
- rCtx.windowOrg.x = view.getInt32(dataOff, true);
1679
- rCtx.windowOrg.y = view.getInt32(dataOff + 4, true);
1680
- activateGdiMappingMode(rCtx);
1681
- }
1682
- return true;
1683
- }
1684
- case EMR_SETVIEWPORTEXTEX: {
1685
- if (recSize >= 16) {
1686
- rCtx.viewportExt.cx = view.getInt32(dataOff, true);
1687
- rCtx.viewportExt.cy = view.getInt32(dataOff + 4, true);
1688
- activateGdiMappingMode(rCtx);
1689
- }
1690
- return true;
1691
- }
1692
- case EMR_SETVIEWPORTORGEX: {
1693
- if (recSize >= 16) {
1694
- rCtx.viewportOrg.x = view.getInt32(dataOff, true);
1695
- rCtx.viewportOrg.y = view.getInt32(dataOff + 4, true);
1696
- activateGdiMappingMode(rCtx);
1697
- }
1698
- return true;
1699
- }
1700
- case EMR_SETMAPMODE: {
1701
- if (recSize >= 12) {
1702
- const mode = view.getUint32(dataOff, true);
1703
- if (mode === 8 || mode === 7) {
1704
- activateGdiMappingMode(rCtx);
1705
- }
1706
- }
1707
- return true;
1708
- }
1709
- case EMR_SCALEVIEWPORTEXTEX: {
1710
- if (recSize >= 24) {
1711
- const xNum = view.getInt32(dataOff, true);
1712
- const xDenom = view.getInt32(dataOff + 4, true);
1713
- const yNum = view.getInt32(dataOff + 8, true);
1714
- const yDenom = view.getInt32(dataOff + 12, true);
1715
- if (xDenom !== 0) {
1716
- rCtx.viewportExt.cx = Math.round(rCtx.viewportExt.cx * xNum / xDenom);
1717
- }
1718
- if (yDenom !== 0) {
1719
- rCtx.viewportExt.cy = Math.round(rCtx.viewportExt.cy * yNum / yDenom);
1720
- }
1721
- activateGdiMappingMode(rCtx);
1722
- }
1723
- return true;
1724
- }
1725
- case EMR_SCALEWINDOWEXTEX: {
1726
- if (recSize >= 24) {
1727
- const xNum = view.getInt32(dataOff, true);
1728
- const xDenom = view.getInt32(dataOff + 4, true);
1729
- const yNum = view.getInt32(dataOff + 8, true);
1730
- const yDenom = view.getInt32(dataOff + 12, true);
1731
- if (xDenom !== 0) {
1732
- rCtx.windowExt.cx = Math.round(rCtx.windowExt.cx * xNum / xDenom);
1733
- }
1734
- if (yDenom !== 0) {
1735
- rCtx.windowExt.cy = Math.round(rCtx.windowExt.cy * yNum / yDenom);
1736
- }
1737
- activateGdiMappingMode(rCtx);
1738
- }
1739
- return true;
1740
- }
1741
- default:
1742
- return false;
1743
- }
1744
- }
1745
- function handleWorldTransformRecord(rCtx, recType, dataOff, recSize) {
1746
- const { view, state } = rCtx;
1747
- switch (recType) {
1748
- case EMR_SETWORLDTRANSFORM: {
1749
- if (recSize >= 32) {
1750
- state.worldTransform = [
1751
- view.getFloat32(dataOff, true),
1752
- view.getFloat32(dataOff + 4, true),
1753
- view.getFloat32(dataOff + 8, true),
1754
- view.getFloat32(dataOff + 12, true),
1755
- view.getFloat32(dataOff + 16, true),
1756
- view.getFloat32(dataOff + 20, true)
1757
- ];
1758
- }
1759
- return true;
1760
- }
1761
- case EMR_MODIFYWORLDTRANSFORM: {
1762
- if (recSize >= 36) {
1763
- const mode = view.getUint32(dataOff + 24, true);
1764
- if (mode === 1) {
1765
- state.worldTransform = [1, 0, 0, 1, 0, 0];
1766
- } else if (mode === 2 || mode === 3) {
1767
- const xf = [
1768
- view.getFloat32(dataOff, true),
1769
- view.getFloat32(dataOff + 4, true),
1770
- view.getFloat32(dataOff + 8, true),
1771
- view.getFloat32(dataOff + 12, true),
1772
- view.getFloat32(dataOff + 16, true),
1773
- view.getFloat32(dataOff + 20, true)
1774
- ];
1775
- const [a1, b1, c1, d1, e1, f1] = state.worldTransform;
1776
- if (mode === 2) {
1777
- state.worldTransform = [
1778
- xf[0] * a1 + xf[1] * c1,
1779
- xf[0] * b1 + xf[1] * d1,
1780
- xf[2] * a1 + xf[3] * c1,
1781
- xf[2] * b1 + xf[3] * d1,
1782
- xf[4] * a1 + xf[5] * c1 + e1,
1783
- xf[4] * b1 + xf[5] * d1 + f1
1784
- ];
1785
- } else {
1786
- state.worldTransform = [
1787
- a1 * xf[0] + b1 * xf[2],
1788
- a1 * xf[1] + b1 * xf[3],
1789
- c1 * xf[0] + d1 * xf[2],
1790
- c1 * xf[1] + d1 * xf[3],
1791
- e1 * xf[0] + f1 * xf[2] + xf[4],
1792
- e1 * xf[1] + f1 * xf[3] + xf[5]
1793
- ];
1794
- }
1795
- }
1796
- }
1797
- return true;
1798
- }
1799
- default:
1800
- return false;
1801
- }
1802
- }
1803
- function handleEmfTransformRecord(rCtx, recType, dataOff, recSize) {
1804
- return handleCoordinateRecord(rCtx, recType, dataOff, recSize) || handleWorldTransformRecord(rCtx, recType, dataOff, recSize);
1805
- }
1806
-
1807
- // src/emf-types.ts
1808
- function defaultState() {
1809
- return {
1810
- penColor: "#000000",
1811
- penWidth: 1,
1812
- penStyle: 0,
1813
- brushColor: "#ffffff",
1814
- brushStyle: 0,
1815
- textColor: "#000000",
1816
- bkColor: "#ffffff",
1817
- bkMode: 1,
1818
- fontHeight: 12,
1819
- fontWeight: 400,
1820
- fontItalic: false,
1821
- fontFamily: "sans-serif",
1822
- curX: 0,
1823
- curY: 0,
1824
- polyFillMode: 1,
1825
- textAlign: 0,
1826
- worldTransform: [1, 0, 0, 1, 0, 0]
1827
- };
1828
- }
1829
- function cloneState(s) {
1830
- return {
1831
- ...s,
1832
- worldTransform: [...s.worldTransform]
1833
- };
1834
- }
1835
- function createEmfPlusState() {
1836
- return {
1837
- objectTable: /* @__PURE__ */ new Map(),
1838
- worldTransform: [1, 0, 0, 1, 0, 0],
1839
- saveStack: [],
1840
- saveIdMap: /* @__PURE__ */ new Map()
1841
- };
1842
- }
1843
-
1844
- // src/emf-gdi-state-handlers.ts
1845
- function handleEmfGdiStateRecord(rCtx, recType, _offset, dataOff, recSize) {
1846
- if (handleEmfTransformRecord(rCtx, recType, dataOff, recSize)) {
1847
- return true;
1848
- }
1849
- if (handleEmfObjectRecord(rCtx, recType, dataOff, recSize)) {
1850
- return true;
1851
- }
1852
- const { ctx, view, state } = rCtx;
1853
- switch (recType) {
1854
- // ---- save / restore ----
1855
- case EMR_SAVEDC: {
1856
- while (rCtx.clipSaveDepth > 0) {
1857
- ctx.restore();
1858
- rCtx.clipSaveDepth--;
1859
- }
1860
- rCtx.stateStack.push(cloneState(state));
1861
- ctx.save();
1862
- return true;
1863
- }
1864
- case EMR_RESTOREDC: {
1865
- if (recSize >= 12) {
1866
- while (rCtx.clipSaveDepth > 0) {
1867
- ctx.restore();
1868
- rCtx.clipSaveDepth--;
1869
- }
1870
- let rel = view.getInt32(dataOff, true);
1871
- if (rel < 0) {
1872
- rel = rCtx.stateStack.length + rel;
1873
- }
1874
- while (rCtx.stateStack.length > rel && rCtx.stateStack.length > 0) {
1875
- rCtx.stateStack.pop();
1876
- ctx.restore();
1877
- }
1878
- const restored = rCtx.stateStack.pop();
1879
- if (restored) {
1880
- Object.assign(state, restored);
1881
- ctx.restore();
1882
- }
1883
- }
1884
- return true;
1885
- }
1886
- // ---- drawing mode / color settings ----
1887
- case EMR_SETTEXTCOLOR: {
1888
- if (recSize >= 12) {
1889
- state.textColor = readColorRef(view, dataOff);
1890
- }
1891
- return true;
1892
- }
1893
- case EMR_SETBKCOLOR: {
1894
- if (recSize >= 12) {
1895
- state.bkColor = readColorRef(view, dataOff);
1896
- }
1897
- return true;
1898
- }
1899
- case EMR_SETBKMODE: {
1900
- if (recSize >= 12) {
1901
- state.bkMode = view.getUint32(dataOff, true);
1902
- }
1903
- return true;
1904
- }
1905
- case EMR_SETPOLYFILLMODE: {
1906
- if (recSize >= 12) {
1907
- state.polyFillMode = view.getUint32(dataOff, true);
1908
- }
1909
- return true;
1910
- }
1911
- case EMR_SETROP2:
1912
- case EMR_SETSTRETCHBLTMODE:
1913
- case EMR_SETMITERLIMIT:
1914
- case EMR_SETTEXTALIGN: {
1915
- if (recType === EMR_SETTEXTALIGN && recSize >= 12) {
1916
- state.textAlign = view.getUint32(dataOff, true);
1917
- }
1918
- return true;
1919
- }
1920
- default:
1921
- return false;
1922
- }
1923
- }
1924
-
1925
- // src/emf-plus-read-helpers.ts
1926
- function readRectFromView(view, offset, compressed) {
1927
- if (compressed) {
1928
- return {
1929
- x: view.getInt16(offset, true),
1930
- y: view.getInt16(offset + 2, true),
1931
- w: view.getInt16(offset + 4, true),
1932
- h: view.getInt16(offset + 6, true)
1933
- };
1934
- }
1935
- return {
1936
- x: view.getFloat32(offset, true),
1937
- y: view.getFloat32(offset + 4, true),
1938
- w: view.getFloat32(offset + 8, true),
1939
- h: view.getFloat32(offset + 12, true)
1940
- };
1941
- }
1942
- function readPointFromView(view, offset, compressed) {
1943
- if (compressed) {
1944
- return {
1945
- x: view.getInt16(offset, true),
1946
- y: view.getInt16(offset + 2, true)
1947
- };
1948
- }
1949
- return {
1950
- x: view.getFloat32(offset, true),
1951
- y: view.getFloat32(offset + 4, true)
1952
- };
1953
- }
1954
-
1955
- // src/emf-plus-path.ts
1956
- function parseEmfPlusPath(data, off, maxLen) {
1957
- if (maxLen < 12) {
1958
- return null;
1959
- }
1960
- data.getUint32(off, true);
1961
- const pointCount = data.getUint32(off + 4, true);
1962
- const pathFlags = data.getUint32(off + 8, true);
1963
- if (pointCount === 0 || pointCount > 1e5) {
1964
- return null;
1965
- }
1966
- const compressed = (pathFlags & 16384) !== 0;
1967
- const pointSize = compressed ? 4 : 8;
1968
- const pointsBytes = pointCount * pointSize;
1969
- const typesBytes = pointCount;
1970
- const neededAfterHeader = pointsBytes + typesBytes;
1971
- if (12 + neededAfterHeader > maxLen) {
1972
- return null;
1973
- }
1974
- const points = [];
1975
- let pOff = off + 12;
1976
- for (let i = 0; i < pointCount; i++) {
1977
- if (compressed) {
1978
- points.push({
1979
- x: data.getInt16(pOff, true),
1980
- y: data.getInt16(pOff + 2, true)
1981
- });
1982
- pOff += 4;
1983
- } else {
1984
- points.push({
1985
- x: data.getFloat32(pOff, true),
1986
- y: data.getFloat32(pOff + 4, true)
1987
- });
1988
- pOff += 8;
1989
- }
1990
- }
1991
- const alignedPOff = pOff + 3 & -4;
1992
- const types = new Uint8Array(data.buffer, data.byteOffset + alignedPOff, pointCount);
1993
- return { kind: "plus-path", points, types: new Uint8Array(types) };
1994
- }
1995
- function replayEmfPlusPath(ctx, path) {
1996
- ctx.beginPath();
1997
- const pts = path.points;
1998
- const types = path.types;
1999
- let i = 0;
2000
- while (i < pts.length) {
2001
- const t = types[i] & 15;
2002
- const close = (types[i] & 128) !== 0;
2003
- if (t === 0) {
2004
- ctx.moveTo(pts[i].x, pts[i].y);
2005
- i++;
2006
- } else if (t === 1) {
2007
- ctx.lineTo(pts[i].x, pts[i].y);
2008
- i++;
2009
- } else if (t === 3) {
2010
- if (i + 2 < pts.length) {
2011
- ctx.bezierCurveTo(
2012
- pts[i].x,
2013
- pts[i].y,
2014
- pts[i + 1].x,
2015
- pts[i + 1].y,
2016
- pts[i + 2].x,
2017
- pts[i + 2].y
2018
- );
2019
- if ((types[i + 2] & 128) !== 0) {
2020
- ctx.closePath();
2021
- }
2022
- i += 3;
2023
- continue;
2024
- } else {
2025
- break;
2026
- }
2027
- } else {
2028
- ctx.lineTo(pts[i].x, pts[i].y);
2029
- i++;
2030
- }
2031
- if (close) {
2032
- ctx.closePath();
2033
- }
2034
- }
2035
- }
2036
-
2037
- // src/emf-plus-state-handlers.ts
2038
- function multiplyMatrix(m1, m2) {
2039
- return [
2040
- m1[0] * m2[0] + m1[1] * m2[2],
2041
- m1[0] * m2[1] + m1[1] * m2[3],
2042
- m1[2] * m2[0] + m1[3] * m2[2],
2043
- m1[2] * m2[1] + m1[3] * m2[3],
2044
- m1[4] * m2[0] + m1[5] * m2[2] + m2[4],
2045
- m1[4] * m2[1] + m1[5] * m2[3] + m2[5]
2046
- ];
2047
- }
2048
- function resolveBrushColor(rCtx, flags, brushIdOrColor) {
2049
- if (flags & 32768) {
2050
- return argbToRgba(brushIdOrColor);
2051
- }
2052
- const obj = rCtx.objectTable.get(brushIdOrColor & 255);
2053
- if (obj && obj.kind === "plus-brush") {
2054
- return obj.color;
2055
- }
2056
- return "rgba(0,0,0,1)";
2057
- }
2058
- function getPageUnitMultiplier(pageUnit, pageScale) {
2059
- const DPI = 96;
2060
- let unitToPixel;
2061
- switch (pageUnit) {
2062
- case 3:
2063
- unitToPixel = DPI / 72;
2064
- break;
2065
- // Point
2066
- case 4:
2067
- unitToPixel = DPI;
2068
- break;
2069
- // Inch
2070
- case 5:
2071
- unitToPixel = DPI / 300;
2072
- break;
2073
- // Document
2074
- case 6:
2075
- unitToPixel = DPI / 25.4;
2076
- break;
2077
- // Millimeter
2078
- default:
2079
- unitToPixel = 1;
2080
- break;
2081
- }
2082
- return unitToPixel * pageScale;
2083
- }
2084
- function applyPlusWorldTransform(rCtx) {
2085
- const wt = rCtx.worldTransform;
2086
- const m = getPageUnitMultiplier(rCtx.pageUnit, rCtx.pageScale);
2087
- const d = rCtx.dpiScale;
2088
- rCtx.ctx.setTransform(
2089
- wt[0] * m * d,
2090
- wt[1] * m * d,
2091
- wt[2] * m * d,
2092
- wt[3] * m * d,
2093
- wt[4] * m * d,
2094
- wt[5] * m * d
2095
- );
2096
- }
2097
- function pushState(rCtx, stackId) {
2098
- rCtx.saveStack.push({
2099
- transform: [...rCtx.worldTransform]
2100
- });
2101
- rCtx.saveIdMap.set(stackId, rCtx.saveStack.length - 1);
2102
- }
2103
- function popState(rCtx, stackId) {
2104
- const idx = rCtx.saveIdMap.get(stackId);
2105
- if (idx !== void 0 && idx < rCtx.saveStack.length) {
2106
- rCtx.worldTransform = [...rCtx.saveStack[idx].transform];
2107
- rCtx.saveStack.length = idx;
2108
- const newMap = /* @__PURE__ */ new Map();
2109
- for (const [k, v] of rCtx.saveIdMap) {
2110
- if (v < idx) {
2111
- newMap.set(k, v);
2112
- }
2113
- }
2114
- rCtx.saveIdMap = newMap;
2115
- }
2116
- }
2117
- function ensureClipSave(rCtx) {
2118
- if (rCtx.clipSaveDepth === 0) {
2119
- rCtx.ctx.save();
2120
- rCtx.clipSaveDepth = 1;
2121
- }
2122
- }
2123
- function resetClipState(rCtx) {
2124
- if (rCtx.clipSaveDepth > 0) {
2125
- rCtx.ctx.restore();
2126
- rCtx.clipSaveDepth = 0;
2127
- }
2128
- }
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
- }
2155
- }
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;
2161
- }
2162
- switch (node.type) {
2163
- case "rect":
2164
- ctx.rect(node.x, node.y, node.width, node.height);
2165
- break;
2166
- case "path":
2167
- replayEmfPlusPath(ctx, node.path);
2168
- break;
2169
- case "infinite":
2170
- ctx.rect(-1e6, -1e6, 2e6, 2e6);
2171
- break;
2172
- 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;
2190
- }
2191
- }
2192
- function applyRegionNodeClip(ctx, rootNode, combineMode) {
2193
- ctx.beginPath();
2194
- traceRegionNodePath(ctx, rootNode);
2195
- try {
2196
- ctx.clip();
2197
- } catch {
2198
- }
2199
- }
2200
- function handleEmfPlusStateRecord(rCtx, recType, recFlags, dataOff, recDataSize) {
2201
- const { view } = rCtx;
2202
- switch (recType) {
2203
- // ---- transforms ----
2204
- case EMFPLUS_SETWORLDTRANSFORM: {
2205
- if (recDataSize >= 24) {
2206
- rCtx.worldTransform = [
2207
- view.getFloat32(dataOff, true),
2208
- view.getFloat32(dataOff + 4, true),
2209
- view.getFloat32(dataOff + 8, true),
2210
- view.getFloat32(dataOff + 12, true),
2211
- view.getFloat32(dataOff + 16, true),
2212
- view.getFloat32(dataOff + 20, true)
2213
- ];
2214
- }
2215
- return true;
2216
- }
2217
- case EMFPLUS_RESETWORLDTRANSFORM: {
2218
- rCtx.worldTransform = [1, 0, 0, 1, 0, 0];
2219
- return true;
2220
- }
2221
- case EMFPLUS_MULTIPLYWORLDTRANSFORM: {
2222
- if (recDataSize >= 24) {
2223
- const xf = [
2224
- view.getFloat32(dataOff, true),
2225
- view.getFloat32(dataOff + 4, true),
2226
- view.getFloat32(dataOff + 8, true),
2227
- view.getFloat32(dataOff + 12, true),
2228
- view.getFloat32(dataOff + 16, true),
2229
- view.getFloat32(dataOff + 20, true)
2230
- ];
2231
- if (recFlags & 8192) {
2232
- rCtx.worldTransform = multiplyMatrix(rCtx.worldTransform, xf);
2233
- } else {
2234
- rCtx.worldTransform = multiplyMatrix(xf, rCtx.worldTransform);
2235
- }
2236
- }
2237
- return true;
2238
- }
2239
- case EMFPLUS_TRANSLATEWORLDTRANSFORM: {
2240
- if (recDataSize >= 8) {
2241
- const dx = view.getFloat32(dataOff, true);
2242
- const dy = view.getFloat32(dataOff + 4, true);
2243
- const xf = [1, 0, 0, 1, dx, dy];
2244
- if (recFlags & 8192) {
2245
- rCtx.worldTransform = multiplyMatrix(rCtx.worldTransform, xf);
2246
- } else {
2247
- rCtx.worldTransform = multiplyMatrix(xf, rCtx.worldTransform);
2248
- }
2249
- }
2250
- return true;
2251
- }
2252
- case EMFPLUS_SCALEWORLDTRANSFORM: {
2253
- if (recDataSize >= 8) {
2254
- const sx = view.getFloat32(dataOff, true);
2255
- const sy = view.getFloat32(dataOff + 4, true);
2256
- const xf = [sx, 0, 0, sy, 0, 0];
2257
- if (recFlags & 8192) {
2258
- rCtx.worldTransform = multiplyMatrix(rCtx.worldTransform, xf);
2259
- } else {
2260
- rCtx.worldTransform = multiplyMatrix(xf, rCtx.worldTransform);
2261
- }
2262
- }
2263
- return true;
2264
- }
2265
- case EMFPLUS_ROTATEWORLDTRANSFORM: {
2266
- if (recDataSize >= 4) {
2267
- const angle = view.getFloat32(dataOff, true) * Math.PI / 180;
2268
- const cos = Math.cos(angle);
2269
- const sin = Math.sin(angle);
2270
- const xf = [cos, sin, -sin, cos, 0, 0];
2271
- if (recFlags & 8192) {
2272
- rCtx.worldTransform = multiplyMatrix(rCtx.worldTransform, xf);
2273
- } else {
2274
- rCtx.worldTransform = multiplyMatrix(xf, rCtx.worldTransform);
2275
- }
2276
- }
2277
- return true;
2278
- }
2279
- // ---- save / restore ----
2280
- case EMFPLUS_SAVE: {
2281
- if (recDataSize >= 4) {
2282
- pushState(rCtx, view.getUint32(dataOff, true));
2283
- }
2284
- return true;
2285
- }
2286
- case EMFPLUS_RESTORE: {
2287
- if (recDataSize >= 4) {
2288
- popState(rCtx, view.getUint32(dataOff, true));
2289
- }
2290
- return true;
2291
- }
2292
- // ---- clipping ----
2293
- case EMFPLUS_SETCLIPRECT: {
2294
- if (recDataSize >= 16) {
2295
- const combineMode = recFlags >> 8 & 15;
2296
- const cx = view.getFloat32(dataOff, true);
2297
- const cy = view.getFloat32(dataOff + 4, true);
2298
- const cw = view.getFloat32(dataOff + 8, true);
2299
- 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
- });
2309
- }
2310
- return true;
2311
- }
2312
- case EMFPLUS_RESETCLIP: {
2313
- resetClipState(rCtx);
2314
- ensureClipSave(rCtx);
2315
- return true;
2316
- }
2317
- case EMFPLUS_SETCLIPREGION: {
2318
- const regionId = recFlags & 255;
2319
- const regionObj = rCtx.objectTable.get(regionId);
2320
- if (regionObj && regionObj.kind === "plus-region" && regionObj.nodes.length > 0) {
2321
- applyPlusWorldTransform(rCtx);
2322
- const rootNode = regionObj.nodes[0];
2323
- applyRegionNodeClip(rCtx.ctx, rootNode);
2324
- }
2325
- return true;
2326
- }
2327
- case EMFPLUS_SETCLIPPATH: {
2328
- const pathId = recFlags & 255;
2329
- const combineMode = recFlags >> 8 & 15;
2330
- const pathObj = rCtx.objectTable.get(pathId);
2331
- 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
- });
2341
- }
2342
- return true;
2343
- }
2344
- case EMFPLUS_OFFSETCLIP: {
2345
- if (recDataSize >= 8) {
2346
- view.getFloat32(dataOff, true);
2347
- view.getFloat32(dataOff + 4, true);
2348
- }
2349
- return true;
2350
- }
2351
- // ---- containers ----
2352
- case EMFPLUS_BEGINCONTAINERNOPARAMS: {
2353
- if (recDataSize >= 4) {
2354
- pushState(rCtx, view.getUint32(dataOff, true));
2355
- }
2356
- return true;
2357
- }
2358
- case EMFPLUS_ENDCONTAINER: {
2359
- if (recDataSize >= 4) {
2360
- popState(rCtx, view.getUint32(dataOff, true));
2361
- }
2362
- return true;
2363
- }
2364
- // ---- page transform ----
2365
- case EMFPLUS_SETPAGETRANSFORM: {
2366
- const pageUnit = recFlags & 255;
2367
- const pageScale = recDataSize >= 4 ? view.getFloat32(dataOff, true) : 1;
2368
- rCtx.pageUnit = pageUnit;
2369
- rCtx.pageScale = pageScale;
2370
- return true;
2371
- }
2372
- // ---- rendering hints (accepted, ignored) ----
2373
- case EMFPLUS_SETANTIALIASMODE:
2374
- case EMFPLUS_SETTEXTRENDERINGHINT:
2375
- case EMFPLUS_SETINTERPOLATIONMODE:
2376
- case EMFPLUS_SETPIXELOFFSETMODE:
2377
- case EMFPLUS_SETCOMPOSITINGQUALITY:
2378
- return true;
2379
- default:
2380
- return false;
2381
- }
2382
- }
2383
-
2384
- // src/emf-plus-draw-handlers.ts
2385
- function applyEmfPlusPen(ctx, pen) {
2386
- ctx.strokeStyle = pen.color;
2387
- ctx.lineWidth = pen.width;
2388
- const w = pen.width || 1;
2389
- switch (pen.dashStyle) {
2390
- case 1:
2391
- ctx.setLineDash([w * 3, Number(w)]);
2392
- break;
2393
- // Dash
2394
- case 2:
2395
- ctx.setLineDash([Number(w), Number(w)]);
2396
- break;
2397
- // Dot
2398
- case 3:
2399
- ctx.setLineDash([w * 3, Number(w), Number(w), Number(w)]);
2400
- break;
2401
- // DashDot
2402
- case 4:
2403
- ctx.setLineDash([w * 3, Number(w), Number(w), Number(w), Number(w), Number(w)]);
2404
- break;
2405
- // DashDotDot
2406
- default:
2407
- ctx.setLineDash([]);
2408
- break;
2409
- }
2410
- }
2411
- function handleEmfPlusDrawRecord(rCtx, recType, recFlags, dataOff, recDataSize) {
2412
- const { ctx, view, objectTable } = rCtx;
2413
- switch (recType) {
2414
- case EMFPLUS_FILLRECTS: {
2415
- if (recDataSize >= 8) {
2416
- const brushVal = view.getUint32(dataOff, true);
2417
- const count = view.getUint32(dataOff + 4, true);
2418
- const compressed = (recFlags & 16384) !== 0;
2419
- const rectSize = compressed ? 8 : 16;
2420
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
2421
- applyPlusWorldTransform(rCtx);
2422
- let rOff = dataOff + 8;
2423
- for (let i = 0; i < count && rOff + rectSize <= dataOff + recDataSize; i++) {
2424
- const { x, y, w, h } = readRectFromView(view, rOff, compressed);
2425
- ctx.fillRect(x, y, w, h);
2426
- rOff += rectSize;
2427
- }
2428
- }
2429
- return true;
2430
- }
2431
- case EMFPLUS_DRAWRECTS: {
2432
- if (recDataSize >= 4) {
2433
- const penId = recFlags & 255;
2434
- const pen = objectTable.get(penId);
2435
- const count = view.getUint32(dataOff, true);
2436
- const compressed = (recFlags & 16384) !== 0;
2437
- const rectSize = compressed ? 8 : 16;
2438
- if (pen && pen.kind === "plus-pen") {
2439
- applyEmfPlusPen(ctx, pen);
2440
- }
2441
- applyPlusWorldTransform(rCtx);
2442
- let rOff = dataOff + 4;
2443
- for (let i = 0; i < count && rOff + rectSize <= dataOff + recDataSize; i++) {
2444
- const { x, y, w, h } = readRectFromView(view, rOff, compressed);
2445
- ctx.strokeRect(x, y, w, h);
2446
- rOff += rectSize;
2447
- }
2448
- }
2449
- return true;
2450
- }
2451
- case EMFPLUS_FILLELLIPSE: {
2452
- if (recDataSize >= 12) {
2453
- const brushVal = view.getUint32(dataOff, true);
2454
- const compressed = (recFlags & 16384) !== 0;
2455
- let x, y, w, h;
2456
- if (compressed) {
2457
- x = view.getInt16(dataOff + 4, true);
2458
- y = view.getInt16(dataOff + 6, true);
2459
- w = view.getInt16(dataOff + 8, true);
2460
- h = view.getInt16(dataOff + 10, true);
2461
- } else {
2462
- if (recDataSize < 20) {
2463
- return true;
2464
- }
2465
- x = view.getFloat32(dataOff + 4, true);
2466
- y = view.getFloat32(dataOff + 8, true);
2467
- w = view.getFloat32(dataOff + 12, true);
2468
- h = view.getFloat32(dataOff + 16, true);
2469
- }
2470
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
2471
- applyPlusWorldTransform(rCtx);
2472
- ctx.beginPath();
2473
- ctx.ellipse(x + w / 2, y + h / 2, Math.abs(w) / 2, Math.abs(h) / 2, 0, 0, Math.PI * 2);
2474
- ctx.fill();
2475
- }
2476
- return true;
2477
- }
2478
- case EMFPLUS_DRAWELLIPSE: {
2479
- const penId = recFlags & 255;
2480
- const pen = objectTable.get(penId);
2481
- const compressed = (recFlags & 16384) !== 0;
2482
- let x, y, w, h;
2483
- if (compressed && recDataSize >= 8) {
2484
- x = view.getInt16(dataOff, true);
2485
- y = view.getInt16(dataOff + 2, true);
2486
- w = view.getInt16(dataOff + 4, true);
2487
- h = view.getInt16(dataOff + 6, true);
2488
- } else if (!compressed && recDataSize >= 16) {
2489
- x = view.getFloat32(dataOff, true);
2490
- y = view.getFloat32(dataOff + 4, true);
2491
- w = view.getFloat32(dataOff + 8, true);
2492
- h = view.getFloat32(dataOff + 12, true);
2493
- } else {
2494
- return true;
2495
- }
2496
- if (pen && pen.kind === "plus-pen") {
2497
- applyEmfPlusPen(ctx, pen);
2498
- }
2499
- applyPlusWorldTransform(rCtx);
2500
- ctx.beginPath();
2501
- ctx.ellipse(x + w / 2, y + h / 2, Math.abs(w) / 2, Math.abs(h) / 2, 0, 0, Math.PI * 2);
2502
- ctx.stroke();
2503
- return true;
2504
- }
2505
- case EMFPLUS_FILLPIE:
2506
- case EMFPLUS_DRAWPIE:
2507
- case EMFPLUS_DRAWARC: {
2508
- const isFill = recType === EMFPLUS_FILLPIE;
2509
- const minSize = isFill ? 12 : 8;
2510
- if (recDataSize < minSize) {
2511
- return true;
2512
- }
2513
- let aOff = dataOff;
2514
- if (isFill) {
2515
- const brushVal = view.getUint32(aOff, true);
2516
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
2517
- aOff += 4;
2518
- }
2519
- const startAngle = view.getFloat32(aOff, true) * Math.PI / 180;
2520
- const sweepAngle = view.getFloat32(aOff + 4, true) * Math.PI / 180;
2521
- aOff += 8;
2522
- const compressed = (recFlags & 16384) !== 0;
2523
- let x, y, w, h;
2524
- if (compressed && aOff + 8 <= dataOff + recDataSize) {
2525
- x = view.getInt16(aOff, true);
2526
- y = view.getInt16(aOff + 2, true);
2527
- w = view.getInt16(aOff + 4, true);
2528
- h = view.getInt16(aOff + 6, true);
2529
- } else if (!compressed && aOff + 16 <= dataOff + recDataSize) {
2530
- x = view.getFloat32(aOff, true);
2531
- y = view.getFloat32(aOff + 4, true);
2532
- w = view.getFloat32(aOff + 8, true);
2533
- h = view.getFloat32(aOff + 12, true);
2534
- } else {
2535
- return true;
2536
- }
2537
- if (recType !== EMFPLUS_FILLPIE) {
2538
- const penId = recFlags & 255;
2539
- const pen = objectTable.get(penId);
2540
- if (pen && pen.kind === "plus-pen") {
2541
- applyEmfPlusPen(ctx, pen);
2542
- }
2543
- }
2544
- applyPlusWorldTransform(rCtx);
2545
- ctx.beginPath();
2546
- const cx = x + w / 2;
2547
- const cy = y + h / 2;
2548
- const rx = Math.abs(w) / 2;
2549
- const ry = Math.abs(h) / 2;
2550
- if (isFill) {
2551
- ctx.moveTo(cx, cy);
2552
- }
2553
- ctx.ellipse(cx, cy, rx, ry, 0, startAngle, startAngle + sweepAngle, sweepAngle < 0);
2554
- if (isFill) {
2555
- ctx.closePath();
2556
- ctx.fill();
2557
- } else {
2558
- ctx.stroke();
2559
- }
2560
- return true;
2561
- }
2562
- case EMFPLUS_DRAWLINES: {
2563
- if (recDataSize >= 4) {
2564
- const penId = recFlags & 255;
2565
- const pen = objectTable.get(penId);
2566
- const count = view.getUint32(dataOff, true);
2567
- const compressed = (recFlags & 16384) !== 0;
2568
- const ptSize = compressed ? 4 : 8;
2569
- if (pen && pen.kind === "plus-pen") {
2570
- applyEmfPlusPen(ctx, pen);
2571
- }
2572
- applyPlusWorldTransform(rCtx);
2573
- ctx.beginPath();
2574
- let pOff = dataOff + 4;
2575
- for (let i = 0; i < count && pOff + ptSize <= dataOff + recDataSize; i++) {
2576
- const pt = readPointFromView(view, pOff, compressed);
2577
- if (i === 0) {
2578
- ctx.moveTo(pt.x, pt.y);
2579
- } else {
2580
- ctx.lineTo(pt.x, pt.y);
2581
- }
2582
- pOff += ptSize;
2583
- }
2584
- if (recFlags & 8192) {
2585
- ctx.closePath();
2586
- }
2587
- ctx.stroke();
2588
- }
2589
- return true;
2590
- }
2591
- case EMFPLUS_FILLPOLYGON: {
2592
- if (recDataSize >= 8) {
2593
- const brushVal = view.getUint32(dataOff, true);
2594
- const count = view.getUint32(dataOff + 4, true);
2595
- const compressed = (recFlags & 16384) !== 0;
2596
- const ptSize = compressed ? 4 : 8;
2597
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
2598
- applyPlusWorldTransform(rCtx);
2599
- ctx.beginPath();
2600
- let pOff = dataOff + 8;
2601
- for (let i = 0; i < count && pOff + ptSize <= dataOff + recDataSize; i++) {
2602
- const pt = readPointFromView(view, pOff, compressed);
2603
- if (i === 0) {
2604
- ctx.moveTo(pt.x, pt.y);
2605
- } else {
2606
- ctx.lineTo(pt.x, pt.y);
2607
- }
2608
- pOff += ptSize;
2609
- }
2610
- ctx.closePath();
2611
- ctx.fill();
2612
- }
2613
- return true;
2614
- }
2615
- default:
2616
- return false;
2617
- }
2618
- }
2619
-
2620
- // src/emf-plus-bitmap-decoder.ts
2621
- var PIXELFORMAT_24BPP_RGB = 137224;
2622
- var PIXELFORMAT_32BPP_RGB = 139273;
2623
- var PIXELFORMAT_32BPP_ARGB = 2498570;
2624
- var PIXELFORMAT_32BPP_PARGB = 925707;
2625
- function decodeEmfPlusBitmapPixels(view, pixelStart, width, height, stride, pixelFormat) {
2626
- const absStride = Math.abs(stride);
2627
- const topDown = stride > 0;
2628
- const rowBytes = width * 4;
2629
- const bmpRowStride = rowBytes + 3 & -4;
2630
- const pixelDataSize = bmpRowStride * height;
2631
- const bmpData = new Uint8Array(pixelDataSize);
2632
- for (let y = 0; y < height; y++) {
2633
- const srcRow = topDown ? y : height - 1 - y;
2634
- const rowOff = pixelStart + srcRow * absStride;
2635
- const dstRow = (height - 1 - y) * bmpRowStride;
2636
- switch (pixelFormat) {
2637
- case PIXELFORMAT_32BPP_ARGB:
2638
- case PIXELFORMAT_32BPP_PARGB: {
2639
- for (let x = 0; x < width; x++) {
2640
- const off = rowOff + x * 4;
2641
- if (off + 3 >= view.byteLength) {
2642
- break;
2643
- }
2644
- let b = view.getUint8(off);
2645
- let g = view.getUint8(off + 1);
2646
- let r = view.getUint8(off + 2);
2647
- const a = view.getUint8(off + 3);
2648
- if (pixelFormat === PIXELFORMAT_32BPP_PARGB && a > 0 && a < 255) {
2649
- r = Math.min(255, Math.round(r * 255 / a));
2650
- g = Math.min(255, Math.round(g * 255 / a));
2651
- b = Math.min(255, Math.round(b * 255 / a));
2652
- }
2653
- const di = dstRow + x * 4;
2654
- bmpData[di] = b;
2655
- bmpData[di + 1] = g;
2656
- bmpData[di + 2] = r;
2657
- bmpData[di + 3] = a;
2658
- }
2659
- break;
2660
- }
2661
- case PIXELFORMAT_32BPP_RGB: {
2662
- for (let x = 0; x < width; x++) {
2663
- const off = rowOff + x * 4;
2664
- if (off + 3 >= view.byteLength) {
2665
- break;
2666
- }
2667
- const di = dstRow + x * 4;
2668
- bmpData[di] = view.getUint8(off);
2669
- bmpData[di + 1] = view.getUint8(off + 1);
2670
- bmpData[di + 2] = view.getUint8(off + 2);
2671
- bmpData[di + 3] = 255;
2672
- }
2673
- break;
2674
- }
2675
- case PIXELFORMAT_24BPP_RGB: {
2676
- for (let x = 0; x < width; x++) {
2677
- const off = rowOff + x * 3;
2678
- if (off + 2 >= view.byteLength) {
2679
- break;
2680
- }
2681
- const di = dstRow + x * 4;
2682
- bmpData[di] = view.getUint8(off);
2683
- bmpData[di + 1] = view.getUint8(off + 1);
2684
- bmpData[di + 2] = view.getUint8(off + 2);
2685
- bmpData[di + 3] = 255;
2686
- }
2687
- break;
2688
- }
2689
- default:
2690
- return null;
2691
- }
2692
- }
2693
- const fileHeaderSize = 14;
2694
- const dibHeaderSize = 108;
2695
- const fileSize = fileHeaderSize + dibHeaderSize + pixelDataSize;
2696
- const bmpFile = new ArrayBuffer(fileSize);
2697
- const bmpView = new DataView(bmpFile);
2698
- const bmpBytes = new Uint8Array(bmpFile);
2699
- bmpView.setUint8(0, 66);
2700
- bmpView.setUint8(1, 77);
2701
- bmpView.setUint32(2, fileSize, true);
2702
- bmpView.setUint32(6, 0, true);
2703
- bmpView.setUint32(10, fileHeaderSize + dibHeaderSize, true);
2704
- bmpView.setUint32(14, dibHeaderSize, true);
2705
- bmpView.setInt32(18, width, true);
2706
- bmpView.setInt32(22, height, true);
2707
- bmpView.setUint16(26, 1, true);
2708
- bmpView.setUint16(28, 32, true);
2709
- bmpView.setUint32(30, 3, true);
2710
- bmpView.setUint32(34, pixelDataSize, true);
2711
- bmpView.setInt32(38, 2835, true);
2712
- bmpView.setInt32(42, 2835, true);
2713
- bmpView.setUint32(46, 0, true);
2714
- bmpView.setUint32(50, 0, true);
2715
- bmpView.setUint32(54, 16711680, true);
2716
- bmpView.setUint32(58, 65280, true);
2717
- bmpView.setUint32(62, 255, true);
2718
- bmpView.setUint32(66, 4278190080, true);
2719
- bmpView.setUint32(70, 1934772034, true);
2720
- bmpBytes.set(bmpData, fileHeaderSize + dibHeaderSize);
2721
- return bmpFile;
2722
- }
2723
-
2724
- // src/emf-plus-object-complex.ts
2725
- function parseEmfPlusPenObject(view, dataOff, recDataSize) {
2726
- if (recDataSize < 20) {
2727
- return null;
2728
- }
2729
- const penFlags = view.getUint32(dataOff + 4, true);
2730
- const penWidth = view.getFloat32(dataOff + 16, true);
2731
- let brushOff = dataOff + 20;
2732
- const flagSizes = [
2733
- [1, 4],
2734
- // Transform (actually 24 bytes)
2735
- [2, 4],
2736
- // StartCap
2737
- [4, 4],
2738
- // EndCap
2739
- [8, 4],
2740
- // Join
2741
- [16, 4],
2742
- // MiterLimit
2743
- [32, 4],
2744
- // LineStyle (DashStyle)
2745
- [64, 4],
2746
- // DashCap
2747
- [128, 4]
2748
- // DashOffset
2749
- ];
2750
- let dashStyle = 0;
2751
- for (const [flag, size] of flagSizes) {
2752
- if (penFlags & flag) {
2753
- if (flag === 1) {
2754
- brushOff += 24;
2755
- } else {
2756
- if (flag === 32 && brushOff + 4 <= dataOff + recDataSize) {
2757
- dashStyle = view.getUint32(brushOff, true);
2758
- }
2759
- brushOff += size;
2760
- }
2761
- }
2762
- }
2763
- if (penFlags & 256) {
2764
- if (brushOff + 4 <= dataOff + recDataSize) {
2765
- const dashCount = view.getUint32(brushOff, true);
2766
- brushOff += 4 + dashCount * 4;
2767
- }
2768
- }
2769
- if (penFlags & 512) {
2770
- if (brushOff + 4 <= dataOff + recDataSize) {
2771
- const compCount = view.getUint32(brushOff, true);
2772
- brushOff += 4 + compCount * 4;
2773
- }
2774
- }
2775
- if (penFlags & 1024) {
2776
- if (brushOff + 4 <= dataOff + recDataSize) {
2777
- const capSize = view.getUint32(brushOff, true);
2778
- brushOff += 4 + capSize;
2779
- }
2780
- }
2781
- if (penFlags & 2048) {
2782
- if (brushOff + 4 <= dataOff + recDataSize) {
2783
- const capSize = view.getUint32(brushOff, true);
2784
- brushOff += 4 + capSize;
2785
- }
2786
- }
2787
- let penColor = "rgba(0,0,0,1)";
2788
- 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));
2792
- }
2793
- }
2794
- return { kind: "plus-pen", color: penColor, width: penWidth || 1, dashStyle };
2795
- }
2796
- function parseEmfPlusImageObject(view, dataOff, recDataSize, objectId) {
2797
- let imgData = null;
2798
- const imgType = view.getUint32(dataOff + 4, true);
2799
- if (imgType === 1 && recDataSize >= 28) {
2800
- const bmpType = view.getUint32(dataOff + 24, true);
2801
- if (bmpType === 1) {
2802
- const bmpW = view.getInt32(dataOff + 8, true);
2803
- const bmpH = view.getInt32(dataOff + 12, true);
2804
- const bmpStride = view.getInt32(dataOff + 16, true);
2805
- const pixelFormat = view.getUint32(dataOff + 20, true);
2806
- emfLog(
2807
- ` Bitmap(Pixel): ${bmpW}\xD7${bmpH}, stride=${bmpStride}, pixelFormat=0x${pixelFormat.toString(16).padStart(8, "0")}`
2808
- );
2809
- const pixelStart = dataOff + 28;
2810
- const absStride = Math.abs(bmpStride);
2811
- if (bmpW > 0 && bmpH > 0 && bmpW <= 8192 && bmpH <= 8192 && pixelStart + absStride * bmpH <= view.byteLength) {
2812
- const decoded = decodeEmfPlusBitmapPixels(
2813
- view,
2814
- pixelStart,
2815
- bmpW,
2816
- bmpH,
2817
- bmpStride,
2818
- pixelFormat
2819
- );
2820
- if (decoded) {
2821
- emfLog(` Bitmap(Pixel): decoded successfully, size=${decoded.byteLength} bytes`);
2822
- imgData = decoded;
2823
- }
2824
- }
2825
- } else if (bmpType === 2) {
2826
- const imgStart = dataOff + 28;
2827
- const imgLen = recDataSize - 28;
2828
- emfLog(` Bitmap(Compressed): imgLen=${imgLen}, imgStart=0x${imgStart.toString(16)}`);
2829
- if (imgLen > 0 && imgStart + imgLen <= view.byteLength) {
2830
- imgData = view.buffer.slice(
2831
- view.byteOffset + imgStart,
2832
- view.byteOffset + imgStart + imgLen
2833
- );
2834
- if (imgData.byteLength >= 4) {
2835
- const hdr = new Uint8Array(imgData, 0, 4);
2836
- emfLog(
2837
- ` Bitmap(Compressed): first 4 bytes = [${Array.from(hdr).map((b) => b.toString(16).padStart(2, "0")).join(" ")}]`
2838
- );
2839
- }
2840
- }
2841
- }
2842
- } else if (imgType === 2 && recDataSize >= 12) {
2843
- view.getUint32(dataOff + 8, true);
2844
- const mfDataSize = view.getUint32(dataOff + 12, true);
2845
- const mfStart = dataOff + 16;
2846
- if (mfDataSize > 0 && mfStart + mfDataSize <= view.byteLength) {
2847
- imgData = view.buffer.slice(
2848
- view.byteOffset + mfStart,
2849
- view.byteOffset + mfStart + mfDataSize
2850
- );
2851
- if (imgData.byteLength >= 4) {
2852
- const hdr = new DataView(imgData);
2853
- hdr.getUint32(0, true);
2854
- }
2855
- } else {
2856
- emfWarn(
2857
- ` Metafile: out of bounds or empty (mfStart=0x${mfStart.toString(16)}, mfDataSize=${mfDataSize}, viewLen=${view.byteLength})`
2858
- );
2859
- }
2860
- }
2861
- return { data: imgData, type: imgType };
2862
- }
2863
- function parseEmfPlusFontObject(view, dataOff, recDataSize) {
2864
- if (recDataSize < 28) {
2865
- return null;
2866
- }
2867
- const emSize = view.getFloat32(dataOff + 4, true);
2868
- const styleFlags = view.getInt32(dataOff + 12, true);
2869
- const nameLen = view.getUint32(dataOff + 20, true);
2870
- let family = "sans-serif";
2871
- if (nameLen > 0 && dataOff + 24 + nameLen * 2 <= dataOff + recDataSize) {
2872
- family = readUtf16LE(view, dataOff + 24, nameLen) || "sans-serif";
2873
- }
2874
- return { kind: "plus-font", emSize: emSize || 12, flags: styleFlags, family };
2875
- }
2876
-
2877
- // src/emf-plus-object-parser.ts
2878
- function handleEmfPlusObjectRecord(rCtx, recFlags, dataOff, recDataSize) {
2879
- const { view, objectTable } = rCtx;
2880
- const objectId = recFlags & 255;
2881
- const objectType = recFlags >> 8 & 127;
2882
- switch (objectType) {
2883
- // ---------------------------------------------------------------
2884
- // Brush
2885
- // ---------------------------------------------------------------
2886
- 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 });
2900
- }
2901
- break;
2902
- }
2903
- // ---------------------------------------------------------------
2904
- // Pen
2905
- // ---------------------------------------------------------------
2906
- case EMFPLUS_OBJECTTYPE_PEN: {
2907
- const pen = parseEmfPlusPenObject(view, dataOff, recDataSize);
2908
- if (pen) {
2909
- objectTable.set(objectId, pen);
2910
- }
2911
- break;
2912
- }
2913
- // ---------------------------------------------------------------
2914
- // Path
2915
- // ---------------------------------------------------------------
2916
- case EMFPLUS_OBJECTTYPE_PATH: {
2917
- const path = parseEmfPlusPath(view, dataOff, recDataSize);
2918
- if (path) {
2919
- objectTable.set(objectId, path);
2920
- }
2921
- break;
2922
- }
2923
- // ---------------------------------------------------------------
2924
- // Font
2925
- // ---------------------------------------------------------------
2926
- case EMFPLUS_OBJECTTYPE_FONT: {
2927
- const font = parseEmfPlusFontObject(view, dataOff, recDataSize);
2928
- if (font) {
2929
- objectTable.set(objectId, font);
2930
- }
2931
- break;
2932
- }
2933
- // ---------------------------------------------------------------
2934
- // StringFormat
2935
- // ---------------------------------------------------------------
2936
- case EMFPLUS_OBJECTTYPE_STRINGFORMAT: {
2937
- if (recDataSize >= 16) {
2938
- const sfFlags = view.getUint32(dataOff + 4, true);
2939
- const alignment = view.getUint32(dataOff + 12, true);
2940
- const lineAlignment = view.getUint32(dataOff + 16, true);
2941
- objectTable.set(objectId, {
2942
- kind: "plus-stringformat",
2943
- flags: sfFlags,
2944
- alignment: alignment ?? 0,
2945
- lineAlignment: lineAlignment ?? 0
2946
- });
2947
- }
2948
- break;
2949
- }
2950
- // ---------------------------------------------------------------
2951
- // Image
2952
- // ---------------------------------------------------------------
2953
- case EMFPLUS_OBJECTTYPE_IMAGE: {
2954
- if (recDataSize < 8) {
2955
- break;
2956
- }
2957
- const parsed = parseEmfPlusImageObject(view, dataOff, recDataSize);
2958
- objectTable.set(objectId, {
2959
- kind: "plus-image",
2960
- data: parsed.data,
2961
- type: parsed.type
2962
- });
2963
- rCtx.totalImageObjects++;
2964
- break;
2965
- }
2966
- // ---------------------------------------------------------------
2967
- // ImageAttributes
2968
- // ---------------------------------------------------------------
2969
- case EMFPLUS_OBJECTTYPE_IMAGEATTRIBUTES: {
2970
- objectTable.set(objectId, { kind: "plus-imageattributes" });
2971
- break;
2972
- }
2973
- // ---------------------------------------------------------------
2974
- // Region
2975
- // ---------------------------------------------------------------
2976
- case EMFPLUS_OBJECTTYPE_REGION: {
2977
- const region = parseEmfPlusRegionObject(view, dataOff, recDataSize);
2978
- if (region) {
2979
- objectTable.set(objectId, region);
2980
- }
2981
- break;
2982
- }
2983
- }
2984
- }
2985
- var MAX_REGION_NODE_DEPTH = 64;
2986
- function parseRegionNode(view, off, endOff, depth = 0) {
2987
- if (off + 4 > endOff) {
2988
- return null;
2989
- }
2990
- if (depth > MAX_REGION_NODE_DEPTH) {
2991
- return null;
2992
- }
2993
- const nodeType = view.getUint32(off, true);
2994
- let cursor = off + 4;
2995
- if (nodeType <= 4) {
2996
- const leftResult = parseRegionNode(view, cursor, endOff, depth + 1);
2997
- if (!leftResult) {
2998
- return null;
2999
- }
3000
- cursor += leftResult.bytesRead;
3001
- const rightResult = parseRegionNode(view, cursor, endOff, depth + 1);
3002
- if (!rightResult) {
3003
- return null;
3004
- }
3005
- cursor += rightResult.bytesRead;
3006
- return {
3007
- node: {
3008
- type: "combine",
3009
- combineMode: nodeType,
3010
- left: leftResult.node,
3011
- right: rightResult.node
3012
- },
3013
- bytesRead: cursor - off
3014
- };
3015
- }
3016
- if (nodeType === 268435456) {
3017
- if (cursor + 16 > endOff) {
3018
- return null;
3019
- }
3020
- const x = view.getFloat32(cursor, true);
3021
- const y = view.getFloat32(cursor + 4, true);
3022
- const w = view.getFloat32(cursor + 8, true);
3023
- const h = view.getFloat32(cursor + 12, true);
3024
- return {
3025
- node: { type: "rect", x, y, width: w, height: h },
3026
- bytesRead: cursor + 16 - off
3027
- };
3028
- }
3029
- if (nodeType === 268435457) {
3030
- if (cursor + 4 > endOff) {
3031
- return null;
3032
- }
3033
- const pathDataSize = view.getInt32(cursor, true);
3034
- cursor += 4;
3035
- if (pathDataSize <= 0 || cursor + pathDataSize > endOff) {
3036
- return null;
3037
- }
3038
- const path = parseEmfPlusPath(view, cursor, pathDataSize);
3039
- return {
3040
- node: path ? { type: "path", path } : { type: "empty" },
3041
- bytesRead: cursor + pathDataSize - off
3042
- };
3043
- }
3044
- if (nodeType === 268435458) {
3045
- return { node: { type: "empty" }, bytesRead: 4 };
3046
- }
3047
- if (nodeType === 268435459) {
3048
- return { node: { type: "infinite" }, bytesRead: 4 };
3049
- }
3050
- emfWarn(`parseRegionNode: unknown node type 0x${nodeType.toString(16)}`);
3051
- return { node: { type: "empty" }, bytesRead: 4 };
3052
- }
3053
- function parseEmfPlusRegionObject(view, off, maxLen) {
3054
- if (maxLen < 8) {
3055
- return null;
3056
- }
3057
- view.getUint32(off, true);
3058
- const regionNodeCount = view.getUint32(off + 4, true);
3059
- if (regionNodeCount === 0 || regionNodeCount > 1e5) {
3060
- return null;
3061
- }
3062
- const endOff = off + maxLen;
3063
- const result = parseRegionNode(view, off + 8, endOff);
3064
- if (!result) {
3065
- return null;
3066
- }
3067
- return {
3068
- kind: "plus-region",
3069
- nodes: [result.node]
3070
- };
3071
- }
3072
-
3073
- // src/emf-plus-text-image-handlers.ts
3074
- function handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataSize) {
3075
- const { ctx, view, objectTable } = rCtx;
3076
- switch (recType) {
3077
- // ---- path-based drawing ----
3078
- case EMFPLUS_FILLPATH: {
3079
- if (recDataSize >= 4) {
3080
- const brushVal = view.getUint32(dataOff, true);
3081
- const pathId = recFlags & 255;
3082
- const pathObj = objectTable.get(pathId);
3083
- if (pathObj && pathObj.kind === "plus-path") {
3084
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
3085
- applyPlusWorldTransform(rCtx);
3086
- replayEmfPlusPath(ctx, pathObj);
3087
- ctx.fill();
3088
- }
3089
- }
3090
- return true;
3091
- }
3092
- case EMFPLUS_DRAWPATH: {
3093
- if (recDataSize >= 4) {
3094
- const penIndex = view.getUint32(dataOff, true);
3095
- const pathId = recFlags & 255;
3096
- const pathObj = objectTable.get(pathId);
3097
- const pen = objectTable.get(penIndex & 255);
3098
- if (pathObj && pathObj.kind === "plus-path") {
3099
- if (pen && pen.kind === "plus-pen") {
3100
- ctx.strokeStyle = pen.color;
3101
- ctx.lineWidth = pen.width;
3102
- }
3103
- applyPlusWorldTransform(rCtx);
3104
- replayEmfPlusPath(ctx, pathObj);
3105
- ctx.stroke();
3106
- }
3107
- }
3108
- return true;
3109
- }
3110
- // ---- text ----
3111
- case EMFPLUS_DRAWSTRING: {
3112
- if (recDataSize >= 28) {
3113
- const brushVal = view.getUint32(dataOff, true);
3114
- const formatId = view.getUint32(dataOff + 4, true);
3115
- const strLen = view.getUint32(dataOff + 8, true);
3116
- const layoutX = view.getFloat32(dataOff + 12, true);
3117
- const layoutY = view.getFloat32(dataOff + 16, true);
3118
- view.getFloat32(dataOff + 20, true);
3119
- view.getFloat32(dataOff + 24, true);
3120
- const fontId = recFlags & 255;
3121
- const font = objectTable.get(fontId);
3122
- if (strLen > 0 && dataOff + 28 + strLen * 2 <= dataOff + recDataSize) {
3123
- const text = readUtf16LE(view, dataOff + 28, strLen);
3124
- if (text.length > 0 && font && font.kind === "plus-font") {
3125
- const bold = font.flags & 1 ? "bold " : "";
3126
- const italic = font.flags & 2 ? "italic " : "";
3127
- ctx.font = `${italic}${bold}${font.emSize}px ${font.family}`;
3128
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
3129
- ctx.textBaseline = "top";
3130
- const sf = objectTable.get(formatId);
3131
- if (sf && sf.kind === "plus-stringformat") {
3132
- switch (sf.alignment) {
3133
- case 1:
3134
- ctx.textAlign = "center";
3135
- break;
3136
- case 2:
3137
- ctx.textAlign = "right";
3138
- break;
3139
- default:
3140
- ctx.textAlign = "left";
3141
- }
3142
- } else {
3143
- ctx.textAlign = "left";
3144
- }
3145
- applyPlusWorldTransform(rCtx);
3146
- ctx.fillText(text, layoutX, layoutY);
3147
- }
3148
- }
3149
- }
3150
- return true;
3151
- }
3152
- case EMFPLUS_DRAWDRIVERSTRING: {
3153
- if (recDataSize >= 16) {
3154
- const brushVal = view.getUint32(dataOff, true);
3155
- const glyphCount = view.getUint32(dataOff + 12, true);
3156
- const fontId = recFlags & 255;
3157
- const font = objectTable.get(fontId);
3158
- const glyphsOff = dataOff + 16;
3159
- const posOff = glyphsOff + glyphCount * 2;
3160
- const alignedPosOff = posOff + 3 & -4;
3161
- if (glyphCount > 0 && glyphCount < 1e5 && alignedPosOff + glyphCount * 8 <= dataOff + recDataSize && font && font.kind === "plus-font") {
3162
- const text = readUtf16LE(view, glyphsOff, glyphCount);
3163
- if (text.length > 0) {
3164
- const bold = font.flags & 1 ? "bold " : "";
3165
- const italic = font.flags & 2 ? "italic " : "";
3166
- ctx.font = `${italic}${bold}${font.emSize}px ${font.family}`;
3167
- ctx.fillStyle = resolveBrushColor(rCtx, recFlags, brushVal);
3168
- ctx.textBaseline = "alphabetic";
3169
- ctx.textAlign = "left";
3170
- applyPlusWorldTransform(rCtx);
3171
- const gx = view.getFloat32(alignedPosOff, true);
3172
- const gy = view.getFloat32(alignedPosOff + 4, true);
3173
- ctx.fillText(text, gx, gy);
3174
- }
3175
- }
3176
- }
3177
- return true;
3178
- }
3179
- // ---- images ----
3180
- case EMFPLUS_DRAWIMAGE: {
3181
- if (recDataSize >= 24) {
3182
- const imgId = recFlags & 255;
3183
- const imgObj = objectTable.get(imgId);
3184
- const compressed = (recFlags & 16384) !== 0;
3185
- const rectOff = dataOff + 24;
3186
- let dx, dy, dw, dh;
3187
- if (compressed && rectOff + 8 <= dataOff + recDataSize) {
3188
- dx = view.getInt16(rectOff, true);
3189
- dy = view.getInt16(rectOff + 2, true);
3190
- dw = view.getInt16(rectOff + 4, true);
3191
- dh = view.getInt16(rectOff + 6, true);
3192
- } else if (!compressed && rectOff + 16 <= dataOff + recDataSize) {
3193
- dx = view.getFloat32(rectOff, true);
3194
- dy = view.getFloat32(rectOff + 4, true);
3195
- dw = view.getFloat32(rectOff + 8, true);
3196
- dh = view.getFloat32(rectOff + 12, true);
3197
- } else {
3198
- return true;
3199
- }
3200
- rCtx.totalDrawImageCalls++;
3201
- const hasData = imgObj && imgObj.kind === "plus-image" && imgObj.data;
3202
- emfLog(
3203
- `DrawImage: imgId=${imgId}, dest=(${dx},${dy},${dw},${dh}), compressed=${compressed}, hasObj=${Boolean(imgObj)}, objKind=${imgObj?.kind}, hasData=${Boolean(hasData)}, dataLen=${hasData ? imgObj.data.byteLength : 0}, isMetafile=${imgObj?.kind === "plus-image" ? imgObj.type === 2 : "N/A"}`
3204
- );
3205
- emfLog(
3206
- `DrawImage: worldTransform=[${rCtx.worldTransform.map((v) => v.toFixed(3)).join(", ")}]`
3207
- );
3208
- if (imgObj && imgObj.kind === "plus-image" && imgObj.data) {
3209
- const wt = rCtx.worldTransform;
3210
- const s = getPageUnitMultiplier(rCtx.pageUnit, rCtx.pageScale) * rCtx.dpiScale;
3211
- rCtx.deferredImages.push({
3212
- imageData: imgObj.data,
3213
- dx,
3214
- dy,
3215
- dw,
3216
- dh,
3217
- transform: [
3218
- wt[0] * s,
3219
- wt[1] * s,
3220
- wt[2] * s,
3221
- wt[3] * s,
3222
- wt[4] * s,
3223
- wt[5] * s
3224
- ],
3225
- isMetafile: imgObj.type === 2
3226
- });
3227
- emfLog(`DrawImage: queued deferred image (total=${rCtx.deferredImages.length})`);
3228
- }
3229
- }
3230
- return true;
3231
- }
3232
- case EMFPLUS_DRAWIMAGEPOINTS: {
3233
- if (recDataSize >= 28) {
3234
- const imgId = recFlags & 255;
3235
- const imgObj = objectTable.get(imgId);
3236
- const count = view.getUint32(dataOff + 24, true);
3237
- const compressed = (recFlags & 16384) !== 0;
3238
- const ptOff = dataOff + 28;
3239
- if (count >= 3 && imgObj && imgObj.kind === "plus-image" && imgObj.data) {
3240
- let p1x, p1y, p2x, p2y, p3x, p3y;
3241
- if (compressed && ptOff + 12 <= dataOff + recDataSize) {
3242
- p1x = view.getInt16(ptOff, true);
3243
- p1y = view.getInt16(ptOff + 2, true);
3244
- p2x = view.getInt16(ptOff + 4, true);
3245
- p2y = view.getInt16(ptOff + 6, true);
3246
- p3x = view.getInt16(ptOff + 8, true);
3247
- p3y = view.getInt16(ptOff + 10, true);
3248
- } else if (!compressed && ptOff + 24 <= dataOff + recDataSize) {
3249
- p1x = view.getFloat32(ptOff, true);
3250
- p1y = view.getFloat32(ptOff + 4, true);
3251
- p2x = view.getFloat32(ptOff + 8, true);
3252
- p2y = view.getFloat32(ptOff + 12, true);
3253
- p3x = view.getFloat32(ptOff + 16, true);
3254
- p3y = view.getFloat32(ptOff + 20, true);
3255
- } else {
3256
- return true;
3257
- }
3258
- const dx = p1x;
3259
- const dy = p1y;
3260
- const dw = Math.sqrt((p2x - p1x) ** 2 + (p2y - p1y) ** 2);
3261
- const dh = Math.sqrt((p3x - p1x) ** 2 + (p3y - p1y) ** 2);
3262
- rCtx.totalDrawImageCalls++;
3263
- emfLog(
3264
- `DrawImagePoints: imgId=${imgId}, points=[(${p1x},${p1y}),(${p2x},${p2y}),(${p3x},${p3y})], dest=(${dx.toFixed(1)},${dy.toFixed(1)},${dw.toFixed(1)},${dh.toFixed(1)})`
3265
- );
3266
- emfLog(
3267
- `DrawImagePoints: worldTransform=[${rCtx.worldTransform.map((v) => v.toFixed(3)).join(", ")}]`
3268
- );
3269
- const wt2 = rCtx.worldTransform;
3270
- const s2 = getPageUnitMultiplier(rCtx.pageUnit, rCtx.pageScale) * rCtx.dpiScale;
3271
- rCtx.deferredImages.push({
3272
- imageData: imgObj.data,
3273
- dx,
3274
- dy,
3275
- dw,
3276
- dh,
3277
- transform: [
3278
- wt2[0] * s2,
3279
- wt2[1] * s2,
3280
- wt2[2] * s2,
3281
- wt2[3] * s2,
3282
- wt2[4] * s2,
3283
- wt2[5] * s2
3284
- ],
3285
- isMetafile: imgObj.type === 2
3286
- });
3287
- emfLog(`DrawImagePoints: queued deferred image (total=${rCtx.deferredImages.length})`);
3288
- } else {
3289
- imgObj && imgObj.kind === "plus-image" && imgObj.data;
3290
- }
3291
- }
3292
- return true;
3293
- }
3294
- default:
3295
- return false;
3296
- }
3297
- }
3298
-
3299
- // src/emf-plus-replay.ts
3300
- var EMFPLUS_REC_NAMES = {
3301
- 16385: "Header",
3302
- 16386: "EndOfFile",
3303
- 16388: "GetDC",
3304
- 16392: "Object",
3305
- 16394: "FillRects",
3306
- 16395: "DrawRects",
3307
- 16396: "FillPolygon",
3308
- 16397: "DrawLines",
3309
- 16398: "FillEllipse",
3310
- 16399: "DrawEllipse",
3311
- 16404: "FillPath",
3312
- 16405: "DrawPath",
3313
- 16410: "DrawImage",
3314
- 16411: "DrawImagePoints",
3315
- 16412: "DrawString",
3316
- 16438: "DrawDriverString",
3317
- 16414: "SetAntiAliasMode",
3318
- 16426: "SetWorldTransform",
3319
- 16427: "ResetWorldTransform",
3320
- 16428: "MultiplyWorldTransform",
3321
- 16432: "SetPageTransform",
3322
- 16433: "ResetClip",
3323
- 16434: "SetClipRect",
3324
- 16435: "SetClipPath",
3325
- 16436: "SetClipRegion",
3326
- 16437: "OffsetClip",
3327
- 16421: "Save",
3328
- 16422: "Restore",
3329
- 16424: "BeginContainerNoParams",
3330
- 16425: "EndContainer"
3331
- };
3332
- function replayEmfPlusRecords(view, offset, length, ctx, _canvasW, _canvasH, state, dpiScale = 1) {
3333
- const s = state ?? createEmfPlusState();
3334
- const rCtx = {
3335
- ctx,
3336
- view,
3337
- objectTable: s.objectTable,
3338
- worldTransform: s.worldTransform,
3339
- deferredImages: [],
3340
- saveStack: s.saveStack,
3341
- saveIdMap: s.saveIdMap,
3342
- totalImageObjects: 0,
3343
- totalDrawImageCalls: 0,
3344
- clipSaveDepth: 0,
3345
- pageUnit: 2,
3346
- pageScale: 1,
3347
- continuationBuffer: null,
3348
- continuationObjectId: -1,
3349
- continuationObjectType: 0,
3350
- continuationTotalSize: 0,
3351
- continuationOffset: 0,
3352
- dpiScale
3353
- };
3354
- const end = offset + length;
3355
- const maxRecords = 5e5;
3356
- let recordCount = 0;
3357
- const emfPlusRecordTypes = /* @__PURE__ */ new Map();
3358
- emfLog(`replayEmfPlusRecords: offset=0x${offset.toString(16)}, length=${length}`);
3359
- while (offset + 12 <= end && recordCount < maxRecords) {
3360
- const recType = view.getUint16(offset, true);
3361
- const recFlags = view.getUint16(offset + 2, true);
3362
- const recSize = view.getUint32(offset + 4, true);
3363
- const recDataSize = view.getUint32(offset + 8, true);
3364
- if (recSize < 12 || offset + recSize > end) {
3365
- break;
3366
- }
3367
- recordCount++;
3368
- emfPlusRecordTypes.set(recType, (emfPlusRecordTypes.get(recType) ?? 0) + 1);
3369
- const dataOff = offset + 12;
3370
- switch (recType) {
3371
- case EMFPLUS_HEADER: {
3372
- if (recDataSize >= 16) {
3373
- view.getFloat32(dataOff + 8, true);
3374
- view.getFloat32(dataOff + 12, true);
3375
- }
3376
- break;
3377
- }
3378
- case EMFPLUS_ENDOFFILE:
3379
- offset = end;
3380
- continue;
3381
- case EMFPLUS_GETDC:
3382
- break;
3383
- case EMFPLUS_OBJECT: {
3384
- const isContinuation = (recFlags & 32768) !== 0;
3385
- const objectId = recFlags & 255;
3386
- if (isContinuation) {
3387
- if (rCtx.continuationBuffer === null) {
3388
- if (recDataSize >= 4) {
3389
- const totalSize = view.getUint32(dataOff, true);
3390
- const objectType = recFlags >> 8 & 127;
3391
- const MAX_CONTINUATION_BYTES = 64 * 1024 * 1024;
3392
- const remainingEmfPlusBytes = view.byteLength - dataOff;
3393
- if (!Number.isFinite(totalSize) || totalSize <= 0 || totalSize > MAX_CONTINUATION_BYTES || totalSize > remainingEmfPlusBytes || recDataSize - 4 < 0) ; else {
3394
- rCtx.continuationTotalSize = totalSize;
3395
- rCtx.continuationObjectId = objectId;
3396
- rCtx.continuationObjectType = objectType;
3397
- rCtx.continuationBuffer = new Uint8Array(totalSize);
3398
- const chunkSize = recDataSize - 4;
3399
- const chunk = new Uint8Array(
3400
- view.buffer,
3401
- view.byteOffset + dataOff + 4,
3402
- Math.min(chunkSize, totalSize)
3403
- );
3404
- rCtx.continuationBuffer.set(chunk, 0);
3405
- rCtx.continuationOffset = chunk.length;
3406
- }
3407
- }
3408
- } else {
3409
- const remaining = rCtx.continuationTotalSize - rCtx.continuationOffset;
3410
- const chunk = new Uint8Array(
3411
- view.buffer,
3412
- view.byteOffset + dataOff,
3413
- Math.min(recDataSize, remaining)
3414
- );
3415
- rCtx.continuationBuffer.set(chunk, rCtx.continuationOffset);
3416
- rCtx.continuationOffset += chunk.length;
3417
- }
3418
- } else if (rCtx.continuationBuffer !== null && objectId === rCtx.continuationObjectId) {
3419
- const remaining = rCtx.continuationTotalSize - rCtx.continuationOffset;
3420
- const chunk = new Uint8Array(
3421
- view.buffer,
3422
- view.byteOffset + dataOff,
3423
- Math.min(recDataSize, remaining)
3424
- );
3425
- rCtx.continuationBuffer.set(chunk, rCtx.continuationOffset);
3426
- const completeView = new DataView(
3427
- rCtx.continuationBuffer.buffer,
3428
- rCtx.continuationBuffer.byteOffset,
3429
- rCtx.continuationBuffer.byteLength
3430
- );
3431
- const assembledFlags = rCtx.continuationObjectType << 8 | objectId;
3432
- handleEmfPlusObjectRecord(
3433
- { ...rCtx, view: completeView },
3434
- assembledFlags,
3435
- 0,
3436
- rCtx.continuationTotalSize
3437
- );
3438
- rCtx.continuationBuffer = null;
3439
- rCtx.continuationObjectId = -1;
3440
- rCtx.continuationObjectType = 0;
3441
- rCtx.continuationTotalSize = 0;
3442
- rCtx.continuationOffset = 0;
3443
- } else {
3444
- handleEmfPlusObjectRecord(rCtx, recFlags, dataOff, recDataSize);
3445
- }
3446
- break;
3447
- }
3448
- default: {
3449
- const handled = handleEmfPlusDrawRecord(rCtx, recType, recFlags, dataOff, recDataSize) || handleEmfPlusTextImageRecord(rCtx, recType, recFlags, dataOff, recDataSize) || handleEmfPlusStateRecord(rCtx, recType, recFlags, dataOff, recDataSize);
3450
- if (!handled) {
3451
- console.warn(`[emf-converter] Unhandled EMF+ record type: 0x${recType.toString(16)}`);
3452
- }
3453
- break;
3454
- }
3455
- }
3456
- offset += recSize;
3457
- }
3458
- if (recordCount >= maxRecords) {
3459
- console.warn(
3460
- `[emf-converter] EMF+ record limit reached (${maxRecords}). Output may be incomplete.`
3461
- );
3462
- }
3463
- const summary = [];
3464
- for (const [type, cnt] of emfPlusRecordTypes) {
3465
- summary.push(`${EMFPLUS_REC_NAMES[type] ?? `0x${type.toString(16)}`}:${cnt}`);
3466
- }
3467
- emfLog(
3468
- `replayEmfPlusRecords: totalImageObjects=${rCtx.totalImageObjects}, totalDrawImageCalls=${rCtx.totalDrawImageCalls}, deferredImages=${rCtx.deferredImages.length}`
3469
- );
3470
- emfLog(
3471
- `replayEmfPlusRecords: object table has ${rCtx.objectTable.size} entries: [${Array.from(
3472
- rCtx.objectTable.entries()
3473
- ).map(([id, obj]) => `${id}:${obj.kind}`).join(", ")}]`
3474
- );
3475
- if (state) {
3476
- state.worldTransform = rCtx.worldTransform;
3477
- state.saveIdMap = rCtx.saveIdMap;
3478
- }
3479
- ctx.setTransform(1, 0, 0, 1, 0, 0);
3480
- return rCtx.deferredImages;
3481
- }
3482
-
3483
- // src/emf-record-replay.ts
3484
- var GDI_NAMES = {
3485
- 1: "EMR_HEADER",
3486
- 2: "EMR_POLYBEZIER",
3487
- 3: "EMR_POLYGON",
3488
- 4: "EMR_POLYLINE",
3489
- 5: "EMR_POLYBEZIERTO",
3490
- 6: "EMR_POLYLINETO",
3491
- 14: "EMR_EOF",
3492
- 27: "EMR_MOVETOEX",
3493
- 37: "EMR_SELECTOBJECT",
3494
- 38: "EMR_CREATEPEN",
3495
- 39: "EMR_CREATEBRUSHINDIRECT",
3496
- 40: "EMR_DELETEOBJECT",
3497
- 42: "EMR_ELLIPSE",
3498
- 43: "EMR_RECTANGLE",
3499
- 54: "EMR_LINETO",
3500
- 59: "EMR_BEGINPATH",
3501
- 60: "EMR_ENDPATH",
3502
- 62: "EMR_FILLPATH",
3503
- 63: "EMR_STROKEANDFILLPATH",
3504
- 64: "EMR_STROKEPATH",
3505
- 70: "EMR_COMMENT",
3506
- 76: "EMR_BITBLT",
3507
- 81: "EMR_STRETCHDIBITS",
3508
- 84: "EMR_EXTTEXTOUTW",
3509
- 85: "EMR_POLYBEZIER16",
3510
- 86: "EMR_POLYGON16",
3511
- 87: "EMR_POLYLINE16",
3512
- 88: "EMR_POLYBEZIERTO16",
3513
- 91: "EMR_POLYPOLYGON16"
3514
- };
3515
- function replayEmfRecords(view, ctx, bounds, canvasW, canvasH, dpiScale = 1) {
3516
- emfLog(
3517
- `replayEmfRecords: bounds=(${bounds.left},${bounds.top})\u2192(${bounds.right},${bounds.bottom}), canvas=${canvasW}\xD7${canvasH}`
3518
- );
3519
- const allDeferredImages = [];
3520
- const emfPlusState = createEmfPlusState();
3521
- const logicalW = bounds.right - bounds.left || 1;
3522
- const logicalH = bounds.bottom - bounds.top || 1;
3523
- const sx = canvasW / logicalW;
3524
- const sy = canvasH / logicalH;
3525
- emfLog(
3526
- `replayEmfRecords: logical=${logicalW}\xD7${logicalH}, scale=(${sx.toFixed(4)},${sy.toFixed(4)})`
3527
- );
3528
- const rCtx = {
3529
- ctx,
3530
- view,
3531
- objectTable: /* @__PURE__ */ new Map(),
3532
- state: defaultState(),
3533
- stateStack: [],
3534
- inPath: false,
3535
- windowOrg: { x: bounds.left, y: bounds.top },
3536
- windowExt: { cx: logicalW, cy: logicalH },
3537
- viewportOrg: { x: 0, y: 0 },
3538
- viewportExt: { cx: canvasW, cy: canvasH },
3539
- useMappingMode: false,
3540
- clipSaveDepth: 0,
3541
- bounds,
3542
- canvasW,
3543
- canvasH,
3544
- sx,
3545
- sy
3546
- };
3547
- let offset = 0;
3548
- const maxOffset = view.byteLength;
3549
- const maxRecords = 2e5;
3550
- let recordCount = 0;
3551
- let emfPlusCommentCount = 0;
3552
- const gdiRecordTypes = /* @__PURE__ */ new Map();
3553
- while (offset + 8 <= maxOffset && recordCount < maxRecords) {
3554
- const recType = view.getUint32(offset, true);
3555
- const recSize = view.getUint32(offset + 4, true);
3556
- if (recSize < 8 || offset + recSize > maxOffset) {
3557
- break;
3558
- }
3559
- recordCount++;
3560
- const dataOff = offset + 8;
3561
- gdiRecordTypes.set(recType, (gdiRecordTypes.get(recType) ?? 0) + 1);
3562
- if (recType === EMR_COMMENT) {
3563
- if (recSize >= 16) {
3564
- const commentDataSize = view.getUint32(dataOff, true);
3565
- const sig = view.getUint32(dataOff + 4, true);
3566
- if (sig === EMFPLUS_SIGNATURE && commentDataSize > 4) {
3567
- emfPlusCommentCount++;
3568
- emfLog(
3569
- `replayEmfRecords: EMF+ comment #${emfPlusCommentCount} at offset 0x${offset.toString(16)}, dataSize=${commentDataSize}`
3570
- );
3571
- const deferred = replayEmfPlusRecords(
3572
- view,
3573
- dataOff + 8,
3574
- commentDataSize - 4,
3575
- ctx,
3576
- canvasW,
3577
- canvasH,
3578
- emfPlusState,
3579
- dpiScale
3580
- );
3581
- emfLog(
3582
- `replayEmfRecords: EMF+ comment #${emfPlusCommentCount} returned ${deferred.length} deferred images`
3583
- );
3584
- allDeferredImages.push(...deferred);
3585
- } else if (sig === EMR_COMMENT_PUBLIC_SIGNATURE) {
3586
- emfLog(
3587
- `replayEmfRecords: EMR_COMMENT_PUBLIC at offset 0x${offset.toString(16)}, size=${commentDataSize}`
3588
- );
3589
- } else {
3590
- emfLog(
3591
- `replayEmfRecords: EMR_COMMENT (sig=0x${sig.toString(16).padStart(8, "0")}) at offset 0x${offset.toString(16)}, size=${commentDataSize}`
3592
- );
3593
- }
3594
- }
3595
- offset += recSize;
3596
- continue;
3597
- }
3598
- if (recType === EMR_EOF) {
3599
- const summary = [];
3600
- for (const [type, count] of gdiRecordTypes) {
3601
- summary.push(`${GDI_NAMES[type] ?? `0x${type.toString(16)}`}:${count}`);
3602
- }
3603
- emfLog(
3604
- `replayEmfRecords: total deferred images = ${allDeferredImages.length}, EMF+ object table size = ${emfPlusState.objectTable.size}`
3605
- );
3606
- break;
3607
- }
3608
- if (recType === EMR_SETBRUSHORGEX || recType === EMR_SETMETARGN || recType === EMR_SETICMMODE || recType === EMR_SETLAYOUT || recType === EMR_HEADER) {
3609
- offset += recSize;
3610
- continue;
3611
- }
3612
- const handled = handleEmfGdiStateRecord(rCtx, recType, offset, dataOff, recSize) || handleEmfGdiDrawRecord(rCtx, recType, offset, dataOff, recSize) || handleEmfGdiPolyPathRecord(rCtx, recType, offset, dataOff, recSize);
3613
- if (!handled) {
3614
- console.warn(`[emf-converter] Unhandled EMR record type: ${recType}`);
3615
- }
3616
- offset += recSize;
3617
- }
3618
- if (recordCount >= maxRecords) {
3619
- console.warn(
3620
- `[emf-converter] EMF record limit reached (${maxRecords}). Output may be incomplete.`
3621
- );
3622
- }
3623
- return allDeferredImages;
3624
- }
3625
-
3626
- // src/wmf-draw-handlers.ts
3627
- function handleWmfDrawRecord(wCtx, recType, offset, dataOff, recSize) {
3628
- const { ctx, view, state, coord } = wCtx;
3629
- const { mx, my, mw, mh } = coord;
3630
- switch (recType) {
3631
- case META_MOVETO:
3632
- if (recSize >= 10) {
3633
- state.curY = view.getInt16(dataOff, true);
3634
- state.curX = view.getInt16(dataOff + 2, true);
3635
- }
3636
- return true;
3637
- case META_LINETO:
3638
- if (recSize >= 10) {
3639
- const ly = view.getInt16(dataOff, true);
3640
- const lx = view.getInt16(dataOff + 2, true);
3641
- applyPen(ctx, state);
3642
- ctx.beginPath();
3643
- ctx.moveTo(mx(state.curX), my(state.curY));
3644
- ctx.lineTo(mx(lx), my(ly));
3645
- ctx.stroke();
3646
- state.curX = lx;
3647
- state.curY = ly;
3648
- }
3649
- return true;
3650
- case META_RECTANGLE:
3651
- if (recSize >= 14) {
3652
- const b = view.getInt16(dataOff, true);
3653
- const r = view.getInt16(dataOff + 2, true);
3654
- const t = view.getInt16(dataOff + 4, true);
3655
- const l = view.getInt16(dataOff + 6, true);
3656
- applyBrush(ctx, state);
3657
- ctx.fillRect(mx(l), my(t), mw(r - l), mh(b - t));
3658
- applyPen(ctx, state);
3659
- ctx.strokeRect(mx(l), my(t), mw(r - l), mh(b - t));
3660
- }
3661
- return true;
3662
- case META_ROUNDRECT:
3663
- if (recSize >= 18) {
3664
- const rh = Math.abs(mh(view.getInt16(dataOff, true))) / 2;
3665
- const rw = Math.abs(mw(view.getInt16(dataOff + 2, true))) / 2;
3666
- const b = view.getInt16(dataOff + 4, true);
3667
- const r = view.getInt16(dataOff + 6, true);
3668
- const t = view.getInt16(dataOff + 8, true);
3669
- const l = view.getInt16(dataOff + 10, true);
3670
- const x1 = mx(l), y1 = my(t);
3671
- const w = mw(r - l), h = mh(b - t);
3672
- const radius = Math.min(rw, rh, w / 2, h / 2);
3673
- ctx.beginPath();
3674
- ctx.moveTo(x1 + radius, y1);
3675
- ctx.lineTo(x1 + w - radius, y1);
3676
- ctx.arcTo(x1 + w, y1, x1 + w, y1 + radius, radius);
3677
- ctx.lineTo(x1 + w, y1 + h - radius);
3678
- ctx.arcTo(x1 + w, y1 + h, x1 + w - radius, y1 + h, radius);
3679
- ctx.lineTo(x1 + radius, y1 + h);
3680
- ctx.arcTo(x1, y1 + h, x1, y1 + h - radius, radius);
3681
- ctx.lineTo(x1, y1 + radius);
3682
- ctx.arcTo(x1, y1, x1 + radius, y1, radius);
3683
- ctx.closePath();
3684
- applyBrush(ctx, state);
3685
- ctx.fill();
3686
- applyPen(ctx, state);
3687
- ctx.stroke();
3688
- }
3689
- return true;
3690
- case META_ELLIPSE:
3691
- if (recSize >= 14) {
3692
- const b = view.getInt16(dataOff, true);
3693
- const r = view.getInt16(dataOff + 2, true);
3694
- const t = view.getInt16(dataOff + 4, true);
3695
- const l = view.getInt16(dataOff + 6, true);
3696
- ctx.beginPath();
3697
- ctx.ellipse(
3698
- mx((l + r) / 2),
3699
- my((t + b) / 2),
3700
- Math.abs(mw(r - l)) / 2,
3701
- Math.abs(mh(b - t)) / 2,
3702
- 0,
3703
- 0,
3704
- Math.PI * 2
3705
- );
3706
- applyBrush(ctx, state);
3707
- ctx.fill();
3708
- applyPen(ctx, state);
3709
- ctx.stroke();
3710
- }
3711
- return true;
3712
- case META_ARC:
3713
- case META_PIE:
3714
- case META_CHORD:
3715
- if (recSize >= 22) {
3716
- const endY = view.getInt16(dataOff, true);
3717
- const endX = view.getInt16(dataOff + 2, true);
3718
- const startY = view.getInt16(dataOff + 4, true);
3719
- const startX = view.getInt16(dataOff + 6, true);
3720
- const b = view.getInt16(dataOff + 8, true);
3721
- const r = view.getInt16(dataOff + 10, true);
3722
- const t = view.getInt16(dataOff + 12, true);
3723
- const l = view.getInt16(dataOff + 14, true);
3724
- const cxA = (l + r) / 2;
3725
- const cyA = (t + b) / 2;
3726
- const rxA = Math.abs(r - l) / 2;
3727
- const ryA = Math.abs(b - t) / 2;
3728
- const startAngle = Math.atan2((startY - cyA) / (ryA || 1), (startX - cxA) / (rxA || 1));
3729
- const endAngle = Math.atan2((endY - cyA) / (ryA || 1), (endX - cxA) / (rxA || 1));
3730
- ctx.beginPath();
3731
- if (recType === META_PIE) {
3732
- ctx.moveTo(mx(cxA), my(cyA));
3733
- }
3734
- ctx.ellipse(
3735
- mx(cxA),
3736
- my(cyA),
3737
- Math.abs(mw(rxA)),
3738
- Math.abs(mh(ryA)),
3739
- 0,
3740
- startAngle,
3741
- endAngle,
3742
- false
3743
- );
3744
- if (recType === META_PIE || recType === META_CHORD) {
3745
- ctx.closePath();
3746
- }
3747
- if (recType === META_PIE || recType === META_CHORD) {
3748
- applyBrush(ctx, state);
3749
- ctx.fill();
3750
- }
3751
- applyPen(ctx, state);
3752
- ctx.stroke();
3753
- }
3754
- return true;
3755
- // ---- poly ----
3756
- case META_POLYGON:
3757
- if (recSize >= 10) {
3758
- const count = view.getInt16(dataOff, true);
3759
- if (count > 0 && dataOff + 2 + count * 4 <= offset + recSize) {
3760
- ctx.beginPath();
3761
- for (let i = 0; i < count; i++) {
3762
- const px = view.getInt16(dataOff + 2 + i * 4, true);
3763
- const py = view.getInt16(dataOff + 4 + i * 4, true);
3764
- if (i === 0) {
3765
- ctx.moveTo(mx(px), my(py));
3766
- } else {
3767
- ctx.lineTo(mx(px), my(py));
3768
- }
3769
- }
3770
- ctx.closePath();
3771
- applyBrush(ctx, state);
3772
- ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
3773
- applyPen(ctx, state);
3774
- ctx.stroke();
3775
- }
3776
- }
3777
- return true;
3778
- case META_POLYLINE:
3779
- if (recSize >= 10) {
3780
- const count = view.getInt16(dataOff, true);
3781
- if (count > 0 && dataOff + 2 + count * 4 <= offset + recSize) {
3782
- ctx.beginPath();
3783
- for (let i = 0; i < count; i++) {
3784
- const px = view.getInt16(dataOff + 2 + i * 4, true);
3785
- const py = view.getInt16(dataOff + 4 + i * 4, true);
3786
- if (i === 0) {
3787
- ctx.moveTo(mx(px), my(py));
3788
- } else {
3789
- ctx.lineTo(mx(px), my(py));
3790
- }
3791
- }
3792
- applyPen(ctx, state);
3793
- ctx.stroke();
3794
- }
3795
- }
3796
- return true;
3797
- case META_POLYPOLYGON:
3798
- if (recSize >= 10) {
3799
- const numPolys = view.getUint16(dataOff, true);
3800
- let polyOff = dataOff + 2;
3801
- const counts = [];
3802
- for (let p = 0; p < numPolys && polyOff + 2 <= offset + recSize; p++) {
3803
- counts.push(view.getInt16(polyOff, true));
3804
- polyOff += 2;
3805
- }
3806
- ctx.beginPath();
3807
- for (const count of counts) {
3808
- if (count > 0 && polyOff + count * 4 <= offset + recSize) {
3809
- for (let i = 0; i < count; i++) {
3810
- const px = view.getInt16(polyOff + i * 4, true);
3811
- const py = view.getInt16(polyOff + i * 4 + 2, true);
3812
- if (i === 0) {
3813
- ctx.moveTo(mx(px), my(py));
3814
- } else {
3815
- ctx.lineTo(mx(px), my(py));
3816
- }
3817
- }
3818
- ctx.closePath();
3819
- polyOff += count * 4;
3820
- }
3821
- }
3822
- applyBrush(ctx, state);
3823
- ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
3824
- applyPen(ctx, state);
3825
- ctx.stroke();
3826
- }
3827
- return true;
3828
- // ---- text ----
3829
- case META_TEXTOUT:
3830
- if (recSize >= 12) {
3831
- const nChars = view.getInt16(dataOff, true);
3832
- if (nChars > 0 && dataOff + 2 + nChars <= offset + recSize) {
3833
- let text = "";
3834
- for (let i = 0; i < nChars; i++) {
3835
- const ch = view.getUint8(dataOff + 2 + i);
3836
- if (ch === 0) {
3837
- break;
3838
- }
3839
- text += String.fromCharCode(ch);
3840
- }
3841
- const strBytes = nChars + nChars % 2;
3842
- const txOff = dataOff + 2 + strBytes;
3843
- if (txOff + 4 <= offset + recSize) {
3844
- const ty2 = view.getInt16(txOff, true);
3845
- const txCoord = view.getInt16(txOff + 2, true);
3846
- applyFont(ctx, state);
3847
- ctx.fillStyle = state.textColor;
3848
- ctx.fillText(text, mx(txCoord), my(ty2));
3849
- }
3850
- }
3851
- }
3852
- return true;
3853
- case META_EXTTEXTOUT:
3854
- if (recSize >= 14) {
3855
- const ty2 = view.getInt16(dataOff, true);
3856
- const txCoord = view.getInt16(dataOff + 2, true);
3857
- const nChars = view.getInt16(dataOff + 4, true);
3858
- const hasClipRect = (view.getUint16(dataOff + 6, true) & 4) !== 0;
3859
- const stringOff = dataOff + 8 + (hasClipRect ? 8 : 0);
3860
- if (nChars > 0 && stringOff + nChars <= offset + recSize) {
3861
- let text = "";
3862
- for (let i = 0; i < nChars; i++) {
3863
- const ch = view.getUint8(stringOff + i);
3864
- if (ch === 0) {
3865
- break;
3866
- }
3867
- text += String.fromCharCode(ch);
3868
- }
3869
- applyFont(ctx, state);
3870
- ctx.fillStyle = state.textColor;
3871
- ctx.fillText(text, mx(txCoord), my(ty2));
3872
- }
3873
- }
3874
- return true;
3875
- default:
3876
- return false;
3877
- }
3878
- }
3879
-
3880
- // src/wmf-replay.ts
3881
- function createWmfCoord(windowOrg, windowExt, canvasW, canvasH) {
3882
- return {
3883
- mx: (x) => (x - windowOrg.x) / (windowExt.cx || 1) * canvasW,
3884
- my: (y) => (y - windowOrg.y) / (windowExt.cy || 1) * canvasH,
3885
- mw: (w) => w / (windowExt.cx || 1) * canvasW,
3886
- mh: (h) => h / (windowExt.cy || 1) * canvasH
3887
- };
3888
- }
3889
- function replayWmfRecords(view, ctx, header, canvasW, canvasH) {
3890
- const logicalW = header.boundsRight - header.boundsLeft || 1;
3891
- const logicalH = header.boundsBottom - header.boundsTop || 1;
3892
- const windowOrg = { x: header.boundsLeft, y: header.boundsTop };
3893
- const windowExt = { cx: logicalW, cy: logicalH };
3894
- const coord = createWmfCoord(windowOrg, windowExt, canvasW, canvasH);
3895
- const objectTable = /* @__PURE__ */ new Map();
3896
- let nextObjectSlot = 0;
3897
- const state = defaultState();
3898
- const stateStack = [];
3899
- const wCtx = { view, ctx, state, coord };
3900
- let offset = header.headerSize;
3901
- const maxOffset = view.byteLength;
3902
- const maxRecords = 2e5;
3903
- let recordCount = 0;
3904
- while (offset + 6 <= maxOffset && recordCount < maxRecords) {
3905
- const recSizeWords = view.getUint32(offset, true);
3906
- const recType = view.getUint16(offset + 4, true);
3907
- const recSize = recSizeWords * 2;
3908
- if (recSize < 6 || offset + recSize > maxOffset) {
3909
- break;
3910
- }
3911
- if (recType === META_EOF) {
3912
- break;
3913
- }
3914
- recordCount++;
3915
- const dataOff = offset + 6;
3916
- if (handleWmfDrawRecord(wCtx, recType, offset, dataOff, recSize)) {
3917
- offset += recSize;
3918
- continue;
3919
- }
3920
- switch (recType) {
3921
- case META_SETWINDOWORG:
3922
- if (recSize >= 10) {
3923
- windowOrg.y = view.getInt16(dataOff, true);
3924
- windowOrg.x = view.getInt16(dataOff + 2, true);
3925
- }
3926
- break;
3927
- case META_SETWINDOWEXT:
3928
- if (recSize >= 10) {
3929
- windowExt.cy = view.getInt16(dataOff, true);
3930
- windowExt.cx = view.getInt16(dataOff + 2, true);
3931
- }
3932
- break;
3933
- case META_SAVEDC:
3934
- stateStack.push(cloneState(state));
3935
- break;
3936
- case META_RESTOREDC: {
3937
- const restored = stateStack.pop();
3938
- if (restored) {
3939
- Object.assign(state, restored);
3940
- }
3941
- break;
3942
- }
3943
- case META_SETTEXTCOLOR:
3944
- if (recSize >= 10) {
3945
- state.textColor = readColorRef(view, dataOff);
3946
- }
3947
- break;
3948
- case META_SETBKCOLOR:
3949
- if (recSize >= 10) {
3950
- state.bkColor = readColorRef(view, dataOff);
3951
- }
3952
- break;
3953
- case META_SETBKMODE:
3954
- if (recSize >= 8) {
3955
- state.bkMode = view.getUint16(dataOff, true);
3956
- }
3957
- break;
3958
- case META_SETROP2:
3959
- break;
3960
- case META_SETPOLYFILLMODE:
3961
- if (recSize >= 8) {
3962
- state.polyFillMode = view.getUint16(dataOff, true);
3963
- }
3964
- break;
3965
- case META_SETTEXTALIGN:
3966
- if (recSize >= 8) {
3967
- state.textAlign = view.getUint16(dataOff, true);
3968
- }
3969
- break;
3970
- case META_CREATEPENINDIRECT:
3971
- if (recSize >= 16) {
3972
- const slot = nextObjectSlot++;
3973
- objectTable.set(slot, {
3974
- kind: "pen",
3975
- style: view.getUint16(dataOff, true) & 255,
3976
- widthX: view.getInt16(dataOff + 2, true),
3977
- color: readColorRef(view, dataOff + 6)
3978
- });
3979
- }
3980
- break;
3981
- case META_CREATEBRUSHINDIRECT:
3982
- if (recSize >= 14) {
3983
- const slot = nextObjectSlot++;
3984
- objectTable.set(slot, {
3985
- kind: "brush",
3986
- style: view.getUint16(dataOff, true),
3987
- color: readColorRef(view, dataOff + 2)
3988
- });
3989
- }
3990
- break;
3991
- case META_CREATEFONTINDIRECT:
3992
- if (recSize >= 24) {
3993
- let family = "";
3994
- for (let i = 0; i < 32 && dataOff + 14 + i < offset + recSize; i++) {
3995
- const ch = view.getUint8(dataOff + 14 + i);
3996
- if (ch === 0) {
3997
- break;
3998
- }
3999
- family += String.fromCharCode(ch);
4000
- }
4001
- const slot = nextObjectSlot++;
4002
- objectTable.set(slot, {
4003
- kind: "font",
4004
- height: Math.abs(view.getInt16(dataOff, true)),
4005
- weight: view.getInt16(dataOff + 8, true),
4006
- italic: view.getUint8(dataOff + 10) !== 0,
4007
- family: family || "sans-serif"
4008
- });
4009
- }
4010
- break;
4011
- case META_SELECTOBJECT:
4012
- if (recSize >= 8) {
4013
- const obj = objectTable.get(view.getUint16(dataOff, true));
4014
- if (obj) {
4015
- switch (obj.kind) {
4016
- case "pen":
4017
- state.penStyle = obj.style;
4018
- state.penWidth = obj.widthX;
4019
- state.penColor = obj.color;
4020
- break;
4021
- case "brush":
4022
- state.brushStyle = obj.style;
4023
- state.brushColor = obj.color;
4024
- break;
4025
- case "font":
4026
- state.fontHeight = obj.height;
4027
- state.fontWeight = obj.weight;
4028
- state.fontItalic = obj.italic;
4029
- state.fontFamily = obj.family;
4030
- break;
4031
- }
4032
- }
4033
- }
4034
- break;
4035
- case META_DELETEOBJECT:
4036
- if (recSize >= 8) {
4037
- objectTable.delete(view.getUint16(dataOff, true));
4038
- }
4039
- break;
4040
- }
4041
- offset += recSize;
4042
- }
4043
- if (recordCount >= maxRecords) {
4044
- console.warn(
4045
- `[emf-converter] WMF record limit reached (${maxRecords}). Output may be incomplete.`
4046
- );
4047
- }
4048
- }
4049
-
4050
- // src/emf-converter.ts
4051
- var MAX_METAFILE_RECURSION = 3;
4052
- async function processDeferredImages(ctx, deferredImages, recursionDepth = 0) {
4053
- emfLog(
4054
- `processDeferredImages: processing ${deferredImages.length} deferred images (recursionDepth=${recursionDepth})...`
4055
- );
4056
- for (let idx = 0; idx < deferredImages.length; idx++) {
4057
- const img = deferredImages[idx];
4058
- emfLog(
4059
- ` Deferred image [${idx}]: isMetafile=${img.isMetafile}, dataLen=${img.imageData.byteLength}, dest=(${img.dx.toFixed(1)},${img.dy.toFixed(1)},${img.dw.toFixed(1)},${img.dh.toFixed(1)}), transform=[${img.transform.map((v) => v.toFixed(3)).join(",")}]`
4060
- );
4061
- try {
4062
- const plainBuffer = new ArrayBuffer(img.imageData.byteLength);
4063
- const dstBytes = new Uint8Array(plainBuffer);
4064
- dstBytes.set(new Uint8Array(img.imageData));
4065
- ctx.setTransform(
4066
- img.transform[0],
4067
- img.transform[1],
4068
- img.transform[2],
4069
- img.transform[3],
4070
- img.transform[4],
4071
- img.transform[5]
4072
- );
4073
- if (img.isMetafile) {
4074
- if (recursionDepth >= MAX_METAFILE_RECURSION) {
4075
- emfWarn(
4076
- ` Deferred image [${idx}]: skipping embedded metafile \u2014 recursion depth ${recursionDepth} >= ${MAX_METAFILE_RECURSION}`
4077
- );
4078
- continue;
4079
- }
4080
- emfLog(` Deferred image [${idx}]: recursively converting embedded metafile...`);
4081
- const metafileDataUrl = await convertEmfToDataUrl(
4082
- plainBuffer,
4083
- void 0,
4084
- void 0,
4085
- void 0,
4086
- recursionDepth + 1
4087
- ) ?? await convertWmfToDataUrl(
4088
- plainBuffer,
4089
- void 0,
4090
- void 0,
4091
- void 0,
4092
- recursionDepth + 1
4093
- );
4094
- if (metafileDataUrl) {
4095
- emfLog(
4096
- ` Deferred image [${idx}]: metafile converted, dataUrl length=${metafileDataUrl.length}`
4097
- );
4098
- const byteString = atob(metafileDataUrl.split(",")[1]);
4099
- const mimeMatch = metafileDataUrl.match(/data:([^;]+)/);
4100
- const mime = mimeMatch ? mimeMatch[1] : "image/png";
4101
- const ab = new ArrayBuffer(byteString.length);
4102
- const ia = new Uint8Array(ab);
4103
- for (let i = 0; i < byteString.length; i++) {
4104
- ia[i] = byteString.charCodeAt(i);
4105
- }
4106
- const metaBlob = new Blob([ab], { type: mime });
4107
- emfLog(
4108
- ` Deferred image [${idx}]: creating ImageBitmap from ${metaBlob.size} byte blob (${mime})...`
4109
- );
4110
- const bitmap = await createImageBitmap(metaBlob);
4111
- emfLog(` Deferred image [${idx}]: ImageBitmap created ${bitmap.width}\xD7${bitmap.height}`);
4112
- ctx.drawImage(bitmap, img.dx, img.dy, img.dw, img.dh);
4113
- bitmap.close();
4114
- } else {
4115
- emfWarn(` Deferred image [${idx}]: metafile conversion returned null`);
4116
- }
4117
- } else {
4118
- emfLog(
4119
- ` Deferred image [${idx}]: creating ImageBitmap from ${plainBuffer.byteLength} byte blob...`
4120
- );
4121
- const blob = new Blob([plainBuffer]);
4122
- const bitmap = await createImageBitmap(blob);
4123
- emfLog(` Deferred image [${idx}]: ImageBitmap created ${bitmap.width}\xD7${bitmap.height}`);
4124
- ctx.drawImage(bitmap, img.dx, img.dy, img.dw, img.dh);
4125
- bitmap.close();
4126
- }
4127
- } catch (imgErr) {
4128
- imgErr instanceof Error ? imgErr.message : String(imgErr);
4129
- console.warn(
4130
- "[emf-converter] Deferred image draw failed:",
4131
- imgErr instanceof Error ? imgErr.message : imgErr,
4132
- `(isMetafile=${img.isMetafile}, dataLen=${img.imageData.byteLength})`
4133
- );
4134
- }
4135
- }
4136
- ctx.setTransform(1, 0, 0, 1, 0, 0);
4137
- }
4138
- async function convertEmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScale, recursionDepth = 0) {
4139
- if (recursionDepth > MAX_METAFILE_RECURSION) {
4140
- return null;
4141
- }
4142
- const opts = typeof optionsOrDpiScale === "number" ? { dpiScale: optionsOrDpiScale } : optionsOrDpiScale ?? {};
4143
- const dpiScale = opts.dpiScale ?? DEFAULT_DPI_SCALE;
4144
- const effectiveMaxWidth = maxWidth ?? opts.maxWidth;
4145
- const effectiveMaxHeight = maxHeight ?? opts.maxHeight;
4146
- try {
4147
- emfLog("=== convertEmfToDataUrl START ===");
4148
- emfLog(
4149
- `Input buffer: ${buffer.byteLength} bytes, maxWidth=${effectiveMaxWidth}, maxHeight=${effectiveMaxHeight}, dpiScale=${dpiScale}`
4150
- );
4151
- if (buffer.byteLength >= 16) {
4152
- const hdrBytes = new Uint8Array(buffer, 0, 16);
4153
- emfLog(
4154
- `First 16 bytes: [${Array.from(hdrBytes).map((b) => b.toString(16).padStart(2, "0")).join(" ")}]`
4155
- );
4156
- }
4157
- const view = new DataView(buffer);
4158
- const header = parseEmfHeader(view);
4159
- if (!header) {
4160
- emfLog("convertEmfToDataUrl: parseEmfHeader returned null \u2014 returning null");
4161
- return null;
4162
- }
4163
- const renderBounds = getRenderableEmfBounds(header);
4164
- if (!renderBounds) {
4165
- emfLog("convertEmfToDataUrl: getRenderableEmfBounds returned null \u2014 returning null");
4166
- return null;
4167
- }
4168
- const logicalW = renderBounds.right - renderBounds.left;
4169
- const logicalH = renderBounds.bottom - renderBounds.top;
4170
- emfLog(`convertEmfToDataUrl: logicalSize=${logicalW}\xD7${logicalH}`);
4171
- const setup = createCanvas(logicalW, logicalH, effectiveMaxWidth, effectiveMaxHeight, dpiScale);
4172
- if (!setup) {
4173
- emfLog("convertEmfToDataUrl: createCanvas returned null \u2014 returning null");
4174
- return null;
4175
- }
4176
- const { canvas, ctx } = setup;
4177
- emfLog(
4178
- `convertEmfToDataUrl: canvas created ${canvas.width}\xD7${canvas.height} (dpiScale=${dpiScale})`
4179
- );
4180
- ctx.save();
4181
- emfLog("convertEmfToDataUrl: starting replayEmfRecords...");
4182
- const deferredImages = replayEmfRecords(
4183
- view,
4184
- ctx,
4185
- renderBounds,
4186
- canvas.width,
4187
- canvas.height,
4188
- dpiScale
4189
- );
4190
- emfLog(
4191
- `convertEmfToDataUrl: replayEmfRecords returned ${deferredImages.length} deferred images`
4192
- );
4193
- ctx.restore();
4194
- await processDeferredImages(ctx, deferredImages);
4195
- emfLog("convertEmfToDataUrl: exporting canvas to PNG data URL...");
4196
- const result = await exportCanvasToPngDataUrl(canvas);
4197
- if (result) {
4198
- emfLog(`convertEmfToDataUrl: SUCCESS \u2014 data URL length=${result.length}`);
4199
- } else {
4200
- emfWarn("convertEmfToDataUrl: exportCanvasToPngDataUrl returned null");
4201
- }
4202
- emfLog("=== convertEmfToDataUrl END ===");
4203
- return result;
4204
- } catch (err) {
4205
- emfWarn("convertEmfToDataUrl: EXCEPTION:", err instanceof Error ? err.message : err);
4206
- console.warn("[pptx-editor] EMF conversion failed:", err instanceof Error ? err.message : err);
4207
- return null;
4208
- }
4209
- }
4210
- async function convertWmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScale, recursionDepth = 0) {
4211
- if (recursionDepth > MAX_METAFILE_RECURSION) {
4212
- return null;
4213
- }
4214
- const opts = typeof optionsOrDpiScale === "number" ? { dpiScale: optionsOrDpiScale } : optionsOrDpiScale ?? {};
4215
- const dpiScale = opts.dpiScale ?? DEFAULT_DPI_SCALE;
4216
- const effectiveMaxWidth = maxWidth ?? opts.maxWidth;
4217
- const effectiveMaxHeight = maxHeight ?? opts.maxHeight;
4218
- try {
4219
- emfLog(
4220
- "=== convertWmfToDataUrl START ===",
4221
- `buffer=${buffer.byteLength} bytes, dpiScale=${dpiScale}`
4222
- );
4223
- const view = new DataView(buffer);
4224
- const header = parseWmfHeader(view);
4225
- if (!header) {
4226
- emfLog("convertWmfToDataUrl: parseWmfHeader returned null");
4227
- return null;
4228
- }
4229
- const logicalW = header.boundsRight - header.boundsLeft;
4230
- const logicalH = header.boundsBottom - header.boundsTop;
4231
- emfLog(`convertWmfToDataUrl: logicalSize=${logicalW}\xD7${logicalH}`);
4232
- if (logicalW <= 0 || logicalH <= 0) {
4233
- emfLog("convertWmfToDataUrl: invalid dimensions \u2014 returning null");
4234
- return null;
4235
- }
4236
- const setup = createCanvas(logicalW, logicalH, effectiveMaxWidth, effectiveMaxHeight, dpiScale);
4237
- if (!setup) {
4238
- return null;
4239
- }
4240
- const { canvas, ctx } = setup;
4241
- ctx.save();
4242
- replayWmfRecords(view, ctx, header, canvas.width, canvas.height);
4243
- ctx.restore();
4244
- const result = await exportCanvasToPngDataUrl(canvas);
4245
- emfLog(`convertWmfToDataUrl: result=${result ? `dataUrl len=${result.length}` : "null"}`);
4246
- emfLog("=== convertWmfToDataUrl END ===");
4247
- return result;
4248
- } catch (err) {
4249
- emfWarn("convertWmfToDataUrl: EXCEPTION:", err instanceof Error ? err.message : err);
4250
- console.warn("[pptx-editor] WMF conversion failed:", err instanceof Error ? err.message : err);
4251
- return null;
4252
- }
4253
- }
4254
-
4255
- exports.DEFAULT_DPI_SCALE = DEFAULT_DPI_SCALE;
4256
- exports.convertEmfToDataUrl = convertEmfToDataUrl;
4257
- exports.convertWmfToDataUrl = convertWmfToDataUrl;