code wiki / _hdl_build / nx_cover_gen.nx
nx_cover_gen.nx source
↩ module page · 158 lines · 11018 B
1// nx_cover_gen.nx -- SOVEREIGN GENERATED book covers (operator: "keep going especially on the book covers"). Many
2// liberated books (MOBI/KF8) + native-authored books have NO cover, so the shelf falls back to a title-text box.
3// This GENERATES a designed SVG cover from the book's title + author: a deterministic palette (title hash -> 1 of 6
4// designed color schemes), a wrapped serif title, the author, a Nishi mark. SVG = sovereign (we emit every byte),
5// vector (crisp at any size), tiny, deterministic (same title -> same cover), and renders in <img> in the shelf +
6// reader. Writes reader/<slug>/cover.svg + sets book.json "cover":"cover.svg" IF the book is coverless (a real
7// extracted cover wins). Usage: nx_cover_gen <slug>. expect_exit: 0 license_tier: ORIGINAL
8import "nx_syscalls.nx"
9const K_MAGIC_1024: i64 = 1024
10const K_MAGIC_5381: i64 = 5381
11const K_MAGIC_16384: i64 = 16384
12
13func cg_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
14func cg_p(s: *u8) -> i64 { sys_write(1, s, cg_slen(s)); return 0 }
15func ensure_dir(path: *u8) -> i64 { __syscall(258, 0-100, path, 0x1ed, 0, 0, 0); return 0 }
16func cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){d[o+i]=s[i];i=i+1} return o+i }
17func writef(path: *u8, buf: *u8, n: i64) -> i64 { let fd: i64=sys_openat_wr(path, 0x1a4); if fd<0 {return 0-1} sys_write(fd, buf, n); sys_close(fd); return 0 }
18// extract "key":"value" from JSON into out; ret len.
19func jval(hay: *u8, hl: i64, key: *u8, out: *u8, cap: i64) -> i64 {
20 let kl: i64 = cg_slen(key); var i: i64 = 0
21 while i + kl <= hl { var k: i64=0; var hit: i64=1; while k<kl { if hay[i+k]!=key[k]{hit=0;k=kl}else{k=k+1} } if hit==1 {
22 var j: i64 = i + kl; var o: i64 = 0
23 while j < hl { let c: i64 = hay[j] as i64; if c==0x22 { j=hl } else { if c==0x5c { if j+1<hl { if o<cap-1{out[o]=hay[j+1];o=o+1} j=j+2 } else { j=j+1 } } else { if o<cap-1{out[o]=hay[j] as u8;o=o+1} j=j+1 } } }
24 out[o]=0 as u8; return o
25 } i=i+1 }
26 out[0]=0 as u8; return 0-1
27}
28// XML-escape into the buffer
29func axe(o: *u8, p: *i64, s: *u8) -> i64 {
30 var i: i64=0
31 while s[i]!=(0 as u8) { let c: i64 = s[i] as i64
32 if c==0x3c { i=i; o[p[0]]=0x26 as u8; p[0]=p[0]+1; o[p[0]]=0x6c as u8; p[0]=p[0]+1; o[p[0]]=0x74 as u8; p[0]=p[0]+1; o[p[0]]=0x3b as u8; p[0]=p[0]+1 }
33 else { if c==0x3e { o[p[0]]=0x26 as u8;p[0]=p[0]+1;o[p[0]]=0x67 as u8;p[0]=p[0]+1;o[p[0]]=0x74 as u8;p[0]=p[0]+1;o[p[0]]=0x3b as u8;p[0]=p[0]+1 }
34 else { if c==0x26 { o[p[0]]=0x26 as u8;p[0]=p[0]+1;o[p[0]]=0x61 as u8;p[0]=p[0]+1;o[p[0]]=0x6d as u8;p[0]=p[0]+1;o[p[0]]=0x70 as u8;p[0]=p[0]+1;o[p[0]]=0x3b as u8;p[0]=p[0]+1 }
35 else { o[p[0]]=s[i]; p[0]=p[0]+1 } } }
36 i=i+1 }
37 return 0
38}
39func aw(o: *u8, p: *i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){ o[p[0]]=s[i]; p[0]=p[0]+1; i=i+1 } return 0 }
40func awn(o: *u8, p: *i64, v0: i64) -> i64 { var v: i64=v0; if v<0 { o[p[0]]=45 as u8; p[0]=p[0]+1; v=0-v } let b: *u8=sys_mmap(24); var k: i64=0; if v==0{b[0]=48 as u8;k=1} while v>0 {b[k]=(48+(v%10)) as u8;v=v/10;k=k+1} var j: i64=k-1; while j>=0 { o[p[0]]=b[j]; p[0]=p[0]+1; j=j-1 } return 0 }
41
42func main(argc: i64, argv: *i64) -> i64 {
43 var slug: *u8 = "real_huffdic" as *u8
44 if argc >= 2 { slug = argv[1] as *u8 }
45 let dir: *u8 = sys_mmap(512); var dl: i64 = cat(dir, 0, "knowledge/staging/media/reader/" as *u8); dl = cat(dir, dl, slug); dir[dl]=0 as u8
46 let jp: *u8 = sys_mmap(K_MAGIC_1024); var jl: i64 = cat(jp, 0, dir as *u8); jl = cat(jp, jl, "/book.json" as *u8); jp[jl]=0 as u8
47 let lp: *i64 = sys_mmap(8) as *i64; lp[0]=0
48 let bj: *u8 = sys_read_file(jp, lp)
49 if (bj as i64)==0 { cg_p("COVER-GEN-FAIL no book.json " as *u8); cg_p(jp); cg_p("\n" as *u8); sys_exit(1); return 1 }
50 let bn: i64 = lp[0]
51
52 let title: *u8 = sys_mmap(K_MAGIC_1024); jval(bj, bn, "\"title\":\"" as *u8, title, K_MAGIC_1024)
53 let author: *u8 = sys_mmap(K_MAGIC_1024); author[0]=0 as u8; jval(bj, bn, "\"author\":\"" as *u8, author, K_MAGIC_1024)
54 let curcov: *u8 = sys_mmap(512); curcov[0]=0 as u8; jval(bj, bn, "\"cover\":\"" as *u8, curcov, 512)
55 if curcov[0] != (0 as u8) { cg_p("COVER-GEN skip (already has cover=" as *u8); cg_p(curcov); cg_p(")\n" as *u8); sys_exit(0); return 0 }
56 if title[0] == (0 as u8) { cat(title, 0, slug); }
57
58 // deterministic palette from the title hash (djb2 % 6). 6 designed schemes: bg1,bg2,accent,ink,ink2.
59 var h: i64 = K_MAGIC_5381; var ti: i64 = 0; while title[ti]!=(0 as u8){ h = ((h*33) + (title[ti] as i64)) & 0x7fffffff; ti=ti+1 }
60 let pal: i64 = h % 6
61 var bg1: *u8="#1a2238" as *u8; var bg2: *u8="#0d1220" as *u8; var ac: *u8="#c9a227" as *u8; var ink: *u8="#f4efe1" as *u8; var ink2: *u8="#bcae8a" as *u8
62 if pal==1 { bg1="#1e3a2f" as *u8; bg2="#0f1f18" as *u8; ac="#e0c068" as *u8; ink="#f1ead6" as *u8; ink2="#b8c9b0" as *u8 }
63 if pal==2 { bg1="#4a1f24" as *u8; bg2="#2a0f12" as *u8; ac="#d8a36b" as *u8; ink="#f5e9da" as *u8; ink2="#d0a59a" as *u8 }
64 if pal==3 { bg1="#22343a" as *u8; bg2="#0f1c20" as *u8; ac="#5fb3a1" as *u8; ink="#eaf2f0" as *u8; ink2="#9fc3bb" as *u8 }
65 if pal==4 { bg1="#3a2440" as *u8; bg2="#1e1224" as *u8; ac="#d98aa8" as *u8; ink="#f3e9f0" as *u8; ink2="#c3a3bd" as *u8 }
66 if pal==5 { bg1="#2b2723" as *u8; bg2="#15120f" as *u8; ac="#e08a3c" as *u8; ink="#f2ebe0" as *u8; ink2="#c2b09a" as *u8 }
67
68 // word-wrap the title into lines of <= 14 chars (store as (off,len) into title)
69 let loff: *i64 = sys_mmap(8*16) as *i64; let llen: *i64 = sys_mmap(8*16) as *i64; var nlines: i64 = 0
70 let tn: i64 = cg_slen(title)
71 var ls: i64 = 0; var le: i64 = 0
72 while le <= tn {
73 let atend: i64 = le; var brk: i64 = 0
74 if le==tn { brk=1 } else { if title[le]==(0x20 as u8) { brk=1 } }
75 if brk==1 {
76 if le - ls > 14 { if le > ls { }
77 // current word makes the line too long -> flush previous line if any
78 }
79 // decide: if the line [ls..le) <= 14 keep going; else flush at last space.
80 le = le
81 }
82 le = le + 1
83 }
84 // simpler greedy wrap: scan words, accumulate
85 nlines = 0; var cs: i64 = 0; var ce: i64 = 0; var i2: i64 = 0
86 while i2 <= tn {
87 let isspace: i64 = (i2==tn) | 0
88 var sp: i64 = 0; if i2==tn { sp=1 } else { if title[i2]==(0x20 as u8) { sp=1 } }
89 if sp==1 {
90 // word is [ce.. i2) following the current line start cs (ce marks last word-start within line)
91 if (i2 - cs) > 14 { if ce > cs {
92 if nlines < 15 { loff[nlines]=cs; llen[nlines]=ce-1-cs; nlines=nlines+1 }
93 cs = ce
94 } }
95 ce = i2 + 1
96 }
97 i2 = i2 + 1
98 }
99 if cs < tn { if nlines < 15 { loff[nlines]=cs; llen[nlines]=tn-cs; nlines=nlines+1 } }
100 if nlines == 0 { loff[0]=0; llen[0]=tn; nlines=1 }
101
102 // ----- emit the SVG -----
103 let H: *u8 = sys_mmap(K_MAGIC_16384); let hp: *i64 = sys_mmap(8) as *i64; hp[0]=0
104 aw(H, hp, "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 400 600\" width=\"400\" height=\"600\" role=\"img\" aria-label=\"Cover: " as *u8)
105 let tmp: *u8 = sys_mmap(K_MAGIC_1024); var tc: i64=0; while title[tc]!=(0 as u8){tmp[tc]=title[tc];tc=tc+1} tmp[tc]=0 as u8
106 axe(H, hp, tmp)
107 aw(H, hp, "\"><defs><linearGradient id=\"g\" x1=\"0\" y1=\"0\" x2=\"0.4\" y2=\"1\"><stop offset=\"0\" stop-color=\"" as *u8); aw(H, hp, bg1)
108 aw(H, hp, "\"/><stop offset=\"1\" stop-color=\"" as *u8); aw(H, hp, bg2); aw(H, hp, "\"/></linearGradient></defs>" as *u8)
109 aw(H, hp, "<rect width=\"400\" height=\"600\" fill=\"url(#g)\"/>" as *u8)
110 aw(H, hp, "<rect x=\"0\" y=\"0\" width=\"400\" height=\"9\" fill=\"" as *u8); aw(H, hp, ac); aw(H, hp, "\"/>" as *u8)
111 aw(H, hp, "<rect x=\"0\" y=\"591\" width=\"400\" height=\"9\" fill=\"" as *u8); aw(H, hp, ac); aw(H, hp, "\"/>" as *u8)
112 aw(H, hp, "<rect x=\"40\" y=\"150\" width=\"54\" height=\"6\" fill=\"" as *u8); aw(H, hp, ac); aw(H, hp, "\"/>" as *u8)
113 // title block, vertically centered around y=270
114 var y0: i64 = 270 - (nlines-1) * 26
115 aw(H, hp, "<text x=\"40\" y=\"" as *u8); awn(H, hp, y0)
116 aw(H, hp, "\" font-family=\"Georgia,'Times New Roman',serif\" font-size=\"44\" font-weight=\"700\" fill=\"" as *u8); aw(H, hp, ink); aw(H, hp, "\">" as *u8)
117 var li: i64 = 0
118 while li < nlines {
119 aw(H, hp, "<tspan x=\"40\" dy=\"" as *u8); if li==0 { awn(H, hp, 0) } else { awn(H, hp, 52) }; aw(H, hp, "\">" as *u8)
120 var c2: i64=0; while c2 < llen[li] { tmp[c2]=title[loff[li]+c2]; c2=c2+1 } tmp[llen[li]]=0 as u8
121 axe(H, hp, tmp)
122 aw(H, hp, "</tspan>" as *u8)
123 li = li + 1
124 }
125 aw(H, hp, "</text>" as *u8)
126 // author
127 if author[0]!=(0 as u8) {
128 aw(H, hp, "<line x1=\"40\" x2=\"160\" y1=\"500\" y2=\"500\" stroke=\"" as *u8); aw(H, hp, ac); aw(H, hp, "\" stroke-width=\"2\"/>" as *u8)
129 aw(H, hp, "<text x=\"40\" y=\"530\" font-family=\"system-ui,-apple-system,Segoe UI,sans-serif\" font-size=\"19\" letter-spacing=\"1\" fill=\"" as *u8); aw(H, hp, ink2); aw(H, hp, "\">" as *u8)
130 var ac2: i64=0; while author[ac2]!=(0 as u8){tmp[ac2]=author[ac2];ac2=ac2+1} tmp[ac2]=0 as u8; axe(H, hp, tmp)
131 aw(H, hp, "</text>" as *u8)
132 }
133 aw(H, hp, "<text x=\"40\" y=\"572\" font-family=\"system-ui,sans-serif\" font-size=\"11\" letter-spacing=\"4\" fill=\"" as *u8); aw(H, hp, ac); aw(H, hp, "\">NISHI</text>" as *u8)
134 aw(H, hp, "</svg>" as *u8)
135 let hn: i64 = hp[0]
136
137 ensure_dir(dir)
138 let cp: *u8 = sys_mmap(K_MAGIC_1024); var cl: i64 = cat(cp, 0, dir as *u8); cl = cat(cp, cl, "/cover.svg" as *u8); cp[cl]=0 as u8
139 if writef(cp, H, hn) != 0 { cg_p("COVER-GEN-FAIL write svg\n" as *u8); sys_exit(1); return 1 }
140
141 // update book.json: "cover":"" -> "cover":"cover.svg"
142 let nb: *u8 = sys_mmap(bn + 64); let np: *i64 = sys_mmap(8) as *i64; np[0]=0
143 let needle: *u8 = "\"cover\":\"\"" as *u8; let ndl: i64 = cg_slen(needle)
144 var done: i64 = 0; var k2: i64 = 0
145 while k2 < bn {
146 var hit: i64 = 0
147 if done==0 { if k2+ndl<=bn { hit=1; var z: i64=0; while z<ndl { if bj[k2+z]!=needle[z]{hit=0;z=ndl}else{z=z+1} } } }
148 if hit==1 { aw(nb, np, "\"cover\":\"cover.svg\"" as *u8); k2=k2+ndl; done=1 }
149 else { nb[np[0]]=bj[k2]; np[0]=np[0]+1; k2=k2+1 }
150 }
151 writef(jp, nb, np[0])
152
153 cg_p("COVER-GEN slug=" as *u8); cg_p(slug); cg_p(" pal=" as *u8); let pb: *u8=sys_mmap(4); pb[0]=(48+pal) as u8; sys_write(1,pb,1)
154 cg_p(" lines=" as *u8); let lb: *u8=sys_mmap(4); lb[0]=(48+nlines) as u8; sys_write(1,lb,1)
155 cg_p(" svg=" as *u8); let szb: *u8=sys_mmap(8); var sv: i64=hn; var sk: i64=0; if sv==0{szb[0]=48 as u8;sk=1} while sv>0{szb[sk]=(48+(sv%10)) as u8;sv=sv/10;sk=sk+1} var sj: i64=sk-1; while sj>=0{sys_write(1,(szb as i64+sj) as *u8,1);sj=sj-1}
156 cg_p("B -> " as *u8); cg_p(cp); cg_p(" (book.json cover set)\n" as *u8)
157 cg_p("COVER-GEN-OK\n" as *u8); sys_exit(0); return 0
158}