code wiki / _hdl_build / nx_rtf_book.nx
nx_rtf_book.nx source
↩ module page · 173 lines · 10775 B
1// nx_rtf_book.nx -- SOVEREIGN RTF (Rich Text Format) -> Nishi reader format. The library holds .rtf books that
2// dead-ended at /file. RTF is text-based (no proprietary compression, no DRM) so this is a clean parser: handle
3// {} groups + per-group ignore, \* + destination groups (fonttbl/colortbl/stylesheet/info/pict/object/...) skipped,
4// \'xx hex (CP1252 byte), \uN unicode (+ skip the \ucN fallback), \par/\line/\sect -> newline, \tab, \\ \{ \}
5// literals, all other control words skipped -> clean prose -> chap0.txt + book.json so the zero-JS reader renders
6// it. \title -> book title (the \info group is otherwise skipped). REUSABLE RTF->text capability. Usage:
7// nx_rtf_book <rtf-path> <slug>. expect_exit: 0 license_tier: ORIGINAL
8import "nx_syscalls.nx"
9import "nx_charset.nx" // MOJIBAKE FIX (reuse): nx_charset_repair_utf8 -- stray CP1252 bytes -> clean UTF-8
10const K_MAGIC_65536: i64 = 65536
11const K_MAGIC_1024: i64 = 1024
12
13func rf_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
14func rf_w(fd: i64, s: *u8) -> i64 { sys_write(fd, s, rf_slen(s)); return 0 }
15func rf_p(s: *u8) -> i64 { rf_w(1, s); return 0 }
16func rf_pn(v: i64) -> i64 { let b: *u8=sys_mmap(24); var m: i64=v; if m<0{m=0-m} let t: *u8=sys_mmap(24); var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var i: i64=0; while i<k{b[i]=t[k-1-i];i=i+1} sys_write(1,b,k); return 0 }
17func ensure_dir(path: *u8) -> i64 { __syscall(258, 0-100, path, 0x1ed, 0, 0, 0); return 0 }
18func er_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 }
19func er_wjson(fd: i64, s: *u8) -> i64 {
20 var i: i64 = 0
21 while s[i] != (0 as u8) {
22 let c: i64 = s[i] as i64
23 if c == 0x22 { sys_write(fd, "\\\"" as *u8, 2) }
24 else { if c == 0x5c { sys_write(fd, "\\\\" as *u8, 2) }
25 else { if c < 0x20 { sys_write(fd, " " as *u8, 1) }
26 else { sys_write(fd, (s as i64 + i) as *u8, 1) } } }
27 i = i + 1
28 }
29 return 0
30}
31func find_sub(hay: *u8, hl: i64, needle: *u8) -> i64 {
32 let nl: i64 = rf_slen(needle); if nl == 0 { return 0-1 }
33 var i: i64 = 0
34 while i + nl <= hl { var k: i64=0; var hit: i64=1; while k<nl { if hay[i+k]!=needle[k]{hit=0;k=nl}else{k=k+1} } if hit==1 {return i} i=i+1 }
35 return 0-1
36}
37func is_letter(c: i64) -> i64 { if c>=65 { if c<=90 { return 1 } } if c>=97 { if c<=122 { return 1 } } return 0 }
38func is_digit(c: i64) -> i64 { if c>=48 { if c<=57 { return 1 } } return 0 }
39func hexval(c: i64) -> i64 { if c>=48 { if c<=57 { return c-48 } } if c>=97 { if c<=102 { return c-87 } } if c>=65 { if c<=70 { return c-55 } } return 0 }
40func utf8_put(out: *u8, o: i64, cp: i64) -> i64 {
41 if cp < 0x80 { out[o] = cp as u8; return o + 1 }
42 if cp < 0x800 { out[o] = (0xC0 | (cp >> 6)) as u8; out[o+1] = (0x80 | (cp & 0x3F)) as u8; return o + 2 }
43 out[o] = (0xE0 | (cp >> 12)) as u8; out[o+1] = (0x80 | ((cp >> 6) & 0x3F)) as u8; out[o+2] = (0x80 | (cp & 0x3F)) as u8; return o + 3
44}
45// compare src[off..off+wlen) to lit (litlen); equal iff wlen==litlen and bytes match.
46func streqn(src: *u8, off: i64, lit: *u8, litlen: i64, wlen: i64) -> i64 {
47 if wlen != litlen { return 0 }
48 var k: i64 = 0
49 while k < litlen { if src[off+k] != lit[k] { return 0 } k = k + 1 }
50 return 1
51}
52func is_destination(src: *u8, ws: i64, wlen: i64) -> i64 {
53 if streqn(src, ws, "fonttbl" as *u8, 7, wlen)==1 { return 1 }
54 if streqn(src, ws, "colortbl" as *u8, 8, wlen)==1 { return 1 }
55 if streqn(src, ws, "stylesheet" as *u8, 10, wlen)==1 { return 1 }
56 if streqn(src, ws, "info" as *u8, 4, wlen)==1 { return 1 }
57 if streqn(src, ws, "pict" as *u8, 4, wlen)==1 { return 1 }
58 if streqn(src, ws, "object" as *u8, 6, wlen)==1 { return 1 }
59 if streqn(src, ws, "themedata" as *u8, 9, wlen)==1 { return 1 }
60 if streqn(src, ws, "latentstyles" as *u8, 12, wlen)==1 { return 1 }
61 if streqn(src, ws, "generator" as *u8, 9, wlen)==1 { return 1 }
62 if streqn(src, ws, "datastore" as *u8, 9, wlen)==1 { return 1 }
63 return 0
64}
65
66// RTF -> plain text. out is caller-owned (>= cap). returns text length.
67func rtf_to_text(src: *u8, n: i64, out: *u8, cap: i64) -> i64 {
68 let ign: *i64 = sys_mmap(8*600) as *i64
69 var depth: i64 = 0
70 ign[0] = 0
71 var uc: i64 = 1
72 var o: i64 = 0
73 var i: i64 = 0
74 while i < n {
75 if o >= cap - 8 { i = n } else {
76 let c: i64 = src[i] as i64
77 if c == 0x7b { depth = depth + 1; if depth < 600 { ign[depth] = ign[depth-1] } i = i + 1 }
78 else { if c == 0x7d { if depth > 0 { depth = depth - 1 } i = i + 1 }
79 else { if c == 0x5c {
80 i = i + 1
81 if i >= n { i = n } else {
82 let d: i64 = src[i] as i64
83 if d == 0x27 { var b: i64 = 0; if i + 2 < n { b = hexval(src[i+1] as i64) * 16 + hexval(src[i+2] as i64) } if ign[depth]==0 { out[o]=b as u8; o=o+1 } i = i + 3 }
84 else { if d == 0x5c { if ign[depth]==0 { out[o]=0x5c as u8; o=o+1 } i=i+1 }
85 else { if d == 0x7b { if ign[depth]==0 { out[o]=0x7b as u8; o=o+1 } i=i+1 }
86 else { if d == 0x7d { if ign[depth]==0 { out[o]=0x7d as u8; o=o+1 } i=i+1 }
87 else { if d == 0x2a { ign[depth] = 1; i = i + 1 }
88 else { if is_letter(d) == 1 {
89 let ws: i64 = i
90 var g1: i64 = 1
91 while g1 == 1 { if i < n { if is_letter(src[i] as i64)==1 { i=i+1 } else { g1=0 } } else { g1=0 } }
92 let wlen: i64 = i - ws
93 var num: i64 = 0; var hasnum: i64 = 0; var neg: i64 = 0
94 if i < n { if src[i]==(0x2d as u8) { neg=1; i=i+1 } }
95 var g2: i64 = 1
96 while g2 == 1 { if i < n { if is_digit(src[i] as i64)==1 { num=num*10+((src[i] as i64)-48); hasnum=1; i=i+1 } else { g2=0 } } else { g2=0 } }
97 if neg == 1 { num = 0 - num }
98 if i < n { if src[i]==(0x20 as u8) { i=i+1 } }
99 if streqn(src, ws, "par" as *u8, 3, wlen)==1 { if ign[depth]==0 { out[o]=0x0a as u8; o=o+1 } }
100 if streqn(src, ws, "line" as *u8, 4, wlen)==1 { if ign[depth]==0 { out[o]=0x0a as u8; o=o+1 } }
101 if streqn(src, ws, "sect" as *u8, 4, wlen)==1 { if ign[depth]==0 { out[o]=0x0a as u8; o=o+1 } }
102 if streqn(src, ws, "tab" as *u8, 3, wlen)==1 { if ign[depth]==0 { out[o]=0x09 as u8; o=o+1 } }
103 if streqn(src, ws, "uc" as *u8, 2, wlen)==1 { if hasnum==1 { uc = num } }
104 if streqn(src, ws, "u" as *u8, 1, wlen)==1 { if hasnum==1 { var cp: i64 = num; if cp < 0 { cp = cp + K_MAGIC_65536 } if ign[depth]==0 { o = utf8_put(out, o, cp) } var sk: i64 = 0; while sk < uc { if i<n { i=i+1 } sk=sk+1 } } }
105 if is_destination(src, ws, wlen)==1 { ign[depth] = 1 }
106 } else { i = i + 1 } } } } } }
107 }
108 }
109 else { if c == 0x0d { i=i+1 } else { if c == 0x0a { i=i+1 } else { if ign[depth]==0 { out[o]=c as u8; o=o+1 } i=i+1 } } }
110 } }
111 }
112 }
113 out[o] = 0 as u8
114 return o
115}
116func extract_rtf_title(src: *u8, n: i64, out: *u8, cap: i64) -> i64 {
117 out[0] = 0 as u8
118 let t: i64 = find_sub(src, n, "\\title" as *u8)
119 if t < 0 { return 0 }
120 var i: i64 = t + 6
121 if i < n { if src[i]==(0x20 as u8) { i=i+1 } }
122 var o: i64 = 0
123 while i < n { let c: i64 = src[i] as i64; if c==0x7d { i=n } else { if c==0x5c { i=n } else { if o<cap-1 { out[o]=src[i]; o=o+1 } i=i+1 } } }
124 out[o] = 0 as u8
125 return o
126}
127
128const RCAP: i64 = 8388608
129
130func do_rtf(path: *u8, slug: *u8) -> i64 {
131 let lb: *i64 = sys_mmap(16) as *i64
132 let z: *u8 = sys_read_file(path, lb)
133 if (z as i64) == 0 { rf_p("RTF-FAIL read " as *u8); rf_p(path); rf_p("\n" as *u8); return 0-1 }
134 let zl: i64 = lb[0]
135 if zl < 20 { rf_p("RTF-FAIL too-small\n" as *u8); return 0-1 }
136 let title: *u8 = sys_mmap(K_MAGIC_1024); title[0]=0 as u8
137 extract_rtf_title(z, zl, title, K_MAGIC_1024)
138 if title[0] == (0 as u8) { var k: i64=0; while slug[k]!=(0 as u8){ title[k]=slug[k]; k=k+1 } title[k]=0 as u8 }
139 let txt: *u8 = sys_mmap(RCAP)
140 let tlen: i64 = rtf_to_text(z, zl, txt, RCAP)
141 // MOJIBAKE FIX: stray CP1252 (smart quotes \'93/\'94, em-dash \'97) -> clean UTF-8 (same as nx_epub/nx_mobi).
142 let txt2: *u8 = sys_mmap(RCAP)
143 let tlen2: i64 = nx_charset_repair_utf8(txt, tlen, txt2, RCAP)
144 if tlen2 < 20 { rf_p("RTF-FAIL no-text\n" as *u8); return 0-1 }
145 ensure_dir("knowledge/staging/media/reader" as *u8)
146 let dir: *u8 = sys_mmap(512); var dl: i64 = er_cat(dir, 0, "knowledge/staging/media/reader/" as *u8); dl = er_cat(dir, dl, slug); dir[dl]=0 as u8
147 ensure_dir(dir)
148 // chap0.txt
149 let cp0: *u8 = sys_mmap(K_MAGIC_1024); var cl: i64 = er_cat(cp0, 0, dir as *u8); cl = er_cat(cp0, cl, "/chap0.txt" as *u8); cp0[cl]=0 as u8
150 let cfd: i64 = sys_openat_wr(cp0, 0x1a4)
151 if cfd < 0 { rf_p("RTF-FAIL open-chap\n" as *u8); return 0-1 }
152 sys_write(cfd, txt2, tlen2); sys_close(cfd)
153 // book.json (single chapter)
154 let jp: *u8 = sys_mmap(K_MAGIC_1024); var jl: i64 = er_cat(jp, 0, dir as *u8); jl = er_cat(jp, jl, "/book.json" as *u8); jp[jl]=0 as u8
155 let jfd: i64 = sys_openat_wr(jp, 0x1a4)
156 if jfd < 0 { rf_p("RTF-FAIL open-json\n" as *u8); return 0-1 }
157 rf_w(jfd, "{\"title\":\"" as *u8); er_wjson(jfd, title)
158 rf_w(jfd, "\",\"author\":\"\",\"publisher\":\"\",\"pubdate\":\"\",\"isbn\":\"\",\"description\":\"\",\"format\":\"rtf\",\"slug\":\"" as *u8); er_wjson(jfd, slug)
159 rf_w(jfd, "\",\"chapters\":[{\"idx\":0,\"title\":\"Chapter 1\",\"file\":\"chap0.txt\",\"chars\":" as *u8)
160 let nb: *u8=sys_mmap(24); var m: i64=tlen2; let tt: *u8=sys_mmap(24); var kk: i64=0; if m==0{tt[0]=48 as u8;kk=1} while m>0{tt[kk]=(48+(m%10)) as u8;m=m/10;kk=kk+1} var ii: i64=0; while ii<kk{nb[ii]=tt[kk-1-ii];ii=ii+1} sys_write(jfd, nb, kk)
161 rf_w(jfd, ",\"is_nav\":0,\"fn\":[]}],\"nchapters\":1,\"toc\":[{\"depth\":0,\"title\":\"Chapter 1\",\"idx\":0,\"href\":\"\"}],\"ntoc\":1,\"toc_source\":\"rtf-single\",\"assets\":[],\"nassets\":0,\"cover\":\"\"}" as *u8)
162 sys_close(jfd)
163 rf_p("RTF-BOOK title=\"" as *u8); rf_w(1, title); rf_p("\" text_chars=" as *u8); rf_pn(tlen2); rf_p(" -> " as *u8); rf_p(dir); rf_p("\n" as *u8)
164 return 0
165}
166func main(argc: i64, argv: *i64) -> i64 {
167 var path: *u8 = "knowledge/fixtures/nishi_rtf_fixture.rtf" as *u8
168 var slug: *u8 = "nishi_rtf_fixture" as *u8
169 if argc >= 3 { path = argv[1] as *u8; slug = argv[2] as *u8 }
170 let r: i64 = do_rtf(path, slug)
171 if r == 0 { rf_p("RTF-BOOK-OK\n" as *u8); sys_exit(0); return 0 }
172 rf_p("RTF-BOOK-FAIL\n" as *u8); sys_exit(1); return 1
173}