emf-converter 4.2.0 → 4.2.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 +1 -1
- package/dist/index.js +140 -44
- package/dist/index.mjs +140 -44
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -214,7 +214,7 @@ Everything below is verified against output painted by Windows itself; `src/gdi-
|
|
|
214
214
|
|
|
215
215
|
Everything is measured against output painted by Windows itself; `src/gdi-parity.fixture.test.ts` holds the exact per-fixture bounds.
|
|
216
216
|
|
|
217
|
-
- **Colour adjustment and image effects**: `EMR_SETCOLORADJUSTMENT` is read but not yet applied, and the `HALFTONE` stretch mode is not bit-exact. EMF+ image effects (`SerializableObject`) are applied to the image's pixels before it is drawn. Colour matrix and lookup table effects follow GDI+'s definitions; MS-EMFPLUS does not specify the algorithms of the others (blur, sharpen, brightness/contrast, levels, colour balance, colour curves, hue/saturation/lightness, tint, red-eye), so those use documented approximations and have not been checked against GDI+ output.
|
|
217
|
+
- **Colour adjustment and image effects**: `EMR_SETCOLORADJUSTMENT` is read but not yet applied, and the `HALFTONE` stretch mode is not bit-exact. EMF+ image effects (`SerializableObject`) are applied to the image's pixels before it is drawn. Colour matrix and lookup table effects follow GDI+'s definitions; MS-EMFPLUS does not specify the algorithms of the others (blur, sharpen, brightness/contrast, levels, colour balance, colour curves, hue/saturation/lightness, tint, red-eye), so those use documented approximations and have not been checked against GDI+ output. An effect is applied to the draw's source rectangle only, and a blur with `expandEdge` grows it by the blur radius, as GDI+ does. When the source rectangle is not in pixels, the effect is applied to the whole image without that growth. An effect is skipped when the image cannot be decoded to pixels: PNG and BMP always can be, while JPEG, GIF, TIFF and similar formats need a canvas backend (a browser or `@napi-rs/canvas`).
|
|
218
218
|
- **Pen transforms**: nonuniform and skewed EMF+ pen transforms are drawn with the transformed (elliptical or sheared) nib, but unlike uniform ones they are not yet verified against Windows output, and a singular pen transform is ignored.
|
|
219
219
|
- **Text**: ANSI, multi-string and small-text records now render; the Windows text fixtures retain a few glyph-edge differences and a `PolyTextOut` C1 control-glyph difference (under 0.1% of pixels; bounds in `src/emf-text-records.fixture.test.ts`). ANSI decoding uses the host's `TextDecoder` for common Windows code pages; unsupported encodings (including Johab and OEM on standard runtimes) fall back to Windows-1252. Vertical `ETO_PDY` advances require the `fonts` engine. Without `fonts`, SVG text measurements use deterministic estimates, so precise justification requires supplying the matching fonts.
|
|
220
220
|
- **Wide pens and paths**: flat-capped GDI pens 7 px and wider can differ by a few pixels at round joins, and dashed wide Bezier curves follow `WidenPath` (which Windows' direct drawing does not quite match); at most 0.2% of pixels on the fixtures. `EMR_WIDENPATH` does not reproduce the extra inner join triangles GDI's own `WidenPath` emits (visible only when the widened outline is itself stroked). EMF+ 1-pixel antialiased lines can differ by one antialiasing sample at their ends, some closed widened outlines by one sample along an edge, and Inset or compound pens on closed figures are approximate.
|
package/dist/index.js
CHANGED
|
@@ -19177,7 +19177,7 @@ function paintBrushThroughMask(rCtx, sampler, box, drawCoverage, mode = "canvas"
|
|
|
19177
19177
|
const device = plusWorldMatrix(rCtx);
|
|
19178
19178
|
const m = mask.ctx;
|
|
19179
19179
|
const shift = aliased ? aliasedSampleShift(rCtx) : geometry ? plusCanvasShift(rCtx) : 0;
|
|
19180
|
-
m.setTransform(device[0], device[1], device[2], device[3], device[4] - box.x + shift
|
|
19180
|
+
m.setTransform(device[0], device[1], device[2], device[3], device[4] - box.x + shift, device[5] - box.y + shift);
|
|
19181
19181
|
m.fillStyle = "#000";
|
|
19182
19182
|
m.strokeStyle = "#000";
|
|
19183
19183
|
drawCoverage(rasterMode !== "canvas" ? flatteningContext(m, device) : m);
|
|
@@ -23940,6 +23940,50 @@ function applyImageEffect(rgba, width, height, effect) {
|
|
|
23940
23940
|
return applyTint(rgba, effect.hue, effect.amount);
|
|
23941
23941
|
}
|
|
23942
23942
|
}
|
|
23943
|
+
function applyImageEffectToRect(rgba, width, height, effect, src) {
|
|
23944
|
+
if (width <= 0 || height <= 0 || rgba.length !== width * height * 4) {
|
|
23945
|
+
return null;
|
|
23946
|
+
}
|
|
23947
|
+
let x0 = 0;
|
|
23948
|
+
let y0 = 0;
|
|
23949
|
+
let x1 = width;
|
|
23950
|
+
let y1 = height;
|
|
23951
|
+
if (src) {
|
|
23952
|
+
x0 = clamp(Math.floor(src.x), 0, width);
|
|
23953
|
+
y0 = clamp(Math.floor(src.y), 0, height);
|
|
23954
|
+
x1 = clamp(Math.ceil(src.x + src.w), 0, width);
|
|
23955
|
+
y1 = clamp(Math.ceil(src.y + src.h), 0, height);
|
|
23956
|
+
if (x1 <= x0 || y1 <= y0) {
|
|
23957
|
+
return null;
|
|
23958
|
+
}
|
|
23959
|
+
}
|
|
23960
|
+
const pad = src && effect.kind === "blur" && effect.expandEdge ? Math.ceil(effect.radius) : 0;
|
|
23961
|
+
const cw = x1 - x0;
|
|
23962
|
+
const ch = y1 - y0;
|
|
23963
|
+
const w = cw + 2 * pad;
|
|
23964
|
+
const h = ch + 2 * pad;
|
|
23965
|
+
let region = rgba;
|
|
23966
|
+
if (w !== width || h !== height || x0 !== 0 || y0 !== 0) {
|
|
23967
|
+
region = new Uint8ClampedArray(w * h * 4);
|
|
23968
|
+
for (let y = 0; y < ch; y++) {
|
|
23969
|
+
const from = ((y0 + y) * width + x0) * 4;
|
|
23970
|
+
region.set(rgba.subarray(from, from + cw * 4), ((pad + y) * w + pad) * 4);
|
|
23971
|
+
}
|
|
23972
|
+
}
|
|
23973
|
+
const ox = x0 - pad;
|
|
23974
|
+
const oy = y0 - pad;
|
|
23975
|
+
const local = effect.kind === "redEyeCorrection" ? {
|
|
23976
|
+
kind: effect.kind,
|
|
23977
|
+
areas: effect.areas.map((a) => ({
|
|
23978
|
+
left: a.left - ox,
|
|
23979
|
+
top: a.top - oy,
|
|
23980
|
+
right: a.right - ox,
|
|
23981
|
+
bottom: a.bottom - oy
|
|
23982
|
+
}))
|
|
23983
|
+
} : effect;
|
|
23984
|
+
const out = applyImageEffect(region, w, h, local);
|
|
23985
|
+
return out ? { rgba: out, x: ox, y: oy, width: w, height: h, pad } : null;
|
|
23986
|
+
}
|
|
23943
23987
|
|
|
23944
23988
|
// src/emf-plus-draw-image.ts
|
|
23945
23989
|
function surfaceSize4(ctx) {
|
|
@@ -24076,7 +24120,7 @@ function drawNestedMetafile(rCtx, img, cached, source) {
|
|
|
24076
24120
|
rCtx.deferredImages.push(...deferred);
|
|
24077
24121
|
return true;
|
|
24078
24122
|
}
|
|
24079
|
-
function
|
|
24123
|
+
function effectedDraw(img, bitmap, effect, draw, source) {
|
|
24080
24124
|
let src = bitmap;
|
|
24081
24125
|
if (!src && img.data) {
|
|
24082
24126
|
const bmp = decodeBmpFile(img.data.slice(0));
|
|
@@ -24087,21 +24131,61 @@ function effectedBitmap(img, bitmap, effect) {
|
|
|
24087
24131
|
if (!src) {
|
|
24088
24132
|
return null;
|
|
24089
24133
|
}
|
|
24090
|
-
const
|
|
24091
|
-
|
|
24134
|
+
const rect = source && source.unit === UNIT_PIXEL && source.srcW > 0 && source.srcH > 0 && [source.srcX, source.srcY, source.srcW, source.srcH].every(Number.isFinite) ? source : null;
|
|
24135
|
+
const region = applyImageEffectToRect(
|
|
24136
|
+
src.rgba,
|
|
24137
|
+
src.width,
|
|
24138
|
+
src.height,
|
|
24139
|
+
effect,
|
|
24140
|
+
rect && { x: rect.srcX, y: rect.srcY, w: rect.srcW, h: rect.srcH }
|
|
24141
|
+
);
|
|
24142
|
+
if (!region) {
|
|
24143
|
+
return null;
|
|
24144
|
+
}
|
|
24145
|
+
const out = { kind: "bitmap", width: region.width, height: region.height, rgba: region.rgba };
|
|
24146
|
+
if (!rect) {
|
|
24147
|
+
return { bitmap: out, draw };
|
|
24148
|
+
}
|
|
24149
|
+
const grow = region.pad;
|
|
24150
|
+
const srcX = rect.srcX - grow - region.x;
|
|
24151
|
+
const srcY = rect.srcY - grow - region.y;
|
|
24152
|
+
const srcW = rect.srcW + 2 * grow;
|
|
24153
|
+
const srcH = rect.srcH + 2 * grow;
|
|
24154
|
+
const shift = [1, 0, 0, 1, region.x, region.y];
|
|
24155
|
+
const toWorld = mulMatrix(rect.toWorld(rect.srcX, rect.srcY, rect.srcW, rect.srcH), shift);
|
|
24156
|
+
return {
|
|
24157
|
+
bitmap: out,
|
|
24158
|
+
draw: {
|
|
24159
|
+
...draw,
|
|
24160
|
+
// The Canvas fallback scales the whole effected bitmap.
|
|
24161
|
+
transform: mulMatrix(draw.transform, toWorld),
|
|
24162
|
+
dx: 0,
|
|
24163
|
+
dy: 0,
|
|
24164
|
+
dw: region.width,
|
|
24165
|
+
dh: region.height,
|
|
24166
|
+
resample: draw.resample && {
|
|
24167
|
+
...draw.resample,
|
|
24168
|
+
srcX,
|
|
24169
|
+
srcY,
|
|
24170
|
+
srcW,
|
|
24171
|
+
srcH,
|
|
24172
|
+
toDevice: mulMatrix(draw.resample.toDevice, shift)
|
|
24173
|
+
}
|
|
24174
|
+
}
|
|
24175
|
+
};
|
|
24092
24176
|
}
|
|
24093
24177
|
function drawEmfPlusImageNow(rCtx, img, draw, source, effect = null) {
|
|
24094
24178
|
const cached = img.cacheKey !== void 0 ? rCtx.imageCache?.get(img.cacheKey) : void 0;
|
|
24095
24179
|
if (img.type === 2) {
|
|
24096
24180
|
return drawNestedMetafile(rCtx, img, cached, source);
|
|
24097
24181
|
}
|
|
24098
|
-
|
|
24099
|
-
const effected = effect ?
|
|
24182
|
+
const bitmap = cached && cached.kind === "bitmap" ? cached : null;
|
|
24183
|
+
const effected = effect ? effectedDraw(img, bitmap, effect, draw, source) : null;
|
|
24100
24184
|
if (effected) {
|
|
24101
|
-
bitmap
|
|
24185
|
+
return isSvgContext(rCtx.ctx) ? paintBitmapSvg(rCtx.ctx, img, effected.bitmap, effected.draw, true) : paintBitmapRaster(rCtx.ctx, effected.bitmap, effected.draw);
|
|
24102
24186
|
}
|
|
24103
24187
|
if (isSvgContext(rCtx.ctx)) {
|
|
24104
|
-
return paintBitmapSvg(rCtx.ctx, img, bitmap, draw
|
|
24188
|
+
return paintBitmapSvg(rCtx.ctx, img, bitmap, draw);
|
|
24105
24189
|
}
|
|
24106
24190
|
return bitmap ? paintBitmapRaster(rCtx.ctx, bitmap, draw) : false;
|
|
24107
24191
|
}
|
|
@@ -24393,28 +24477,32 @@ function strokePlusGeometry(rCtx, pen, buildPath, points, closedFigure = false)
|
|
|
24393
24477
|
if (mode !== "canvas" && size && strokeGdiplus(rCtx, pen, nib, sampler, buildPath, device, size, mode, closedFigure)) {
|
|
24394
24478
|
return;
|
|
24395
24479
|
}
|
|
24396
|
-
const
|
|
24397
|
-
|
|
24398
|
-
|
|
24480
|
+
const outline = hasCustomCap(pen) ? penOutlineWorld(rCtx, pen, nib, buildPath, closedFigure) : null;
|
|
24481
|
+
let box = null;
|
|
24482
|
+
if (size && outline) {
|
|
24483
|
+
box = deviceBounds(outline.flat(), device, size, 2);
|
|
24484
|
+
} else if (size) {
|
|
24485
|
+
const miter = Math.max(1, ctx.miterLimit || 10);
|
|
24486
|
+
const margin = pen.width * scale * miter / 2 + 2;
|
|
24487
|
+
box = deviceBounds(points, device, size, margin);
|
|
24488
|
+
}
|
|
24399
24489
|
if (size && !box) {
|
|
24400
24490
|
return;
|
|
24401
24491
|
}
|
|
24402
|
-
|
|
24403
|
-
|
|
24404
|
-
|
|
24405
|
-
|
|
24406
|
-
(c
|
|
24407
|
-
|
|
24408
|
-
|
|
24409
|
-
|
|
24410
|
-
|
|
24411
|
-
|
|
24412
|
-
|
|
24413
|
-
|
|
24414
|
-
|
|
24415
|
-
|
|
24416
|
-
(c, x, y) => c.isPointInStroke(x, y) && (!inset || c.isPointInPath(x, y))
|
|
24417
|
-
)) {
|
|
24492
|
+
const drawMask = outline ? (c) => {
|
|
24493
|
+
traceOutline(c, outline);
|
|
24494
|
+
c.fill("nonzero");
|
|
24495
|
+
} : (c) => {
|
|
24496
|
+
applyPlusPenStyle(c, pen, widthScale);
|
|
24497
|
+
stroke(c);
|
|
24498
|
+
c.beginPath();
|
|
24499
|
+
buildPath(c);
|
|
24500
|
+
if (nib) {
|
|
24501
|
+
c.transform(nib[0], nib[1], nib[2], nib[3], 0, 0);
|
|
24502
|
+
}
|
|
24503
|
+
};
|
|
24504
|
+
const hitTest = outline ? (c, x, y) => c.isPointInPath(x, y, "nonzero") : (c, x, y) => c.isPointInStroke(x, y) && (!inset || c.isPointInPath(x, y));
|
|
24505
|
+
if (box && paintBrushThroughMask(rCtx, sampler, box, drawMask, mode, hitTest)) {
|
|
24418
24506
|
return;
|
|
24419
24507
|
}
|
|
24420
24508
|
}
|
|
@@ -24579,18 +24667,18 @@ function customCapOutline(figures, width, devicePen, pen) {
|
|
|
24579
24667
|
outline.push(...geo.polygons);
|
|
24580
24668
|
return outline;
|
|
24581
24669
|
}
|
|
24582
|
-
function
|
|
24670
|
+
function penOutlineWorld(rCtx, pen, nib, buildPath, closedFigure) {
|
|
24583
24671
|
const device = plusWorldMatrix(rCtx);
|
|
24584
24672
|
const frame2 = penFrame(device, nib);
|
|
24585
24673
|
const deviceInv = invertAffine(device);
|
|
24586
24674
|
if (!frame2 || !deviceInv || !(frame2.unit > 0)) {
|
|
24587
|
-
return
|
|
24675
|
+
return null;
|
|
24588
24676
|
}
|
|
24589
24677
|
let figures;
|
|
24590
24678
|
try {
|
|
24591
24679
|
figures = recordDeviceFigures(buildPath, frame2.record);
|
|
24592
24680
|
} catch {
|
|
24593
|
-
return
|
|
24681
|
+
return null;
|
|
24594
24682
|
}
|
|
24595
24683
|
if (closedFigure) {
|
|
24596
24684
|
figures = figures.map((f) => ({ ...f, closed: true }));
|
|
@@ -24598,25 +24686,33 @@ function fillPenOutline(rCtx, pen, nib, paint, buildPath, closedFigure) {
|
|
|
24598
24686
|
const width = (pen.width || 1) * frame2.unit;
|
|
24599
24687
|
const devicePen = framePen(pen, frame2.unit);
|
|
24600
24688
|
const outline = hasCustomCap(pen) ? customCapOutline(figures, width, devicePen, pen) : widenFigures(figures, devicePen);
|
|
24601
|
-
const
|
|
24689
|
+
const m = mulMatrix(deviceInv, frame2.toDevice);
|
|
24690
|
+
return outline.map((poly) => poly.map((p) => ({ x: m[0] * p.x + m[2] * p.y + m[4], y: m[1] * p.x + m[3] * p.y + m[5] })));
|
|
24691
|
+
}
|
|
24692
|
+
function traceOutline(c, outline) {
|
|
24693
|
+
c.beginPath();
|
|
24694
|
+
for (const poly of outline) {
|
|
24695
|
+
poly.forEach((p, i) => {
|
|
24696
|
+
if (i === 0) {
|
|
24697
|
+
c.moveTo(p.x, p.y);
|
|
24698
|
+
} else {
|
|
24699
|
+
c.lineTo(p.x, p.y);
|
|
24700
|
+
}
|
|
24701
|
+
});
|
|
24702
|
+
c.closePath();
|
|
24703
|
+
}
|
|
24704
|
+
}
|
|
24705
|
+
function fillPenOutline(rCtx, pen, nib, paint, buildPath, closedFigure) {
|
|
24706
|
+
const outline = penOutlineWorld(rCtx, pen, nib, buildPath, closedFigure);
|
|
24707
|
+
if (!outline) {
|
|
24708
|
+
return false;
|
|
24709
|
+
}
|
|
24602
24710
|
const { ctx } = rCtx;
|
|
24603
24711
|
ctx.save();
|
|
24604
24712
|
try {
|
|
24605
24713
|
ctx.fillStyle = paint;
|
|
24606
24714
|
applyPlusWorldTransform(rCtx);
|
|
24607
|
-
ctx
|
|
24608
|
-
for (const poly of outline) {
|
|
24609
|
-
poly.forEach((p, i) => {
|
|
24610
|
-
const x = toWorld[0] * p.x + toWorld[2] * p.y + toWorld[4];
|
|
24611
|
-
const y = toWorld[1] * p.x + toWorld[3] * p.y + toWorld[5];
|
|
24612
|
-
if (i === 0) {
|
|
24613
|
-
ctx.moveTo(x, y);
|
|
24614
|
-
} else {
|
|
24615
|
-
ctx.lineTo(x, y);
|
|
24616
|
-
}
|
|
24617
|
-
});
|
|
24618
|
-
ctx.closePath();
|
|
24619
|
-
}
|
|
24715
|
+
traceOutline(ctx, outline);
|
|
24620
24716
|
ctx.fill("nonzero");
|
|
24621
24717
|
} finally {
|
|
24622
24718
|
ctx.restore();
|
package/dist/index.mjs
CHANGED
|
@@ -19175,7 +19175,7 @@ function paintBrushThroughMask(rCtx, sampler, box, drawCoverage, mode = "canvas"
|
|
|
19175
19175
|
const device = plusWorldMatrix(rCtx);
|
|
19176
19176
|
const m = mask.ctx;
|
|
19177
19177
|
const shift = aliased ? aliasedSampleShift(rCtx) : geometry ? plusCanvasShift(rCtx) : 0;
|
|
19178
|
-
m.setTransform(device[0], device[1], device[2], device[3], device[4] - box.x + shift
|
|
19178
|
+
m.setTransform(device[0], device[1], device[2], device[3], device[4] - box.x + shift, device[5] - box.y + shift);
|
|
19179
19179
|
m.fillStyle = "#000";
|
|
19180
19180
|
m.strokeStyle = "#000";
|
|
19181
19181
|
drawCoverage(rasterMode !== "canvas" ? flatteningContext(m, device) : m);
|
|
@@ -23938,6 +23938,50 @@ function applyImageEffect(rgba, width, height, effect) {
|
|
|
23938
23938
|
return applyTint(rgba, effect.hue, effect.amount);
|
|
23939
23939
|
}
|
|
23940
23940
|
}
|
|
23941
|
+
function applyImageEffectToRect(rgba, width, height, effect, src) {
|
|
23942
|
+
if (width <= 0 || height <= 0 || rgba.length !== width * height * 4) {
|
|
23943
|
+
return null;
|
|
23944
|
+
}
|
|
23945
|
+
let x0 = 0;
|
|
23946
|
+
let y0 = 0;
|
|
23947
|
+
let x1 = width;
|
|
23948
|
+
let y1 = height;
|
|
23949
|
+
if (src) {
|
|
23950
|
+
x0 = clamp(Math.floor(src.x), 0, width);
|
|
23951
|
+
y0 = clamp(Math.floor(src.y), 0, height);
|
|
23952
|
+
x1 = clamp(Math.ceil(src.x + src.w), 0, width);
|
|
23953
|
+
y1 = clamp(Math.ceil(src.y + src.h), 0, height);
|
|
23954
|
+
if (x1 <= x0 || y1 <= y0) {
|
|
23955
|
+
return null;
|
|
23956
|
+
}
|
|
23957
|
+
}
|
|
23958
|
+
const pad = src && effect.kind === "blur" && effect.expandEdge ? Math.ceil(effect.radius) : 0;
|
|
23959
|
+
const cw = x1 - x0;
|
|
23960
|
+
const ch = y1 - y0;
|
|
23961
|
+
const w = cw + 2 * pad;
|
|
23962
|
+
const h = ch + 2 * pad;
|
|
23963
|
+
let region = rgba;
|
|
23964
|
+
if (w !== width || h !== height || x0 !== 0 || y0 !== 0) {
|
|
23965
|
+
region = new Uint8ClampedArray(w * h * 4);
|
|
23966
|
+
for (let y = 0; y < ch; y++) {
|
|
23967
|
+
const from = ((y0 + y) * width + x0) * 4;
|
|
23968
|
+
region.set(rgba.subarray(from, from + cw * 4), ((pad + y) * w + pad) * 4);
|
|
23969
|
+
}
|
|
23970
|
+
}
|
|
23971
|
+
const ox = x0 - pad;
|
|
23972
|
+
const oy = y0 - pad;
|
|
23973
|
+
const local = effect.kind === "redEyeCorrection" ? {
|
|
23974
|
+
kind: effect.kind,
|
|
23975
|
+
areas: effect.areas.map((a) => ({
|
|
23976
|
+
left: a.left - ox,
|
|
23977
|
+
top: a.top - oy,
|
|
23978
|
+
right: a.right - ox,
|
|
23979
|
+
bottom: a.bottom - oy
|
|
23980
|
+
}))
|
|
23981
|
+
} : effect;
|
|
23982
|
+
const out = applyImageEffect(region, w, h, local);
|
|
23983
|
+
return out ? { rgba: out, x: ox, y: oy, width: w, height: h, pad } : null;
|
|
23984
|
+
}
|
|
23941
23985
|
|
|
23942
23986
|
// src/emf-plus-draw-image.ts
|
|
23943
23987
|
function surfaceSize4(ctx) {
|
|
@@ -24074,7 +24118,7 @@ function drawNestedMetafile(rCtx, img, cached, source) {
|
|
|
24074
24118
|
rCtx.deferredImages.push(...deferred);
|
|
24075
24119
|
return true;
|
|
24076
24120
|
}
|
|
24077
|
-
function
|
|
24121
|
+
function effectedDraw(img, bitmap, effect, draw, source) {
|
|
24078
24122
|
let src = bitmap;
|
|
24079
24123
|
if (!src && img.data) {
|
|
24080
24124
|
const bmp = decodeBmpFile(img.data.slice(0));
|
|
@@ -24085,21 +24129,61 @@ function effectedBitmap(img, bitmap, effect) {
|
|
|
24085
24129
|
if (!src) {
|
|
24086
24130
|
return null;
|
|
24087
24131
|
}
|
|
24088
|
-
const
|
|
24089
|
-
|
|
24132
|
+
const rect = source && source.unit === UNIT_PIXEL && source.srcW > 0 && source.srcH > 0 && [source.srcX, source.srcY, source.srcW, source.srcH].every(Number.isFinite) ? source : null;
|
|
24133
|
+
const region = applyImageEffectToRect(
|
|
24134
|
+
src.rgba,
|
|
24135
|
+
src.width,
|
|
24136
|
+
src.height,
|
|
24137
|
+
effect,
|
|
24138
|
+
rect && { x: rect.srcX, y: rect.srcY, w: rect.srcW, h: rect.srcH }
|
|
24139
|
+
);
|
|
24140
|
+
if (!region) {
|
|
24141
|
+
return null;
|
|
24142
|
+
}
|
|
24143
|
+
const out = { kind: "bitmap", width: region.width, height: region.height, rgba: region.rgba };
|
|
24144
|
+
if (!rect) {
|
|
24145
|
+
return { bitmap: out, draw };
|
|
24146
|
+
}
|
|
24147
|
+
const grow = region.pad;
|
|
24148
|
+
const srcX = rect.srcX - grow - region.x;
|
|
24149
|
+
const srcY = rect.srcY - grow - region.y;
|
|
24150
|
+
const srcW = rect.srcW + 2 * grow;
|
|
24151
|
+
const srcH = rect.srcH + 2 * grow;
|
|
24152
|
+
const shift = [1, 0, 0, 1, region.x, region.y];
|
|
24153
|
+
const toWorld = mulMatrix(rect.toWorld(rect.srcX, rect.srcY, rect.srcW, rect.srcH), shift);
|
|
24154
|
+
return {
|
|
24155
|
+
bitmap: out,
|
|
24156
|
+
draw: {
|
|
24157
|
+
...draw,
|
|
24158
|
+
// The Canvas fallback scales the whole effected bitmap.
|
|
24159
|
+
transform: mulMatrix(draw.transform, toWorld),
|
|
24160
|
+
dx: 0,
|
|
24161
|
+
dy: 0,
|
|
24162
|
+
dw: region.width,
|
|
24163
|
+
dh: region.height,
|
|
24164
|
+
resample: draw.resample && {
|
|
24165
|
+
...draw.resample,
|
|
24166
|
+
srcX,
|
|
24167
|
+
srcY,
|
|
24168
|
+
srcW,
|
|
24169
|
+
srcH,
|
|
24170
|
+
toDevice: mulMatrix(draw.resample.toDevice, shift)
|
|
24171
|
+
}
|
|
24172
|
+
}
|
|
24173
|
+
};
|
|
24090
24174
|
}
|
|
24091
24175
|
function drawEmfPlusImageNow(rCtx, img, draw, source, effect = null) {
|
|
24092
24176
|
const cached = img.cacheKey !== void 0 ? rCtx.imageCache?.get(img.cacheKey) : void 0;
|
|
24093
24177
|
if (img.type === 2) {
|
|
24094
24178
|
return drawNestedMetafile(rCtx, img, cached, source);
|
|
24095
24179
|
}
|
|
24096
|
-
|
|
24097
|
-
const effected = effect ?
|
|
24180
|
+
const bitmap = cached && cached.kind === "bitmap" ? cached : null;
|
|
24181
|
+
const effected = effect ? effectedDraw(img, bitmap, effect, draw, source) : null;
|
|
24098
24182
|
if (effected) {
|
|
24099
|
-
bitmap
|
|
24183
|
+
return isSvgContext(rCtx.ctx) ? paintBitmapSvg(rCtx.ctx, img, effected.bitmap, effected.draw, true) : paintBitmapRaster(rCtx.ctx, effected.bitmap, effected.draw);
|
|
24100
24184
|
}
|
|
24101
24185
|
if (isSvgContext(rCtx.ctx)) {
|
|
24102
|
-
return paintBitmapSvg(rCtx.ctx, img, bitmap, draw
|
|
24186
|
+
return paintBitmapSvg(rCtx.ctx, img, bitmap, draw);
|
|
24103
24187
|
}
|
|
24104
24188
|
return bitmap ? paintBitmapRaster(rCtx.ctx, bitmap, draw) : false;
|
|
24105
24189
|
}
|
|
@@ -24391,28 +24475,32 @@ function strokePlusGeometry(rCtx, pen, buildPath, points, closedFigure = false)
|
|
|
24391
24475
|
if (mode !== "canvas" && size && strokeGdiplus(rCtx, pen, nib, sampler, buildPath, device, size, mode, closedFigure)) {
|
|
24392
24476
|
return;
|
|
24393
24477
|
}
|
|
24394
|
-
const
|
|
24395
|
-
|
|
24396
|
-
|
|
24478
|
+
const outline = hasCustomCap(pen) ? penOutlineWorld(rCtx, pen, nib, buildPath, closedFigure) : null;
|
|
24479
|
+
let box = null;
|
|
24480
|
+
if (size && outline) {
|
|
24481
|
+
box = deviceBounds(outline.flat(), device, size, 2);
|
|
24482
|
+
} else if (size) {
|
|
24483
|
+
const miter = Math.max(1, ctx.miterLimit || 10);
|
|
24484
|
+
const margin = pen.width * scale * miter / 2 + 2;
|
|
24485
|
+
box = deviceBounds(points, device, size, margin);
|
|
24486
|
+
}
|
|
24397
24487
|
if (size && !box) {
|
|
24398
24488
|
return;
|
|
24399
24489
|
}
|
|
24400
|
-
|
|
24401
|
-
|
|
24402
|
-
|
|
24403
|
-
|
|
24404
|
-
(c
|
|
24405
|
-
|
|
24406
|
-
|
|
24407
|
-
|
|
24408
|
-
|
|
24409
|
-
|
|
24410
|
-
|
|
24411
|
-
|
|
24412
|
-
|
|
24413
|
-
|
|
24414
|
-
(c, x, y) => c.isPointInStroke(x, y) && (!inset || c.isPointInPath(x, y))
|
|
24415
|
-
)) {
|
|
24490
|
+
const drawMask = outline ? (c) => {
|
|
24491
|
+
traceOutline(c, outline);
|
|
24492
|
+
c.fill("nonzero");
|
|
24493
|
+
} : (c) => {
|
|
24494
|
+
applyPlusPenStyle(c, pen, widthScale);
|
|
24495
|
+
stroke(c);
|
|
24496
|
+
c.beginPath();
|
|
24497
|
+
buildPath(c);
|
|
24498
|
+
if (nib) {
|
|
24499
|
+
c.transform(nib[0], nib[1], nib[2], nib[3], 0, 0);
|
|
24500
|
+
}
|
|
24501
|
+
};
|
|
24502
|
+
const hitTest = outline ? (c, x, y) => c.isPointInPath(x, y, "nonzero") : (c, x, y) => c.isPointInStroke(x, y) && (!inset || c.isPointInPath(x, y));
|
|
24503
|
+
if (box && paintBrushThroughMask(rCtx, sampler, box, drawMask, mode, hitTest)) {
|
|
24416
24504
|
return;
|
|
24417
24505
|
}
|
|
24418
24506
|
}
|
|
@@ -24577,18 +24665,18 @@ function customCapOutline(figures, width, devicePen, pen) {
|
|
|
24577
24665
|
outline.push(...geo.polygons);
|
|
24578
24666
|
return outline;
|
|
24579
24667
|
}
|
|
24580
|
-
function
|
|
24668
|
+
function penOutlineWorld(rCtx, pen, nib, buildPath, closedFigure) {
|
|
24581
24669
|
const device = plusWorldMatrix(rCtx);
|
|
24582
24670
|
const frame2 = penFrame(device, nib);
|
|
24583
24671
|
const deviceInv = invertAffine(device);
|
|
24584
24672
|
if (!frame2 || !deviceInv || !(frame2.unit > 0)) {
|
|
24585
|
-
return
|
|
24673
|
+
return null;
|
|
24586
24674
|
}
|
|
24587
24675
|
let figures;
|
|
24588
24676
|
try {
|
|
24589
24677
|
figures = recordDeviceFigures(buildPath, frame2.record);
|
|
24590
24678
|
} catch {
|
|
24591
|
-
return
|
|
24679
|
+
return null;
|
|
24592
24680
|
}
|
|
24593
24681
|
if (closedFigure) {
|
|
24594
24682
|
figures = figures.map((f) => ({ ...f, closed: true }));
|
|
@@ -24596,25 +24684,33 @@ function fillPenOutline(rCtx, pen, nib, paint, buildPath, closedFigure) {
|
|
|
24596
24684
|
const width = (pen.width || 1) * frame2.unit;
|
|
24597
24685
|
const devicePen = framePen(pen, frame2.unit);
|
|
24598
24686
|
const outline = hasCustomCap(pen) ? customCapOutline(figures, width, devicePen, pen) : widenFigures(figures, devicePen);
|
|
24599
|
-
const
|
|
24687
|
+
const m = mulMatrix(deviceInv, frame2.toDevice);
|
|
24688
|
+
return outline.map((poly) => poly.map((p) => ({ x: m[0] * p.x + m[2] * p.y + m[4], y: m[1] * p.x + m[3] * p.y + m[5] })));
|
|
24689
|
+
}
|
|
24690
|
+
function traceOutline(c, outline) {
|
|
24691
|
+
c.beginPath();
|
|
24692
|
+
for (const poly of outline) {
|
|
24693
|
+
poly.forEach((p, i) => {
|
|
24694
|
+
if (i === 0) {
|
|
24695
|
+
c.moveTo(p.x, p.y);
|
|
24696
|
+
} else {
|
|
24697
|
+
c.lineTo(p.x, p.y);
|
|
24698
|
+
}
|
|
24699
|
+
});
|
|
24700
|
+
c.closePath();
|
|
24701
|
+
}
|
|
24702
|
+
}
|
|
24703
|
+
function fillPenOutline(rCtx, pen, nib, paint, buildPath, closedFigure) {
|
|
24704
|
+
const outline = penOutlineWorld(rCtx, pen, nib, buildPath, closedFigure);
|
|
24705
|
+
if (!outline) {
|
|
24706
|
+
return false;
|
|
24707
|
+
}
|
|
24600
24708
|
const { ctx } = rCtx;
|
|
24601
24709
|
ctx.save();
|
|
24602
24710
|
try {
|
|
24603
24711
|
ctx.fillStyle = paint;
|
|
24604
24712
|
applyPlusWorldTransform(rCtx);
|
|
24605
|
-
ctx
|
|
24606
|
-
for (const poly of outline) {
|
|
24607
|
-
poly.forEach((p, i) => {
|
|
24608
|
-
const x = toWorld[0] * p.x + toWorld[2] * p.y + toWorld[4];
|
|
24609
|
-
const y = toWorld[1] * p.x + toWorld[3] * p.y + toWorld[5];
|
|
24610
|
-
if (i === 0) {
|
|
24611
|
-
ctx.moveTo(x, y);
|
|
24612
|
-
} else {
|
|
24613
|
-
ctx.lineTo(x, y);
|
|
24614
|
-
}
|
|
24615
|
-
});
|
|
24616
|
-
ctx.closePath();
|
|
24617
|
-
}
|
|
24713
|
+
traceOutline(ctx, outline);
|
|
24618
24714
|
ctx.fill("nonzero");
|
|
24619
24715
|
} finally {
|
|
24620
24716
|
ctx.restore();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "emf-converter",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.1",
|
|
4
4
|
"description": "Convert EMF/WMF metafile binaries to PNG data URLs or SVG (markup, data URL, React/JSX)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"canvas",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"test": "vitest run",
|
|
46
46
|
"test:watch": "vitest",
|
|
47
47
|
"pack": "bun run build && bun pm pack",
|
|
48
|
+
"docs:typecheck": "vue-tsc --noEmit -p docs/tsconfig.json",
|
|
48
49
|
"docs:dev": "vitepress dev docs",
|
|
49
50
|
"docs:build": "vitepress build docs",
|
|
50
51
|
"docs:preview": "vitepress preview docs"
|
|
@@ -59,7 +60,8 @@
|
|
|
59
60
|
"tsup": "^8.5.1",
|
|
60
61
|
"typescript": "^6.0.3",
|
|
61
62
|
"vitepress": "^1.6.4",
|
|
62
|
-
"vitest": "^5.0.0"
|
|
63
|
+
"vitest": "^5.0.0",
|
|
64
|
+
"vue-tsc": "^3.3.11"
|
|
63
65
|
},
|
|
64
66
|
"peerDependencies": {
|
|
65
67
|
"@napi-rs/canvas": "^1.0.9"
|