nx_ocr_assemble.nx source
↩ module page · 85 lines · 3410 B
1// nx_ocr_assemble.nx -- R5 of the sovereign scanning stack: recognized glyphs -> text. Groups glyphs into lines by
2// vertical overlap, orders each line left-to-right, inserts a space where the horizontal gap is wide, and breaks
3// lines top-to-bottom -> a plain text string. That text is what feeds nx_money_ledger extraction -> money-OS,
4// closing the loop: scanned document -> recognized text -> the money system. Integer-exact, zero deps. Each glyph
5// is [minx, miny, maxx, maxy, char]. license_tier: ORIGINAL
6import "nx_syscalls.nx"
7
8const GLYPH_MINX: i64 = 0
9const GLYPH_MINY: i64 = 1
10const GLYPH_MAXX: i64 = 2
11const GLYPH_MAXY: i64 = 3
12const GLYPH_CHAR: i64 = 4
13const GLYPH_FIELDS: i64 = 5
14
15// assemble glyphs into text in out[]; a horizontal gap > space_gap inserts a space. Returns text length.
16func asm_assemble(glyphs: *i64, ng: i64, space_gap: i64, out: *u8) -> i64 {
17 if ng <= 0 { return 0 }
18 let idx: *i64 = sys_mmap((ng + 1) * 8) as *i64
19 var i: i64 = 0
20 while i < ng { idx[i] = i; i = i + 1 }
21
22 // 1. insertion sort idx by miny (top -> bottom), stable
23 var a: i64 = 1
24 while a < ng {
25 let key: i64 = idx[a]
26 let kv: i64 = glyphs[key * GLYPH_FIELDS + GLYPH_MINY]
27 var j: i64 = a - 1
28 var run: i64 = 1
29 while run == 1 {
30 if j < 0 { run = 0 }
31 else { if glyphs[idx[j] * GLYPH_FIELDS + GLYPH_MINY] > kv { idx[j+1] = idx[j]; j = j - 1 } else { run = 0 } }
32 }
33 idx[j+1] = key
34 a = a + 1
35 }
36
37 var o: i64 = 0
38 var g: i64 = 0
39 while g < ng {
40 // 2. gather this line: consecutive (in miny order) glyphs whose miny <= the line's running bottom
41 var line_maxy: i64 = glyphs[idx[g] * GLYPH_FIELDS + GLYPH_MAXY]
42 var g2: i64 = g + 1
43 var run2: i64 = 1
44 while run2 == 1 {
45 if g2 >= ng { run2 = 0 }
46 else {
47 if glyphs[idx[g2] * GLYPH_FIELDS + GLYPH_MINY] <= line_maxy {
48 if glyphs[idx[g2] * GLYPH_FIELDS + GLYPH_MAXY] > line_maxy { line_maxy = glyphs[idx[g2] * GLYPH_FIELDS + GLYPH_MAXY] }
49 g2 = g2 + 1
50 } else { run2 = 0 }
51 }
52 }
53 // 3. insertion sort idx[g..g2) by minx (left -> right)
54 var b: i64 = g + 1
55 while b < g2 {
56 let key2: i64 = idx[b]
57 let kv2: i64 = glyphs[key2 * GLYPH_FIELDS + GLYPH_MINX]
58 var j2: i64 = b - 1
59 var run3: i64 = 1
60 while run3 == 1 {
61 if j2 < g { run3 = 0 }
62 else { if glyphs[idx[j2] * GLYPH_FIELDS + GLYPH_MINX] > kv2 { idx[j2+1] = idx[j2]; j2 = j2 - 1 } else { run3 = 0 } }
63 }
64 idx[j2+1] = key2
65 b = b + 1
66 }
67 // 4. emit the line, inserting spaces on wide gaps
68 var k: i64 = g
69 var prev_maxx: i64 = 0 - 1
70 while k < g2 {
71 let gi: i64 = idx[k]
72 if prev_maxx >= 0 {
73 let gap: i64 = glyphs[gi * GLYPH_FIELDS + GLYPH_MINX] - prev_maxx
74 if gap > space_gap { out[o] = 32 as u8; o = o + 1 }
75 }
76 out[o] = glyphs[gi * GLYPH_FIELDS + GLYPH_CHAR] as u8
77 o = o + 1
78 prev_maxx = glyphs[gi * GLYPH_FIELDS + GLYPH_MAXX]
79 k = k + 1
80 }
81 if g2 < ng { out[o] = 10 as u8; o = o + 1 } // newline between lines
82 g = g2
83 }
84 return o
85}