nx_pdf_text.nx source
↩ module page · 660 lines · 33806 B
1// nx_pdf_text.nx -- SOVEREIGN PDF -> text with PER-FONT /ToUnicode CMap association.
2// OSS pipeline (poppler/pdfminer/Docling = reference + benchmark, never deps):
3// objects -> FlateDecode -> per-font CMap (each /Font resource entry resolved to its font object,
4// its encoding [Identity-H = 2-byte] and its /ToUnicode map) -> track the current font via `/Fn Tf`
5// in the content stream -> decode each (...) operand with THAT font's width+map -> UTF-8.
6// This reads MIXED simple+CID/typeset documents (the real IEEE-standard shape), not just pure docs.
7// Composes nx_zlib_wrap (rule 15 DRY). license_tier: ORIGINAL
8// LIMITS: generation-0 objects only; resources found by scanning /Font dicts (no inherited-Resources
9// tree walk); no object streams (ObjStm)/xref-streams/encryption; bfrange array-dst not handled; no
10// glyph-position layout. Proven by gates: simple Type1/TrueType, pure Identity-H, and MIXED simple+CID.
11import "nx_syscalls.nx"
12import "nx_zlib_wrap.nx"
13const PT_MAGIC_65536: i64 = 65536
14const PT_MAGIC_8192: i64 = 8192
15const PT_MAGIC_50331648: i64 = 50331648
16
17const PT_MAX_OUT: i64 = 16777216
18const PT_MAX_STREAM: i64 = 8388608
19const PT_MAP_N: i64 = 65536
20const PT_MAXFONTS: i64 = 64
21const PT_NAMESLOT: i64 = 24
22
23func pt_find(hay: *u8, n: i64, start: i64, needle: *u8, nlen: i64) -> i64 {
24 if nlen <= 0 { return 0 - 1 }
25 var i: i64 = start
26 while i + nlen <= n {
27 var j: i64 = 0
28 var ok: i64 = 1
29 while j < nlen { if hay[i + j] != needle[j] { ok = 0; j = nlen } else { j = j + 1 } }
30 if ok == 1 { return i }
31 i = i + 1
32 }
33 return 0 - 1
34}
35func pt_hexv(ch: i64) -> i64 {
36 if ch >= 0x30 { if ch <= 0x39 { return ch - 0x30 } }
37 if ch >= 0x41 { if ch <= 0x46 { return ch - 0x41 + 10 } }
38 if ch >= 0x61 { if ch <= 0x66 { return ch - 0x61 + 10 } }
39 return 0 - 1
40}
41func pt_read_hex(c: *u8, clen: i64, ip: *i64) -> i64 {
42 var i: i64 = ip[0]; var val: i64 = 0
43 if i < clen { if (c[i] as i64) == 0x3c { i = i + 1 } }
44 var go: i64 = 1
45 while go == 1 {
46 if i >= clen { go = 0 }
47 else { let v: i64 = pt_hexv(c[i] as i64); if v < 0 { if (c[i] as i64) == 0x3e { i = i + 1 } go = 0 } else { val = (val << 4) | v; i = i + 1 } }
48 }
49 ip[0] = i
50 return val
51}
52func pt_is_ws(ch: i64) -> i64 { if ch == 0x20 { return 1 } if ch == 0x0a { return 1 } if ch == 0x0d { return 1 } if ch == 0x09 { return 1 } if ch == 0x0c { return 1 } if ch == 0x00 { return 1 } return 0 }
53func pt_is_delim(ch: i64) -> i64 {
54 if pt_is_ws(ch) == 1 { return 1 }
55 if ch == 0x2f { return 1 } if ch == 0x28 { return 1 } if ch == 0x29 { return 1 } if ch == 0x3c { return 1 } if ch == 0x3e { return 1 }
56 if ch == 0x5b { return 1 } if ch == 0x5d { return 1 } if ch == 0x7b { return 1 } if ch == 0x7d { return 1 } if ch == 0x25 { return 1 }
57 return 0
58}
59func pt_skipws(c: *u8, clen: i64, i: i64) -> i64 {
60 var k: i64 = i; var go: i64 = 1
61 while go == 1 { if k >= clen { go = 0 } else { if pt_is_ws(c[k] as i64) == 1 { k = k + 1 } else { go = 0 } } }
62 return k
63}
64func pt_read_uint(c: *u8, clen: i64, i_p: *i64) -> i64 {
65 var i: i64 = i_p[0]; var v: i64 = 0; var nd: i64 = 0; var go: i64 = 1
66 while go == 1 { if i >= clen { go = 0 } else { let d: i64 = c[i] as i64; if d >= 0x30 { if d <= 0x39 { v = v * 10 + (d - 0x30); nd = nd + 1; i = i + 1 } else { go = 0 } } else { go = 0 } } }
67 i_p[0] = i
68 if nd == 0 { return 0 - 1 }
69 return v
70}
71func pt_itoa(num: i64, buf: *u8) -> i64 {
72 if num == 0 { buf[0] = 0x30 as u8; return 1 }
73 let t: *u8 = sys_mmap(24); var m: i64 = num; var k: i64 = 0
74 while m > 0 { t[k] = (0x30 + (m % 10)) as u8; m = m / 10; k = k + 1 }
75 var i: i64 = 0
76 while i < k { buf[i] = t[k - 1 - i]; i = i + 1 }
77 return k
78}
79// offset just past "num 0 obj" (gen 0), or -1
80func pt_find_obj(buf: *u8, n: i64, num: i64) -> i64 {
81 let nd: *u8 = sys_mmap(32)
82 var p: i64 = pt_itoa(num, nd)
83 nd[p] = 0x20 as u8; nd[p+1] = 0x30 as u8; nd[p+2] = 0x20 as u8; nd[p+3] = 0x6f as u8; nd[p+4] = 0x62 as u8; nd[p+5] = 0x6a as u8; p = p + 6
84 var start: i64 = 0; var go: i64 = 1
85 while go == 1 {
86 let f: i64 = pt_find(buf, n, start, nd, p)
87 if f < 0 { return 0 - 1 }
88 var okp: i64 = 1
89 if f > 0 { let pc: i64 = buf[f-1] as i64; if pc >= 0x30 { if pc <= 0x39 { okp = 0 } } }
90 if okp == 1 { return f + p }
91 start = f + 1
92 }
93 return 0 - 1
94}
95func pt_emit_utf8(out: *u8, op_p: *i64, cap: i64, cp: i64) -> i64 {
96 var op: i64 = op_p[0]
97 if cp < 0x80 { if op < cap { out[op] = cp as u8; op = op + 1 } }
98 else { if cp < 0x800 { if op + 1 < cap { out[op] = (0xc0 | (cp >> 6)) as u8; out[op+1] = (0x80 | (cp & 0x3f)) as u8; op = op + 2 } }
99 else { if op + 2 < cap { out[op] = (0xe0 | (cp >> 12)) as u8; out[op+1] = (0x80 | ((cp >> 6) & 0x3f)) as u8; out[op+2] = (0x80 | (cp & 0x3f)) as u8; op = op + 3 } } }
100 op_p[0] = op
101 return 0
102}
103// emit a codepoint, expanding packed-ASCII ligatures (bit 0x40000000 = up to 3 low bytes).
104func pt_emit_cp(out: *u8, op_p: *i64, cap: i64, cp: i64) -> i64 {
105 if (cp & 0x40000000) != 0 {
106 var pc: i64 = cp & 0xff
107 if pc != 0 { pt_emit_utf8(out, op_p, cap, pc) }
108 pc = (cp >> 8) & 0xff
109 if pc != 0 { pt_emit_utf8(out, op_p, cap, pc) }
110 pc = (cp >> 16) & 0xff
111 if pc != 0 { pt_emit_utf8(out, op_p, cap, pc) }
112 return 0
113 }
114 pt_emit_utf8(out, op_p, cap, cp)
115 return 0
116}
117// parse bfchar+bfrange from a decoded CMap into map[code]=unicode
118func pt_parse_cmap_block(c: *u8, clen: i64, map: *i64) -> i64 {
119 var cnt: i64 = 0
120 let ip: *i64 = sys_mmap(16) as *i64
121 var pos: i64 = 0; var g1: i64 = 1
122 while g1 == 1 {
123 let s: i64 = pt_find(c, clen, pos, "beginbfchar" as *u8, 11)
124 if s < 0 { g1 = 0 }
125 else {
126 var ee: i64 = pt_find(c, clen, s, "endbfchar" as *u8, 9)
127 let had: i64 = ee
128 if ee < 0 { ee = clen }
129 var i: i64 = pt_skipws(c, ee, s + 11)
130 while i < ee {
131 if (c[i] as i64) == 0x3c {
132 ip[0] = i; let src: i64 = pt_read_hex(c, ee, ip)
133 let j: i64 = pt_skipws(c, ee, ip[0]); ip[0] = j
134 let dst: i64 = pt_read_hex(c, ee, ip)
135 i = pt_skipws(c, ee, ip[0])
136 if src >= 0 { if src < PT_MAP_N { map[src] = dst; cnt = cnt + 1 } }
137 } else { i = i + 1 }
138 }
139 if had < 0 { g1 = 0 } else { pos = ee + 9 }
140 }
141 }
142 pos = 0; var g2: i64 = 1
143 while g2 == 1 {
144 let s: i64 = pt_find(c, clen, pos, "beginbfrange" as *u8, 12)
145 if s < 0 { g2 = 0 }
146 else {
147 var ee: i64 = pt_find(c, clen, s, "endbfrange" as *u8, 10)
148 let had: i64 = ee
149 if ee < 0 { ee = clen }
150 var i: i64 = pt_skipws(c, ee, s + 12)
151 while i < ee {
152 if (c[i] as i64) == 0x3c {
153 ip[0] = i; let lo: i64 = pt_read_hex(c, ee, ip)
154 var j: i64 = pt_skipws(c, ee, ip[0]); ip[0] = j
155 let hi: i64 = pt_read_hex(c, ee, ip)
156 j = pt_skipws(c, ee, ip[0])
157 if j < ee { if (c[j] as i64) == 0x3c { ip[0] = j; let dst: i64 = pt_read_hex(c, ee, ip); i = pt_skipws(c, ee, ip[0]); var code: i64 = lo; while code <= hi { if code >= 0 { if code < PT_MAP_N { map[code] = dst + (code - lo); cnt = cnt + 1 } } code = code + 1 } } else { i = j + 1 } } else { i = ee }
158 } else { i = i + 1 }
159 }
160 if had < 0 { g2 = 0 } else { pos = ee + 10 }
161 }
162 }
163 return cnt
164}
165// read a (...) literal raw bytes (escapes decoded) into tmp; returns index past ')'; rlen_p[0]=len
166func pt_read_raw(c: *u8, clen: i64, i_start: i64, tmp: *u8, cap: i64, rlen_p: *i64) -> i64 {
167 var op: i64 = 0; var i: i64 = i_start + 1; var depth: i64 = 1; var go: i64 = 1
168 while go == 1 {
169 if i >= clen { go = 0 }
170 else {
171 let ch: i64 = c[i] as i64
172 if ch == 0x5c {
173 i = i + 1
174 if i < clen {
175 let e: i64 = c[i] as i64
176 var isoct: i64 = 0
177 if e >= 0x30 { if e <= 0x37 { isoct = 1 } }
178 if isoct == 1 {
179 // octal escape \ddd (1-3 digits) -> byte value
180 var val: i64 = e - 0x30
181 i = i + 1
182 var dc: i64 = 1
183 var cont: i64 = 1
184 while cont == 1 {
185 if dc >= 3 { cont = 0 }
186 else { if i >= clen { cont = 0 }
187 else { let e2: i64 = c[i] as i64
188 if e2 >= 0x30 { if e2 <= 0x37 { val = val * 8 + (e2 - 0x30); i = i + 1; dc = dc + 1 } else { cont = 0 } }
189 else { cont = 0 } } }
190 }
191 if op < cap { tmp[op] = val as u8; op = op + 1 }
192 } else {
193 var o: i64 = e
194 if e == 0x6e { o = 0x0a } if e == 0x72 { o = 0x0d } if e == 0x74 { o = 0x09 } if e == 0x62 { o = 0x08 } if e == 0x66 { o = 0x0c }
195 if op < cap { tmp[op] = o as u8; op = op + 1 }
196 i = i + 1
197 }
198 }
199 } else {
200 if ch == 0x28 { depth = depth + 1; if op < cap { tmp[op] = ch as u8; op = op + 1 } i = i + 1 }
201 else { if ch == 0x29 { depth = depth - 1; if depth == 0 { i = i + 1; go = 0 } else { if op < cap { tmp[op] = ch as u8; op = op + 1 } i = i + 1 } }
202 else { if op < cap { tmp[op] = ch as u8; op = op + 1 } i = i + 1 } }
203 }
204 }
205 }
206 rlen_p[0] = op
207 return i
208}
209// emit one decoded (...) run into out: 2-byte via map if is2, else raw bytes
210func pt_emit_run(tmp: *u8, rlen: i64, out: *u8, cap: i64, op_p: *i64, is2: i64, map: *i64) -> i64 {
211 if is2 == 1 {
212 var k: i64 = 0
213 while k + 1 < rlen {
214 let code: i64 = ((tmp[k] as i64) << 8) | (tmp[k+1] as i64)
215 var cp: i64 = 0
216 if (map as i64) != 0 { if code < PT_MAP_N { cp = map[code] } }
217 if cp == 0 { cp = code }
218 pt_emit_cp(out, op_p, cap, cp)
219 k = k + 2
220 }
221 } else {
222 var k: i64 = 0
223 while k < rlen {
224 let code: i64 = tmp[k] as i64
225 var cp: i64 = 0
226 if (map as i64) != 0 { if code < PT_MAP_N { cp = map[code] } }
227 if cp != 0 { pt_emit_cp(out, op_p, cap, cp) }
228 else { if op_p[0] < cap { out[op_p[0]] = tmp[k]; op_p[0] = op_p[0] + 1 } }
229 k = k + 1
230 }
231 }
232 // auto-space only after MULTI-byte runs (word/line-level Tj). Glyph-at-a-time
233 // fonts (Apache FOP: one (\ddd) Tj per glyph) emit explicit /space glyphs, so
234 // a per-glyph auto-space would split every word ("n c e" instead of "nce").
235 var op: i64 = op_p[0]
236 var addsp: i64 = 0
237 if rlen > 1 { addsp = 1 }
238 if op > 0 { let last: i64 = out[op-1] as i64; if last == 0x20 { addsp = 0 } if last == 0x0a { addsp = 0 } if last == 0x09 { addsp = 0 } }
239 if addsp == 1 { if op < cap { out[op] = 0x20 as u8; op_p[0] = op + 1 } }
240 return 0
241}
242// content extraction with per-font tracking. fname=PT_MAXFONTS*PT_NAMESLOT bytes; fnlen/fis2/fmap arrays.
243func pt_extract_pf(c: *u8, clen: i64, out: *u8, cap: i64, op_p: *i64,
244 fname: *u8, fnlen: *i64, fis2: *i64, fmap: *i64, nf: i64, g_is2: i64, g_map: *i64) -> i64 {
245 let tmp: *u8 = sys_mmap(PT_MAGIC_65536)
246 let rl: *i64 = sys_mmap(16) as *i64
247 let lname: *u8 = sys_mmap(PT_NAMESLOT)
248 let llen_p: *i64 = sys_mmap(16) as *i64
249 llen_p[0] = 0
250 var cur_is2: i64 = g_is2
251 var cur_map: *i64 = g_map
252 var i: i64 = 0
253 while i < clen {
254 let ch: i64 = c[i] as i64
255 if ch == 0x2f { // '/name' -> remember last name token
256 var o: i64 = 0; var k: i64 = i + 1
257 var go: i64 = 1
258 while go == 1 { if k >= clen { go = 0 } else { if pt_is_delim(c[k] as i64) == 1 { go = 0 } else { if o < PT_NAMESLOT { lname[o] = c[k]; o = o + 1 } k = k + 1 } } }
259 llen_p[0] = o; i = k
260 } else { if ch == 0x28 { // '(' string -> emit with current font
261 i = pt_read_raw(c, clen, i, tmp, PT_MAGIC_65536, rl)
262 pt_emit_run(tmp, rl[0], out, cap, op_p, cur_is2, cur_map)
263 } else {
264 // T-operators: Tf=font-select; Tm/T*=line/block reposition -> word space
265 // (FOP writes one Tm per line + Td per glyph, and omits /space at line
266 // wraps, so "andlong-term" merges; a space at Tm/T* rejoins them).
267 var top: i64 = 0
268 if ch == 0x54 { if i + 1 < clen {
269 let nxt: i64 = c[i+1] as i64
270 var okb: i64 = 1; if i > 0 { if pt_is_delim(c[i-1] as i64) == 0 { okb = 0 } }
271 var oka: i64 = 1; if i + 2 < clen { if pt_is_delim(c[i+2] as i64) == 0 { oka = 0 } }
272 if okb == 1 { if oka == 1 {
273 if nxt == 0x66 { top = 1 }
274 if nxt == 0x6d { top = 2 }
275 if nxt == 0x2a { top = 2 }
276 } }
277 } }
278 if top == 1 {
279 var idx: i64 = 0 - 1
280 var j: i64 = 0
281 while j < nf {
282 if fnlen[j] == llen_p[0] {
283 var eq: i64 = 1; var m: i64 = 0
284 while m < llen_p[0] { if fname[j * PT_NAMESLOT + m] != lname[m] { eq = 0; m = llen_p[0] } else { m = m + 1 } }
285 if eq == 1 { idx = j; j = nf }
286 }
287 j = j + 1
288 }
289 if idx >= 0 { cur_is2 = fis2[idx]; if fmap[idx] != 0 { cur_map = fmap[idx] as *i64 } else { cur_map = g_map } }
290 else { cur_is2 = g_is2; cur_map = g_map }
291 i = i + 2
292 } else { if top == 2 {
293 if op_p[0] > 0 { let lc: i64 = out[op_p[0]-1] as i64; if lc != 0x20 { if lc != 0x0a { if lc != 0x09 { if op_p[0] < cap { out[op_p[0]] = 0x20 as u8; op_p[0] = op_p[0] + 1 } } } } }
294 i = i + 2
295 } else { i = i + 1 } }
296 } }
297 }
298 return 0
299}
300// name == NUL-terminated literal ?
301func pt_name_eq(name: *u8, nlen: i64, lit: *u8) -> i64 {
302 var i: i64 = 0
303 while i < nlen { if lit[i] == (0 as u8) { return 0 } if name[i] != lit[i] { return 0 } i = i + 1 }
304 if lit[nlen] != (0 as u8) { return 0 }
305 return 1
306}
307// (pt_hexv is defined ONCE, earlier in this file at :35. A semantically identical second
308// definition -- same three ranges, only the branch ORDER and parameter name differ -- used
309// to sit here. nx_cc accepted the redefinition silently and picked a winner; removed
310// 2026-07-31 with debt 1785447657.)
311// Adobe glyph name -> Unicode codepoint (0 = unknown). Ligatures return a
312// packed-ASCII value (bit 0x40000000, up to 3 low bytes) expanded by pt_emit_cp.
313func pt_glyph_uni(name: *u8, nlen: i64) -> i64 {
314 if nlen <= 0 { return 0 }
315 if nlen == 1 { return name[0] as i64 }
316 if nlen == 7 { if name[0] == 0x75 { if name[1] == 0x6e { if name[2] == 0x69 {
317 var v: i64 = 0; var ok: i64 = 1; var i: i64 = 3
318 while i < 7 { let h: i64 = pt_hexv(name[i] as i64); if h < 0 { ok = 0; i = 7 } else { v = v * 16 + h; i = i + 1 } }
319 if ok == 1 { return v }
320 } } } }
321 if pt_name_eq(name, nlen, "space" as *u8) { return 0x20 }
322 if pt_name_eq(name, nlen, "hyphen" as *u8) { return 0x2D }
323 if pt_name_eq(name, nlen, "period" as *u8) { return 0x2E }
324 if pt_name_eq(name, nlen, "comma" as *u8) { return 0x2C }
325 if pt_name_eq(name, nlen, "colon" as *u8) { return 0x3A }
326 if pt_name_eq(name, nlen, "semicolon" as *u8) { return 0x3B }
327 if pt_name_eq(name, nlen, "parenleft" as *u8) { return 0x28 }
328 if pt_name_eq(name, nlen, "parenright" as *u8) { return 0x29 }
329 if pt_name_eq(name, nlen, "bracketleft" as *u8) { return 0x5B }
330 if pt_name_eq(name, nlen, "bracketright" as *u8) { return 0x5D }
331 if pt_name_eq(name, nlen, "braceleft" as *u8) { return 0x7B }
332 if pt_name_eq(name, nlen, "braceright" as *u8) { return 0x7D }
333 if pt_name_eq(name, nlen, "slash" as *u8) { return 0x2F }
334 if pt_name_eq(name, nlen, "backslash" as *u8) { return 0x5C }
335 if pt_name_eq(name, nlen, "bullet" as *u8) { return 0x2022 }
336 if pt_name_eq(name, nlen, "quotesingle" as *u8) { return 0x27 }
337 if pt_name_eq(name, nlen, "quotedbl" as *u8) { return 0x22 }
338 if pt_name_eq(name, nlen, "quoteright" as *u8) { return 0x27 }
339 if pt_name_eq(name, nlen, "quoteleft" as *u8) { return 0x60 }
340 if pt_name_eq(name, nlen, "quotedblleft" as *u8) { return 0x201C }
341 if pt_name_eq(name, nlen, "quotedblright" as *u8) { return 0x201D }
342 if pt_name_eq(name, nlen, "endash" as *u8) { return 0x2013 }
343 if pt_name_eq(name, nlen, "emdash" as *u8) { return 0x2014 }
344 if pt_name_eq(name, nlen, "ampersand" as *u8) { return 0x26 }
345 if pt_name_eq(name, nlen, "percent" as *u8) { return 0x25 }
346 if pt_name_eq(name, nlen, "asterisk" as *u8) { return 0x2A }
347 if pt_name_eq(name, nlen, "plus" as *u8) { return 0x2B }
348 if pt_name_eq(name, nlen, "equal" as *u8) { return 0x3D }
349 if pt_name_eq(name, nlen, "less" as *u8) { return 0x3C }
350 if pt_name_eq(name, nlen, "greater" as *u8) { return 0x3E }
351 if pt_name_eq(name, nlen, "question" as *u8) { return 0x3F }
352 if pt_name_eq(name, nlen, "exclam" as *u8) { return 0x21 }
353 if pt_name_eq(name, nlen, "at" as *u8) { return 0x40 }
354 if pt_name_eq(name, nlen, "numbersign" as *u8) { return 0x23 }
355 if pt_name_eq(name, nlen, "dollar" as *u8) { return 0x24 }
356 if pt_name_eq(name, nlen, "underscore" as *u8) { return 0x5F }
357 if pt_name_eq(name, nlen, "bar" as *u8) { return 0x7C }
358 if pt_name_eq(name, nlen, "asciitilde" as *u8) { return 0x7E }
359 if pt_name_eq(name, nlen, "grave" as *u8) { return 0x60 }
360 if pt_name_eq(name, nlen, "degree" as *u8) { return 0xB0 }
361 if pt_name_eq(name, nlen, "zero" as *u8) { return 0x30 }
362 if pt_name_eq(name, nlen, "one" as *u8) { return 0x31 }
363 if pt_name_eq(name, nlen, "two" as *u8) { return 0x32 }
364 if pt_name_eq(name, nlen, "three" as *u8) { return 0x33 }
365 if pt_name_eq(name, nlen, "four" as *u8) { return 0x34 }
366 if pt_name_eq(name, nlen, "five" as *u8) { return 0x35 }
367 if pt_name_eq(name, nlen, "six" as *u8) { return 0x36 }
368 if pt_name_eq(name, nlen, "seven" as *u8) { return 0x37 }
369 if pt_name_eq(name, nlen, "eight" as *u8) { return 0x38 }
370 if pt_name_eq(name, nlen, "nine" as *u8) { return 0x39 }
371 if pt_name_eq(name, nlen, "ff" as *u8) { return 0x40000000 | 0x66 | (0x66 << 8) }
372 if pt_name_eq(name, nlen, "fi" as *u8) { return 0x40000000 | 0x66 | (0x69 << 8) }
373 if pt_name_eq(name, nlen, "fl" as *u8) { return 0x40000000 | 0x66 | (0x6C << 8) }
374 if pt_name_eq(name, nlen, "ffi" as *u8) { return 0x40000000 | 0x66 | (0x66 << 8) | (0x69 << 16) }
375 if pt_name_eq(name, nlen, "ffl" as *u8) { return 0x40000000 | 0x66 | (0x66 << 8) | (0x6C << 16) }
376 if pt_name_eq(name, nlen, "f_f" as *u8) { return 0x40000000 | 0x66 | (0x66 << 8) }
377 if pt_name_eq(name, nlen, "f_i" as *u8) { return 0x40000000 | 0x66 | (0x69 << 8) }
378 if pt_name_eq(name, nlen, "f_l" as *u8) { return 0x40000000 | 0x66 | (0x6C << 8) }
379 if pt_name_eq(name, nlen, "f_f_i" as *u8) { return 0x40000000 | 0x66 | (0x66 << 8) | (0x69 << 16) }
380 if pt_name_eq(name, nlen, "f_f_l" as *u8) { return 0x40000000 | 0x66 | (0x66 << 8) | (0x6C << 16) }
381 return 0
382}
383// parse an /Differences array [ code /name /name code /name ... ] into map[code]=unicode
384func pt_parse_differences(buf: *u8, end: i64, start: i64, map: *i64) -> i64 {
385 var i: i64 = start
386 var go: i64 = 1
387 while go == 1 { if i >= end { go = 0 } else { if (buf[i] as i64) == 0x5b { i = i + 1; go = 0 } else { i = i + 1 } } }
388 let nm: *u8 = sys_mmap(64)
389 let ipp: *i64 = sys_mmap(16) as *i64
390 var code: i64 = 0
391 var cnt: i64 = 0
392 go = 1
393 while go == 1 {
394 if i >= end { go = 0 }
395 else {
396 let ch: i64 = buf[i] as i64
397 if ch == 0x5d { go = 0 }
398 else { if ch == 0x2f {
399 var o: i64 = 0; var k: i64 = i + 1; var g2: i64 = 1
400 while g2 == 1 { if k >= end { g2 = 0 } else { if pt_is_delim(buf[k] as i64) == 1 { g2 = 0 } else { if o < 63 { nm[o] = buf[k] } o = o + 1; k = k + 1 } } }
401 if code >= 0 { if code < PT_MAP_N { let u: i64 = pt_glyph_uni(nm, o); if u != 0 { map[code] = u; cnt = cnt + 1 } } }
402 code = code + 1
403 i = k
404 } else { if ch >= 0x30 { if ch <= 0x39 {
405 ipp[0] = i; let v: i64 = pt_read_uint(buf, end, ipp)
406 if v >= 0 { code = v }
407 i = ipp[0]
408 } else { i = i + 1 } } else { i = i + 1 } } }
409 }
410 }
411 return cnt
412}
413// resolve a font object -> is2 (Identity-H) + per-font /ToUnicode map pointer (0 if none)
414func pt_resolve_font(buf: *u8, n: i64, objnum: i64, is2_p: *i64, map_p: *i64) -> i64 {
415 is2_p[0] = 0; map_p[0] = 0
416 let off: i64 = pt_find_obj(buf, n, objnum)
417 if off < 0 { return 0 }
418 var end: i64 = pt_find(buf, n, off, "endobj" as *u8, 6)
419 if end < 0 { end = n }
420 if pt_find(buf, end, off, "/Type0" as *u8, 6) >= 0 { if pt_find(buf, end, off, "/Identity-H" as *u8, 11) >= 0 { is2_p[0] = 1 } }
421 let tu: i64 = pt_find(buf, end, off, "/ToUnicode" as *u8, 10)
422 if tu >= 0 {
423 let ipp: *i64 = sys_mmap(16) as *i64
424 ipp[0] = pt_skipws(buf, end, tu + 10)
425 let tunum: i64 = pt_read_uint(buf, end, ipp)
426 if tunum >= 0 {
427 let coff: i64 = pt_find_obj(buf, n, tunum)
428 if coff >= 0 {
429 let cs: i64 = pt_find(buf, n, coff, "stream" as *u8, 6)
430 if cs >= 0 {
431 var cds: i64 = cs + 6
432 if cds < n { if (buf[cds] as i64) == 0x0d { cds = cds + 1 } }
433 if cds < n { if (buf[cds] as i64) == 0x0a { cds = cds + 1 } }
434 let ce: i64 = pt_find(buf, n, cds, "endstream" as *u8, 9)
435 if ce >= 0 {
436 let cln: i64 = ce - cds
437 var dec: *u8 = ((buf as i64) + cds) as *u8
438 var dlen: i64 = cln
439 let z: *NxZlibResult = nx_zlib_inflate(((buf as i64) + cds) as *u8, cln, PT_MAX_STREAM)
440 if (z as i64) != 0 { if z.error_code == NX_ZLIB_OK { dec = z.output_data; dlen = z.output_size } }
441 let map: *i64 = sys_mmap(PT_MAP_N * 8)
442 pt_parse_cmap_block(dec, dlen, map)
443 map_p[0] = map as i64
444 }
445 }
446 }
447 }
448 }
449 // simple fonts (no /ToUnicode): decode via /Encoding /Differences (glyph names)
450 if map_p[0] == 0 {
451 let en: i64 = pt_find(buf, end, off, "/Encoding" as *u8, 9)
452 if en >= 0 {
453 let ipp3: *i64 = sys_mmap(16) as *i64
454 ipp3[0] = pt_skipws(buf, end, en + 9)
455 let after: i64 = ipp3[0]
456 var denc_off: i64 = 0 - 1
457 var denc_end: i64 = 0 - 1
458 if after < end {
459 if (buf[after] as i64) == 0x3c {
460 denc_off = after
461 denc_end = pt_find(buf, n, after, ">>" as *u8, 2)
462 if denc_end < 0 { denc_end = end }
463 } else {
464 let encnum: i64 = pt_read_uint(buf, end, ipp3)
465 if encnum >= 0 {
466 let eoff: i64 = pt_find_obj(buf, n, encnum)
467 if eoff >= 0 { denc_off = eoff; denc_end = pt_find(buf, n, eoff, "endobj" as *u8, 6); if denc_end < 0 { denc_end = n } }
468 }
469 }
470 }
471 if denc_off >= 0 {
472 let dff: i64 = pt_find(buf, denc_end, denc_off, "/Differences" as *u8, 12)
473 if dff >= 0 {
474 let dmap: *i64 = sys_mmap(PT_MAP_N * 8)
475 pt_parse_differences(buf, denc_end, dff + 12, dmap)
476 map_p[0] = dmap as i64
477 }
478 }
479 }
480 }
481 return 1
482}
483// scan /Font resource dicts -> name table (fname/fnlen/fis2/fmap); returns font count
484func pt_build_fonts(buf: *u8, n: i64, fname: *u8, fnlen: *i64, fis2: *i64, fmap: *i64) -> i64 {
485 var nf: i64 = 0
486 let i2: *i64 = sys_mmap(16) as *i64
487 let mp: *i64 = sys_mmap(16) as *i64
488 var pos: i64 = 0; var go: i64 = 1
489 while go == 1 {
490 let f: i64 = pt_find(buf, n, pos, "/Font" as *u8, 5)
491 if f < 0 { go = 0 }
492 else {
493 var ok: i64 = 0
494 if f + 5 < n { let nc: i64 = buf[f+5] as i64; if pt_is_ws(nc) == 1 { ok = 1 } if nc == 0x3c { ok = 1 } }
495 if ok == 1 {
496 let dd: i64 = pt_find(buf, n, f + 5, "<<" as *u8, 2)
497 if dd >= 0 {
498 var ee: i64 = pt_find(buf, n, dd + 2, ">>" as *u8, 2)
499 if ee < 0 { ee = n }
500 var i: i64 = dd + 2
501 while i < ee {
502 if (buf[i] as i64) == 0x2f {
503 var o: i64 = 0; var k: i64 = i + 1; var g2: i64 = 1
504 while g2 == 1 { if k >= ee { g2 = 0 } else { if pt_is_delim(buf[k] as i64) == 1 { g2 = 0 } else { if (o < PT_NAMESLOT) { if nf < PT_MAXFONTS { fname[nf * PT_NAMESLOT + o] = buf[k] } } o = o + 1; k = k + 1 } } }
505 let ipp: *i64 = sys_mmap(16) as *i64
506 ipp[0] = pt_skipws(buf, ee, k)
507 let num: i64 = pt_read_uint(buf, ee, ipp)
508 i = ipp[0]
509 if num >= 0 { if o > 0 { if nf < PT_MAXFONTS {
510 fnlen[nf] = o
511 pt_resolve_font(buf, n, num, i2, mp)
512 fis2[nf] = i2[0]; fmap[nf] = mp[0]
513 nf = nf + 1
514 } } }
515 } else { i = i + 1 }
516 }
517 }
518 }
519 pos = f + 5
520 }
521 }
522 return nf
523}
524// PDF 1.5+ : rehydrate /ObjStm (compressed object streams) into plain "num 0 obj <body> endobj" text
525// appended after the original buffer, so the plain-text object/font/CMap resolution reaches objects
526// packed inside them. Streams (incl. /ToUnicode CMaps) are never in ObjStm, so they stay resolvable.
527func pt_expand_objstm(buf: *u8, n: i64, exp: *u8, cap: i64) -> i64 {
528 var ep: i64 = 0
529 while ep < n { if ep < cap { exp[ep] = buf[ep] } ep = ep + 1 }
530 if ep > cap { ep = cap }
531 let ipp: *i64 = sys_mmap(16) as *i64
532 var pos: i64 = 0; var go: i64 = 1
533 while go == 1 {
534 let os: i64 = pt_find(buf, n, pos, "/ObjStm" as *u8, 7)
535 if os < 0 { go = 0 }
536 else {
537 let st: i64 = pt_find(buf, n, os, "stream" as *u8, 6)
538 if st < 0 { go = 0 }
539 else {
540 var nn: i64 = 0
541 let pN: i64 = pt_find(buf, st, os, "/N" as *u8, 2)
542 if pN >= 0 { ipp[0] = pt_skipws(buf, st, pN + 2); let v: i64 = pt_read_uint(buf, st, ipp); if v >= 0 { nn = v } }
543 var first: i64 = 0
544 let pF: i64 = pt_find(buf, st, os, "/First" as *u8, 6)
545 if pF >= 0 { ipp[0] = pt_skipws(buf, st, pF + 6); let v: i64 = pt_read_uint(buf, st, ipp); if v >= 0 { first = v } }
546 var ds: i64 = st + 6
547 if ds < n { if (buf[ds] as i64) == 0x0d { ds = ds + 1 } }
548 if ds < n { if (buf[ds] as i64) == 0x0a { ds = ds + 1 } }
549 let es: i64 = pt_find(buf, n, ds, "endstream" as *u8, 9)
550 if es >= 0 { if nn > 0 { if nn < PT_MAGIC_8192 {
551 let clen: i64 = es - ds
552 var dec: *u8 = ((buf as i64) + ds) as *u8
553 var dlen: i64 = clen
554 let z: *NxZlibResult = nx_zlib_inflate(((buf as i64) + ds) as *u8, clen, PT_MAX_STREAM)
555 if (z as i64) != 0 { if z.error_code == NX_ZLIB_OK { dec = z.output_data; dlen = z.output_size } }
556 let onums: *i64 = sys_mmap(nn * 8 + 64)
557 let offs: *i64 = sys_mmap(nn * 8 + 64)
558 var hp: i64 = 0; var k: i64 = 0
559 while k < nn {
560 hp = pt_skipws(dec, dlen, hp); ipp[0] = hp; let onum: i64 = pt_read_uint(dec, dlen, ipp); hp = ipp[0]
561 hp = pt_skipws(dec, dlen, hp); ipp[0] = hp; let ooff: i64 = pt_read_uint(dec, dlen, ipp); hp = ipp[0]
562 onums[k] = onum; offs[k] = ooff
563 k = k + 1
564 }
565 k = 0
566 while k < nn {
567 let bstart: i64 = first + offs[k]
568 var bend: i64 = dlen
569 if k + 1 < nn { bend = first + offs[k+1] }
570 if bstart >= 0 { if bend <= dlen { if bstart <= bend { if onums[k] >= 0 {
571 let nb: *u8 = sys_mmap(32); let nl: i64 = pt_itoa(onums[k], nb)
572 var q: i64 = 0
573 while q < nl { if ep < cap { exp[ep] = nb[q]; ep = ep + 1 } q = q + 1 }
574 let lit: *u8 = " 0 obj\n" as *u8
575 q = 0; while q < 7 { if ep < cap { exp[ep] = lit[q]; ep = ep + 1 } q = q + 1 }
576 var b: i64 = bstart
577 while b < bend { if ep < cap { exp[ep] = dec[b]; ep = ep + 1 } b = b + 1 }
578 let lit2: *u8 = "\nendobj\n" as *u8
579 q = 0; while q < 8 { if ep < cap { exp[ep] = lit2[q]; ep = ep + 1 } q = q + 1 }
580 } } } }
581 k = k + 1
582 }
583 } } }
584 pos = st + 6
585 }
586 }
587 }
588 return ep
589}
590func nx_pdf_extract_text(buf: *u8, n: i64, out: *u8, cap: i64) -> i64 {
591 let op_p: *i64 = sys_mmap(16) as *i64
592 op_p[0] = 0
593 // rehydrate compressed object streams (PDF 1.5+) so font/CMap objects inside them become resolvable
594 let EXPCAP: i64 = PT_MAGIC_50331648
595 let xb: *u8 = sys_mmap(EXPCAP)
596 let xn: i64 = pt_expand_objstm(buf, n, xb, EXPCAP)
597 // per-font table
598 let fname: *u8 = sys_mmap(PT_MAXFONTS * PT_NAMESLOT)
599 let fnlen: *i64 = sys_mmap(PT_MAXFONTS * 8) as *i64
600 let fis2: *i64 = sys_mmap(PT_MAXFONTS * 8) as *i64
601 let fmap: *i64 = sys_mmap(PT_MAXFONTS * 8) as *i64
602 let nf: i64 = pt_build_fonts(xb, xn, fname, fnlen, fis2, fmap)
603 var g_is2: i64 = 0
604 let g_map: *i64 = sys_mmap(PT_MAP_N * 8)
605 if pt_find(xb, xn, 0, "/Type0" as *u8, 6) >= 0 { if pt_find(xb, xn, 0, "/Identity-H" as *u8, 11) >= 0 {
606 g_is2 = 1
607 if pt_find(xb, xn, 0, "/TrueType" as *u8, 9) >= 0 { g_is2 = 0 }
608 if pt_find(xb, xn, 0, "/Type1" as *u8, 6) >= 0 { g_is2 = 0 }
609 } }
610 if g_is2 == 1 { var j: i64 = 0; while j < nf { if fmap[j] != 0 { let m: *i64 = fmap[j] as *i64; var c: i64 = 0; while c < PT_MAP_N { if m[c] != 0 { if g_map[c] == 0 { g_map[c] = m[c] } } c = c + 1 } } j = j + 1 } }
611 let kw_stream: *u8 = "stream" as *u8
612 let kw_endstream: *u8 = "endstream" as *u8
613 let kw_tj: *u8 = "Tj" as *u8
614 let kw_tj2: *u8 = "TJ" as *u8
615 let kw_bt: *u8 = "BT" as *u8
616 var pos: i64 = 0; var go: i64 = 1
617 while go == 1 {
618 let s: i64 = pt_find(xb, xn, pos, kw_stream, 6)
619 if s < 0 { go = 0 }
620 else {
621 var ds: i64 = s + 6
622 if ds < xn { if (xb[ds] as i64) == 0x0d { ds = ds + 1 } }
623 if ds < xn { if (xb[ds] as i64) == 0x0a { ds = ds + 1 } }
624 let e: i64 = pt_find(xb, xn, ds, kw_endstream, 9)
625 if e < 0 { go = 0 }
626 else {
627 let slen: i64 = e - ds
628 var dec: *u8 = ((xb as i64) + ds) as *u8
629 var dlen: i64 = slen
630 let z: *NxZlibResult = nx_zlib_inflate(((xb as i64) + ds) as *u8, slen, PT_MAX_STREAM)
631 if (z as i64) != 0 { if z.error_code == NX_ZLIB_OK { dec = z.output_data; dlen = z.output_size } }
632 var iscontent: i64 = 0
633 if pt_find(dec, dlen, 0, kw_tj, 2) >= 0 { iscontent = 1 }
634 if pt_find(dec, dlen, 0, kw_tj2, 2) >= 0 { iscontent = 1 }
635 if pt_find(dec, dlen, 0, kw_bt, 2) >= 0 { iscontent = 1 }
636 if pt_find(dec, dlen, 0, "beginbfchar" as *u8, 11) >= 0 { iscontent = 0 }
637 if pt_find(dec, dlen, 0, "begincmap" as *u8, 9) >= 0 { iscontent = 0 }
638 if iscontent == 1 {
639 pt_extract_pf(dec, dlen, out, cap, op_p, fname, fnlen, fis2, fmap, nf, g_is2, g_map)
640 if op_p[0] < cap { out[op_p[0]] = 0x0a as u8; op_p[0] = op_p[0] + 1 }
641 }
642 pos = e + 9
643 }
644 }
645 }
646 return op_p[0]
647}
648func main(argc: i64, argv: *i64) -> i64 {
649 if argc < 2 { sys_write(1, "usage: nx_pdf_text <file.pdf> [out.txt]\n" as *u8, 39); return 1 }
650 let path: *u8 = argv[1] as *u8
651 let lb: *i64 = sys_mmap(16) as *i64
652 let buf: *u8 = sys_read_file(path, lb)
653 if (buf as i64) == 0 { sys_write(1, "REJECT: cannot read file\n" as *u8, 25); return 1 }
654 let n: i64 = lb[0]
655 let out: *u8 = sys_mmap(PT_MAX_OUT)
656 let oplen: i64 = nx_pdf_extract_text(buf, n, out, PT_MAX_OUT)
657 sys_write(1, out, oplen)
658 if argc >= 3 { let opath: *u8 = argv[2] as *u8; let fd: i64 = sys_openat_wr(opath, 0x1a4); if fd >= 0 { sys_write(fd, out, oplen); sys_close(fd) } }
659 return 0
660}