emf-converter 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -5
- package/dist/index.js +1623 -1112
- package/dist/index.mjs +1623 -1112
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -50,6 +50,428 @@ function invertCssColor(color) {
|
|
|
50
50
|
return color;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// src/emf-dib-rle-decoder.ts
|
|
54
|
+
function decodeRleBitmap(view, bitsOffset, bitsSize, width, height, _topDown, isRle4, colorTable, out, setPixel) {
|
|
55
|
+
let x = 0;
|
|
56
|
+
let y = height - 1;
|
|
57
|
+
let off = bitsOffset;
|
|
58
|
+
const endOff = bitsOffset + bitsSize;
|
|
59
|
+
while (off + 1 < endOff && y >= 0) {
|
|
60
|
+
const first = view.getUint8(off);
|
|
61
|
+
const second = view.getUint8(off + 1);
|
|
62
|
+
off += 2;
|
|
63
|
+
if (first === 0) {
|
|
64
|
+
if (second === 0) {
|
|
65
|
+
x = 0;
|
|
66
|
+
y--;
|
|
67
|
+
} else if (second === 1) {
|
|
68
|
+
break;
|
|
69
|
+
} else if (second === 2) {
|
|
70
|
+
if (off + 1 >= endOff) {
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
x += view.getUint8(off);
|
|
74
|
+
y -= view.getUint8(off + 1);
|
|
75
|
+
off += 2;
|
|
76
|
+
} else {
|
|
77
|
+
const count = second;
|
|
78
|
+
if (!isRle4) {
|
|
79
|
+
for (let i = 0; i < count && off < endOff && x < width; i++) {
|
|
80
|
+
const idx = view.getUint8(off++);
|
|
81
|
+
if (idx < colorTable.length) {
|
|
82
|
+
setPixel(
|
|
83
|
+
x,
|
|
84
|
+
height - 1 - y,
|
|
85
|
+
colorTable[idx][0],
|
|
86
|
+
colorTable[idx][1],
|
|
87
|
+
colorTable[idx][2],
|
|
88
|
+
255
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
x++;
|
|
92
|
+
}
|
|
93
|
+
if (count & 1) {
|
|
94
|
+
off++;
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
const bytes = Math.ceil(count / 2);
|
|
98
|
+
let pi = 0;
|
|
99
|
+
for (let i = 0; i < bytes && off < endOff; i++) {
|
|
100
|
+
const byte = view.getUint8(off++);
|
|
101
|
+
for (let nibble = 0; nibble < 2 && pi < count; nibble++) {
|
|
102
|
+
const idx = nibble === 0 ? byte >> 4 & 15 : byte & 15;
|
|
103
|
+
if (idx < colorTable.length && x < width) {
|
|
104
|
+
setPixel(
|
|
105
|
+
x,
|
|
106
|
+
height - 1 - y,
|
|
107
|
+
colorTable[idx][0],
|
|
108
|
+
colorTable[idx][1],
|
|
109
|
+
colorTable[idx][2],
|
|
110
|
+
255
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
x++;
|
|
114
|
+
pi++;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (bytes & 1) {
|
|
118
|
+
off++;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} else if (!isRle4) {
|
|
123
|
+
const idx = second;
|
|
124
|
+
const c = idx < colorTable.length ? colorTable[idx] : [0, 0, 0];
|
|
125
|
+
for (let i = 0; i < first && x < width; i++) {
|
|
126
|
+
setPixel(x, height - 1 - y, c[0], c[1], c[2], 255);
|
|
127
|
+
x++;
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
const hi = second >> 4 & 15;
|
|
131
|
+
const lo = second & 15;
|
|
132
|
+
for (let i = 0; i < first && x < width; i++) {
|
|
133
|
+
const idx = (i & 1) === 0 ? hi : lo;
|
|
134
|
+
if (idx < colorTable.length) {
|
|
135
|
+
setPixel(
|
|
136
|
+
x,
|
|
137
|
+
height - 1 - y,
|
|
138
|
+
colorTable[idx][0],
|
|
139
|
+
colorTable[idx][1],
|
|
140
|
+
colorTable[idx][2],
|
|
141
|
+
255
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
x++;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return createImageDataCompat(
|
|
149
|
+
new Uint8ClampedArray(out.buffer, out.byteOffset, out.byteLength),
|
|
150
|
+
width,
|
|
151
|
+
height
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/emf-dib-uncompressed.ts
|
|
156
|
+
function countTrailingZeros(v) {
|
|
157
|
+
if (v === 0) {
|
|
158
|
+
return 0;
|
|
159
|
+
}
|
|
160
|
+
let c = 0;
|
|
161
|
+
let val = v;
|
|
162
|
+
while ((val & 1) === 0) {
|
|
163
|
+
val >>>= 1;
|
|
164
|
+
c++;
|
|
165
|
+
}
|
|
166
|
+
return c;
|
|
167
|
+
}
|
|
168
|
+
var BI_BITFIELDS = 3;
|
|
169
|
+
function parseBitfieldMasks(view, bmiOffset, headerSize, compression, bitCount) {
|
|
170
|
+
let rMask = 0, gMask = 0, bMask = 0;
|
|
171
|
+
let rShift = 0, gShift = 0, bShift = 0;
|
|
172
|
+
let rMax = 1, gMax = 1, bMax = 1;
|
|
173
|
+
if (compression === BI_BITFIELDS) {
|
|
174
|
+
const bfOff = bmiOffset + headerSize;
|
|
175
|
+
if (bfOff + 12 > view.byteLength) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
rMask = view.getUint32(bfOff, true);
|
|
179
|
+
gMask = view.getUint32(bfOff + 4, true);
|
|
180
|
+
bMask = view.getUint32(bfOff + 8, true);
|
|
181
|
+
rShift = countTrailingZeros(rMask);
|
|
182
|
+
gShift = countTrailingZeros(gMask);
|
|
183
|
+
bShift = countTrailingZeros(bMask);
|
|
184
|
+
rMax = rMask >>> rShift || 1;
|
|
185
|
+
gMax = gMask >>> gShift || 1;
|
|
186
|
+
bMax = bMask >>> bShift || 1;
|
|
187
|
+
} else if (bitCount === 16) {
|
|
188
|
+
rMask = 31744;
|
|
189
|
+
gMask = 992;
|
|
190
|
+
bMask = 31;
|
|
191
|
+
rShift = 10;
|
|
192
|
+
gShift = 5;
|
|
193
|
+
bShift = 0;
|
|
194
|
+
rMax = 31;
|
|
195
|
+
gMax = 31;
|
|
196
|
+
bMax = 31;
|
|
197
|
+
}
|
|
198
|
+
return { rMask, gMask, bMask, rShift, gShift, bShift, rMax, gMax, bMax };
|
|
199
|
+
}
|
|
200
|
+
function decodeUncompressedRows(view, bitsOffset, width, height, topDown, bitCount, colorTable, masks, out) {
|
|
201
|
+
const rowStride = Math.floor((bitCount * width + 31) / 32) * 4;
|
|
202
|
+
const { rMask, gMask, bMask, rShift, gShift, bShift, rMax, gMax, bMax } = masks;
|
|
203
|
+
for (let y = 0; y < height; y++) {
|
|
204
|
+
const srcY = topDown ? y : height - 1 - y;
|
|
205
|
+
const rowStart = bitsOffset + srcY * rowStride;
|
|
206
|
+
if (rowStart + rowStride > view.byteLength) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
for (let x = 0; x < width; x++) {
|
|
210
|
+
const dstPx = (y * width + x) * 4;
|
|
211
|
+
if (bitCount === 1) {
|
|
212
|
+
const byteIdx = rowStart + (x >> 3);
|
|
213
|
+
const bit = view.getUint8(byteIdx) >> 7 - (x & 7) & 1;
|
|
214
|
+
if (bit < colorTable.length) {
|
|
215
|
+
out[dstPx] = colorTable[bit][0];
|
|
216
|
+
out[dstPx + 1] = colorTable[bit][1];
|
|
217
|
+
out[dstPx + 2] = colorTable[bit][2];
|
|
218
|
+
}
|
|
219
|
+
out[dstPx + 3] = 255;
|
|
220
|
+
} else if (bitCount === 4) {
|
|
221
|
+
const byteIdx = rowStart + (x >> 1);
|
|
222
|
+
const nibble = (x & 1) === 0 ? view.getUint8(byteIdx) >> 4 & 15 : view.getUint8(byteIdx) & 15;
|
|
223
|
+
if (nibble < colorTable.length) {
|
|
224
|
+
out[dstPx] = colorTable[nibble][0];
|
|
225
|
+
out[dstPx + 1] = colorTable[nibble][1];
|
|
226
|
+
out[dstPx + 2] = colorTable[nibble][2];
|
|
227
|
+
}
|
|
228
|
+
out[dstPx + 3] = 255;
|
|
229
|
+
} else if (bitCount === 8) {
|
|
230
|
+
const idx = view.getUint8(rowStart + x);
|
|
231
|
+
if (idx < colorTable.length) {
|
|
232
|
+
out[dstPx] = colorTable[idx][0];
|
|
233
|
+
out[dstPx + 1] = colorTable[idx][1];
|
|
234
|
+
out[dstPx + 2] = colorTable[idx][2];
|
|
235
|
+
}
|
|
236
|
+
out[dstPx + 3] = 255;
|
|
237
|
+
} else if (bitCount === 16) {
|
|
238
|
+
const val = view.getUint16(rowStart + x * 2, true);
|
|
239
|
+
out[dstPx] = Math.round(((val & rMask) >>> rShift) * 255 / rMax);
|
|
240
|
+
out[dstPx + 1] = Math.round(((val & gMask) >>> gShift) * 255 / gMax);
|
|
241
|
+
out[dstPx + 2] = Math.round(((val & bMask) >>> bShift) * 255 / bMax);
|
|
242
|
+
out[dstPx + 3] = 255;
|
|
243
|
+
} else if (bitCount === 24) {
|
|
244
|
+
const srcPx = rowStart + x * 3;
|
|
245
|
+
out[dstPx] = view.getUint8(srcPx + 2);
|
|
246
|
+
out[dstPx + 1] = view.getUint8(srcPx + 1);
|
|
247
|
+
out[dstPx + 2] = view.getUint8(srcPx);
|
|
248
|
+
out[dstPx + 3] = 255;
|
|
249
|
+
} else {
|
|
250
|
+
const srcPx = rowStart + x * 4;
|
|
251
|
+
const bb = view.getUint8(srcPx);
|
|
252
|
+
const gg = view.getUint8(srcPx + 1);
|
|
253
|
+
const rr = view.getUint8(srcPx + 2);
|
|
254
|
+
const aa = view.getUint8(srcPx + 3);
|
|
255
|
+
out[dstPx] = rr;
|
|
256
|
+
out[dstPx + 1] = gg;
|
|
257
|
+
out[dstPx + 2] = bb;
|
|
258
|
+
out[dstPx + 3] = aa === 0 ? 255 : aa;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/emf-dib-decoder.ts
|
|
265
|
+
function decodeDibToImageData(view, bmiOffset, bitsOffset, bitsSize) {
|
|
266
|
+
if (bmiOffset < 0 || bitsOffset < 0 || bmiOffset + 40 > view.byteLength || bitsOffset + bitsSize > view.byteLength) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
const headerSize = view.getUint32(bmiOffset, true);
|
|
270
|
+
if (headerSize < 40 || bmiOffset + headerSize > view.byteLength) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
const width = view.getInt32(bmiOffset + 4, true);
|
|
274
|
+
const heightRaw = view.getInt32(bmiOffset + 8, true);
|
|
275
|
+
const planes = view.getUint16(bmiOffset + 12, true);
|
|
276
|
+
const bitCount = view.getUint16(bmiOffset + 14, true);
|
|
277
|
+
const compression = view.getUint32(bmiOffset + 16, true);
|
|
278
|
+
if (planes !== 1 || width <= 0 || heightRaw === 0) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
if (width > 8192 || Math.abs(heightRaw) > 8192) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
const BI_RGB = 0;
|
|
285
|
+
const BI_RLE8 = 1;
|
|
286
|
+
const BI_RLE4 = 2;
|
|
287
|
+
const BI_BITFIELDS2 = 3;
|
|
288
|
+
if (bitCount !== 1 && bitCount !== 4 && bitCount !== 8 && bitCount !== 16 && bitCount !== 24 && bitCount !== 32) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
if (compression === BI_RLE8 && bitCount !== 8) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
if (compression === BI_RLE4 && bitCount !== 4) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
if (compression === BI_BITFIELDS2 && bitCount !== 16 && bitCount !== 32) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
if (compression !== BI_RGB && compression !== BI_RLE8 && compression !== BI_RLE4 && compression !== BI_BITFIELDS2) {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
const height = Math.abs(heightRaw);
|
|
304
|
+
const topDown = heightRaw < 0;
|
|
305
|
+
const colorTable = [];
|
|
306
|
+
if (bitCount <= 8) {
|
|
307
|
+
const maxColors = 1 << bitCount;
|
|
308
|
+
const colorsUsed = view.getUint32(bmiOffset + 32, true) || maxColors;
|
|
309
|
+
const numColors = Math.min(colorsUsed, maxColors);
|
|
310
|
+
const ctOffset = bmiOffset + headerSize;
|
|
311
|
+
if (ctOffset + numColors * 4 > view.byteLength) {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
for (let i = 0; i < numColors; i++) {
|
|
315
|
+
const b = view.getUint8(ctOffset + i * 4);
|
|
316
|
+
const g = view.getUint8(ctOffset + i * 4 + 1);
|
|
317
|
+
const r = view.getUint8(ctOffset + i * 4 + 2);
|
|
318
|
+
colorTable.push([r, g, b]);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const masks = parseBitfieldMasks(view, bmiOffset, headerSize, compression, bitCount);
|
|
322
|
+
if (!masks) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
const out = new Uint8ClampedArray(width * height * 4);
|
|
326
|
+
if (compression === BI_RLE8 || compression === BI_RLE4) {
|
|
327
|
+
const setPixel = (x, y, r, g, b, a) => {
|
|
328
|
+
const dstPx = (y * width + x) * 4;
|
|
329
|
+
out[dstPx] = r;
|
|
330
|
+
out[dstPx + 1] = g;
|
|
331
|
+
out[dstPx + 2] = b;
|
|
332
|
+
out[dstPx + 3] = a;
|
|
333
|
+
};
|
|
334
|
+
return decodeRleBitmap(
|
|
335
|
+
view,
|
|
336
|
+
bitsOffset,
|
|
337
|
+
bitsSize,
|
|
338
|
+
width,
|
|
339
|
+
height,
|
|
340
|
+
topDown,
|
|
341
|
+
compression === BI_RLE4,
|
|
342
|
+
colorTable,
|
|
343
|
+
out,
|
|
344
|
+
setPixel
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
decodeUncompressedRows(
|
|
348
|
+
view,
|
|
349
|
+
bitsOffset,
|
|
350
|
+
width,
|
|
351
|
+
height,
|
|
352
|
+
topDown,
|
|
353
|
+
bitCount,
|
|
354
|
+
colorTable,
|
|
355
|
+
masks,
|
|
356
|
+
out
|
|
357
|
+
);
|
|
358
|
+
return createImageDataCompat(out, width, height);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/emf-gdi-brush-pattern.ts
|
|
362
|
+
var HATCH_ROWS = [
|
|
363
|
+
[0, 0, 0, 255, 0, 0, 0, 0],
|
|
364
|
+
// HS_HORIZONTAL
|
|
365
|
+
[8, 8, 8, 8, 8, 8, 8, 8],
|
|
366
|
+
// HS_VERTICAL
|
|
367
|
+
[128, 64, 32, 16, 8, 4, 2, 1],
|
|
368
|
+
// HS_FDIAGONAL
|
|
369
|
+
[1, 2, 4, 8, 16, 32, 64, 128],
|
|
370
|
+
// HS_BDIAGONAL
|
|
371
|
+
[8, 8, 8, 255, 8, 8, 8, 8],
|
|
372
|
+
// HS_CROSS
|
|
373
|
+
[129, 66, 36, 24, 24, 36, 66, 129]
|
|
374
|
+
// HS_DIAGCROSS
|
|
375
|
+
];
|
|
376
|
+
function hatchBit(hatch, x, y) {
|
|
377
|
+
const rows = HATCH_ROWS[hatch] ?? HATCH_ROWS[0];
|
|
378
|
+
return (rows[y & 7] >> 7 - (x & 7) & 1) === 1;
|
|
379
|
+
}
|
|
380
|
+
function cssHexToRgb(color) {
|
|
381
|
+
const m = /^#([0-9a-f]{6}|[0-9a-f]{3})$/i.exec(color.trim());
|
|
382
|
+
if (!m) {
|
|
383
|
+
return 0;
|
|
384
|
+
}
|
|
385
|
+
const hex = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];
|
|
386
|
+
return parseInt(hex, 16);
|
|
387
|
+
}
|
|
388
|
+
function decodeMonoBits(view, bmiOffset, bitsOffset) {
|
|
389
|
+
if (bmiOffset + 16 > view.byteLength) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
const width = view.getInt32(bmiOffset + 4, true);
|
|
393
|
+
const heightRaw = view.getInt32(bmiOffset + 8, true);
|
|
394
|
+
const bitCount = view.getUint16(bmiOffset + 14, true);
|
|
395
|
+
const height = Math.abs(heightRaw);
|
|
396
|
+
if (bitCount !== 1 || width <= 0 || height === 0 || width > 256 || height > 256) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
const stride = (width + 31 >> 5) * 4;
|
|
400
|
+
if (bitsOffset + stride * height > view.byteLength) {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
const bits = new Uint8Array(width * height);
|
|
404
|
+
for (let row = 0; row < height; row++) {
|
|
405
|
+
const y = heightRaw > 0 ? height - 1 - row : row;
|
|
406
|
+
const rowOff = bitsOffset + row * stride;
|
|
407
|
+
for (let x = 0; x < width; x++) {
|
|
408
|
+
bits[y * width + x] = view.getUint8(rowOff + (x >> 3)) >> 7 - (x & 7) & 1;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return { width, height, bits };
|
|
412
|
+
}
|
|
413
|
+
function parsePatternBrush(view, offset, dataOff, mono) {
|
|
414
|
+
if (dataOff + 24 > view.byteLength) {
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
const offBmi = view.getUint32(dataOff + 8, true);
|
|
418
|
+
const offBits = view.getUint32(dataOff + 16, true);
|
|
419
|
+
const cbBits = view.getUint32(dataOff + 20, true);
|
|
420
|
+
const bmi = offset + offBmi;
|
|
421
|
+
const bitsAt = offset + offBits;
|
|
422
|
+
const bitCount = bmi + 16 <= view.byteLength ? view.getUint16(bmi + 14, true) : 0;
|
|
423
|
+
if (mono || bitCount === 1) {
|
|
424
|
+
const decoded = decodeMonoBits(view, bmi, bitsAt);
|
|
425
|
+
if (decoded && mono) {
|
|
426
|
+
return { kind: "mono", ...decoded };
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const image = decodeDibToImageData(view, bmi, bitsAt, cbBits);
|
|
430
|
+
if (!image) {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
const rgb = new Uint32Array(image.width * image.height);
|
|
434
|
+
const d = image.data;
|
|
435
|
+
for (let i = 0; i < rgb.length; i++) {
|
|
436
|
+
rgb[i] = d[i * 4] << 16 | d[i * 4 + 1] << 8 | d[i * 4 + 2];
|
|
437
|
+
}
|
|
438
|
+
return { kind: "bitmap", width: image.width, height: image.height, rgb };
|
|
439
|
+
}
|
|
440
|
+
function realizeBrush(state) {
|
|
441
|
+
if (state.brushStyle === 1) {
|
|
442
|
+
return { kind: "none" };
|
|
443
|
+
}
|
|
444
|
+
const pattern = state.brushPattern;
|
|
445
|
+
if (!pattern) {
|
|
446
|
+
return { kind: "solid", rgb: cssHexToRgb(state.brushColor) };
|
|
447
|
+
}
|
|
448
|
+
if (pattern.kind === "bitmap") {
|
|
449
|
+
return { kind: "tile", width: pattern.width, height: pattern.height, rgb: pattern.rgb };
|
|
450
|
+
}
|
|
451
|
+
const bk = cssHexToRgb(state.bkColor);
|
|
452
|
+
if (pattern.kind === "hatch") {
|
|
453
|
+
const fg = cssHexToRgb(state.brushColor);
|
|
454
|
+
const rgb2 = new Uint32Array(64);
|
|
455
|
+
for (let y = 0; y < 8; y++) {
|
|
456
|
+
for (let x = 0; x < 8; x++) {
|
|
457
|
+
rgb2[y * 8 + x] = hatchBit(pattern.hatch, x, y) ? fg : bk;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return { kind: "tile", width: 8, height: 8, rgb: rgb2 };
|
|
461
|
+
}
|
|
462
|
+
const text = cssHexToRgb(state.textColor);
|
|
463
|
+
const rgb = new Uint32Array(pattern.width * pattern.height);
|
|
464
|
+
for (let i = 0; i < rgb.length; i++) {
|
|
465
|
+
rgb[i] = pattern.bits[i] ? bk : text;
|
|
466
|
+
}
|
|
467
|
+
return { kind: "tile", width: pattern.width, height: pattern.height, rgb };
|
|
468
|
+
}
|
|
469
|
+
function sampleTile(tile, devX, devY, orgX, orgY) {
|
|
470
|
+
const tx = ((devX - orgX) % tile.width + tile.width) % tile.width;
|
|
471
|
+
const ty = ((devY - orgY) % tile.height + tile.height) % tile.height;
|
|
472
|
+
return tile.rgb[ty * tile.width + tx];
|
|
473
|
+
}
|
|
474
|
+
|
|
53
475
|
// src/emf-gdi-text-layout.ts
|
|
54
476
|
function cumulativeGlyphOffsets(dx) {
|
|
55
477
|
const offsets = [];
|
|
@@ -235,6 +657,7 @@ var EMFPLUS_OBJECTTYPE_STRINGFORMAT = 7;
|
|
|
235
657
|
var EMFPLUS_OBJECTTYPE_REGION = 8;
|
|
236
658
|
var EMFPLUS_BRUSHTYPE_SOLID = 0;
|
|
237
659
|
var EMFPLUS_BRUSHTYPE_HATCHFILL = 1;
|
|
660
|
+
var EMFPLUS_BRUSHTYPE_TEXTUREFILL = 2;
|
|
238
661
|
var EMFPLUS_BRUSHTYPE_PATHGRADIENT = 3;
|
|
239
662
|
var EMFPLUS_BRUSHTYPE_LINEARGRADIENT = 4;
|
|
240
663
|
var META_EOF = 0;
|
|
@@ -485,7 +908,8 @@ function applyPen(ctx, state) {
|
|
|
485
908
|
function applyBrush(ctx, state) {
|
|
486
909
|
const paint = rop2Paint(state.rop2);
|
|
487
910
|
ctx.globalCompositeOperation = paint.gco;
|
|
488
|
-
|
|
911
|
+
const realized = realizeBrush(state);
|
|
912
|
+
if (realized.kind === "none") {
|
|
489
913
|
ctx.fillStyle = "rgba(0,0,0,0)";
|
|
490
914
|
return;
|
|
491
915
|
}
|
|
@@ -637,793 +1061,784 @@ async function exportCanvasToPngDataUrl(canvas) {
|
|
|
637
1061
|
emfLog(
|
|
638
1062
|
`exportCanvasToPngDataUrl: using HTMLCanvasElement.toDataURL (${canvas.width}\xD7${canvas.height})`
|
|
639
1063
|
);
|
|
640
|
-
return canvas.toDataURL("image/png");
|
|
641
|
-
}
|
|
642
|
-
if (nodeCanvasModule && canvas instanceof nodeCanvasModule.Canvas) {
|
|
643
|
-
emfLog(
|
|
644
|
-
`exportCanvasToPngDataUrl: using @napi-rs/canvas toDataURLAsync (${canvas.width}\xD7${canvas.height})`
|
|
645
|
-
);
|
|
646
|
-
return canvas.toDataURLAsync();
|
|
647
|
-
}
|
|
648
|
-
return null;
|
|
649
|
-
}
|
|
650
|
-
async function decodeDeferredImageBytes(bytes, mime) {
|
|
651
|
-
if (nodeCanvasModule) {
|
|
652
|
-
const image = await nodeCanvasModule.loadImage(new Uint8Array(bytes));
|
|
653
|
-
return { drawable: image, width: image.width, height: image.height, close: () => {
|
|
654
|
-
} };
|
|
655
|
-
}
|
|
656
|
-
if (typeof createImageBitmap !== "function") {
|
|
657
|
-
return null;
|
|
658
|
-
}
|
|
659
|
-
const blob = mime !== void 0 ? new Blob([bytes], { type: mime }) : new Blob([bytes]);
|
|
660
|
-
const bitmap = await createImageBitmap(blob);
|
|
661
|
-
return { drawable: bitmap, width: bitmap.width, height: bitmap.height, close: () => bitmap.close() };
|
|
662
|
-
}
|
|
663
|
-
function canvasDrawImage(ctx, image, dx, dy, dw, dh) {
|
|
664
|
-
const draw = ctx.drawImage;
|
|
665
|
-
draw.call(ctx, image, dx, dy, dw, dh);
|
|
666
|
-
}
|
|
667
|
-
function canvasGetImageData(ctx, x, y, w, h) {
|
|
668
|
-
const get = ctx.getImageData;
|
|
669
|
-
return get.call(ctx, x, y, w, h);
|
|
670
|
-
}
|
|
671
|
-
function canvasPutImageData(ctx, data, x, y) {
|
|
672
|
-
const put = ctx.putImageData;
|
|
673
|
-
put.call(ctx, data, x, y);
|
|
674
|
-
}
|
|
675
|
-
function canvasCreatePattern(ctx, image, repetition) {
|
|
676
|
-
const create = ctx.createPattern;
|
|
677
|
-
return create.call(ctx, image, repetition);
|
|
678
|
-
}
|
|
679
|
-
function createImageDataCompat(data, width, height) {
|
|
680
|
-
if (typeof ImageData !== "undefined") {
|
|
681
|
-
return new ImageData(data, width, height);
|
|
682
|
-
}
|
|
683
|
-
if (nodeCanvasModule) {
|
|
684
|
-
const Ctor = nodeCanvasModule.ImageData;
|
|
685
|
-
return new Ctor(data, width, height);
|
|
686
|
-
}
|
|
687
|
-
throw new ReferenceError("ImageData is not defined");
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
// src/emf-header-parser.ts
|
|
691
|
-
function parseEmfHeader(view) {
|
|
692
|
-
emfLog("parseEmfHeader: byteLength =", view.byteLength);
|
|
693
|
-
if (view.byteLength < 88) {
|
|
694
|
-
return null;
|
|
695
|
-
}
|
|
696
|
-
const recordType = view.getUint32(0, true);
|
|
697
|
-
if (recordType !== EMR_HEADER) {
|
|
698
|
-
return null;
|
|
699
|
-
}
|
|
700
|
-
const boundsLeft = view.getInt32(8, true);
|
|
701
|
-
const boundsTop = view.getInt32(12, true);
|
|
702
|
-
const boundsRight = view.getInt32(16, true);
|
|
703
|
-
const boundsBottom = view.getInt32(20, true);
|
|
704
|
-
const frameLeft = view.getInt32(24, true);
|
|
705
|
-
const frameTop = view.getInt32(28, true);
|
|
706
|
-
const frameRight = view.getInt32(32, true);
|
|
707
|
-
const frameBottom = view.getInt32(36, true);
|
|
708
|
-
const frameW = frameRight - frameLeft;
|
|
709
|
-
const frameH = frameBottom - frameTop;
|
|
710
|
-
return {
|
|
711
|
-
bounds: {
|
|
712
|
-
left: boundsLeft,
|
|
713
|
-
top: boundsTop,
|
|
714
|
-
right: boundsRight,
|
|
715
|
-
bottom: boundsBottom
|
|
716
|
-
},
|
|
717
|
-
frameW,
|
|
718
|
-
frameH
|
|
719
|
-
};
|
|
720
|
-
}
|
|
721
|
-
function getRenderableEmfBounds(header) {
|
|
722
|
-
const boundsW = header.bounds.right - header.bounds.left;
|
|
723
|
-
const boundsH = header.bounds.bottom - header.bounds.top;
|
|
724
|
-
if (boundsW > 0 && boundsH > 0) {
|
|
725
|
-
return header.bounds;
|
|
726
|
-
}
|
|
727
|
-
if (header.frameW > 0 && header.frameH > 0) {
|
|
728
|
-
emfLog(
|
|
729
|
-
`getRenderableEmfBounds: bounds invalid (${boundsW}\xD7${boundsH}), falling back to frame ${header.frameW}\xD7${header.frameH}`
|
|
730
|
-
);
|
|
731
|
-
return { left: 0, top: 0, right: header.frameW, bottom: header.frameH };
|
|
732
|
-
}
|
|
733
|
-
return null;
|
|
734
|
-
}
|
|
735
|
-
function parseWmfHeader(view) {
|
|
736
|
-
if (view.byteLength < 22) {
|
|
737
|
-
return null;
|
|
738
|
-
}
|
|
739
|
-
const magic = view.getUint32(0, true);
|
|
740
|
-
let headerOffset = 0;
|
|
741
|
-
let boundsLeft = 0;
|
|
742
|
-
let boundsTop = 0;
|
|
743
|
-
let boundsRight = 800;
|
|
744
|
-
let boundsBottom = 600;
|
|
745
|
-
let unitsPerInch = 96;
|
|
746
|
-
if (magic === 2596720087) {
|
|
747
|
-
boundsLeft = view.getInt16(6, true);
|
|
748
|
-
boundsTop = view.getInt16(8, true);
|
|
749
|
-
boundsRight = view.getInt16(10, true);
|
|
750
|
-
boundsBottom = view.getInt16(12, true);
|
|
751
|
-
unitsPerInch = view.getUint16(14, true) || 96;
|
|
752
|
-
headerOffset = 22;
|
|
753
|
-
}
|
|
754
|
-
if (headerOffset + 18 > view.byteLength) {
|
|
755
|
-
return null;
|
|
756
|
-
}
|
|
757
|
-
const fileType = view.getUint16(headerOffset, true);
|
|
758
|
-
if (fileType !== 1 && fileType !== 2) {
|
|
759
|
-
return null;
|
|
760
|
-
}
|
|
761
|
-
const headerSize = view.getUint16(headerOffset + 2, true) * 2;
|
|
762
|
-
const maxRecordSize = view.getUint32(headerOffset + 8, true) * 2;
|
|
763
|
-
return {
|
|
764
|
-
headerSize: headerOffset + headerSize,
|
|
765
|
-
maxRecordSize,
|
|
766
|
-
boundsLeft,
|
|
767
|
-
boundsTop,
|
|
768
|
-
boundsRight,
|
|
769
|
-
boundsBottom,
|
|
770
|
-
unitsPerInch
|
|
771
|
-
};
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
// src/emf-gdi-coord.ts
|
|
775
|
-
function gmx(r, x) {
|
|
776
|
-
const wt = r.state.worldTransform;
|
|
777
|
-
const px = wt[0] * x + wt[4];
|
|
778
|
-
if (r.useMappingMode) {
|
|
779
|
-
return (px - r.windowOrg.x) / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1) + r.viewportOrg.x;
|
|
780
|
-
}
|
|
781
|
-
return (px - r.bounds.left) * r.sx;
|
|
782
|
-
}
|
|
783
|
-
function gmy(r, y) {
|
|
784
|
-
const wt = r.state.worldTransform;
|
|
785
|
-
const py = wt[3] * y + wt[5];
|
|
786
|
-
if (r.useMappingMode) {
|
|
787
|
-
return (py - r.windowOrg.y) / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1) + r.viewportOrg.y;
|
|
788
|
-
}
|
|
789
|
-
return (py - r.bounds.top) * r.sy;
|
|
790
|
-
}
|
|
791
|
-
function gmw(r, w) {
|
|
792
|
-
const pw = r.state.worldTransform[0] * w;
|
|
793
|
-
if (r.useMappingMode) {
|
|
794
|
-
return pw / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1);
|
|
795
|
-
}
|
|
796
|
-
return pw * r.sx;
|
|
797
|
-
}
|
|
798
|
-
function gmh(r, h) {
|
|
799
|
-
const ph = r.state.worldTransform[3] * h;
|
|
800
|
-
if (r.useMappingMode) {
|
|
801
|
-
return ph / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1);
|
|
802
|
-
}
|
|
803
|
-
return ph * r.sy;
|
|
804
|
-
}
|
|
805
|
-
function activateGdiMappingMode(r) {
|
|
806
|
-
r.useMappingMode = true;
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
// src/emf-gdi-draw-shapes.ts
|
|
810
|
-
function handleSetPixelV(rCtx, dataOff, recSize) {
|
|
811
|
-
const { ctx, view } = rCtx;
|
|
812
|
-
if (recSize >= 20) {
|
|
813
|
-
const x = view.getInt32(dataOff, true);
|
|
814
|
-
const y = view.getInt32(dataOff + 4, true);
|
|
815
|
-
const color = readColorRef(view, dataOff + 8);
|
|
816
|
-
ctx.fillStyle = color;
|
|
817
|
-
ctx.fillRect(gmx(rCtx, x), gmy(rCtx, y), 1, 1);
|
|
818
|
-
}
|
|
819
|
-
return true;
|
|
820
|
-
}
|
|
821
|
-
function handleMoveToEx(rCtx, dataOff, recSize) {
|
|
822
|
-
const { ctx, view, state, inPath } = rCtx;
|
|
823
|
-
if (recSize >= 16) {
|
|
824
|
-
state.curX = view.getInt32(dataOff, true);
|
|
825
|
-
state.curY = view.getInt32(dataOff + 4, true);
|
|
826
|
-
if (inPath) {
|
|
827
|
-
ctx.moveTo(gmx(rCtx, state.curX), gmy(rCtx, state.curY));
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
return true;
|
|
831
|
-
}
|
|
832
|
-
function handleLineTo(rCtx, dataOff, recSize) {
|
|
833
|
-
const { ctx, view, state, inPath } = rCtx;
|
|
834
|
-
if (recSize >= 16) {
|
|
835
|
-
const lx = view.getInt32(dataOff, true);
|
|
836
|
-
const ly = view.getInt32(dataOff + 4, true);
|
|
837
|
-
if (inPath) {
|
|
838
|
-
ctx.lineTo(gmx(rCtx, lx), gmy(rCtx, ly));
|
|
839
|
-
} else {
|
|
840
|
-
applyPen(ctx, state);
|
|
841
|
-
ctx.beginPath();
|
|
842
|
-
ctx.moveTo(gmx(rCtx, state.curX), gmy(rCtx, state.curY));
|
|
843
|
-
ctx.lineTo(gmx(rCtx, lx), gmy(rCtx, ly));
|
|
844
|
-
ctx.stroke();
|
|
845
|
-
}
|
|
846
|
-
state.curX = lx;
|
|
847
|
-
state.curY = ly;
|
|
1064
|
+
return canvas.toDataURL("image/png");
|
|
848
1065
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
const l = view.getInt32(dataOff, true);
|
|
855
|
-
const t = view.getInt32(dataOff + 4, true);
|
|
856
|
-
const r = view.getInt32(dataOff + 8, true);
|
|
857
|
-
const b = view.getInt32(dataOff + 12, true);
|
|
858
|
-
if (inPath) {
|
|
859
|
-
ctx.rect(gmx(rCtx, l), gmy(rCtx, t), gmw(rCtx, r - l), gmh(rCtx, b - t));
|
|
860
|
-
} else {
|
|
861
|
-
applyBrush(ctx, state);
|
|
862
|
-
ctx.fillRect(gmx(rCtx, l), gmy(rCtx, t), gmw(rCtx, r - l), gmh(rCtx, b - t));
|
|
863
|
-
applyPen(ctx, state);
|
|
864
|
-
ctx.strokeRect(gmx(rCtx, l), gmy(rCtx, t), gmw(rCtx, r - l), gmh(rCtx, b - t));
|
|
865
|
-
}
|
|
1066
|
+
if (nodeCanvasModule && canvas instanceof nodeCanvasModule.Canvas) {
|
|
1067
|
+
emfLog(
|
|
1068
|
+
`exportCanvasToPngDataUrl: using @napi-rs/canvas toDataURLAsync (${canvas.width}\xD7${canvas.height})`
|
|
1069
|
+
);
|
|
1070
|
+
return canvas.toDataURLAsync();
|
|
866
1071
|
}
|
|
867
|
-
return
|
|
1072
|
+
return null;
|
|
868
1073
|
}
|
|
869
|
-
function
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
const r = view.getInt32(dataOff + 8, true);
|
|
875
|
-
const b = view.getInt32(dataOff + 12, true);
|
|
876
|
-
const rw = Math.abs(gmw(rCtx, view.getInt32(dataOff + 16, true))) / 2;
|
|
877
|
-
const rh = Math.abs(gmh(rCtx, view.getInt32(dataOff + 20, true))) / 2;
|
|
878
|
-
const x1 = gmx(rCtx, l);
|
|
879
|
-
const y1 = gmy(rCtx, t);
|
|
880
|
-
const w = gmw(rCtx, r - l);
|
|
881
|
-
const h = gmh(rCtx, b - t);
|
|
882
|
-
const drawRoundRect = () => {
|
|
883
|
-
const radius = Math.min(rw, rh, w / 2, h / 2);
|
|
884
|
-
ctx.moveTo(x1 + radius, y1);
|
|
885
|
-
ctx.lineTo(x1 + w - radius, y1);
|
|
886
|
-
ctx.arcTo(x1 + w, y1, x1 + w, y1 + radius, radius);
|
|
887
|
-
ctx.lineTo(x1 + w, y1 + h - radius);
|
|
888
|
-
ctx.arcTo(x1 + w, y1 + h, x1 + w - radius, y1 + h, radius);
|
|
889
|
-
ctx.lineTo(x1 + radius, y1 + h);
|
|
890
|
-
ctx.arcTo(x1, y1 + h, x1, y1 + h - radius, radius);
|
|
891
|
-
ctx.lineTo(x1, y1 + radius);
|
|
892
|
-
ctx.arcTo(x1, y1, x1 + radius, y1, radius);
|
|
893
|
-
ctx.closePath();
|
|
894
|
-
};
|
|
895
|
-
if (inPath) {
|
|
896
|
-
drawRoundRect();
|
|
897
|
-
} else {
|
|
898
|
-
ctx.beginPath();
|
|
899
|
-
drawRoundRect();
|
|
900
|
-
applyBrush(ctx, state);
|
|
901
|
-
ctx.fill();
|
|
902
|
-
applyPen(ctx, state);
|
|
903
|
-
ctx.stroke();
|
|
904
|
-
}
|
|
1074
|
+
async function decodeDeferredImageBytes(bytes, mime) {
|
|
1075
|
+
if (nodeCanvasModule) {
|
|
1076
|
+
const image = await nodeCanvasModule.loadImage(new Uint8Array(bytes));
|
|
1077
|
+
return { drawable: image, width: image.width, height: image.height, close: () => {
|
|
1078
|
+
} };
|
|
905
1079
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
function handleEllipse(rCtx, dataOff, recSize) {
|
|
909
|
-
const { ctx, view, state, inPath } = rCtx;
|
|
910
|
-
if (recSize >= 24) {
|
|
911
|
-
const l = view.getInt32(dataOff, true);
|
|
912
|
-
const t = view.getInt32(dataOff + 4, true);
|
|
913
|
-
const r = view.getInt32(dataOff + 8, true);
|
|
914
|
-
const b = view.getInt32(dataOff + 12, true);
|
|
915
|
-
const cx = gmx(rCtx, (l + r) / 2);
|
|
916
|
-
const cy = gmy(rCtx, (t + b) / 2);
|
|
917
|
-
const rx = Math.abs(gmw(rCtx, r - l)) / 2;
|
|
918
|
-
const ry = Math.abs(gmh(rCtx, b - t)) / 2;
|
|
919
|
-
if (inPath) {
|
|
920
|
-
ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
|
|
921
|
-
} else {
|
|
922
|
-
ctx.beginPath();
|
|
923
|
-
ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
|
|
924
|
-
applyBrush(ctx, state);
|
|
925
|
-
ctx.fill();
|
|
926
|
-
applyPen(ctx, state);
|
|
927
|
-
ctx.stroke();
|
|
928
|
-
}
|
|
1080
|
+
if (typeof createImageBitmap !== "function") {
|
|
1081
|
+
return null;
|
|
929
1082
|
}
|
|
930
|
-
|
|
1083
|
+
const blob = mime !== void 0 ? new Blob([bytes], { type: mime }) : new Blob([bytes]);
|
|
1084
|
+
const bitmap = await createImageBitmap(blob);
|
|
1085
|
+
return { drawable: bitmap, width: bitmap.width, height: bitmap.height, close: () => bitmap.close() };
|
|
931
1086
|
}
|
|
932
|
-
function
|
|
933
|
-
const
|
|
934
|
-
|
|
935
|
-
const l = view.getInt32(dataOff, true);
|
|
936
|
-
const t = view.getInt32(dataOff + 4, true);
|
|
937
|
-
const r = view.getInt32(dataOff + 8, true);
|
|
938
|
-
const b = view.getInt32(dataOff + 12, true);
|
|
939
|
-
const startX = view.getInt32(dataOff + 16, true);
|
|
940
|
-
const startY = view.getInt32(dataOff + 20, true);
|
|
941
|
-
const endX = view.getInt32(dataOff + 24, true);
|
|
942
|
-
const endY = view.getInt32(dataOff + 28, true);
|
|
943
|
-
const cxA = (l + r) / 2;
|
|
944
|
-
const cyA = (t + b) / 2;
|
|
945
|
-
const rx = Math.abs(r - l) / 2;
|
|
946
|
-
const ry = Math.abs(b - t) / 2;
|
|
947
|
-
const startAngle = Math.atan2((startY - cyA) / (ry || 1), (startX - cxA) / (rx || 1));
|
|
948
|
-
const endAngle = Math.atan2((endY - cyA) / (ry || 1), (endX - cxA) / (rx || 1));
|
|
949
|
-
const mcx = gmx(rCtx, cxA);
|
|
950
|
-
const mcy = gmy(rCtx, cyA);
|
|
951
|
-
const mrx = Math.abs(gmw(rCtx, rx));
|
|
952
|
-
const mry = Math.abs(gmh(rCtx, ry));
|
|
953
|
-
const isArcTo = recType === EMR_ARCTO;
|
|
954
|
-
const needsFill = recType === EMR_PIE || recType === EMR_CHORD;
|
|
955
|
-
if (!inPath) {
|
|
956
|
-
ctx.beginPath();
|
|
957
|
-
}
|
|
958
|
-
if (recType === EMR_PIE) {
|
|
959
|
-
ctx.moveTo(mcx, mcy);
|
|
960
|
-
}
|
|
961
|
-
if (isArcTo) {
|
|
962
|
-
ctx.lineTo(mcx + mrx * Math.cos(startAngle), mcy + mry * Math.sin(startAngle));
|
|
963
|
-
}
|
|
964
|
-
ctx.ellipse(mcx, mcy, mrx, mry, 0, startAngle, endAngle, false);
|
|
965
|
-
if (recType === EMR_PIE || recType === EMR_CHORD) {
|
|
966
|
-
ctx.closePath();
|
|
967
|
-
}
|
|
968
|
-
if (!inPath) {
|
|
969
|
-
if (needsFill) {
|
|
970
|
-
applyBrush(ctx, state);
|
|
971
|
-
ctx.fill();
|
|
972
|
-
}
|
|
973
|
-
applyPen(ctx, state);
|
|
974
|
-
ctx.stroke();
|
|
975
|
-
}
|
|
976
|
-
if (isArcTo) {
|
|
977
|
-
state.curX = endX;
|
|
978
|
-
state.curY = endY;
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
return true;
|
|
1087
|
+
function canvasDrawImage(ctx, image, dx, dy, dw, dh) {
|
|
1088
|
+
const draw = ctx.drawImage;
|
|
1089
|
+
draw.call(ctx, image, dx, dy, dw, dh);
|
|
982
1090
|
}
|
|
983
|
-
function
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
default:
|
|
1003
|
-
return false;
|
|
1091
|
+
function canvasGetImageData(ctx, x, y, w, h) {
|
|
1092
|
+
const get = ctx.getImageData;
|
|
1093
|
+
return get.call(ctx, x, y, w, h);
|
|
1094
|
+
}
|
|
1095
|
+
function canvasPutImageData(ctx, data, x, y) {
|
|
1096
|
+
const put = ctx.putImageData;
|
|
1097
|
+
put.call(ctx, data, x, y);
|
|
1098
|
+
}
|
|
1099
|
+
function canvasCreatePattern(ctx, image, repetition) {
|
|
1100
|
+
const create = ctx.createPattern;
|
|
1101
|
+
return create.call(ctx, image, repetition);
|
|
1102
|
+
}
|
|
1103
|
+
function createImageDataCompat(data, width, height) {
|
|
1104
|
+
if (typeof ImageData !== "undefined") {
|
|
1105
|
+
return new ImageData(data, width, height);
|
|
1106
|
+
}
|
|
1107
|
+
if (nodeCanvasModule) {
|
|
1108
|
+
const Ctor = nodeCanvasModule.ImageData;
|
|
1109
|
+
return new Ctor(data, width, height);
|
|
1004
1110
|
}
|
|
1111
|
+
throw new ReferenceError("ImageData is not defined");
|
|
1005
1112
|
}
|
|
1006
1113
|
|
|
1007
|
-
// src/emf-
|
|
1008
|
-
function
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
);
|
|
1044
|
-
}
|
|
1045
|
-
x++;
|
|
1046
|
-
}
|
|
1047
|
-
if (count & 1) {
|
|
1048
|
-
off++;
|
|
1049
|
-
}
|
|
1050
|
-
} else {
|
|
1051
|
-
const bytes = Math.ceil(count / 2);
|
|
1052
|
-
let pi = 0;
|
|
1053
|
-
for (let i = 0; i < bytes && off < endOff; i++) {
|
|
1054
|
-
const byte = view.getUint8(off++);
|
|
1055
|
-
for (let nibble = 0; nibble < 2 && pi < count; nibble++) {
|
|
1056
|
-
const idx = nibble === 0 ? byte >> 4 & 15 : byte & 15;
|
|
1057
|
-
if (idx < colorTable.length && x < width) {
|
|
1058
|
-
setPixel(
|
|
1059
|
-
x,
|
|
1060
|
-
height - 1 - y,
|
|
1061
|
-
colorTable[idx][0],
|
|
1062
|
-
colorTable[idx][1],
|
|
1063
|
-
colorTable[idx][2],
|
|
1064
|
-
255
|
|
1065
|
-
);
|
|
1066
|
-
}
|
|
1067
|
-
x++;
|
|
1068
|
-
pi++;
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
if (bytes & 1) {
|
|
1072
|
-
off++;
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
} else if (!isRle4) {
|
|
1077
|
-
const idx = second;
|
|
1078
|
-
const c = idx < colorTable.length ? colorTable[idx] : [0, 0, 0];
|
|
1079
|
-
for (let i = 0; i < first && x < width; i++) {
|
|
1080
|
-
setPixel(x, height - 1 - y, c[0], c[1], c[2], 255);
|
|
1081
|
-
x++;
|
|
1082
|
-
}
|
|
1083
|
-
} else {
|
|
1084
|
-
const hi = second >> 4 & 15;
|
|
1085
|
-
const lo = second & 15;
|
|
1086
|
-
for (let i = 0; i < first && x < width; i++) {
|
|
1087
|
-
const idx = (i & 1) === 0 ? hi : lo;
|
|
1088
|
-
if (idx < colorTable.length) {
|
|
1089
|
-
setPixel(
|
|
1090
|
-
x,
|
|
1091
|
-
height - 1 - y,
|
|
1092
|
-
colorTable[idx][0],
|
|
1093
|
-
colorTable[idx][1],
|
|
1094
|
-
colorTable[idx][2],
|
|
1095
|
-
255
|
|
1096
|
-
);
|
|
1097
|
-
}
|
|
1098
|
-
x++;
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1114
|
+
// src/emf-header-parser.ts
|
|
1115
|
+
function parseEmfHeader(view) {
|
|
1116
|
+
emfLog("parseEmfHeader: byteLength =", view.byteLength);
|
|
1117
|
+
if (view.byteLength < 88) {
|
|
1118
|
+
return null;
|
|
1119
|
+
}
|
|
1120
|
+
const recordType = view.getUint32(0, true);
|
|
1121
|
+
if (recordType !== EMR_HEADER) {
|
|
1122
|
+
return null;
|
|
1123
|
+
}
|
|
1124
|
+
const boundsLeft = view.getInt32(8, true);
|
|
1125
|
+
const boundsTop = view.getInt32(12, true);
|
|
1126
|
+
const boundsRight = view.getInt32(16, true);
|
|
1127
|
+
const boundsBottom = view.getInt32(20, true);
|
|
1128
|
+
const frameLeft = view.getInt32(24, true);
|
|
1129
|
+
const frameTop = view.getInt32(28, true);
|
|
1130
|
+
const frameRight = view.getInt32(32, true);
|
|
1131
|
+
const frameBottom = view.getInt32(36, true);
|
|
1132
|
+
const frameW = frameRight - frameLeft;
|
|
1133
|
+
const frameH = frameBottom - frameTop;
|
|
1134
|
+
return {
|
|
1135
|
+
bounds: {
|
|
1136
|
+
left: boundsLeft,
|
|
1137
|
+
top: boundsTop,
|
|
1138
|
+
right: boundsRight,
|
|
1139
|
+
bottom: boundsBottom
|
|
1140
|
+
},
|
|
1141
|
+
frameW,
|
|
1142
|
+
frameH
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
function getRenderableEmfBounds(header) {
|
|
1146
|
+
const boundsW = header.bounds.right - header.bounds.left;
|
|
1147
|
+
const boundsH = header.bounds.bottom - header.bounds.top;
|
|
1148
|
+
if (boundsW > 0 && boundsH > 0) {
|
|
1149
|
+
return header.bounds;
|
|
1101
1150
|
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1151
|
+
if (header.frameW > 0 && header.frameH > 0) {
|
|
1152
|
+
emfLog(
|
|
1153
|
+
`getRenderableEmfBounds: bounds invalid (${boundsW}\xD7${boundsH}), falling back to frame ${header.frameW}\xD7${header.frameH}`
|
|
1154
|
+
);
|
|
1155
|
+
return { left: 0, top: 0, right: header.frameW, bottom: header.frameH };
|
|
1156
|
+
}
|
|
1157
|
+
return null;
|
|
1158
|
+
}
|
|
1159
|
+
function parseWmfHeader(view) {
|
|
1160
|
+
if (view.byteLength < 22) {
|
|
1161
|
+
return null;
|
|
1162
|
+
}
|
|
1163
|
+
const magic = view.getUint32(0, true);
|
|
1164
|
+
let headerOffset = 0;
|
|
1165
|
+
let boundsLeft = 0;
|
|
1166
|
+
let boundsTop = 0;
|
|
1167
|
+
let boundsRight = 800;
|
|
1168
|
+
let boundsBottom = 600;
|
|
1169
|
+
let unitsPerInch = 96;
|
|
1170
|
+
if (magic === 2596720087) {
|
|
1171
|
+
boundsLeft = view.getInt16(6, true);
|
|
1172
|
+
boundsTop = view.getInt16(8, true);
|
|
1173
|
+
boundsRight = view.getInt16(10, true);
|
|
1174
|
+
boundsBottom = view.getInt16(12, true);
|
|
1175
|
+
unitsPerInch = view.getUint16(14, true) || 96;
|
|
1176
|
+
headerOffset = 22;
|
|
1177
|
+
}
|
|
1178
|
+
if (headerOffset + 18 > view.byteLength) {
|
|
1179
|
+
return null;
|
|
1180
|
+
}
|
|
1181
|
+
const fileType = view.getUint16(headerOffset, true);
|
|
1182
|
+
if (fileType !== 1 && fileType !== 2) {
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1185
|
+
const headerSize = view.getUint16(headerOffset + 2, true) * 2;
|
|
1186
|
+
const maxRecordSize = view.getUint32(headerOffset + 8, true) * 2;
|
|
1187
|
+
return {
|
|
1188
|
+
headerSize: headerOffset + headerSize,
|
|
1189
|
+
maxRecordSize,
|
|
1190
|
+
boundsLeft,
|
|
1191
|
+
boundsTop,
|
|
1192
|
+
boundsRight,
|
|
1193
|
+
boundsBottom,
|
|
1194
|
+
unitsPerInch
|
|
1195
|
+
};
|
|
1107
1196
|
}
|
|
1108
1197
|
|
|
1109
|
-
// src/emf-
|
|
1110
|
-
function
|
|
1111
|
-
|
|
1112
|
-
|
|
1198
|
+
// src/emf-gdi-coord.ts
|
|
1199
|
+
function gmx(r, x) {
|
|
1200
|
+
const wt = r.state.worldTransform;
|
|
1201
|
+
const px = wt[0] * x + wt[4];
|
|
1202
|
+
if (r.useMappingMode) {
|
|
1203
|
+
return (px - r.windowOrg.x) / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1) + r.viewportOrg.x;
|
|
1113
1204
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1205
|
+
return (px - r.bounds.left) * r.sx;
|
|
1206
|
+
}
|
|
1207
|
+
function gmy(r, y) {
|
|
1208
|
+
const wt = r.state.worldTransform;
|
|
1209
|
+
const py = wt[3] * y + wt[5];
|
|
1210
|
+
if (r.useMappingMode) {
|
|
1211
|
+
return (py - r.windowOrg.y) / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1) + r.viewportOrg.y;
|
|
1119
1212
|
}
|
|
1120
|
-
return
|
|
1213
|
+
return (py - r.bounds.top) * r.sy;
|
|
1121
1214
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
let rMax = 1, gMax = 1, bMax = 1;
|
|
1127
|
-
if (compression === BI_BITFIELDS) {
|
|
1128
|
-
const bfOff = bmiOffset + headerSize;
|
|
1129
|
-
if (bfOff + 12 > view.byteLength) {
|
|
1130
|
-
return null;
|
|
1131
|
-
}
|
|
1132
|
-
rMask = view.getUint32(bfOff, true);
|
|
1133
|
-
gMask = view.getUint32(bfOff + 4, true);
|
|
1134
|
-
bMask = view.getUint32(bfOff + 8, true);
|
|
1135
|
-
rShift = countTrailingZeros(rMask);
|
|
1136
|
-
gShift = countTrailingZeros(gMask);
|
|
1137
|
-
bShift = countTrailingZeros(bMask);
|
|
1138
|
-
rMax = rMask >>> rShift || 1;
|
|
1139
|
-
gMax = gMask >>> gShift || 1;
|
|
1140
|
-
bMax = bMask >>> bShift || 1;
|
|
1141
|
-
} else if (bitCount === 16) {
|
|
1142
|
-
rMask = 31744;
|
|
1143
|
-
gMask = 992;
|
|
1144
|
-
bMask = 31;
|
|
1145
|
-
rShift = 10;
|
|
1146
|
-
gShift = 5;
|
|
1147
|
-
bShift = 0;
|
|
1148
|
-
rMax = 31;
|
|
1149
|
-
gMax = 31;
|
|
1150
|
-
bMax = 31;
|
|
1215
|
+
function gmw(r, w) {
|
|
1216
|
+
const pw = r.state.worldTransform[0] * w;
|
|
1217
|
+
if (r.useMappingMode) {
|
|
1218
|
+
return pw / (r.windowExt.cx || 1) * (r.viewportExt.cx || 1);
|
|
1151
1219
|
}
|
|
1152
|
-
return
|
|
1220
|
+
return pw * r.sx;
|
|
1153
1221
|
}
|
|
1154
|
-
function
|
|
1155
|
-
const
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1222
|
+
function gmh(r, h) {
|
|
1223
|
+
const ph = r.state.worldTransform[3] * h;
|
|
1224
|
+
if (r.useMappingMode) {
|
|
1225
|
+
return ph / (r.windowExt.cy || 1) * (r.viewportExt.cy || 1);
|
|
1226
|
+
}
|
|
1227
|
+
return ph * r.sy;
|
|
1228
|
+
}
|
|
1229
|
+
function activateGdiMappingMode(r) {
|
|
1230
|
+
r.useMappingMode = true;
|
|
1231
|
+
}
|
|
1232
|
+
function hasWorldRotation(r) {
|
|
1233
|
+
const wt = r.state.worldTransform;
|
|
1234
|
+
return wt[1] !== 0 || wt[2] !== 0;
|
|
1235
|
+
}
|
|
1236
|
+
function gdiDeviceMatrix(r) {
|
|
1237
|
+
const wt = r.state.worldTransform;
|
|
1238
|
+
if (r.useMappingMode) {
|
|
1239
|
+
const kx = (r.viewportExt.cx || 1) / (r.windowExt.cx || 1);
|
|
1240
|
+
const ky = (r.viewportExt.cy || 1) / (r.windowExt.cy || 1);
|
|
1241
|
+
return [
|
|
1242
|
+
wt[0] * kx,
|
|
1243
|
+
wt[1] * ky,
|
|
1244
|
+
wt[2] * kx,
|
|
1245
|
+
wt[3] * ky,
|
|
1246
|
+
(wt[4] - r.windowOrg.x) * kx + r.viewportOrg.x,
|
|
1247
|
+
(wt[5] - r.windowOrg.y) * ky + r.viewportOrg.y
|
|
1248
|
+
];
|
|
1249
|
+
}
|
|
1250
|
+
const sx = r.sx || 1;
|
|
1251
|
+
const sy = r.sy || 1;
|
|
1252
|
+
return [wt[0] * sx, wt[1] * sy, wt[2] * sx, wt[3] * sy, (wt[4] - r.bounds.left) * sx, (wt[5] - r.bounds.top) * sy];
|
|
1253
|
+
}
|
|
1254
|
+
function gmapPoint(r, x, y) {
|
|
1255
|
+
const m = gdiDeviceMatrix(r);
|
|
1256
|
+
return { x: m[0] * x + m[2] * y + m[4], y: m[1] * x + m[3] * y + m[5] };
|
|
1257
|
+
}
|
|
1258
|
+
function gdiEllipseParams(r, cx0, cy0, rx0, ry0) {
|
|
1259
|
+
const m = gdiDeviceMatrix(r);
|
|
1260
|
+
const center = gmapPoint(r, cx0, cy0);
|
|
1261
|
+
const a11 = m[0] * rx0;
|
|
1262
|
+
const a12 = m[2] * ry0;
|
|
1263
|
+
const a21 = m[1] * rx0;
|
|
1264
|
+
const a22 = m[3] * ry0;
|
|
1265
|
+
const p = a11 * a11 + a12 * a12;
|
|
1266
|
+
const q = a11 * a21 + a12 * a22;
|
|
1267
|
+
const s = a21 * a21 + a22 * a22;
|
|
1268
|
+
const mid = (p + s) / 2;
|
|
1269
|
+
const spread = Math.sqrt(Math.max(0, ((p - s) / 2) ** 2 + q * q));
|
|
1270
|
+
const lambda1 = Math.max(0, mid + spread);
|
|
1271
|
+
const lambda2 = Math.max(0, mid - spread);
|
|
1272
|
+
const rotation = 0.5 * Math.atan2(2 * q, p - s);
|
|
1273
|
+
return {
|
|
1274
|
+
cx: center.x,
|
|
1275
|
+
cy: center.y,
|
|
1276
|
+
rx: Math.sqrt(lambda1),
|
|
1277
|
+
ry: Math.sqrt(lambda2),
|
|
1278
|
+
rotation
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// src/emf-rop3.ts
|
|
1283
|
+
function rop3Index(rop) {
|
|
1284
|
+
return rop >>> 16 & 255;
|
|
1285
|
+
}
|
|
1286
|
+
function rop3Operands(index) {
|
|
1287
|
+
const t = index & 255;
|
|
1288
|
+
return {
|
|
1289
|
+
usesP: (t >> 4 & 15) !== (t & 15),
|
|
1290
|
+
usesS: (t >> 2 & 51) !== (t & 51),
|
|
1291
|
+
usesD: (t >> 1 & 85) !== (t & 85)
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
function evalRop3(index, p, s, d, mask = 16777215) {
|
|
1295
|
+
let out = 0;
|
|
1296
|
+
const np = ~p;
|
|
1297
|
+
const ns = ~s;
|
|
1298
|
+
const nd = ~d;
|
|
1299
|
+
for (let i = 0; i < 8; i++) {
|
|
1300
|
+
if (index & 1 << i) {
|
|
1301
|
+
out |= (i & 4 ? p : np) & (i & 2 ? s : ns) & (i & 1 ? d : nd);
|
|
1162
1302
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
out[dstPx + 2] = view.getUint8(srcPx);
|
|
1202
|
-
out[dstPx + 3] = 255;
|
|
1203
|
-
} else {
|
|
1204
|
-
const srcPx = rowStart + x * 4;
|
|
1205
|
-
const bb = view.getUint8(srcPx);
|
|
1206
|
-
const gg = view.getUint8(srcPx + 1);
|
|
1207
|
-
const rr = view.getUint8(srcPx + 2);
|
|
1208
|
-
const aa = view.getUint8(srcPx + 3);
|
|
1209
|
-
out[dstPx] = rr;
|
|
1210
|
-
out[dstPx + 1] = gg;
|
|
1211
|
-
out[dstPx + 2] = bb;
|
|
1212
|
-
out[dstPx + 3] = aa === 0 ? 255 : aa;
|
|
1213
|
-
}
|
|
1303
|
+
}
|
|
1304
|
+
return out & mask;
|
|
1305
|
+
}
|
|
1306
|
+
function classifyRop3(rop) {
|
|
1307
|
+
const index = rop3Index(rop);
|
|
1308
|
+
switch (index) {
|
|
1309
|
+
case 204:
|
|
1310
|
+
return { kind: "copy" };
|
|
1311
|
+
case 0:
|
|
1312
|
+
return { kind: "solid", color: "black" };
|
|
1313
|
+
case 255:
|
|
1314
|
+
return { kind: "solid", color: "white" };
|
|
1315
|
+
case 170:
|
|
1316
|
+
return { kind: "noop" };
|
|
1317
|
+
case 85:
|
|
1318
|
+
return { kind: "invert-dest" };
|
|
1319
|
+
default:
|
|
1320
|
+
return { kind: "ternary", index, operands: rop3Operands(index) };
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
function applyRop3(dst, src, pattern, index, originX, originY) {
|
|
1324
|
+
const d = dst.data;
|
|
1325
|
+
const s = src ? src.data : null;
|
|
1326
|
+
const w = dst.width;
|
|
1327
|
+
const h = dst.height;
|
|
1328
|
+
const solidP = typeof pattern === "number" ? pattern : 0;
|
|
1329
|
+
const sampleP = typeof pattern === "function" ? pattern : null;
|
|
1330
|
+
for (let y = 0; y < h; y++) {
|
|
1331
|
+
for (let x = 0; x < w; x++) {
|
|
1332
|
+
const i = (y * w + x) * 4;
|
|
1333
|
+
const dv = d[i] << 16 | d[i + 1] << 8 | d[i + 2];
|
|
1334
|
+
const sv = s ? s[i] << 16 | s[i + 1] << 8 | s[i + 2] : 0;
|
|
1335
|
+
const pv = sampleP ? sampleP(originX + x, originY + y) : solidP;
|
|
1336
|
+
const r = evalRop3(index, pv, sv, dv);
|
|
1337
|
+
d[i] = r >> 16 & 255;
|
|
1338
|
+
d[i + 1] = r >> 8 & 255;
|
|
1339
|
+
d[i + 2] = r & 255;
|
|
1340
|
+
d[i + 3] = 255;
|
|
1214
1341
|
}
|
|
1215
1342
|
}
|
|
1216
1343
|
}
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1344
|
+
function clampPositiveRect(x, y, w, h, canvasW, canvasH) {
|
|
1345
|
+
let left = w < 0 ? x + w : x;
|
|
1346
|
+
let top = h < 0 ? y + h : y;
|
|
1347
|
+
let width = Math.abs(w);
|
|
1348
|
+
let height = Math.abs(h);
|
|
1349
|
+
left = Math.round(left);
|
|
1350
|
+
top = Math.round(top);
|
|
1351
|
+
width = Math.round(width);
|
|
1352
|
+
height = Math.round(height);
|
|
1353
|
+
const right = Math.min(left + width, Math.round(canvasW));
|
|
1354
|
+
const bottom = Math.min(top + height, Math.round(canvasH));
|
|
1355
|
+
left = Math.max(left, 0);
|
|
1356
|
+
top = Math.max(top, 0);
|
|
1357
|
+
width = right - left;
|
|
1358
|
+
height = bottom - top;
|
|
1359
|
+
if (width <= 0 || height <= 0) {
|
|
1225
1360
|
return null;
|
|
1226
1361
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1362
|
+
return { x: left, y: top, w: width, h: height };
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
// src/emf-rop2-exact.ts
|
|
1366
|
+
var ROP2_EXACT_INDEX = {
|
|
1367
|
+
[R2_MASKPEN]: 160,
|
|
1368
|
+
// P & D
|
|
1369
|
+
[R2_MERGEPEN]: 250,
|
|
1370
|
+
// P | D
|
|
1371
|
+
[R2_XORPEN]: 90,
|
|
1372
|
+
// P ^ D
|
|
1373
|
+
[R2_NOTXORPEN]: 165,
|
|
1374
|
+
// ~(P ^ D)
|
|
1375
|
+
[R2_MASKPENNOT]: 80,
|
|
1376
|
+
// P & ~D
|
|
1377
|
+
[R2_MERGEPENNOT]: 245,
|
|
1378
|
+
// P | ~D
|
|
1379
|
+
[R2_MASKNOTPEN]: 10,
|
|
1380
|
+
// ~P & D
|
|
1381
|
+
[R2_MERGENOTPEN]: 175,
|
|
1382
|
+
// ~P | D
|
|
1383
|
+
[R2_NOTMASKPEN]: 95,
|
|
1384
|
+
// ~(P & D)
|
|
1385
|
+
[R2_NOTMERGEPEN]: 5
|
|
1386
|
+
// ~(P | D)
|
|
1387
|
+
};
|
|
1388
|
+
function isExactRop2Bitwise(rop2) {
|
|
1389
|
+
return rop2 in ROP2_EXACT_INDEX;
|
|
1390
|
+
}
|
|
1391
|
+
function packRgb(r, g, b) {
|
|
1392
|
+
return r << 16 | g << 8 | b;
|
|
1393
|
+
}
|
|
1394
|
+
function surfaceSize(ctx) {
|
|
1395
|
+
const canvas = ctx.canvas;
|
|
1396
|
+
const w = canvas?.width;
|
|
1397
|
+
const h = canvas?.height;
|
|
1398
|
+
return typeof w === "number" && typeof h === "number" && w > 0 && h > 0 ? { w, h } : null;
|
|
1399
|
+
}
|
|
1400
|
+
function paintWithExactRop2(ctx, rop2, color, draw) {
|
|
1401
|
+
const index = ROP2_EXACT_INDEX[rop2];
|
|
1402
|
+
if (index === void 0) {
|
|
1403
|
+
return false;
|
|
1234
1404
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1405
|
+
const size = surfaceSize(ctx);
|
|
1406
|
+
if (!size) {
|
|
1407
|
+
return false;
|
|
1237
1408
|
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1409
|
+
try {
|
|
1410
|
+
const temp = createTempCanvas(size.w, size.h);
|
|
1411
|
+
if (!temp) {
|
|
1412
|
+
return false;
|
|
1413
|
+
}
|
|
1414
|
+
temp.ctx.fillStyle = color;
|
|
1415
|
+
temp.ctx.strokeStyle = color;
|
|
1416
|
+
draw(temp.ctx);
|
|
1417
|
+
const paint = canvasGetImageData(temp.ctx, 0, 0, size.w, size.h);
|
|
1418
|
+
const dest = canvasGetImageData(ctx, 0, 0, size.w, size.h);
|
|
1419
|
+
const pd = paint.data;
|
|
1420
|
+
const dd = dest.data;
|
|
1421
|
+
for (let i = 0; i < dd.length; i += 4) {
|
|
1422
|
+
const coverage = pd[i + 3] / 255;
|
|
1423
|
+
if (coverage <= 0) {
|
|
1424
|
+
continue;
|
|
1425
|
+
}
|
|
1426
|
+
const p = packRgb(pd[i], pd[i + 1], pd[i + 2]);
|
|
1427
|
+
const d = packRgb(dd[i], dd[i + 1], dd[i + 2]);
|
|
1428
|
+
const combined = evalRop3(index, p, d, d);
|
|
1429
|
+
const cr = combined >> 16 & 255;
|
|
1430
|
+
const cg = combined >> 8 & 255;
|
|
1431
|
+
const cb = combined & 255;
|
|
1432
|
+
dd[i] = Math.round(dd[i] + (cr - dd[i]) * coverage);
|
|
1433
|
+
dd[i + 1] = Math.round(dd[i + 1] + (cg - dd[i + 1]) * coverage);
|
|
1434
|
+
dd[i + 2] = Math.round(dd[i + 2] + (cb - dd[i + 2]) * coverage);
|
|
1435
|
+
dd[i + 3] = 255;
|
|
1436
|
+
}
|
|
1437
|
+
canvasPutImageData(ctx, dest, 0, 0);
|
|
1438
|
+
return true;
|
|
1439
|
+
} catch {
|
|
1440
|
+
return false;
|
|
1244
1441
|
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
// src/emf-gdi-shape-paint.ts
|
|
1445
|
+
function surfaceSize2(ctx) {
|
|
1446
|
+
const canvas = ctx.canvas;
|
|
1447
|
+
const w = canvas?.width;
|
|
1448
|
+
const h = canvas?.height;
|
|
1449
|
+
return typeof w === "number" && typeof h === "number" && w > 0 && h > 0 ? { w, h } : null;
|
|
1450
|
+
}
|
|
1451
|
+
function fillCurrentPathWithGdiPattern(rCtx, fillRule) {
|
|
1452
|
+
const { ctx, bounds, state } = rCtx;
|
|
1453
|
+
const realized = realizeBrush(state);
|
|
1454
|
+
if (realized.kind !== "tile") {
|
|
1455
|
+
return false;
|
|
1247
1456
|
}
|
|
1248
|
-
|
|
1249
|
-
|
|
1457
|
+
const size = surfaceSize2(ctx);
|
|
1458
|
+
if (!size || typeof ctx.isPointInPath !== "function") {
|
|
1459
|
+
return false;
|
|
1250
1460
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1461
|
+
const sx = rCtx.sx || 1;
|
|
1462
|
+
const sy = rCtx.sy || 1;
|
|
1463
|
+
try {
|
|
1464
|
+
const img = canvasGetImageData(ctx, 0, 0, size.w, size.h);
|
|
1465
|
+
const d = img.data;
|
|
1466
|
+
for (let y = 0; y < size.h; y++) {
|
|
1467
|
+
for (let x = 0; x < size.w; x++) {
|
|
1468
|
+
if (!ctx.isPointInPath(x + 0.5, y + 0.5, fillRule)) {
|
|
1469
|
+
continue;
|
|
1470
|
+
}
|
|
1471
|
+
const lx = Math.floor(bounds.left + (x + 0.5) / sx);
|
|
1472
|
+
const ly = Math.floor(bounds.top + (y + 0.5) / sy);
|
|
1473
|
+
const c = sampleTile(realized, lx, ly, state.brushOrgX, state.brushOrgY);
|
|
1474
|
+
const o = (y * size.w + x) * 4;
|
|
1475
|
+
d[o] = c >>> 16 & 255;
|
|
1476
|
+
d[o + 1] = c >>> 8 & 255;
|
|
1477
|
+
d[o + 2] = c & 255;
|
|
1478
|
+
d[o + 3] = 255;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
canvasPutImageData(ctx, img, 0, 0);
|
|
1482
|
+
return true;
|
|
1483
|
+
} catch {
|
|
1484
|
+
return false;
|
|
1253
1485
|
}
|
|
1254
|
-
|
|
1255
|
-
|
|
1486
|
+
}
|
|
1487
|
+
function fillShapeExactOrFast(rCtx, buildPath, fillRule = "nonzero") {
|
|
1488
|
+
const { ctx, state } = rCtx;
|
|
1489
|
+
if (state.brushStyle === 1) {
|
|
1490
|
+
return;
|
|
1256
1491
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
if (bitCount <= 8) {
|
|
1261
|
-
const maxColors = 1 << bitCount;
|
|
1262
|
-
const colorsUsed = view.getUint32(bmiOffset + 32, true) || maxColors;
|
|
1263
|
-
const numColors = Math.min(colorsUsed, maxColors);
|
|
1264
|
-
const ctOffset = bmiOffset + headerSize;
|
|
1265
|
-
if (ctOffset + numColors * 4 > view.byteLength) {
|
|
1266
|
-
return null;
|
|
1492
|
+
if (realizeBrush(state).kind === "tile") {
|
|
1493
|
+
if (fillCurrentPathWithGdiPattern(rCtx, fillRule)) {
|
|
1494
|
+
return;
|
|
1267
1495
|
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
const
|
|
1272
|
-
|
|
1496
|
+
} else {
|
|
1497
|
+
const paint = rop2Paint(state.rop2);
|
|
1498
|
+
if (!paint.exact && isExactRop2Bitwise(state.rop2)) {
|
|
1499
|
+
const handled = paintWithExactRop2(ctx, state.rop2, state.brushColor, (scratch) => {
|
|
1500
|
+
buildPath(scratch);
|
|
1501
|
+
scratch.fill(fillRule);
|
|
1502
|
+
});
|
|
1503
|
+
if (handled) {
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1273
1506
|
}
|
|
1274
1507
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1508
|
+
applyBrush(ctx, state);
|
|
1509
|
+
ctx.fill(fillRule);
|
|
1510
|
+
}
|
|
1511
|
+
function applyPenGeometry(ctx, state) {
|
|
1512
|
+
ctx.lineWidth = Math.max(state.penWidth, 1);
|
|
1513
|
+
switch (state.penStyle) {
|
|
1514
|
+
case 1:
|
|
1515
|
+
ctx.setLineDash([8, 4]);
|
|
1516
|
+
break;
|
|
1517
|
+
case 2:
|
|
1518
|
+
ctx.setLineDash([2, 2]);
|
|
1519
|
+
break;
|
|
1520
|
+
case 3:
|
|
1521
|
+
ctx.setLineDash([8, 4, 2, 4]);
|
|
1522
|
+
break;
|
|
1523
|
+
case 4:
|
|
1524
|
+
ctx.setLineDash([8, 4, 2, 4, 2, 4]);
|
|
1525
|
+
break;
|
|
1526
|
+
default:
|
|
1527
|
+
ctx.setLineDash([]);
|
|
1528
|
+
break;
|
|
1278
1529
|
}
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
width,
|
|
1293
|
-
height,
|
|
1294
|
-
topDown,
|
|
1295
|
-
compression === BI_RLE4,
|
|
1296
|
-
colorTable,
|
|
1297
|
-
out,
|
|
1298
|
-
setPixel
|
|
1299
|
-
);
|
|
1530
|
+
}
|
|
1531
|
+
function strokeShapeExactOrFast(rCtx, buildPath) {
|
|
1532
|
+
const { ctx, state } = rCtx;
|
|
1533
|
+
const paint = rop2Paint(state.rop2);
|
|
1534
|
+
if (!paint.exact && isExactRop2Bitwise(state.rop2) && state.penStyle !== 5) {
|
|
1535
|
+
const handled = paintWithExactRop2(ctx, state.rop2, state.penColor, (scratch) => {
|
|
1536
|
+
applyPenGeometry(scratch, state);
|
|
1537
|
+
buildPath(scratch);
|
|
1538
|
+
scratch.stroke();
|
|
1539
|
+
});
|
|
1540
|
+
if (handled) {
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1300
1543
|
}
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
bitsOffset,
|
|
1304
|
-
width,
|
|
1305
|
-
height,
|
|
1306
|
-
topDown,
|
|
1307
|
-
bitCount,
|
|
1308
|
-
colorTable,
|
|
1309
|
-
masks,
|
|
1310
|
-
out
|
|
1311
|
-
);
|
|
1312
|
-
return createImageDataCompat(out, width, height);
|
|
1544
|
+
applyPen(ctx, state);
|
|
1545
|
+
ctx.stroke();
|
|
1313
1546
|
}
|
|
1314
1547
|
|
|
1315
|
-
// src/emf-gdi-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
// HS_VERTICAL
|
|
1321
|
-
[128, 64, 32, 16, 8, 4, 2, 1],
|
|
1322
|
-
// HS_FDIAGONAL
|
|
1323
|
-
[1, 2, 4, 8, 16, 32, 64, 128],
|
|
1324
|
-
// HS_BDIAGONAL
|
|
1325
|
-
[8, 8, 8, 255, 8, 8, 8, 8],
|
|
1326
|
-
// HS_CROSS
|
|
1327
|
-
[129, 66, 36, 24, 24, 36, 66, 129]
|
|
1328
|
-
// HS_DIAGCROSS
|
|
1329
|
-
];
|
|
1330
|
-
function hatchBit(hatch, x, y) {
|
|
1331
|
-
const rows = HATCH_ROWS[hatch] ?? HATCH_ROWS[0];
|
|
1332
|
-
return (rows[y & 7] >> 7 - (x & 7) & 1) === 1;
|
|
1548
|
+
// src/emf-gdi-draw-shapes.ts
|
|
1549
|
+
function needsPathBasedRectangle(rCtx) {
|
|
1550
|
+
const paint = rop2Paint(rCtx.state.rop2);
|
|
1551
|
+
const ropExact = !paint.exact && isExactRop2Bitwise(rCtx.state.rop2);
|
|
1552
|
+
return ropExact || realizeBrush(rCtx.state).kind === "tile";
|
|
1333
1553
|
}
|
|
1334
|
-
function
|
|
1335
|
-
const
|
|
1336
|
-
if (
|
|
1337
|
-
|
|
1554
|
+
function handleSetPixelV(rCtx, dataOff, recSize) {
|
|
1555
|
+
const { ctx, view } = rCtx;
|
|
1556
|
+
if (recSize >= 20) {
|
|
1557
|
+
const x = view.getInt32(dataOff, true);
|
|
1558
|
+
const y = view.getInt32(dataOff + 4, true);
|
|
1559
|
+
const color = readColorRef(view, dataOff + 8);
|
|
1560
|
+
const p = hasWorldRotation(rCtx) ? gmapPoint(rCtx, x, y) : { x: gmx(rCtx, x), y: gmy(rCtx, y) };
|
|
1561
|
+
ctx.fillStyle = color;
|
|
1562
|
+
ctx.fillRect(p.x, p.y, 1, 1);
|
|
1338
1563
|
}
|
|
1339
|
-
|
|
1340
|
-
return parseInt(hex, 16);
|
|
1564
|
+
return true;
|
|
1341
1565
|
}
|
|
1342
|
-
function
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
return null;
|
|
1566
|
+
function handleMoveToEx(rCtx, dataOff, recSize) {
|
|
1567
|
+
const { ctx, view, state, inPath } = rCtx;
|
|
1568
|
+
if (recSize >= 16) {
|
|
1569
|
+
state.curX = view.getInt32(dataOff, true);
|
|
1570
|
+
state.curY = view.getInt32(dataOff + 4, true);
|
|
1571
|
+
if (inPath) {
|
|
1572
|
+
const p = hasWorldRotation(rCtx) ? gmapPoint(rCtx, state.curX, state.curY) : { x: gmx(rCtx, state.curX), y: gmy(rCtx, state.curY) };
|
|
1573
|
+
ctx.moveTo(p.x, p.y);
|
|
1574
|
+
}
|
|
1352
1575
|
}
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1576
|
+
return true;
|
|
1577
|
+
}
|
|
1578
|
+
function handleLineTo(rCtx, dataOff, recSize) {
|
|
1579
|
+
const { ctx, view, state, inPath } = rCtx;
|
|
1580
|
+
if (recSize >= 16) {
|
|
1581
|
+
const lx = view.getInt32(dataOff, true);
|
|
1582
|
+
const ly = view.getInt32(dataOff + 4, true);
|
|
1583
|
+
const rotated = hasWorldRotation(rCtx);
|
|
1584
|
+
const to = rotated ? gmapPoint(rCtx, lx, ly) : { x: gmx(rCtx, lx), y: gmy(rCtx, ly) };
|
|
1585
|
+
if (inPath) {
|
|
1586
|
+
ctx.lineTo(to.x, to.y);
|
|
1587
|
+
} else {
|
|
1588
|
+
const from = rotated ? gmapPoint(rCtx, state.curX, state.curY) : { x: gmx(rCtx, state.curX), y: gmy(rCtx, state.curY) };
|
|
1589
|
+
const build = (c) => {
|
|
1590
|
+
c.beginPath();
|
|
1591
|
+
c.moveTo(from.x, from.y);
|
|
1592
|
+
c.lineTo(to.x, to.y);
|
|
1593
|
+
};
|
|
1594
|
+
build(ctx);
|
|
1595
|
+
strokeShapeExactOrFast(rCtx, build);
|
|
1596
|
+
}
|
|
1597
|
+
state.curX = lx;
|
|
1598
|
+
state.curY = ly;
|
|
1356
1599
|
}
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1600
|
+
return true;
|
|
1601
|
+
}
|
|
1602
|
+
function handleRectangle(rCtx, dataOff, recSize) {
|
|
1603
|
+
const { ctx, view, state, inPath } = rCtx;
|
|
1604
|
+
if (recSize >= 24) {
|
|
1605
|
+
const l = view.getInt32(dataOff, true);
|
|
1606
|
+
const t = view.getInt32(dataOff + 4, true);
|
|
1607
|
+
const r = view.getInt32(dataOff + 8, true);
|
|
1608
|
+
const b = view.getInt32(dataOff + 12, true);
|
|
1609
|
+
const rotated = hasWorldRotation(rCtx);
|
|
1610
|
+
if (rotated) {
|
|
1611
|
+
const p1 = gmapPoint(rCtx, l, t);
|
|
1612
|
+
const p2 = gmapPoint(rCtx, r, t);
|
|
1613
|
+
const p3 = gmapPoint(rCtx, r, b);
|
|
1614
|
+
const p4 = gmapPoint(rCtx, l, b);
|
|
1615
|
+
const build = (c) => {
|
|
1616
|
+
c.beginPath();
|
|
1617
|
+
c.moveTo(p1.x, p1.y);
|
|
1618
|
+
c.lineTo(p2.x, p2.y);
|
|
1619
|
+
c.lineTo(p3.x, p3.y);
|
|
1620
|
+
c.lineTo(p4.x, p4.y);
|
|
1621
|
+
c.closePath();
|
|
1622
|
+
};
|
|
1623
|
+
if (inPath) {
|
|
1624
|
+
build(ctx);
|
|
1625
|
+
} else {
|
|
1626
|
+
build(ctx);
|
|
1627
|
+
fillShapeExactOrFast(rCtx, build);
|
|
1628
|
+
strokeShapeExactOrFast(rCtx, build);
|
|
1629
|
+
}
|
|
1630
|
+
return true;
|
|
1631
|
+
}
|
|
1632
|
+
if (inPath) {
|
|
1633
|
+
ctx.rect(gmx(rCtx, l), gmy(rCtx, t), gmw(rCtx, r - l), gmh(rCtx, b - t));
|
|
1634
|
+
} else {
|
|
1635
|
+
const x = gmx(rCtx, l);
|
|
1636
|
+
const y = gmy(rCtx, t);
|
|
1637
|
+
const w = gmw(rCtx, r - l);
|
|
1638
|
+
const h = gmh(rCtx, b - t);
|
|
1639
|
+
if (needsPathBasedRectangle(rCtx)) {
|
|
1640
|
+
const build = (c) => {
|
|
1641
|
+
c.beginPath();
|
|
1642
|
+
c.rect(x, y, w, h);
|
|
1643
|
+
};
|
|
1644
|
+
build(ctx);
|
|
1645
|
+
fillShapeExactOrFast(rCtx, build);
|
|
1646
|
+
strokeShapeExactOrFast(rCtx, build);
|
|
1647
|
+
} else {
|
|
1648
|
+
applyBrush(ctx, state);
|
|
1649
|
+
ctx.fillRect(x, y, w, h);
|
|
1650
|
+
applyPen(ctx, state);
|
|
1651
|
+
ctx.strokeRect(x, y, w, h);
|
|
1652
|
+
}
|
|
1363
1653
|
}
|
|
1364
1654
|
}
|
|
1365
|
-
return
|
|
1655
|
+
return true;
|
|
1366
1656
|
}
|
|
1367
|
-
function
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1380
|
-
|
|
1657
|
+
function handleRoundRect(rCtx, dataOff, recSize) {
|
|
1658
|
+
const { ctx, view, inPath } = rCtx;
|
|
1659
|
+
if (recSize >= 32) {
|
|
1660
|
+
const l = view.getInt32(dataOff, true);
|
|
1661
|
+
const t = view.getInt32(dataOff + 4, true);
|
|
1662
|
+
const r = view.getInt32(dataOff + 8, true);
|
|
1663
|
+
const b = view.getInt32(dataOff + 12, true);
|
|
1664
|
+
const cornerW = view.getInt32(dataOff + 16, true);
|
|
1665
|
+
const cornerH = view.getInt32(dataOff + 20, true);
|
|
1666
|
+
const rotated = hasWorldRotation(rCtx);
|
|
1667
|
+
const rw = Math.abs(rotated ? cornerW / 2 : gmw(rCtx, cornerW) / 2);
|
|
1668
|
+
const rh = Math.abs(rotated ? cornerH / 2 : gmh(rCtx, cornerH) / 2);
|
|
1669
|
+
const x1 = rotated ? l : gmx(rCtx, l);
|
|
1670
|
+
const y1 = rotated ? t : gmy(rCtx, t);
|
|
1671
|
+
const w = rotated ? r - l : gmw(rCtx, r - l);
|
|
1672
|
+
const h = rotated ? b - t : gmh(rCtx, b - t);
|
|
1673
|
+
const map = (x, y) => rotated ? gmapPoint(rCtx, x, y) : { x, y };
|
|
1674
|
+
const drawRoundRect = (c) => {
|
|
1675
|
+
const radius = Math.min(rw, rh, Math.abs(w) / 2, Math.abs(h) / 2);
|
|
1676
|
+
const start = map(x1 + radius, y1);
|
|
1677
|
+
c.moveTo(start.x, start.y);
|
|
1678
|
+
if (rotated) {
|
|
1679
|
+
const corners = [
|
|
1680
|
+
[x1 + w - radius, y1],
|
|
1681
|
+
[x1 + w, y1 + radius],
|
|
1682
|
+
[x1 + w, y1 + h - radius],
|
|
1683
|
+
[x1 + w - radius, y1 + h],
|
|
1684
|
+
[x1 + radius, y1 + h],
|
|
1685
|
+
[x1, y1 + h - radius],
|
|
1686
|
+
[x1, y1 + radius],
|
|
1687
|
+
[x1 + radius, y1]
|
|
1688
|
+
];
|
|
1689
|
+
for (const [px, py] of corners) {
|
|
1690
|
+
const q = map(px, py);
|
|
1691
|
+
c.lineTo(q.x, q.y);
|
|
1692
|
+
}
|
|
1693
|
+
} else {
|
|
1694
|
+
const corner2 = map(x1 + w - radius, y1);
|
|
1695
|
+
c.lineTo(corner2.x, corner2.y);
|
|
1696
|
+
const arc1a = map(x1 + w, y1);
|
|
1697
|
+
const arc1b = map(x1 + w, y1 + radius);
|
|
1698
|
+
c.arcTo(arc1a.x, arc1a.y, arc1b.x, arc1b.y, radius);
|
|
1699
|
+
const corner3 = map(x1 + w, y1 + h - radius);
|
|
1700
|
+
c.lineTo(corner3.x, corner3.y);
|
|
1701
|
+
const arc2a = map(x1 + w, y1 + h);
|
|
1702
|
+
const arc2b = map(x1 + w - radius, y1 + h);
|
|
1703
|
+
c.arcTo(arc2a.x, arc2a.y, arc2b.x, arc2b.y, radius);
|
|
1704
|
+
const corner4 = map(x1 + radius, y1 + h);
|
|
1705
|
+
c.lineTo(corner4.x, corner4.y);
|
|
1706
|
+
const arc3a = map(x1, y1 + h);
|
|
1707
|
+
const arc3b = map(x1, y1 + h - radius);
|
|
1708
|
+
c.arcTo(arc3a.x, arc3a.y, arc3b.x, arc3b.y, radius);
|
|
1709
|
+
const corner1 = map(x1, y1 + radius);
|
|
1710
|
+
c.lineTo(corner1.x, corner1.y);
|
|
1711
|
+
const arc4a = map(x1, y1);
|
|
1712
|
+
const arc4b = map(x1 + radius, y1);
|
|
1713
|
+
c.arcTo(arc4a.x, arc4a.y, arc4b.x, arc4b.y, radius);
|
|
1714
|
+
}
|
|
1715
|
+
c.closePath();
|
|
1716
|
+
};
|
|
1717
|
+
if (inPath) {
|
|
1718
|
+
drawRoundRect(ctx);
|
|
1719
|
+
} else {
|
|
1720
|
+
const build = (c) => {
|
|
1721
|
+
c.beginPath();
|
|
1722
|
+
drawRoundRect(c);
|
|
1723
|
+
};
|
|
1724
|
+
build(ctx);
|
|
1725
|
+
fillShapeExactOrFast(rCtx, build);
|
|
1726
|
+
strokeShapeExactOrFast(rCtx, build);
|
|
1381
1727
|
}
|
|
1382
1728
|
}
|
|
1383
|
-
|
|
1384
|
-
if (!image) {
|
|
1385
|
-
return null;
|
|
1386
|
-
}
|
|
1387
|
-
const rgb = new Uint32Array(image.width * image.height);
|
|
1388
|
-
const d = image.data;
|
|
1389
|
-
for (let i = 0; i < rgb.length; i++) {
|
|
1390
|
-
rgb[i] = d[i * 4] << 16 | d[i * 4 + 1] << 8 | d[i * 4 + 2];
|
|
1391
|
-
}
|
|
1392
|
-
return { kind: "bitmap", width: image.width, height: image.height, rgb };
|
|
1729
|
+
return true;
|
|
1393
1730
|
}
|
|
1394
|
-
function
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1731
|
+
function handleEllipse(rCtx, dataOff, recSize) {
|
|
1732
|
+
const { ctx, view, inPath } = rCtx;
|
|
1733
|
+
if (recSize >= 24) {
|
|
1734
|
+
const l = view.getInt32(dataOff, true);
|
|
1735
|
+
const t = view.getInt32(dataOff + 4, true);
|
|
1736
|
+
const r = view.getInt32(dataOff + 8, true);
|
|
1737
|
+
const b = view.getInt32(dataOff + 12, true);
|
|
1738
|
+
const params = hasWorldRotation(rCtx) ? gdiEllipseParams(rCtx, (l + r) / 2, (t + b) / 2, Math.abs(r - l) / 2, Math.abs(b - t) / 2) : {
|
|
1739
|
+
cx: gmx(rCtx, (l + r) / 2),
|
|
1740
|
+
cy: gmy(rCtx, (t + b) / 2),
|
|
1741
|
+
rx: Math.abs(gmw(rCtx, r - l)) / 2,
|
|
1742
|
+
ry: Math.abs(gmh(rCtx, b - t)) / 2,
|
|
1743
|
+
rotation: 0
|
|
1744
|
+
};
|
|
1745
|
+
if (inPath) {
|
|
1746
|
+
ctx.ellipse(params.cx, params.cy, params.rx, params.ry, params.rotation, 0, Math.PI * 2);
|
|
1747
|
+
} else {
|
|
1748
|
+
const build = (c) => {
|
|
1749
|
+
c.beginPath();
|
|
1750
|
+
c.ellipse(params.cx, params.cy, params.rx, params.ry, params.rotation, 0, Math.PI * 2);
|
|
1751
|
+
};
|
|
1752
|
+
build(ctx);
|
|
1753
|
+
fillShapeExactOrFast(rCtx, build);
|
|
1754
|
+
strokeShapeExactOrFast(rCtx, build);
|
|
1755
|
+
}
|
|
1404
1756
|
}
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1757
|
+
return true;
|
|
1758
|
+
}
|
|
1759
|
+
function handleArcFamily(rCtx, recType, dataOff, recSize) {
|
|
1760
|
+
const { ctx, view, state, inPath } = rCtx;
|
|
1761
|
+
if (recSize >= 40) {
|
|
1762
|
+
const l = view.getInt32(dataOff, true);
|
|
1763
|
+
const t = view.getInt32(dataOff + 4, true);
|
|
1764
|
+
const r = view.getInt32(dataOff + 8, true);
|
|
1765
|
+
const b = view.getInt32(dataOff + 12, true);
|
|
1766
|
+
const startX = view.getInt32(dataOff + 16, true);
|
|
1767
|
+
const startY = view.getInt32(dataOff + 20, true);
|
|
1768
|
+
const endX = view.getInt32(dataOff + 24, true);
|
|
1769
|
+
const endY = view.getInt32(dataOff + 28, true);
|
|
1770
|
+
const cxA = (l + r) / 2;
|
|
1771
|
+
const cyA = (t + b) / 2;
|
|
1772
|
+
const rx = Math.abs(r - l) / 2;
|
|
1773
|
+
const ry = Math.abs(b - t) / 2;
|
|
1774
|
+
const startAngle = Math.atan2((startY - cyA) / (ry || 1), (startX - cxA) / (rx || 1));
|
|
1775
|
+
const endAngle = Math.atan2((endY - cyA) / (ry || 1), (endX - cxA) / (rx || 1));
|
|
1776
|
+
const rotated = hasWorldRotation(rCtx);
|
|
1777
|
+
const params = rotated ? gdiEllipseParams(rCtx, cxA, cyA, rx, ry) : {
|
|
1778
|
+
cx: gmx(rCtx, cxA),
|
|
1779
|
+
cy: gmy(rCtx, cyA),
|
|
1780
|
+
rx: Math.abs(gmw(rCtx, rx)),
|
|
1781
|
+
ry: Math.abs(gmh(rCtx, ry)),
|
|
1782
|
+
rotation: 0
|
|
1783
|
+
};
|
|
1784
|
+
const isArcTo = recType === EMR_ARCTO;
|
|
1785
|
+
const needsFill = recType === EMR_PIE || recType === EMR_CHORD;
|
|
1786
|
+
const startPoint = rotated ? gmapPoint(rCtx, cxA + rx * Math.cos(startAngle), cyA + ry * Math.sin(startAngle)) : { x: params.cx + params.rx * Math.cos(startAngle), y: params.cy + params.ry * Math.sin(startAngle) };
|
|
1787
|
+
const build = (c) => {
|
|
1788
|
+
if (recType === EMR_PIE) {
|
|
1789
|
+
c.moveTo(params.cx, params.cy);
|
|
1790
|
+
}
|
|
1791
|
+
if (isArcTo) {
|
|
1792
|
+
c.lineTo(startPoint.x, startPoint.y);
|
|
1793
|
+
}
|
|
1794
|
+
c.ellipse(params.cx, params.cy, params.rx, params.ry, params.rotation, startAngle, endAngle, false);
|
|
1795
|
+
if (needsFill) {
|
|
1796
|
+
c.closePath();
|
|
1797
|
+
}
|
|
1798
|
+
};
|
|
1799
|
+
if (!inPath) {
|
|
1800
|
+
ctx.beginPath();
|
|
1801
|
+
}
|
|
1802
|
+
build(ctx);
|
|
1803
|
+
if (!inPath) {
|
|
1804
|
+
const buildWithPath = (c) => {
|
|
1805
|
+
c.beginPath();
|
|
1806
|
+
build(c);
|
|
1807
|
+
};
|
|
1808
|
+
if (needsFill) {
|
|
1809
|
+
fillShapeExactOrFast(rCtx, buildWithPath);
|
|
1412
1810
|
}
|
|
1811
|
+
strokeShapeExactOrFast(rCtx, buildWithPath);
|
|
1812
|
+
}
|
|
1813
|
+
if (isArcTo) {
|
|
1814
|
+
state.curX = endX;
|
|
1815
|
+
state.curY = endY;
|
|
1413
1816
|
}
|
|
1414
|
-
return { kind: "tile", width: 8, height: 8, rgb: rgb2 };
|
|
1415
|
-
}
|
|
1416
|
-
const text = cssHexToRgb(state.textColor);
|
|
1417
|
-
const rgb = new Uint32Array(pattern.width * pattern.height);
|
|
1418
|
-
for (let i = 0; i < rgb.length; i++) {
|
|
1419
|
-
rgb[i] = pattern.bits[i] ? bk : text;
|
|
1420
1817
|
}
|
|
1421
|
-
return
|
|
1818
|
+
return true;
|
|
1422
1819
|
}
|
|
1423
|
-
function
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1820
|
+
function handleEmfGdiShapeRecord(rCtx, recType, dataOff, recSize) {
|
|
1821
|
+
switch (recType) {
|
|
1822
|
+
case EMR_SETPIXELV:
|
|
1823
|
+
return handleSetPixelV(rCtx, dataOff, recSize);
|
|
1824
|
+
case EMR_MOVETOEX:
|
|
1825
|
+
return handleMoveToEx(rCtx, dataOff, recSize);
|
|
1826
|
+
case EMR_LINETO:
|
|
1827
|
+
return handleLineTo(rCtx, dataOff, recSize);
|
|
1828
|
+
case EMR_RECTANGLE:
|
|
1829
|
+
return handleRectangle(rCtx, dataOff, recSize);
|
|
1830
|
+
case EMR_ROUNDRECT:
|
|
1831
|
+
return handleRoundRect(rCtx, dataOff, recSize);
|
|
1832
|
+
case EMR_ELLIPSE:
|
|
1833
|
+
return handleEllipse(rCtx, dataOff, recSize);
|
|
1834
|
+
case EMR_ARC:
|
|
1835
|
+
case EMR_ARCTO:
|
|
1836
|
+
case EMR_CHORD:
|
|
1837
|
+
case EMR_PIE:
|
|
1838
|
+
return handleArcFamily(rCtx, recType, dataOff, recSize);
|
|
1839
|
+
default:
|
|
1840
|
+
return false;
|
|
1841
|
+
}
|
|
1427
1842
|
}
|
|
1428
1843
|
|
|
1429
1844
|
// src/emf-gdi-stretch.ts
|
|
@@ -1478,108 +1893,25 @@ function stretchGdi(src, sx, sy, sw, sh, dw, dh, mode) {
|
|
|
1478
1893
|
let first = true;
|
|
1479
1894
|
for (const yy of ys) {
|
|
1480
1895
|
for (const xx of xs) {
|
|
1481
|
-
const p = read(xx, yy);
|
|
1482
|
-
if (mode === BLACKONWHITE) {
|
|
1483
|
-
v &= p;
|
|
1484
|
-
} else if (mode === WHITEONBLACK) {
|
|
1485
|
-
v |= p;
|
|
1486
|
-
} else if (first) {
|
|
1487
|
-
v = p;
|
|
1488
|
-
}
|
|
1489
|
-
first = false;
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
const o = (y * W + x) * 4;
|
|
1493
|
-
data[o] = v >> 16 & 255;
|
|
1494
|
-
data[o + 1] = v >> 8 & 255;
|
|
1495
|
-
data[o + 2] = v & 255;
|
|
1496
|
-
data[o + 3] = 255;
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
return { width: W, height: H, data };
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
// src/emf-rop3.ts
|
|
1503
|
-
function rop3Index(rop) {
|
|
1504
|
-
return rop >>> 16 & 255;
|
|
1505
|
-
}
|
|
1506
|
-
function rop3Operands(index) {
|
|
1507
|
-
const t = index & 255;
|
|
1508
|
-
return {
|
|
1509
|
-
usesP: (t >> 4 & 15) !== (t & 15),
|
|
1510
|
-
usesS: (t >> 2 & 51) !== (t & 51),
|
|
1511
|
-
usesD: (t >> 1 & 85) !== (t & 85)
|
|
1512
|
-
};
|
|
1513
|
-
}
|
|
1514
|
-
function evalRop3(index, p, s, d, mask = 16777215) {
|
|
1515
|
-
let out = 0;
|
|
1516
|
-
const np = ~p;
|
|
1517
|
-
const ns = ~s;
|
|
1518
|
-
const nd = ~d;
|
|
1519
|
-
for (let i = 0; i < 8; i++) {
|
|
1520
|
-
if (index & 1 << i) {
|
|
1521
|
-
out |= (i & 4 ? p : np) & (i & 2 ? s : ns) & (i & 1 ? d : nd);
|
|
1522
|
-
}
|
|
1523
|
-
}
|
|
1524
|
-
return out & mask;
|
|
1525
|
-
}
|
|
1526
|
-
function classifyRop3(rop) {
|
|
1527
|
-
const index = rop3Index(rop);
|
|
1528
|
-
switch (index) {
|
|
1529
|
-
case 204:
|
|
1530
|
-
return { kind: "copy" };
|
|
1531
|
-
case 0:
|
|
1532
|
-
return { kind: "solid", color: "black" };
|
|
1533
|
-
case 255:
|
|
1534
|
-
return { kind: "solid", color: "white" };
|
|
1535
|
-
case 170:
|
|
1536
|
-
return { kind: "noop" };
|
|
1537
|
-
case 85:
|
|
1538
|
-
return { kind: "invert-dest" };
|
|
1539
|
-
default:
|
|
1540
|
-
return { kind: "ternary", index, operands: rop3Operands(index) };
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
function applyRop3(dst, src, pattern, index, originX, originY) {
|
|
1544
|
-
const d = dst.data;
|
|
1545
|
-
const s = src ? src.data : null;
|
|
1546
|
-
const w = dst.width;
|
|
1547
|
-
const h = dst.height;
|
|
1548
|
-
const solidP = typeof pattern === "number" ? pattern : 0;
|
|
1549
|
-
const sampleP = typeof pattern === "function" ? pattern : null;
|
|
1550
|
-
for (let y = 0; y < h; y++) {
|
|
1551
|
-
for (let x = 0; x < w; x++) {
|
|
1552
|
-
const i = (y * w + x) * 4;
|
|
1553
|
-
const dv = d[i] << 16 | d[i + 1] << 8 | d[i + 2];
|
|
1554
|
-
const sv = s ? s[i] << 16 | s[i + 1] << 8 | s[i + 2] : 0;
|
|
1555
|
-
const pv = sampleP ? sampleP(originX + x, originY + y) : solidP;
|
|
1556
|
-
const r = evalRop3(index, pv, sv, dv);
|
|
1557
|
-
d[i] = r >> 16 & 255;
|
|
1558
|
-
d[i + 1] = r >> 8 & 255;
|
|
1559
|
-
d[i + 2] = r & 255;
|
|
1560
|
-
d[i + 3] = 255;
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
function clampPositiveRect(x, y, w, h, canvasW, canvasH) {
|
|
1565
|
-
let left = w < 0 ? x + w : x;
|
|
1566
|
-
let top = h < 0 ? y + h : y;
|
|
1567
|
-
let width = Math.abs(w);
|
|
1568
|
-
let height = Math.abs(h);
|
|
1569
|
-
left = Math.round(left);
|
|
1570
|
-
top = Math.round(top);
|
|
1571
|
-
width = Math.round(width);
|
|
1572
|
-
height = Math.round(height);
|
|
1573
|
-
const right = Math.min(left + width, Math.round(canvasW));
|
|
1574
|
-
const bottom = Math.min(top + height, Math.round(canvasH));
|
|
1575
|
-
left = Math.max(left, 0);
|
|
1576
|
-
top = Math.max(top, 0);
|
|
1577
|
-
width = right - left;
|
|
1578
|
-
height = bottom - top;
|
|
1579
|
-
if (width <= 0 || height <= 0) {
|
|
1580
|
-
return null;
|
|
1896
|
+
const p = read(xx, yy);
|
|
1897
|
+
if (mode === BLACKONWHITE) {
|
|
1898
|
+
v &= p;
|
|
1899
|
+
} else if (mode === WHITEONBLACK) {
|
|
1900
|
+
v |= p;
|
|
1901
|
+
} else if (first) {
|
|
1902
|
+
v = p;
|
|
1903
|
+
}
|
|
1904
|
+
first = false;
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
const o = (y * W + x) * 4;
|
|
1908
|
+
data[o] = v >> 16 & 255;
|
|
1909
|
+
data[o + 1] = v >> 8 & 255;
|
|
1910
|
+
data[o + 2] = v & 255;
|
|
1911
|
+
data[o + 3] = 255;
|
|
1912
|
+
}
|
|
1581
1913
|
}
|
|
1582
|
-
return {
|
|
1914
|
+
return { width: W, height: H, data };
|
|
1583
1915
|
}
|
|
1584
1916
|
|
|
1585
1917
|
// src/emf-gdi-draw-bitmap.ts
|
|
@@ -2315,6 +2647,25 @@ function handleEmfGdiDrawRecord(rCtx, recType, offset, dataOff, recSize) {
|
|
|
2315
2647
|
}
|
|
2316
2648
|
|
|
2317
2649
|
// src/emf-gdi-polypolygon-helpers.ts
|
|
2650
|
+
function buildPolyPolygonPath(target, rCtx, readPoint, countsOff, numPolys, totalPoints, close) {
|
|
2651
|
+
const { view } = rCtx;
|
|
2652
|
+
let pIdx = 0;
|
|
2653
|
+
for (let p = 0; p < numPolys; p++) {
|
|
2654
|
+
const count = view.getUint32(countsOff + p * 4, true);
|
|
2655
|
+
for (let i = 0; i < count && pIdx < totalPoints; i++) {
|
|
2656
|
+
const pt = readPoint(pIdx);
|
|
2657
|
+
if (i === 0) {
|
|
2658
|
+
target.moveTo(pt.x, pt.y);
|
|
2659
|
+
} else {
|
|
2660
|
+
target.lineTo(pt.x, pt.y);
|
|
2661
|
+
}
|
|
2662
|
+
pIdx++;
|
|
2663
|
+
}
|
|
2664
|
+
if (close) {
|
|
2665
|
+
target.closePath();
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2318
2669
|
function handlePolyPolygon32(rCtx, offset, dataOff, recSize) {
|
|
2319
2670
|
const { ctx, view, state, inPath } = rCtx;
|
|
2320
2671
|
const numPolys = view.getUint32(dataOff + 16, true);
|
|
@@ -2327,33 +2678,25 @@ function handlePolyPolygon32(rCtx, offset, dataOff, recSize) {
|
|
|
2327
2678
|
if (ptOff + totalPoints * 8 > offset + recSize) {
|
|
2328
2679
|
return;
|
|
2329
2680
|
}
|
|
2681
|
+
const readPoint = (pIdx) => gmapPoint(rCtx, view.getInt32(ptOff + pIdx * 8, true), view.getInt32(ptOff + pIdx * 8 + 4, true));
|
|
2682
|
+
const build = (target) => {
|
|
2683
|
+
buildPolyPolygonPath(target, rCtx, readPoint, countsOff, numPolys, totalPoints, true);
|
|
2684
|
+
};
|
|
2330
2685
|
if (!inPath) {
|
|
2331
2686
|
ctx.beginPath();
|
|
2332
2687
|
}
|
|
2333
|
-
|
|
2334
|
-
for (let p = 0; p < numPolys; p++) {
|
|
2335
|
-
const count = view.getUint32(countsOff + p * 4, true);
|
|
2336
|
-
for (let i = 0; i < count && pIdx < totalPoints; i++) {
|
|
2337
|
-
const px = view.getInt32(ptOff + pIdx * 8, true);
|
|
2338
|
-
const py = view.getInt32(ptOff + pIdx * 8 + 4, true);
|
|
2339
|
-
if (i === 0) {
|
|
2340
|
-
ctx.moveTo(gmx(rCtx, px), gmy(rCtx, py));
|
|
2341
|
-
} else {
|
|
2342
|
-
ctx.lineTo(gmx(rCtx, px), gmy(rCtx, py));
|
|
2343
|
-
}
|
|
2344
|
-
pIdx++;
|
|
2345
|
-
}
|
|
2346
|
-
ctx.closePath();
|
|
2347
|
-
}
|
|
2688
|
+
build(ctx);
|
|
2348
2689
|
if (!inPath) {
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2690
|
+
const buildWithPath = (target) => {
|
|
2691
|
+
target.beginPath();
|
|
2692
|
+
build(target);
|
|
2693
|
+
};
|
|
2694
|
+
fillShapeExactOrFast(rCtx, buildWithPath, state.polyFillMode === 2 ? "nonzero" : "evenodd");
|
|
2695
|
+
strokeShapeExactOrFast(rCtx, buildWithPath);
|
|
2353
2696
|
}
|
|
2354
2697
|
}
|
|
2355
2698
|
function handlePolyPolyline32(rCtx, offset, dataOff, recSize) {
|
|
2356
|
-
const { ctx, view,
|
|
2699
|
+
const { ctx, view, inPath } = rCtx;
|
|
2357
2700
|
const numPolys = view.getUint32(dataOff + 16, true);
|
|
2358
2701
|
const totalPoints = view.getUint32(dataOff + 20, true);
|
|
2359
2702
|
if (numPolys === 0 || numPolys >= 1e4 || totalPoints >= 1e5) {
|
|
@@ -2364,26 +2707,20 @@ function handlePolyPolyline32(rCtx, offset, dataOff, recSize) {
|
|
|
2364
2707
|
if (ptOff + totalPoints * 8 > offset + recSize) {
|
|
2365
2708
|
return;
|
|
2366
2709
|
}
|
|
2710
|
+
const readPoint = (pIdx) => gmapPoint(rCtx, view.getInt32(ptOff + pIdx * 8, true), view.getInt32(ptOff + pIdx * 8 + 4, true));
|
|
2711
|
+
const build = (target) => {
|
|
2712
|
+
buildPolyPolygonPath(target, rCtx, readPoint, countsOff, numPolys, totalPoints, false);
|
|
2713
|
+
};
|
|
2367
2714
|
if (!inPath) {
|
|
2368
2715
|
ctx.beginPath();
|
|
2369
2716
|
}
|
|
2370
|
-
|
|
2371
|
-
for (let p = 0; p < numPolys; p++) {
|
|
2372
|
-
const count = view.getUint32(countsOff + p * 4, true);
|
|
2373
|
-
for (let i = 0; i < count && pIdx < totalPoints; i++) {
|
|
2374
|
-
const px = view.getInt32(ptOff + pIdx * 8, true);
|
|
2375
|
-
const py = view.getInt32(ptOff + pIdx * 8 + 4, true);
|
|
2376
|
-
if (i === 0) {
|
|
2377
|
-
ctx.moveTo(gmx(rCtx, px), gmy(rCtx, py));
|
|
2378
|
-
} else {
|
|
2379
|
-
ctx.lineTo(gmx(rCtx, px), gmy(rCtx, py));
|
|
2380
|
-
}
|
|
2381
|
-
pIdx++;
|
|
2382
|
-
}
|
|
2383
|
-
}
|
|
2717
|
+
build(ctx);
|
|
2384
2718
|
if (!inPath) {
|
|
2385
|
-
|
|
2386
|
-
|
|
2719
|
+
const buildWithPath = (target) => {
|
|
2720
|
+
target.beginPath();
|
|
2721
|
+
build(target);
|
|
2722
|
+
};
|
|
2723
|
+
strokeShapeExactOrFast(rCtx, buildWithPath);
|
|
2387
2724
|
}
|
|
2388
2725
|
}
|
|
2389
2726
|
function handlePolyPolygon16(rCtx, offset, dataOff, recSize) {
|
|
@@ -2398,29 +2735,21 @@ function handlePolyPolygon16(rCtx, offset, dataOff, recSize) {
|
|
|
2398
2735
|
if (ptOff + totalPoints * 4 > offset + recSize) {
|
|
2399
2736
|
return;
|
|
2400
2737
|
}
|
|
2738
|
+
const readPoint = (pIdx) => gmapPoint(rCtx, view.getInt16(ptOff + pIdx * 4, true), view.getInt16(ptOff + pIdx * 4 + 2, true));
|
|
2739
|
+
const build = (target) => {
|
|
2740
|
+
buildPolyPolygonPath(target, rCtx, readPoint, countsOff, numPolys, totalPoints, true);
|
|
2741
|
+
};
|
|
2401
2742
|
if (!inPath) {
|
|
2402
2743
|
ctx.beginPath();
|
|
2403
2744
|
}
|
|
2404
|
-
|
|
2405
|
-
for (let p = 0; p < numPolys; p++) {
|
|
2406
|
-
const count = view.getUint32(countsOff + p * 4, true);
|
|
2407
|
-
for (let i = 0; i < count && pIdx < totalPoints; i++) {
|
|
2408
|
-
const px = view.getInt16(ptOff + pIdx * 4, true);
|
|
2409
|
-
const py = view.getInt16(ptOff + pIdx * 4 + 2, true);
|
|
2410
|
-
if (i === 0) {
|
|
2411
|
-
ctx.moveTo(gmx(rCtx, px), gmy(rCtx, py));
|
|
2412
|
-
} else {
|
|
2413
|
-
ctx.lineTo(gmx(rCtx, px), gmy(rCtx, py));
|
|
2414
|
-
}
|
|
2415
|
-
pIdx++;
|
|
2416
|
-
}
|
|
2417
|
-
ctx.closePath();
|
|
2418
|
-
}
|
|
2745
|
+
build(ctx);
|
|
2419
2746
|
if (!inPath) {
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2747
|
+
const buildWithPath = (target) => {
|
|
2748
|
+
target.beginPath();
|
|
2749
|
+
build(target);
|
|
2750
|
+
};
|
|
2751
|
+
fillShapeExactOrFast(rCtx, buildWithPath, state.polyFillMode === 2 ? "nonzero" : "evenodd");
|
|
2752
|
+
strokeShapeExactOrFast(rCtx, buildWithPath);
|
|
2424
2753
|
}
|
|
2425
2754
|
}
|
|
2426
2755
|
|
|
@@ -2438,43 +2767,44 @@ function handlePoly32(rCtx, recType, offset, dataOff, recSize) {
|
|
|
2438
2767
|
const isPolygon = recType === EMR_POLYGON;
|
|
2439
2768
|
const isBezier = recType === EMR_POLYBEZIER || recType === EMR_POLYBEZIERTO;
|
|
2440
2769
|
const isTo = recType === EMR_POLYBEZIERTO || recType === EMR_POLYLINETO;
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
)
|
|
2458
|
-
|
|
2770
|
+
const pt = (i) => gmapPoint(rCtx, view.getInt32(ptOff + i * 8, true), view.getInt32(ptOff + i * 8 + 4, true));
|
|
2771
|
+
const build = (target) => {
|
|
2772
|
+
if (!isTo) {
|
|
2773
|
+
const p0 = pt(0);
|
|
2774
|
+
target.moveTo(p0.x, p0.y);
|
|
2775
|
+
}
|
|
2776
|
+
let i = isTo ? 0 : 1;
|
|
2777
|
+
if (isBezier) {
|
|
2778
|
+
while (i + 2 < count) {
|
|
2779
|
+
const p1 = pt(i);
|
|
2780
|
+
const p2 = pt(i + 1);
|
|
2781
|
+
const p3 = pt(i + 2);
|
|
2782
|
+
target.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
|
|
2783
|
+
i += 3;
|
|
2784
|
+
}
|
|
2785
|
+
} else {
|
|
2786
|
+
for (; i < count; i++) {
|
|
2787
|
+
const p = pt(i);
|
|
2788
|
+
target.lineTo(p.x, p.y);
|
|
2789
|
+
}
|
|
2459
2790
|
}
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
ctx.lineTo(
|
|
2463
|
-
gmx(rCtx, view.getInt32(ptOff + i * 8, true)),
|
|
2464
|
-
gmy(rCtx, view.getInt32(ptOff + i * 8 + 4, true))
|
|
2465
|
-
);
|
|
2791
|
+
if (isPolygon) {
|
|
2792
|
+
target.closePath();
|
|
2466
2793
|
}
|
|
2794
|
+
};
|
|
2795
|
+
if (!inPath) {
|
|
2796
|
+
ctx.beginPath();
|
|
2467
2797
|
}
|
|
2468
|
-
|
|
2469
|
-
ctx.closePath();
|
|
2470
|
-
}
|
|
2798
|
+
build(ctx);
|
|
2471
2799
|
if (!inPath) {
|
|
2800
|
+
const buildWithPath = (target) => {
|
|
2801
|
+
target.beginPath();
|
|
2802
|
+
build(target);
|
|
2803
|
+
};
|
|
2472
2804
|
if (isPolygon) {
|
|
2473
|
-
|
|
2474
|
-
ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
|
|
2805
|
+
fillShapeExactOrFast(rCtx, buildWithPath, state.polyFillMode === 2 ? "nonzero" : "evenodd");
|
|
2475
2806
|
}
|
|
2476
|
-
|
|
2477
|
-
ctx.stroke();
|
|
2807
|
+
strokeShapeExactOrFast(rCtx, buildWithPath);
|
|
2478
2808
|
}
|
|
2479
2809
|
if (count > 0) {
|
|
2480
2810
|
const last = count - 1;
|
|
@@ -2496,43 +2826,44 @@ function handlePoly16(rCtx, recType, offset, dataOff, recSize) {
|
|
|
2496
2826
|
const isPolygon = recType === EMR_POLYGON16;
|
|
2497
2827
|
const isBezier = recType === EMR_POLYBEZIER16 || recType === EMR_POLYBEZIERTO16;
|
|
2498
2828
|
const isTo = recType === EMR_POLYBEZIERTO16 || recType === EMR_POLYLINETO16;
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
)
|
|
2516
|
-
|
|
2829
|
+
const pt = (i) => gmapPoint(rCtx, view.getInt16(ptOff + i * 4, true), view.getInt16(ptOff + i * 4 + 2, true));
|
|
2830
|
+
const build = (target) => {
|
|
2831
|
+
if (!isTo) {
|
|
2832
|
+
const p0 = pt(0);
|
|
2833
|
+
target.moveTo(p0.x, p0.y);
|
|
2834
|
+
}
|
|
2835
|
+
let i = isTo ? 0 : 1;
|
|
2836
|
+
if (isBezier) {
|
|
2837
|
+
while (i + 2 < count) {
|
|
2838
|
+
const p1 = pt(i);
|
|
2839
|
+
const p2 = pt(i + 1);
|
|
2840
|
+
const p3 = pt(i + 2);
|
|
2841
|
+
target.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
|
|
2842
|
+
i += 3;
|
|
2843
|
+
}
|
|
2844
|
+
} else {
|
|
2845
|
+
for (; i < count; i++) {
|
|
2846
|
+
const p = pt(i);
|
|
2847
|
+
target.lineTo(p.x, p.y);
|
|
2848
|
+
}
|
|
2517
2849
|
}
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
ctx.lineTo(
|
|
2521
|
-
gmx(rCtx, view.getInt16(ptOff + i * 4, true)),
|
|
2522
|
-
gmy(rCtx, view.getInt16(ptOff + i * 4 + 2, true))
|
|
2523
|
-
);
|
|
2850
|
+
if (isPolygon) {
|
|
2851
|
+
target.closePath();
|
|
2524
2852
|
}
|
|
2853
|
+
};
|
|
2854
|
+
if (!inPath) {
|
|
2855
|
+
ctx.beginPath();
|
|
2525
2856
|
}
|
|
2526
|
-
|
|
2527
|
-
ctx.closePath();
|
|
2528
|
-
}
|
|
2857
|
+
build(ctx);
|
|
2529
2858
|
if (!inPath) {
|
|
2859
|
+
const buildWithPath = (target) => {
|
|
2860
|
+
target.beginPath();
|
|
2861
|
+
build(target);
|
|
2862
|
+
};
|
|
2530
2863
|
if (isPolygon) {
|
|
2531
|
-
|
|
2532
|
-
ctx.fill(state.polyFillMode === 2 ? "nonzero" : "evenodd");
|
|
2864
|
+
fillShapeExactOrFast(rCtx, buildWithPath, state.polyFillMode === 2 ? "nonzero" : "evenodd");
|
|
2533
2865
|
}
|
|
2534
|
-
|
|
2535
|
-
ctx.stroke();
|
|
2866
|
+
strokeShapeExactOrFast(rCtx, buildWithPath);
|
|
2536
2867
|
}
|
|
2537
2868
|
if (count > 0) {
|
|
2538
2869
|
const last = count - 1;
|
|
@@ -2585,16 +2916,24 @@ function handleEmfGdiPolyPathRecord(rCtx, recType, offset, dataOff, recSize) {
|
|
|
2585
2916
|
case EMR_CLOSEFIGURE:
|
|
2586
2917
|
ctx.closePath();
|
|
2587
2918
|
return true;
|
|
2588
|
-
case EMR_FILLPATH:
|
|
2589
|
-
|
|
2590
|
-
|
|
2919
|
+
case EMR_FILLPATH: {
|
|
2920
|
+
const fillRule = state.polyFillMode === 2 ? "nonzero" : "evenodd";
|
|
2921
|
+
if (!fillCurrentPathWithGdiPattern(rCtx, fillRule)) {
|
|
2922
|
+
applyBrush(ctx, state);
|
|
2923
|
+
ctx.fill(fillRule);
|
|
2924
|
+
}
|
|
2591
2925
|
return true;
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2926
|
+
}
|
|
2927
|
+
case EMR_STROKEANDFILLPATH: {
|
|
2928
|
+
const fillRule = state.polyFillMode === 2 ? "nonzero" : "evenodd";
|
|
2929
|
+
if (!fillCurrentPathWithGdiPattern(rCtx, fillRule)) {
|
|
2930
|
+
applyBrush(ctx, state);
|
|
2931
|
+
ctx.fill(fillRule);
|
|
2932
|
+
}
|
|
2595
2933
|
applyPen(ctx, state);
|
|
2596
2934
|
ctx.stroke();
|
|
2597
2935
|
return true;
|
|
2936
|
+
}
|
|
2598
2937
|
case EMR_STROKEPATH:
|
|
2599
2938
|
applyPen(ctx, state);
|
|
2600
2939
|
ctx.stroke();
|
|
@@ -3311,14 +3650,14 @@ function plainLinear(ctx, grad) {
|
|
|
3311
3650
|
}
|
|
3312
3651
|
var SEAM_BIAS = 1e-5;
|
|
3313
3652
|
var MAX_PERIODS = 2048;
|
|
3314
|
-
function
|
|
3653
|
+
function surfaceSize3(ctx) {
|
|
3315
3654
|
const canvas = ctx.canvas;
|
|
3316
3655
|
const w = canvas?.width;
|
|
3317
3656
|
const h = canvas?.height;
|
|
3318
3657
|
return typeof w === "number" && typeof h === "number" && w > 0 && h > 0 ? { w, h } : null;
|
|
3319
3658
|
}
|
|
3320
3659
|
function tiledLinear(ctx, grad, rect, device) {
|
|
3321
|
-
const size =
|
|
3660
|
+
const size = surfaceSize3(ctx);
|
|
3322
3661
|
const inv = invertLinear(device);
|
|
3323
3662
|
if (!size || !inv || typeof ctx.createLinearGradient !== "function") {
|
|
3324
3663
|
return null;
|
|
@@ -3460,6 +3799,42 @@ function createBrushGradient(ctx, grad, device = IDENTITY) {
|
|
|
3460
3799
|
}
|
|
3461
3800
|
}
|
|
3462
3801
|
|
|
3802
|
+
// src/emf-plus-brush-texture.ts
|
|
3803
|
+
var IDENTITY2 = [1, 0, 0, 1, 0, 0];
|
|
3804
|
+
function packArgb(rgba, i) {
|
|
3805
|
+
return (rgba[i + 3] << 24 | rgba[i] << 16 | rgba[i + 1] << 8 | rgba[i + 2]) >>> 0;
|
|
3806
|
+
}
|
|
3807
|
+
function createBrushTexture(ctx, texture, device = IDENTITY2) {
|
|
3808
|
+
const { width, height, rgba } = texture;
|
|
3809
|
+
if (width <= 0 || height <= 0) {
|
|
3810
|
+
return null;
|
|
3811
|
+
}
|
|
3812
|
+
try {
|
|
3813
|
+
const brush = texture.transform ?? IDENTITY2;
|
|
3814
|
+
const colorAt = (bx, by) => {
|
|
3815
|
+
const ix = Math.floor(bx);
|
|
3816
|
+
const iy = Math.floor(by);
|
|
3817
|
+
if (ix < 0 || iy < 0 || ix >= width || iy >= height) {
|
|
3818
|
+
return null;
|
|
3819
|
+
}
|
|
3820
|
+
return packArgb(rgba, (iy * width + ix) * 4);
|
|
3821
|
+
};
|
|
3822
|
+
return buildPattern(
|
|
3823
|
+
ctx,
|
|
3824
|
+
{ x: 0, y: 0, w: width, h: height },
|
|
3825
|
+
width,
|
|
3826
|
+
height,
|
|
3827
|
+
texture.wrapMode,
|
|
3828
|
+
brush,
|
|
3829
|
+
halfPixelDelta(device),
|
|
3830
|
+
{ phaseX: 0, lagX: 0, lagY: 0 },
|
|
3831
|
+
colorAt
|
|
3832
|
+
);
|
|
3833
|
+
} catch {
|
|
3834
|
+
return null;
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3463
3838
|
// src/emf-plus-path.ts
|
|
3464
3839
|
function parseEmfPlusPath(data, off, maxLen) {
|
|
3465
3840
|
if (maxLen < 12) {
|
|
@@ -3606,6 +3981,12 @@ function resolveBrushPaint(rCtx, flags, brushIdOrColor) {
|
|
|
3606
3981
|
return g;
|
|
3607
3982
|
}
|
|
3608
3983
|
}
|
|
3984
|
+
if (obj.texture) {
|
|
3985
|
+
const p = createBrushTexture(rCtx.ctx, obj.texture, plusWorldMatrix(rCtx));
|
|
3986
|
+
if (p) {
|
|
3987
|
+
return p;
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3609
3990
|
return obj.color;
|
|
3610
3991
|
}
|
|
3611
3992
|
return "rgba(0,0,0,1)";
|
|
@@ -4101,64 +4482,238 @@ function handleEmfPlusDrawRecord(rCtx, recType, recFlags, dataOff, recDataSize)
|
|
|
4101
4482
|
} else {
|
|
4102
4483
|
ctx.stroke();
|
|
4103
4484
|
}
|
|
4104
|
-
return true;
|
|
4485
|
+
return true;
|
|
4486
|
+
}
|
|
4487
|
+
case EMFPLUS_DRAWLINES: {
|
|
4488
|
+
if (recDataSize >= 4) {
|
|
4489
|
+
const penId = recFlags & 255;
|
|
4490
|
+
const pen = objectTable.get(penId);
|
|
4491
|
+
const count = view.getUint32(dataOff, true);
|
|
4492
|
+
const compressed = (recFlags & 16384) !== 0;
|
|
4493
|
+
const ptSize = compressed ? 4 : 8;
|
|
4494
|
+
if (pen && pen.kind === "plus-pen") {
|
|
4495
|
+
applyEmfPlusPen(ctx, pen);
|
|
4496
|
+
}
|
|
4497
|
+
applyPlusWorldTransform(rCtx);
|
|
4498
|
+
ctx.beginPath();
|
|
4499
|
+
let pOff = dataOff + 4;
|
|
4500
|
+
for (let i = 0; i < count && pOff + ptSize <= dataOff + recDataSize; i++) {
|
|
4501
|
+
const pt = readPointFromView(view, pOff, compressed);
|
|
4502
|
+
if (i === 0) {
|
|
4503
|
+
ctx.moveTo(pt.x, pt.y);
|
|
4504
|
+
} else {
|
|
4505
|
+
ctx.lineTo(pt.x, pt.y);
|
|
4506
|
+
}
|
|
4507
|
+
pOff += ptSize;
|
|
4508
|
+
}
|
|
4509
|
+
if (recFlags & 8192) {
|
|
4510
|
+
ctx.closePath();
|
|
4511
|
+
}
|
|
4512
|
+
ctx.stroke();
|
|
4513
|
+
}
|
|
4514
|
+
return true;
|
|
4515
|
+
}
|
|
4516
|
+
case EMFPLUS_FILLPOLYGON: {
|
|
4517
|
+
if (recDataSize >= 8) {
|
|
4518
|
+
const brushVal = view.getUint32(dataOff, true);
|
|
4519
|
+
const count = view.getUint32(dataOff + 4, true);
|
|
4520
|
+
const compressed = (recFlags & 16384) !== 0;
|
|
4521
|
+
const ptSize = compressed ? 4 : 8;
|
|
4522
|
+
ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
|
|
4523
|
+
applyPlusWorldTransform(rCtx);
|
|
4524
|
+
ctx.beginPath();
|
|
4525
|
+
let pOff = dataOff + 8;
|
|
4526
|
+
for (let i = 0; i < count && pOff + ptSize <= dataOff + recDataSize; i++) {
|
|
4527
|
+
const pt = readPointFromView(view, pOff, compressed);
|
|
4528
|
+
if (i === 0) {
|
|
4529
|
+
ctx.moveTo(pt.x, pt.y);
|
|
4530
|
+
} else {
|
|
4531
|
+
ctx.lineTo(pt.x, pt.y);
|
|
4532
|
+
}
|
|
4533
|
+
pOff += ptSize;
|
|
4534
|
+
}
|
|
4535
|
+
ctx.closePath();
|
|
4536
|
+
ctx.fill();
|
|
4537
|
+
}
|
|
4538
|
+
return true;
|
|
4539
|
+
}
|
|
4540
|
+
default:
|
|
4541
|
+
return false;
|
|
4542
|
+
}
|
|
4543
|
+
}
|
|
4544
|
+
|
|
4545
|
+
// src/emf-plus-bitmap-decoder.ts
|
|
4546
|
+
var PIXELFORMAT_24BPP_RGB = 137224;
|
|
4547
|
+
var PIXELFORMAT_32BPP_RGB = 139273;
|
|
4548
|
+
var PIXELFORMAT_32BPP_ARGB = 2498570;
|
|
4549
|
+
var PIXELFORMAT_32BPP_PARGB = 925707;
|
|
4550
|
+
function decodeEmfPlusBitmapPixels(view, pixelStart, width, height, stride, pixelFormat) {
|
|
4551
|
+
const absStride = Math.abs(stride);
|
|
4552
|
+
const topDown = stride > 0;
|
|
4553
|
+
const rowBytes = width * 4;
|
|
4554
|
+
const bmpRowStride = rowBytes + 3 & -4;
|
|
4555
|
+
const pixelDataSize = bmpRowStride * height;
|
|
4556
|
+
const bmpData = new Uint8Array(pixelDataSize);
|
|
4557
|
+
for (let y = 0; y < height; y++) {
|
|
4558
|
+
const srcRow = topDown ? y : height - 1 - y;
|
|
4559
|
+
const rowOff = pixelStart + srcRow * absStride;
|
|
4560
|
+
const dstRow = (height - 1 - y) * bmpRowStride;
|
|
4561
|
+
switch (pixelFormat) {
|
|
4562
|
+
case PIXELFORMAT_32BPP_ARGB:
|
|
4563
|
+
case PIXELFORMAT_32BPP_PARGB: {
|
|
4564
|
+
for (let x = 0; x < width; x++) {
|
|
4565
|
+
const off = rowOff + x * 4;
|
|
4566
|
+
if (off + 3 >= view.byteLength) {
|
|
4567
|
+
break;
|
|
4568
|
+
}
|
|
4569
|
+
let b = view.getUint8(off);
|
|
4570
|
+
let g = view.getUint8(off + 1);
|
|
4571
|
+
let r = view.getUint8(off + 2);
|
|
4572
|
+
const a = view.getUint8(off + 3);
|
|
4573
|
+
if (pixelFormat === PIXELFORMAT_32BPP_PARGB && a > 0 && a < 255) {
|
|
4574
|
+
r = Math.min(255, Math.round(r * 255 / a));
|
|
4575
|
+
g = Math.min(255, Math.round(g * 255 / a));
|
|
4576
|
+
b = Math.min(255, Math.round(b * 255 / a));
|
|
4577
|
+
}
|
|
4578
|
+
const di = dstRow + x * 4;
|
|
4579
|
+
bmpData[di] = b;
|
|
4580
|
+
bmpData[di + 1] = g;
|
|
4581
|
+
bmpData[di + 2] = r;
|
|
4582
|
+
bmpData[di + 3] = a;
|
|
4583
|
+
}
|
|
4584
|
+
break;
|
|
4585
|
+
}
|
|
4586
|
+
case PIXELFORMAT_32BPP_RGB: {
|
|
4587
|
+
for (let x = 0; x < width; x++) {
|
|
4588
|
+
const off = rowOff + x * 4;
|
|
4589
|
+
if (off + 3 >= view.byteLength) {
|
|
4590
|
+
break;
|
|
4591
|
+
}
|
|
4592
|
+
const di = dstRow + x * 4;
|
|
4593
|
+
bmpData[di] = view.getUint8(off);
|
|
4594
|
+
bmpData[di + 1] = view.getUint8(off + 1);
|
|
4595
|
+
bmpData[di + 2] = view.getUint8(off + 2);
|
|
4596
|
+
bmpData[di + 3] = 255;
|
|
4597
|
+
}
|
|
4598
|
+
break;
|
|
4599
|
+
}
|
|
4600
|
+
case PIXELFORMAT_24BPP_RGB: {
|
|
4601
|
+
for (let x = 0; x < width; x++) {
|
|
4602
|
+
const off = rowOff + x * 3;
|
|
4603
|
+
if (off + 2 >= view.byteLength) {
|
|
4604
|
+
break;
|
|
4605
|
+
}
|
|
4606
|
+
const di = dstRow + x * 4;
|
|
4607
|
+
bmpData[di] = view.getUint8(off);
|
|
4608
|
+
bmpData[di + 1] = view.getUint8(off + 1);
|
|
4609
|
+
bmpData[di + 2] = view.getUint8(off + 2);
|
|
4610
|
+
bmpData[di + 3] = 255;
|
|
4611
|
+
}
|
|
4612
|
+
break;
|
|
4613
|
+
}
|
|
4614
|
+
default:
|
|
4615
|
+
return null;
|
|
4105
4616
|
}
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
ctx.lineTo(pt.x, pt.y);
|
|
4617
|
+
}
|
|
4618
|
+
return wrapBmpFile(bmpData, bmpRowStride, width, height, pixelDataSize);
|
|
4619
|
+
}
|
|
4620
|
+
function decodeEmfPlusBitmapPixelsToRgba(view, pixelStart, width, height, stride, pixelFormat) {
|
|
4621
|
+
const absStride = Math.abs(stride);
|
|
4622
|
+
const topDown = stride > 0;
|
|
4623
|
+
const rgba = new Uint8ClampedArray(width * height * 4);
|
|
4624
|
+
for (let y = 0; y < height; y++) {
|
|
4625
|
+
const srcRow = topDown ? y : height - 1 - y;
|
|
4626
|
+
const rowOff = pixelStart + srcRow * absStride;
|
|
4627
|
+
const dstRow = y * width * 4;
|
|
4628
|
+
switch (pixelFormat) {
|
|
4629
|
+
case PIXELFORMAT_32BPP_ARGB:
|
|
4630
|
+
case PIXELFORMAT_32BPP_PARGB: {
|
|
4631
|
+
for (let x = 0; x < width; x++) {
|
|
4632
|
+
const off = rowOff + x * 4;
|
|
4633
|
+
if (off + 3 >= view.byteLength) {
|
|
4634
|
+
break;
|
|
4125
4635
|
}
|
|
4126
|
-
|
|
4636
|
+
let b = view.getUint8(off);
|
|
4637
|
+
let g = view.getUint8(off + 1);
|
|
4638
|
+
let r = view.getUint8(off + 2);
|
|
4639
|
+
const a = view.getUint8(off + 3);
|
|
4640
|
+
if (pixelFormat === PIXELFORMAT_32BPP_PARGB && a > 0 && a < 255) {
|
|
4641
|
+
r = Math.min(255, Math.round(r * 255 / a));
|
|
4642
|
+
g = Math.min(255, Math.round(g * 255 / a));
|
|
4643
|
+
b = Math.min(255, Math.round(b * 255 / a));
|
|
4644
|
+
}
|
|
4645
|
+
const di = dstRow + x * 4;
|
|
4646
|
+
rgba[di] = r;
|
|
4647
|
+
rgba[di + 1] = g;
|
|
4648
|
+
rgba[di + 2] = b;
|
|
4649
|
+
rgba[di + 3] = a;
|
|
4127
4650
|
}
|
|
4128
|
-
|
|
4129
|
-
|
|
4651
|
+
break;
|
|
4652
|
+
}
|
|
4653
|
+
case PIXELFORMAT_32BPP_RGB: {
|
|
4654
|
+
for (let x = 0; x < width; x++) {
|
|
4655
|
+
const off = rowOff + x * 4;
|
|
4656
|
+
if (off + 3 >= view.byteLength) {
|
|
4657
|
+
break;
|
|
4658
|
+
}
|
|
4659
|
+
const di = dstRow + x * 4;
|
|
4660
|
+
rgba[di] = view.getUint8(off + 2);
|
|
4661
|
+
rgba[di + 1] = view.getUint8(off + 1);
|
|
4662
|
+
rgba[di + 2] = view.getUint8(off);
|
|
4663
|
+
rgba[di + 3] = 255;
|
|
4130
4664
|
}
|
|
4131
|
-
|
|
4665
|
+
break;
|
|
4132
4666
|
}
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
const count = view.getUint32(dataOff + 4, true);
|
|
4139
|
-
const compressed = (recFlags & 16384) !== 0;
|
|
4140
|
-
const ptSize = compressed ? 4 : 8;
|
|
4141
|
-
ctx.fillStyle = resolveBrushPaint(rCtx, recFlags, brushVal);
|
|
4142
|
-
applyPlusWorldTransform(rCtx);
|
|
4143
|
-
ctx.beginPath();
|
|
4144
|
-
let pOff = dataOff + 8;
|
|
4145
|
-
for (let i = 0; i < count && pOff + ptSize <= dataOff + recDataSize; i++) {
|
|
4146
|
-
const pt = readPointFromView(view, pOff, compressed);
|
|
4147
|
-
if (i === 0) {
|
|
4148
|
-
ctx.moveTo(pt.x, pt.y);
|
|
4149
|
-
} else {
|
|
4150
|
-
ctx.lineTo(pt.x, pt.y);
|
|
4667
|
+
case PIXELFORMAT_24BPP_RGB: {
|
|
4668
|
+
for (let x = 0; x < width; x++) {
|
|
4669
|
+
const off = rowOff + x * 3;
|
|
4670
|
+
if (off + 2 >= view.byteLength) {
|
|
4671
|
+
break;
|
|
4151
4672
|
}
|
|
4152
|
-
|
|
4673
|
+
const di = dstRow + x * 4;
|
|
4674
|
+
rgba[di] = view.getUint8(off + 2);
|
|
4675
|
+
rgba[di + 1] = view.getUint8(off + 1);
|
|
4676
|
+
rgba[di + 2] = view.getUint8(off);
|
|
4677
|
+
rgba[di + 3] = 255;
|
|
4153
4678
|
}
|
|
4154
|
-
|
|
4155
|
-
ctx.fill();
|
|
4679
|
+
break;
|
|
4156
4680
|
}
|
|
4157
|
-
|
|
4681
|
+
default:
|
|
4682
|
+
return null;
|
|
4158
4683
|
}
|
|
4159
|
-
default:
|
|
4160
|
-
return false;
|
|
4161
4684
|
}
|
|
4685
|
+
return rgba;
|
|
4686
|
+
}
|
|
4687
|
+
function wrapBmpFile(bmpData, bmpRowStride, width, height, pixelDataSize) {
|
|
4688
|
+
const fileHeaderSize = 14;
|
|
4689
|
+
const dibHeaderSize = 108;
|
|
4690
|
+
const fileSize = fileHeaderSize + dibHeaderSize + pixelDataSize;
|
|
4691
|
+
const bmpFile = new ArrayBuffer(fileSize);
|
|
4692
|
+
const bmpView = new DataView(bmpFile);
|
|
4693
|
+
const bmpBytes = new Uint8Array(bmpFile);
|
|
4694
|
+
bmpView.setUint8(0, 66);
|
|
4695
|
+
bmpView.setUint8(1, 77);
|
|
4696
|
+
bmpView.setUint32(2, fileSize, true);
|
|
4697
|
+
bmpView.setUint32(6, 0, true);
|
|
4698
|
+
bmpView.setUint32(10, fileHeaderSize + dibHeaderSize, true);
|
|
4699
|
+
bmpView.setUint32(14, dibHeaderSize, true);
|
|
4700
|
+
bmpView.setInt32(18, width, true);
|
|
4701
|
+
bmpView.setInt32(22, height, true);
|
|
4702
|
+
bmpView.setUint16(26, 1, true);
|
|
4703
|
+
bmpView.setUint16(28, 32, true);
|
|
4704
|
+
bmpView.setUint32(30, 3, true);
|
|
4705
|
+
bmpView.setUint32(34, pixelDataSize, true);
|
|
4706
|
+
bmpView.setInt32(38, 2835, true);
|
|
4707
|
+
bmpView.setInt32(42, 2835, true);
|
|
4708
|
+
bmpView.setUint32(46, 0, true);
|
|
4709
|
+
bmpView.setUint32(50, 0, true);
|
|
4710
|
+
bmpView.setUint32(54, 16711680, true);
|
|
4711
|
+
bmpView.setUint32(58, 65280, true);
|
|
4712
|
+
bmpView.setUint32(62, 255, true);
|
|
4713
|
+
bmpView.setUint32(66, 4278190080, true);
|
|
4714
|
+
bmpView.setUint32(70, 1934772034, true);
|
|
4715
|
+
bmpBytes.set(bmpData, fileHeaderSize + dibHeaderSize);
|
|
4716
|
+
return bmpFile;
|
|
4162
4717
|
}
|
|
4163
4718
|
|
|
4164
4719
|
// src/emf-plus-brush-parser.ts
|
|
@@ -4314,6 +4869,62 @@ function parseLinearGradient(view, b, end) {
|
|
|
4314
4869
|
}
|
|
4315
4870
|
};
|
|
4316
4871
|
}
|
|
4872
|
+
function decodeTextureImage(view, off, end) {
|
|
4873
|
+
if (off + 28 > end) {
|
|
4874
|
+
return null;
|
|
4875
|
+
}
|
|
4876
|
+
const imageDataType = view.getUint32(off + 4, true);
|
|
4877
|
+
if (imageDataType !== 1) {
|
|
4878
|
+
return null;
|
|
4879
|
+
}
|
|
4880
|
+
const width = view.getInt32(off + 8, true);
|
|
4881
|
+
const height = view.getInt32(off + 12, true);
|
|
4882
|
+
const stride = view.getInt32(off + 16, true);
|
|
4883
|
+
const pixelFormat = view.getUint32(off + 20, true);
|
|
4884
|
+
const pixelStart = off + 28;
|
|
4885
|
+
const absStride = Math.abs(stride);
|
|
4886
|
+
if (width <= 0 || height <= 0 || width > 8192 || height > 8192 || pixelStart + absStride * height > end) {
|
|
4887
|
+
return null;
|
|
4888
|
+
}
|
|
4889
|
+
const rgba = decodeEmfPlusBitmapPixelsToRgba(view, pixelStart, width, height, stride, pixelFormat);
|
|
4890
|
+
return rgba ? { width, height, rgba } : null;
|
|
4891
|
+
}
|
|
4892
|
+
function parseTextureBrush(view, b, end) {
|
|
4893
|
+
if (b + 8 > end) {
|
|
4894
|
+
return null;
|
|
4895
|
+
}
|
|
4896
|
+
const flags = view.getUint32(b, true);
|
|
4897
|
+
const wrapMode = readWrapMode(view.getUint32(b + 4, true));
|
|
4898
|
+
let o = b + 8;
|
|
4899
|
+
let transform = null;
|
|
4900
|
+
if (flags & BRUSH_DATA_TRANSFORM && o + 24 <= end) {
|
|
4901
|
+
transform = readTransform(view, o);
|
|
4902
|
+
o += 24;
|
|
4903
|
+
}
|
|
4904
|
+
const image = decodeTextureImage(view, o, end);
|
|
4905
|
+
if (!image) {
|
|
4906
|
+
return null;
|
|
4907
|
+
}
|
|
4908
|
+
const texture = {
|
|
4909
|
+
width: image.width,
|
|
4910
|
+
height: image.height,
|
|
4911
|
+
rgba: image.rgba,
|
|
4912
|
+
wrapMode,
|
|
4913
|
+
transform
|
|
4914
|
+
};
|
|
4915
|
+
let r = 0;
|
|
4916
|
+
let g = 0;
|
|
4917
|
+
let bl = 0;
|
|
4918
|
+
const n = image.width * image.height;
|
|
4919
|
+
for (let i = 0; i < n; i++) {
|
|
4920
|
+
r += image.rgba[i * 4];
|
|
4921
|
+
g += image.rgba[i * 4 + 1];
|
|
4922
|
+
bl += image.rgba[i * 4 + 2];
|
|
4923
|
+
}
|
|
4924
|
+
const avg = n > 0 ? `rgba(${Math.round(r / n)},${Math.round(g / n)},${Math.round(bl / n)},1)` : "rgba(128,128,128,1)";
|
|
4925
|
+
emfLog(`parseEmfPlusBrushObject: texture fill ${image.width}x${image.height}, wrapMode=${wrapMode}`);
|
|
4926
|
+
return { kind: "plus-brush", color: avg, texture };
|
|
4927
|
+
}
|
|
4317
4928
|
function parsePathGradient(view, b, end) {
|
|
4318
4929
|
if (b + 24 > end) {
|
|
4319
4930
|
return null;
|
|
@@ -4521,6 +5132,10 @@ function parseEmfPlusBrushObject(view, dataOff, recDataSize) {
|
|
|
4521
5132
|
return { kind: "plus-brush", color: argbToRgba(view.getUint32(b + 4, true)) };
|
|
4522
5133
|
}
|
|
4523
5134
|
return { kind: "plus-brush", color: "rgba(0,0,0,1)" };
|
|
5135
|
+
case EMFPLUS_BRUSHTYPE_TEXTUREFILL: {
|
|
5136
|
+
const brush = parseTextureBrush(view, b, end);
|
|
5137
|
+
return brush ?? { kind: "plus-brush", color: "rgba(0,0,0,1)" };
|
|
5138
|
+
}
|
|
4524
5139
|
case EMFPLUS_BRUSHTYPE_LINEARGRADIENT: {
|
|
4525
5140
|
const brush = parseLinearGradient(view, b, end);
|
|
4526
5141
|
return brush ?? { kind: "plus-brush", color: "rgba(0,0,0,1)" };
|
|
@@ -4534,110 +5149,6 @@ function parseEmfPlusBrushObject(view, dataOff, recDataSize) {
|
|
|
4534
5149
|
}
|
|
4535
5150
|
}
|
|
4536
5151
|
|
|
4537
|
-
// src/emf-plus-bitmap-decoder.ts
|
|
4538
|
-
var PIXELFORMAT_24BPP_RGB = 137224;
|
|
4539
|
-
var PIXELFORMAT_32BPP_RGB = 139273;
|
|
4540
|
-
var PIXELFORMAT_32BPP_ARGB = 2498570;
|
|
4541
|
-
var PIXELFORMAT_32BPP_PARGB = 925707;
|
|
4542
|
-
function decodeEmfPlusBitmapPixels(view, pixelStart, width, height, stride, pixelFormat) {
|
|
4543
|
-
const absStride = Math.abs(stride);
|
|
4544
|
-
const topDown = stride > 0;
|
|
4545
|
-
const rowBytes = width * 4;
|
|
4546
|
-
const bmpRowStride = rowBytes + 3 & -4;
|
|
4547
|
-
const pixelDataSize = bmpRowStride * height;
|
|
4548
|
-
const bmpData = new Uint8Array(pixelDataSize);
|
|
4549
|
-
for (let y = 0; y < height; y++) {
|
|
4550
|
-
const srcRow = topDown ? y : height - 1 - y;
|
|
4551
|
-
const rowOff = pixelStart + srcRow * absStride;
|
|
4552
|
-
const dstRow = (height - 1 - y) * bmpRowStride;
|
|
4553
|
-
switch (pixelFormat) {
|
|
4554
|
-
case PIXELFORMAT_32BPP_ARGB:
|
|
4555
|
-
case PIXELFORMAT_32BPP_PARGB: {
|
|
4556
|
-
for (let x = 0; x < width; x++) {
|
|
4557
|
-
const off = rowOff + x * 4;
|
|
4558
|
-
if (off + 3 >= view.byteLength) {
|
|
4559
|
-
break;
|
|
4560
|
-
}
|
|
4561
|
-
let b = view.getUint8(off);
|
|
4562
|
-
let g = view.getUint8(off + 1);
|
|
4563
|
-
let r = view.getUint8(off + 2);
|
|
4564
|
-
const a = view.getUint8(off + 3);
|
|
4565
|
-
if (pixelFormat === PIXELFORMAT_32BPP_PARGB && a > 0 && a < 255) {
|
|
4566
|
-
r = Math.min(255, Math.round(r * 255 / a));
|
|
4567
|
-
g = Math.min(255, Math.round(g * 255 / a));
|
|
4568
|
-
b = Math.min(255, Math.round(b * 255 / a));
|
|
4569
|
-
}
|
|
4570
|
-
const di = dstRow + x * 4;
|
|
4571
|
-
bmpData[di] = b;
|
|
4572
|
-
bmpData[di + 1] = g;
|
|
4573
|
-
bmpData[di + 2] = r;
|
|
4574
|
-
bmpData[di + 3] = a;
|
|
4575
|
-
}
|
|
4576
|
-
break;
|
|
4577
|
-
}
|
|
4578
|
-
case PIXELFORMAT_32BPP_RGB: {
|
|
4579
|
-
for (let x = 0; x < width; x++) {
|
|
4580
|
-
const off = rowOff + x * 4;
|
|
4581
|
-
if (off + 3 >= view.byteLength) {
|
|
4582
|
-
break;
|
|
4583
|
-
}
|
|
4584
|
-
const di = dstRow + x * 4;
|
|
4585
|
-
bmpData[di] = view.getUint8(off);
|
|
4586
|
-
bmpData[di + 1] = view.getUint8(off + 1);
|
|
4587
|
-
bmpData[di + 2] = view.getUint8(off + 2);
|
|
4588
|
-
bmpData[di + 3] = 255;
|
|
4589
|
-
}
|
|
4590
|
-
break;
|
|
4591
|
-
}
|
|
4592
|
-
case PIXELFORMAT_24BPP_RGB: {
|
|
4593
|
-
for (let x = 0; x < width; x++) {
|
|
4594
|
-
const off = rowOff + x * 3;
|
|
4595
|
-
if (off + 2 >= view.byteLength) {
|
|
4596
|
-
break;
|
|
4597
|
-
}
|
|
4598
|
-
const di = dstRow + x * 4;
|
|
4599
|
-
bmpData[di] = view.getUint8(off);
|
|
4600
|
-
bmpData[di + 1] = view.getUint8(off + 1);
|
|
4601
|
-
bmpData[di + 2] = view.getUint8(off + 2);
|
|
4602
|
-
bmpData[di + 3] = 255;
|
|
4603
|
-
}
|
|
4604
|
-
break;
|
|
4605
|
-
}
|
|
4606
|
-
default:
|
|
4607
|
-
return null;
|
|
4608
|
-
}
|
|
4609
|
-
}
|
|
4610
|
-
const fileHeaderSize = 14;
|
|
4611
|
-
const dibHeaderSize = 108;
|
|
4612
|
-
const fileSize = fileHeaderSize + dibHeaderSize + pixelDataSize;
|
|
4613
|
-
const bmpFile = new ArrayBuffer(fileSize);
|
|
4614
|
-
const bmpView = new DataView(bmpFile);
|
|
4615
|
-
const bmpBytes = new Uint8Array(bmpFile);
|
|
4616
|
-
bmpView.setUint8(0, 66);
|
|
4617
|
-
bmpView.setUint8(1, 77);
|
|
4618
|
-
bmpView.setUint32(2, fileSize, true);
|
|
4619
|
-
bmpView.setUint32(6, 0, true);
|
|
4620
|
-
bmpView.setUint32(10, fileHeaderSize + dibHeaderSize, true);
|
|
4621
|
-
bmpView.setUint32(14, dibHeaderSize, true);
|
|
4622
|
-
bmpView.setInt32(18, width, true);
|
|
4623
|
-
bmpView.setInt32(22, height, true);
|
|
4624
|
-
bmpView.setUint16(26, 1, true);
|
|
4625
|
-
bmpView.setUint16(28, 32, true);
|
|
4626
|
-
bmpView.setUint32(30, 3, true);
|
|
4627
|
-
bmpView.setUint32(34, pixelDataSize, true);
|
|
4628
|
-
bmpView.setInt32(38, 2835, true);
|
|
4629
|
-
bmpView.setInt32(42, 2835, true);
|
|
4630
|
-
bmpView.setUint32(46, 0, true);
|
|
4631
|
-
bmpView.setUint32(50, 0, true);
|
|
4632
|
-
bmpView.setUint32(54, 16711680, true);
|
|
4633
|
-
bmpView.setUint32(58, 65280, true);
|
|
4634
|
-
bmpView.setUint32(62, 255, true);
|
|
4635
|
-
bmpView.setUint32(66, 4278190080, true);
|
|
4636
|
-
bmpView.setUint32(70, 1934772034, true);
|
|
4637
|
-
bmpBytes.set(bmpData, fileHeaderSize + dibHeaderSize);
|
|
4638
|
-
return bmpFile;
|
|
4639
|
-
}
|
|
4640
|
-
|
|
4641
5152
|
// src/emf-plus-object-complex.ts
|
|
4642
5153
|
function parseEmfPlusPenObject(view, dataOff, recDataSize) {
|
|
4643
5154
|
if (recDataSize < 20) {
|