code wiki / _hdl_build / nx_pdf.nx
nx_pdf.nx source
↩ module page · 137 lines · 8001 B
1// nx_pdf.nx -- sovereign PDF -> HTML preview (v1 = TEXT-LAYER preview). Composes the proven extractor
2// nx_pdf_text (objects -> FlateDecode -> per-font /ToUnicode CMap -> UTF-8; rule 15 DRY, fork-exec'd) and
3// adds the viewer half: a TEXT-QUALITY LINE FILTER (real PDFs leak non-text streams + glyph soup from
4// subset fonts without usable ToUnicode -- a line is kept only if it looks like language: a >=3-letter word,
5// >=40% ASCII-alpha of non-space bytes, no control bytes) then wraps the survivors as the standard 0-JS
6// preview page. HONEST v1: text layer only -- figures/graphics/exact typography are NOT painted, and the
7// page footer says so. Same `html <in> <out.html> [dlurl]` iface as nx_docx/nx_md/nx_csv, so the
8// nx_doc_view registry routes .pdf with zero special-casing. license_tier: ORIGINAL
9import "nx_syscalls.nx"
10import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
11const K_MAGIC_6000: i64 = 6000
12const K_MAGIC_33554432: i64 = 33554432
13
14func pf_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8) { n=n+1 } return n }
15func pf_puts(s: *u8) -> i64 { sys_write(1, s, pf_slen(s)); return 0 }
16// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
17// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
18// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
19// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
20func pf_num(v: i64) -> i64 { nxi_out(v); return 0 }
21func pf_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 }
22
23func pf_exists(path: *u8) -> i64 { let fd: i64=sys_openat_rd(path); if fd<0 { return 0 } sys_close(fd); return 1 }
24
25// fork the blessed extractor: _offc/nx_pdf_text.elf <in.pdf> <out.txt>; return child exit code.
26func pf_extract(inp: *u8, txtout: *u8) -> i64 {
27 let pid: i64=sys_fork()
28 if pid==0 {
29 let lfd: i64=sys_openat_wr("/tmp/nx_pdf_extract.log" as *u8, 0x1a4)
30 if lfd>=0 { sys_dup3(lfd,1,0); sys_dup3(lfd,2,0) }
31 let argv: *i64=sys_mmap(48) as *i64
32 argv[0]="_offc/nx_pdf_text.elf" as *u8 as i64
33 argv[1]=inp as i64
34 argv[2]=txtout as i64
35 argv[3]=0
36 let envp: *i64=sys_mmap(16) as *i64; envp[0]=0
37 sys_execve("_offc/nx_pdf_text.elf" as *u8, argv, envp)
38 sys_exit(127)
39 }
40 let st: *i64=sys_mmap(16) as *i64; sys_wait4(pid, st, 0)
41 let sig: i64=st[0]&0x7f; if sig!=0 { return 128+sig }
42 return (st[0]>>8)&0xff
43}
44
45// does src[s,e) look like LANGUAGE? (>=3-letter run, alpha>=40% of non-space, no control bytes)
46func pf_is_text(src: *u8, s: i64, e: i64) -> i64 {
47 var alpha: i64=0
48 var nonspace: i64=0
49 var run: i64=0
50 var bestrun: i64=0
51 var i: i64=s
52 while i<e {
53 let c: i64=src[i]&0xff
54 if c<32 { if c!=9 { return 0 } } // control byte -> not text
55 if c==127 { return 0 }
56 var isal: i64=0
57 if c>=65 { if c<=90 { isal=1 } }
58 if c>=97 { if c<=122 { isal=1 } }
59 if isal==1 { alpha=alpha+1; run=run+1; if run>bestrun { bestrun=run } } else { run=0 }
60 if c!=32 { if c!=9 { nonspace=nonspace+1 } }
61 i=i+1
62 }
63 if bestrun<3 { return 0 }
64 if nonspace<4 { return 0 }
65 if alpha*100 < nonspace*40 { return 0 }
66 return 1
67}
68
69// escape src[s,e) into dst as HTML text (& < >); 0xC2 0xA0 (nbsp) -> plain space; return new offset
70func pf_esc(dst: *u8, off: i64, src: *u8, s: i64, e: i64) -> i64 {
71 var o: i64=off; var i: i64=s
72 while i<e {
73 let c: i64=src[i]&0xff
74 if c==38 { o=pf_cat(dst,o,"&" as *u8); i=i+1 } else {
75 if c==60 { o=pf_cat(dst,o,"<" as *u8); i=i+1 } else {
76 if c==62 { o=pf_cat(dst,o,">" as *u8); i=i+1 } else {
77 if c==194 { if i+1<e { if (src[i+1]&0xff)==160 { dst[o]=32 as u8; o=o+1; i=i+2 } else { dst[o]=src[i]; o=o+1; i=i+1 } } else { i=i+1 } } else {
78 dst[o]=src[i]; o=o+1; i=i+1
79 } } } }
80 }
81 return o
82}
83
84func pf_to_html(txt: *u8, tlen: i64, out: *u8, dlurl: *u8) -> i64 {
85 var o: i64=0
86 o=pf_cat(out,o,"<!DOCTYPE html>\n<html lang=en><head><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>Document Preview - Nishi Office</title><style>\nbody{font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;margin:0;background:#eef1f5;color:#1a1d21}\nheader{background:#0f172a;color:#fff;padding:16px 24px}header h1{margin:0;font-size:17px;font-weight:600}\n.doc{max-width:840px;margin:22px auto;background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:34px 46px;box-shadow:0 1px 4px rgba(15,23,42,.08)}\n.doc p{margin:9px 0}\n.note{max-width:840px;margin:0 auto 8px;background:#fef9c3;border:1px solid #fde047;border-radius:8px;padding:8px 14px;font-size:13px;color:#713f12}\n.bar{max-width:840px;margin:0 auto 6px;text-align:right}.dl{display:inline-block;padding:9px 18px;background:#2563eb;color:#fff;border-radius:8px;text-decoration:none;font-size:14px;font-weight:500}\nfooter{max-width:840px;margin:14px auto 46px;color:#64748b;font-size:13px;text-align:center}\n</style></head>\n<body>\n<header><h1>📄 Document Preview — Nishi Office</h1></header>\n<div class=note>Text-layer preview (v1): the document's TEXT, filtered and readable. Figures, graphics and exact typography are not painted yet.</div>\n<div class=doc>\n" as *u8)
87 var kept: i64=0
88 var i: i64=0
89 while i<tlen {
90 var e: i64=i
91 while e<tlen { if txt[e]==(10 as u8) { break } e=e+1 }
92 var end: i64=e
93 if end>i { if txt[end-1]==(13 as u8) { end=end-1 } }
94 if end>i {
95 if kept<K_MAGIC_6000 {
96 let ok: i64=pf_is_text(txt, i, end)
97 if ok==1 {
98 o=pf_cat(out,o,"<p>" as *u8)
99 o=pf_esc(out,o,txt,i,end)
100 o=pf_cat(out,o,"</p>\n" as *u8)
101 kept=kept+1
102 }
103 }
104 }
105 i=e+1
106 }
107 if kept==0 { o=pf_cat(out,o,"<p><i>No readable text layer found in this PDF (it may be scanned images or use fonts without a text map).</i></p>\n" as *u8) }
108 o=pf_cat(out,o,"</div>\n" as *u8)
109 if (dlurl as i64)!=0 { o=pf_cat(out,o,"<div class=bar><a class=dl href=\"" as *u8); o=pf_cat(out,o,dlurl); o=pf_cat(out,o,"\">⬇ Download .pdf</a></div>\n" as *u8) }
110 o=pf_cat(out,o,"<footer>Rendered natively by <b>nx_pdf</b> (sovereign extractor nx_pdf_text: FlateDecode + per-font ToUnicode) · viewable inline in the Nishi browser</footer>\n</body></html>\n" as *u8)
111 return o
112}
113
114func main(argc: i64, argv: *i64) -> i64 {
115 if argc<4 { pf_puts("usage: nx_pdf html <in.pdf> <out.html> [download_url]\n" as *u8); sys_exit(2); return 2 }
116 let cmd: *u8=argv[1] as *u8
117 if cmd[0]!=(104 as u8) { pf_puts("nx_pdf: unknown command (html)\n" as *u8); sys_exit(2); return 2 }
118 let inp: *u8=argv[2] as *u8
119 let outp: *u8=argv[3] as *u8
120 var dlu: *u8=0 as *u8
121 if argc>4 { dlu=argv[4] as *u8 }
122 if pf_exists(inp)==0 { pf_puts("PDF-HTML-FAIL cannot-read\n" as *u8); sys_exit(1); return 1 }
123 let txtp: *u8="/tmp/nx_pdf_extract.txt" as *u8
124 let xrc: i64=pf_extract(inp, txtp)
125 if xrc!=0 { pf_puts("PDF-HTML-FAIL extractor rc=" as *u8); pf_num(xrc); pf_puts("\n" as *u8); sys_exit(1); return 1 }
126 let lp: *i64=sys_mmap(16) as *i64
127 let txt: *u8=sys_read_file(txtp, lp)
128 let tlen: i64=lp[0]
129 if tlen<=0 { pf_puts("PDF-HTML-FAIL empty-extract\n" as *u8); sys_exit(1); return 1 }
130 let out: *u8=sys_mmap(K_MAGIC_33554432)
131 let n: i64=pf_to_html(txt, tlen, out, dlu)
132 let fd: i64=sys_openat_wr(outp, 0x1a4)
133 if fd<0 { pf_puts("PDF-HTML-FAIL cannot-write\n" as *u8); sys_exit(1); return 1 }
134 sys_write(fd, out, n); sys_close(fd)
135 pf_puts("PDF-HTML-OK path=" as *u8); pf_puts(outp); pf_puts(" bytes=" as *u8); pf_num(n); pf_puts("\n" as *u8)
136 sys_exit(0); return 0
137}