reamkit 1.15.4 → 1.17.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/bidi/segments.js +1 -2
- package/dist/esm/core/converter/facade.d.ts +5 -0
- package/dist/esm/core/converter/project.js +5 -1
- package/dist/esm/core/converter/ream.d.ts +6 -0
- package/dist/esm/core/converter/ream.js +34 -8
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +659 -7
- package/dist/esm/core/drawingml/chart-geometry.d.ts +34 -10
- package/dist/esm/core/drawingml/chart-geometry.js +424 -92
- package/dist/esm/core/drawingml/chart-parser.js +301 -11
- package/dist/esm/core/drawingml/colors.d.ts +7 -7
- package/dist/esm/core/drawingml/colors.js +84 -18
- package/dist/esm/core/drawingml/preset-geometry.js +240 -6
- package/dist/esm/core/drawingml/shape-render.d.ts +12 -0
- package/dist/esm/core/drawingml/shape-render.js +116 -3
- package/dist/esm/core/drawingml/sparkline-geometry.js +6 -4
- package/dist/esm/core/drawingml/theme-parser.d.ts +51 -0
- package/dist/esm/core/drawingml/theme-parser.js +92 -1
- package/dist/esm/core/font/measure.d.ts +5 -0
- package/dist/esm/core/font/measure.js +21 -1
- package/dist/esm/core/font/ttf-subset.js +26 -1
- package/dist/esm/core/images.d.ts +2 -2
- package/dist/esm/core/images.js +353 -43
- package/dist/esm/core/indexed-colors.d.ts +11 -0
- package/dist/esm/core/indexed-colors.js +81 -0
- package/dist/esm/core/ir/flow.d.ts +23 -1
- package/dist/esm/core/ir/sheet.d.ts +61 -0
- package/dist/esm/{excel → core}/number-format.d.ts +37 -0
- package/dist/esm/core/number-format.js +800 -0
- package/dist/esm/core/numbering/apply.d.ts +5 -3
- package/dist/esm/core/numbering/apply.js +42 -11
- package/dist/esm/core/numbering/index.js +2 -0
- package/dist/esm/core/numbering/state.d.ts +13 -1
- package/dist/esm/core/numbering/state.js +256 -15
- package/dist/esm/core/opc/alternate-content.d.ts +13 -0
- package/dist/esm/core/opc/alternate-content.js +72 -0
- package/dist/esm/core/opc/core-properties.js +1 -0
- package/dist/esm/core/opc/package.d.ts +14 -1
- package/dist/esm/core/opc/package.js +43 -4
- package/dist/esm/core/opc/relationships.js +1 -0
- package/dist/esm/core/opc/xml-entities.d.ts +12 -0
- package/dist/esm/core/opc/xml-entities.js +91 -0
- package/dist/esm/core/po-helpers.js +16 -1
- package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
- package/dist/esm/core/spreadsheet-model/types.d.ts +137 -0
- package/dist/esm/core/style-cascade/resolver.js +66 -11
- package/dist/esm/core/style-cascade/table.js +35 -11
- package/dist/esm/core/style-cascade/types.d.ts +42 -1
- package/dist/esm/core/style-cascade/types.js +4 -0
- package/dist/esm/core/vector.d.ts +30 -0
- package/dist/esm/excel/activex-parser.js +1 -0
- package/dist/esm/excel/column-bands.d.ts +26 -1
- package/dist/esm/excel/column-bands.js +156 -20
- package/dist/esm/excel/comments-parser.js +5 -3
- package/dist/esm/excel/conditional-format.d.ts +25 -2
- package/dist/esm/excel/conditional-format.js +63 -19
- package/dist/esm/excel/escaped-text.d.ts +9 -0
- package/dist/esm/excel/escaped-text.js +20 -0
- package/dist/esm/excel/form-control-parser.js +1 -0
- package/dist/esm/excel/formula/dates.js +1 -1
- package/dist/esm/excel/header-footer.d.ts +9 -1
- package/dist/esm/excel/header-footer.js +162 -14
- package/dist/esm/excel/index.d.ts +1 -1
- package/dist/esm/excel/index.js +2 -2
- package/dist/esm/excel/pivot-table-parser.js +1 -0
- package/dist/esm/excel/print-model.d.ts +75 -0
- package/dist/esm/excel/print-model.js +899 -77
- package/dist/esm/excel/printer-settings.d.ts +16 -0
- package/dist/esm/excel/printer-settings.js +36 -0
- package/dist/esm/excel/rich-value.d.ts +13 -0
- package/dist/esm/excel/rich-value.js +120 -0
- package/dist/esm/excel/shared-strings-parser.js +11 -7
- package/dist/esm/excel/sheet-drawing.d.ts +11 -0
- package/dist/esm/excel/sheet-drawing.js +26 -3
- package/dist/esm/excel/sheet-shape-parser.d.ts +53 -1
- package/dist/esm/excel/sheet-shape-parser.js +461 -32
- package/dist/esm/excel/sheet-to-flow.d.ts +23 -2
- package/dist/esm/excel/sheet-to-flow.js +553 -36
- package/dist/esm/excel/slicer-parser.js +1 -0
- package/dist/esm/excel/styles-parser.d.ts +30 -2
- package/dist/esm/excel/styles-parser.js +177 -40
- package/dist/esm/excel/table-parser.js +1 -0
- package/dist/esm/excel/vml-drawing.d.ts +68 -0
- package/dist/esm/excel/vml-drawing.js +158 -0
- package/dist/esm/excel/workbook-parser.d.ts +5 -0
- package/dist/esm/excel/workbook-parser.js +5 -1
- package/dist/esm/excel/worksheet-parser.d.ts +2 -1
- package/dist/esm/excel/worksheet-parser.js +150 -61
- package/dist/esm/excel/xlsx-reader.js +242 -11
- package/dist/esm/excel/xlsx-to-pdf.js +12 -1
- package/dist/esm/html/html-writer.js +29 -6
- package/dist/esm/layout/page-doc.d.ts +87 -1
- package/dist/esm/layout/styled-layout.d.ts +46 -2
- package/dist/esm/layout/styled-layout.js +2345 -274
- package/dist/esm/pdf/cid-font.js +20 -9
- package/dist/esm/pdf/objects.d.ts +13 -0
- package/dist/esm/pdf/objects.js +18 -1
- package/dist/esm/pdf/shading.js +76 -6
- package/dist/esm/pdf/styled-page-emitter.js +355 -49
- package/dist/esm/pdf/text-page-renderer.js +1 -0
- package/dist/esm/pdf/vector-graphics.d.ts +31 -2
- package/dist/esm/pdf/vector-graphics.js +103 -3
- package/dist/esm/pptx/pptx-reader.js +2 -0
- package/dist/esm/pptx/slide-parser.d.ts +21 -4
- package/dist/esm/pptx/slide-parser.js +98 -24
- package/dist/esm/svg/svg-writer.js +23 -4
- package/dist/esm/word/document-parser.d.ts +96 -7
- package/dist/esm/word/document-parser.js +538 -116
- package/dist/esm/word/docx-reader.js +135 -43
- package/dist/esm/word/docx-writer.js +6 -1
- package/dist/esm/word/drawing-parser.d.ts +86 -5
- package/dist/esm/word/drawing-parser.js +1590 -54
- package/dist/esm/word/index.d.ts +4 -4
- package/dist/esm/word/numbering-parser.d.ts +2 -1
- package/dist/esm/word/numbering-parser.js +108 -21
- package/dist/esm/word/paragraph-properties.d.ts +11 -1
- package/dist/esm/word/paragraph-properties.js +219 -10
- package/dist/esm/word/run-properties.js +45 -1
- package/dist/esm/word/settings-parser.d.ts +24 -0
- package/dist/esm/word/settings-parser.js +32 -5
- package/dist/esm/word/shading.d.ts +11 -0
- package/dist/esm/word/shading.js +64 -0
- package/dist/esm/word/styles-parser.js +9 -2
- package/dist/esm/word/table-parser.d.ts +9 -1
- package/dist/esm/word/table-parser.js +182 -30
- package/package.json +7 -3
- package/dist/esm/excel/number-format.js +0 -476
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
var HEAD_CHECKSUM_ADJUSTMENT_OFFSET = 8;
|
|
3
3
|
var HEAD_INDEX_TO_LOC_FORMAT_OFFSET = 50;
|
|
4
4
|
var MAGIC_HEAD_CHECKSUM = 2981146554;
|
|
5
|
+
/**
|
|
6
|
+
* The tables a `/FontFile2` may carry (ISO 32000-1 §9.9.2).
|
|
7
|
+
*
|
|
8
|
+
* The spec names `glyf`, `head`, `hhea`, `hmtx`, `loca` and `maxp` as required,
|
|
9
|
+
* `cvt `/`fpgm`/`prep` when the glyphs are hinted, and `cmap` for a SYMBOLIC
|
|
10
|
+
* font. A CIDFontType2 with `/CIDToGIDMap /Identity` is not symbolic in that
|
|
11
|
+
* sense — §9.7.4.2 makes the CID the glyph index and never consults `cmap`, so
|
|
12
|
+
* 9.3 KB of it rode along in every font we embedded. Everything else is dead
|
|
13
|
+
* weight too, in a PDF that does its own positioning and text extraction: we copied `post`, `GPOS`, `GSUB`, `GDEF`, `name`,
|
|
14
|
+
* `hdmx`, `LTSH` and the rest through untouched, and they were 120 KB of the
|
|
15
|
+
* 136 KB font program in 55906-MultiSheetRefs.xlsx. `OS/2` is kept because
|
|
16
|
+
* viewers read its metrics when a descriptor is incomplete.
|
|
17
|
+
*/
|
|
18
|
+
var EMBEDDABLE_TABLES = new Set([
|
|
19
|
+
"glyf",
|
|
20
|
+
"head",
|
|
21
|
+
"hhea",
|
|
22
|
+
"hmtx",
|
|
23
|
+
"loca",
|
|
24
|
+
"maxp",
|
|
25
|
+
"cvt ",
|
|
26
|
+
"fpgm",
|
|
27
|
+
"prep",
|
|
28
|
+
"OS/2"
|
|
29
|
+
]);
|
|
5
30
|
var COMPOSITE_ARG_1_AND_2_ARE_WORDS = 1;
|
|
6
31
|
var COMPOSITE_WE_HAVE_A_SCALE = 8;
|
|
7
32
|
var COMPOSITE_MORE_COMPONENTS = 32;
|
|
@@ -115,7 +140,7 @@ function assembleSubsetTtf(parsed, newGlyf, newLoca) {
|
|
|
115
140
|
newHead[HEAD_CHECKSUM_ADJUSTMENT_OFFSET + 3] = 0;
|
|
116
141
|
newHead[HEAD_INDEX_TO_LOC_FORMAT_OFFSET] = 0;
|
|
117
142
|
newHead[HEAD_INDEX_TO_LOC_FORMAT_OFFSET + 1] = 1;
|
|
118
|
-
const orderedTags = [...parsed.tables.keys()];
|
|
143
|
+
const orderedTags = [...parsed.tables.keys()].filter((t) => EMBEDDABLE_TABLES.has(t));
|
|
119
144
|
const tableData = /* @__PURE__ */ new Map();
|
|
120
145
|
for (const tag of orderedTags) if (tag === "glyf") tableData.set(tag, newGlyf);
|
|
121
146
|
else if (tag === "loca") tableData.set(tag, newLocaBytes);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** The raster formats this module recognizes and can prepare for embedding. */
|
|
2
|
-
export type ImageFormat = 'jpeg' | 'png' | 'jpeg2000';
|
|
2
|
+
export type ImageFormat = 'jpeg' | 'png' | 'jpeg2000' | 'gif';
|
|
3
3
|
/**
|
|
4
4
|
* Sniff the raster format from a file's leading magic bytes (JPEG SOI, the PNG
|
|
5
5
|
* signature, or a JP2 box / raw JPEG 2000 codestream). Returns the
|
|
@@ -24,7 +24,7 @@ export interface EmbedImageOptions {
|
|
|
24
24
|
*/
|
|
25
25
|
export interface PreparedImage {
|
|
26
26
|
readonly format: ImageFormat;
|
|
27
|
-
readonly mimeType: 'image/jp2' | 'image/jpeg' | 'image/png';
|
|
27
|
+
readonly mimeType: 'image/jp2' | 'image/jpeg' | 'image/png' | 'image/gif';
|
|
28
28
|
readonly widthPx: number;
|
|
29
29
|
readonly heightPx: number;
|
|
30
30
|
/**
|
package/dist/esm/core/images.js
CHANGED
|
@@ -10,6 +10,7 @@ function detectImageFormat(bytes) {
|
|
|
10
10
|
if (bytes.length >= 8 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71 && bytes[4] === 13 && bytes[5] === 10 && bytes[6] === 26 && bytes[7] === 10) return "png";
|
|
11
11
|
if (bytes.length >= 12 && bytes[0] === 0 && bytes[1] === 0 && bytes[2] === 0 && bytes[3] === 12 && bytes[4] === 106 && bytes[5] === 80 && bytes[6] === 32 && bytes[7] === 32 && bytes[8] === 13 && bytes[9] === 10 && bytes[10] === 135 && bytes[11] === 10) return "jpeg2000";
|
|
12
12
|
if (bytes.length >= 4 && bytes[0] === 255 && bytes[1] === 79 && bytes[2] === 255 && bytes[3] === 81) return "jpeg2000";
|
|
13
|
+
if (bytes.length >= 6 && bytes[0] === 71 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 56 && (bytes[4] === 55 || bytes[4] === 57) && bytes[5] === 97) return "gif";
|
|
13
14
|
return null;
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
@@ -24,6 +25,7 @@ function prepareImage(bytes, options = {}) {
|
|
|
24
25
|
if (format === "jpeg") return prepareJpeg(bytes);
|
|
25
26
|
if (format === "png") return preparePng(bytes, options);
|
|
26
27
|
if (format === "jpeg2000") return prepareJpeg2000(bytes);
|
|
28
|
+
if (format === "gif") return prepareGif(bytes, options);
|
|
27
29
|
throw new Error("Unsupported image format");
|
|
28
30
|
}
|
|
29
31
|
function prepareJpeg2000(bytes) {
|
|
@@ -125,6 +127,185 @@ function readJpegInfo(bytes) {
|
|
|
125
127
|
}
|
|
126
128
|
throw new Error("JPEG SOFn marker not found");
|
|
127
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Decode a GIF (87a/89a) into a {@link PreparedImage}: the first frame's
|
|
132
|
+
* palette indices expanded to RGB and re-compressed, with a transparent index
|
|
133
|
+
* becoming a soft mask. PDF has no GIF filter, so the pixels have to be handed
|
|
134
|
+
* over as raw ones — dml-picture-in-textframe.docx (and seventeen other corpus
|
|
135
|
+
* documents) draw nothing at all without this.
|
|
136
|
+
*
|
|
137
|
+
* @param bytes The GIF file.
|
|
138
|
+
* @param options Emit options (PDF/A-1 forbids the soft mask).
|
|
139
|
+
* @returns The prepared image.
|
|
140
|
+
* @throws Error when the file is truncated or holds no image.
|
|
141
|
+
*/
|
|
142
|
+
function prepareGif(bytes, options = {}) {
|
|
143
|
+
const frame = decodeGif(bytes);
|
|
144
|
+
const rgb = new Uint8Array(frame.width * frame.height * 3);
|
|
145
|
+
const alpha = frame.transparentIndex !== void 0 ? new Uint8Array(frame.width * frame.height) : void 0;
|
|
146
|
+
for (let i = 0; i < frame.indices.length; i++) {
|
|
147
|
+
const idx = frame.indices[i];
|
|
148
|
+
const p = idx * 3;
|
|
149
|
+
rgb[i * 3] = frame.palette[p] ?? 0;
|
|
150
|
+
rgb[i * 3 + 1] = frame.palette[p + 1] ?? 0;
|
|
151
|
+
rgb[i * 3 + 2] = frame.palette[p + 2] ?? 0;
|
|
152
|
+
if (alpha) alpha[i] = idx === frame.transparentIndex ? 0 : 255;
|
|
153
|
+
}
|
|
154
|
+
if (alpha && options.flattenAlpha) {
|
|
155
|
+
for (let i = 0; i < alpha.length; i++) if (alpha[i] === 0) {
|
|
156
|
+
rgb[i * 3] = 255;
|
|
157
|
+
rgb[i * 3 + 1] = 255;
|
|
158
|
+
rgb[i * 3 + 2] = 255;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
format: "gif",
|
|
163
|
+
mimeType: "image/gif",
|
|
164
|
+
widthPx: frame.width,
|
|
165
|
+
heightPx: frame.height,
|
|
166
|
+
colorSpace: "DeviceRGB",
|
|
167
|
+
bitsPerComponent: 8,
|
|
168
|
+
filter: "FlateDecode",
|
|
169
|
+
data: zlibSync(rgb),
|
|
170
|
+
...alpha && !options.flattenAlpha ? { smaskData: zlibSync(alpha) } : {}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function decodeGif(bytes) {
|
|
174
|
+
const u16 = (i) => (bytes[i] ?? 0) | (bytes[i + 1] ?? 0) << 8;
|
|
175
|
+
const screenW = u16(6);
|
|
176
|
+
const screenH = u16(8);
|
|
177
|
+
const packed = bytes[10] ?? 0;
|
|
178
|
+
let pos = 13;
|
|
179
|
+
let globalPalette = new Uint8Array(0);
|
|
180
|
+
if ((packed & 128) !== 0) {
|
|
181
|
+
const size = 3 * (1 << (packed & 7) + 1);
|
|
182
|
+
globalPalette = bytes.subarray(pos, pos + size);
|
|
183
|
+
pos += size;
|
|
184
|
+
}
|
|
185
|
+
let transparentIndex;
|
|
186
|
+
while (pos < bytes.length) {
|
|
187
|
+
const block = bytes[pos];
|
|
188
|
+
if (block === 33) {
|
|
189
|
+
const label = bytes[pos + 1];
|
|
190
|
+
pos += 2;
|
|
191
|
+
if (label === 249 && (bytes[pos + 1] & 1) !== 0) transparentIndex = bytes[pos + 4];
|
|
192
|
+
while (pos < bytes.length && bytes[pos] !== 0) pos += bytes[pos] + 1;
|
|
193
|
+
pos++;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (block !== 44) break;
|
|
197
|
+
const width = u16(pos + 5) || screenW;
|
|
198
|
+
const height = u16(pos + 7) || screenH;
|
|
199
|
+
const flags = bytes[pos + 9];
|
|
200
|
+
pos += 10;
|
|
201
|
+
let palette = globalPalette;
|
|
202
|
+
if ((flags & 128) !== 0) {
|
|
203
|
+
const size = 3 * (1 << (flags & 7) + 1);
|
|
204
|
+
palette = bytes.subarray(pos, pos + size);
|
|
205
|
+
pos += size;
|
|
206
|
+
}
|
|
207
|
+
const minCodeSize = bytes[pos];
|
|
208
|
+
pos++;
|
|
209
|
+
const chunks = [];
|
|
210
|
+
while (pos < bytes.length && bytes[pos] !== 0) {
|
|
211
|
+
const len = bytes[pos];
|
|
212
|
+
chunks.push(bytes.subarray(pos + 1, pos + 1 + len));
|
|
213
|
+
pos += len + 1;
|
|
214
|
+
}
|
|
215
|
+
const indices = lzwDecode(concatBytes(chunks), minCodeSize, width * height);
|
|
216
|
+
return {
|
|
217
|
+
width,
|
|
218
|
+
height,
|
|
219
|
+
indices: (flags & 64) !== 0 ? deinterlace(indices, width, height) : indices,
|
|
220
|
+
palette,
|
|
221
|
+
...transparentIndex !== void 0 ? { transparentIndex } : {}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
throw new Error("GIF holds no image");
|
|
225
|
+
}
|
|
226
|
+
function lzwDecode(data, minCodeSize, pixelCount) {
|
|
227
|
+
const clear = 1 << minCodeSize;
|
|
228
|
+
const end = clear + 1;
|
|
229
|
+
const out = new Uint8Array(pixelCount);
|
|
230
|
+
let outPos = 0;
|
|
231
|
+
let prefix = new Int32Array(4096);
|
|
232
|
+
let suffix = new Uint8Array(4096);
|
|
233
|
+
let length = new Int32Array(4096);
|
|
234
|
+
const reset = () => {
|
|
235
|
+
prefix = new Int32Array(4096);
|
|
236
|
+
suffix = new Uint8Array(4096);
|
|
237
|
+
length = new Int32Array(4096);
|
|
238
|
+
for (let i = 0; i < clear; i++) {
|
|
239
|
+
prefix[i] = -1;
|
|
240
|
+
suffix[i] = i;
|
|
241
|
+
length[i] = 1;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
reset();
|
|
245
|
+
let next = end + 1;
|
|
246
|
+
let codeSize = minCodeSize + 1;
|
|
247
|
+
let prev = -1;
|
|
248
|
+
let bitBuf = 0;
|
|
249
|
+
let bitCount = 0;
|
|
250
|
+
const stack = new Uint8Array(4096);
|
|
251
|
+
for (let i = 0; i < data.length || bitCount >= codeSize;) {
|
|
252
|
+
while (bitCount < codeSize && i < data.length) {
|
|
253
|
+
bitBuf |= (data[i] & 255) << bitCount;
|
|
254
|
+
bitCount += 8;
|
|
255
|
+
i++;
|
|
256
|
+
}
|
|
257
|
+
if (bitCount < codeSize) break;
|
|
258
|
+
const code = bitBuf & (1 << codeSize) - 1;
|
|
259
|
+
bitBuf >>>= codeSize;
|
|
260
|
+
bitCount -= codeSize;
|
|
261
|
+
if (code === clear) {
|
|
262
|
+
reset();
|
|
263
|
+
next = end + 1;
|
|
264
|
+
codeSize = minCodeSize + 1;
|
|
265
|
+
prev = -1;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (code === end) break;
|
|
269
|
+
let cur = code;
|
|
270
|
+
if (code >= next) {
|
|
271
|
+
if (prev < 0) break;
|
|
272
|
+
cur = prev;
|
|
273
|
+
}
|
|
274
|
+
let depth = 0;
|
|
275
|
+
while (cur >= 0 && depth < 4096) {
|
|
276
|
+
stack[depth] = suffix[cur];
|
|
277
|
+
depth++;
|
|
278
|
+
cur = prefix[cur];
|
|
279
|
+
}
|
|
280
|
+
if (code >= next) stack[depth++] = stack[depth - 2] ?? 0;
|
|
281
|
+
for (let d = depth - 1; d >= 0 && outPos < out.length; d--) out[outPos++] = stack[d];
|
|
282
|
+
if (prev >= 0 && next < 4096) {
|
|
283
|
+
prefix[next] = prev;
|
|
284
|
+
suffix[next] = stack[depth - 1];
|
|
285
|
+
length[next] = (length[prev] ?? 1) + 1;
|
|
286
|
+
next++;
|
|
287
|
+
if (next === 1 << codeSize && codeSize < 12) codeSize++;
|
|
288
|
+
}
|
|
289
|
+
prev = code < next ? code : prev;
|
|
290
|
+
if (outPos >= out.length) break;
|
|
291
|
+
}
|
|
292
|
+
return out;
|
|
293
|
+
}
|
|
294
|
+
function deinterlace(src, width, height) {
|
|
295
|
+
const out = new Uint8Array(src.length);
|
|
296
|
+
const passes = [
|
|
297
|
+
[0, 8],
|
|
298
|
+
[4, 8],
|
|
299
|
+
[2, 4],
|
|
300
|
+
[1, 2]
|
|
301
|
+
];
|
|
302
|
+
let row = 0;
|
|
303
|
+
for (const [start, step] of passes) for (let y = start; y < height; y += step) {
|
|
304
|
+
out.set(src.subarray(row * width, (row + 1) * width), y * width);
|
|
305
|
+
row++;
|
|
306
|
+
}
|
|
307
|
+
return out;
|
|
308
|
+
}
|
|
128
309
|
function preparePng(bytes, options = {}) {
|
|
129
310
|
let decoded = decodePng(bytes);
|
|
130
311
|
if (options.flattenAlpha && decoded.smaskRaw) decoded = flattenAlphaOnWhite(decoded);
|
|
@@ -146,6 +327,8 @@ function decodePng(bytes) {
|
|
|
146
327
|
let bitDepth = 0;
|
|
147
328
|
let colorType = 0;
|
|
148
329
|
let interlaceMethod = 0;
|
|
330
|
+
let palette;
|
|
331
|
+
let paletteAlpha;
|
|
149
332
|
const idatChunks = [];
|
|
150
333
|
let pos = 8;
|
|
151
334
|
while (pos + 12 <= bytes.length) {
|
|
@@ -158,57 +341,161 @@ function decodePng(bytes) {
|
|
|
158
341
|
bitDepth = bytes[dataOff + 8];
|
|
159
342
|
colorType = bytes[dataOff + 9];
|
|
160
343
|
interlaceMethod = bytes[dataOff + 12];
|
|
161
|
-
} else if (type === "
|
|
344
|
+
} else if (type === "PLTE") palette = bytes.subarray(dataOff, dataOff + len);
|
|
345
|
+
else if (type === "tRNS") paletteAlpha = bytes.subarray(dataOff, dataOff + len);
|
|
346
|
+
else if (type === "IDAT") idatChunks.push(bytes.subarray(dataOff, dataOff + len));
|
|
162
347
|
else if (type === "IEND") break;
|
|
163
348
|
pos = dataOff + len + 4;
|
|
164
349
|
}
|
|
165
350
|
if (width === 0 || height === 0) throw new Error("PNG: missing IHDR");
|
|
166
351
|
if (idatChunks.length === 0) throw new Error("PNG: no IDAT");
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
352
|
+
if (![
|
|
353
|
+
1,
|
|
354
|
+
2,
|
|
355
|
+
4,
|
|
356
|
+
8,
|
|
357
|
+
16
|
|
358
|
+
].includes(bitDepth)) throw new Error(`PNG: bit depth ${bitDepth} not supported`);
|
|
170
359
|
const channels = pngChannels(colorType);
|
|
171
360
|
if (channels === 0) throw new Error(`PNG color type ${colorType} not supported`);
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
361
|
+
const raw = decodeSamples(unzlibSync(concatBytes(idatChunks)), width, height, channels, bitDepth, interlaceMethod === 1, colorType !== 3);
|
|
362
|
+
return splitChannels(width, height, colorType, raw, palette, paletteAlpha);
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* §7.2 / §9 — the image's samples, one byte each, in `width × height ×
|
|
366
|
+
* channels` order: the IDAT stream inflated, each scanline unfiltered against
|
|
367
|
+
* the one before it, and (when the image is interlaced) each of Adam7's seven
|
|
368
|
+
* passes scattered back into the full-size grid.
|
|
369
|
+
*
|
|
370
|
+
* @param inflated The inflated IDAT stream.
|
|
371
|
+
* @param width Image width in pixels.
|
|
372
|
+
* @param height Image height in pixels.
|
|
373
|
+
* @param channels Samples per pixel for the colour type.
|
|
374
|
+
* @param bitDepth Bits per sample (1, 2, 4, 8 or 16).
|
|
375
|
+
* @param interlaced True for interlace method 1 (Adam7).
|
|
376
|
+
* @param scaleToByte Stretch a sub-byte sample to 0…255 — right for an
|
|
377
|
+
* intensity, wrong for a palette index.
|
|
378
|
+
* @returns One byte per sample.
|
|
379
|
+
*/
|
|
380
|
+
function decodeSamples(inflated, width, height, channels, bitDepth, interlaced, scaleToByte) {
|
|
381
|
+
const out = new Uint8Array(width * height * channels);
|
|
382
|
+
const passes = interlaced ? ADAM7_PASSES : [{
|
|
383
|
+
xStart: 0,
|
|
384
|
+
yStart: 0,
|
|
385
|
+
xStep: 1,
|
|
386
|
+
yStep: 1
|
|
387
|
+
}];
|
|
388
|
+
const maxSample = (1 << Math.min(bitDepth, 8)) - 1;
|
|
389
|
+
const bpp = Math.max(1, Math.ceil(channels * bitDepth / 8));
|
|
390
|
+
let off = 0;
|
|
391
|
+
for (const p of passes) {
|
|
392
|
+
const pw = Math.ceil((width - p.xStart) / p.xStep);
|
|
393
|
+
const ph = Math.ceil((height - p.yStart) / p.yStep);
|
|
394
|
+
if (pw <= 0 || ph <= 0) continue;
|
|
395
|
+
const lineBytes = Math.ceil(pw * channels * bitDepth / 8);
|
|
396
|
+
let prev = null;
|
|
397
|
+
for (let y = 0; y < ph; y++) {
|
|
398
|
+
if (off + 1 + lineBytes > inflated.length) throw new Error("PNG: truncated image data");
|
|
399
|
+
const filterType = inflated[off];
|
|
400
|
+
const line = unfilterScanline(inflated, off + 1, lineBytes, bpp, filterType, prev);
|
|
401
|
+
off += 1 + lineBytes;
|
|
402
|
+
const destY = p.yStart + y * p.yStep;
|
|
403
|
+
for (let px = 0; px < pw; px++) {
|
|
404
|
+
const destX = p.xStart + px * p.xStep;
|
|
405
|
+
const dest = (destY * width + destX) * channels;
|
|
406
|
+
for (let ch = 0; ch < channels; ch++) {
|
|
407
|
+
const v = sampleAt(line, px * channels + ch, bitDepth);
|
|
408
|
+
out[dest + ch] = scaleToByte && bitDepth < 8 ? Math.round(v * 255 / maxSample) : v;
|
|
409
|
+
}
|
|
205
410
|
}
|
|
206
|
-
|
|
411
|
+
prev = line;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return out;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* §9.2 — Adam7's seven passes, each a sub-grid of the image: where the pass
|
|
418
|
+
* starts and how far apart its pixels sit.
|
|
419
|
+
*/
|
|
420
|
+
var ADAM7_PASSES = [
|
|
421
|
+
{
|
|
422
|
+
xStart: 0,
|
|
423
|
+
yStart: 0,
|
|
424
|
+
xStep: 8,
|
|
425
|
+
yStep: 8
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
xStart: 4,
|
|
429
|
+
yStart: 0,
|
|
430
|
+
xStep: 8,
|
|
431
|
+
yStep: 8
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
xStart: 0,
|
|
435
|
+
yStart: 4,
|
|
436
|
+
xStep: 4,
|
|
437
|
+
yStep: 8
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
xStart: 2,
|
|
441
|
+
yStart: 0,
|
|
442
|
+
xStep: 4,
|
|
443
|
+
yStep: 4
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
xStart: 0,
|
|
447
|
+
yStart: 2,
|
|
448
|
+
xStep: 2,
|
|
449
|
+
yStep: 4
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
xStart: 1,
|
|
453
|
+
yStart: 0,
|
|
454
|
+
xStep: 2,
|
|
455
|
+
yStep: 2
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
xStart: 0,
|
|
459
|
+
yStart: 1,
|
|
460
|
+
xStep: 1,
|
|
461
|
+
yStep: 2
|
|
462
|
+
}
|
|
463
|
+
];
|
|
464
|
+
function unfilterScanline(src, from, lineBytes, bpp, filterType, prev) {
|
|
465
|
+
const decoded = new Uint8Array(lineBytes);
|
|
466
|
+
for (let x = 0; x < lineBytes; x++) {
|
|
467
|
+
const filt = src[from + x];
|
|
468
|
+
const a = x < bpp ? 0 : decoded[x - bpp];
|
|
469
|
+
const b = prev ? prev[x] : 0;
|
|
470
|
+
const c = !prev || x < bpp ? 0 : prev[x - bpp];
|
|
471
|
+
let v;
|
|
472
|
+
switch (filterType) {
|
|
473
|
+
case 0:
|
|
474
|
+
v = filt;
|
|
475
|
+
break;
|
|
476
|
+
case 1:
|
|
477
|
+
v = filt + a;
|
|
478
|
+
break;
|
|
479
|
+
case 2:
|
|
480
|
+
v = filt + b;
|
|
481
|
+
break;
|
|
482
|
+
case 3:
|
|
483
|
+
v = filt + Math.floor((a + b) / 2);
|
|
484
|
+
break;
|
|
485
|
+
case 4:
|
|
486
|
+
v = filt + paethPredictor(a, b, c);
|
|
487
|
+
break;
|
|
488
|
+
default: throw new Error(`PNG: unknown filter type ${filterType}`);
|
|
207
489
|
}
|
|
208
|
-
|
|
209
|
-
prev = decoded;
|
|
490
|
+
decoded[x] = v & 255;
|
|
210
491
|
}
|
|
211
|
-
return
|
|
492
|
+
return decoded;
|
|
493
|
+
}
|
|
494
|
+
function sampleAt(line, i, bitDepth) {
|
|
495
|
+
if (bitDepth === 8) return line[i];
|
|
496
|
+
if (bitDepth === 16) return line[i * 2];
|
|
497
|
+
const per = 8 / bitDepth;
|
|
498
|
+
return (line[Math.floor(i / per)] ?? 0) >> 8 - bitDepth * (i % per + 1) & (1 << bitDepth) - 1;
|
|
212
499
|
}
|
|
213
500
|
function flattenAlphaOnWhite(decoded) {
|
|
214
501
|
const alpha = decoded.smaskRaw;
|
|
@@ -231,7 +518,7 @@ function flattenAlphaOnWhite(decoded) {
|
|
|
231
518
|
bitsPerComponent: decoded.bitsPerComponent
|
|
232
519
|
};
|
|
233
520
|
}
|
|
234
|
-
function splitChannels(width, height, colorType, raw) {
|
|
521
|
+
function splitChannels(width, height, colorType, raw, palette, paletteAlpha) {
|
|
235
522
|
const pixelCount = width * height;
|
|
236
523
|
if (colorType === 0) return {
|
|
237
524
|
width,
|
|
@@ -240,6 +527,28 @@ function splitChannels(width, height, colorType, raw) {
|
|
|
240
527
|
colorSpace: "DeviceGray",
|
|
241
528
|
bitsPerComponent: 8
|
|
242
529
|
};
|
|
530
|
+
if (colorType === 3) {
|
|
531
|
+
if (!palette || palette.length < 3) throw new Error("PNG: palette image without PLTE");
|
|
532
|
+
const entries = Math.floor(palette.length / 3);
|
|
533
|
+
const rgb = new Uint8Array(pixelCount * 3);
|
|
534
|
+
const alpha = paletteAlpha && paletteAlpha.length > 0 ? new Uint8Array(pixelCount) : void 0;
|
|
535
|
+
for (let i = 0; i < pixelCount; i++) {
|
|
536
|
+
const idx = raw[i];
|
|
537
|
+
const src = (idx < entries ? idx : 0) * 3;
|
|
538
|
+
rgb[i * 3] = palette[src];
|
|
539
|
+
rgb[i * 3 + 1] = palette[src + 1];
|
|
540
|
+
rgb[i * 3 + 2] = palette[src + 2];
|
|
541
|
+
if (alpha) alpha[i] = paletteAlpha[idx] ?? 255;
|
|
542
|
+
}
|
|
543
|
+
return {
|
|
544
|
+
width,
|
|
545
|
+
height,
|
|
546
|
+
raw: rgb,
|
|
547
|
+
colorSpace: "DeviceRGB",
|
|
548
|
+
bitsPerComponent: 8,
|
|
549
|
+
...alpha ? { smaskRaw: alpha } : {}
|
|
550
|
+
};
|
|
551
|
+
}
|
|
243
552
|
if (colorType === 2) return {
|
|
244
553
|
width,
|
|
245
554
|
height,
|
|
@@ -281,11 +590,12 @@ function splitChannels(width, height, colorType, raw) {
|
|
|
281
590
|
smaskRaw: alpha
|
|
282
591
|
};
|
|
283
592
|
}
|
|
284
|
-
throw new Error(`PNG color type ${colorType} not supported
|
|
593
|
+
throw new Error(`PNG color type ${colorType} not supported`);
|
|
285
594
|
}
|
|
286
595
|
function pngChannels(colorType) {
|
|
287
596
|
if (colorType === 0) return 1;
|
|
288
597
|
if (colorType === 2) return 3;
|
|
598
|
+
if (colorType === 3) return 1;
|
|
289
599
|
if (colorType === 4) return 2;
|
|
290
600
|
if (colorType === 6) return 4;
|
|
291
601
|
return 0;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ECMA-376 §18.8.27 — the legacy indexed colour palette a `<color indexed="N">`
|
|
3
|
+
* refers to. It predates `rgb` and theme colours and is still what Excel writes
|
|
4
|
+
* for anything picked from the classic 40-colour dropdown, so a file using it
|
|
5
|
+
* is not unusual: tdf58243.xlsx colours its header cells `indexed="10"`, and
|
|
6
|
+
* ignoring the attribute rendered them black where every other reader shows red.
|
|
7
|
+
*
|
|
8
|
+
* Entries 0-7 are repeated at 8-15 (a quirk of the original palette); 64 and 65
|
|
9
|
+
* are the system foreground and background.
|
|
10
|
+
*/
|
|
11
|
+
export declare const INDEXED_COLORS: ReadonlyArray<string>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
//#region src/core/indexed-colors.ts
|
|
2
|
+
/**
|
|
3
|
+
* ECMA-376 §18.8.27 — the legacy indexed colour palette a `<color indexed="N">`
|
|
4
|
+
* refers to. It predates `rgb` and theme colours and is still what Excel writes
|
|
5
|
+
* for anything picked from the classic 40-colour dropdown, so a file using it
|
|
6
|
+
* is not unusual: tdf58243.xlsx colours its header cells `indexed="10"`, and
|
|
7
|
+
* ignoring the attribute rendered them black where every other reader shows red.
|
|
8
|
+
*
|
|
9
|
+
* Entries 0-7 are repeated at 8-15 (a quirk of the original palette); 64 and 65
|
|
10
|
+
* are the system foreground and background.
|
|
11
|
+
*/
|
|
12
|
+
var INDEXED_COLORS = [
|
|
13
|
+
"000000",
|
|
14
|
+
"FFFFFF",
|
|
15
|
+
"FF0000",
|
|
16
|
+
"00FF00",
|
|
17
|
+
"0000FF",
|
|
18
|
+
"FFFF00",
|
|
19
|
+
"FF00FF",
|
|
20
|
+
"00FFFF",
|
|
21
|
+
"000000",
|
|
22
|
+
"FFFFFF",
|
|
23
|
+
"FF0000",
|
|
24
|
+
"00FF00",
|
|
25
|
+
"0000FF",
|
|
26
|
+
"FFFF00",
|
|
27
|
+
"FF00FF",
|
|
28
|
+
"00FFFF",
|
|
29
|
+
"800000",
|
|
30
|
+
"008000",
|
|
31
|
+
"000080",
|
|
32
|
+
"808000",
|
|
33
|
+
"800080",
|
|
34
|
+
"008080",
|
|
35
|
+
"C0C0C0",
|
|
36
|
+
"808080",
|
|
37
|
+
"9999FF",
|
|
38
|
+
"993366",
|
|
39
|
+
"FFFFCC",
|
|
40
|
+
"CCFFFF",
|
|
41
|
+
"660066",
|
|
42
|
+
"FF8080",
|
|
43
|
+
"0066CC",
|
|
44
|
+
"CCCCFF",
|
|
45
|
+
"000080",
|
|
46
|
+
"FF00FF",
|
|
47
|
+
"FFFF00",
|
|
48
|
+
"00FFFF",
|
|
49
|
+
"800080",
|
|
50
|
+
"800000",
|
|
51
|
+
"008080",
|
|
52
|
+
"0000FF",
|
|
53
|
+
"00CCFF",
|
|
54
|
+
"CCFFFF",
|
|
55
|
+
"CCFFCC",
|
|
56
|
+
"FFFF99",
|
|
57
|
+
"99CCFF",
|
|
58
|
+
"FF99CC",
|
|
59
|
+
"CC99FF",
|
|
60
|
+
"FFCC99",
|
|
61
|
+
"3366FF",
|
|
62
|
+
"33CCCC",
|
|
63
|
+
"99CC00",
|
|
64
|
+
"FFCC00",
|
|
65
|
+
"FF9900",
|
|
66
|
+
"FF6600",
|
|
67
|
+
"666699",
|
|
68
|
+
"969696",
|
|
69
|
+
"003366",
|
|
70
|
+
"339966",
|
|
71
|
+
"003300",
|
|
72
|
+
"333300",
|
|
73
|
+
"993300",
|
|
74
|
+
"993366",
|
|
75
|
+
"333399",
|
|
76
|
+
"333333",
|
|
77
|
+
"000000",
|
|
78
|
+
"FFFFFF"
|
|
79
|
+
];
|
|
80
|
+
//#endregion
|
|
81
|
+
export { INDEXED_COLORS };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BodyElement, Chart, Comment, DocumentInfo, Numbering, Section, SectionProperties, StyleSheet } from '../document-model/index.js';
|
|
1
|
+
import { BodyElement, Chart, Comment, DocumentInfo, Numbering, Section, SectionProperties, ShapeFill, StyleSheet } from '../document-model/index.js';
|
|
2
2
|
import { FontRegistry } from '../font/index.js';
|
|
3
3
|
import { ResourceStore } from './resources.js';
|
|
4
4
|
/**
|
|
@@ -46,4 +46,26 @@ export interface FlowDoc {
|
|
|
46
46
|
readonly info?: DocumentInfo;
|
|
47
47
|
/** Document natural language hint (BCP-47), e.g. for tagged-PDF /Lang. */
|
|
48
48
|
readonly language?: string;
|
|
49
|
+
/**
|
|
50
|
+
* ECMA-376 §17.15.1.35 `w:doNotExpandShiftReturn` — a justified line that
|
|
51
|
+
* ends at a soft line break keeps its natural width.
|
|
52
|
+
*/
|
|
53
|
+
readonly doNotExpandShiftReturn?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* ECMA-376 §17.2.1 `w:background` — the colour every page is painted, when
|
|
56
|
+
* the document asks for one AND §17.15.1.28 `w:displayBackgroundShape` says
|
|
57
|
+
* to draw it.
|
|
58
|
+
*/
|
|
59
|
+
readonly pageBackgroundColorHex?: string;
|
|
60
|
+
/**
|
|
61
|
+
* ECMA-376 §17.2.1 — the same background as the FILL it is, when that is more
|
|
62
|
+
* than a flat colour: the `v:background`'s gradient or picture. The colour
|
|
63
|
+
* above stays the flat fallback, for writers that paint only colours.
|
|
64
|
+
*/
|
|
65
|
+
readonly pageBackgroundFill?: ShapeFill;
|
|
66
|
+
/**
|
|
67
|
+
* ECMA-376 §17.15.1.38 `w:gutterAtTop` — the binding space `w:pgMar
|
|
68
|
+
* @w:gutter` reserves belongs to the TOP margin rather than the left.
|
|
69
|
+
*/
|
|
70
|
+
readonly gutterAtTop?: boolean;
|
|
49
71
|
}
|