code wiki / _hdl_build / nx_pptx.nx
nx_pptx.nx source
↩ module page · 299 lines · 17473 B
1// nx_pptx.nx -- SOVEREIGN presentations (.pptx / PresentationML) for the Nishi Office suite: the census's
2// biggest ABSENT category (OF-P1 author + OF-P2 render). A .pptx is an OPC package (shared nx_opc, STORED
3// ZIP like .docx/.xlsx) with ppt/presentation.xml + ppt/slides/slideN.xml parts.
4// fromspec : spec text -> deck. "S <title>" starts a slide, "B <bullet>" adds a bullet (XML-escaped).
5// read : deck -> per-slide title + bullets (walks slide parts, extracts <a:t> runs).
6// html : deck -> 0-JS inline SLIDE-DECK preview (16:9 cards) the Nishi browser (and the nx_doc_view
7// registry) renders -- same `html <in> <out> [dlurl]` iface as nx_docx/nx_md/nx_csv/nx_pdf/nx_xlsx.
8// HONEST SCOPE (v1, named): title + bullet slides only -- no slide masters/layouts/themes/images/animations.
9// The package is valid OPC + PresentationML that OUR tools round-trip; "opens in PowerPoint" is NOT claimed
10// until verified against real PowerPoint (masters/layouts may be required there) -- that check is a named
11// follow-up, exactly like the docx harness's third-party validation was.
12// nx_pptx fromspec <spec.txt> <out.pptx> | read <in.pptx> | html <in.pptx> <out.html> [dlurl]
13// license_tier: ORIGINAL
14import "nx_syscalls.nx"
15import "nx_opc.nx"
16const PP_MAGIC_1048592: i64 = 1048592
17const PP_MAGIC_1048576: i64 = 1048576
18const PP_MAGIC_16384: i64 = 16384
19const PP_MAGIC_262144: i64 = 262144
20const PP_MAGIC_65536: i64 = 65536
21const PP_MAGIC_8388608: i64 = 8388608
22
23const PP_MAXSLIDES: i64 = 64
24
25func pp_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8) { n=n+1 } return n }
26func pp_puts(s: *u8) -> i64 { sys_write(1, s, pp_slen(s)); return 0 }
27func pp_num(v: i64) -> i64 {
28 let b: *u8=sys_mmap(28); var m: i64=v; if m<0 { m=0-m; sys_write(1,"-" as *u8,1) }
29 let t: *u8=sys_mmap(28); var k: i64=0; if m==0 { t[0]=48 as u8; k=1 }
30 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 }
31 var i: i64=0; while i<k { b[i]=t[k-1-i]; i=i+1 } sys_write(1,b,k); return 0
32}
33func pp_streq(a: *u8, b: *u8) -> i64 { var i: i64=0; while a[i]!=(0 as u8) { if a[i]!=b[i] { return 0 } i=i+1 } if b[i]!=(0 as u8) { return 0 } return 1 }
34func pp_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8) { dst[off+i]=s[i]; i=i+1 } return off+i }
35func pp_uint(dst: *u8, off: i64, v: i64) -> i64 {
36 var m: i64=v; let t: *u8=sys_mmap(28); var k: i64=0; if m==0 { t[0]=48 as u8; k=1 }
37 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 }
38 var o: i64=off; var i: i64=0; while i<k { dst[o]=t[k-1-i]; o=o+1; i=i+1 } return o
39}
40func pp_esc(out: *u8, o: i64, src: *u8, soff: i64, slen: i64) -> i64 {
41 var oo: i64=o; var k: i64=0
42 while k<slen {
43 let c: i64=src[soff+k] as i64
44 if c==38 { oo=pp_cat(out,oo,"&" as *u8) } else { if c==60 { oo=pp_cat(out,oo,"<" as *u8) } else { if c==62 { oo=pp_cat(out,oo,">" as *u8) } else { out[oo]=src[soff+k]; oo=oo+1 } } }
45 k=k+1
46 }
47 return oo
48}
49func pp_readfile(path: *u8, buf: *u8, cap: i64) -> i64 {
50 let fd: i64=sys_openat_rd(path); if fd<0 { return 0-1 }
51 var tot: i64=0; while tot<cap { let r: i64=sys_read(fd,(buf as i64+tot) as *u8,cap-tot); if r<=0 { break } tot=tot+r }
52 sys_close(fd); return tot
53}
54func pp_match(buf: *u8, pos: i64, n: i64, pat: *u8) -> i64 {
55 var i: i64=0
56 while pat[i]!=(0 as u8) { if pos+i>=n { return 0 } if buf[pos+i]!=pat[i] { return 0 } i=i+1 }
57 return 1
58}
59
60// one slide's XML: title shape + one body shape with a bullet paragraph per bullet
61func pp_build_slide(out: *u8, spec: *u8, tso: *i64, tsl: *i64, nb: i64) -> i64 {
62 // tso/tsl: [0]=title span, [1..nb]=bullet spans
63 var o: i64=0
64 o=pp_cat(out,o,"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<p:sld xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:p=\"http://schemas.openxmlformats.org/presentationml/2006/main\"><p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id=\"1\" name=\"\"/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr/>" as *u8)
65 // title shape
66 o=pp_cat(out,o,"<p:sp><p:nvSpPr><p:cNvPr id=\"2\" name=\"Title\"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm><a:off x=\"838200\" y=\"365125\"/><a:ext cx=\"10515600\" cy=\"1325563\"/></a:xfrm></p:spPr><p:txBody><a:bodyPr/><a:p><a:r><a:rPr lang=\"en-US\" sz=\"3600\" b=\"1\"/><a:t>" as *u8)
67 o=pp_esc(out,o,spec,tso[0],tsl[0])
68 o=pp_cat(out,o,"</a:t></a:r></a:p></p:txBody></p:sp>" as *u8)
69 // body shape with bullets
70 if nb>0 {
71 o=pp_cat(out,o,"<p:sp><p:nvSpPr><p:cNvPr id=\"3\" name=\"Content\"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm><a:off x=\"838200\" y=\"1825625\"/><a:ext cx=\"10515600\" cy=\"4351338\"/></a:xfrm></p:spPr><p:txBody><a:bodyPr/>" as *u8)
72 var bi: i64=1
73 while bi<=nb {
74 o=pp_cat(out,o,"<a:p><a:r><a:rPr lang=\"en-US\" sz=\"2000\"/><a:t>" as *u8)
75 o=pp_esc(out,o,spec,tso[bi],tsl[bi])
76 o=pp_cat(out,o,"</a:t></a:r></a:p>" as *u8)
77 bi=bi+1
78 }
79 o=pp_cat(out,o,"</p:txBody></p:sp>" as *u8)
80 }
81 o=pp_cat(out,o,"</p:spTree></p:cSld></p:sld>" as *u8)
82 return o
83}
84
85// spec -> deck: parse "S <title>" / "B <bullet>" lines, build one slide part per S.
86func pptx_write_spec(specpath: *u8, outpath: *u8) -> i64 {
87 let spec: *u8=sys_mmap(PP_MAGIC_1048592)
88 let sn: i64=pp_readfile(specpath, spec, PP_MAGIC_1048576)
89 if sn<=0 { return 0-1 }
90 // collect per-slide spans
91 let so: *i64=sys_mmap(8*PP_MAXSLIDES*33) as *i64 // per slide: [0]=title, [1..32]=bullets
92 let sl: *i64=sys_mmap(8*PP_MAXSLIDES*33) as *i64
93 let nbul: *i64=sys_mmap(8*PP_MAXSLIDES) as *i64
94 var ns: i64=0
95 var i: i64=0
96 while i<sn {
97 var e: i64=i
98 while e<sn { if spec[e]==(10 as u8) { break } e=e+1 }
99 var end: i64=e
100 if end>i { if spec[end-1]==(13 as u8) { end=end-1 } }
101 if end>=i+2 { if spec[i+1]==(32 as u8) {
102 let c0: i64=spec[i] as i64
103 if c0==83 { if ns<PP_MAXSLIDES { so[ns*33]=i+2; sl[ns*33]=end-(i+2); nbul[ns]=0; ns=ns+1 } }
104 if c0==66 { if ns>0 { let b: i64=nbul[ns-1]; if b<32 { so[(ns-1)*33+1+b]=i+2; sl[(ns-1)*33+1+b]=end-(i+2); nbul[ns-1]=b+1 } } }
105 } }
106 i=e+1
107 }
108 if ns==0 { return 0-2 }
109 // OPC parts: content types + root rels + presentation + its rels + N slides
110 let np: i64=4+ns
111 let names: *i64=sys_mmap(8*(np+2)) as *i64
112 let datap: *i64=sys_mmap(8*(np+2)) as *i64
113 let lens: *i64=sys_mmap(8*(np+2)) as *i64
114 // [Content_Types].xml (Override per slide)
115 let ct: *u8=sys_mmap(PP_MAGIC_16384)
116 var cto: i64=0
117 cto=pp_cat(ct,cto,"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\"><Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/><Default Extension=\"xml\" ContentType=\"application/xml\"/><Override PartName=\"/ppt/presentation.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml\"/>" as *u8)
118 var si: i64=1
119 while si<=ns {
120 cto=pp_cat(ct,cto,"<Override PartName=\"/ppt/slides/slide" as *u8); cto=pp_uint(ct,cto,si)
121 cto=pp_cat(ct,cto,".xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.slide+xml\"/>" as *u8)
122 si=si+1
123 }
124 cto=pp_cat(ct,cto,"</Types>" as *u8)
125 // root rels
126 let rr: *u8="<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\"><Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\" Target=\"ppt/presentation.xml\"/></Relationships>" as *u8
127 // presentation.xml (sldIdLst + 16:9 size)
128 let pres: *u8=sys_mmap(PP_MAGIC_16384)
129 var po: i64=0
130 po=pp_cat(pres,po,"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<p:presentation xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:p=\"http://schemas.openxmlformats.org/presentationml/2006/main\"><p:sldIdLst>" as *u8)
131 si=1
132 while si<=ns {
133 po=pp_cat(pres,po,"<p:sldId id=\"" as *u8); po=pp_uint(pres,po,255+si)
134 po=pp_cat(pres,po,"\" r:id=\"rId" as *u8); po=pp_uint(pres,po,si); po=pp_cat(pres,po,"\"/>" as *u8)
135 si=si+1
136 }
137 po=pp_cat(pres,po,"</p:sldIdLst><p:sldSz cx=\"12192000\" cy=\"6858000\"/></p:presentation>" as *u8)
138 // presentation rels
139 let prels: *u8=sys_mmap(PP_MAGIC_16384)
140 var pro: i64=0
141 pro=pp_cat(prels,pro,"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">" as *u8)
142 si=1
143 while si<=ns {
144 pro=pp_cat(prels,pro,"<Relationship Id=\"rId" as *u8); pro=pp_uint(prels,pro,si)
145 pro=pp_cat(prels,pro,"\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide\" Target=\"slides/slide" as *u8)
146 pro=pp_uint(prels,pro,si); pro=pp_cat(prels,pro,".xml\"/>" as *u8)
147 si=si+1
148 }
149 pro=pp_cat(prels,pro,"</Relationships>" as *u8)
150 names[0]="[Content_Types].xml" as *u8 as i64; datap[0]=ct as i64; lens[0]=cto
151 names[1]="_rels/.rels" as *u8 as i64; datap[1]=rr as i64; lens[1]=pp_slen(rr)
152 names[2]="ppt/presentation.xml" as *u8 as i64; datap[2]=pres as i64; lens[2]=po
153 names[3]="ppt/_rels/presentation.xml.rels" as *u8 as i64; datap[3]=prels as i64; lens[3]=pro
154 // slides (part NAMES need distinct buffers)
155 let nmbuf: *u8=sys_mmap(PP_MAXSLIDES*40)
156 var nmo: i64=0
157 si=0
158 while si<ns {
159 let sxml: *u8=sys_mmap(PP_MAGIC_262144)
160 let tso: *i64=((so as i64)+si*33*8) as *i64
161 let tsl: *i64=((sl as i64)+si*33*8) as *i64
162 let slen2: i64=pp_build_slide(sxml, spec, tso, tsl, nbul[si])
163 let nm: *u8=((nmbuf as i64)+nmo) as *u8
164 var q: i64=pp_cat(nm,0,"ppt/slides/slide" as *u8)
165 q=pp_uint(nm,q,si+1)
166 q=pp_cat(nm,q,".xml" as *u8)
167 nm[q]=0 as u8
168 nmo=nmo+q+1
169 names[4+si]=nm as i64
170 datap[4+si]=sxml as i64
171 lens[4+si]=slen2
172 si=si+1
173 }
174 return opc_write(outpath, names, datap, lens, np)
175}
176
177// extract slide si's <a:t> runs: first run -> title (tb), rest -> bullets appended to out with "- " prefix.
178// Returns 1 if the slide part exists, else 0.
179func pp_read_slide(path: *u8, si: i64, fd: i64) -> i64 {
180 let nm: *u8=sys_mmap(64)
181 var q: i64=pp_cat(nm,0,"ppt/slides/slide" as *u8)
182 q=pp_uint(nm,q,si)
183 q=pp_cat(nm,q,".xml" as *u8)
184 nm[q]=0 as u8
185 let sx: *u8=sys_mmap(PP_MAGIC_1048592)
186 let n: i64=opc_read_part(path, nm, sx, PP_MAGIC_1048576)
187 if n<0 { return 0 }
188 let line: *u8=sys_mmap(PP_MAGIC_65536)
189 var o: i64=0
190 o=pp_cat(line,o,"SLIDE " as *u8); o=pp_uint(line,o,si); o=pp_cat(line,o,": " as *u8)
191 var first: i64=1
192 var i: i64=0
193 while i<n {
194 if pp_match(sx,i,n,"<a:t>" as *u8)==1 {
195 var k: i64=i+5
196 if first==0 { o=pp_cat(line,o,"\n- " as *u8) }
197 while k<n {
198 if pp_match(sx,k,n,"</a:t>" as *u8)==1 { break }
199 if sx[k]==(38 as u8) {
200 if pp_match(sx,k,n,"&" as *u8)==1 { line[o]=38 as u8; o=o+1; k=k+5 } else {
201 if pp_match(sx,k,n,"<" as *u8)==1 { line[o]=60 as u8; o=o+1; k=k+4 } else {
202 if pp_match(sx,k,n,">" as *u8)==1 { line[o]=62 as u8; o=o+1; k=k+4 } else {
203 line[o]=sx[k]; o=o+1; k=k+1 } } }
204 } else { line[o]=sx[k]; o=o+1; k=k+1 }
205 }
206 first=0
207 i=k+6
208 } else { i=i+1 }
209 }
210 line[o]=10 as u8
211 o=o+1
212 sys_write(fd, line, o)
213 return 1
214}
215
216// deck -> 0-JS slide-card preview (16:9), one card per slide
217func pptx_to_html(path: *u8, out: *u8, dlurl: *u8) -> i64 {
218 var o: i64=0
219 o=pp_cat(out,o,"<!DOCTYPE html>\n<html lang=en><head><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>Slide Deck Preview - Nishi Office</title><style>\nbody{font:16px/1.6 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;margin:0;background:#eef1f5;color:#1a1d21}\nheader{background:#0f172a;color:#fff;padding:16px 24px}header h1{margin:0;font-size:17px;font-weight:600}\n.deck{max-width:900px;margin:22px auto;padding:0 12px}\n.slide{background:#fff;border:1px solid #e2e8f0;border-radius:12px;box-shadow:0 1px 4px rgba(15,23,42,.08);margin:0 0 22px;padding:34px 44px;aspect-ratio:16/9;overflow:hidden;position:relative}\n.slide h2{font-size:28px;margin:0 0 18px;color:#0f172a;border-bottom:2px solid #2563eb;padding-bottom:10px}\n.slide ul{margin:0;padding-left:26px}.slide li{margin:9px 0;font-size:19px}\n.sn{position:absolute;right:14px;bottom:8px;color:#94a3b8;font-size:12px}\n.bar{max-width:900px;margin:0 auto 6px;text-align:right;padding:0 12px}.dl{display:inline-block;padding:9px 18px;background:#2563eb;color:#fff;border-radius:8px;text-decoration:none;font-size:14px;font-weight:500}\nfooter{max-width:900px;margin:14px auto 46px;color:#64748b;font-size:13px;text-align:center}\n</style></head>\n<body>\n<header><h1>🎬 Slide Deck Preview — Nishi Office</h1></header>\n<div class=deck>\n" as *u8)
220 let sx: *u8=sys_mmap(PP_MAGIC_1048592)
221 var si: i64=1
222 var shown: i64=0
223 while si<=PP_MAXSLIDES {
224 let nm: *u8=sys_mmap(64)
225 var q: i64=pp_cat(nm,0,"ppt/slides/slide" as *u8)
226 q=pp_uint(nm,q,si)
227 q=pp_cat(nm,q,".xml" as *u8)
228 nm[q]=0 as u8
229 let n: i64=opc_read_part(path, nm, sx, PP_MAGIC_1048576)
230 if n<0 { si=PP_MAXSLIDES+1 } else {
231 o=pp_cat(out,o,"<div class=slide>" as *u8)
232 var first: i64=1
233 var inlist: i64=0
234 var i: i64=0
235 while i<n {
236 if pp_match(sx,i,n,"<a:t>" as *u8)==1 {
237 var k: i64=i+5
238 if first==1 { o=pp_cat(out,o,"<h2>" as *u8) } else {
239 if inlist==0 { o=pp_cat(out,o,"<ul>" as *u8); inlist=1 }
240 o=pp_cat(out,o,"<li>" as *u8)
241 }
242 while k<n {
243 if pp_match(sx,k,n,"</a:t>" as *u8)==1 { break }
244 out[o]=sx[k]; o=o+1; k=k+1
245 }
246 if first==1 { o=pp_cat(out,o,"</h2>" as *u8); first=0 } else { o=pp_cat(out,o,"</li>" as *u8) }
247 i=k+6
248 } else { i=i+1 }
249 }
250 if inlist==1 { o=pp_cat(out,o,"</ul>" as *u8) }
251 o=pp_cat(out,o,"<div class=sn>" as *u8); o=pp_uint(out,o,si); o=pp_cat(out,o,"</div></div>\n" as *u8)
252 shown=shown+1
253 si=si+1
254 }
255 }
256 if shown==0 { return 0-1 }
257 o=pp_cat(out,o,"</div>\n" as *u8)
258 if (dlurl as i64)!=0 { o=pp_cat(out,o,"<div class=bar><a class=dl href=\"" as *u8); o=pp_cat(out,o,dlurl); o=pp_cat(out,o,"\">⬇ Download .pptx</a></div>\n" as *u8) }
259 o=pp_cat(out,o,"<footer>Rendered natively from PresentationML by <b>nx_pptx</b> · " as *u8)
260 o=pp_uint(out,o,shown)
261 o=pp_cat(out,o," slides · viewable inline in the Nishi browser</footer>\n</body></html>\n" as *u8)
262 return o
263}
264
265func main(argc: i64, argv: *i64) -> i64 {
266 if argc<3 { pp_puts("usage: nx_pptx fromspec <spec.txt> <out.pptx> | read <in.pptx> | html <in.pptx> <out.html> [download_url]\n" as *u8); sys_exit(2); return 2 }
267 let cmd: *u8=argv[1] as *u8
268 if pp_streq(cmd,"fromspec" as *u8)==1 {
269 if argc<4 { pp_puts("usage: nx_pptx fromspec <spec.txt> <out.pptx>\n" as *u8); sys_exit(2); return 2 }
270 let sz: i64=pptx_write_spec(argv[2] as *u8, argv[3] as *u8)
271 if sz<0 { pp_puts("PPTX-WRITE-FAIL code=" as *u8); pp_num(sz); pp_puts("\n" as *u8); sys_exit(1); return 1 }
272 pp_puts("PPTX-WRITE-OK path=" as *u8); pp_puts(argv[3] as *u8); pp_puts(" bytes=" as *u8); pp_num(sz); pp_puts("\n" as *u8)
273 sys_exit(0); return 0
274 }
275 if pp_streq(cmd,"read" as *u8)==1 {
276 pp_puts("PPTX-DECK-BEGIN\n" as *u8)
277 var si: i64=1
278 var n: i64=0
279 while si<=PP_MAXSLIDES { if pp_read_slide(argv[2] as *u8, si, 1)==1 { n=n+1; si=si+1 } else { si=PP_MAXSLIDES+1 } }
280 pp_puts("PPTX-DECK-END slides=" as *u8); pp_num(n); pp_puts("\n" as *u8)
281 if n==0 { sys_exit(1); return 1 }
282 sys_exit(0); return 0
283 }
284 if pp_streq(cmd,"html" as *u8)==1 {
285 if argc<4 { pp_puts("usage: nx_pptx html <in.pptx> <out.html> [download_url]\n" as *u8); sys_exit(2); return 2 }
286 var dlu: *u8=0 as *u8
287 if argc>4 { dlu=argv[4] as *u8 }
288 let out: *u8=sys_mmap(PP_MAGIC_8388608)
289 let hn: i64=pptx_to_html(argv[2] as *u8, out, dlu)
290 if hn<0 { pp_puts("PPTX-HTML-FAIL cannot-read-deck\n" as *u8); sys_exit(1); return 1 }
291 let fd: i64=sys_openat_wr(argv[3] as *u8, 0x1a4)
292 if fd<0 { pp_puts("PPTX-HTML-FAIL cannot-write\n" as *u8); sys_exit(1); return 1 }
293 sys_write(fd, out, hn); sys_close(fd)
294 pp_puts("PPTX-HTML-OK path=" as *u8); pp_puts(argv[3] as *u8); pp_puts(" bytes=" as *u8); pp_num(hn); pp_puts("\n" as *u8)
295 sys_exit(0); return 0
296 }
297 pp_puts("nx_pptx: unknown command (fromspec|read|html)\n" as *u8)
298 sys_exit(2); return 2
299}