pptxdiff 0.1.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/LICENSE +201 -0
- package/README.md +85 -0
- package/bin/cli.js +46 -0
- package/package.json +38 -0
- package/src/pptxdiff/index.html +3872 -0
- package/src/pptxdiff/sample-pptx.js +300 -0
- package/src/pptxdiff/support.js +1603 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
// Builds real, minimal-but-valid .pptx buffers from a compact shape spec, covering the full
|
|
2
|
+
// diff-engine feature set: text, images, tables (with per-cell formatting), charts, SmartArt,
|
|
3
|
+
// backgrounds, animations, transitions, speaker notes, hyperlinks, borders/wrap, embedded fonts,
|
|
4
|
+
// and dummy video/audio media. Used both to render (via @aiden0z/pptx-renderer) and to diff
|
|
5
|
+
// (via the app's XML parser), so the sample is a single source of truth. Requires global JSZip.
|
|
6
|
+
|
|
7
|
+
const NS = {
|
|
8
|
+
ct: 'http://schemas.openxmlformats.org/package/2006/content-types',
|
|
9
|
+
rel: 'http://schemas.openxmlformats.org/package/2006/relationships',
|
|
10
|
+
oR: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
|
|
11
|
+
a: 'http://schemas.openxmlformats.org/drawingml/2006/main',
|
|
12
|
+
p: 'http://schemas.openxmlformats.org/presentationml/2006/main'
|
|
13
|
+
};
|
|
14
|
+
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
15
|
+
const TINY_PNG_B64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=';
|
|
16
|
+
function tinyPngBytes() {
|
|
17
|
+
const bin = atob(TINY_PNG_B64);
|
|
18
|
+
const u8 = new Uint8Array(bin.length);
|
|
19
|
+
for (let i = 0; i < bin.length; i++) u8[i] = bin.charCodeAt(i);
|
|
20
|
+
return u8;
|
|
21
|
+
}
|
|
22
|
+
function dummyMediaBytes(seed, len) {
|
|
23
|
+
const u8 = new Uint8Array(len);
|
|
24
|
+
let h = 0;
|
|
25
|
+
for (let i = 0; i < seed.length; i++) h = (h * 31 + seed.charCodeAt(i)) >>> 0;
|
|
26
|
+
for (let i = 0; i < len; i++) { h = (h * 1103515245 + 12345) >>> 0; u8[i] = h & 0xff; }
|
|
27
|
+
return u8;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function shapeXml(s, id, linkRid) {
|
|
31
|
+
const wrap = s.wrap === 'none' ? 'none' : 'square';
|
|
32
|
+
const ln = s.border ? `<a:ln w="${Math.round(s.border.w * 12700)}"><a:solidFill><a:srgbClr val="${s.border.color.replace('#', '')}"/></a:solidFill></a:ln>` : '';
|
|
33
|
+
const paras = s.paras.map((p) => {
|
|
34
|
+
const algn = s.align && s.align !== 'l' ? ` algn="${s.align}"` : '';
|
|
35
|
+
const bu = p.bullet ? `<a:buFont typeface="Arial"/><a:buChar char="\u2022"/>` : `<a:buNone/>`;
|
|
36
|
+
const link = linkRid ? `<a:hlinkClick r:id="${linkRid}"/>` : '';
|
|
37
|
+
const rPr = `<a:rPr lang="en-US" sz="${Math.round(s.size * 100)}" b="${s.bold ? 1 : 0}" i="${s.italic ? 1 : 0}" dirty="0">`
|
|
38
|
+
+ `<a:solidFill><a:srgbClr val="${(s.color || '#000000').replace('#', '')}"/></a:solidFill>`
|
|
39
|
+
+ `<a:latin typeface="${esc(s.font)}"/>${link}</a:rPr>`;
|
|
40
|
+
return `<a:p><a:pPr${algn} indent="${p.bullet ? -274638 : 0}" marL="${p.bullet ? 274638 : 0}">${bu}</a:pPr>`
|
|
41
|
+
+ `<a:r>${rPr}<a:t>${esc(p.text)}</a:t></a:r></a:p>`;
|
|
42
|
+
}).join('');
|
|
43
|
+
return `<p:sp><p:nvSpPr><p:cNvPr id="${id}" name="${esc(s.name)}"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr>${s.ph ? `<p:ph type="${s.ph}"/>` : ''}</p:nvPr></p:nvSpPr>`
|
|
44
|
+
+ `<p:spPr><a:xfrm><a:off x="${Math.round(s.x)}" y="${Math.round(s.y)}"/><a:ext cx="${Math.round(s.cx)}" cy="${Math.round(s.cy)}"/></a:xfrm>`
|
|
45
|
+
+ `<a:prstGeom prst="rect"><a:avLst/></a:prstGeom>${ln}</p:spPr>`
|
|
46
|
+
+ `<p:txBody><a:bodyPr wrap="${wrap}" rtlCol="0"><a:normAutofit/></a:bodyPr><a:lstStyle/>${paras}</p:txBody></p:sp>`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function cellXml(cell) {
|
|
50
|
+
const c = typeof cell === 'string' ? { text: cell } : cell;
|
|
51
|
+
const fill = c.bg ? `<a:solidFill><a:srgbClr val="${c.bg.replace('#', '')}"/></a:solidFill>` : '<a:noFill/>';
|
|
52
|
+
const bw = c.border ? Math.round(c.border.w * 12700) : 6350;
|
|
53
|
+
const bc = c.border ? c.border.color.replace('#', '') : 'CCCCCC';
|
|
54
|
+
const side = (tag) => `<a:${tag} w="${bw}"><a:solidFill><a:srgbClr val="${bc}"/></a:solidFill></a:${tag}>`;
|
|
55
|
+
return `<a:tc><a:txBody><a:bodyPr/><a:lstStyle/><a:p><a:r><a:rPr lang="en-US" sz="1400" dirty="0"/><a:t>${esc(c.text)}</a:t></a:r></a:p></a:txBody>`
|
|
56
|
+
+ `<a:tcPr marL="45720" marR="45720" marT="22860" marB="22860">${side('lnL')}${side('lnR')}${side('lnT')}${side('lnB')}${fill}</a:tcPr></a:tc>`;
|
|
57
|
+
}
|
|
58
|
+
function tableXml(t, id) {
|
|
59
|
+
const cols = t.rows[0].map(() => Math.round(t.cx / t.rows[0].length));
|
|
60
|
+
const rowH = Math.round(t.cy / t.rows.length);
|
|
61
|
+
const trs = t.rows.map((row) => `<a:tr h="${rowH}">${row.map(cellXml).join('')}</a:tr>`).join('');
|
|
62
|
+
return `<p:graphicFrame><p:nvGraphicFramePr><p:cNvPr id="${id}" name="${esc(t.name)}"/><p:cNvGraphicFramePr/><p:nvPr/></p:nvGraphicFramePr>`
|
|
63
|
+
+ `<p:xfrm><a:off x="${Math.round(t.x)}" y="${Math.round(t.y)}"/><a:ext cx="${Math.round(t.cx)}" cy="${Math.round(t.cy)}"/></p:xfrm>`
|
|
64
|
+
+ `<a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table">`
|
|
65
|
+
+ `<a:tbl><a:tblPr firstRow="1" bandRow="1"/><a:tblGrid>${cols.map((w) => `<a:gridCol w="${w}"/>`).join('')}</a:tblGrid>${trs}</a:tbl>`
|
|
66
|
+
+ `</a:graphicData></a:graphic></p:graphicFrame>`;
|
|
67
|
+
}
|
|
68
|
+
function mediaShapeXml(m, id, mediaRid, posterRid) {
|
|
69
|
+
const tag = m.kind === 'video' ? 'videoFile' : 'audioFile';
|
|
70
|
+
return `<p:pic><p:nvPicPr><p:cNvPr id="${id}" name="${esc(m.name)}"/><p:cNvPicPr/><p:nvPr><p:${tag} r:link="${mediaRid}"/></p:nvPr></p:nvPicPr>`
|
|
71
|
+
+ `<p:blipFill><a:blip r:embed="${posterRid}"/><a:stretch><a:fillRect/></a:stretch></p:blipFill>`
|
|
72
|
+
+ `<p:spPr><a:xfrm><a:off x="${Math.round(m.x)}" y="${Math.round(m.y)}"/><a:ext cx="${Math.round(m.cx)}" cy="${Math.round(m.cy)}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr></p:pic>`;
|
|
73
|
+
}
|
|
74
|
+
function imagePicXml(img, id, rid) {
|
|
75
|
+
return `<p:pic><p:nvPicPr><p:cNvPr id="${id}" name="${esc(img.name)}"/><p:cNvPicPr/><p:nvPr/></p:nvPicPr>`
|
|
76
|
+
+ `<p:blipFill><a:blip r:embed="${rid}"/><a:stretch><a:fillRect/></a:stretch></p:blipFill>`
|
|
77
|
+
+ `<p:spPr><a:xfrm><a:off x="${Math.round(img.x)}" y="${Math.round(img.y)}"/><a:ext cx="${Math.round(img.cx)}" cy="${Math.round(img.cy)}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr></p:pic>`;
|
|
78
|
+
}
|
|
79
|
+
function chartPartXml(chart) {
|
|
80
|
+
const sers = chart.series.map((s, i) => `<c:ser><c:idx val="${i}"/><c:order val="${i}"/>`
|
|
81
|
+
+ `<c:tx><c:strRef><c:f>Sheet1!$${String.fromCharCode(66 + i)}$1</c:f><c:strCache><c:ptCount val="1"/><c:pt idx="0"><c:v>${esc(s.name)}</c:v></c:pt></c:strCache></c:strRef></c:tx>`
|
|
82
|
+
+ `<c:cat><c:strRef><c:f>Sheet1!$A$2:$A$${s.values.length + 1}</c:f><c:strCache><c:ptCount val="${s.values.length}"/>${s.values.map((v, j) => `<c:pt idx="${j}"><c:v>Cat${j + 1}</c:v></c:pt>`).join('')}</c:strCache></c:strRef></c:cat>`
|
|
83
|
+
+ `<c:val><c:numRef><c:f>Sheet1!$${String.fromCharCode(66 + i)}$2:$${String.fromCharCode(66 + i)}$${s.values.length + 1}</c:f><c:numCache><c:formatCode>General</c:formatCode><c:ptCount val="${s.values.length}"/>${s.values.map((v, j) => `<c:pt idx="${j}"><c:v>${v}</c:v></c:pt>`).join('')}</c:numCache></c:numRef></c:val></c:ser>`).join('');
|
|
84
|
+
const tag = { bar: 'barChart', line: 'lineChart', pie: 'pieChart' }[chart.chartType] || 'barChart';
|
|
85
|
+
const axes = tag === 'pieChart' ? '' : `<c:catAx><c:axId val="1"/><c:scaling><c:orientation val="minMax"/></c:scaling><c:delete val="0"/><c:axPos val="b"/><c:crossAx val="2"/></c:catAx><c:valAx><c:axId val="2"/><c:scaling><c:orientation val="minMax"/></c:scaling><c:delete val="0"/><c:axPos val="l"/><c:crossAx val="1"/></c:valAx>`;
|
|
86
|
+
const barDir = tag === 'barChart' ? '<c:barDir val="col"/>' : '';
|
|
87
|
+
const axIds = tag === 'pieChart' ? '' : `<c:axId val="1"/><c:axId val="2"/>`;
|
|
88
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<c:chartSpace xmlns:c="http://schemas.openxmlformats.org/drawingml/2006/chart" xmlns:a="${NS.a}" xmlns:r="${NS.oR}">`
|
|
89
|
+
+ `<c:chart><c:plotArea><c:layout/><c:${tag}>${barDir}${sers}${axIds}</c:${tag}>${axes}</c:plotArea></c:chart></c:chartSpace>`;
|
|
90
|
+
}
|
|
91
|
+
function smartArtDataXml(items) {
|
|
92
|
+
const pts = items.map((t, i) => `<dsp:sp><dsp:txBody><a:bodyPr/><a:p><a:r><a:t>${esc(t)}</a:t></a:r></a:p></dsp:txBody></dsp:sp>`).join('');
|
|
93
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<a:root xmlns:a="${NS.a}"><a:t>${items.map(esc).join('</a:t><a:t>')}</a:t></a:root>`;
|
|
94
|
+
}
|
|
95
|
+
function transitionXml(t) {
|
|
96
|
+
if (!t || t.type === 'None') return '';
|
|
97
|
+
const attrs = (t.spd ? ` spd="${t.spd}"` : '') + (t.advTm ? ` advTm="${t.advTm}" advClick="0"` : '');
|
|
98
|
+
return `<p:transition${attrs}><p:${t.type}/></p:transition>`;
|
|
99
|
+
}
|
|
100
|
+
function notesXml(spec) {
|
|
101
|
+
const s = typeof spec === 'string' ? { text: spec } : spec;
|
|
102
|
+
const lines = String(s.text).split('\n');
|
|
103
|
+
const paras = lines.map((line) => {
|
|
104
|
+
const rPr = `<a:rPr lang="en-US" b="${s.bold ? 1 : 0}" i="${s.italic ? 1 : 0}" dirty="0">`
|
|
105
|
+
+ (s.color ? `<a:solidFill><a:srgbClr val="${s.color.replace('#', '')}"/></a:solidFill>` : '')
|
|
106
|
+
+ (s.font ? `<a:latin typeface="${esc(s.font)}"/>` : '') + `</a:rPr>`;
|
|
107
|
+
return `<a:p><a:r>${rPr}<a:t>${esc(line)}</a:t></a:r></a:p>`;
|
|
108
|
+
}).join('');
|
|
109
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<p:notes xmlns:a="${NS.a}" xmlns:r="${NS.oR}" xmlns:p="${NS.p}">`
|
|
110
|
+
+ `<p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>`
|
|
111
|
+
+ `<p:grpSpPr/><p:sp><p:nvSpPr><p:cNvPr id="2" name="Notes Placeholder"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr><p:ph type="body" idx="1"/></p:nvPr></p:nvSpPr>`
|
|
112
|
+
+ `<p:spPr/><p:txBody><a:bodyPr/><a:lstStyle/>${paras}</p:txBody></p:sp></p:spTree></p:cSld></p:notes>`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function buildSlidePart(slide, slideIdx) {
|
|
116
|
+
let rid = 2;
|
|
117
|
+
const rid_ptr = { get v() { return rid; }, set v(x) { rid = x; } };
|
|
118
|
+
const rels = [`<Relationship Id="rId1" Type="${NS.oR}/slideLayout" Target="../slideLayouts/slideLayout1.xml"/>`];
|
|
119
|
+
const extraFiles = [];
|
|
120
|
+
|
|
121
|
+
const shapesXml = slide.shapes.map((s, i) => {
|
|
122
|
+
let linkRid = null;
|
|
123
|
+
if (s.link) { linkRid = 'rId' + (rid++); rels.push(`<Relationship Id="${linkRid}" Type="${NS.oR}/hyperlink" Target="${esc(s.link)}" TargetMode="External"/>`); }
|
|
124
|
+
return shapeXml(s, 2 + i, linkRid);
|
|
125
|
+
}).join('');
|
|
126
|
+
|
|
127
|
+
const tablesXml = (slide.tables || []).map((t, i) => tableXml(t, 100 + i)).join('');
|
|
128
|
+
|
|
129
|
+
const imagesXml = (slide.images || []).map((img, i) => {
|
|
130
|
+
const rid = 'rId' + (rid_ptr.v++);
|
|
131
|
+
const name = `img_${slideIdx}_${i}.png`;
|
|
132
|
+
rels.push(`<Relationship Id="${rid}" Type="${NS.oR}/image" Target="../media/${name}"/>`);
|
|
133
|
+
extraFiles.push({ path: `ppt/media/${name}`, bytes: img.bytes || tinyPngBytes() });
|
|
134
|
+
return imagePicXml(img, 300 + i, rid);
|
|
135
|
+
}).join('');
|
|
136
|
+
|
|
137
|
+
const chartsXml = (slide.charts || []).map((ch, i) => {
|
|
138
|
+
const rid = 'rId' + (rid_ptr.v++);
|
|
139
|
+
const name = `chart_${slideIdx}_${i}.xml`;
|
|
140
|
+
rels.push(`<Relationship Id="${rid}" Type="${NS.oR}/chart" Target="../charts/${name}"/>`);
|
|
141
|
+
extraFiles.push({ path: `ppt/charts/${name}`, content: chartPartXml(ch) });
|
|
142
|
+
return `<p:graphicFrame><p:nvGraphicFramePr><p:cNvPr id="${400 + i}" name="${esc(ch.name)}"/><p:cNvGraphicFramePr/><p:nvPr/></p:nvGraphicFramePr>`
|
|
143
|
+
+ `<p:xfrm><a:off x="${Math.round(ch.x)}" y="${Math.round(ch.y)}"/><a:ext cx="${Math.round(ch.cx)}" cy="${Math.round(ch.cy)}"/></p:xfrm>`
|
|
144
|
+
+ `<a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/chart"><c:chart xmlns:c="http://schemas.openxmlformats.org/drawingml/2006/chart" xmlns:r="${NS.oR}" r:id="${rid}"/></a:graphicData></a:graphic></p:graphicFrame>`;
|
|
145
|
+
}).join('');
|
|
146
|
+
|
|
147
|
+
const smartArtXml = (slide.smartArt || []).map((sa, i) => {
|
|
148
|
+
const rid = 'rId' + (rid_ptr.v++);
|
|
149
|
+
const name = `data_${slideIdx}_${i}.xml`;
|
|
150
|
+
rels.push(`<Relationship Id="${rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramData" Target="../diagrams/${name}"/>`);
|
|
151
|
+
extraFiles.push({ path: `ppt/diagrams/${name}`, content: smartArtDataXml(sa.texts) });
|
|
152
|
+
return `<p:graphicFrame><p:nvGraphicFramePr><p:cNvPr id="${500 + i}" name="${esc(sa.name)}"/><p:cNvGraphicFramePr/><p:nvPr/></p:nvGraphicFramePr>`
|
|
153
|
+
+ `<p:xfrm><a:off x="${Math.round(sa.x)}" y="${Math.round(sa.y)}"/><a:ext cx="${Math.round(sa.cx)}" cy="${Math.round(sa.cy)}"/></p:xfrm>`
|
|
154
|
+
+ `<a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/diagram"><dgm:relIds xmlns:dgm="http://schemas.openxmlformats.org/drawingml/2006/diagram" xmlns:r="${NS.oR}" r:dm="${rid}" r:lo="${rid}" r:qs="${rid}" r:cs="${rid}"/></a:graphicData></a:graphic></p:graphicFrame>`;
|
|
155
|
+
}).join('');
|
|
156
|
+
|
|
157
|
+
const mediaXml = (slide.media || []).map((m, i) => {
|
|
158
|
+
const mediaRid = 'rId' + (rid++);
|
|
159
|
+
const ext = m.kind === 'video' ? 'mp4' : 'mp3';
|
|
160
|
+
const mediaName = `media_${slideIdx}_${i}.${ext}`;
|
|
161
|
+
rels.push(`<Relationship Id="${mediaRid}" Type="${NS.oR}/${m.kind}" Target="../media/${mediaName}"/>`);
|
|
162
|
+
const posterRid = 'rId' + (rid++);
|
|
163
|
+
const posterName = `poster_${slideIdx}_${i}.png`;
|
|
164
|
+
rels.push(`<Relationship Id="${posterRid}" Type="${NS.oR}/image" Target="../media/${posterName}"/>`);
|
|
165
|
+
extraFiles.push({ path: `ppt/media/${mediaName}`, bytes: m.bytes });
|
|
166
|
+
extraFiles.push({ path: `ppt/media/${posterName}`, bytes: tinyPngBytes() });
|
|
167
|
+
return mediaShapeXml(m, 200 + i, mediaRid, posterRid);
|
|
168
|
+
}).join('');
|
|
169
|
+
|
|
170
|
+
if (slide.notes) {
|
|
171
|
+
const notesRid = 'rId' + (rid++);
|
|
172
|
+
rels.push(`<Relationship Id="${notesRid}" Type="${NS.oR}/notesSlide" Target="../notesSlides/notesSlide${slideIdx + 1}.xml"/>`);
|
|
173
|
+
extraFiles.push({ path: `ppt/notesSlides/notesSlide${slideIdx + 1}.xml`, content: notesXml(slide.notes) });
|
|
174
|
+
extraFiles.push({ path: `ppt/notesSlides/_rels/notesSlide${slideIdx + 1}.xml.rels`, content: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Relationships xmlns="${NS.rel}"></Relationships>` });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const bg = slide.bg ? `<p:bg><p:bgPr><a:solidFill><a:srgbClr val="${slide.bg.replace('#', '')}"/></a:solidFill><a:effectLst/></p:bgPr></p:bg>` : '';
|
|
178
|
+
const trans = transitionXml(slide.transition);
|
|
179
|
+
const sldXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n`
|
|
180
|
+
+ `<p:sld xmlns:a="${NS.a}" xmlns:r="${NS.oR}" xmlns:p="${NS.p}">`
|
|
181
|
+
+ `<p:cSld>${bg}<p:spTree>`
|
|
182
|
+
+ `<p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>`
|
|
183
|
+
+ `<p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr>`
|
|
184
|
+
+ `${shapesXml}${tablesXml}${imagesXml}${chartsXml}${smartArtXml}${mediaXml}</p:spTree></p:cSld>${trans}<p:clrMapOvr><a:overrideClrMapping `
|
|
185
|
+
+ `bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/></p:clrMapOvr></p:sld>`;
|
|
186
|
+
const relsXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Relationships xmlns="${NS.rel}">${rels.join('')}</Relationships>`;
|
|
187
|
+
return { sldXml, relsXml, extraFiles };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const THEME = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
191
|
+
<a:theme xmlns:a="${NS.a}" name="Office"><a:themeElements>
|
|
192
|
+
<a:clrScheme name="Office">
|
|
193
|
+
<a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1>
|
|
194
|
+
<a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1>
|
|
195
|
+
<a:dk2><a:srgbClr val="44413A"/></a:dk2><a:lt2><a:srgbClr val="EAE4D6"/></a:lt2>
|
|
196
|
+
<a:accent1><a:srgbClr val="C9684A"/></a:accent1><a:accent2><a:srgbClr val="3E7C5A"/></a:accent2>
|
|
197
|
+
<a:accent3><a:srgbClr val="2A6FDB"/></a:accent3><a:accent4><a:srgbClr val="B23A2E"/></a:accent4>
|
|
198
|
+
<a:accent5><a:srgbClr val="7A5BD6"/></a:accent5><a:accent6><a:srgbClr val="9A9486"/></a:accent6>
|
|
199
|
+
<a:hlink><a:srgbClr val="2A6FDB"/></a:hlink><a:folHlink><a:srgbClr val="7A5BD6"/></a:folHlink>
|
|
200
|
+
</a:clrScheme>
|
|
201
|
+
<a:fontScheme name="Office">
|
|
202
|
+
<a:majorFont><a:latin typeface="Arial"/><a:ea typeface=""/><a:cs typeface=""/></a:majorFont>
|
|
203
|
+
<a:minorFont><a:latin typeface="Arial"/><a:ea typeface=""/><a:cs typeface=""/></a:minorFont>
|
|
204
|
+
</a:fontScheme>
|
|
205
|
+
<a:fmtScheme name="Office">
|
|
206
|
+
<a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:fillStyleLst>
|
|
207
|
+
<a:lnStyleLst><a:ln w="6350"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:ln><a:ln w="12700"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:ln><a:ln w="19050"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:ln></a:lnStyleLst>
|
|
208
|
+
<a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle></a:effectStyleLst>
|
|
209
|
+
<a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:bgFillStyleLst>
|
|
210
|
+
</a:fmtScheme>
|
|
211
|
+
</a:themeElements></a:theme>`;
|
|
212
|
+
|
|
213
|
+
const MASTER = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
214
|
+
<p:sldMaster xmlns:a="${NS.a}" xmlns:r="${NS.oR}" xmlns:p="${NS.p}">
|
|
215
|
+
<p:cSld><p:bg><p:bgRef idx="1001"><a:schemeClr val="bg1"/></p:bgRef></p:bg>
|
|
216
|
+
<p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
|
|
217
|
+
<p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr>
|
|
218
|
+
</p:spTree></p:cSld>
|
|
219
|
+
<p:clrMap bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/>
|
|
220
|
+
<p:sldLayoutIdLst><p:sldLayoutId id="2147483649" r:id="rId1"/></p:sldLayoutIdLst>
|
|
221
|
+
<p:txStyles>
|
|
222
|
+
<p:titleStyle><a:lvl1pPr><a:defRPr sz="1800"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mj-lt"/></a:defRPr></a:lvl1pPr></p:titleStyle>
|
|
223
|
+
<p:bodyStyle><a:lvl1pPr><a:defRPr sz="1800"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/></a:defRPr></a:lvl1pPr></p:bodyStyle>
|
|
224
|
+
<p:otherStyle><a:defPPr><a:defRPr/></a:defPPr></p:otherStyle>
|
|
225
|
+
</p:txStyles></p:sldMaster>`;
|
|
226
|
+
|
|
227
|
+
const LAYOUT = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
228
|
+
<p:sldLayout xmlns:a="${NS.a}" xmlns:r="${NS.oR}" xmlns:p="${NS.p}" type="blank" preserve="1">
|
|
229
|
+
<p:cSld name="Blank"><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
|
|
230
|
+
<p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr>
|
|
231
|
+
</p:spTree></p:cSld><p:clrMapOvr><a:overrideClrMapping bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/></p:clrMapOvr></p:sldLayout>`;
|
|
232
|
+
|
|
233
|
+
export function makeVideo(seed, kb) { return { bytes: dummyMediaBytes('video:' + seed, kb * 1024) }; }
|
|
234
|
+
export function makeAudio(seed, kb) { return { bytes: dummyMediaBytes('audio:' + seed, kb * 1024) }; }
|
|
235
|
+
|
|
236
|
+
export async function buildPptx(slides, cx, cy, opts) {
|
|
237
|
+
if (typeof JSZip === 'undefined') throw new Error('JSZip not loaded');
|
|
238
|
+
opts = opts || {};
|
|
239
|
+
const zip = new JSZip();
|
|
240
|
+
const fonts = opts.embeddedFonts || [];
|
|
241
|
+
|
|
242
|
+
zip.file('[Content_Types].xml',
|
|
243
|
+
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Types xmlns="${NS.ct}">`
|
|
244
|
+
+ `<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>`
|
|
245
|
+
+ `<Default Extension="xml" ContentType="application/xml"/>`
|
|
246
|
+
+ `<Default Extension="png" ContentType="image/png"/>`
|
|
247
|
+
+ `<Default Extension="mp4" ContentType="video/mp4"/>`
|
|
248
|
+
+ `<Default Extension="mp3" ContentType="audio/mpeg"/>`
|
|
249
|
+
+ `<Default Extension="fntdata" ContentType="application/x-font-fntdata"/>`
|
|
250
|
+
+ `<Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/>`
|
|
251
|
+
+ `<Override PartName="/ppt/slideMasters/slideMaster1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"/>`
|
|
252
|
+
+ `<Override PartName="/ppt/slideLayouts/slideLayout1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"/>`
|
|
253
|
+
+ slides.map((_, i) => `<Override PartName="/ppt/slides/slide${i + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>`).join('')
|
|
254
|
+
+ slides.map((sl, i) => sl.notes ? `<Override PartName="/ppt/notesSlides/notesSlide${i + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml"/>` : '').join('')
|
|
255
|
+
+ slides.map((sl, i) => (sl.charts || []).map((_, ci) => `<Override PartName="/ppt/charts/chart_${i}_${ci}.xml" ContentType="application/vnd.openxmlformats-officedocument.drawingml.chart+xml"/>`).join('')).join('')
|
|
256
|
+
+ `<Override PartName="/ppt/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>`
|
|
257
|
+
+ `</Types>`);
|
|
258
|
+
|
|
259
|
+
zip.file('_rels/.rels',
|
|
260
|
+
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Relationships xmlns="${NS.rel}">`
|
|
261
|
+
+ `<Relationship Id="rId1" Type="${NS.oR}/officeDocument" Target="ppt/presentation.xml"/></Relationships>`);
|
|
262
|
+
|
|
263
|
+
const sldIds = slides.map((_, i) => `<p:sldId id="${256 + i}" r:id="rId${10 + i}"/>`).join('');
|
|
264
|
+
const embedFontLst = fonts.length
|
|
265
|
+
? `<p:embeddedFontLst>${fonts.map((f, i) => `<p:embeddedFont><p:font typeface="${esc(f)}"/><p:regular r:id="rIdFont${i}"/></p:embeddedFont>`).join('')}</p:embeddedFontLst>`
|
|
266
|
+
: '';
|
|
267
|
+
zip.file('ppt/presentation.xml',
|
|
268
|
+
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<p:presentation xmlns:a="${NS.a}" xmlns:r="${NS.oR}" xmlns:p="${NS.p}"${fonts.length ? ' embedTrueTypeFonts="1"' : ''}>`
|
|
269
|
+
+ `<p:sldMasterIdLst><p:sldMasterId id="2147483648" r:id="rId1"/></p:sldMasterIdLst>`
|
|
270
|
+
+ `<p:sldIdLst>${sldIds}</p:sldIdLst>`
|
|
271
|
+
+ `<p:sldSz cx="${cx}" cy="${cy}"/><p:notesSz cx="6858000" cy="9144000"/>${embedFontLst}</p:presentation>`);
|
|
272
|
+
|
|
273
|
+
const presRels = [`<Relationship Id="rId1" Type="${NS.oR}/slideMaster" Target="slideMasters/slideMaster1.xml"/>`,
|
|
274
|
+
`<Relationship Id="rId2" Type="${NS.oR}/theme" Target="theme/theme1.xml"/>`]
|
|
275
|
+
.concat(slides.map((_, i) => `<Relationship Id="rId${10 + i}" Type="${NS.oR}/slide" Target="slides/slide${i + 1}.xml"/>`))
|
|
276
|
+
.concat(fonts.map((f, i) => `<Relationship Id="rIdFont${i}" Type="${NS.oR}/font" Target="fonts/font${i}.fntdata"/>`))
|
|
277
|
+
.join('');
|
|
278
|
+
zip.file('ppt/_rels/presentation.xml.rels', `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Relationships xmlns="${NS.rel}">${presRels}</Relationships>`);
|
|
279
|
+
fonts.forEach((f, i) => zip.file(`ppt/fonts/font${i}.fntdata`, dummyMediaBytes('font:' + f, 512)));
|
|
280
|
+
|
|
281
|
+
zip.file('ppt/theme/theme1.xml', THEME);
|
|
282
|
+
zip.file('ppt/slideMasters/slideMaster1.xml', MASTER);
|
|
283
|
+
zip.file('ppt/slideMasters/_rels/slideMaster1.xml.rels',
|
|
284
|
+
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Relationships xmlns="${NS.rel}">`
|
|
285
|
+
+ `<Relationship Id="rId1" Type="${NS.oR}/slideLayout" Target="../slideLayouts/slideLayout1.xml"/>`
|
|
286
|
+
+ `<Relationship Id="rId2" Type="${NS.oR}/theme" Target="../theme/theme1.xml"/></Relationships>`);
|
|
287
|
+
zip.file('ppt/slideLayouts/slideLayout1.xml', LAYOUT);
|
|
288
|
+
zip.file('ppt/slideLayouts/_rels/slideLayout1.xml.rels',
|
|
289
|
+
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Relationships xmlns="${NS.rel}">`
|
|
290
|
+
+ `<Relationship Id="rId1" Type="${NS.oR}/slideMaster" Target="../slideMasters/slideMaster1.xml"/></Relationships>`);
|
|
291
|
+
|
|
292
|
+
slides.forEach((sl, i) => {
|
|
293
|
+
const part = buildSlidePart(sl, i);
|
|
294
|
+
zip.file(`ppt/slides/slide${i + 1}.xml`, part.sldXml);
|
|
295
|
+
zip.file(`ppt/slides/_rels/slide${i + 1}.xml.rels`, part.relsXml);
|
|
296
|
+
part.extraFiles.forEach((f) => zip.file(f.path, f.bytes || f.content));
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
return await zip.generateAsync({ type: 'arraybuffer' });
|
|
300
|
+
}
|