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, "<" as *u8) }
193 else { if ch == 62 { w = pr_cat(html, w, ">" as *u8) }
194 else { if ch == 38 { w = pr_cat(html, w, "&" 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}