chiark / gitweb /
made its first tsv!
[ypp-sc-tools.db-test.git] / pctb / ocr.c
1 /*
2   */
3
4 #include "ocr.h"
5
6 typedef struct {
7   Pixcol col;
8   struct DatabaseNode *then;
9 } DatabaseLink;
10
11 #define MAXGLYPHCHRS 7
12
13 typedef struct DatabaseNode {
14   char s[MAXGLYPHCHRS+1]; /* null-terminated; "" means no match here */
15   int nlinks, alinks;
16   unsigned endsword:1;
17   DatabaseLink *links;
18 } DatabaseNode;
19
20 static const char *context_names[]= {
21   "Lower",
22   "Upper",
23   "Digit"
24 };
25
26 #define NCONTEXTS (sizeof(context_names)/sizeof(context_names[0]))
27
28 #define SPACE_SPACES 4
29
30 struct OcrReader {
31   int h;
32   DatabaseNode contexts[NCONTEXTS];
33   OcrResultGlyph *results;
34   int aresults, nresults;
35 };
36
37 static FILE *resolver;
38 static pid_t resolver_pid;
39 static int resolver_done;
40
41 DEBUG_DEFINE_DEBUGF(ocr)
42
43 static void fgetsline(FILE *f, char *lbuf, size_t lbufsz) {
44   char *s= fgets(lbuf,lbufsz,f);
45   eassert(s);
46   int l= strlen(lbuf);
47   eassert(l>0);  eassert(lbuf[--l]='\n');
48   lbuf[l]= 0;
49 }
50 #define FGETSLINE(f,buf) (fgetsline(f,buf,sizeof(buf)))
51
52 static void cleardb_node(DatabaseNode *n) {
53   int i;
54   n->s[0]= 0;
55   for (i=0; i<n->nlinks; i++)
56     cleardb_node(n->links[i].then);
57 }
58
59 static void readdb(OcrReader *rd) {
60   int nchrs;
61   DatabaseNode *current, *additional;
62   char chrs[MAXGLYPHCHRS+1];
63   Pixcol cv;
64   int r,j,ctxi;
65   int h, endsword;
66   char lbuf[100];
67   FILE *db;
68
69   for (ctxi=0; ctxi<NCONTEXTS; ctxi++)
70     cleardb_node(&rd->contexts[ctxi]);
71
72   char *dbfname=0;
73   asprintf(&dbfname,"%s/charset-%d.txt",get_vardir(),rd->h);
74   eassert(dbfname);
75   
76   db= fopen(dbfname,"r");
77   free(dbfname);
78   if (!db) {
79     eassert(errno==ENOENT);
80     return;
81   }
82
83   FGETSLINE(db,lbuf);
84   eassert(!strcmp(lbuf,"# ypp-sc-tools pctb font v1"));
85
86   r= fscanf(db, "%d", &h);
87   eassert(r==1);
88   eassert(h==rd->h);
89
90   for (;;) {
91     FGETSLINE(db,lbuf);
92     if (!lbuf || lbuf[0]=='#') continue;
93     if (!strcmp(lbuf,".")) break;
94
95     for (ctxi=0; ctxi<NCONTEXTS; ctxi++)
96       if (!strcmp(lbuf,context_names[ctxi]))
97         goto found_ctx;
98     /* not found, just skip */
99     for (;;) { FGETSLINE(db,lbuf); if (!lbuf[0]) break; }
100     continue;
101
102   found_ctx:
103     for (nchrs=0;;) {
104       int c= fgetc(db);  eassert(c!=EOF);
105       if (c=='\n') { eassert(nchrs); break; }
106       eassert(nchrs<MAXGLYPHCHRS);
107       if (c=='\\') {
108         unsigned cr;
109         c= fgetc(db);  eassert(c=='x');
110         r= fscanf(db, "%2x", &cr);  eassert(r==1);
111         assert(cr>0 && cr<=255);
112         c= cr;
113       }
114       chrs[nchrs++]= c;
115     }
116     endsword= 0;
117     if (nchrs>1 && chrs[nchrs-1]==' ') {
118       endsword= 1;
119       nchrs--;
120     }
121     chrs[nchrs]= 0;
122
123     current= &rd->contexts[ctxi];
124     for (;;) {
125       FGETSLINE(db,lbuf);
126       if (!lbuf[0]) { eassert(current != &rd->contexts[ctxi]); break; }
127       char *ep;
128       cv= strtoul(lbuf,&ep,16);  eassert(!*ep);
129       eassert(!(cv & ~((1UL << rd->h)-1)));
130       
131       for (j=0; j<current->nlinks; j++)
132         if (current->links[j].col == cv) {
133           current= current->links[j].then;
134           goto found_link;
135         }
136
137       additional= malloc(sizeof(*additional)); eassert(additional);
138       additional->s[0]= 0;
139       additional->nlinks= additional->alinks= 0;
140       additional->links= 0;
141       if (current->nlinks==current->alinks) {
142         current->alinks++;
143         current->alinks<<=1;
144         current->links= realloc(current->links,
145                                 sizeof(*current->links) * current->alinks);
146         eassert(current->links);
147       }
148       current->links[current->nlinks].col= cv;
149       current->links[current->nlinks].then= additional;
150       current->nlinks++;
151       current= additional;
152
153     found_link:;
154     }
155
156     eassert(!current->s[0]);
157     strcpy(current->s, chrs);
158     current->endsword= endsword;
159   }
160   eassert(!ferror(db));
161   eassert(!fclose(db));
162 }
163
164 static void cu_pr_ctxmap(unsigned ctxmap) {
165   fprintf(resolver,"{");
166   const char *spc="";
167   int ctxi;
168   for (ctxi=0; ctxi<NCONTEXTS; ctxi++) {
169     if (!(ctxmap & (1u << ctxi))) continue;
170     fprintf(resolver,"%s%s",spc,context_names[ctxi]);
171     spc=" ";
172   }
173   fprintf(resolver,"}");
174 }
175
176 static void callout_unknown(OcrReader *rd, int w, Pixcol cols[],
177                             int unk_l, int unk_r, unsigned unk_ctxmap) {
178   int jobpipe[2],donepipe[2], c, r,i, x,y;
179   const OcrResultGlyph *s;
180   const char *p;
181   char cb;
182   Pixcol pv;
183   
184   if (!resolver) {
185     r= pipe(jobpipe);  eassert(!r);
186     r= pipe(donepipe);  eassert(!r);
187     resolver_pid= fork();
188     eassert(resolver_pid!=-1);
189     if (!resolver_pid) {
190       r= dup2(jobpipe[0],0); eassert(r==0);
191       r= close(jobpipe[1]); eassert(!r);
192       r= close(donepipe[0]); eassert(!r);
193       /* we know donepipe[1] is >= 4 and we have dealt with all the others
194        * so we aren't in any danger of overwriting some other fd 4: */
195       r= dup2(donepipe[1],4); eassert(r==4);
196       execlp("./show-thing.tcl", "./show-thing.tcl",
197              DEBUGP(callout) ? "--debug" : "--noop-arg",
198              "--automatic-1",
199              (char*)0);
200       eassert(!"execlp failed");
201     }
202     r= close(jobpipe[0]); eassert(!r);
203     r= close(donepipe[1]); eassert(!r);
204     resolver= fdopen(jobpipe[1],"w"); eassert(resolver);
205     resolver_done= donepipe[0];
206   }
207   fprintf(resolver,"%d %d ",unk_l,unk_r);
208   cu_pr_ctxmap(unk_ctxmap);
209   for (i=0, s=rd->results; i<rd->nresults; i++, s++) {
210     if (!strcmp(s->s," ")) continue;
211     fprintf(resolver," %d %d ",s->l,s->r);
212     cu_pr_ctxmap(s->ctxmap);
213     fprintf(resolver," ");
214     for (p=s->s; (c= *p); p++) {
215       if (c=='\\') fprintf(resolver,"\\%c",c);
216       else if (c>=33 && c<=126) fputc(c,resolver);
217       else fprintf(resolver,"\\x%02x",(unsigned char)c);
218     }
219   }
220   fputc('\n',resolver);
221
222   fprintf(resolver,
223           "/* XPM */\n"
224           "static char *t[] = {\n"
225           "/* columns rows colors chars-per-pixel */\n"
226           "\"%d %d 2 1\",\n"
227           "\"  c black\",\n"
228           "\"o c white\",\n",
229           w,rd->h);
230   for (y=0, pv=1; y<rd->h; y++, pv<<=1) {
231     fputc('"',resolver);
232     for (x=0; x<w; x++)
233       fputc(cols[x] & pv ? 'o' : ' ', resolver);
234     fputs("\",\n",resolver);
235   }
236   fputs("};\n",resolver);
237   eassert(!ferror(resolver));
238   eassert(!fflush(resolver));
239
240   eassert(resolver);
241
242   for (;;) {
243     r= read(resolver_done,&cb,1);
244     if (r==-1) { eassert(errno==EINTR); continue; }
245     break;
246   }
247
248   if (r==0) {
249     pid_t pid;
250     for (;;) {
251       pid= waitpid(resolver_pid, &r, 0);
252       if (pid==-1) { eassert(errno==EINTR); continue; }
253       break;
254     }
255     eassert(pid==resolver_pid);
256     if (WIFEXITED(r)) {
257       eassert(!WEXITSTATUS(r));
258       fclose(resolver);
259       close(resolver_done);
260       resolver= 0;
261     } else if (WIFSIGNALED(r)) {
262       eassert(!"resolver child died due to signal");
263     } else {
264       eassert(!"weird wait status");
265     }
266   } else {
267     eassert(r==1);
268     eassert(cb==0);
269   }
270
271   readdb(rd);
272 }
273
274 static void add_result(OcrReader *rd, const char *s, int l, int r,
275                        unsigned ctxmap) {
276   if (rd->nresults >= rd->aresults) {
277     rd->aresults++; rd->aresults<<=1;
278     rd->results= realloc(rd->results,sizeof(*rd->results)*rd->aresults);
279     eassert(rd->results);
280   }
281   rd->results[rd->nresults].s= s;
282   rd->results[rd->nresults].l= l;
283   rd->results[rd->nresults].r= r;
284   rd->results[rd->nresults].ctxmap= ctxmap;
285   rd->nresults++;
286 }
287
288 struct OcrCellTypeInfo {
289   unsigned initial, nextword, midword;
290 };
291 const struct OcrCellTypeInfo ocr_celltype_number= {
292   4,4,4
293 };
294 const struct OcrCellTypeInfo ocr_celltype_text= {
295   .initial=2 /* Uppercase */,
296   .nextword=3 /* Either */,
297   .midword=1 /* Lower only */
298 };
299
300 OcrResultGlyph *ocr(OcrReader *rd, OcrCellType ct, int w, Pixcol cols[]) {
301   int nspaces;
302   unsigned ctxmap;
303   int ctxi, i, x;
304
305  restart:
306
307   nspaces=- w;
308   ctxmap= ct->initial;
309   rd->nresults=0;
310   debugf("OCR h=%d w=%d",rd->h,w);
311   for (x=0; x<w; x++) debugf(" %"PSPIXCOL(PRIx),cols[x]);
312   debugf("\n");
313   debug_flush();
314
315   x=0;
316   for (;;) {
317     debug_flush();
318     /* skip spaces */
319     if (x>=w)
320       break;
321
322     if (!cols[x]) {
323       nspaces++;
324       x++;
325       if (nspaces==SPACE_SPACES) {
326         debugf("OCR  x=%x nspaces=%d space\n",x,nspaces);
327         ctxmap= ct->nextword;
328       }
329       continue;
330     }
331
332     /* something here, so we need to add the spaces */
333     if (nspaces>=SPACE_SPACES)
334       add_result(rd," ",x-nspaces,x+1,0);
335     nspaces=0;
336
337     /* find character */
338     int lx=x;
339
340     DatabaseNode *uniquematch= 0;
341     int uniquematch_rx=-1;
342     
343     debugf("OCR  lx=%d ctxmap=%x  ",lx,ctxmap);
344
345     for (ctxi=0; ctxi<NCONTEXTS; ctxi++) {
346       DatabaseNode *current= &rd->contexts[ctxi];;
347       DatabaseNode *bestmatch= 0;
348       int bestmatch_rx=-1;
349
350       x= lx;
351       if (!(ctxmap & (1u << ctxi))) continue;
352       debugf(" || %s",context_names[ctxi]);
353
354       for (;;) {
355         debug_flush();
356         debugf(" | x=%d",x);
357         if (x>w) break;
358         Pixcol cv= cols[x];
359         debugf(" cv=%"PSPIXCOL(PRIx),cv);
360         for (i=0; i<current->nlinks; i++)
361           if (current->links[i].col == cv)
362             goto found;
363         /* not found */
364         debugf(" ?");
365         break;
366
367       found:
368         current= current->links[i].then;
369         if (current->s[0]) {
370           debugf(" \"%s\"%s",current->s,current->endsword?"_":"");
371           bestmatch= current;
372           bestmatch_rx= x;
373         } else {
374           debugf(" ...");
375         }
376
377         x++;
378       }
379       
380       if (bestmatch) {
381         if (uniquematch && strcmp(bestmatch->s, uniquematch->s)) {
382           debugf( " ambiguous");
383           uniquematch= 0;
384           break;
385         }
386         uniquematch= bestmatch;
387         uniquematch_rx= bestmatch_rx;
388       }
389     }
390
391     if (uniquematch) {
392       debugf(" || YES");
393       add_result(rd, uniquematch->s, lx, uniquematch_rx, ctxmap);
394       x= uniquematch_rx+1;
395       if (uniquematch->s[0]) ctxmap= ct->midword;
396       else debugf(" (empty)");
397       if (uniquematch->endsword) {
398         nspaces= SPACE_SPACES;
399         debugf("_");
400         ctxmap= ct->nextword;
401       }
402       debugf("\n");
403     } else {
404       int rx;
405       debugf(" || UNKNOWN");
406       for (rx=lx; rx<w && cols[rx]; rx++);
407       debugf(" x=%d ctxmap=%x %d..%d\n",x, ctxmap, lx,rx);
408       debug_flush();
409       callout_unknown(rd, w,cols, lx,rx-1, ctxmap);
410       goto restart;
411     }
412   }
413   add_result(rd, 0,-1,-1,0);
414   debugf("OCR  finished %d glyphs\n",rd->nresults);
415   debug_flush();
416   return rd->results;
417 }
418
419 OcrReader *ocr_init(int h) {
420   OcrReader *rd;
421
422   rd= malloc(sizeof(*rd));  eassert(rd);
423   memset(rd,0,sizeof(*rd));
424   rd->h= h;
425   readdb(rd);
426   return rd;
427 }