emf-converter 1.6.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -10
- package/dist/index.d.mts +8 -12
- package/dist/index.d.ts +8 -12
- package/dist/index.js +37 -38
- package/dist/index.mjs +37 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,22 +44,20 @@ const pngDataUrl = await convertEmfToDataUrl(emfBuffer);
|
|
|
44
44
|
const wmfPng = await convertWmfToDataUrl(wmfBuffer);
|
|
45
45
|
|
|
46
46
|
// Optional: limit output dimensions (aspect ratio preserved)
|
|
47
|
-
const scaled = await convertEmfToDataUrl(emfBuffer, 1024, 768);
|
|
47
|
+
const scaled = await convertEmfToDataUrl(emfBuffer, { maxWidth: 1024, maxHeight: 768 });
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
Both functions return `Promise<string | null>` — `null` if the buffer is invalid or no Canvas API is available.
|
|
51
51
|
|
|
52
52
|
## API
|
|
53
53
|
|
|
54
|
-
### `convertEmfToDataUrl(buffer,
|
|
54
|
+
### `convertEmfToDataUrl(buffer, options?)` · `convertWmfToDataUrl(buffer, options?)`
|
|
55
55
|
|
|
56
|
-
| Parameter | Type
|
|
57
|
-
| ----------- |
|
|
58
|
-
| `buffer` | `ArrayBuffer`
|
|
59
|
-
| `
|
|
60
|
-
|
|
|
61
|
-
| `options` | `EmfConvertOptions \| number` (opt.) | Options object, or a numeric `dpiScale` (legacy) |
|
|
62
|
-
| **Returns** | `Promise<string \| null>` | PNG data URL or `null` on failure |
|
|
56
|
+
| Parameter | Type | Description |
|
|
57
|
+
| ----------- | ----------------------------- | ---------------------------------------------------- |
|
|
58
|
+
| `buffer` | `ArrayBuffer` | Raw EMF/WMF file bytes |
|
|
59
|
+
| `options` | `EmfConvertOptions` (optional)| Output size, DPI scale, record limits, font mapping |
|
|
60
|
+
| **Returns** | `Promise<string \| null>` | PNG data URL or `null` on failure |
|
|
63
61
|
|
|
64
62
|
#### `EmfConvertOptions`
|
|
65
63
|
|
|
@@ -73,7 +71,7 @@ Both functions return `Promise<string | null>` — `null` if the buffer is inval
|
|
|
73
71
|
| `fontFamilyMap` | `Record<string, string>` | — | Maps Windows face names (case-insensitive) to fonts available locally, e.g. `{ calibri: 'Carlito' }` |
|
|
74
72
|
|
|
75
73
|
```ts
|
|
76
|
-
const png = await convertEmfToDataUrl(buffer,
|
|
74
|
+
const png = await convertEmfToDataUrl(buffer, {
|
|
77
75
|
dpiScale: 2,
|
|
78
76
|
fontFamilyMap: { calibri: 'Carlito', 'ms shell dlg': 'Tahoma' },
|
|
79
77
|
});
|
package/dist/index.d.mts
CHANGED
|
@@ -58,14 +58,12 @@ interface EmfConvertOptions {
|
|
|
58
58
|
* - The logical bounds are zero-sized or negative.
|
|
59
59
|
* - No canvas API is available (e.g. headless test environment).
|
|
60
60
|
*
|
|
61
|
-
* @param buffer
|
|
62
|
-
* @param
|
|
63
|
-
*
|
|
64
|
-
* @param optionsOrDpiScale - Either an {@link EmfConvertOptions} object, or
|
|
65
|
-
* a numeric DPI scale (for backward compatibility). Default DPI scale is 2.
|
|
61
|
+
* @param buffer - The raw EMF file bytes.
|
|
62
|
+
* @param options - Optional {@link EmfConvertOptions} controlling output size,
|
|
63
|
+
* DPI scale, record limits, and font mapping.
|
|
66
64
|
* @returns A `data:image/png;base64,…` string, or `null` on failure.
|
|
67
65
|
*/
|
|
68
|
-
declare function convertEmfToDataUrl(buffer: ArrayBuffer,
|
|
66
|
+
declare function convertEmfToDataUrl(buffer: ArrayBuffer, options?: EmfConvertOptions, recursionDepth?: number): Promise<string | null>;
|
|
69
67
|
/**
|
|
70
68
|
* Converts a WMF (Windows Metafile) binary buffer to a PNG data-URL string.
|
|
71
69
|
*
|
|
@@ -80,14 +78,12 @@ declare function convertEmfToDataUrl(buffer: ArrayBuffer, maxWidth?: number, max
|
|
|
80
78
|
* - The header cannot be parsed or reports invalid dimensions.
|
|
81
79
|
* - No canvas API is available.
|
|
82
80
|
*
|
|
83
|
-
* @param buffer
|
|
84
|
-
* @param
|
|
85
|
-
*
|
|
86
|
-
* @param optionsOrDpiScale - Either an {@link EmfConvertOptions} object, or
|
|
87
|
-
* a numeric DPI scale. Default DPI scale is 2.
|
|
81
|
+
* @param buffer - The raw WMF file bytes.
|
|
82
|
+
* @param options - Optional {@link EmfConvertOptions} controlling output size,
|
|
83
|
+
* DPI scale, record limits, and font mapping.
|
|
88
84
|
* @returns A `data:image/png;base64,…` string, or `null` on failure.
|
|
89
85
|
*/
|
|
90
|
-
declare function convertWmfToDataUrl(buffer: ArrayBuffer,
|
|
86
|
+
declare function convertWmfToDataUrl(buffer: ArrayBuffer, options?: EmfConvertOptions, recursionDepth?: number): Promise<string | null>;
|
|
91
87
|
|
|
92
88
|
/**
|
|
93
89
|
* Canvas creation, styling, string reading, stock objects, and export helpers.
|
package/dist/index.d.ts
CHANGED
|
@@ -58,14 +58,12 @@ interface EmfConvertOptions {
|
|
|
58
58
|
* - The logical bounds are zero-sized or negative.
|
|
59
59
|
* - No canvas API is available (e.g. headless test environment).
|
|
60
60
|
*
|
|
61
|
-
* @param buffer
|
|
62
|
-
* @param
|
|
63
|
-
*
|
|
64
|
-
* @param optionsOrDpiScale - Either an {@link EmfConvertOptions} object, or
|
|
65
|
-
* a numeric DPI scale (for backward compatibility). Default DPI scale is 2.
|
|
61
|
+
* @param buffer - The raw EMF file bytes.
|
|
62
|
+
* @param options - Optional {@link EmfConvertOptions} controlling output size,
|
|
63
|
+
* DPI scale, record limits, and font mapping.
|
|
66
64
|
* @returns A `data:image/png;base64,…` string, or `null` on failure.
|
|
67
65
|
*/
|
|
68
|
-
declare function convertEmfToDataUrl(buffer: ArrayBuffer,
|
|
66
|
+
declare function convertEmfToDataUrl(buffer: ArrayBuffer, options?: EmfConvertOptions, recursionDepth?: number): Promise<string | null>;
|
|
69
67
|
/**
|
|
70
68
|
* Converts a WMF (Windows Metafile) binary buffer to a PNG data-URL string.
|
|
71
69
|
*
|
|
@@ -80,14 +78,12 @@ declare function convertEmfToDataUrl(buffer: ArrayBuffer, maxWidth?: number, max
|
|
|
80
78
|
* - The header cannot be parsed or reports invalid dimensions.
|
|
81
79
|
* - No canvas API is available.
|
|
82
80
|
*
|
|
83
|
-
* @param buffer
|
|
84
|
-
* @param
|
|
85
|
-
*
|
|
86
|
-
* @param optionsOrDpiScale - Either an {@link EmfConvertOptions} object, or
|
|
87
|
-
* a numeric DPI scale. Default DPI scale is 2.
|
|
81
|
+
* @param buffer - The raw WMF file bytes.
|
|
82
|
+
* @param options - Optional {@link EmfConvertOptions} controlling output size,
|
|
83
|
+
* DPI scale, record limits, and font mapping.
|
|
88
84
|
* @returns A `data:image/png;base64,…` string, or `null` on failure.
|
|
89
85
|
*/
|
|
90
|
-
declare function convertWmfToDataUrl(buffer: ArrayBuffer,
|
|
86
|
+
declare function convertWmfToDataUrl(buffer: ArrayBuffer, options?: EmfConvertOptions, recursionDepth?: number): Promise<string | null>;
|
|
91
87
|
|
|
92
88
|
/**
|
|
93
89
|
* Canvas creation, styling, string reading, stock objects, and export helpers.
|
package/dist/index.js
CHANGED
|
@@ -423,19 +423,22 @@ function mapFontFamily(face, map) {
|
|
|
423
423
|
}
|
|
424
424
|
return resolved;
|
|
425
425
|
}
|
|
426
|
-
function
|
|
426
|
+
function fontSizePx(state, scale = 1) {
|
|
427
|
+
return Math.max(Math.abs(state.fontHeight) * Math.abs(scale || 1), 8);
|
|
428
|
+
}
|
|
429
|
+
function applyFont(ctx, state, scale = 1) {
|
|
427
430
|
const italic = state.fontItalic ? "italic " : "";
|
|
428
431
|
const weight = cssFontWeight(state.fontWeight);
|
|
429
432
|
const weightPart = weight ? `${weight} ` : "";
|
|
430
|
-
const size =
|
|
433
|
+
const size = fontSizePx(state, scale);
|
|
431
434
|
const family = mapFontFamily(state.fontFamily, state.fontFamilyMap);
|
|
432
435
|
ctx.font = `${italic}${weightPart}${size}px ${family}`;
|
|
433
436
|
}
|
|
434
|
-
function drawTextDecorations(ctx, state, x, y, width) {
|
|
437
|
+
function drawTextDecorations(ctx, state, x, y, width, scale = 1) {
|
|
435
438
|
if (!state.fontUnderline && !state.fontStrikeOut) {
|
|
436
439
|
return;
|
|
437
440
|
}
|
|
438
|
-
const size =
|
|
441
|
+
const size = fontSizePx(state, scale);
|
|
439
442
|
const thickness = Math.max(1, Math.round(size / 14));
|
|
440
443
|
const prevFill = ctx.fillStyle;
|
|
441
444
|
ctx.fillStyle = state.textColor;
|
|
@@ -1400,16 +1403,12 @@ function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
|
|
|
1400
1403
|
if (nChars > 0 && offString > 0 && offset + offString + nChars * 2 <= maxOffset) {
|
|
1401
1404
|
const text = readUtf16LE(view, offset + offString, nChars);
|
|
1402
1405
|
if (text.length > 0) {
|
|
1403
|
-
|
|
1406
|
+
const fontScale = Math.abs(gmh(rCtx, 1));
|
|
1407
|
+
applyFont(ctx, state, fontScale);
|
|
1404
1408
|
ctx.fillStyle = state.textColor;
|
|
1405
|
-
|
|
1409
|
+
const vAlign = state.textAlign & 24;
|
|
1410
|
+
const alignBaseline = vAlign === 24 ? "alphabetic" : vAlign === 8 ? "bottom" : "top";
|
|
1406
1411
|
let alignHoriz = "left";
|
|
1407
|
-
if (state.textAlign & 8) {
|
|
1408
|
-
alignBaseline = "bottom";
|
|
1409
|
-
}
|
|
1410
|
-
if (state.textAlign & 24) {
|
|
1411
|
-
alignBaseline = "alphabetic";
|
|
1412
|
-
}
|
|
1413
1412
|
if (state.textAlign & 6) {
|
|
1414
1413
|
alignHoriz = "center";
|
|
1415
1414
|
}
|
|
@@ -1420,7 +1419,7 @@ function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
|
|
|
1420
1419
|
ctx.textAlign = alignHoriz;
|
|
1421
1420
|
if (state.bkMode === 2) {
|
|
1422
1421
|
const measured = ctx.measureText(text);
|
|
1423
|
-
const bgH = state
|
|
1422
|
+
const bgH = fontSizePx(state, fontScale);
|
|
1424
1423
|
ctx.fillStyle = state.bkColor;
|
|
1425
1424
|
ctx.fillRect(gmx(rCtx, refX), gmy(rCtx, refY) - bgH, measured.width, bgH);
|
|
1426
1425
|
ctx.fillStyle = state.textColor;
|
|
@@ -1430,24 +1429,36 @@ function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
|
|
|
1430
1429
|
const w = ctx.measureText(text).width;
|
|
1431
1430
|
const baseX = gmx(rCtx, refX);
|
|
1432
1431
|
const startX = alignHoriz === "center" ? baseX - w / 2 : alignHoriz === "right" ? baseX - w : baseX;
|
|
1433
|
-
drawTextDecorations(ctx, state, startX, gmy(rCtx, refY), w);
|
|
1432
|
+
drawTextDecorations(ctx, state, startX, gmy(rCtx, refY), w, fontScale);
|
|
1434
1433
|
}
|
|
1435
1434
|
}
|
|
1436
1435
|
}
|
|
1437
1436
|
}
|
|
1438
1437
|
return true;
|
|
1439
1438
|
}
|
|
1439
|
+
var ROP_PATCOPY = 15728673;
|
|
1440
|
+
var BS_NULL = 1;
|
|
1440
1441
|
function handleBitBlt(rCtx, offset, dataOff, recSize) {
|
|
1441
|
-
const { ctx, view } = rCtx;
|
|
1442
|
+
const { ctx, view, state } = rCtx;
|
|
1442
1443
|
if (recSize >= 96) {
|
|
1443
1444
|
const dstX = view.getInt32(dataOff + 16, true);
|
|
1444
1445
|
const dstY = view.getInt32(dataOff + 20, true);
|
|
1445
1446
|
const dstW = view.getInt32(dataOff + 24, true);
|
|
1446
1447
|
const dstH = view.getInt32(dataOff + 28, true);
|
|
1448
|
+
const rop = view.getUint32(dataOff + 32, true);
|
|
1447
1449
|
const offBmiSrc = view.getUint32(dataOff + 76, true);
|
|
1448
1450
|
const cbBmiSrc = view.getUint32(dataOff + 80, true);
|
|
1449
1451
|
const offBitsSrc = view.getUint32(dataOff + 84, true);
|
|
1450
1452
|
const cbBitsSrc = view.getUint32(dataOff + 88, true);
|
|
1453
|
+
if (offBmiSrc === 0 && rop === ROP_PATCOPY) {
|
|
1454
|
+
const prevFill = ctx.fillStyle;
|
|
1455
|
+
if (state.brushStyle !== BS_NULL) {
|
|
1456
|
+
ctx.fillStyle = state.brushColor;
|
|
1457
|
+
ctx.fillRect(gmx(rCtx, dstX), gmy(rCtx, dstY), gmw(rCtx, dstW), gmh(rCtx, dstH));
|
|
1458
|
+
}
|
|
1459
|
+
ctx.fillStyle = prevFill;
|
|
1460
|
+
return true;
|
|
1461
|
+
}
|
|
1451
1462
|
if (offBmiSrc > 0 && cbBmiSrc > 0 && offBitsSrc > 0 && cbBitsSrc > 0) {
|
|
1452
1463
|
const imageData = decodeDibToImageData(
|
|
1453
1464
|
view,
|
|
@@ -4609,7 +4620,7 @@ function handleWmfDrawRecord(wCtx, recType, offset, dataOff, recSize) {
|
|
|
4609
4620
|
if (txOff + 4 <= offset + recSize) {
|
|
4610
4621
|
const ty2 = view.getInt16(txOff, true);
|
|
4611
4622
|
const txCoord = view.getInt16(txOff + 2, true);
|
|
4612
|
-
applyFont(ctx, state);
|
|
4623
|
+
applyFont(ctx, state, Math.abs(mh(1)));
|
|
4613
4624
|
ctx.fillStyle = state.textColor;
|
|
4614
4625
|
ctx.fillText(text, mx(txCoord), my(ty2));
|
|
4615
4626
|
}
|
|
@@ -4632,7 +4643,7 @@ function handleWmfDrawRecord(wCtx, recType, offset, dataOff, recSize) {
|
|
|
4632
4643
|
}
|
|
4633
4644
|
text += String.fromCharCode(ch);
|
|
4634
4645
|
}
|
|
4635
|
-
applyFont(ctx, state);
|
|
4646
|
+
applyFont(ctx, state, Math.abs(mh(1)));
|
|
4636
4647
|
ctx.fillStyle = state.textColor;
|
|
4637
4648
|
ctx.fillText(text, mx(txCoord), my(ty2));
|
|
4638
4649
|
}
|
|
@@ -4857,19 +4868,7 @@ async function processDeferredImages(ctx, deferredImages, recursionDepth = 0) {
|
|
|
4857
4868
|
continue;
|
|
4858
4869
|
}
|
|
4859
4870
|
emfLog(` Deferred image [${idx}]: recursively converting embedded metafile...`);
|
|
4860
|
-
const metafileDataUrl = await convertEmfToDataUrl(
|
|
4861
|
-
plainBuffer,
|
|
4862
|
-
void 0,
|
|
4863
|
-
void 0,
|
|
4864
|
-
void 0,
|
|
4865
|
-
recursionDepth + 1
|
|
4866
|
-
) ?? await convertWmfToDataUrl(
|
|
4867
|
-
plainBuffer,
|
|
4868
|
-
void 0,
|
|
4869
|
-
void 0,
|
|
4870
|
-
void 0,
|
|
4871
|
-
recursionDepth + 1
|
|
4872
|
-
);
|
|
4871
|
+
const metafileDataUrl = await convertEmfToDataUrl(plainBuffer, void 0, recursionDepth + 1) ?? await convertWmfToDataUrl(plainBuffer, void 0, recursionDepth + 1);
|
|
4873
4872
|
if (metafileDataUrl) {
|
|
4874
4873
|
emfLog(
|
|
4875
4874
|
` Deferred image [${idx}]: metafile converted, dataUrl length=${metafileDataUrl.length}`
|
|
@@ -4914,14 +4913,14 @@ async function processDeferredImages(ctx, deferredImages, recursionDepth = 0) {
|
|
|
4914
4913
|
}
|
|
4915
4914
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
4916
4915
|
}
|
|
4917
|
-
async function convertEmfToDataUrl(buffer,
|
|
4916
|
+
async function convertEmfToDataUrl(buffer, options, recursionDepth = 0) {
|
|
4918
4917
|
if (recursionDepth > MAX_METAFILE_RECURSION) {
|
|
4919
4918
|
return null;
|
|
4920
4919
|
}
|
|
4921
|
-
const opts =
|
|
4920
|
+
const opts = options ?? {};
|
|
4922
4921
|
const dpiScale = opts.dpiScale ?? DEFAULT_DPI_SCALE;
|
|
4923
|
-
const effectiveMaxWidth =
|
|
4924
|
-
const effectiveMaxHeight =
|
|
4922
|
+
const effectiveMaxWidth = opts.maxWidth;
|
|
4923
|
+
const effectiveMaxHeight = opts.maxHeight;
|
|
4925
4924
|
const replayOptions = {
|
|
4926
4925
|
maxRecords: opts.maxRecords,
|
|
4927
4926
|
maxRecordsEmfPlus: opts.maxRecords,
|
|
@@ -4999,14 +4998,14 @@ async function convertEmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScal
|
|
|
4999
4998
|
return null;
|
|
5000
4999
|
}
|
|
5001
5000
|
}
|
|
5002
|
-
async function convertWmfToDataUrl(buffer,
|
|
5001
|
+
async function convertWmfToDataUrl(buffer, options, recursionDepth = 0) {
|
|
5003
5002
|
if (recursionDepth > MAX_METAFILE_RECURSION) {
|
|
5004
5003
|
return null;
|
|
5005
5004
|
}
|
|
5006
|
-
const opts =
|
|
5005
|
+
const opts = options ?? {};
|
|
5007
5006
|
const dpiScale = opts.dpiScale ?? DEFAULT_DPI_SCALE;
|
|
5008
|
-
const effectiveMaxWidth =
|
|
5009
|
-
const effectiveMaxHeight =
|
|
5007
|
+
const effectiveMaxWidth = opts.maxWidth;
|
|
5008
|
+
const effectiveMaxHeight = opts.maxHeight;
|
|
5010
5009
|
const replayOptions = {
|
|
5011
5010
|
maxRecords: opts.maxRecords,
|
|
5012
5011
|
fontFamilyMap: opts.fontFamilyMap
|
package/dist/index.mjs
CHANGED
|
@@ -421,19 +421,22 @@ function mapFontFamily(face, map) {
|
|
|
421
421
|
}
|
|
422
422
|
return resolved;
|
|
423
423
|
}
|
|
424
|
-
function
|
|
424
|
+
function fontSizePx(state, scale = 1) {
|
|
425
|
+
return Math.max(Math.abs(state.fontHeight) * Math.abs(scale || 1), 8);
|
|
426
|
+
}
|
|
427
|
+
function applyFont(ctx, state, scale = 1) {
|
|
425
428
|
const italic = state.fontItalic ? "italic " : "";
|
|
426
429
|
const weight = cssFontWeight(state.fontWeight);
|
|
427
430
|
const weightPart = weight ? `${weight} ` : "";
|
|
428
|
-
const size =
|
|
431
|
+
const size = fontSizePx(state, scale);
|
|
429
432
|
const family = mapFontFamily(state.fontFamily, state.fontFamilyMap);
|
|
430
433
|
ctx.font = `${italic}${weightPart}${size}px ${family}`;
|
|
431
434
|
}
|
|
432
|
-
function drawTextDecorations(ctx, state, x, y, width) {
|
|
435
|
+
function drawTextDecorations(ctx, state, x, y, width, scale = 1) {
|
|
433
436
|
if (!state.fontUnderline && !state.fontStrikeOut) {
|
|
434
437
|
return;
|
|
435
438
|
}
|
|
436
|
-
const size =
|
|
439
|
+
const size = fontSizePx(state, scale);
|
|
437
440
|
const thickness = Math.max(1, Math.round(size / 14));
|
|
438
441
|
const prevFill = ctx.fillStyle;
|
|
439
442
|
ctx.fillStyle = state.textColor;
|
|
@@ -1398,16 +1401,12 @@ function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
|
|
|
1398
1401
|
if (nChars > 0 && offString > 0 && offset + offString + nChars * 2 <= maxOffset) {
|
|
1399
1402
|
const text = readUtf16LE(view, offset + offString, nChars);
|
|
1400
1403
|
if (text.length > 0) {
|
|
1401
|
-
|
|
1404
|
+
const fontScale = Math.abs(gmh(rCtx, 1));
|
|
1405
|
+
applyFont(ctx, state, fontScale);
|
|
1402
1406
|
ctx.fillStyle = state.textColor;
|
|
1403
|
-
|
|
1407
|
+
const vAlign = state.textAlign & 24;
|
|
1408
|
+
const alignBaseline = vAlign === 24 ? "alphabetic" : vAlign === 8 ? "bottom" : "top";
|
|
1404
1409
|
let alignHoriz = "left";
|
|
1405
|
-
if (state.textAlign & 8) {
|
|
1406
|
-
alignBaseline = "bottom";
|
|
1407
|
-
}
|
|
1408
|
-
if (state.textAlign & 24) {
|
|
1409
|
-
alignBaseline = "alphabetic";
|
|
1410
|
-
}
|
|
1411
1410
|
if (state.textAlign & 6) {
|
|
1412
1411
|
alignHoriz = "center";
|
|
1413
1412
|
}
|
|
@@ -1418,7 +1417,7 @@ function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
|
|
|
1418
1417
|
ctx.textAlign = alignHoriz;
|
|
1419
1418
|
if (state.bkMode === 2) {
|
|
1420
1419
|
const measured = ctx.measureText(text);
|
|
1421
|
-
const bgH = state
|
|
1420
|
+
const bgH = fontSizePx(state, fontScale);
|
|
1422
1421
|
ctx.fillStyle = state.bkColor;
|
|
1423
1422
|
ctx.fillRect(gmx(rCtx, refX), gmy(rCtx, refY) - bgH, measured.width, bgH);
|
|
1424
1423
|
ctx.fillStyle = state.textColor;
|
|
@@ -1428,24 +1427,36 @@ function handleExtTextOutW(rCtx, offset, dataOff, recSize) {
|
|
|
1428
1427
|
const w = ctx.measureText(text).width;
|
|
1429
1428
|
const baseX = gmx(rCtx, refX);
|
|
1430
1429
|
const startX = alignHoriz === "center" ? baseX - w / 2 : alignHoriz === "right" ? baseX - w : baseX;
|
|
1431
|
-
drawTextDecorations(ctx, state, startX, gmy(rCtx, refY), w);
|
|
1430
|
+
drawTextDecorations(ctx, state, startX, gmy(rCtx, refY), w, fontScale);
|
|
1432
1431
|
}
|
|
1433
1432
|
}
|
|
1434
1433
|
}
|
|
1435
1434
|
}
|
|
1436
1435
|
return true;
|
|
1437
1436
|
}
|
|
1437
|
+
var ROP_PATCOPY = 15728673;
|
|
1438
|
+
var BS_NULL = 1;
|
|
1438
1439
|
function handleBitBlt(rCtx, offset, dataOff, recSize) {
|
|
1439
|
-
const { ctx, view } = rCtx;
|
|
1440
|
+
const { ctx, view, state } = rCtx;
|
|
1440
1441
|
if (recSize >= 96) {
|
|
1441
1442
|
const dstX = view.getInt32(dataOff + 16, true);
|
|
1442
1443
|
const dstY = view.getInt32(dataOff + 20, true);
|
|
1443
1444
|
const dstW = view.getInt32(dataOff + 24, true);
|
|
1444
1445
|
const dstH = view.getInt32(dataOff + 28, true);
|
|
1446
|
+
const rop = view.getUint32(dataOff + 32, true);
|
|
1445
1447
|
const offBmiSrc = view.getUint32(dataOff + 76, true);
|
|
1446
1448
|
const cbBmiSrc = view.getUint32(dataOff + 80, true);
|
|
1447
1449
|
const offBitsSrc = view.getUint32(dataOff + 84, true);
|
|
1448
1450
|
const cbBitsSrc = view.getUint32(dataOff + 88, true);
|
|
1451
|
+
if (offBmiSrc === 0 && rop === ROP_PATCOPY) {
|
|
1452
|
+
const prevFill = ctx.fillStyle;
|
|
1453
|
+
if (state.brushStyle !== BS_NULL) {
|
|
1454
|
+
ctx.fillStyle = state.brushColor;
|
|
1455
|
+
ctx.fillRect(gmx(rCtx, dstX), gmy(rCtx, dstY), gmw(rCtx, dstW), gmh(rCtx, dstH));
|
|
1456
|
+
}
|
|
1457
|
+
ctx.fillStyle = prevFill;
|
|
1458
|
+
return true;
|
|
1459
|
+
}
|
|
1449
1460
|
if (offBmiSrc > 0 && cbBmiSrc > 0 && offBitsSrc > 0 && cbBitsSrc > 0) {
|
|
1450
1461
|
const imageData = decodeDibToImageData(
|
|
1451
1462
|
view,
|
|
@@ -4607,7 +4618,7 @@ function handleWmfDrawRecord(wCtx, recType, offset, dataOff, recSize) {
|
|
|
4607
4618
|
if (txOff + 4 <= offset + recSize) {
|
|
4608
4619
|
const ty2 = view.getInt16(txOff, true);
|
|
4609
4620
|
const txCoord = view.getInt16(txOff + 2, true);
|
|
4610
|
-
applyFont(ctx, state);
|
|
4621
|
+
applyFont(ctx, state, Math.abs(mh(1)));
|
|
4611
4622
|
ctx.fillStyle = state.textColor;
|
|
4612
4623
|
ctx.fillText(text, mx(txCoord), my(ty2));
|
|
4613
4624
|
}
|
|
@@ -4630,7 +4641,7 @@ function handleWmfDrawRecord(wCtx, recType, offset, dataOff, recSize) {
|
|
|
4630
4641
|
}
|
|
4631
4642
|
text += String.fromCharCode(ch);
|
|
4632
4643
|
}
|
|
4633
|
-
applyFont(ctx, state);
|
|
4644
|
+
applyFont(ctx, state, Math.abs(mh(1)));
|
|
4634
4645
|
ctx.fillStyle = state.textColor;
|
|
4635
4646
|
ctx.fillText(text, mx(txCoord), my(ty2));
|
|
4636
4647
|
}
|
|
@@ -4855,19 +4866,7 @@ async function processDeferredImages(ctx, deferredImages, recursionDepth = 0) {
|
|
|
4855
4866
|
continue;
|
|
4856
4867
|
}
|
|
4857
4868
|
emfLog(` Deferred image [${idx}]: recursively converting embedded metafile...`);
|
|
4858
|
-
const metafileDataUrl = await convertEmfToDataUrl(
|
|
4859
|
-
plainBuffer,
|
|
4860
|
-
void 0,
|
|
4861
|
-
void 0,
|
|
4862
|
-
void 0,
|
|
4863
|
-
recursionDepth + 1
|
|
4864
|
-
) ?? await convertWmfToDataUrl(
|
|
4865
|
-
plainBuffer,
|
|
4866
|
-
void 0,
|
|
4867
|
-
void 0,
|
|
4868
|
-
void 0,
|
|
4869
|
-
recursionDepth + 1
|
|
4870
|
-
);
|
|
4869
|
+
const metafileDataUrl = await convertEmfToDataUrl(plainBuffer, void 0, recursionDepth + 1) ?? await convertWmfToDataUrl(plainBuffer, void 0, recursionDepth + 1);
|
|
4871
4870
|
if (metafileDataUrl) {
|
|
4872
4871
|
emfLog(
|
|
4873
4872
|
` Deferred image [${idx}]: metafile converted, dataUrl length=${metafileDataUrl.length}`
|
|
@@ -4912,14 +4911,14 @@ async function processDeferredImages(ctx, deferredImages, recursionDepth = 0) {
|
|
|
4912
4911
|
}
|
|
4913
4912
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
4914
4913
|
}
|
|
4915
|
-
async function convertEmfToDataUrl(buffer,
|
|
4914
|
+
async function convertEmfToDataUrl(buffer, options, recursionDepth = 0) {
|
|
4916
4915
|
if (recursionDepth > MAX_METAFILE_RECURSION) {
|
|
4917
4916
|
return null;
|
|
4918
4917
|
}
|
|
4919
|
-
const opts =
|
|
4918
|
+
const opts = options ?? {};
|
|
4920
4919
|
const dpiScale = opts.dpiScale ?? DEFAULT_DPI_SCALE;
|
|
4921
|
-
const effectiveMaxWidth =
|
|
4922
|
-
const effectiveMaxHeight =
|
|
4920
|
+
const effectiveMaxWidth = opts.maxWidth;
|
|
4921
|
+
const effectiveMaxHeight = opts.maxHeight;
|
|
4923
4922
|
const replayOptions = {
|
|
4924
4923
|
maxRecords: opts.maxRecords,
|
|
4925
4924
|
maxRecordsEmfPlus: opts.maxRecords,
|
|
@@ -4997,14 +4996,14 @@ async function convertEmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrDpiScal
|
|
|
4997
4996
|
return null;
|
|
4998
4997
|
}
|
|
4999
4998
|
}
|
|
5000
|
-
async function convertWmfToDataUrl(buffer,
|
|
4999
|
+
async function convertWmfToDataUrl(buffer, options, recursionDepth = 0) {
|
|
5001
5000
|
if (recursionDepth > MAX_METAFILE_RECURSION) {
|
|
5002
5001
|
return null;
|
|
5003
5002
|
}
|
|
5004
|
-
const opts =
|
|
5003
|
+
const opts = options ?? {};
|
|
5005
5004
|
const dpiScale = opts.dpiScale ?? DEFAULT_DPI_SCALE;
|
|
5006
|
-
const effectiveMaxWidth =
|
|
5007
|
-
const effectiveMaxHeight =
|
|
5005
|
+
const effectiveMaxWidth = opts.maxWidth;
|
|
5006
|
+
const effectiveMaxHeight = opts.maxHeight;
|
|
5008
5007
|
const replayOptions = {
|
|
5009
5008
|
maxRecords: opts.maxRecords,
|
|
5010
5009
|
fontFamilyMap: opts.fontFamilyMap
|