code wiki / _hdl_build / _pdf_reflow_lib.nx

_pdf_reflow_lib.nx source

↩ module page · 205 lines · 10116 B

1// _pdf_reflow_lib.nx -- SOVEREIGN PDF -> reflowable-mobile-HTML pipeline (shared library). 2// Imported by nx_pdf_reflow.nx (the KAT gate) and nx_media_server.nx (the live /reflow route), so the 3// extraction logic lives once (DRY). Pure functions: caller provides the html/txt/dec buffers. 4// 5// pr_pdf_to_reflow(pdf,n,html,txt,dec,txt_out) -> html_len: iterates EVERY "stream".."endstream", decodes 6// FlateDecode (zlib +2) or raw, runs an operator-aware content-stream text extractor (BT/ET, Tj/TJ, '/", 7// Td/TD/T*, (..)+<hex> strings, TJ word-gaps, line breaks; i+op in an *i64 state cell + pr_lit/pr_hex helpers 8// to keep per-function locals low -- a fat function miscompiles under nx_cc), and emits reflowable mobile HTML. 9// txt_out[0] receives the extracted-text char count (the reflow-vs-pageimage signal). license_tier: ORIGINAL 10import "nx_syscalls.nx" 11import "_inflate_lib_authored.nx" 12import "nx_charset.nx" // MOJIBAKE FIX: nx_charset_repair_utf8 -- WinAnsi/CP1252 PDF text -> clean UTF-8 13 14func pr_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 15func pr_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 } 16 17func pr_sub(buf: *u8, n: i64, from: i64, needle: *u8, nl: i64) -> i64 { 18 var i: i64=from 19 while i+nl<=n { var ok: i64=1; var j: i64=0; while j<nl { if buf[i+j]!=needle[j] { ok=0; j=nl } else { j=j+1 } } if ok==1 { return i } i=i+1 } 20 return 0-1 21} 22func pr_has(hay: *u8, hn: i64, needle: *u8) -> i64 { 23 let nl: i64 = pr_slen(needle) 24 if nl==0 { return 0 } 25 var i: i64=0 26 while i+nl<=hn { var ok: i64=1; var j: i64=0; while j<nl { if hay[i+j]!=needle[j] { ok=0; j=nl } else { j=j+1 } } if ok==1 { return 1 } i=i+1 } 27 return 0 28} 29func pr_back_flate(pdf: *u8, pos: i64, win: i64) -> i64 { 30 var s: i64 = pos - win 31 if s < 0 { s = 0 } 32 if pr_sub(pdf, pos, s, "/FlateDecode" as *u8, 12) >= 0 { return 1 } 33 return 0 34} 35func pr_alpha(ch: i64) -> i64 { if ch>=65 { if ch<=90 { return 1 } } if ch>=97 { if ch<=122 { return 1 } } return 0 } 36 37// read a literal ( .. ) string. st[0]=index of '('; on return st[0]=index after ')'; appends to out, advances st[1]. 38func pr_lit(c: *u8, n: i64, out: *u8, st: *i64) -> i64 { 39 var i: i64 = st[0] + 1 40 var op: i64 = st[1] 41 var go: i64 = 1 42 while go == 1 { 43 if i >= n { go = 0 } else { 44 let d: i64 = c[i] as i64 45 if d == 92 { 46 i = i + 1 47 if i < n { 48 let e: i64 = c[i] as i64 49 if e == 110 { out[op]=10 as u8; op=op+1 } 50 else { if e == 114 { out[op]=13 as u8; op=op+1 } 51 else { if e == 116 { out[op]=9 as u8; op=op+1 } 52 else { if e == 10 { } else { if e == 13 { } else { out[op]=e as u8; op=op+1 } } } } } 53 i = i + 1 54 } 55 } else { if d == 41 { go = 0; i = i + 1 } else { out[op]=d as u8; op=op+1; i=i+1 } } 56 } 57 } 58 st[0] = i; st[1] = op 59 return 0 60} 61 62// read a < hex > string. st[0]=index of '<'; on return st[0]=index after '>'; appends decoded bytes. 63func pr_hex(c: *u8, n: i64, out: *u8, st: *i64) -> i64 { 64 var i: i64 = st[0] + 1 65 var op: i64 = st[1] 66 var hi: i64 = 0 - 1 67 var go: i64 = 1 68 while go == 1 { 69 if i >= n { go = 0 } else { 70 let d: i64 = c[i] as i64 71 if d == 62 { go = 0; i = i + 1 } else { 72 var hv: i64 = 0 - 1 73 if d>=48 { if d<=57 { hv = d-48 } } 74 if d>=65 { if d<=70 { hv = d-55 } } 75 if d>=97 { if d<=102 { hv = d-87 } } 76 if hv >= 0 { if hi < 0 { hi = hv } else { out[op]=((hi*16)+hv) as u8; op=op+1; hi = 0 - 1 } } 77 i = i + 1 78 } 79 } 80 } 81 if hi >= 0 { out[op]=(hi*16) as u8; op=op+1 } 82 st[0] = i; st[1] = op 83 return 0 84} 85 86// operator-aware content-stream text extractor. appends to out at op0; returns new op. 87func pr_extract(c: *u8, n: i64, out: *u8, op0: i64) -> i64 { 88 let st: *i64 = sys_mmap(16) as *i64 89 st[0] = 0 90 st[1] = op0 91 var inarray: i64 = 0 92 var more: i64 = 1 93 while more == 1 { 94 let i: i64 = st[0] 95 if i >= n { more = 0 } else { 96 let ch: i64 = c[i] as i64 97 if ch == 40 { pr_lit(c, n, out, st) } 98 else { if ch == 60 { 99 if i+1 < n { if c[i+1]==(60 as u8) { st[0]=i+2 } else { pr_hex(c, n, out, st) } } else { st[0]=i+1 } 100 } 101 else { if ch == 91 { inarray=1; st[0]=i+1 } 102 else { if ch == 93 { inarray=0; st[0]=i+1 } 103 else { if ch == 39 { out[st[1]]=10 as u8; st[1]=st[1]+1; st[0]=i+1 } 104 else { if ch == 34 { out[st[1]]=10 as u8; st[1]=st[1]+1; st[0]=i+1 } 105 else { 106 if inarray == 1 { 107 if ch == 45 { 108 var j: i64 = i+1 109 var mag: i64 = 0 110 var g3: i64 = 1 111 while g3==1 { if j<n { let f: i64=c[j] as i64; if f>=48 { if f<=57 { mag=mag*10+(f-48); j=j+1 } else {g3=0} } else {g3=0} } else {g3=0} } 112 if mag>=100 { out[st[1]]=32 as u8; st[1]=st[1]+1 } 113 st[0]=j 114 } else { st[0]=i+1 } 115 } else { 116 if pr_alpha(ch)==1 { 117 var j2: i64 = i 118 var g4: i64 = 1 119 while g4==1 { if j2<n { if pr_alpha(c[j2] as i64)==1 {j2=j2+1} else {g4=0} } else {g4=0} } 120 var nl: i64 = 0 121 let L: i64 = j2-i 122 if L==2 { let a: i64=c[i] as i64; let b: i64=c[i+1] as i64; if a==84 { if b==100 {nl=1} if b==68 {nl=1} } if a==69 { if b==84 {nl=1} } } 123 if L==1 { if c[i]==(84 as u8) { if j2<n { if c[j2]==(42 as u8) { nl=1; j2=j2+1 } } } } 124 if nl==1 { out[st[1]]=10 as u8; st[1]=st[1]+1 } 125 st[0]=j2 126 } else { st[0]=i+1 } 127 } 128 } } } } } } 129 } 130 } 131 return st[1] 132} 133 134func pr_pdf_to_reflow(pdf: *u8, n: i64, html: *u8, txt: *u8, dec: *u8, txt_out: *i64) -> i64 { 135 var tp: i64 = 0 136 var pos: i64 = 0 137 var guard: i64 = 0 138 var more: i64 = 1 139 while more == 1 { 140 guard = guard + 1 141 if guard > 200000 { more = 0 } else { 142 let s: i64 = pr_sub(pdf, n, pos, "stream" as *u8, 6) 143 if s < 0 { more = 0 } else { 144 var ds: i64 = s + 6 145 if ds < n { if pdf[ds]==(13 as u8) { ds=ds+1 } } 146 if ds < n { if pdf[ds]==(10 as u8) { ds=ds+1 } } 147 let e: i64 = pr_sub(pdf, n, ds, "endstream" as *u8, 9) 148 if e < 0 { more = 0 } else { 149 var de: i64 = e 150 if de>ds { if pdf[de-1]==(10 as u8) { de=de-1 } } 151 if de>ds { if pdf[de-1]==(13 as u8) { de=de-1 } } 152 let flate: i64 = pr_back_flate(pdf, s, 600) 153 var dlen: i64 = 0 154 if flate == 1 { 155 if de-ds-2 > 0 { dlen = inflate(((pdf as i64)+ds+2) as *u8, de-ds-2, dec) } 156 } else { 157 var k: i64 = 0 158 while k < de-ds { dec[k]=pdf[ds+k]; k=k+1 } 159 dlen = de-ds 160 } 161 if dlen > 0 { if dlen < 4194000 { tp = pr_extract(dec, dlen, txt, tp); txt[tp]=10 as u8; tp=tp+1; txt[tp]=10 as u8; tp=tp+1 } } 162 pos = e + 9 163 } 164 } 165 } 166 } 167 txt_out[0] = tp 168 // MOJIBAKE FIX: PDF content-stream text is font-encoded (commonly WinAnsi/CP1252), so a symbol like the 169 // copyright © is a raw 0xA9 byte -> in a UTF-8 page that renders as U+FFFD (). Repair the extracted text to 170 // clean UTF-8 (idempotent on any genuine UTF-8) BEFORE emitting. Reuse `dec` (the inflate scratch, fully 171 // consumed once tp is extracted; the function already assumes dec holds ~4MB via the <4194000 guard above). 172 let tp2: i64 = nx_charset_repair_utf8(txt, tp, dec, 4194000) 173 174 var w: i64 = 0 175 w = pr_cat(html, w, "<!doctype html>\n<html lang=\"en\"><head><meta charset=\"utf-8\">\n" as *u8) 176 w = pr_cat(html, w, "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n" as *u8) 177 w = pr_cat(html, w, "<title>Nishi Library - reflowed PDF</title>\n" as *u8) 178 w = pr_cat(html, w, "<style>:root{color-scheme:light dark}body{max-width:38rem;margin:0 auto;padding:1.2rem 1.1rem 4rem;" as *u8) 179 w = pr_cat(html, w, "font:1.12rem/1.65 -apple-system,Segoe UI,Roboto,Georgia,serif;hyphens:auto;word-wrap:break-word}" as *u8) 180 w = pr_cat(html, w, "p{margin:0 0 1em}a.pg{position:fixed;top:.5rem;right:.5rem;font:.8rem/1 sans-serif;background:#0008;color:#fff;padding:.4rem .6rem;border-radius:.4rem;text-decoration:none}" as *u8) 181 w = pr_cat(html, w, "@media(max-width:480px){body{font-size:1.06rem;padding:1rem .9rem 4rem}}</style>\n" as *u8) 182 w = pr_cat(html, w, "</head><body>\n<p>" as *u8) 183 var i: i64 = 0 184 var pend: i64 = 0 185 var any: i64 = 0 186 while i < tp2 { 187 let ch: i64 = dec[i] as i64 188 if ch == 10 { pend = pend + 1; i = i + 1 } 189 else { if ch == 13 { i = i + 1 } 190 else { 191 if pend > 0 { if pend >= 2 { w = pr_cat(html, w, "</p>\n<p>" as *u8) } else { html[w]=32 as u8; w=w+1 } pend = 0 } 192 if ch == 60 { w = pr_cat(html, w, "&lt;" as *u8) } 193 else { if ch == 62 { w = pr_cat(html, w, "&gt;" as *u8) } 194 else { if ch == 38 { w = pr_cat(html, w, "&amp;" as *u8) } 195 else { if ch == 9 { html[w]=32 as u8; w=w+1 } 196 else { html[w]=ch as u8; w=w+1 } } } } 197 if ch > 32 { any = 1 } 198 i = i + 1 199 } } 200 } 201 w = pr_cat(html, w, "</p>\n" as *u8) 202 if any == 0 { w = pr_cat(html, w, "<p><em>No extractable text layer (likely a scanned / image-only PDF) - use page view.</em></p>\n" as *u8) } 203 w = pr_cat(html, w, "</body></html>\n" as *u8) 204 return w 205}