reamkit 1.22.0 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/core/converter/ream.d.ts +16 -0
- package/dist/esm/core/converter/ream.js +59 -9
- package/dist/esm/core/document-model/types.d.ts +18 -0
- package/dist/esm/core/drawingml/chart-geometry.js +11 -5
- package/dist/esm/core/drawingml/colors.d.ts +4 -2
- package/dist/esm/core/drawingml/colors.js +15 -0
- package/dist/esm/core/drawingml/theme-parser.d.ts +38 -0
- package/dist/esm/core/drawingml/theme-parser.js +64 -1
- package/dist/esm/core/fonts/families.d.ts +10 -0
- package/dist/esm/core/fonts/families.js +44 -0
- package/dist/esm/core/fonts/index.d.ts +3 -2
- package/dist/esm/core/fonts/remote-fonts.d.ts +50 -0
- package/dist/esm/core/fonts/remote-fonts.js +114 -8
- package/dist/esm/core/fonts/scripts.d.ts +26 -0
- package/dist/esm/core/fonts/scripts.js +177 -0
- package/dist/esm/core/metafile/blit.d.ts +33 -0
- package/dist/esm/core/metafile/blit.js +67 -0
- package/dist/esm/core/metafile/dib.d.ts +61 -0
- package/dist/esm/core/metafile/dib.js +205 -0
- package/dist/esm/core/metafile/emf.js +137 -23
- package/dist/esm/core/metafile/picture.d.ts +12 -1
- package/dist/esm/core/metafile/wmf.js +149 -9
- package/dist/esm/core/ole/escher-blip.d.ts +12 -0
- package/dist/esm/core/ole/escher-blip.js +88 -0
- package/dist/esm/{pdf-reader → core}/png-encode.js +1 -1
- package/dist/esm/excel/print-model.js +3 -1
- package/dist/esm/excel/styles-parser.js +1 -1
- package/dist/esm/excel/xls/biff-reader.js +53 -18
- package/dist/esm/excel/xls/biff-styles.d.ts +5 -2
- package/dist/esm/excel/xls/biff-styles.js +9 -7
- package/dist/esm/excel/xls/escher.js +90 -24
- package/dist/esm/layout/page-doc.d.ts +39 -0
- package/dist/esm/layout/styled-layout.d.ts +2 -2
- package/dist/esm/layout/styled-layout.js +301 -35
- package/dist/esm/pdf/styled-page-emitter.js +35 -2
- package/dist/esm/pdf-reader/image-decode.js +1 -1
- package/dist/esm/pptx/embedded-fonts.d.ts +31 -0
- package/dist/esm/pptx/embedded-fonts.js +104 -0
- package/dist/esm/pptx/placeholder-cascade.d.ts +73 -6
- package/dist/esm/pptx/placeholder-cascade.js +114 -18
- package/dist/esm/pptx/ppt/ppt-reader.js +120 -10
- package/dist/esm/pptx/ppt/ppt-text.d.ts +66 -1
- package/dist/esm/pptx/ppt/ppt-text.js +576 -137
- package/dist/esm/pptx/pptx-reader.d.ts +8 -0
- package/dist/esm/pptx/pptx-reader.js +27 -5
- package/dist/esm/pptx/preset-table-styles.d.ts +9 -0
- package/dist/esm/pptx/preset-table-styles.js +27 -0
- package/dist/esm/pptx/slide-parser.d.ts +7 -1
- package/dist/esm/pptx/slide-parser.js +106 -33
- package/dist/esm/pptx/sp-helpers.d.ts +2 -1
- package/dist/esm/pptx/sp-helpers.js +3 -2
- package/dist/esm/pptx/table-style.d.ts +9 -1
- package/dist/esm/pptx/table-style.js +49 -7
- package/dist/esm/word/document-parser.d.ts +7 -1
- package/dist/esm/word/document-parser.js +6 -6
- package/dist/esm/word/docx-reader.js +6 -3
- package/dist/esm/word/docx-to-pdf.d.ts +8 -7
- package/dist/esm/word/docx-to-pdf.js +25 -7
- package/dist/esm/word/drawing-parser.js +31 -19
- package/dist/esm/word/numbering-parser.d.ts +2 -1
- package/dist/esm/word/numbering-parser.js +6 -6
- package/dist/esm/word/paragraph-properties.d.ts +2 -1
- package/dist/esm/word/paragraph-properties.js +2 -2
- package/dist/esm/word/run-properties.d.ts +5 -2
- package/dist/esm/word/run-properties.js +13 -8
- package/dist/esm/word/styles-parser.d.ts +2 -1
- package/dist/esm/word/styles-parser.js +12 -12
- package/dist/esm/word/table-parser.js +3 -3
- package/dist/esm/word/theme-fonts.d.ts +10 -0
- package/dist/esm/word/theme-fonts.js +28 -0
- package/package.json +1 -1
- /package/dist/esm/{pdf-reader → core}/png-encode.d.ts +0 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
//#region src/core/fonts/scripts.ts
|
|
2
|
+
var RANGES = [
|
|
3
|
+
[
|
|
4
|
+
1424,
|
|
5
|
+
1535,
|
|
6
|
+
"hebrew"
|
|
7
|
+
],
|
|
8
|
+
[
|
|
9
|
+
1536,
|
|
10
|
+
1791,
|
|
11
|
+
"arabic"
|
|
12
|
+
],
|
|
13
|
+
[
|
|
14
|
+
1872,
|
|
15
|
+
1919,
|
|
16
|
+
"arabic"
|
|
17
|
+
],
|
|
18
|
+
[
|
|
19
|
+
2208,
|
|
20
|
+
2303,
|
|
21
|
+
"arabic"
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
3584,
|
|
25
|
+
3711,
|
|
26
|
+
"thai"
|
|
27
|
+
],
|
|
28
|
+
[
|
|
29
|
+
4352,
|
|
30
|
+
4607,
|
|
31
|
+
"kr"
|
|
32
|
+
],
|
|
33
|
+
[
|
|
34
|
+
8592,
|
|
35
|
+
8703,
|
|
36
|
+
"symbols"
|
|
37
|
+
],
|
|
38
|
+
[
|
|
39
|
+
8960,
|
|
40
|
+
9215,
|
|
41
|
+
"symbols"
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
9312,
|
|
45
|
+
9471,
|
|
46
|
+
"symbols"
|
|
47
|
+
],
|
|
48
|
+
[
|
|
49
|
+
9472,
|
|
50
|
+
10175,
|
|
51
|
+
"symbols"
|
|
52
|
+
],
|
|
53
|
+
[
|
|
54
|
+
11008,
|
|
55
|
+
11263,
|
|
56
|
+
"symbols"
|
|
57
|
+
],
|
|
58
|
+
[
|
|
59
|
+
12288,
|
|
60
|
+
12351,
|
|
61
|
+
"han"
|
|
62
|
+
],
|
|
63
|
+
[
|
|
64
|
+
12352,
|
|
65
|
+
12543,
|
|
66
|
+
"jp"
|
|
67
|
+
],
|
|
68
|
+
[
|
|
69
|
+
12592,
|
|
70
|
+
12687,
|
|
71
|
+
"kr"
|
|
72
|
+
],
|
|
73
|
+
[
|
|
74
|
+
13312,
|
|
75
|
+
19903,
|
|
76
|
+
"han"
|
|
77
|
+
],
|
|
78
|
+
[
|
|
79
|
+
19968,
|
|
80
|
+
40959,
|
|
81
|
+
"han"
|
|
82
|
+
],
|
|
83
|
+
[
|
|
84
|
+
44032,
|
|
85
|
+
55215,
|
|
86
|
+
"kr"
|
|
87
|
+
],
|
|
88
|
+
[
|
|
89
|
+
63744,
|
|
90
|
+
64255,
|
|
91
|
+
"han"
|
|
92
|
+
],
|
|
93
|
+
[
|
|
94
|
+
64285,
|
|
95
|
+
64335,
|
|
96
|
+
"hebrew"
|
|
97
|
+
],
|
|
98
|
+
[
|
|
99
|
+
64336,
|
|
100
|
+
65023,
|
|
101
|
+
"arabic"
|
|
102
|
+
],
|
|
103
|
+
[
|
|
104
|
+
65136,
|
|
105
|
+
65279,
|
|
106
|
+
"arabic"
|
|
107
|
+
],
|
|
108
|
+
[
|
|
109
|
+
65280,
|
|
110
|
+
65519,
|
|
111
|
+
"han"
|
|
112
|
+
]
|
|
113
|
+
];
|
|
114
|
+
/**
|
|
115
|
+
* The writing system a character needs a face for, or `undefined` when a Latin
|
|
116
|
+
* family covers it.
|
|
117
|
+
*
|
|
118
|
+
* @param cp The code point.
|
|
119
|
+
* @param hanFace Which face draws unified Han in this document (see
|
|
120
|
+
* {@link scriptsInFlow}); defaults to Simplified.
|
|
121
|
+
*/
|
|
122
|
+
function scriptForCodepoint(cp, hanFace = "sc") {
|
|
123
|
+
for (const [lo, hi, key] of RANGES) {
|
|
124
|
+
if (cp < lo) break;
|
|
125
|
+
if (cp <= hi) return key === "han" ? hanFace : key;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The writing systems a document holds text in, beside the Latin one.
|
|
130
|
+
*
|
|
131
|
+
* Unified Han is written the same in Japanese, Korean and Chinese and drawn
|
|
132
|
+
* differently in each, and the character does not say which — the document
|
|
133
|
+
* does: Kana beside it means Japanese, Hangul means Korean. So the whole text
|
|
134
|
+
* is read once, and the answer applies to every Han character in it.
|
|
135
|
+
*
|
|
136
|
+
* @param flow The parsed document.
|
|
137
|
+
* @returns The scripts to fetch a face for, and the one Han is drawn with.
|
|
138
|
+
*/
|
|
139
|
+
function scriptsInFlow(flow) {
|
|
140
|
+
const scripts = /* @__PURE__ */ new Set();
|
|
141
|
+
const marks = /* @__PURE__ */ new Set();
|
|
142
|
+
const read = (text) => {
|
|
143
|
+
for (const ch of text) {
|
|
144
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
145
|
+
if (cp < 1424) continue;
|
|
146
|
+
const key = scriptForCodepoint(cp, "sc");
|
|
147
|
+
if (key === void 0) continue;
|
|
148
|
+
if (key === "sc") {
|
|
149
|
+
marks.add("han");
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
scripts.add(key);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const shapeText = (shape) => {
|
|
156
|
+
if (shape.text) visit(shape.text.content);
|
|
157
|
+
for (const child of shape.children ?? []) shapeText(child.shape);
|
|
158
|
+
};
|
|
159
|
+
const visit = (elements) => {
|
|
160
|
+
for (const el of elements) if (el.kind === "paragraph") for (const run of el.paragraph.runs) read(run.text);
|
|
161
|
+
else if (el.kind === "table") for (const row of el.table.rows) for (const cell of row.cells) visit(cell.content);
|
|
162
|
+
else if (el.kind === "shape") shapeText(el.shape);
|
|
163
|
+
};
|
|
164
|
+
visit(flow.body);
|
|
165
|
+
for (const band of flow.headersFooters?.values() ?? []) visit(band);
|
|
166
|
+
for (const note of flow.footnotes?.values() ?? []) visit(note);
|
|
167
|
+
for (const note of flow.endnotes?.values() ?? []) visit(note);
|
|
168
|
+
for (const comment of flow.comments?.values() ?? []) visit(comment.content);
|
|
169
|
+
const hanFace = scripts.has("jp") ? "jp" : scripts.has("kr") ? "kr" : "sc";
|
|
170
|
+
if (marks.has("han")) scripts.add(hanFace);
|
|
171
|
+
return {
|
|
172
|
+
scripts,
|
|
173
|
+
hanFace
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
//#endregion
|
|
177
|
+
export { scriptForCodepoint, scriptsInFlow };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { DibImage } from './dib.js';
|
|
2
|
+
import { PictureImage } from './picture.js';
|
|
3
|
+
/** How a blit's ROP wants the source drawn. */
|
|
4
|
+
export type BlitMode =
|
|
5
|
+
/** SRCCOPY and its neighbours: the source replaces what is under it. */
|
|
6
|
+
'opaque'
|
|
7
|
+
/** SRCAND: a mask, held until the picture it belongs to arrives. */
|
|
8
|
+
| 'mask'
|
|
9
|
+
/** SRCPAINT: ORed in, so the source's BLACK leaves the ground showing. */
|
|
10
|
+
| 'or'
|
|
11
|
+
/** A ROP that ignores the source, or one whose result is not a picture. */
|
|
12
|
+
| 'skip';
|
|
13
|
+
/** The destination rectangle a blit paints, in the metafile's own units. */
|
|
14
|
+
export interface BlitRect {
|
|
15
|
+
readonly x: number;
|
|
16
|
+
readonly y: number;
|
|
17
|
+
readonly width: number;
|
|
18
|
+
readonly height: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* What a ternary raster operation asks a reader to draw.
|
|
22
|
+
*
|
|
23
|
+
* @param rop The ROP code, as the record states it.
|
|
24
|
+
*/
|
|
25
|
+
export declare function blitMode(rop: number): BlitMode;
|
|
26
|
+
/**
|
|
27
|
+
* A sink for a metafile's blits that puts the AND/OR pair back together.
|
|
28
|
+
*
|
|
29
|
+
* @param emit Where a finished picture goes — the reader's primitive list.
|
|
30
|
+
*/
|
|
31
|
+
export declare function makeBlitter(emit: (prim: PictureImage) => void): {
|
|
32
|
+
blit: (img: DibImage, dest: BlitRect, rop: number) => void;
|
|
33
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { dibToPng, maskedDib } from "./dib.js";
|
|
2
|
+
//#region src/core/metafile/blit.ts
|
|
3
|
+
/**
|
|
4
|
+
* What a ternary raster operation asks a reader to draw.
|
|
5
|
+
*
|
|
6
|
+
* @param rop The ROP code, as the record states it.
|
|
7
|
+
*/
|
|
8
|
+
function blitMode(rop) {
|
|
9
|
+
switch (rop >>> 0) {
|
|
10
|
+
case 13369376:
|
|
11
|
+
case 14812998: return "opaque";
|
|
12
|
+
case 8913094: return "mask";
|
|
13
|
+
case 15597702: return "or";
|
|
14
|
+
case 6684742:
|
|
15
|
+
case 15728673:
|
|
16
|
+
case 66:
|
|
17
|
+
case 16711778: return "skip";
|
|
18
|
+
default: return (rop >>> 2 & 3342336) !== (rop & 3342336) ? "opaque" : "skip";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A sink for a metafile's blits that puts the AND/OR pair back together.
|
|
23
|
+
*
|
|
24
|
+
* @param emit Where a finished picture goes — the reader's primitive list.
|
|
25
|
+
*/
|
|
26
|
+
function makeBlitter(emit) {
|
|
27
|
+
let pending;
|
|
28
|
+
const place = (img, dest) => {
|
|
29
|
+
emit({
|
|
30
|
+
kind: "image",
|
|
31
|
+
...dest,
|
|
32
|
+
png: dibToPng(img)
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
return { blit: (img, dest, rop) => {
|
|
36
|
+
const mode = blitMode(rop);
|
|
37
|
+
if (mode === "skip") return;
|
|
38
|
+
if (mode === "mask") {
|
|
39
|
+
pending = {
|
|
40
|
+
img,
|
|
41
|
+
dest
|
|
42
|
+
};
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const mask = pending;
|
|
46
|
+
pending = void 0;
|
|
47
|
+
if (mode === "or") {
|
|
48
|
+
place(mask && sameRect(mask.dest, dest) ? maskedDib(img, mask.img) : keyBlack(img), dest);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
place(img, dest);
|
|
52
|
+
} };
|
|
53
|
+
}
|
|
54
|
+
function sameRect(a, b) {
|
|
55
|
+
return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
|
|
56
|
+
}
|
|
57
|
+
function keyBlack(img) {
|
|
58
|
+
const rgba = new Uint8Array(img.rgba);
|
|
59
|
+
for (let i = 0; i < rgba.length; i += 4) if (rgba[i] === 0 && rgba[i + 1] === 0 && rgba[i + 2] === 0) rgba[i + 3] = 0;
|
|
60
|
+
return {
|
|
61
|
+
width: img.width,
|
|
62
|
+
height: img.height,
|
|
63
|
+
rgba
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
export { makeBlitter };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** A decoded bitmap: straight (un-premultiplied) RGBA, TOP row first. */
|
|
2
|
+
export interface DibImage {
|
|
3
|
+
readonly width: number;
|
|
4
|
+
readonly height: number;
|
|
5
|
+
readonly rgba: Uint8Array;
|
|
6
|
+
/**
|
|
7
|
+
* Whether the source stored its rows bottom-first — which is the usual way,
|
|
8
|
+
* and which a caller needs to know to read a source rectangle: a blit states
|
|
9
|
+
* one in the BITMAP's own coordinates, whose origin is its first row.
|
|
10
|
+
*/
|
|
11
|
+
readonly bottomUp?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** What a caller knows about the bitmap that its own header does not say. */
|
|
14
|
+
export interface DibOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Where the pixels start. EMF states this; a WMF stores the two parts back to
|
|
17
|
+
* back, so leaving it out means "after the palette".
|
|
18
|
+
*/
|
|
19
|
+
readonly bitsAt?: number;
|
|
20
|
+
/** How many pixel bytes there are, when the record states it. */
|
|
21
|
+
readonly cbBits?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Whether a 32-bit bitmap's fourth byte is ALPHA. It is reserved in an
|
|
24
|
+
* ordinary blit — GDI ignores it and so must this, or a picture whose
|
|
25
|
+
* producer left it zero comes out invisible — and it is the alpha channel
|
|
26
|
+
* in an EMR_ALPHABLEND that says so.
|
|
27
|
+
*/
|
|
28
|
+
readonly alpha?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Decode a packed DIB into RGBA.
|
|
32
|
+
*
|
|
33
|
+
* @param bytes The metafile.
|
|
34
|
+
* @param bmiAt Where the bitmap's header starts.
|
|
35
|
+
* @param o What the record says about the bitmap; see {@link DibOptions}.
|
|
36
|
+
* @returns The bitmap, or `undefined` when the header is malformed or its
|
|
37
|
+
* compression is one this decoder does not do.
|
|
38
|
+
*/
|
|
39
|
+
export declare function readDib(bytes: Uint8Array, bmiAt: number, o?: DibOptions): DibImage | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* The colour bitmap seen through a monochrome one — the two-blit idiom every
|
|
42
|
+
* clipart of this age is drawn with: an AND of the mask, which knocks the
|
|
43
|
+
* picture's ground to white, and then an OR of the picture, which is black
|
|
44
|
+
* exactly where the ground was. Kept apart they draw a black box; put together
|
|
45
|
+
* they are one bitmap with an alpha channel.
|
|
46
|
+
*
|
|
47
|
+
* @param color The bitmap the second blit paints.
|
|
48
|
+
* @param mask The first blit's mask: WHITE where the picture is to show
|
|
49
|
+
* through, black where it is opaque.
|
|
50
|
+
*/
|
|
51
|
+
export declare function maskedDib(color: DibImage, mask: DibImage): DibImage;
|
|
52
|
+
/**
|
|
53
|
+
* The bitmap as a PNG file, ready for the resource store. An opaque one is
|
|
54
|
+
* written without an alpha channel — a quarter less to deflate, and the
|
|
55
|
+
* writers that flatten transparency then have nothing to do.
|
|
56
|
+
*/
|
|
57
|
+
export declare function dibToPng(img: DibImage): Uint8Array;
|
|
58
|
+
/** The bitmap at a constant transparency — what a blend function's `SrcConstantAlpha` asks for. */
|
|
59
|
+
export declare function fadedDib(img: DibImage, alpha: number): DibImage;
|
|
60
|
+
/** The part of the bitmap a blit's source rectangle names, or all of it. */
|
|
61
|
+
export declare function cropDib(img: DibImage, x: number, y: number, w: number, h: number): DibImage;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { encodePng } from "../png-encode.js";
|
|
2
|
+
//#region src/core/metafile/dib.ts
|
|
3
|
+
var MAX_PIXELS = 3e7;
|
|
4
|
+
/**
|
|
5
|
+
* Decode a packed DIB into RGBA.
|
|
6
|
+
*
|
|
7
|
+
* @param bytes The metafile.
|
|
8
|
+
* @param bmiAt Where the bitmap's header starts.
|
|
9
|
+
* @param o What the record says about the bitmap; see {@link DibOptions}.
|
|
10
|
+
* @returns The bitmap, or `undefined` when the header is malformed or its
|
|
11
|
+
* compression is one this decoder does not do.
|
|
12
|
+
*/
|
|
13
|
+
function readDib(bytes, bmiAt, o = {}) {
|
|
14
|
+
const { bitsAt, cbBits } = o;
|
|
15
|
+
if (bmiAt < 0 || bmiAt + 12 > bytes.length) return void 0;
|
|
16
|
+
const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
17
|
+
const headerSize = v.getUint32(bmiAt, true);
|
|
18
|
+
const core = headerSize === 12;
|
|
19
|
+
if (!core && (headerSize < 40 || bmiAt + 40 > bytes.length)) return void 0;
|
|
20
|
+
const width = core ? v.getInt16(bmiAt + 4, true) : v.getInt32(bmiAt + 4, true);
|
|
21
|
+
const rawHeight = core ? v.getInt16(bmiAt + 6, true) : v.getInt32(bmiAt + 8, true);
|
|
22
|
+
const bpp = core ? v.getUint16(bmiAt + 10, true) : v.getUint16(bmiAt + 14, true);
|
|
23
|
+
const compression = core ? 0 : v.getUint32(bmiAt + 16, true);
|
|
24
|
+
const topDown = rawHeight < 0;
|
|
25
|
+
const height = Math.abs(rawHeight);
|
|
26
|
+
if (width <= 0 || height <= 0 || width * height > MAX_PIXELS) return void 0;
|
|
27
|
+
if (compression !== 0 && compression !== 3) return void 0;
|
|
28
|
+
if (bpp !== 1 && bpp !== 4 && bpp !== 8 && bpp !== 16 && bpp !== 24 && bpp !== 32) return;
|
|
29
|
+
const paletteAt = bmiAt + headerSize;
|
|
30
|
+
const entry = core ? 3 : 4;
|
|
31
|
+
const declared = core || headerSize < 40 ? 0 : v.getUint32(bmiAt + 32, true);
|
|
32
|
+
const paletteCount = bpp <= 8 ? declared > 0 ? Math.min(declared, 1 << bpp) : 1 << bpp : 0;
|
|
33
|
+
const masksAt = compression === 3 ? paletteAt : void 0;
|
|
34
|
+
const paletteBytes = compression === 3 ? 12 : paletteCount * entry;
|
|
35
|
+
const start = bitsAt ?? paletteAt + paletteBytes;
|
|
36
|
+
const stride = (width * bpp + 7 >> 3) + 3 & -4;
|
|
37
|
+
const need = stride * height;
|
|
38
|
+
if (start < 0 || start + Math.min(need, cbBits ?? need) > bytes.length) return void 0;
|
|
39
|
+
const palette = readPalette(v, paletteAt, paletteCount, entry);
|
|
40
|
+
const channels = masksAt !== void 0 ? [
|
|
41
|
+
maskChannel(v.getUint32(masksAt, true)),
|
|
42
|
+
maskChannel(v.getUint32(masksAt + 4, true)),
|
|
43
|
+
maskChannel(v.getUint32(masksAt + 8, true))
|
|
44
|
+
] : void 0;
|
|
45
|
+
const rgba = new Uint8Array(width * height * 4);
|
|
46
|
+
for (let y = 0; y < height; y++) {
|
|
47
|
+
const row = start + (topDown ? y : height - 1 - y) * stride;
|
|
48
|
+
let dst = y * width * 4;
|
|
49
|
+
for (let x = 0; x < width; x++) {
|
|
50
|
+
let r = 0;
|
|
51
|
+
let g = 0;
|
|
52
|
+
let b = 0;
|
|
53
|
+
let a = 255;
|
|
54
|
+
if (bpp <= 8) {
|
|
55
|
+
const bitsPer = bpp;
|
|
56
|
+
const bit = x * bitsPer;
|
|
57
|
+
const rgb = palette[(bytes[row + (bit >> 3)] ?? 0) >> 8 - bitsPer - (bit & 7) & (1 << bitsPer) - 1] ?? 0;
|
|
58
|
+
r = rgb >>> 16 & 255;
|
|
59
|
+
g = rgb >>> 8 & 255;
|
|
60
|
+
b = rgb & 255;
|
|
61
|
+
} else if (bpp === 16) {
|
|
62
|
+
const word = (bytes[row + x * 2] ?? 0) | (bytes[row + x * 2 + 1] ?? 0) << 8;
|
|
63
|
+
if (channels) [r, g, b] = [
|
|
64
|
+
channels[0](word),
|
|
65
|
+
channels[1](word),
|
|
66
|
+
channels[2](word)
|
|
67
|
+
];
|
|
68
|
+
else {
|
|
69
|
+
r = expand5(word >> 10 & 31);
|
|
70
|
+
g = expand5(word >> 5 & 31);
|
|
71
|
+
b = expand5(word & 31);
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
const at = row + x * (bpp >> 3);
|
|
75
|
+
b = bytes[at] ?? 0;
|
|
76
|
+
g = bytes[at + 1] ?? 0;
|
|
77
|
+
r = bytes[at + 2] ?? 0;
|
|
78
|
+
if (bpp === 32) {
|
|
79
|
+
const word = (b | g << 8 | r << 16 | (bytes[at + 3] ?? 0) << 24) >>> 0 >>> 0;
|
|
80
|
+
if (channels) [r, g, b] = [
|
|
81
|
+
channels[0](word),
|
|
82
|
+
channels[1](word),
|
|
83
|
+
channels[2](word)
|
|
84
|
+
];
|
|
85
|
+
if (o.alpha === true) {
|
|
86
|
+
a = bytes[at + 3] ?? 0;
|
|
87
|
+
if (a > 0 && a < 255) {
|
|
88
|
+
r = Math.min(255, Math.round(r * 255 / a));
|
|
89
|
+
g = Math.min(255, Math.round(g * 255 / a));
|
|
90
|
+
b = Math.min(255, Math.round(b * 255 / a));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
rgba[dst] = r;
|
|
96
|
+
rgba[dst + 1] = g;
|
|
97
|
+
rgba[dst + 2] = b;
|
|
98
|
+
rgba[dst + 3] = a;
|
|
99
|
+
dst += 4;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
width,
|
|
104
|
+
height,
|
|
105
|
+
rgba,
|
|
106
|
+
...topDown ? {} : { bottomUp: true }
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The colour bitmap seen through a monochrome one — the two-blit idiom every
|
|
111
|
+
* clipart of this age is drawn with: an AND of the mask, which knocks the
|
|
112
|
+
* picture's ground to white, and then an OR of the picture, which is black
|
|
113
|
+
* exactly where the ground was. Kept apart they draw a black box; put together
|
|
114
|
+
* they are one bitmap with an alpha channel.
|
|
115
|
+
*
|
|
116
|
+
* @param color The bitmap the second blit paints.
|
|
117
|
+
* @param mask The first blit's mask: WHITE where the picture is to show
|
|
118
|
+
* through, black where it is opaque.
|
|
119
|
+
*/
|
|
120
|
+
function maskedDib(color, mask) {
|
|
121
|
+
const rgba = new Uint8Array(color.rgba);
|
|
122
|
+
for (let y = 0; y < color.height; y++) {
|
|
123
|
+
const my = Math.min(mask.height - 1, Math.floor(y * mask.height / color.height));
|
|
124
|
+
for (let x = 0; x < color.width; x++) {
|
|
125
|
+
const mx = Math.min(mask.width - 1, Math.floor(x * mask.width / color.width));
|
|
126
|
+
const m = my * mask.width * 4 + mx * 4;
|
|
127
|
+
if ((mask.rgba[m] + mask.rgba[m + 1] + mask.rgba[m + 2]) / 3 >= 128) rgba[(y * color.width + x) * 4 + 3] = 0;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
width: color.width,
|
|
132
|
+
height: color.height,
|
|
133
|
+
rgba
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The bitmap as a PNG file, ready for the resource store. An opaque one is
|
|
138
|
+
* written without an alpha channel — a quarter less to deflate, and the
|
|
139
|
+
* writers that flatten transparency then have nothing to do.
|
|
140
|
+
*/
|
|
141
|
+
function dibToPng(img) {
|
|
142
|
+
let opaque = true;
|
|
143
|
+
for (let i = 3; i < img.rgba.length; i += 4) if (img.rgba[i] !== 255) {
|
|
144
|
+
opaque = false;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
if (!opaque) return encodePng(img.width, img.height, "rgba", img.rgba);
|
|
148
|
+
const rgb = new Uint8Array(img.width * img.height * 3);
|
|
149
|
+
for (let i = 0, j = 0; i < img.rgba.length; i += 4, j += 3) {
|
|
150
|
+
rgb[j] = img.rgba[i];
|
|
151
|
+
rgb[j + 1] = img.rgba[i + 1];
|
|
152
|
+
rgb[j + 2] = img.rgba[i + 2];
|
|
153
|
+
}
|
|
154
|
+
return encodePng(img.width, img.height, "rgb", rgb);
|
|
155
|
+
}
|
|
156
|
+
/** The bitmap at a constant transparency — what a blend function's `SrcConstantAlpha` asks for. */
|
|
157
|
+
function fadedDib(img, alpha) {
|
|
158
|
+
const rgba = new Uint8Array(img.rgba);
|
|
159
|
+
for (let i = 3; i < rgba.length; i += 4) rgba[i] = Math.round(rgba[i] * alpha);
|
|
160
|
+
return {
|
|
161
|
+
width: img.width,
|
|
162
|
+
height: img.height,
|
|
163
|
+
rgba
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/** The part of the bitmap a blit's source rectangle names, or all of it. */
|
|
167
|
+
function cropDib(img, x, y, w, h) {
|
|
168
|
+
const left = Math.max(0, Math.min(img.width - 1, x));
|
|
169
|
+
const top = Math.max(0, Math.min(img.height - 1, y));
|
|
170
|
+
const width = Math.max(1, Math.min(img.width - left, w));
|
|
171
|
+
const height = Math.max(1, Math.min(img.height - top, h));
|
|
172
|
+
if (left === 0 && top === 0 && width === img.width && height === img.height) return img;
|
|
173
|
+
const rgba = new Uint8Array(width * height * 4);
|
|
174
|
+
for (let row = 0; row < height; row++) {
|
|
175
|
+
const src = ((top + row) * img.width + left) * 4;
|
|
176
|
+
rgba.set(img.rgba.subarray(src, src + width * 4), row * width * 4);
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
width,
|
|
180
|
+
height,
|
|
181
|
+
rgba
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function readPalette(v, at, count, entry) {
|
|
185
|
+
const out = new Uint32Array(Math.max(count, 2));
|
|
186
|
+
out[1] = 16777215;
|
|
187
|
+
for (let i = 0; i < count; i++) {
|
|
188
|
+
const o = at + i * entry;
|
|
189
|
+
if (o + 3 > v.byteLength) break;
|
|
190
|
+
out[i] = v.getUint8(o + 2) << 16 | v.getUint8(o + 1) << 8 | v.getUint8(o);
|
|
191
|
+
}
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
194
|
+
function maskChannel(mask) {
|
|
195
|
+
if (mask === 0) return () => 0;
|
|
196
|
+
let shift = 0;
|
|
197
|
+
while ((mask >>> shift & 1) === 0) shift++;
|
|
198
|
+
const span = (mask >>> shift) + 1;
|
|
199
|
+
return (word) => Math.round(((word & mask) >>> shift) * 255 / (span - 1));
|
|
200
|
+
}
|
|
201
|
+
function expand5(v) {
|
|
202
|
+
return v << 3 | v >> 2;
|
|
203
|
+
}
|
|
204
|
+
//#endregion
|
|
205
|
+
export { cropDib, dibToPng, fadedDib, maskedDib, readDib };
|