dsh-design-studio 0.4.1 → 0.5.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/README.en.md +13 -7
- package/README.md +33 -11
- package/cordis.patch.yml +1 -1
- package/lib/browser.js +251 -0
- package/lib/bundle.js +228 -0
- package/lib/client.js +1067 -46
- package/lib/core.js +233 -21
- package/lib/doc.js +356 -0
- package/lib/index.js +428 -21
- package/lib/look.js +550 -0
- package/lib/picker.js +1118 -24
- package/lib/serve.js +23 -7
- package/lib/tool.js +6 -3
- package/locale/tool/en.json +2 -2
- package/locale/tool/zh.json +2 -2
- package/package.json +1 -1
- package/skills/ui-design/SKILL.md +40 -2
- package/skills/ui-design/references/app.md +1 -1
package/lib/doc.js
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 导出成文档:几屏截图(JPEG)拼成 PDF(每屏一页)或 PPT(每屏一张幻灯片,整页一张图)。
|
|
3
|
+
* 不靠第三方库:PDF 直接写对象和交叉引用表,JPEG 原样塞进去(DCTDecode);PPTX 是一个 zip,自己写最简的那几份 XML。
|
|
4
|
+
* 屏和屏之间的跳转(原型里的按钮)照样能点:PDF 是链接注释,PPT 是盖在按钮上的透明形状(放映时点了跳到那一张)。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import zlib from 'node:zlib'
|
|
8
|
+
|
|
9
|
+
/** JPEG 的宽高和通道数(读 SOF 段);不是 JPEG 返回 null */
|
|
10
|
+
export function jpegInfo(buf) {
|
|
11
|
+
if (!Buffer.isBuffer(buf) || buf.length < 4 || buf[0] !== 0xff || buf[1] !== 0xd8) return null
|
|
12
|
+
let i = 2
|
|
13
|
+
while (i + 9 < buf.length) {
|
|
14
|
+
if (buf[i] !== 0xff) return null
|
|
15
|
+
const m = buf[i + 1]
|
|
16
|
+
if (m === 0xff) { i++; continue } // 填充
|
|
17
|
+
if (m === 0xd8 || m === 0x01 || (m >= 0xd0 && m <= 0xd7)) { i += 2; continue }
|
|
18
|
+
const len = buf.readUInt16BE(i + 2)
|
|
19
|
+
// SOF0–SOF15,除了 DHT(C4)、JPG(C8)、DAC(CC)
|
|
20
|
+
if (m >= 0xc0 && m <= 0xcf && m !== 0xc4 && m !== 0xc8 && m !== 0xcc) {
|
|
21
|
+
return { height: buf.readUInt16BE(i + 5), width: buf.readUInt16BE(i + 7), comps: buf[i + 9] }
|
|
22
|
+
}
|
|
23
|
+
i += 2 + len
|
|
24
|
+
}
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 这一页上跳到别的页的地方:[{ x, y, w, h, to }],坐标是这一页的 CSS 像素,to 是目标页的下标。
|
|
30
|
+
* 跳到自己、目标不存在、在页面外的都丢掉;最多 50 个
|
|
31
|
+
*/
|
|
32
|
+
export function pageLinks(p, count, self) {
|
|
33
|
+
const out = []
|
|
34
|
+
for (const l of Array.isArray(p && p.links) ? p.links : []) {
|
|
35
|
+
const to = Number(l && l.to)
|
|
36
|
+
if (!Number.isInteger(to) || to < 0 || to >= count || to === self) continue
|
|
37
|
+
const [x, y, w, h] = [l.x, l.y, l.w, l.h].map(Number)
|
|
38
|
+
if (![x, y, w, h].every(Number.isFinite)) continue
|
|
39
|
+
const x1 = Math.max(0, x)
|
|
40
|
+
const y1 = Math.max(0, y)
|
|
41
|
+
const x2 = Math.min(p.w, x + w)
|
|
42
|
+
const y2 = Math.min(p.h, y + h)
|
|
43
|
+
if (x2 - x1 < 2 || y2 - y1 < 2) continue
|
|
44
|
+
out.push({ x: x1, y: y1, w: x2 - x1, h: y2 - y1, to })
|
|
45
|
+
if (out.length >= 50) break
|
|
46
|
+
}
|
|
47
|
+
return out
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── PDF ──────────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
/** PDF 文本串:纯 ASCII 用 ( ),带中文用 UTF-16BE 十六进制 */
|
|
53
|
+
function pdfText(s) {
|
|
54
|
+
s = String(s)
|
|
55
|
+
if (/^[\x20-\x7e]*$/.test(s)) return '(' + s.replace(/[\\()]/g, '\\$&') + ')'
|
|
56
|
+
const b = Buffer.from('' + s, 'utf16le')
|
|
57
|
+
for (let i = 0; i < b.length; i += 2) { const x = b[i]; b[i] = b[i + 1]; b[i + 1] = x }
|
|
58
|
+
return '<' + b.toString('hex').toUpperCase() + '>'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* pages: [{ jpg: Buffer, w, h, title?, links? }],w / h 是这一页的 CSS 像素(1px 按 0.75pt 算,和浏览器打印一样大)
|
|
63
|
+
* 每页一张图铺满;有名字的页进书签,links 是点了跳到别的页的地方(见 pageLinks)。
|
|
64
|
+
*/
|
|
65
|
+
export function makePdf(pages, { title = '' } = {}) {
|
|
66
|
+
const objs = [] // 下标 + 1 = 对象号
|
|
67
|
+
const add = (parts) => { objs.push(parts); return objs.length }
|
|
68
|
+
const catalog = add(null)
|
|
69
|
+
const tree = add(null)
|
|
70
|
+
const kids = pages.map(() => add(null)) // 页先占号:页上的链接要指到后面的页
|
|
71
|
+
pages.forEach((p, i) => {
|
|
72
|
+
const info = jpegInfo(p.jpg)
|
|
73
|
+
if (!info) throw new Error('不是 JPEG')
|
|
74
|
+
const cs = info.comps === 1 ? '/DeviceGray' : info.comps === 4 ? '/DeviceCMYK' : '/DeviceRGB'
|
|
75
|
+
const W = +(p.w * 0.75).toFixed(2)
|
|
76
|
+
const H = +(p.h * 0.75).toFixed(2)
|
|
77
|
+
const img = add([`<< /Type /XObject /Subtype /Image /Width ${info.width} /Height ${info.height} /ColorSpace ${cs} /BitsPerComponent 8 /Filter /DCTDecode${info.comps === 4 ? ' /Decode [1 0 1 0 1 0 1 0]' : ''} /Length ${p.jpg.length} >>\nstream\n`, p.jpg, '\nendstream'])
|
|
78
|
+
const draw = `q ${W} 0 0 ${H} 0 0 cm /Im0 Do Q`
|
|
79
|
+
const content = add([`<< /Length ${draw.length} >>\nstream\n${draw}\nendstream`])
|
|
80
|
+
const pt = (v) => +(v * 0.75).toFixed(2)
|
|
81
|
+
const annots = pageLinks(p, pages.length, i).map((l) =>
|
|
82
|
+
add([`<< /Type /Annot /Subtype /Link /Rect [${pt(l.x)} ${pt(p.h - l.y - l.h)} ${pt(l.x + l.w)} ${pt(p.h - l.y)}] /Border [0 0 0] /Dest [${kids[l.to]} 0 R /Fit] >>`]))
|
|
83
|
+
objs[kids[i] - 1] = [`<< /Type /Page /Parent ${tree} 0 R /MediaBox [0 0 ${W} ${H}] /Resources << /XObject << /Im0 ${img} 0 R >> >> /Contents ${content} 0 R` +
|
|
84
|
+
`${annots.length ? ` /Annots [${annots.map((a) => `${a} 0 R`).join(' ')}]` : ''} >>`]
|
|
85
|
+
})
|
|
86
|
+
// 书签:每屏一条,名字是屏名(只有一页就不要了)
|
|
87
|
+
const marks = pages.map((p, i) => ({ i, title: String(p.title || '').trim().slice(0, 120) })).filter((x) => x.title)
|
|
88
|
+
let outlines = 0
|
|
89
|
+
if (pages.length > 1 && marks.length) {
|
|
90
|
+
outlines = add(null)
|
|
91
|
+
const items = marks.map(() => add(null))
|
|
92
|
+
items.forEach((o, k) => {
|
|
93
|
+
objs[o - 1] = [`<< /Title ${pdfText(marks[k].title)} /Parent ${outlines} 0 R${k ? ` /Prev ${items[k - 1]} 0 R` : ''}${k < items.length - 1 ? ` /Next ${items[k + 1]} 0 R` : ''} /Dest [${kids[marks[k].i]} 0 R /Fit] >>`]
|
|
94
|
+
})
|
|
95
|
+
objs[outlines - 1] = [`<< /Type /Outlines /First ${items[0]} 0 R /Last ${items[items.length - 1]} 0 R /Count ${items.length} >>`]
|
|
96
|
+
}
|
|
97
|
+
objs[catalog - 1] = [`<< /Type /Catalog /Pages ${tree} 0 R${outlines ? ` /Outlines ${outlines} 0 R` : ''} >>`]
|
|
98
|
+
objs[tree - 1] = [`<< /Type /Pages /Kids [${kids.map((k) => `${k} 0 R`).join(' ')}] /Count ${kids.length} >>`]
|
|
99
|
+
const meta = add([`<< /Producer (dsh-design-studio)${title ? ` /Title ${pdfText(title)}` : ''} >>`])
|
|
100
|
+
|
|
101
|
+
const chunks = [Buffer.from('%PDF-1.4\n%\xe2\xe3\xcf\xd3\n', 'latin1')]
|
|
102
|
+
let at = chunks[0].length
|
|
103
|
+
const offsets = []
|
|
104
|
+
objs.forEach((parts, i) => {
|
|
105
|
+
offsets.push(at)
|
|
106
|
+
const bufs = [Buffer.from(`${i + 1} 0 obj\n`), ...parts.map((x) => (Buffer.isBuffer(x) ? x : Buffer.from(x, 'latin1'))), Buffer.from('\nendobj\n')]
|
|
107
|
+
for (const b of bufs) { chunks.push(b); at += b.length }
|
|
108
|
+
})
|
|
109
|
+
const xref = `xref\n0 ${objs.length + 1}\n0000000000 65535 f \n` + offsets.map((o) => `${String(o).padStart(10, '0')} 00000 n \n`).join('') +
|
|
110
|
+
`trailer\n<< /Size ${objs.length + 1} /Root ${catalog} 0 R /Info ${meta} 0 R >>\nstartxref\n${at}\n%%EOF\n`
|
|
111
|
+
chunks.push(Buffer.from(xref, 'latin1'))
|
|
112
|
+
return Buffer.concat(chunks)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── ZIP(PPTX 用)─────────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
const CRC_TABLE = (() => {
|
|
118
|
+
const t = new Uint32Array(256)
|
|
119
|
+
for (let n = 0; n < 256; n++) {
|
|
120
|
+
let c = n
|
|
121
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1
|
|
122
|
+
t[n] = c >>> 0
|
|
123
|
+
}
|
|
124
|
+
return t
|
|
125
|
+
})()
|
|
126
|
+
|
|
127
|
+
export function crc32(buf) {
|
|
128
|
+
let c = 0xffffffff
|
|
129
|
+
for (let i = 0; i < buf.length; i++) c = CRC_TABLE[(c ^ buf[i]) & 0xff] ^ (c >>> 8)
|
|
130
|
+
return (c ^ 0xffffffff) >>> 0
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** files: [{ name, data }];文字压缩,图片(已经压过)原样存 */
|
|
134
|
+
export function zip(files) {
|
|
135
|
+
const local = []
|
|
136
|
+
const central = []
|
|
137
|
+
let at = 0
|
|
138
|
+
// 固定一个 DOS 时间(2026-01-01 00:00),同样的输入出同样的文件
|
|
139
|
+
const dosTime = 0
|
|
140
|
+
const dosDate = ((2026 - 1980) << 9) | (1 << 5) | 1
|
|
141
|
+
for (const f of files) {
|
|
142
|
+
const name = Buffer.from(f.name, 'utf8')
|
|
143
|
+
const raw = Buffer.isBuffer(f.data) ? f.data : Buffer.from(String(f.data), 'utf8')
|
|
144
|
+
const store = /\.(jpe?g|png)$/i.test(f.name)
|
|
145
|
+
const body = store ? raw : zlib.deflateRawSync(raw)
|
|
146
|
+
const crc = crc32(raw)
|
|
147
|
+
const head = Buffer.alloc(30)
|
|
148
|
+
head.writeUInt32LE(0x04034b50, 0)
|
|
149
|
+
head.writeUInt16LE(20, 4)
|
|
150
|
+
head.writeUInt16LE(0x0800, 6) // 文件名是 UTF-8
|
|
151
|
+
head.writeUInt16LE(store ? 0 : 8, 8)
|
|
152
|
+
head.writeUInt16LE(dosTime, 10)
|
|
153
|
+
head.writeUInt16LE(dosDate, 12)
|
|
154
|
+
head.writeUInt32LE(crc, 14)
|
|
155
|
+
head.writeUInt32LE(body.length, 18)
|
|
156
|
+
head.writeUInt32LE(raw.length, 22)
|
|
157
|
+
head.writeUInt16LE(name.length, 26)
|
|
158
|
+
head.writeUInt16LE(0, 28)
|
|
159
|
+
local.push(head, name, body)
|
|
160
|
+
const cen = Buffer.alloc(46)
|
|
161
|
+
cen.writeUInt32LE(0x02014b50, 0)
|
|
162
|
+
cen.writeUInt16LE(20, 4)
|
|
163
|
+
cen.writeUInt16LE(20, 6)
|
|
164
|
+
cen.writeUInt16LE(0x0800, 8)
|
|
165
|
+
cen.writeUInt16LE(store ? 0 : 8, 10)
|
|
166
|
+
cen.writeUInt16LE(dosTime, 12)
|
|
167
|
+
cen.writeUInt16LE(dosDate, 14)
|
|
168
|
+
cen.writeUInt32LE(crc, 16)
|
|
169
|
+
cen.writeUInt32LE(body.length, 20)
|
|
170
|
+
cen.writeUInt32LE(raw.length, 24)
|
|
171
|
+
cen.writeUInt16LE(name.length, 28)
|
|
172
|
+
cen.writeUInt32LE(at, 42)
|
|
173
|
+
central.push(cen, name)
|
|
174
|
+
at += head.length + name.length + body.length
|
|
175
|
+
}
|
|
176
|
+
const cdir = Buffer.concat(central)
|
|
177
|
+
const end = Buffer.alloc(22)
|
|
178
|
+
end.writeUInt32LE(0x06054b50, 0)
|
|
179
|
+
end.writeUInt16LE(files.length, 8)
|
|
180
|
+
end.writeUInt16LE(files.length, 10)
|
|
181
|
+
end.writeUInt32LE(cdir.length, 12)
|
|
182
|
+
end.writeUInt32LE(at, 16)
|
|
183
|
+
return Buffer.concat([...local, cdir, end])
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ── PPTX ─────────────────────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
const NS_A = 'http://schemas.openxmlformats.org/drawingml/2006/main'
|
|
189
|
+
const NS_R = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'
|
|
190
|
+
const NS_P = 'http://schemas.openxmlformats.org/presentationml/2006/main'
|
|
191
|
+
const REL = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'
|
|
192
|
+
const XML = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
|
|
193
|
+
const EMU_MAX = 51206400 // PowerPoint 允许的最大边长(56 英寸)
|
|
194
|
+
const EMU_MIN = 914400
|
|
195
|
+
|
|
196
|
+
const xmlEsc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]).replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, '')
|
|
197
|
+
|
|
198
|
+
function rels(list) {
|
|
199
|
+
return XML + `<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">` +
|
|
200
|
+
list.map(([id, type, target]) => `<Relationship Id="${id}" Type="${type}" Target="${target}"/>`).join('') + '</Relationships>'
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const EMPTY_TREE = '<p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>' +
|
|
204
|
+
'<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>'
|
|
205
|
+
|
|
206
|
+
function theme() {
|
|
207
|
+
const solid = (c) => `<a:solidFill><a:schemeClr val="${c}"/></a:solidFill>`
|
|
208
|
+
const ln = (w) => `<a:ln w="${w}" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln>`
|
|
209
|
+
const font = (latin) => `<a:latin typeface="${latin}"/><a:ea typeface=""/><a:cs typeface=""/>`
|
|
210
|
+
const sys = (name, val) => `<a:${name}><a:srgbClr val="${val}"/></a:${name}>`
|
|
211
|
+
return XML + `<a:theme xmlns:a="${NS_A}" name="Design Studio"><a:themeElements>` +
|
|
212
|
+
'<a:clrScheme name="Design Studio">' + sys('dk1', '000000') + sys('lt1', 'FFFFFF') + sys('dk2', '1F2937') + sys('lt2', 'F3F4F6') +
|
|
213
|
+
sys('accent1', '4D6BFE') + sys('accent2', 'F59E0B') + sys('accent3', '10B981') + sys('accent4', 'EF4444') + sys('accent5', '8B5CF6') + sys('accent6', '06B6D4') +
|
|
214
|
+
sys('hlink', '4D6BFE') + sys('folHlink', '8B5CF6') + '</a:clrScheme>' +
|
|
215
|
+
`<a:fontScheme name="Design Studio"><a:majorFont>${font('Calibri')}</a:majorFont><a:minorFont>${font('Calibri')}</a:minorFont></a:fontScheme>` +
|
|
216
|
+
'<a:fmtScheme name="Design Studio">' +
|
|
217
|
+
`<a:fillStyleLst>${solid('phClr')}${solid('phClr')}${solid('phClr')}</a:fillStyleLst>` +
|
|
218
|
+
`<a:lnStyleLst>${ln(6350)}${ln(12700)}${ln(19050)}</a:lnStyleLst>` +
|
|
219
|
+
'<a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle></a:effectStyleLst>' +
|
|
220
|
+
`<a:bgFillStyleLst>${solid('phClr')}${solid('phClr')}${solid('phClr')}</a:bgFillStyleLst>` +
|
|
221
|
+
'</a:fmtScheme></a:themeElements><a:objectDefaults/><a:extraClrSchemeLst/></a:theme>'
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** 幻灯片尺寸:按第一屏的比例,长边 13.333 英寸(和 PowerPoint 默认 16:9 一样宽) */
|
|
225
|
+
export function slideSize(w, h) {
|
|
226
|
+
const LONG = 12192000
|
|
227
|
+
let cx = w >= h ? LONG : Math.round(LONG * w / h)
|
|
228
|
+
let cy = w >= h ? Math.round(LONG * h / w) : LONG
|
|
229
|
+
cx = Math.min(EMU_MAX, Math.max(EMU_MIN, cx))
|
|
230
|
+
cy = Math.min(EMU_MAX, Math.max(EMU_MIN, cy))
|
|
231
|
+
return { cx, cy }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* pages: [{ jpg: Buffer, w, h, title, links? }];每张幻灯片一张图,按比例放进去居中(比例不一样的留边,边上铺 bg 色)。
|
|
236
|
+
* links 上盖透明形状,放映时点了跳到那一张。bg: 'RRGGBB'
|
|
237
|
+
*/
|
|
238
|
+
export function makePptx(pages, { title = '', bg = 'FFFFFF', date = new Date() } = {}) {
|
|
239
|
+
if (!pages.length) throw new Error('没有页面')
|
|
240
|
+
const { cx, cy } = slideSize(pages[0].w, pages[0].h)
|
|
241
|
+
const fill = /^[0-9a-f]{6}$/i.test(bg) ? bg.toUpperCase() : 'FFFFFF'
|
|
242
|
+
const files = []
|
|
243
|
+
const slides = pages.map((p, i) => {
|
|
244
|
+
if (!jpegInfo(p.jpg)) throw new Error('不是 JPEG')
|
|
245
|
+
const n = i + 1
|
|
246
|
+
const k = Math.min(cx / p.w, cy / p.h)
|
|
247
|
+
const w = Math.round(p.w * k)
|
|
248
|
+
const ht = Math.round(p.h * k)
|
|
249
|
+
const x = Math.round((cx - w) / 2)
|
|
250
|
+
const y = Math.round((cy - ht) / 2)
|
|
251
|
+
const name = xmlEsc(p.title || `Screen ${n}`)
|
|
252
|
+
// 跳转:每个目标页一条关系(rId3 起),每个按钮一个透明形状
|
|
253
|
+
const targets = []
|
|
254
|
+
const hot = pageLinks(p, pages.length, i).map((l, j) => {
|
|
255
|
+
let r = targets.indexOf(l.to)
|
|
256
|
+
if (r < 0) { targets.push(l.to); r = targets.length - 1 }
|
|
257
|
+
return `<p:sp><p:nvSpPr><p:cNvPr id="${j + 3}" name="Link ${j + 1}"><a:hlinkClick r:id="rId${r + 3}" action="ppaction://hlinksldjump"/></p:cNvPr><p:cNvSpPr/><p:nvPr/></p:nvSpPr>` +
|
|
258
|
+
`<p:spPr><a:xfrm><a:off x="${x + Math.round(l.x * k)}" y="${y + Math.round(l.y * k)}"/><a:ext cx="${Math.max(1, Math.round(l.w * k))}" cy="${Math.max(1, Math.round(l.h * k))}"/></a:xfrm>` +
|
|
259
|
+
'<a:prstGeom prst="rect"><a:avLst/></a:prstGeom><a:noFill/><a:ln><a:noFill/></a:ln></p:spPr></p:sp>'
|
|
260
|
+
}).join('')
|
|
261
|
+
files.push({ name: `ppt/media/image${n}.jpeg`, data: p.jpg })
|
|
262
|
+
files.push({
|
|
263
|
+
name: `ppt/slides/slide${n}.xml`,
|
|
264
|
+
data: XML + `<p:sld xmlns:a="${NS_A}" xmlns:r="${NS_R}" xmlns:p="${NS_P}"><p:cSld name="${name}">` +
|
|
265
|
+
`<p:bg><p:bgPr><a:solidFill><a:srgbClr val="${fill}"/></a:solidFill><a:effectLst/></p:bgPr></p:bg>` +
|
|
266
|
+
`<p:spTree>${EMPTY_TREE}<p:pic><p:nvPicPr><p:cNvPr id="2" name="${name}" descr="${name}"/><p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr><p:nvPr/></p:nvPicPr>` +
|
|
267
|
+
`<p:blipFill><a:blip r:embed="rId2"/><a:stretch><a:fillRect/></a:stretch></p:blipFill>` +
|
|
268
|
+
`<p:spPr><a:xfrm><a:off x="${x}" y="${y}"/><a:ext cx="${w}" cy="${ht}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr></p:pic>` +
|
|
269
|
+
hot + '</p:spTree></p:cSld><p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr></p:sld>',
|
|
270
|
+
})
|
|
271
|
+
files.push({
|
|
272
|
+
name: `ppt/slides/_rels/slide${n}.xml.rels`,
|
|
273
|
+
data: rels([['rId1', `${REL}/slideLayout`, '../slideLayouts/slideLayout1.xml'], ['rId2', `${REL}/image`, `../media/image${n}.jpeg`],
|
|
274
|
+
...targets.map((to, r) => [`rId${r + 3}`, `${REL}/slide`, `slide${to + 1}.xml`])]),
|
|
275
|
+
})
|
|
276
|
+
return n
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
const CT = 'application/vnd.openxmlformats-officedocument.presentationml'
|
|
280
|
+
files.unshift(
|
|
281
|
+
{
|
|
282
|
+
name: '[Content_Types].xml',
|
|
283
|
+
data: XML + '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' +
|
|
284
|
+
'<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>' +
|
|
285
|
+
'<Default Extension="xml" ContentType="application/xml"/>' +
|
|
286
|
+
'<Default Extension="jpeg" ContentType="image/jpeg"/>' +
|
|
287
|
+
`<Override PartName="/ppt/presentation.xml" ContentType="${CT}.presentation.main+xml"/>` +
|
|
288
|
+
`<Override PartName="/ppt/slideMasters/slideMaster1.xml" ContentType="${CT}.slideMaster+xml"/>` +
|
|
289
|
+
`<Override PartName="/ppt/slideLayouts/slideLayout1.xml" ContentType="${CT}.slideLayout+xml"/>` +
|
|
290
|
+
slides.map((n) => `<Override PartName="/ppt/slides/slide${n}.xml" ContentType="${CT}.slide+xml"/>`).join('') +
|
|
291
|
+
`<Override PartName="/ppt/presProps.xml" ContentType="${CT}.presProps+xml"/>` +
|
|
292
|
+
`<Override PartName="/ppt/viewProps.xml" ContentType="${CT}.viewProps+xml"/>` +
|
|
293
|
+
'<Override PartName="/ppt/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>' +
|
|
294
|
+
`<Override PartName="/ppt/tableStyles.xml" ContentType="${CT}.tableStyles+xml"/>` +
|
|
295
|
+
'<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>' +
|
|
296
|
+
'<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>' +
|
|
297
|
+
'</Types>',
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: '_rels/.rels',
|
|
301
|
+
data: rels([
|
|
302
|
+
['rId1', `${REL}/officeDocument`, 'ppt/presentation.xml'],
|
|
303
|
+
['rId2', 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties', 'docProps/core.xml'],
|
|
304
|
+
['rId3', `${REL}/extended-properties`, 'docProps/app.xml'],
|
|
305
|
+
]),
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
name: 'docProps/core.xml',
|
|
309
|
+
data: XML + '<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' +
|
|
310
|
+
`<dc:title>${xmlEsc(title)}</dc:title><dc:creator>dsh-design-studio</dc:creator>` +
|
|
311
|
+
`<dcterms:created xsi:type="dcterms:W3CDTF">${date.toISOString().replace(/\.\d+Z$/, 'Z')}</dcterms:created>` +
|
|
312
|
+
'</cp:coreProperties>',
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
name: 'docProps/app.xml',
|
|
316
|
+
data: XML + '<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"><Application>dsh-design-studio</Application>' +
|
|
317
|
+
`<Slides>${slides.length}</Slides></Properties>`,
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
name: 'ppt/presentation.xml',
|
|
321
|
+
data: XML + `<p:presentation xmlns:a="${NS_A}" xmlns:r="${NS_R}" xmlns:p="${NS_P}" saveSubsetFonts="1">` +
|
|
322
|
+
'<p:sldMasterIdLst><p:sldMasterId id="2147483648" r:id="rId1"/></p:sldMasterIdLst>' +
|
|
323
|
+
`<p:sldIdLst>${slides.map((n) => `<p:sldId id="${255 + n}" r:id="rId${n + 2}"/>`).join('')}</p:sldIdLst>` +
|
|
324
|
+
`<p:sldSz cx="${cx}" cy="${cy}"/><p:notesSz cx="6858000" cy="9144000"/></p:presentation>`,
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: 'ppt/_rels/presentation.xml.rels',
|
|
328
|
+
data: rels([
|
|
329
|
+
['rId1', `${REL}/slideMaster`, 'slideMasters/slideMaster1.xml'],
|
|
330
|
+
['rId2', `${REL}/theme`, 'theme/theme1.xml'],
|
|
331
|
+
...slides.map((n) => [`rId${n + 2}`, `${REL}/slide`, `slides/slide${n}.xml`]),
|
|
332
|
+
[`rId${slides.length + 3}`, `${REL}/presProps`, 'presProps.xml'],
|
|
333
|
+
[`rId${slides.length + 4}`, `${REL}/viewProps`, 'viewProps.xml'],
|
|
334
|
+
[`rId${slides.length + 5}`, `${REL}/tableStyles`, 'tableStyles.xml'],
|
|
335
|
+
]),
|
|
336
|
+
},
|
|
337
|
+
{ name: 'ppt/presProps.xml', data: XML + `<p:presentationPr xmlns:a="${NS_A}" xmlns:r="${NS_R}" xmlns:p="${NS_P}"/>` },
|
|
338
|
+
{ name: 'ppt/viewProps.xml', data: XML + `<p:viewPr xmlns:a="${NS_A}" xmlns:r="${NS_R}" xmlns:p="${NS_P}"><p:normalViewPr><p:restoredLeft sz="15620"/><p:restoredTop sz="94660"/></p:normalViewPr><p:gridSpacing cx="76200" cy="76200"/></p:viewPr>` },
|
|
339
|
+
{ name: 'ppt/tableStyles.xml', data: XML + `<a:tblStyleLst xmlns:a="${NS_A}" def="{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}"/>` },
|
|
340
|
+
{ name: 'ppt/theme/theme1.xml', data: theme() },
|
|
341
|
+
{
|
|
342
|
+
name: 'ppt/slideMasters/slideMaster1.xml',
|
|
343
|
+
data: XML + `<p:sldMaster xmlns:a="${NS_A}" xmlns:r="${NS_R}" xmlns:p="${NS_P}"><p:cSld><p:bg><p:bgRef idx="1001"><a:schemeClr val="bg1"/></p:bgRef></p:bg><p:spTree>${EMPTY_TREE}</p:spTree></p:cSld>` +
|
|
344
|
+
'<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"/>' +
|
|
345
|
+
'<p:sldLayoutIdLst><p:sldLayoutId id="2147483649" r:id="rId1"/></p:sldLayoutIdLst></p:sldMaster>',
|
|
346
|
+
},
|
|
347
|
+
{ name: 'ppt/slideMasters/_rels/slideMaster1.xml.rels', data: rels([['rId1', `${REL}/slideLayout`, '../slideLayouts/slideLayout1.xml'], ['rId2', `${REL}/theme`, '../theme/theme1.xml']]) },
|
|
348
|
+
{
|
|
349
|
+
name: 'ppt/slideLayouts/slideLayout1.xml',
|
|
350
|
+
data: XML + `<p:sldLayout xmlns:a="${NS_A}" xmlns:r="${NS_R}" xmlns:p="${NS_P}" type="blank" preserve="1"><p:cSld name="Blank"><p:spTree>${EMPTY_TREE}</p:spTree></p:cSld>` +
|
|
351
|
+
'<p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr></p:sldLayout>',
|
|
352
|
+
},
|
|
353
|
+
{ name: 'ppt/slideLayouts/_rels/slideLayout1.xml.rels', data: rels([['rId1', `${REL}/slideMaster`, '../slideMasters/slideMaster1.xml']]) },
|
|
354
|
+
)
|
|
355
|
+
return zip(files)
|
|
356
|
+
}
|