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}