chiark / gitweb /
before rip out timing stuff and use just minimum snapshot delay
[ypp-sc-tools.web-live.git] / pctb / show-thing.tcl
1 #!/usr/bin/wish
2
3 # usage:
4 #  run show-thing without args
5 #  then on stdin write
6 #     one line which is a Tcl list for unk_{l,r} unk_contexts glyphsdone
7 #     the xpm in the format expected
8 #  then expect child to raise SIGSTOP or exit 0 or exit nonzero
9 #  if child raised SIGSTOP, check database was updated
10
11
12 proc manyset {list args} {
13     foreach val $list var $args {
14         upvar 1 $var my
15         set my $val
16     }
17 }
18
19
20 #---------- display core ----------
21
22 set mul 6
23 set inter 1
24
25 set gotsh 20
26 set csrh 20
27 set ctxh 20
28
29 proc debug {m} {
30     puts stderr "SHOW-THING $m"
31 }
32
33 proc init_widgets {} {
34     # idempotent
35     global csrh gotsh ctxh
36
37     if {[winfo exists .d]} return
38     
39     frame .d
40
41     image create bitmap image/main
42     label .d.mi -image image/main -borderwidth 0
43
44     frame .d.csr -bg black -height $csrh
45     frame .d.got -bg black -height $gotsh
46     frame .d.ctx -bg black
47
48     image create bitmap image/cursor -data \
49 {#define csr_width 11
50 #define csr_height 11
51 static unsigned char csr_bits[] = {
52    0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x21, 0x04, 0x22, 0x02, 0x25, 0x05,
53    0xaa, 0x02, 0x74, 0x01, 0xa8, 0x00, 0x70, 0x00, 0x20, 0x00};
54 }
55
56     frame .d.csr.csr
57     label .d.csr.csr.l -image image/cursor -compound left
58     entry .d.csr.csr.e -bd 0
59     pack .d.csr.csr.l -side left
60
61     frame .d.mi.csr_0 -bg white -width 1
62     frame .d.mi.csr_1 -bg white -width 1
63
64     pack .d.csr .d.mi .d.got .d.ctx -side top
65     pack .d
66
67     frame .help
68     pack .help
69 }
70
71 proc show_context {maxhv x ctxs} {
72     global mul
73     upvar 1 $maxhv maxh
74     set w .d.ctx.at$x
75     if {[llength $ctxs]==1} { set fg blue } { set fg yellow }
76     label $w -bg black -fg $fg -text [join $ctxs "/\n"] -justify left
77     place $w -x [expr {($x-1)*$mul}] -y 0
78     set wh [winfo reqheight $w]
79     if {$wh > $maxh} { set maxh $wh }
80 }
81
82 proc resize_widgets {} {
83     global mulcols mulrows csrh gotsh ctxh glyphsdone
84     global unk_l unk_contexts
85     
86     foreach w {.d.csr .d.got .d.ctx} {
87         $w configure -width $mulcols
88     }
89     #.d configure -height [expr {$csrh+$mulrows+$gotsh+$ctxh}]
90     foreach w {0 1} {
91         .d.mi.csr_$w configure -height $mulrows
92     }
93
94     eval destroy [winfo children .d.ctx]
95
96     set maxh 0
97     foreach {min max contexts got} $glyphsdone {
98         show_context maxh $min $contexts
99     }
100     show_context maxh $unk_l $unk_contexts
101     .d.ctx configure -height $maxh
102 }
103
104
105 #---------- xpm input processor ----------
106
107 proc read_xpm {f} {
108     global glyphsdone mul inter rhsmost_max unk_l unk_r mulcols mulrows
109     global cols rows wordmap
110     
111     set o {}
112     set y -3
113     while 1 {
114         if {[gets $f l] < 0} { error "huh? "}
115         if {![regexp {^"(.*)",$} $l dummy l]} {
116             append o "$l\n"
117             if {[regexp {^\}\;$} $l]} break
118             continue
119         }
120         if {$y==-3} {
121             manyset $l cols rows colours cpp
122             if {$colours!=2 || $cpp!=1} { error "$l ?" }
123
124             set chop_l [expr {$unk_l - 80}]
125             set chop_r [expr {$cols - $unk_l - 100}]
126             if {$chop_l<0} { set chop_l 0 }
127
128             set unk_l [expr {$unk_l - $chop_l}]
129             set unk_r [expr {$unk_r - $chop_l}]
130             set ngd {}
131             foreach {min max contexts got} $glyphsdone {
132                 lappend ngd \
133                     [expr {$min-$chop_l}] \
134                     [expr {$max-$chop_l}] \
135                     $contexts $got
136             }
137             set glyphsdone $ngd
138
139             set realcols $cols
140             set cols [expr {$cols - $chop_l - $chop_r}]
141             debug "NOW cols=$cols chop_l,r=$chop_l,$chop_r rows=$rows\
142                 $unk_l $unk_r $ngd"
143             
144             set mulcols [expr {$cols*$mul+$inter}]
145             set mulrows [expr {$rows*$mul+$inter}]
146             append o "\"$mulcols $mulrows 9 1\",\n"
147             for {set x 0} {$x<$cols} {incr x} { set wordmap($x) 0 }
148         } elseif {$y==-2} { # first pixel
149             append o \
150 "\"+ c #111\",
151 \"a c #800\",
152 \"A c #fcc\",
153 \"b c #00c\",
154 \"B c #fff\",
155 \"u c #000\",
156 \"U c #ff0\",
157 \"q c #000\",
158 \"Q c #ff0\",\n"
159         } elseif {$y==-1} { # 2nd pixel but we've already printed ours
160         } else {
161             set ybit [expr {1<<$y}]
162             set x 0
163             set ol "\"+"
164             set olh $ol
165             if {$chop_r>=0} {
166                 set l [string range $l $chop_l end-$chop_r]
167             } else {
168                 set l [string range $l $chop_l end]
169                 append l [string repeat " " [expr -$chop_r]]
170             }
171             foreach c [split $l ""] {
172                 set how "u"
173                 if {$x >= $unk_l && $x <= $unk_r} {
174                     set how q
175                 } else {
176                     set ab 0
177                     foreach {min max contexts got} $glyphsdone {
178                         set rhsmost_max $max
179                         if {$x >= $min && $x <= $max} {
180                             set how [lindex {a b} $ab]
181                             break
182                         }
183                         set ab [expr {!$ab}]
184                     }
185                 }
186                 switch -exact $c {
187                     " " { set p $how }
188                     "o" {
189                         set p [string toupper $how]
190                         incr wordmap($x) $ybit
191                     }
192                     default { error "$c ?" }
193                 }
194                 append ol "[string repeat $p [expr {$mul-$inter}]][
195                          string repeat + $inter]"
196                 append olh [string repeat + $mul]
197                 incr x
198             }
199             set ole "\",\n"
200             append ol $ole
201             append olh $ole
202             set olhn [string repeat $olh $inter]
203             if {!$y} { append o $olhn }
204             append o [string repeat $ol [expr {$mul-1}]]
205             append o $olhn
206         }
207         incr y
208     }
209     set data [exec xpmtoppm << $o]
210     image create photo image/main -data $data
211 }
212
213
214 #---------- per-invocation display ----------
215
216 proc draw_glyphsdone {} {
217     global glyphsdone mul inter
218     eval destroy [winfo children .d.got]
219     foreach {min max contexts got} $glyphsdone {
220         frame .d.got.m$min -bd 0 -background \#888
221         label .d.got.m$min.l -text "$got" -fg white -bg black -bd 0
222         pack .d.got.m$min.l -padx 1 -pady 1
223         place .d.got.m$min -x [expr {$min*$mul+$inter}] -y 0
224     }
225 }
226
227 proc startup_cursor {} {
228     global cur_already cur_mode cur_0 cur_1 last_ht
229     global glyphsdone unk_l unk_r
230     
231     set cur_already [expr {[llength $glyphsdone]/4-1}]
232     set cur_mode 1 ;# one of:   0 1 already text
233
234     set cur_0 $unk_l
235     set cur_1 [expr {$unk_r+1}]
236     set last_ht {}
237
238     recursor
239 }
240
241
242 #---------- runtime display and keystroke handling ----------
243
244 proc helptext {t} {
245     global last_ht
246     if {![string compare $t $last_ht]} return
247     eval destroy [grid slaves .help]
248     set y 0; foreach l $t {
249         set x 0; foreach c $l {
250             set w .help.at${x}x${y}
251             label $w -text $c
252             grid $w -row $y -column $x -padx 5
253             incr x
254         }
255         incr y
256     }
257     set last_ht $t
258 }
259
260 proc recursor/0 {} { recursor//01 0 }
261 proc recursor/1 {} { recursor//01 1 }
262 proc recursor//01 {z1} {
263     global mul rhsmost_max cols glyphsdone
264     upvar #0 cur_$z1 cur
265     .d.csr.csr.l configure -text {adjust}
266     place .d.csr.csr -x [expr {$cur*$mul - 7}]
267     bind_key space { othercursor }
268     bind_leftright cur_$z1 0 [expr {$cols-1}]
269     if {[llength $glyphsdone]} {
270         bind_key Tab { set cur_mode already; recursor }
271     } else {
272         bind_key Tab {}
273     }
274     bind_key Return {
275         if {$cur_0 != $cur_1} {
276             .d.csr.csr.e delete 0 end
277             set cur_mode text
278             recursor
279         }
280     }
281     helptext {
282         {{<- ->}   {move cursor, adjusting area to define}}
283         {Space     {switch to moving other cursor}}
284         {Return    {confirm location, enter letter(s)}}
285         {Tab       {switch to correcting earlier ocr}}
286     }
287 }
288 proc othercursor {} {
289     global cur_mode
290     set cur_mode [expr {!$cur_mode}]
291     recursor
292 }
293
294 proc recursor/text {} {
295     helptext {
296         {Return   {confirm entry of new glyph}}
297         {Escape   {abandon entry}}
298     }
299     unbind_all_keys
300     .d.csr.csr.l configure -text {define:}
301     pack .d.csr.csr.e -side left
302     focus .d.csr.csr.e
303     bind_key Return {
304         set strq [.d.csr.csr.e get]
305         if {[regexp {^(?:[!-[]|[]-~]|\\\\|\\x[0-9a-f]{2})+} $strq]} {
306             RETURN_RESULT DEFINE "$cur_0 $cur_1 $strq"
307         }
308     }
309     bind_key Escape {
310         bind_key Escape {}
311         pack forget .d.csr.csr.e
312         set cur_mode 1
313         recursor
314     }
315 }
316
317 proc recursor/already {} {
318     global mul
319     global glyphsdone
320     global cur_already mul
321     global glyphsdone cur_already mul
322     .d.csr.csr.l configure -text {correct}
323     set rmax [lindex $glyphsdone [expr {$cur_already*4}]]
324     place .d.csr.csr -x [expr {$rmax*$mul-3}]
325     bind_key Return {}
326     bind_key space {}
327     bind_leftright cur_already 0 [expr {[llength $glyphsdone]/4-1}]
328     bind_key Tab { bind_key Delete {}; set cur_mode 1; recursor }
329     bind_key Delete {
330         RETURN_RESULT DELETE [lrange $glyphsdone \
331                                   [expr $cur_already*4] \
332                                   [expr $cur_already*4+2]]
333     }
334     helptext {
335         {{<- ->}   {move cursor, selecting glyph to correct}}
336         {Del       {clear this glyph from the recognition database}}
337         {Tab       {switch to selecting area to define as new glyph}}
338     }
339 }
340
341 proc bind_key {k proc} {
342     global keybindings
343     bind . <Key-$k> $proc
344     set keybindings($k) [expr {!![string length $proc]}]
345 }
346 proc unbind_all_keys {} {
347     global keybindings
348     foreach k [array names keybindings] { bind_key $k {} }
349 }
350
351 proc bind_leftright {var min max} {
352     bind_key Left  [list leftright $var $min $max -1]
353     bind_key Right [list leftright $var $min $max +1]
354 }
355 proc leftright {var min max inc} {
356     upvar #0 $var v
357     set vnew $v
358     incr vnew $inc
359     if {$vnew < $min || $vnew > $max} return
360     set v $vnew
361     recursor
362 }
363
364 proc recursor {} {
365     global csrh cur_mode cur_0 cur_1 mul
366     foreach z1 {0 1} {
367         place .d.mi.csr_$z1 -y 0 -x [expr {[set cur_$z1] * $mul}]
368     }
369     recursor/$cur_mode
370 }
371
372
373 #---------- database read and write ----------
374
375 # database format:
376 # series of glyphs:
377 #   <context> <ncharacters> <hex>...
378 #   width
379 #   <hex-bitmap>
380
381 # $database($context 0x<bits> 0x<bits>...) = $hex
382
383 set database_header {# ypp-sc-tools pctb font v1}
384
385 proc db_getsl {f} {
386     if {[gets $f l] < 0} { error "unexpected db eof" }
387     return $l
388 }
389     
390 proc read_database {} {
391     global database database_header rows database_fn
392     catch { unset database }
393     set database_fn ./charset-$rows.txt
394     if {![file exists $database_fn]} return
395     set f [open $database_fn r]
396     if {[string compare [db_getsl $f] $database_header]} { error "$l ?" }
397     if {([db_getsl $f])+0 != $rows} { error "wrong h ?" }
398     while 1 {
399         set context [db_getsl $f]
400         if {![string length $context]} continue
401         if {[regexp {^\#} $context]} continue
402         if {![string compare . $context]} break
403
404         set bm $context
405         set strq [db_getsl $f]
406         while 1 {
407             set l [db_getsl $f]
408             if {![string length $l]} break
409             lappend bm [format %x 0x$l]
410         }
411         set database($bm) $strq
412     }
413     close $f
414 }
415
416 proc write_database {} {
417     global database rows database_fn database_header
418     set ol {}
419     foreach bm [array names database] {
420         set strq $database($bm)
421         set o "[lindex $bm 0]\n$strq\n"
422         foreach x [lrange $bm 1 end] { append o "$x\n" }
423         
424         lappend ol $o
425     }
426     set f [open $database_fn.new w]
427     puts $f "$database_header\n$rows\n"
428     foreach o [lsort $ol] {
429         puts $f $o
430     }
431     puts $f "."
432     close $f
433     file rename -force $database_fn.new $database_fn
434 }
435
436 proc dbkey {ctx l r} {
437     global wordmap
438     set bm $ctx
439     for {set x $l} {$x <= $r} {incr x} {
440         lappend bm [format %x $wordmap($x)]
441     }
442     return $bm
443 }
444
445 proc update_database/DEFINE {c0 c1 strq} {
446     global glyphsdone unk_l unk_contexts wordmap database
447     if {$c0 > $c1} { manyset [list $c0 $c1] c1 c0 }
448     if {$c0 == $unk_l} {
449         set ncontexts $unk_contexts
450     } else {
451         foreach {l r contexts got} $glyphsdone {
452             if {$l==$c0} { set ncontexts $contexts; break }
453         }
454         if {![info exists ncontexts]} {
455             puts stderr "must start at letter LHS!"
456             return
457         }
458     }
459     incr c1 -1
460     foreach c $ncontexts {
461         set bm [dbkey $c $c0 $c1]
462         set database($bm) $strq
463     }
464     write_database
465 }
466
467 proc update_database/DELETE {l r ctxs} {
468     global database
469     foreach ctx $ctxs {
470         set bm [dbkey $ctx $l $r]
471         catch { unset database($bm) }
472     }
473     write_database
474 }
475     
476
477 proc RETURN_RESULT {how what} {
478     global mainkind
479     place forget .d.csr.csr
480     pack forget .d.csr.csr.e
481     helptext {{{ Processing }}}
482     unbind_all_keys
483     update idletasks
484     debug "$how $what"
485     eval update_database/$how $what
486     done/$mainkind
487 }
488
489 #---------- main progrm ----------
490
491 proc main/test {} {
492     global glyphsdone unk_l unk_r unk_contexts
493
494     set glyphsdone {
495         7 11 1 M
496         13 17 0 a
497         19 23 0 n
498     }
499     set unk_l 25
500     set unk_r 29
501     set unk_contexts Test
502
503     set f [open text.xpm]
504     read_xpm $f
505     close $f
506
507     read_database
508     resize_widgets
509     draw_glyphsdone
510     startup_cursor
511 }
512 proc done/test {} {
513 }
514
515 proc required {} {
516     global glyphsdone unk_l unk_r unk_contexts
517     
518     if {[gets stdin l]<0} {
519         if {[eof stdin]} { fconfigure stdin -blocking yes; exit 0 }
520         return
521     }
522     init_widgets
523     manyset [lrange $l 0 3] unk_l unk_r unk_contexts
524     set glyphsdone [lrange $l 3 end]
525     debug "GOT $l"
526
527     fileevent stdin readable {}
528
529     read_xpm stdin
530     resize_widgets
531     read_database
532     draw_glyphsdone
533     startup_cursor
534 }
535
536 proc main/automatic {} {
537     fconfigure stdin -blocking no
538     fileevent stdin readable required
539 }
540 proc done/automatic {} {
541     exec sh -c {printf \\0 >&4}
542     fileevent stdin readable required
543 }
544
545 switch -exact -- $argv {
546     {}               { set mainkind test }
547     {--automatic 1}  { set mainkind automatic }
548     {--automatic*}   { error "incompatible versions - install problem" }
549     default          { error "huh $argv ?" }
550 }
551
552 main/$mainkind