@qvac/harness 0.7.3 → 0.8.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/CHANGELOG.md +45 -1
- package/NOTICE.md +16 -0
- package/dist-lib/lib/capabilities/completion.d.ts +1 -0
- package/dist-lib/lib/capabilities/completion.js +28 -8
- package/dist-lib/lib/capabilities/tool-turns.d.ts +3 -0
- package/dist-lib/lib/capabilities/tool-turns.js +7 -0
- package/dist-lib/lib/context-window.js +2 -2
- package/dist-lib/lib/helpers/run-outcome.d.ts +4 -1
- package/dist-lib/lib/helpers/run-outcome.js +14 -8
- package/dist-lib/lib/helpers/tool-call-markup.js +4 -4
- package/dist-lib/lib/hooks/built-in/arg-repair.js +7 -0
- package/dist-lib/lib/office/engine/codes.d.ts +62 -0
- package/dist-lib/lib/office/engine/codes.js +61 -0
- package/dist-lib/lib/office/engine/docx/document.d.ts +216 -0
- package/dist-lib/lib/office/engine/docx/document.js +1152 -0
- package/dist-lib/lib/office/engine/docx/index.d.ts +7 -0
- package/dist-lib/lib/office/engine/docx/index.js +8 -0
- package/dist-lib/lib/office/engine/docx/styles.d.ts +31 -0
- package/dist-lib/lib/office/engine/docx/styles.js +173 -0
- package/dist-lib/lib/office/engine/docx/template.d.ts +18 -0
- package/dist-lib/lib/office/engine/docx/template.js +133 -0
- package/dist-lib/lib/office/engine/docx/text.d.ts +55 -0
- package/dist-lib/lib/office/engine/docx/text.js +538 -0
- package/dist-lib/lib/office/engine/drawingml/chart.d.ts +63 -0
- package/dist-lib/lib/office/engine/drawingml/chart.js +539 -0
- package/dist-lib/lib/office/engine/drawingml/text.d.ts +35 -0
- package/dist-lib/lib/office/engine/drawingml/text.js +352 -0
- package/dist-lib/lib/office/engine/errors.d.ts +6 -0
- package/dist-lib/lib/office/engine/errors.js +12 -0
- package/dist-lib/lib/office/engine/image.d.ts +8 -0
- package/dist-lib/lib/office/engine/image.js +48 -0
- package/dist-lib/lib/office/engine/index.d.ts +12 -0
- package/dist-lib/lib/office/engine/index.js +7 -0
- package/dist-lib/lib/office/engine/opc/content-types.d.ts +17 -0
- package/dist-lib/lib/office/engine/opc/content-types.js +74 -0
- package/dist-lib/lib/office/engine/opc/media.d.ts +13 -0
- package/dist-lib/lib/office/engine/opc/media.js +11 -0
- package/dist-lib/lib/office/engine/opc/package.d.ts +51 -0
- package/dist-lib/lib/office/engine/opc/package.js +324 -0
- package/dist-lib/lib/office/engine/opc/paths.d.ts +7 -0
- package/dist-lib/lib/office/engine/opc/paths.js +46 -0
- package/dist-lib/lib/office/engine/opc/properties.d.ts +7 -0
- package/dist-lib/lib/office/engine/opc/properties.js +75 -0
- package/dist-lib/lib/office/engine/opc/relationships.d.ts +23 -0
- package/dist-lib/lib/office/engine/opc/relationships.js +65 -0
- package/dist-lib/lib/office/engine/opc/zip.d.ts +15 -0
- package/dist-lib/lib/office/engine/opc/zip.js +264 -0
- package/dist-lib/lib/office/engine/pptx/fit.d.ts +12 -0
- package/dist-lib/lib/office/engine/pptx/fit.js +37 -0
- package/dist-lib/lib/office/engine/pptx/index.d.ts +8 -0
- package/dist-lib/lib/office/engine/pptx/index.js +8 -0
- package/dist-lib/lib/office/engine/pptx/inherit.d.ts +29 -0
- package/dist-lib/lib/office/engine/pptx/inherit.js +155 -0
- package/dist-lib/lib/office/engine/pptx/notes.d.ts +5 -0
- package/dist-lib/lib/office/engine/pptx/notes.js +85 -0
- package/dist-lib/lib/office/engine/pptx/presentation.d.ts +54 -0
- package/dist-lib/lib/office/engine/pptx/presentation.js +749 -0
- package/dist-lib/lib/office/engine/pptx/read.d.ts +9 -0
- package/dist-lib/lib/office/engine/pptx/read.js +66 -0
- package/dist-lib/lib/office/engine/pptx/shapes.d.ts +52 -0
- package/dist-lib/lib/office/engine/pptx/shapes.js +276 -0
- package/dist-lib/lib/office/engine/pptx/slides.d.ts +27 -0
- package/dist-lib/lib/office/engine/pptx/slides.js +123 -0
- package/dist-lib/lib/office/engine/pptx/template.d.ts +20 -0
- package/dist-lib/lib/office/engine/pptx/template.js +391 -0
- package/dist-lib/lib/office/engine/pptx/types.d.ts +120 -0
- package/dist-lib/lib/office/engine/pptx/types.js +1 -0
- package/dist-lib/lib/office/engine/units.d.ts +3 -0
- package/dist-lib/lib/office/engine/units.js +12 -0
- package/dist-lib/lib/office/engine/utf8.d.ts +2 -0
- package/dist-lib/lib/office/engine/utf8.js +103 -0
- package/dist-lib/lib/office/engine/xlsx/formula.d.ts +49 -0
- package/dist-lib/lib/office/engine/xlsx/formula.js +381 -0
- package/dist-lib/lib/office/engine/xlsx/index.d.ts +8 -0
- package/dist-lib/lib/office/engine/xlsx/index.js +8 -0
- package/dist-lib/lib/office/engine/xlsx/refs.d.ts +22 -0
- package/dist-lib/lib/office/engine/xlsx/refs.js +82 -0
- package/dist-lib/lib/office/engine/xlsx/reshape.d.ts +21 -0
- package/dist-lib/lib/office/engine/xlsx/reshape.js +424 -0
- package/dist-lib/lib/office/engine/xlsx/sheet.d.ts +108 -0
- package/dist-lib/lib/office/engine/xlsx/sheet.js +662 -0
- package/dist-lib/lib/office/engine/xlsx/strings.d.ts +15 -0
- package/dist-lib/lib/office/engine/xlsx/strings.js +93 -0
- package/dist-lib/lib/office/engine/xlsx/styles.d.ts +38 -0
- package/dist-lib/lib/office/engine/xlsx/styles.js +369 -0
- package/dist-lib/lib/office/engine/xlsx/template.d.ts +7 -0
- package/dist-lib/lib/office/engine/xlsx/template.js +54 -0
- package/dist-lib/lib/office/engine/xlsx/workbook.d.ts +161 -0
- package/dist-lib/lib/office/engine/xlsx/workbook.js +850 -0
- package/dist-lib/lib/office/engine/xlsx/write.d.ts +2 -0
- package/dist-lib/lib/office/engine/xlsx/write.js +42 -0
- package/dist-lib/lib/office/engine/xml.d.ts +45 -0
- package/dist-lib/lib/office/engine/xml.js +258 -0
- package/dist-lib/lib/tools/exec/scope.d.ts +2 -0
- package/dist-lib/lib/tools/exec/scope.js +33 -2
- package/dist-lib/lib/tools/pdf/build.d.ts +43 -0
- package/dist-lib/lib/tools/pdf/build.js +150 -0
- package/dist-lib/lib/tools/pdf/images.d.ts +7 -0
- package/dist-lib/lib/tools/pdf/images.js +82 -0
- package/dist-lib/lib/tools/pdf/request.d.ts +63 -0
- package/dist-lib/lib/tools/pdf/request.js +335 -0
- package/dist-lib/lib/tools/pdf.d.ts +6 -0
- package/dist-lib/lib/tools/pdf.js +302 -0
- package/dist-lib/lib/tools/skill.js +14 -0
- package/package.json +25 -1
- package/worker/harness-sidecar.mjs +3 -1
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import * as xml from '../xml.js';
|
|
2
|
+
import { DocsError } from '../errors.js';
|
|
3
|
+
import { isRecord } from '../../../guards.js';
|
|
4
|
+
const A = xml.ns.a;
|
|
5
|
+
const STYLE_KEYS = ['size', 'bold', 'italic', 'underline', 'color', 'font', 'align'];
|
|
6
|
+
const TOGGLES = ['bold', 'italic', 'underline'];
|
|
7
|
+
const ALIGNMENTS = {
|
|
8
|
+
left: 'l',
|
|
9
|
+
center: 'ctr',
|
|
10
|
+
right: 'r',
|
|
11
|
+
justify: 'just'
|
|
12
|
+
};
|
|
13
|
+
const FILLS = ['noFill', 'solidFill', 'gradFill', 'blipFill', 'pattFill', 'grpFill'];
|
|
14
|
+
const LINKS = ['hlinkClick', 'hlinkMouseOver'];
|
|
15
|
+
const RUN_PROPERTY_ORDER = [
|
|
16
|
+
'ln',
|
|
17
|
+
'noFill',
|
|
18
|
+
'solidFill',
|
|
19
|
+
'gradFill',
|
|
20
|
+
'blipFill',
|
|
21
|
+
'pattFill',
|
|
22
|
+
'grpFill',
|
|
23
|
+
'effectLst',
|
|
24
|
+
'effectDag',
|
|
25
|
+
'highlight',
|
|
26
|
+
'uLnTx',
|
|
27
|
+
'uLn',
|
|
28
|
+
'uFillTx',
|
|
29
|
+
'uFill',
|
|
30
|
+
'latin',
|
|
31
|
+
'ea',
|
|
32
|
+
'cs',
|
|
33
|
+
'sym',
|
|
34
|
+
'hlinkClick',
|
|
35
|
+
'hlinkMouseOver',
|
|
36
|
+
'rtl',
|
|
37
|
+
'extLst'
|
|
38
|
+
];
|
|
39
|
+
function isAlignment(value) {
|
|
40
|
+
return typeof value === 'string' && value in ALIGNMENTS;
|
|
41
|
+
}
|
|
42
|
+
function paragraphText(p) {
|
|
43
|
+
let out = '';
|
|
44
|
+
for (const node of xml.children(p, A)) {
|
|
45
|
+
if (node.localName === 'r' || node.localName === 'fld') {
|
|
46
|
+
out += xml.text(xml.child(node, A, 't'));
|
|
47
|
+
}
|
|
48
|
+
else if (node.localName === 'br') {
|
|
49
|
+
out += '\n';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
function paragraphLevel(p) {
|
|
55
|
+
const pPr = xml.child(p, A, 'pPr');
|
|
56
|
+
return pPr?.hasAttribute('lvl') ? Number(pPr.getAttribute('lvl')) : 0;
|
|
57
|
+
}
|
|
58
|
+
export function readParagraphs(txBody) {
|
|
59
|
+
return xml
|
|
60
|
+
.children(txBody, A, 'p')
|
|
61
|
+
.map((p) => ({ text: paragraphText(p), level: paragraphLevel(p) }));
|
|
62
|
+
}
|
|
63
|
+
export function readText(txBody) {
|
|
64
|
+
return readParagraphs(txBody)
|
|
65
|
+
.map((p) => p.text)
|
|
66
|
+
.join('\n');
|
|
67
|
+
}
|
|
68
|
+
function normalizeItem(item) {
|
|
69
|
+
if (typeof item === 'string') {
|
|
70
|
+
return { text: item };
|
|
71
|
+
}
|
|
72
|
+
if (typeof item !== 'object' ||
|
|
73
|
+
item === null ||
|
|
74
|
+
!('text' in item) ||
|
|
75
|
+
typeof item.text !== 'string') {
|
|
76
|
+
throw new DocsError('INVALID_TEXT', 'each paragraph must be a string or { text, level }');
|
|
77
|
+
}
|
|
78
|
+
const level = 'level' in item ? item.level : undefined;
|
|
79
|
+
if (level !== undefined &&
|
|
80
|
+
!(typeof level === 'number' && Number.isInteger(level) && level >= 0 && level <= 8)) {
|
|
81
|
+
throw new DocsError('INVALID_TEXT', `paragraph level must be an integer from 0 to 8, got ${String(level)}`);
|
|
82
|
+
}
|
|
83
|
+
return { text: item.text, level };
|
|
84
|
+
}
|
|
85
|
+
export function normalizeParagraphs(input) {
|
|
86
|
+
if (typeof input === 'string') {
|
|
87
|
+
return input.split('\n').map((text) => ({ text }));
|
|
88
|
+
}
|
|
89
|
+
if (!Array.isArray(input)) {
|
|
90
|
+
throw new DocsError('INVALID_TEXT', 'text must be a string or an array of paragraphs');
|
|
91
|
+
}
|
|
92
|
+
return input.map(normalizeItem);
|
|
93
|
+
}
|
|
94
|
+
function unlinked(rPr) {
|
|
95
|
+
for (const local of LINKS) {
|
|
96
|
+
for (const el of xml.children(rPr, A, local)) {
|
|
97
|
+
xml.remove(el);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return rPr;
|
|
101
|
+
}
|
|
102
|
+
export function losses(txBody) {
|
|
103
|
+
const out = {};
|
|
104
|
+
const hyperlinks = LINKS.reduce((n, local) => n + xml.descendants(txBody, A, local).length, 0);
|
|
105
|
+
const fields = xml.descendants(txBody, A, 'fld').length;
|
|
106
|
+
if (hyperlinks) {
|
|
107
|
+
out.hyperlinks = hyperlinks;
|
|
108
|
+
}
|
|
109
|
+
if (fields) {
|
|
110
|
+
out.fields = fields;
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
function runPropertiesOf(template) {
|
|
115
|
+
if (!template)
|
|
116
|
+
return null;
|
|
117
|
+
const run = xml.child(template, A, 'r');
|
|
118
|
+
if (run) {
|
|
119
|
+
const rPr = xml.child(run, A, 'rPr');
|
|
120
|
+
return rPr ? unlinked(xml.clone(rPr)) : null;
|
|
121
|
+
}
|
|
122
|
+
const end = xml.child(template, A, 'endParaRPr');
|
|
123
|
+
if (!end) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const rPr = xml.builder(xml.owner(template), A)('rPr');
|
|
127
|
+
for (const attr of Array.from(end.attributes)) {
|
|
128
|
+
rPr.setAttribute(attr.name, attr.value);
|
|
129
|
+
}
|
|
130
|
+
for (const node of xml.children(end)) {
|
|
131
|
+
rPr.appendChild(xml.clone(node));
|
|
132
|
+
}
|
|
133
|
+
return unlinked(rPr);
|
|
134
|
+
}
|
|
135
|
+
function paragraphProperties(template, paragraph, a) {
|
|
136
|
+
const existing = template ? xml.child(template, A, 'pPr') : null;
|
|
137
|
+
const pPr = existing ? xml.clone(existing) : null;
|
|
138
|
+
if (paragraph.level === undefined) {
|
|
139
|
+
return pPr;
|
|
140
|
+
}
|
|
141
|
+
const out = pPr ?? a('pPr');
|
|
142
|
+
if (paragraph.level === 0) {
|
|
143
|
+
out.removeAttribute('lvl');
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
out.setAttribute('lvl', String(paragraph.level));
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
function appendLine(p, line, rPr) {
|
|
151
|
+
const doc = xml.owner(p);
|
|
152
|
+
const a = xml.builder(doc, A);
|
|
153
|
+
const r = xml.append(p, a('r'));
|
|
154
|
+
if (rPr) {
|
|
155
|
+
r.appendChild(xml.clone(rPr));
|
|
156
|
+
}
|
|
157
|
+
xml.append(r, a('t')).appendChild(doc.createTextNode(line));
|
|
158
|
+
}
|
|
159
|
+
function buildParagraph(template, paragraph, a) {
|
|
160
|
+
const p = a('p');
|
|
161
|
+
const pPr = paragraphProperties(template, paragraph, a);
|
|
162
|
+
if (pPr) {
|
|
163
|
+
p.appendChild(pPr);
|
|
164
|
+
}
|
|
165
|
+
const rPr = runPropertiesOf(template);
|
|
166
|
+
paragraph.text.split(/\r\n|[\n\v]/).forEach((raw, i) => {
|
|
167
|
+
const line = xml.clean(raw);
|
|
168
|
+
if (i > 0) {
|
|
169
|
+
const br = xml.append(p, a('br'));
|
|
170
|
+
if (rPr) {
|
|
171
|
+
br.appendChild(xml.clone(rPr));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (line !== '') {
|
|
175
|
+
appendLine(p, line, rPr);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
const end = template ? xml.child(template, A, 'endParaRPr') : null;
|
|
179
|
+
if (end) {
|
|
180
|
+
p.appendChild(unlinked(xml.clone(end)));
|
|
181
|
+
}
|
|
182
|
+
return p;
|
|
183
|
+
}
|
|
184
|
+
export function writeParagraphs(txBody, input) {
|
|
185
|
+
const paragraphs = normalizeParagraphs(input);
|
|
186
|
+
if (paragraphs.length === 0) {
|
|
187
|
+
paragraphs.push({ text: '' });
|
|
188
|
+
}
|
|
189
|
+
const a = xml.builder(xml.owner(txBody), A);
|
|
190
|
+
const old = xml.children(txBody, A, 'p');
|
|
191
|
+
const built = paragraphs.map((paragraph, i) => buildParagraph(old[Math.min(i, old.length - 1)], paragraph, a));
|
|
192
|
+
const anchor = old.length ? old[old.length - 1].nextSibling : null;
|
|
193
|
+
for (const p of old) {
|
|
194
|
+
xml.remove(p);
|
|
195
|
+
}
|
|
196
|
+
for (const p of built) {
|
|
197
|
+
txBody.insertBefore(p, anchor);
|
|
198
|
+
}
|
|
199
|
+
return built.length;
|
|
200
|
+
}
|
|
201
|
+
export function ensureTextBody(parent, order) {
|
|
202
|
+
const namespace = parent.namespaceURI ?? A;
|
|
203
|
+
const existing = xml.child(parent, namespace, 'txBody');
|
|
204
|
+
if (existing) {
|
|
205
|
+
return existing;
|
|
206
|
+
}
|
|
207
|
+
const doc = xml.owner(parent);
|
|
208
|
+
const a = xml.builder(doc, A);
|
|
209
|
+
const txBody = xml.builder(doc, namespace)('txBody');
|
|
210
|
+
for (const local of ['bodyPr', 'lstStyle', 'p']) {
|
|
211
|
+
txBody.appendChild(a(local));
|
|
212
|
+
}
|
|
213
|
+
if (order) {
|
|
214
|
+
xml.insertOrdered(parent, txBody, order);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
parent.appendChild(txBody);
|
|
218
|
+
}
|
|
219
|
+
return txBody;
|
|
220
|
+
}
|
|
221
|
+
function isRecordLike(value) {
|
|
222
|
+
return isRecord(value) && !Array.isArray(value);
|
|
223
|
+
}
|
|
224
|
+
function checkStyleKeys(style) {
|
|
225
|
+
const keys = Object.keys(style);
|
|
226
|
+
if (keys.length === 0) {
|
|
227
|
+
throw new DocsError('INVALID_STYLE', `style names nothing to change; use ${STYLE_KEYS.join(', ')}`);
|
|
228
|
+
}
|
|
229
|
+
const unknownKey = keys.find((key) => !STYLE_KEYS.includes(key));
|
|
230
|
+
if (unknownKey) {
|
|
231
|
+
throw new DocsError('INVALID_STYLE', `unknown style "${unknownKey}"; use ${STYLE_KEYS.join(', ')}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function checkStyleValues(style) {
|
|
235
|
+
const { size, color, font, align } = style;
|
|
236
|
+
if (size !== undefined && !(typeof size === 'number' && size >= 1 && size <= 400)) {
|
|
237
|
+
throw new DocsError('INVALID_STYLE', `size is in points, from 1 to 400, got ${String(size)}`);
|
|
238
|
+
}
|
|
239
|
+
const toggle = TOGGLES.find((key) => style[key] !== undefined && typeof style[key] !== 'boolean');
|
|
240
|
+
if (toggle) {
|
|
241
|
+
throw new DocsError('INVALID_STYLE', `${toggle} must be true or false`);
|
|
242
|
+
}
|
|
243
|
+
if (color !== undefined && !(typeof color === 'string' && /^#?[0-9a-fA-F]{6}$/.test(color))) {
|
|
244
|
+
throw new DocsError('INVALID_STYLE', `color must be a hex RGB value like "1A73E8", got ${String(color)}`);
|
|
245
|
+
}
|
|
246
|
+
if (align !== undefined && !isAlignment(align)) {
|
|
247
|
+
throw new DocsError('INVALID_STYLE', `align must be left, center, right or justify, got ${String(align)}`);
|
|
248
|
+
}
|
|
249
|
+
if (font !== undefined && !(typeof font === 'string' && font.trim())) {
|
|
250
|
+
throw new DocsError('INVALID_STYLE', 'font must be a typeface name');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function flag(value) {
|
|
254
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
255
|
+
}
|
|
256
|
+
export function validateStyle(style) {
|
|
257
|
+
if (!isRecordLike(style)) {
|
|
258
|
+
throw new DocsError('INVALID_STYLE', 'style must be an object');
|
|
259
|
+
}
|
|
260
|
+
checkStyleKeys(style);
|
|
261
|
+
checkStyleValues(style);
|
|
262
|
+
const { size, color, font, align } = style;
|
|
263
|
+
return {
|
|
264
|
+
size: typeof size === 'number' ? size : undefined,
|
|
265
|
+
bold: flag(style.bold),
|
|
266
|
+
italic: flag(style.italic),
|
|
267
|
+
underline: flag(style.underline),
|
|
268
|
+
color: typeof color === 'string' ? color : undefined,
|
|
269
|
+
font: typeof font === 'string' ? font : undefined,
|
|
270
|
+
align: isAlignment(align) ? align : undefined
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function setColor(rPr, color) {
|
|
274
|
+
const a = xml.builder(xml.owner(rPr), A);
|
|
275
|
+
for (const fill of xml.children(rPr, A)) {
|
|
276
|
+
if (FILLS.includes(fill.localName ?? '')) {
|
|
277
|
+
xml.remove(fill);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const solid = a('solidFill');
|
|
281
|
+
solid.appendChild(a('srgbClr', { val: color.replace('#', '').toUpperCase() }));
|
|
282
|
+
xml.insertOrdered(rPr, solid, RUN_PROPERTY_ORDER);
|
|
283
|
+
}
|
|
284
|
+
function applyRunStyle(rPr, style) {
|
|
285
|
+
if (style.size !== undefined) {
|
|
286
|
+
rPr.setAttribute('sz', String(Math.round(style.size * 100)));
|
|
287
|
+
}
|
|
288
|
+
if (style.bold !== undefined) {
|
|
289
|
+
rPr.setAttribute('b', style.bold ? '1' : '0');
|
|
290
|
+
}
|
|
291
|
+
if (style.italic !== undefined) {
|
|
292
|
+
rPr.setAttribute('i', style.italic ? '1' : '0');
|
|
293
|
+
}
|
|
294
|
+
if (style.underline !== undefined) {
|
|
295
|
+
rPr.setAttribute('u', style.underline ? 'sng' : 'none');
|
|
296
|
+
}
|
|
297
|
+
if (style.color !== undefined) {
|
|
298
|
+
setColor(rPr, style.color);
|
|
299
|
+
}
|
|
300
|
+
if (style.font !== undefined) {
|
|
301
|
+
const latin = xml.child(rPr, A, 'latin') ??
|
|
302
|
+
xml.insertOrdered(rPr, xml.builder(xml.owner(rPr), A)('latin'), RUN_PROPERTY_ORDER);
|
|
303
|
+
latin.setAttribute('typeface', style.font);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function ensureChild(parent, local, before) {
|
|
307
|
+
const existing = xml.child(parent, A, local);
|
|
308
|
+
if (existing) {
|
|
309
|
+
return existing;
|
|
310
|
+
}
|
|
311
|
+
const el = xml.builder(xml.owner(parent), A)(local);
|
|
312
|
+
parent.insertBefore(el, before);
|
|
313
|
+
return el;
|
|
314
|
+
}
|
|
315
|
+
function styleParagraph(p, style, styleRuns) {
|
|
316
|
+
if (style.align !== undefined) {
|
|
317
|
+
ensureChild(p, 'pPr', p.firstChild).setAttribute('algn', ALIGNMENTS[style.align]);
|
|
318
|
+
}
|
|
319
|
+
let runs = 0;
|
|
320
|
+
for (const node of xml.children(p, A)) {
|
|
321
|
+
if (node.localName === 'r' || node.localName === 'fld') {
|
|
322
|
+
if (styleRuns) {
|
|
323
|
+
applyRunStyle(ensureChild(node, 'rPr', node.firstChild), style);
|
|
324
|
+
}
|
|
325
|
+
runs++;
|
|
326
|
+
}
|
|
327
|
+
else if (node.localName === 'br') {
|
|
328
|
+
const rPr = xml.child(node, A, 'rPr');
|
|
329
|
+
if (rPr) {
|
|
330
|
+
applyRunStyle(rPr, style);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (styleRuns) {
|
|
335
|
+
applyRunStyle(ensureChild(p, 'endParaRPr', null), style);
|
|
336
|
+
}
|
|
337
|
+
return runs;
|
|
338
|
+
}
|
|
339
|
+
export function applyStyle(txBody, input, target = {}) {
|
|
340
|
+
const style = validateStyle(input);
|
|
341
|
+
const paragraphs = xml.children(txBody, A, 'p');
|
|
342
|
+
let targets = paragraphs;
|
|
343
|
+
if (target.paragraph !== undefined) {
|
|
344
|
+
const p = paragraphs[target.paragraph];
|
|
345
|
+
if (!p) {
|
|
346
|
+
throw new DocsError('PARAGRAPH_NOT_FOUND', `paragraph ${target.paragraph} does not exist; this text has ${paragraphs.length} (0 to ${paragraphs.length - 1})`);
|
|
347
|
+
}
|
|
348
|
+
targets = [p];
|
|
349
|
+
}
|
|
350
|
+
const styleRuns = Object.keys(input ?? {}).some((key) => key !== 'align');
|
|
351
|
+
return targets.reduce((runs, p) => runs + styleParagraph(p, style, styleRuns), 0);
|
|
352
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { DocsError } from './errors.js';
|
|
2
|
+
function jpegSize(bytes, view) {
|
|
3
|
+
let offset = 2;
|
|
4
|
+
while (offset + 9 < bytes.byteLength && bytes[offset] === 0xff) {
|
|
5
|
+
const marker = bytes[offset + 1];
|
|
6
|
+
if (marker === 0xff) {
|
|
7
|
+
offset++;
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
const frame = marker >= 0xc0 && marker <= 0xcf && ![0xc4, 0xc8, 0xcc].includes(marker);
|
|
11
|
+
if (frame) {
|
|
12
|
+
return {
|
|
13
|
+
format: 'jpeg',
|
|
14
|
+
contentType: 'image/jpeg',
|
|
15
|
+
width: view.getUint16(offset + 7),
|
|
16
|
+
height: view.getUint16(offset + 5)
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
offset += 2 + view.getUint16(offset + 2);
|
|
20
|
+
}
|
|
21
|
+
throw new DocsError('INVALID_IMAGE', 'the JPEG has no frame header, so its size is unknown');
|
|
22
|
+
}
|
|
23
|
+
export function probe(bytes) {
|
|
24
|
+
if (!(bytes instanceof Uint8Array) || bytes.byteLength < 24) {
|
|
25
|
+
throw new DocsError('INVALID_IMAGE', 'the image is empty or truncated');
|
|
26
|
+
}
|
|
27
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
28
|
+
if (view.getUint32(0) === 0x89504e47 && view.getUint32(4) === 0x0d0a1a0a) {
|
|
29
|
+
return {
|
|
30
|
+
format: 'png',
|
|
31
|
+
contentType: 'image/png',
|
|
32
|
+
width: view.getUint32(16),
|
|
33
|
+
height: view.getUint32(20)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46) {
|
|
37
|
+
return {
|
|
38
|
+
format: 'gif',
|
|
39
|
+
contentType: 'image/gif',
|
|
40
|
+
width: view.getUint16(6, true),
|
|
41
|
+
height: view.getUint16(8, true)
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (bytes[0] === 0xff && bytes[1] === 0xd8) {
|
|
45
|
+
return jpegSize(bytes, view);
|
|
46
|
+
}
|
|
47
|
+
throw new DocsError('UNSUPPORTED_IMAGE', 'only PNG, JPEG and GIF images can be embedded');
|
|
48
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * as pptx from './pptx/index.ts';
|
|
2
|
+
export * as docx from './docx/index.ts';
|
|
3
|
+
export * as xlsx from './xlsx/index.ts';
|
|
4
|
+
export * as units from './units.ts';
|
|
5
|
+
export { Package } from './opc/package.ts';
|
|
6
|
+
export { DocsError } from './errors.ts';
|
|
7
|
+
export { CODES } from './codes.ts';
|
|
8
|
+
export type { DocsErrorCode } from './codes.ts';
|
|
9
|
+
export type { OpenOptions, PartData } from './opc/package.ts';
|
|
10
|
+
export type { SaveOptions } from './opc/properties.ts';
|
|
11
|
+
export type { ChartData, ChartInfo, ChartSpec, ChartType } from './drawingml/chart.ts';
|
|
12
|
+
export type { Paragraph, Style, TextInput } from './drawingml/text.ts';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * as pptx from './pptx/index.js';
|
|
2
|
+
export * as docx from './docx/index.js';
|
|
3
|
+
export * as xlsx from './xlsx/index.js';
|
|
4
|
+
export * as units from './units.js';
|
|
5
|
+
export { Package } from './opc/package.js';
|
|
6
|
+
export { DocsError } from './errors.js';
|
|
7
|
+
export { CODES } from './codes.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Document, Element } from '../xml.ts';
|
|
2
|
+
export declare const CONTENT_TYPES_NAME = "/[Content_Types].xml";
|
|
3
|
+
export declare class ContentTypes {
|
|
4
|
+
doc: Document;
|
|
5
|
+
dirty: boolean;
|
|
6
|
+
private original;
|
|
7
|
+
constructor(bytes: Uint8Array | null);
|
|
8
|
+
get root(): Element;
|
|
9
|
+
get(partName: string): string | null;
|
|
10
|
+
hasDefault(ext: string): boolean;
|
|
11
|
+
addDefault(ext: string, contentType: string): void;
|
|
12
|
+
setOverride(partName: string, contentType: string): void;
|
|
13
|
+
removeOverride(partName: string): void;
|
|
14
|
+
serialize(): Uint8Array;
|
|
15
|
+
private defaults;
|
|
16
|
+
private override;
|
|
17
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as xml from '../xml.js';
|
|
2
|
+
import { extension } from './paths.js';
|
|
3
|
+
export const CONTENT_TYPES_NAME = '/[Content_Types].xml';
|
|
4
|
+
export class ContentTypes {
|
|
5
|
+
doc;
|
|
6
|
+
dirty;
|
|
7
|
+
original;
|
|
8
|
+
constructor(bytes) {
|
|
9
|
+
this.original = bytes;
|
|
10
|
+
this.doc = xml.parse(bytes ?? `<Types xmlns="${xml.ns.ct}"/>`, CONTENT_TYPES_NAME);
|
|
11
|
+
this.dirty = !bytes;
|
|
12
|
+
}
|
|
13
|
+
get root() {
|
|
14
|
+
return xml.root(this.doc);
|
|
15
|
+
}
|
|
16
|
+
get(partName) {
|
|
17
|
+
const override = this.override(partName);
|
|
18
|
+
if (override) {
|
|
19
|
+
return override.getAttribute('ContentType');
|
|
20
|
+
}
|
|
21
|
+
const ext = extension(partName);
|
|
22
|
+
const entry = this.defaults().find((el) => xml.attr(el, 'Extension').toLowerCase() === ext);
|
|
23
|
+
return entry ? entry.getAttribute('ContentType') : null;
|
|
24
|
+
}
|
|
25
|
+
hasDefault(ext) {
|
|
26
|
+
return this.defaults().some((el) => xml.attr(el, 'Extension').toLowerCase() === ext.toLowerCase());
|
|
27
|
+
}
|
|
28
|
+
addDefault(ext, contentType) {
|
|
29
|
+
if (this.hasDefault(ext))
|
|
30
|
+
return;
|
|
31
|
+
const el = xml.setAttributes(xml.named(this.doc, 'Default', xml.ns.ct), {
|
|
32
|
+
Extension: ext,
|
|
33
|
+
ContentType: contentType
|
|
34
|
+
});
|
|
35
|
+
this.root.insertBefore(el, xml.child(this.root, xml.ns.ct, 'Override'));
|
|
36
|
+
this.dirty = true;
|
|
37
|
+
}
|
|
38
|
+
setOverride(partName, contentType) {
|
|
39
|
+
const existing = this.override(partName);
|
|
40
|
+
if (existing?.getAttribute('ContentType') === contentType) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (existing) {
|
|
44
|
+
existing.setAttribute('ContentType', contentType);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const el = xml.named(this.doc, 'Override', xml.ns.ct);
|
|
48
|
+
this.root.appendChild(xml.setAttributes(el, { PartName: partName, ContentType: contentType }));
|
|
49
|
+
}
|
|
50
|
+
this.dirty = true;
|
|
51
|
+
}
|
|
52
|
+
removeOverride(partName) {
|
|
53
|
+
const existing = this.override(partName);
|
|
54
|
+
if (!existing) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
xml.remove(existing);
|
|
58
|
+
this.dirty = true;
|
|
59
|
+
}
|
|
60
|
+
serialize() {
|
|
61
|
+
if (!this.dirty && this.original)
|
|
62
|
+
return this.original;
|
|
63
|
+
return xml.serialize(this.doc);
|
|
64
|
+
}
|
|
65
|
+
defaults() {
|
|
66
|
+
return xml.children(this.root, xml.ns.ct, 'Default');
|
|
67
|
+
}
|
|
68
|
+
override(partName) {
|
|
69
|
+
const wanted = partName.toLowerCase();
|
|
70
|
+
return xml
|
|
71
|
+
.children(this.root, xml.ns.ct, 'Override')
|
|
72
|
+
.find((el) => xml.attr(el, 'PartName').toLowerCase() === wanted);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ImageInfo } from '../image.ts';
|
|
2
|
+
import type { Package } from './package.ts';
|
|
3
|
+
export declare const IMAGE_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
|
|
4
|
+
export interface ImagePlacement {
|
|
5
|
+
source: string;
|
|
6
|
+
dir: string;
|
|
7
|
+
}
|
|
8
|
+
export interface AddedImage {
|
|
9
|
+
rId: string;
|
|
10
|
+
part: string;
|
|
11
|
+
info: ImageInfo;
|
|
12
|
+
}
|
|
13
|
+
export declare function addImage(pkg: Package, bytes: Uint8Array, at: ImagePlacement): AddedImage;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { probe } from '../image.js';
|
|
2
|
+
import * as paths from './paths.js';
|
|
3
|
+
export const IMAGE_REL = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image';
|
|
4
|
+
export function addImage(pkg, bytes, at) {
|
|
5
|
+
const info = probe(bytes);
|
|
6
|
+
pkg.contentTypes.addDefault(info.format, info.contentType);
|
|
7
|
+
const part = pkg.freeName(paths.resolve(at.source, `${at.dir}/image%d.${info.format}`));
|
|
8
|
+
pkg.add(part, bytes);
|
|
9
|
+
const rId = pkg.rels(at.source).add(IMAGE_REL, part);
|
|
10
|
+
return { rId, part, info };
|
|
11
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ContentTypes } from './content-types.ts';
|
|
2
|
+
import { Relationships } from './relationships.ts';
|
|
3
|
+
import type { Document, Element } from '../xml.ts';
|
|
4
|
+
import type { Limits } from './zip.ts';
|
|
5
|
+
export type PartData = Uint8Array | Document;
|
|
6
|
+
export interface OpenOptions {
|
|
7
|
+
limits?: Partial<Limits>;
|
|
8
|
+
strict?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare class Package {
|
|
11
|
+
static strict: boolean;
|
|
12
|
+
strict: boolean;
|
|
13
|
+
contentTypes: ContentTypes;
|
|
14
|
+
private parts;
|
|
15
|
+
private folded;
|
|
16
|
+
private order;
|
|
17
|
+
private relsBySource;
|
|
18
|
+
private removed;
|
|
19
|
+
private edited;
|
|
20
|
+
private strays;
|
|
21
|
+
constructor(entries: [string, Uint8Array][], opts?: OpenOptions);
|
|
22
|
+
static open(bytes: Uint8Array | ArrayBuffer, opts?: OpenOptions): Package;
|
|
23
|
+
static create(opts?: OpenOptions): Package;
|
|
24
|
+
get changed(): boolean;
|
|
25
|
+
names(): string[];
|
|
26
|
+
has(name: string | null): boolean;
|
|
27
|
+
bytes(name: string): Uint8Array;
|
|
28
|
+
xml(name: string): Document;
|
|
29
|
+
root(name: string): Element;
|
|
30
|
+
touch(name: string): void;
|
|
31
|
+
contentType(name: string): string | null;
|
|
32
|
+
add(name: string, data: PartData, contentType?: string): void;
|
|
33
|
+
write(name: string, data: PartData): void;
|
|
34
|
+
remove(input: string): boolean;
|
|
35
|
+
rels(source: string): Relationships;
|
|
36
|
+
related(source: string, type: string): string[];
|
|
37
|
+
dropUnreferenced(source: string, types: readonly string[]): string[];
|
|
38
|
+
freeName(pattern: string): string;
|
|
39
|
+
reachable(): Set<string>;
|
|
40
|
+
prune(): string[];
|
|
41
|
+
private unreachable;
|
|
42
|
+
save(): Uint8Array;
|
|
43
|
+
private checkUntouched;
|
|
44
|
+
private flushRels;
|
|
45
|
+
private require;
|
|
46
|
+
private resolveExisting;
|
|
47
|
+
private find;
|
|
48
|
+
private store;
|
|
49
|
+
private forget;
|
|
50
|
+
}
|
|
51
|
+
export declare function referencedIds(top: Element): Set<string>;
|