@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,9 @@
|
|
|
1
|
+
import type { Document } from '../xml.ts';
|
|
2
|
+
import type { ShapeEntry } from './shapes.ts';
|
|
3
|
+
import type { ReadOptions, ShapeInfo } from './types.ts';
|
|
4
|
+
export declare const BODY_TYPES: Set<string>;
|
|
5
|
+
export declare function pick(total: number, opts: ReadOptions): (i: number) => boolean;
|
|
6
|
+
export declare function layoutName(doc: Document): string;
|
|
7
|
+
export declare function layoutType(doc: Document): string;
|
|
8
|
+
export declare function hasBody(doc: Document): boolean;
|
|
9
|
+
export declare function describe(shape: ShapeEntry): ShapeInfo;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as xml from '../xml.js';
|
|
2
|
+
import * as text from '../drawingml/text.js';
|
|
3
|
+
import { DocsError } from '../errors.js';
|
|
4
|
+
import { shapesOf } from './shapes.js';
|
|
5
|
+
const { a: A, p: P } = xml.ns;
|
|
6
|
+
export const BODY_TYPES = new Set(['body', 'obj', 'subTitle']);
|
|
7
|
+
function window(total, opts) {
|
|
8
|
+
const from = opts.from ?? 0;
|
|
9
|
+
const count = opts.count ?? Math.max(total, 1);
|
|
10
|
+
if (!Number.isInteger(from) || from < 0 || (from >= total && total > 0)) {
|
|
11
|
+
throw new DocsError('INVALID_OPTIONS', `from must be a slide index from 0 to ${Math.max(0, total - 1)}, got ${String(opts.from)}`);
|
|
12
|
+
}
|
|
13
|
+
if (!Number.isInteger(count) || count < 1) {
|
|
14
|
+
throw new DocsError('INVALID_OPTIONS', `count must be at least 1, got ${String(opts.count)}`);
|
|
15
|
+
}
|
|
16
|
+
return { from, to: Math.min(total, from + count) };
|
|
17
|
+
}
|
|
18
|
+
export function pick(total, opts) {
|
|
19
|
+
if (opts.slides === undefined) {
|
|
20
|
+
const { from, to } = window(total, opts);
|
|
21
|
+
return (i) => i >= from && i < to;
|
|
22
|
+
}
|
|
23
|
+
if (opts.from !== undefined || opts.count !== undefined) {
|
|
24
|
+
throw new DocsError('INVALID_OPTIONS', 'pass either slides or from/count, not both');
|
|
25
|
+
}
|
|
26
|
+
const slides = opts.slides;
|
|
27
|
+
const valid = Array.isArray(slides) && slides.every((i) => Number.isInteger(i) && i >= 0 && i < total);
|
|
28
|
+
if (!valid) {
|
|
29
|
+
throw new DocsError('SLIDE_NOT_FOUND', `slides must list slide indexes from 0 to ${total - 1}`);
|
|
30
|
+
}
|
|
31
|
+
const set = new Set(slides);
|
|
32
|
+
return (i) => set.has(i);
|
|
33
|
+
}
|
|
34
|
+
export function layoutName(doc) {
|
|
35
|
+
const cSld = xml.child(xml.root(doc), P, 'cSld');
|
|
36
|
+
return cSld?.getAttribute('name') || '';
|
|
37
|
+
}
|
|
38
|
+
export function layoutType(doc) {
|
|
39
|
+
return xml.root(doc).getAttribute('type') || 'cust';
|
|
40
|
+
}
|
|
41
|
+
export function hasBody(doc) {
|
|
42
|
+
return shapesOf(doc).some((s) => s.placeholder !== null && BODY_TYPES.has(s.placeholder) && s.placeholder !== 'subTitle');
|
|
43
|
+
}
|
|
44
|
+
function tableRows(shape) {
|
|
45
|
+
return xml.descendants(shape.el, A, 'tr').map((tr) => xml.children(tr, A, 'tc').map((tc) => {
|
|
46
|
+
const txBody = xml.child(tc, A, 'txBody');
|
|
47
|
+
return txBody ? text.readText(txBody) : '';
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
export function describe(shape) {
|
|
51
|
+
const out = { id: shape.id, name: shape.name, kind: shape.kind };
|
|
52
|
+
if (shape.placeholder) {
|
|
53
|
+
out.placeholder = shape.placeholder;
|
|
54
|
+
}
|
|
55
|
+
if (shape.group !== null) {
|
|
56
|
+
out.group = shape.group;
|
|
57
|
+
}
|
|
58
|
+
if (shape.kind === 'text') {
|
|
59
|
+
const txBody = xml.child(shape.el, P, 'txBody');
|
|
60
|
+
out.paragraphs = txBody ? text.readParagraphs(txBody) : [];
|
|
61
|
+
}
|
|
62
|
+
else if (shape.kind === 'table') {
|
|
63
|
+
out.rows = tableRows(shape);
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Document, Element } from '../xml.ts';
|
|
2
|
+
import type { Box } from './inherit.ts';
|
|
3
|
+
export type ShapeKind = 'text' | 'shape' | 'picture' | 'group' | 'connector' | 'table' | 'chart' | 'diagram' | 'frame';
|
|
4
|
+
export interface ShapeEntry {
|
|
5
|
+
el: Element;
|
|
6
|
+
id: number | null;
|
|
7
|
+
name: string;
|
|
8
|
+
kind: ShapeKind;
|
|
9
|
+
placeholder: string | null;
|
|
10
|
+
group: number | null;
|
|
11
|
+
}
|
|
12
|
+
export type Cell = string | number | null;
|
|
13
|
+
export interface PictureFrame {
|
|
14
|
+
id: number;
|
|
15
|
+
rId: string;
|
|
16
|
+
box: Box;
|
|
17
|
+
name?: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface TextBoxFrame {
|
|
21
|
+
id: number;
|
|
22
|
+
box: Box;
|
|
23
|
+
text?: unknown;
|
|
24
|
+
style?: unknown;
|
|
25
|
+
fill?: unknown;
|
|
26
|
+
name?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface TableFrame {
|
|
29
|
+
id: number;
|
|
30
|
+
box: Box;
|
|
31
|
+
rows: unknown;
|
|
32
|
+
header?: boolean;
|
|
33
|
+
name?: string;
|
|
34
|
+
tableStyle: string;
|
|
35
|
+
}
|
|
36
|
+
export interface ChartFrame {
|
|
37
|
+
id: number;
|
|
38
|
+
box: Box;
|
|
39
|
+
rId: string;
|
|
40
|
+
name?: string;
|
|
41
|
+
}
|
|
42
|
+
export declare const SHAPE_ORDER: string[];
|
|
43
|
+
export declare function shapesOf(doc: Document): ShapeEntry[];
|
|
44
|
+
export declare function placeholderFrom(doc: Document, source: ShapeEntry, id: number): Element;
|
|
45
|
+
export declare function pictureElement(doc: Document, frame: PictureFrame): Element;
|
|
46
|
+
export declare function nextShapeId(doc: Document): number;
|
|
47
|
+
export declare function validateColor(color: unknown, name?: string): string;
|
|
48
|
+
export declare function textBoxElement(doc: Document, frame: TextBoxFrame): Element;
|
|
49
|
+
export declare function validateRows(rows: unknown): Cell[][];
|
|
50
|
+
export declare function tableElement(doc: Document, frame: TableFrame): Element;
|
|
51
|
+
export declare function chartFrameElement(doc: Document, frame: ChartFrame): Element;
|
|
52
|
+
export declare function setBackground(doc: Document, color: unknown): void;
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import * as xml from '../xml.js';
|
|
2
|
+
import * as text from '../drawingml/text.js';
|
|
3
|
+
import * as chart from '../drawingml/chart.js';
|
|
4
|
+
import { DocsError } from '../errors.js';
|
|
5
|
+
const { a: A, p: P, r: R } = xml.ns;
|
|
6
|
+
export const SHAPE_ORDER = ['nvSpPr', 'spPr', 'style', 'txBody', 'extLst'];
|
|
7
|
+
const TABLE_URI = 'http://schemas.openxmlformats.org/drawingml/2006/table';
|
|
8
|
+
function graphicKind(el) {
|
|
9
|
+
if (xml.descendants(el, A, 'tbl').length)
|
|
10
|
+
return 'table';
|
|
11
|
+
const data = xml.descendants(el, A, 'graphicData')[0];
|
|
12
|
+
const uri = data?.getAttribute('uri') ?? '';
|
|
13
|
+
if (uri.endsWith('/chart')) {
|
|
14
|
+
return 'chart';
|
|
15
|
+
}
|
|
16
|
+
if (uri.endsWith('/diagram')) {
|
|
17
|
+
return 'diagram';
|
|
18
|
+
}
|
|
19
|
+
return 'frame';
|
|
20
|
+
}
|
|
21
|
+
function kindOf(el) {
|
|
22
|
+
switch (el.localName) {
|
|
23
|
+
case 'sp': {
|
|
24
|
+
const nv = xml.children(el)[0];
|
|
25
|
+
const placeholder = nv ? xml.descendants(nv, P, 'ph').length > 0 : false;
|
|
26
|
+
return xml.child(el, P, 'txBody') || placeholder ? 'text' : 'shape';
|
|
27
|
+
}
|
|
28
|
+
case 'pic':
|
|
29
|
+
return 'picture';
|
|
30
|
+
case 'grpSp':
|
|
31
|
+
return 'group';
|
|
32
|
+
case 'cxnSp':
|
|
33
|
+
return 'connector';
|
|
34
|
+
case 'graphicFrame':
|
|
35
|
+
return graphicKind(el);
|
|
36
|
+
default:
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function entryOf(el, kind, group) {
|
|
41
|
+
const nv = xml.children(el)[0];
|
|
42
|
+
const cNvPr = nv ? xml.child(nv, P, 'cNvPr') : null;
|
|
43
|
+
const nvPr = nv ? xml.child(nv, P, 'nvPr') : null;
|
|
44
|
+
const ph = nvPr ? xml.child(nvPr, P, 'ph') : null;
|
|
45
|
+
return {
|
|
46
|
+
el,
|
|
47
|
+
id: cNvPr ? Number(cNvPr.getAttribute('id')) : null,
|
|
48
|
+
name: cNvPr?.getAttribute('name') ?? '',
|
|
49
|
+
kind,
|
|
50
|
+
placeholder: ph ? ph.getAttribute('type') || 'obj' : null,
|
|
51
|
+
group
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function walk(container, group, out) {
|
|
55
|
+
for (const el of xml.children(container, P)) {
|
|
56
|
+
const kind = kindOf(el);
|
|
57
|
+
if (!kind) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const shape = entryOf(el, kind, group);
|
|
61
|
+
out.push(shape);
|
|
62
|
+
if (kind === 'group') {
|
|
63
|
+
walk(el, shape.id, out);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export function shapesOf(doc) {
|
|
68
|
+
const spTree = xml.descendants(doc, P, 'spTree')[0];
|
|
69
|
+
const out = [];
|
|
70
|
+
if (spTree) {
|
|
71
|
+
walk(spTree, null, out);
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
export function placeholderFrom(doc, source, id) {
|
|
76
|
+
const p = xml.builder(doc, P);
|
|
77
|
+
const a = xml.builder(doc, A);
|
|
78
|
+
const sourceNv = xml.children(source.el)[0];
|
|
79
|
+
const sourceNvPr = sourceNv ? xml.child(sourceNv, P, 'nvPr') : null;
|
|
80
|
+
const sourcePh = sourceNvPr ? xml.child(sourceNvPr, P, 'ph') : null;
|
|
81
|
+
const sp = p('sp');
|
|
82
|
+
const nvSpPr = xml.append(sp, p('nvSpPr'));
|
|
83
|
+
nvSpPr.appendChild(p('cNvPr', { id, name: source.name || `Placeholder ${id - 1}` }));
|
|
84
|
+
xml.append(nvSpPr, p('cNvSpPr')).appendChild(a('spLocks', { noGrp: 1 }));
|
|
85
|
+
const ph = xml.append(xml.append(nvSpPr, p('nvPr')), p('ph'));
|
|
86
|
+
for (const name of ['type', 'orient', 'sz', 'idx']) {
|
|
87
|
+
const value = sourcePh?.getAttribute(name);
|
|
88
|
+
if (value !== null && value !== undefined) {
|
|
89
|
+
ph.setAttribute(name, value);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
sp.appendChild(p('spPr'));
|
|
93
|
+
text.ensureTextBody(sp, SHAPE_ORDER);
|
|
94
|
+
return sp;
|
|
95
|
+
}
|
|
96
|
+
function xfrm(doc, box, namespace) {
|
|
97
|
+
const a = xml.builder(doc, A);
|
|
98
|
+
const el = xml.builder(doc, namespace)('xfrm');
|
|
99
|
+
el.appendChild(a('off', { x: box.x, y: box.y }));
|
|
100
|
+
el.appendChild(a('ext', { cx: box.width, cy: box.height }));
|
|
101
|
+
return el;
|
|
102
|
+
}
|
|
103
|
+
function rectangle(doc) {
|
|
104
|
+
const a = xml.builder(doc, A);
|
|
105
|
+
const geom = a('prstGeom', { prst: 'rect' });
|
|
106
|
+
geom.appendChild(a('avLst'));
|
|
107
|
+
return geom;
|
|
108
|
+
}
|
|
109
|
+
export function pictureElement(doc, frame) {
|
|
110
|
+
const p = xml.builder(doc, P);
|
|
111
|
+
const a = xml.builder(doc, A);
|
|
112
|
+
const pic = p('pic');
|
|
113
|
+
const nv = xml.append(pic, p('nvPicPr'));
|
|
114
|
+
nv.appendChild(p('cNvPr', {
|
|
115
|
+
id: frame.id,
|
|
116
|
+
name: frame.name || `Picture ${frame.id - 1}`,
|
|
117
|
+
descr: frame.description
|
|
118
|
+
}));
|
|
119
|
+
xml.append(nv, p('cNvPicPr')).appendChild(a('picLocks', { noChangeAspect: 1 }));
|
|
120
|
+
nv.appendChild(p('nvPr'));
|
|
121
|
+
const fill = xml.append(pic, p('blipFill'));
|
|
122
|
+
const blip = xml.append(fill, a('blip'));
|
|
123
|
+
xml.setNsAttribute(blip, { namespace: R, local: 'embed' }, frame.rId);
|
|
124
|
+
xml.append(fill, a('stretch')).appendChild(a('fillRect'));
|
|
125
|
+
const spPr = xml.append(pic, p('spPr'));
|
|
126
|
+
spPr.appendChild(xfrm(doc, frame.box, A));
|
|
127
|
+
spPr.appendChild(rectangle(doc));
|
|
128
|
+
return pic;
|
|
129
|
+
}
|
|
130
|
+
export function nextShapeId(doc) {
|
|
131
|
+
let max = 0;
|
|
132
|
+
for (const el of xml.descendants(doc, P, 'cNvPr')) {
|
|
133
|
+
max = Math.max(max, Number(el.getAttribute('id')) || 0);
|
|
134
|
+
}
|
|
135
|
+
return max + 1;
|
|
136
|
+
}
|
|
137
|
+
function solidFill(doc, color) {
|
|
138
|
+
const a = xml.builder(doc, A);
|
|
139
|
+
const fill = a('solidFill');
|
|
140
|
+
fill.appendChild(a('srgbClr', { val: color.replace('#', '').toUpperCase() }));
|
|
141
|
+
return fill;
|
|
142
|
+
}
|
|
143
|
+
export function validateColor(color, name = 'color') {
|
|
144
|
+
if (typeof color !== 'string' || !/^#?[0-9a-fA-F]{6}$/.test(color)) {
|
|
145
|
+
throw new DocsError('INVALID_STYLE', `${name} must be a hex RGB value like "1A73E8", got ${String(color)}`);
|
|
146
|
+
}
|
|
147
|
+
return color;
|
|
148
|
+
}
|
|
149
|
+
export function textBoxElement(doc, frame) {
|
|
150
|
+
const p = xml.builder(doc, P);
|
|
151
|
+
const a = xml.builder(doc, A);
|
|
152
|
+
const sp = p('sp');
|
|
153
|
+
const nv = xml.append(sp, p('nvSpPr'));
|
|
154
|
+
nv.appendChild(p('cNvPr', { id: frame.id, name: frame.name || `TextBox ${frame.id - 1}` }));
|
|
155
|
+
nv.appendChild(p('cNvSpPr', { txBox: 1 }));
|
|
156
|
+
nv.appendChild(p('nvPr'));
|
|
157
|
+
const spPr = xml.append(sp, p('spPr'));
|
|
158
|
+
spPr.appendChild(xfrm(doc, frame.box, A));
|
|
159
|
+
spPr.appendChild(rectangle(doc));
|
|
160
|
+
spPr.appendChild(frame.fill ? solidFill(doc, validateColor(frame.fill, 'fill')) : a('noFill'));
|
|
161
|
+
const txBody = text.ensureTextBody(sp, SHAPE_ORDER);
|
|
162
|
+
xml.child(txBody, A, 'bodyPr')?.setAttribute('wrap', 'square');
|
|
163
|
+
text.writeParagraphs(txBody, frame.text === undefined ? '' : frame.text);
|
|
164
|
+
if (frame.style) {
|
|
165
|
+
text.applyStyle(txBody, frame.style);
|
|
166
|
+
}
|
|
167
|
+
return sp;
|
|
168
|
+
}
|
|
169
|
+
function isCell(value) {
|
|
170
|
+
return (value === null ||
|
|
171
|
+
typeof value === 'string' ||
|
|
172
|
+
(typeof value === 'number' && Number.isFinite(value)));
|
|
173
|
+
}
|
|
174
|
+
export function validateRows(rows) {
|
|
175
|
+
if (!Array.isArray(rows) || rows.length === 0 || !rows.every(Array.isArray)) {
|
|
176
|
+
throw new DocsError('INVALID_TABLE', 'rows must be a non-empty array of rows, each an array of cells');
|
|
177
|
+
}
|
|
178
|
+
const out = rows.map((row, r) => row.map((value, c) => {
|
|
179
|
+
if (isCell(value))
|
|
180
|
+
return value;
|
|
181
|
+
throw new DocsError('INVALID_TABLE', `cell ${r},${c} must be a string, a number or null`);
|
|
182
|
+
}));
|
|
183
|
+
const columns = Math.max(...out.map((r) => r.length));
|
|
184
|
+
if (columns === 0) {
|
|
185
|
+
throw new DocsError('INVALID_TABLE', 'the table has no columns');
|
|
186
|
+
}
|
|
187
|
+
if (out.length > 1000 || columns > 64) {
|
|
188
|
+
throw new DocsError('INVALID_TABLE', 'a table holds at most 1000 rows and 64 columns');
|
|
189
|
+
}
|
|
190
|
+
return out;
|
|
191
|
+
}
|
|
192
|
+
function graphicFrame(doc, spec) {
|
|
193
|
+
const p = xml.builder(doc, P);
|
|
194
|
+
const a = xml.builder(doc, A);
|
|
195
|
+
const frame = p('graphicFrame');
|
|
196
|
+
const nv = xml.append(frame, p('nvGraphicFramePr'));
|
|
197
|
+
nv.appendChild(p('cNvPr', { id: spec.id, name: spec.name }));
|
|
198
|
+
const locks = xml.append(nv, p('cNvGraphicFramePr'));
|
|
199
|
+
if (spec.locked) {
|
|
200
|
+
locks.appendChild(a('graphicFrameLocks', { noGrp: 1 }));
|
|
201
|
+
}
|
|
202
|
+
nv.appendChild(p('nvPr'));
|
|
203
|
+
frame.appendChild(xfrm(doc, spec.box, P));
|
|
204
|
+
const data = xml.append(xml.append(frame, a('graphic')), a('graphicData', { uri: spec.uri }));
|
|
205
|
+
return { frame, data };
|
|
206
|
+
}
|
|
207
|
+
function tableCell(doc, value) {
|
|
208
|
+
const a = xml.builder(doc, A);
|
|
209
|
+
const tc = a('tc');
|
|
210
|
+
const txBody = xml.append(tc, a('txBody'));
|
|
211
|
+
for (const local of ['bodyPr', 'lstStyle', 'p']) {
|
|
212
|
+
txBody.appendChild(a(local));
|
|
213
|
+
}
|
|
214
|
+
text.writeParagraphs(txBody, value === null || value === undefined ? '' : String(value));
|
|
215
|
+
tc.appendChild(a('tcPr'));
|
|
216
|
+
return tc;
|
|
217
|
+
}
|
|
218
|
+
export function tableElement(doc, frame) {
|
|
219
|
+
const rows = validateRows(frame.rows);
|
|
220
|
+
const columns = Math.max(...rows.map((r) => r.length));
|
|
221
|
+
const a = xml.builder(doc, A);
|
|
222
|
+
const { frame: el, data } = graphicFrame(doc, {
|
|
223
|
+
id: frame.id,
|
|
224
|
+
box: frame.box,
|
|
225
|
+
name: frame.name || `Table ${frame.id - 1}`,
|
|
226
|
+
uri: TABLE_URI,
|
|
227
|
+
locked: true
|
|
228
|
+
});
|
|
229
|
+
const tbl = xml.append(data, a('tbl'));
|
|
230
|
+
const tblPr = xml.append(tbl, a('tblPr', { firstRow: frame.header === false ? undefined : 1, bandRow: 1 }));
|
|
231
|
+
xml.setText(xml.append(tblPr, a('tableStyleId')), frame.tableStyle);
|
|
232
|
+
const grid = xml.append(tbl, a('tblGrid'));
|
|
233
|
+
const width = Math.floor(frame.box.width / columns);
|
|
234
|
+
for (let c = 0; c < columns; c++) {
|
|
235
|
+
grid.appendChild(a('gridCol', { w: width }));
|
|
236
|
+
}
|
|
237
|
+
const height = Math.floor(frame.box.height / rows.length);
|
|
238
|
+
for (const row of rows) {
|
|
239
|
+
const tr = xml.append(tbl, a('tr', { h: height }));
|
|
240
|
+
for (let c = 0; c < columns; c++) {
|
|
241
|
+
tr.appendChild(tableCell(doc, row[c]));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return el;
|
|
245
|
+
}
|
|
246
|
+
export function chartFrameElement(doc, frame) {
|
|
247
|
+
const { frame: el, data } = graphicFrame(doc, {
|
|
248
|
+
id: frame.id,
|
|
249
|
+
box: frame.box,
|
|
250
|
+
name: frame.name || `Chart ${frame.id - 1}`,
|
|
251
|
+
uri: chart.GRAPHIC_URI,
|
|
252
|
+
locked: false
|
|
253
|
+
});
|
|
254
|
+
const ref = xml.append(data, xml.builder(doc, chart.C)('chart'));
|
|
255
|
+
xml.setNsAttribute(ref, { namespace: R, local: 'id' }, frame.rId);
|
|
256
|
+
return el;
|
|
257
|
+
}
|
|
258
|
+
export function setBackground(doc, color) {
|
|
259
|
+
const cSld = xml.child(xml.root(doc), P, 'cSld');
|
|
260
|
+
if (!cSld) {
|
|
261
|
+
throw new DocsError('BROKEN_PPTX', 'the slide has no common slide data');
|
|
262
|
+
}
|
|
263
|
+
const existing = xml.child(cSld, P, 'bg');
|
|
264
|
+
if (existing) {
|
|
265
|
+
xml.remove(existing);
|
|
266
|
+
}
|
|
267
|
+
if (color === null) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const p = xml.builder(doc, P);
|
|
271
|
+
const bg = p('bg');
|
|
272
|
+
const bgPr = xml.append(bg, p('bgPr'));
|
|
273
|
+
bgPr.appendChild(solidFill(doc, validateColor(color)));
|
|
274
|
+
bgPr.appendChild(xml.builder(doc, A)('effectLst'));
|
|
275
|
+
cSld.insertBefore(bg, cSld.firstChild);
|
|
276
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Element } from '../xml.ts';
|
|
2
|
+
import type { Package } from '../opc/package.ts';
|
|
3
|
+
import type { AddSlideOptions } from './types.ts';
|
|
4
|
+
export interface Deck {
|
|
5
|
+
pkg: Package;
|
|
6
|
+
main: string;
|
|
7
|
+
}
|
|
8
|
+
export interface SlideEntry {
|
|
9
|
+
el: Element;
|
|
10
|
+
id: string;
|
|
11
|
+
rId: string;
|
|
12
|
+
part: string;
|
|
13
|
+
}
|
|
14
|
+
interface LayoutEntry {
|
|
15
|
+
master: string;
|
|
16
|
+
part: string;
|
|
17
|
+
}
|
|
18
|
+
export declare const PRESENTATION_ORDER: string[];
|
|
19
|
+
export declare function slideIdList(deck: Deck): Element | null;
|
|
20
|
+
export declare function ensureSlideIdList(deck: Deck): Element;
|
|
21
|
+
export declare function entries(deck: Deck): SlideEntry[];
|
|
22
|
+
export declare function slideAt(deck: Deck, index: unknown): SlideEntry;
|
|
23
|
+
export declare function layoutOf(pkg: Package, slidePart: string): string | null;
|
|
24
|
+
export declare function allLayouts(deck: Deck): LayoutEntry[];
|
|
25
|
+
export declare function pickLayout(deck: Deck, opts: AddSlideOptions): string;
|
|
26
|
+
export declare function placeInSection(deck: Deck, id: string): void;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import * as xml from '../xml.js';
|
|
2
|
+
import { DocsError } from '../errors.js';
|
|
3
|
+
import { layoutName, layoutType, hasBody } from './read.js';
|
|
4
|
+
const { p: P, r: R, p14: P14 } = xml.ns;
|
|
5
|
+
const REL_BASE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/';
|
|
6
|
+
export const PRESENTATION_ORDER = [
|
|
7
|
+
'sldMasterIdLst',
|
|
8
|
+
'notesMasterIdLst',
|
|
9
|
+
'handoutMasterIdLst',
|
|
10
|
+
'sldIdLst',
|
|
11
|
+
'sldSz'
|
|
12
|
+
];
|
|
13
|
+
export function slideIdList(deck) {
|
|
14
|
+
return xml.child(deck.pkg.root(deck.main), P, 'sldIdLst');
|
|
15
|
+
}
|
|
16
|
+
export function ensureSlideIdList(deck) {
|
|
17
|
+
const existing = slideIdList(deck);
|
|
18
|
+
if (existing) {
|
|
19
|
+
return existing;
|
|
20
|
+
}
|
|
21
|
+
const top = deck.pkg.root(deck.main);
|
|
22
|
+
return xml.insertOrdered(top, xml.builder(xml.owner(top), P)('sldIdLst'), PRESENTATION_ORDER);
|
|
23
|
+
}
|
|
24
|
+
export function entries(deck) {
|
|
25
|
+
const list = slideIdList(deck);
|
|
26
|
+
if (!list) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
const rels = deck.pkg.rels(deck.main);
|
|
30
|
+
return xml.children(list, P, 'sldId').map((el) => {
|
|
31
|
+
const rId = el.getAttributeNS(R, 'id');
|
|
32
|
+
const rel = rels.get(rId);
|
|
33
|
+
if (!rel || rel.external || !rel.part || !deck.pkg.has(rel.part)) {
|
|
34
|
+
throw new DocsError('BROKEN_PPTX', `slide id ${String(el.getAttribute('id'))} points at a missing part`);
|
|
35
|
+
}
|
|
36
|
+
return { el, id: xml.attr(el, 'id'), rId: rId ?? '', part: rel.part };
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export function slideAt(deck, index) {
|
|
40
|
+
const all = entries(deck);
|
|
41
|
+
const found = typeof index === 'number' && Number.isInteger(index) ? all[index] : undefined;
|
|
42
|
+
if (!found) {
|
|
43
|
+
const range = all.length ? ` (0 to ${all.length - 1})` : '';
|
|
44
|
+
throw new DocsError('SLIDE_NOT_FOUND', `slide ${String(index)} does not exist; the deck has ${all.length} slides${range}`);
|
|
45
|
+
}
|
|
46
|
+
return found;
|
|
47
|
+
}
|
|
48
|
+
export function layoutOf(pkg, slidePart) {
|
|
49
|
+
return pkg.related(slidePart, REL_BASE + 'slideLayout')[0] ?? null;
|
|
50
|
+
}
|
|
51
|
+
export function allLayouts(deck) {
|
|
52
|
+
return deck.pkg
|
|
53
|
+
.related(deck.main, REL_BASE + 'slideMaster')
|
|
54
|
+
.flatMap((master) => deck.pkg.related(master, REL_BASE + 'slideLayout').map((part) => ({ master, part })));
|
|
55
|
+
}
|
|
56
|
+
function layoutByName(deck, layouts, name) {
|
|
57
|
+
const wanted = name.toLowerCase();
|
|
58
|
+
const match = layouts.find(({ part }) => layoutName(deck.pkg.xml(part)).toLowerCase() === wanted);
|
|
59
|
+
if (match) {
|
|
60
|
+
return match.part;
|
|
61
|
+
}
|
|
62
|
+
const names = layouts.map(({ part }) => `"${layoutName(deck.pkg.xml(part))}"`);
|
|
63
|
+
throw new DocsError('LAYOUT_NOT_FOUND', `no layout is named "${name}"; the deck has ${names.join(', ')}`);
|
|
64
|
+
}
|
|
65
|
+
function defaultLayout(deck, layouts) {
|
|
66
|
+
const { pkg } = deck;
|
|
67
|
+
const used = entries(deck)
|
|
68
|
+
.map((e) => layoutOf(pkg, e.part))
|
|
69
|
+
.filter((part) => part !== null && layoutType(pkg.xml(part)) !== 'title' && hasBody(pkg.xml(part)));
|
|
70
|
+
const last = used[used.length - 1];
|
|
71
|
+
if (last) {
|
|
72
|
+
return last;
|
|
73
|
+
}
|
|
74
|
+
const content = layouts.find(({ part }) => ['obj', 'tx'].includes(layoutType(pkg.xml(part))));
|
|
75
|
+
return (content ?? layouts[0]).part;
|
|
76
|
+
}
|
|
77
|
+
export function pickLayout(deck, opts) {
|
|
78
|
+
if (opts.like !== undefined && opts.layout !== undefined) {
|
|
79
|
+
throw new DocsError('INVALID_OPTIONS', 'pass either like or layout, not both');
|
|
80
|
+
}
|
|
81
|
+
if (opts.like !== undefined) {
|
|
82
|
+
const layout = layoutOf(deck.pkg, slideAt(deck, opts.like).part);
|
|
83
|
+
if (!layout) {
|
|
84
|
+
throw new DocsError('BROKEN_PPTX', `slide ${opts.like} has no layout`);
|
|
85
|
+
}
|
|
86
|
+
return layout;
|
|
87
|
+
}
|
|
88
|
+
const layouts = allLayouts(deck);
|
|
89
|
+
if (layouts.length === 0) {
|
|
90
|
+
throw new DocsError('BROKEN_PPTX', 'the deck has no slide layouts');
|
|
91
|
+
}
|
|
92
|
+
if (opts.layout !== undefined) {
|
|
93
|
+
return layoutByName(deck, layouts, String(opts.layout));
|
|
94
|
+
}
|
|
95
|
+
return defaultLayout(deck, layouts);
|
|
96
|
+
}
|
|
97
|
+
function sectionIds(list) {
|
|
98
|
+
return xml.children(list, P14, 'sldId').map((ref) => xml.attr(ref, 'id'));
|
|
99
|
+
}
|
|
100
|
+
export function placeInSection(deck, id) {
|
|
101
|
+
const lists = xml.descendants(deck.pkg.root(deck.main), P14, 'sldIdLst');
|
|
102
|
+
if (lists.length === 0) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
for (const list of lists) {
|
|
106
|
+
for (const ref of xml.children(list, P14, 'sldId')) {
|
|
107
|
+
if (ref.getAttribute('id') === id) {
|
|
108
|
+
xml.remove(ref);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const order = entries(deck).map((e) => e.id);
|
|
113
|
+
const pos = order.indexOf(id);
|
|
114
|
+
const neighbor = order[pos - 1] ?? order[pos + 1];
|
|
115
|
+
const home = lists.find((list) => sectionIds(list).includes(neighbor)) ?? lists[0];
|
|
116
|
+
home.appendChild(xml.setAttributes(xml.named(xml.owner(home), `${home.prefix || 'p14'}:sldId`, P14), { id }));
|
|
117
|
+
const sorted = xml
|
|
118
|
+
.children(home, P14, 'sldId')
|
|
119
|
+
.sort((x, y) => order.indexOf(xml.attr(x, 'id')) - order.indexOf(xml.attr(y, 'id')));
|
|
120
|
+
for (const el of sorted) {
|
|
121
|
+
home.appendChild(el);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Package } from '../opc/package.ts';
|
|
2
|
+
export interface SlideSize {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
}
|
|
6
|
+
export type ThemeColor = 'dark1' | 'light1' | 'dark2' | 'light2' | 'accent1' | 'accent2' | 'accent3' | 'accent4' | 'accent5' | 'accent6' | 'hyperlink' | 'followedHyperlink';
|
|
7
|
+
export interface ThemeFonts {
|
|
8
|
+
heading: string;
|
|
9
|
+
body: string;
|
|
10
|
+
}
|
|
11
|
+
export interface CreateOptions {
|
|
12
|
+
size?: '16:9' | '4:3' | SlideSize;
|
|
13
|
+
title?: string;
|
|
14
|
+
fonts?: Partial<ThemeFonts>;
|
|
15
|
+
colors?: Partial<Record<ThemeColor, string>>;
|
|
16
|
+
}
|
|
17
|
+
export declare const SIZES: Record<'16:9' | '4:3', SlideSize>;
|
|
18
|
+
export declare const TABLE_STYLE = "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}";
|
|
19
|
+
export declare function build(pkg: Package, opts?: unknown): void;
|
|
20
|
+
export declare function notesTheme(): Uint8Array;
|