code wiki / (root) / nx_pdf_text.nx

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}