nx_manga_page.nx source
↩ module page · 264 lines · 12088 B
1// nx_manga_page.nx -- R1 KEYSTONE of the mangagen lane (comparewatch- contracts mangagen_mp_page_emit
2// + mangagen_mp_layout_rows): compose N input images onto ONE manga page PNG from a DATA-DRIVEN layout
3// row. Layouts are rows, never code (house law):
4// layouts.conf line: name|pageW|pageH|border|order|x,y,w,h;x,y,w,h;...
5// panel rects are PER-MILLE of the page; order rtl reverses the input->panel mapping (manga read
6// order); border = black frame thickness px drawn inside each panel rect.
7// FAIL-CLOSED: input count != panel count REFUSES (a page with empty or silently-dropped panels is a
8// wrong artifact wearing a clean filename); unknown layout REFUSES naming the conf and the ask.
9// Partition is PRINTED per the R1 accept rule: every input placed exactly once, panel<-input mapping.
10// usage: nx_manga_page <layouts.conf> <layout> <out.png> <img1> ... <imgN>
11// license_tier: ORIGINAL expect_exit: 0
12import "nx_str.nx"
13import "nx_syscalls.nx"
14import "nx_img_to_rgb.nx"
15import "nx_png_write.nx"
16const MP_MAXP: i64 = 24
17const MP_CONFCAP: i64 = 65536
18func mp_puts(s: *u8) -> i64 { sys_write(1, s, nx_str_len(s)); return 0 }
19func mp_pi(v: i64) -> i64 {
20 let t: *u8 = sys_mmap(32)
21 let o: *u8 = sys_mmap(32)
22 var m: i64 = v
23 var k: i64 = 0
24 if m < 0 { m = 0 - m; sys_write(1, "-" as *u8, 1) }
25 if m == 0 { t[0] = 48; k = 1 }
26 while m > 0 { t[k] = 48 + (m % 10); m = m / 10; k = k + 1 }
27 var i: i64 = 0
28 while i < k { o[i] = t[k - 1 - i]; i = i + 1 }
29 sys_write(1, o, k)
30 return 0
31}
32func mp_read(path: *u8, buf: *u8, cap: i64) -> i64 {
33 let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 - 1 }
34 var tot: i64 = 0
35 while tot < cap { let r: i64 = sys_read(fd, (buf as i64 + tot) as *u8, cap - tot); if r <= 0 { break } tot = tot + r }
36 sys_close(fd); return tot
37}
38// parse int from buf[a..b) (digits only, ignores others up to first digit run end)
39func mp_int(buf: *u8, a: i64, b: i64) -> i64 {
40 var v: i64 = 0
41 var i: i64 = a
42 var seen: i64 = 0
43 var go: i64 = 1
44 while go == 1 {
45 if i >= b { go = 0 } else {
46 let c: i64 = buf[i] as i64
47 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); seen = 1; i = i + 1 } else { if seen == 1 { go = 0 } else { i = i + 1 } } } else { if seen == 1 { go = 0 } else { i = i + 1 } }
48 }
49 }
50 return v
51}
52// THE LAYOUT-ROWS CONTRACT (watched as mangagen_mp_layout_rows): find <name> in the conf, fill
53// meta[0..4]=W,H,border,order(0 ltr/1 rtl),npanels and rects[i*4..] per-mille. Returns npanels, or
54// -1 conf-unreadable, -2 layout-not-found, -3 too-many-panels.
55func mp_layout_rows(confpath: *u8, name: *u8, meta: *i64, rects: *i64) -> i64 {
56 let buf: *u8 = sys_mmap(MP_CONFCAP)
57 let n: i64 = mp_read(confpath, buf, MP_CONFCAP)
58 if n <= 0 { return 0 - 1 }
59 var p: i64 = 0
60 while p < n {
61 var e: i64 = p
62 while e < n { if buf[e] == (10 as u8) { break } e = e + 1 }
63 if buf[p] != (35 as u8) {
64 // match name up to first '|'
65 var i: i64 = 0
66 var ok: i64 = 1
67 var stop: i64 = 0
68 while stop == 0 {
69 if p + i >= e { stop = 1 } else {
70 let c: i64 = buf[p + i] as i64
71 if c == 124 { stop = 1 } else {
72 let nc: i64 = name[i] as i64
73 if nc == 0 { ok = 0; stop = 1 } else { if nc != c { ok = 0; stop = 1 } else { i = i + 1 } }
74 }
75 }
76 }
77 if ok == 1 { if (name[i] as i64) == 0 { if i > 0 {
78 // fields: name|W|H|border|order|rects
79 var f: i64 = 0
80 let fp: *i64 = sys_mmap(64) as *i64
81 var x: i64 = p
82 while x < e { if buf[x] == (124 as u8) { if f < 6 { fp[f] = x; f = f + 1 } } x = x + 1 }
83 if f >= 5 {
84 meta[0] = mp_int(buf, fp[0] + 1, fp[1])
85 meta[1] = mp_int(buf, fp[1] + 1, fp[2])
86 meta[2] = mp_int(buf, fp[2] + 1, fp[3])
87 var ord: i64 = 0
88 if buf[fp[3] + 1] == (114 as u8) { ord = 1 }
89 meta[3] = ord
90 var np: i64 = 0
91 var rp: i64 = fp[4] + 1
92 var go2: i64 = 1
93 while go2 == 1 {
94 if rp >= e { go2 = 0 } else {
95 if np >= MP_MAXP { return 0 - 3 }
96 var re: i64 = rp
97 while re < e { if buf[re] == (59 as u8) { break } re = re + 1 }
98 // rect x,y,w,h
99 var cp: i64 = rp
100 var ci: i64 = 0
101 while ci < 4 {
102 var ce: i64 = cp
103 while ce < re { if buf[ce] == (44 as u8) { break } ce = ce + 1 }
104 rects[np * 4 + ci] = mp_int(buf, cp, ce)
105 cp = ce + 1
106 ci = ci + 1
107 }
108 np = np + 1
109 rp = re + 1
110 }
111 }
112 meta[4] = np
113 return np
114 }
115 } } }
116 }
117 p = e + 1
118 }
119 return 0 - 2
120}
121// box-average blit (the cs_draw mechanism, composed not re-invented -- one scaler idiom estate-wide)
122func mp_draw(page: *u8, PW: i64, ox: i64, oy: i64, fw: i64, fh: i64, src: *u8, sw: i64, sh: i64) -> i64 {
123 var y: i64 = 0
124 while y < fh {
125 var x: i64 = 0
126 while x < fw {
127 let sx0: i64 = (x * sw) / fw
128 let sy0: i64 = (y * sh) / fh
129 var sx1: i64 = ((x + 1) * sw) / fw
130 var sy1: i64 = ((y + 1) * sh) / fh
131 if sx1 <= sx0 { sx1 = sx0 + 1 }
132 if sy1 <= sy0 { sy1 = sy0 + 1 }
133 if sx1 > sw { sx1 = sw }
134 if sy1 > sh { sy1 = sh }
135 var r: i64 = 0
136 var g: i64 = 0
137 var b: i64 = 0
138 var cnt: i64 = 0
139 var yy: i64 = sy0
140 while yy < sy1 {
141 var xx: i64 = sx0
142 while xx < sx1 {
143 let o: i64 = (yy * sw + xx) * 3
144 r = r + (src[o] as i64)
145 g = g + (src[o+1] as i64)
146 b = b + (src[o+2] as i64)
147 cnt = cnt + 1
148 xx = xx + 1
149 }
150 yy = yy + 1
151 }
152 if cnt > 0 {
153 let d: i64 = ((oy + y) * PW + (ox + x)) * 3
154 page[d] = (r / cnt) as u8
155 page[d+1] = (g / cnt) as u8
156 page[d+2] = (b / cnt) as u8
157 }
158 x = x + 1
159 }
160 y = y + 1
161 }
162 return 0
163}
164func mp_hline(page: *u8, PW: i64, x0: i64, x1: i64, y: i64) -> i64 {
165 var x: i64 = x0
166 while x < x1 { let d: i64 = (y * PW + x) * 3; page[d] = 16 as u8; page[d+1] = 16 as u8; page[d+2] = 20 as u8; x = x + 1 }
167 return 0
168}
169func mp_vline(page: *u8, PW: i64, x: i64, y0: i64, y1: i64) -> i64 {
170 var y: i64 = y0
171 while y < y1 { let d: i64 = (y * PW + x) * 3; page[d] = 16 as u8; page[d+1] = 16 as u8; page[d+2] = 20 as u8; y = y + 1 }
172 return 0
173}
174// THE PAGE-EMIT CONTRACT (watched as mangagen_mp_page_emit): decode each input, place onto the page
175// per the layout (aspect-fit, centered, white ground; black frame border px inside each rect), print
176// the partition (panel<-input, counts must sum), write the PNG. Returns 0 or a named nonzero.
177func mp_page_emit(meta: *i64, rects: *i64, paths: *i64, n: i64, outp: *u8) -> i64 {
178 let PW: i64 = meta[0]
179 let PH: i64 = meta[1]
180 let bord: i64 = meta[2]
181 let ord: i64 = meta[3]
182 let page: *u8 = sys_mmap(PW * PH * 3 + 64)
183 var p: i64 = 0
184 while p < PW * PH { let d: i64 = p * 3; page[d] = 255 as u8; page[d+1] = 255 as u8; page[d+2] = 255 as u8; p = p + 1 }
185 let wh: *i64 = sys_mmap(32) as *i64
186 var placed: i64 = 0
187 var i: i64 = 0
188 while i < n {
189 var j: i64 = i
190 if ord == 1 { j = n - 1 - i }
191 let path: *u8 = paths[j] as *u8
192 wh[0] = 0
193 wh[1] = 0
194 let rgb: *u8 = nx_img_to_rgb(path, wh)
195 if rgb == 0 as *u8 { mp_puts("MANGA-PAGE-REFUSE undecodable input: " as *u8); mp_puts(path); mp_puts("\n" as *u8); return 4 }
196 if wh[0] <= 0 { return 4 }
197 if wh[1] <= 0 { return 4 }
198 let px: i64 = (rects[i*4+0] * PW) / 1000
199 let py: i64 = (rects[i*4+1] * PH) / 1000
200 let pw2: i64 = (rects[i*4+2] * PW) / 1000
201 let ph2: i64 = (rects[i*4+3] * PH) / 1000
202 let iw: i64 = pw2 - 2 * bord
203 let ih: i64 = ph2 - 2 * bord
204 let sw: i64 = wh[0]
205 let sh: i64 = wh[1]
206 var fw: i64 = iw
207 var fh: i64 = ih
208 if sw * ih >= sh * iw { fh = (iw * sh) / sw } else { fw = (ih * sw) / sh }
209 if fw < 1 { fw = 1 }
210 if fh < 1 { fh = 1 }
211 let ox: i64 = px + bord + (iw - fw) / 2
212 let oy: i64 = py + bord + (ih - fh) / 2
213 mp_draw(page, PW, ox, oy, fw, fh, rgb, sw, sh)
214 // black frame, bord px, inside the rect
215 var t: i64 = 0
216 while t < bord {
217 mp_hline(page, PW, px, px + pw2, py + t)
218 mp_hline(page, PW, px, px + pw2, py + ph2 - 1 - t)
219 mp_vline(page, PW, px + t, py, py + ph2)
220 mp_vline(page, PW, px + pw2 - 1 - t, py, py + ph2)
221 t = t + 1
222 }
223 mp_puts(" panel " as *u8); mp_pi(i); mp_puts(" <- input " as *u8); mp_pi(j)
224 mp_puts(" (" as *u8); mp_puts(path); mp_puts(") " as *u8); mp_pi(fw); mp_puts("x" as *u8); mp_pi(fh); mp_puts("\n" as *u8)
225 placed = placed + 1
226 i = i + 1
227 }
228 let wrc: i64 = nx_png_write_rgb(outp, page, PW, PH)
229 mp_puts(" partition: placed=" as *u8); mp_pi(placed); mp_puts(" of panels=" as *u8); mp_pi(n)
230 if placed == n { mp_puts(" (parts sum)\n" as *u8) } else { mp_puts(" PARTITION-LEAK\n" as *u8); return 5 }
231 if wrc != 0 { mp_puts("MANGA-PAGE-REFUSE png write failed\n" as *u8); return 6 }
232 return 0
233}
234func main(argc: i64, argv: *i64) -> i64 {
235 if argc < 5 {
236 mp_puts("usage: nx_manga_page <layouts.conf> <layout> <out.png> <img1> ... <imgN>\n" as *u8)
237 mp_puts(" layouts are DATA rows: name|pageW|pageH|border|order|x,y,w,h;... (per-mille rects)\n" as *u8)
238 sys_exit(2); return 2
239 }
240 let meta: *i64 = sys_mmap(64) as *i64
241 let rects: *i64 = sys_mmap(MP_MAXP * 4 * 8) as *i64
242 let np: i64 = mp_layout_rows(argv[1] as *u8, argv[2] as *u8, meta, rects)
243 if np == 0 - 1 { mp_puts("MANGA-PAGE-REFUSE conf unreadable: " as *u8); mp_puts(argv[1] as *u8); mp_puts("\n" as *u8); sys_exit(3); return 3 }
244 if np == 0 - 2 { mp_puts("MANGA-PAGE-REFUSE layout not found in conf: " as *u8); mp_puts(argv[2] as *u8); mp_puts(" (add a DATA row, never code)\n" as *u8); sys_exit(3); return 3 }
245 if np <= 0 { mp_puts("MANGA-PAGE-REFUSE bad layout row\n" as *u8); sys_exit(3); return 3 }
246 let nin: i64 = argc - 4
247 if nin != np {
248 mp_puts("MANGA-PAGE-REFUSE input count " as *u8); mp_pi(nin)
249 mp_puts(" != layout panels " as *u8); mp_pi(np)
250 mp_puts(" -- a page with empty or dropped panels is a wrong artifact; supply exactly " as *u8); mp_pi(np); mp_puts(" images\n" as *u8)
251 sys_exit(3); return 3
252 }
253 let paths: *i64 = sys_mmap(MP_MAXP * 8 + 16) as *i64
254 var k: i64 = 0
255 while k < nin { paths[k] = argv[4 + k]; k = k + 1 }
256 mp_puts("=== nx_manga_page layout=" as *u8); mp_puts(argv[2] as *u8)
257 mp_puts(" page=" as *u8); mp_pi(meta[0]); mp_puts("x" as *u8); mp_pi(meta[1])
258 mp_puts(" panels=" as *u8); mp_pi(np)
259 if meta[3] == 1 { mp_puts(" order=rtl\n" as *u8) } else { mp_puts(" order=ltr\n" as *u8) }
260 let rc: i64 = mp_page_emit(meta, rects, paths, np, argv[3] as *u8)
261 if rc == 0 { mp_puts("MANGA-PAGE-OK " as *u8); mp_puts(argv[3] as *u8); mp_puts("\n" as *u8); sys_exit(0); return 0 }
262 sys_exit(rc)
263 return rc
264}