X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~yarrgweb/git?a=blobdiff_plain;f=yarrg%2Fdictionary-manager;fp=yarrg%2Fdictionary-manager;h=5501765992e14e21953238e4fce7724a4801bc32;hb=c68fb80a6bbf7acbcac4b2cb2143f5fea745cd2b;hp=0000000000000000000000000000000000000000;hpb=b9cce976550d000f15e5a8f2b690740bdae1e468;p=ypp-sc-tools.web-live.git diff --git a/yarrg/dictionary-manager b/yarrg/dictionary-manager new file mode 100755 index 0000000..5501765 --- /dev/null +++ b/yarrg/dictionary-manager @@ -0,0 +1,1443 @@ +#!/usr/bin/wish + +# helper program for OCR in PCTB upload client + +# This is part of ypp-sc-tools, a set of third-party tools for assisting +# players of Yohoho Puzzle Pirates. +# +# Copyright (C) 2009 Ian Jackson +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Yohoho and Puzzle Pirates are probably trademarks of Three Rings and +# are used without permission. This program is not endorsed or +# sponsored by Three Rings. + + +# ./dictionary-manager --approve-updates ijackson@login.chiark.greenend.org.uk /home/ijackson/things/ypp-sc-tools.pctb-dict/pctb /home/ftp/users/ijackson/pctb + +# ./dictionary-manager --approve-updates ijackson@login.chiark.greenend.org.uk /home/ijackson/things/ypp-sc-tools.pctb-dict-test/pctb /home/ftp/users/ijackson/pctb/test + +# ./dictionary-manager --approve-updates '' . . + + +# invocation: +# OUT OF DATE +# run this without args +# then on stdin write +# one line which is a Tcl list for unk_{l,r} unk_contexts glyphsdone etc. +# the xpm in the format expected +# then expect child to exit 0, or write a single 0 byte to fd 4 +# if it wrote a byte to fd 4, it can take another question + + +set aadepth 2 + + +#---------- library routines ---------- + +proc manyset {list args} { + foreach val $list var $args { + upvar 1 $var my + set my $val + } +} + +proc must_gets {f lvar} { + upvar 1 $lvar l + if {[gets $f l] < 0} { error "huh?" } +} + +proc must_gets_imagel {f lvar} { + global debug_rect + upvar 1 $lvar l + must_gets $f l + if {$debug_rect} { debug "<< $l" } +} + +proc must_gets_exactly {f expected} { + must_gets $f got + if {[string compare $expected $got]} { error "$expected $got ?" } +} + +proc read_counted {f var} { + upvar 1 $var val + must_gets $f count + set val [read $f $count] + must_gets $f eol + if {[string length $eol]} { error "$eol ?" } + debug "READ_COUNTED $count $var" +} + +proc puts_counted {f dvar} { + upvar 1 $dvar d + set count [string length $d] + puts $f $count + puts $f $d + debug "PUTS_COUNTED $count $dvar" +} + +proc bgerror {m} { + global errorCode errorInfo + puts stderr "ERROR: $m\n[list $errorCode]\n$errorInfo\n"; + exit 16 +} + +proc execpnm_createphoto {photoname args} { + set tmpfile ./_dictimage.tmp + eval exec $args > $tmpfile + image create photo $photoname -file $tmpfile + file delete $tmpfile +} + +#---------- display core ---------- + +set mul 6 +set inter 1 + +set gotsh 20 +set csrh 20 +set ctxh 20 + +proc init_widgets {} { + # idempotent + global csrh gotsh ctxh + + if {[winfo exists .d]} return + + frame .privacy -bd 2 -relief groove + pack .privacy -side top -padx 2 -pady 2 -fill x + + upload_init + + frame .d -pady 2 -padx 2 -bg black -bd 2 -relief sunken + + image create bitmap image/main + label .d.mi -image image/main -bd 0 + + frame .d.csr -bg black -height $csrh + frame .d.got -bg black -height $gotsh + frame .d.ctx -bg black + + image create bitmap image/cursor -foreground white -background black \ + -data \ +{#define csr_width 11 +#define csr_height 11 +static unsigned char csr_bits[] = { + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x21, 0x04, 0x22, 0x02, 0x25, 0x05, + 0xaa, 0x02, 0x74, 0x01, 0xa8, 0x00, 0x70, 0x00, 0x20, 0x00}; +} + + frame .d.csr.csr + label .d.csr.csr.l -image image/cursor -compound left -fg white -bg black + entry .d.csr.csr.e -bd 0 + pack .d.csr.csr.l -side left + + frame .selctx -bd 2 -relief groove + frame .d.mi.csr_0 -bg white -width 1 + frame .d.mi.csr_1 -bg white -width 1 + frame .pe + frame .pe.grid + + button .pe.ok -text OK + pack .pe.grid .pe.ok -side left + bind .pe.ok { .pe.ok invoke } + + pack .d.mi .d.ctx -side top -anchor w + pack .d -fill x -padx 2 -pady 2 + + frame .help -bd 2 -relief groove + pack .help -pady 2 -padx 2 +} + +proc resize_widgets_core {} { + global mulcols mulrows csrh gotsh ctxh + global unk_l unk_contexts + + foreach w {.d.csr .d.got .d.ctx} { + $w configure -width $mulcols + } + .d.csr configure -width [expr {$mulcols+150}] + + eval destroy [winfo children .d.ctx] +} + +set last_ht {} + +proc helptext {t} { + global last_ht + if {![string compare $t $last_ht]} return + eval destroy [grid slaves .help] + set y 0; foreach l $t { + set x 0; foreach c $l { + set w .help.at${x}x${y} + label $w -text $c + grid $w -row $y -column $x -padx 5 -sticky w + incr x + } + incr y + } + set last_ht $t +} + +proc bind_key {k proc} { + global keybindings + bind .d $proc + set keybindings($k) [expr {!![string length $proc]}] + .d configure -takefocus 1 +} +proc unbind_all_keys {} { + global keybindings + foreach k [array names keybindings] { bind_key $k {} } + .d configure -takefocus 0 +} + +#---------- database read and write common wrapper ---------- + +proc db_getsl {f} { + if {[gets $f l] < 0} { error "unexpected db eof" } + return $l +} + +proc read_database {fn} { + global reqkind database database_fn + upvar #0 database_magic/$reqkind magic + catch { unset database } + + set database_fn $fn + if {![file exists $database_fn]} return + set f [open $database_fn r] + if {[string compare [db_getsl $f] $magic]} { error "$magic $reqkind ?" } + + read_database_header/$reqkind $f + while 1 { + set l1 [db_getsl $f] + + if {![string length $l1]} continue + if {[regexp {^\#} $l1]} continue + if {![string compare . $l1]} break + + read_database_entry/$reqkind $f $l1 + } + close $f +} + +proc write_database {} { + global reqkind database_fn database + upvar #0 database_magic/$reqkind magic + + set f [open $database_fn.tmp w] + puts $f $magic + + write_database_header/$reqkind $f + + set ol {} + foreach bm [array names database] { + lappend ol [format_database_entry/$reqkind $bm $database($bm)] + } + foreach o [lsort $ol] { + puts $f $o + } + puts $f "." + close $f + file rename -force $database_fn.tmp $database_fn +} + +proc select_database {dbname_spec} { + global dbname + set dbname $dbname_spec + read_database "./_local-$dbname.txt" +} + +proc do_database_update {im def} { + global database + maybe_upload_entry $im $def + set database($im) $def + write_database +} + +proc required/char {} { + global mulrows glyphsdone unk_l unk_r unk_contexts rows new_context + global all_contexts debug_rect + + must_gets stdin l + debug "GOT $l" + + manyset [lrange $l 0 3] unk_l unk_r unk_contexts + set glyphsdone [lrange $l 3 end] + + char_read_pgm stdin + + catch { unset all_contexts } + + resize_widgets_core + foreach w {0 1} { + .d.mi.csr_$w configure -height $mulrows + } + set maxh 0 + foreach {min max context contexts got} $glyphsdone { + show_context maxh $min $context + foreach ctx $contexts { set all_contexts($ctx) 1 } + } + foreach ctx $unk_contexts { set all_contexts($ctx) 1 } + + eval destroy [winfo children .selctx] + label .selctx.title -text \ + {Select match context for altering dictionary:} + pack .selctx.title -side left + set new_context {} + + set ci 0; foreach ctx [lsort [array names all_contexts]] { + set all_contexts($ctx) $ci + set selw .selctx.c$ci + set seltxt $ctx + radiobutton $selw -variable new_context -value $ctx -text $seltxt + pack $selw -side left + incr ci + } + $selw configure -text "$seltxt." + label .selctx.warning -text {See README.charset.} + pack .selctx.warning -side left + + show_context maxh $unk_l $unk_contexts + .d.ctx configure -height $maxh + pack forget .pe + pack .selctx -before .d -padx 2 -fill x + pack .d.csr -side top -before .d.mi -anchor w + pack .d.got .d.ctx -side top -after .d.mi -anchor w + pack configure .selctx -fill x + focus .d + + select_database char$rows + draw_glyphsdone + startup_cursor +} + +proc approve_showentry_xinfo/char {w def} { + set unic [string2unicodenames $def] + label $w -text $unic +} + +#========== PIXMAPS ========== + +#---------- pixmap database read and write ---------- + +set database_magic/pixmap {# ypp-sc-tools pctb pixmaps v1} + +proc read_database_header/pixmap {f} { } +proc read_database_entry/pixmap {f def} { + global database + + set im "" + + set p3 [db_getsl $f]; append im $p3 "\n" + if {[string compare $p3 P3]} { error "$p3 ?" } + + set wh [db_getsl $f]; append im $wh "\n"; manyset $wh w h + set depth [db_getsl $f]; append im $depth "\n" + + for {set y 0} {$y < $h} {incr y} { + set line [db_getsl $f]; append im $line "\n" + } + set database($im) $def +} +proc write_database_header/pixmap {f} { puts $f "" } +proc format_database_entry/pixmap {im def} { + return "$def\n$im" +} + +#---------- pixmap display and input handling ---------- + +proc foreach_pixmap_col {var body} { + global alloptions + upvar 1 $var col + for {set col 0} {$col < [llength $alloptions]/3} {incr col} { + uplevel 1 $body + } +} + +proc pixmap_select {ncol} { + global alloptions + debug "PIX SELECT $ncol [llength $alloptions]" + foreach_pixmap_col col { + if {$col==$ncol} continue + .pe.grid.l$col selection clear 0 end + } + if {[pixmap_maybe_ok]} { + focus .pe.ok + } +} +proc pixmap_maybe_ok {} { + global alloptions pixmap_selcol pixmap_selrow + set nsel 0 + foreach_pixmap_col col { + set cs [.pe.grid.l$col curselection] + set lcs [llength $cs] + if {!$lcs} continue + incr nsel $lcs + set pixmap_selcol $col + set pixmap_selrow [lindex $cs 0] + } + if {$nsel==1} { + debug "MAYBE_OK YES col=$pixmap_selcol row=$pixmap_selrow." + .pe.ok configure -state normal -command pixmap_ok + return 1 + } else { + .pe.ok configure -state disabled -command {} + return 0 + } +} +proc pixmap_ok {} { + global database ppm pixmap_selcol pixmap_selrow mainkind alloptions + + return_result_start + foreach_pixmap_col col { + .pe.grid.l$col configure -state disabled + } + .pe.ok configure -state disabled + + manyset [lrange $alloptions [expr {$pixmap_selcol*3}] end] \ + colname coldesc rows + manyset [lrange $rows [expr {$pixmap_selrow*2}] end] \ + rowname rowdesc + set result "$colname - $rowname" + debug "UPDATE PIXMAP AS >$result<" + + do_database_update $ppm $result + + return_result_finish +} + +proc required/pixmap {} { + global unk_what ppm mulcols alloptions + must_gets stdin unk_what + debug "GOT pixmap $unk_what" + set ppm {} + while 1 { + must_gets_imagel stdin ppml + if {![string length $ppml]} break + append ppm $ppml "\n" + } + execpnm_createphoto image/main pnmscale 2 << $ppm + + set alloptions [exec ./database-info-fetch $unk_what] + + select_database pixmap + + set mulcols [image width image/main] + set mulrows [image height image/main] + resize_widgets_core + place forget .d.mi.csr_0 + place forget .d.mi.csr_1 + + pack forget .selctx .d.csr .d.got + pack .pe -side top -before .d -pady 2 + .d configure -takefocus 0 + #-pady 2 -fill x + + eval destroy [winfo children .pe.grid] + set col 0; foreach {colname coldesc rows} $alloptions { + debug "INIT $col $colname \"$coldesc\"" + label .pe.grid.t$col -text $colname + listbox .pe.grid.l$col + foreach {rowname rowdesc} $rows { + debug "INIT $col $colname \"$coldesc\" $rowname \"$rowdesc\"" + .pe.grid.l$col insert end $rowdesc + } + bind .pe.grid.l$col <> [list pixmap_select $col] + grid .pe.grid.t$col -column $col -row 0 + grid .pe.grid.l$col -column $col -row 1 + incr col + } + pixmap_maybe_ok + + helptext { + {{Indicate the meaning of this image; then click OK or hit Return.}} + } +} + +proc approve_showentry_xinfo/pixmap {w def} { + label $w -image image/empty +} + +#========== UPLOADS TO DICTIONARY SERVER ========== + +proc upload_init {} { + global privacy_setting + + set privacy_setting [upload_status] + + label .privacy.warn -text " Privacy " + if {$privacy_setting} { + .privacy.warn configure -background yellow -foreground black + } + label .privacy.overall -text " Upload new dictionary entry:" + label .privacy.reference -text " See README.privacy." + + pack .privacy.warn .privacy.overall -side left + + foreach {setting string} { + 0 {No} + 1 {Yes, anonymously} + 2 {Yes, quoting my pirate name.} + } { + radiobutton .privacy.o$setting -text $string \ + -value $setting -variable privacy_setting + pack .privacy.o$setting -side left + if {$setting > $privacy_setting} { + .privacy.o$setting configure -state disabled + } + } + pack .privacy.reference -side left + + if {!$privacy_setting} { + foreach w [winfo children .privacy] { + $w configure -state disabled + } + } + if {$privacy_setting} { + package require http + ::http::config -urlencoding utf-8 + } +} + +proc upload_status {} { + # returns 0, 1, 2 for none, anon, with pirate name + global env + + if {![info exists env(YPPSC_PCTB_DICT_SUBMIT)]} { debug a; return 0 } + if {![string compare 0 $env(YPPSC_PCTB_DICT_SUBMIT)]} { debug b; return 0 } + + if {![info exists env(YPPSC_PIRATE)]} { return 1 } + if {![info exists env(YPPSC_OCEAN)]} { return 1 } + if {![string length $env(YPPSC_PIRATE)]} { return 1 } + if {![string length $env(YPPSC_OCEAN)]} { return 1 } + + return 2 +} + +proc maybe_upload_entry {im def} { + global reqkind privacy_setting env dbname quiet aadepth + + debug "DB-UPDATE PRIVACY $privacy_setting" + if {!$privacy_setting} return + + debug "DB-UPDATE UPLOADING" + + set pl {} + lappend pl dict $dbname + lappend pl version 3 + lappend pl depth $aadepth + + if {$privacy_setting>=2} { + set pirate [string totitle $env(YPPSC_PIRATE)] + set ocean [string totitle $env(YPPSC_OCEAN)] + debug "DB-UPDATE NON-ANON $ocean $pirate" + lappend pl \ + pirate $pirate \ + ocean $ocean + } + lappend pl entry [format_database_entry/$reqkind $im $def] + + set url $env(YPPSC_PCTB_DICT_SUBMIT) + append url dictionary-update-receiver + + set query [eval ::http::formatQuery $pl] + regsub -all {%0d} $query {} query + debug "DB-UPDATE QUERY $query" + + if {[regexp {^\.?/} $url]} { + set cmd [list $url $query] + debug "SUBMIT CMD [string range $cmd 0 200]..." + set body [eval exec $cmd 2>@ stderr] + regsub {^Content-Type: text/plain.*\n\n} $body {} body + } else { + + if {[catch { + set req [::http::geturl $url -query $query] + } emsg]} { + puts stderr \ + "\nWARNING: Cannot do dictionary upload: $emsg\n" + return + } + upvar #0 $req s + debug "DB-UPDATE DONE $req $s(status) [array names s]" + set ncode [::http::ncode $req] + + if {!(![string compare ok $s(status)] && + ![string compare 200 $ncode])} { + set m "\nWARNING: Dictionary upload failed:" + foreach v {status http error posterror} { + if {[info exists s($v)]} { append m "\n $v: $s($v)" } + } + puts stderr $m + return + } + set body $s(body) + ::http::cleanup $req + } + + if {![string match {OK *} $body]} { + set m "\nWARNING: Dictionary upload went wrong:\n" + append m "\n " [join [split $body "\n"] "\n "] + puts stderr $m + return + } + + if {!$quiet} { + puts stderr \ + "Uploaded $dbname `$def': $body" + } +} + +#========== CHARACTER SET ========== + +#---------- pgm input processor ---------- + +proc char_read_pgm {f} { + global glyphsdone mul inter rhsmost_max unk_l unk_r mulcols mulrows + global cols rows charkey + + must_gets_imagel $f l + if {[string compare $l P2]} { error "magic $l ?" } + + must_gets_imagel $f l + if {![regexp {^([1-9]\d{0,3}) ([1-9]\d{0,3}) ([1-9]\d?)$} \ + $l dummy cols rows maxval]} { error "head $l ?" } + + for {set depth 1} {$maxval != (1<<$depth)-1} {incr depth} { + if {$depth >= 16} { error "maxval $maxval ?" } + } + + set chop_l [expr {$unk_l - 80}] + set chop_r [expr {$cols - $unk_l - 100}] + if {$chop_l<0} { set chop_l 0 } + if {$chop_r<0} { set chop_r 0 } + + set unk_l [expr {$unk_l - $chop_l}] + set unk_r [expr {$unk_r - $chop_l}] + set ngd {} + foreach {min max context contexts got} $glyphsdone { + lappend ngd \ + [expr {$min-$chop_l}] \ + [expr {$max-$chop_l}] \ + $context $contexts $got + } + set glyphsdone $ngd + + set realcols $cols + set cols [expr {$cols - $chop_l - $chop_r}] + debug "NOW cols=$cols chop_l,r=$chop_l,$chop_r rows=$rows\ + $unk_l $unk_r $ngd" + + set mulcols [expr {$cols*$mul+$inter}] + set mulrows [expr {$rows*$mul+$inter}] + + set o "P3\n$mulcols $mulrows 15\n" + + for {set x 0} {$x<$cols} {incr x} { set charkey($x) {} } + + set ointer1 " 1 1 1" + set ointer [string repeat $ointer1 $inter] + set ointerl "[string repeat $ointer1 $mulcols]\n" + + append o $ointerl + + for {set y 0} {$y<$rows} {incr y} { + must_gets_imagel $f l + if {[llength $l] != $realcols} { error "realcols=$realcols $l ?" } + + set ol $ointer + + for {set x 0} {$x<$cols} {incr x} { + set realx [expr {$x + $chop_l}] + set c [lindex $l $realx] + append charkey($x) [format %x $c] + + set how "u" + if {$x >= $unk_l && $x <= $unk_r} { + set how q + } else { + set ab 0 + foreach {min max context contexts got} $glyphsdone { + set rhsmost_max $max + if {$x >= $min && $x <= $max} { + set how [lindex {a b} $ab] + break + } + set ab [expr {!$ab}] + } + } + set c15 [expr {$c << (16-$depth)}] + set c15 [expr {$c15 | ($c15 >> $depth)}] + set c15 [expr {$c15 | ($c15 >> $depth*2)}] + set c15 [expr {$c15 >> 12}] + + foreach rgb {r g b} { set $rgb {$c15} } + switch -exact $how { + a { set r {$c15>>1 | 0x8} } + b { set b {$c15>>2 | 0xc} } + u { set b 0 } + q { set b 0 } + default { error "how $how ?" } + } +#debug "PIXEL $x,$y how=$how c=$c c15=$c15 $r $g $b" + + set opixel " " + foreach rgb {r g b} { + append opixel [format " %2d" [expr [set $rgb]]] + } + append ol [string repeat $opixel [expr {$mul-$inter}]] + append ol $ointer + } + append ol "\n" + append o [string repeat $ol [expr {$mul-$inter}]] + append o $ointerl + } + +# debug "DATA1 $o" + + execpnm_createphoto image/main pnmscale 1 << $o +} + +#---------- character set editor display ---------- + +proc show_context {maxhv x ctxs} { + global mul + upvar 1 $maxhv maxh + set w .d.ctx.at$x + if {[llength $ctxs]==1} { set fg blue } { set fg yellow } + label $w -bg black -fg $fg -text [join $ctxs "/\n"] -justify left + place $w -x [expr {($x-1)*$mul}] -y 0 + set wh [winfo reqheight $w] + if {$wh > $maxh} { set maxh $wh } +} + +proc draw_glyphsdone {} { + global glyphsdone mul inter + eval destroy [winfo children .d.got] + foreach {min max context contexts got} $glyphsdone { + frame .d.got.m$min -bd 0 -background \#888 + label .d.got.m$min.l -text "$got" -fg white -bg black -bd 0 + pack .d.got.m$min.l -padx 1 -pady 1 + place .d.got.m$min -x [expr {$min*$mul+$inter}] -y 0 + } +} + +proc startup_cursor {} { + global cur_already cur_mode cur_0 cur_1 last_ht + global glyphsdone unk_l unk_r + + set cur_already [expr {[llength $glyphsdone]/5-1}] + set cur_mode 0 ;# one of: 0 1 already text + + set cur_0 $unk_l + set cur_1 [expr {$unk_r+1}] + + recursor +} + +#---------- character set runtime display and keystroke handling ---------- + +proc char_exactly_selctxts {contexts} { + global all_contexts + foreach ctx [array names all_contexts] { + set ci $all_contexts($ctx) + set selw .selctx.c$ci + if {[lsearch -exact $contexts $ctx]>=0} { + set state normal + } else { + set state disabled + } + $selw configure -state $state + } +} + +proc recursor/0 {} { recursor//01 0 } +proc recursor/1 {} { recursor//01 1 } +proc recursor//01 {z1} { + global mul rhsmost_max cols glyphsdone cur_0 cur_1 + global all_contexts + upvar #0 cur_$z1 cur + .d.csr.csr.l configure -text "adjust [char_get_definition_context_actual]" + place .d.csr.csr -x [expr {$cur*$mul - 7}] + + set okctxts [char_get_definition_contexts] + char_exactly_selctxts $okctxts + + foreach ctx [lsort [array names all_contexts]] { + set key [string range $ctx 0 0] + if {[lsearch -exact $okctxts $ctx] >= 0} { + bind_key [string tolower $key] " + [list set new_context $ctx] + char_start_define_text + " + } else { + bind_key [string tolower $key] {} + } + lappend context_help $key + } + set context_help [list [join $context_help " "] \ + {Set match context for new glyph, confirm location, and start entry.}] + + bind_key space { othercursor } + bind_leftright_q cur_$z1 0 [expr {$cols-1}] + if {[llength $glyphsdone]} { + bind_key BackSpace { set cur_mode already; recursor } + } else { + bind_key BackSpace {} + } + bind_key Return { + char_start_define_text + } + helptext [list \ + {{<- ->} {move cursor, adjusting area to define}} \ + {Space {switch to moving other cursor}} \ + {Return {confirm location, enter letter(s)}} \ + {Backspace {switch to correcting earlier ocr}} \ + {Q {quit and abandon OCR run}} \ + $context_help \ + ] +} +proc othercursor {} { + global cur_mode + set cur_mode [expr {!$cur_mode}] + recursor +} + +proc char_start_define_text {} { + global cur_0 cur_1 cur_mode + if {$cur_0 == $cur_1} return + set cdgdca [char_get_definition_context_actual] + if {![string length $cdgdca]} return + .d.csr.csr.e delete 0 end + set cur_mode text + .d.csr.csr.l configure -text "define $cdgdca:" + recursor +} + +proc recursor/text {} { + global all_contexts + + helptext { + {Return {confirm entry of new glyph}} + {Escape {abandon entry}} + } + unbind_all_keys + pack .d.csr.csr.e -side left -padx 2 + focus .d.csr.csr.e + bind .d.csr.csr.e { + set strq [.d.csr.csr.e get] + if {[string length $strq]} { + RETURN_RESULT DEFINE [list $strq] + } + } + bind .d.csr.csr.e { + bind_key Escape {} + pack forget .d.csr.csr.e + set cur_mode 1 + focus .d + recursor + } +} + +proc recursor/already {} { + global mul + global cur_already mul + global glyphsdone cur_already mul + + char_exactly_selctxts [lindex $glyphsdone [expr {$cur_already*5+2}]] + + .d.csr.csr.l configure -text {correct} + set rmax [lindex $glyphsdone [expr {$cur_already*5}]] + place .d.csr.csr -x [expr {$rmax*$mul-3}] + bind_key Return {} + bind_leftright_q cur_already 0 [expr {[llength $glyphsdone]/5-1}] + bind_key space { bind_key Delete {}; set cur_mode 1; recursor } + bind_key Delete { + RETURN_RESULT DELETE [lrange $glyphsdone \ + [expr $cur_already*5] \ + [expr $cur_already*5+2]] + } + helptext { + {{<- ->} {move cursor, selecting glyph to correct}} + {Del {clear this glyph from the recognition database}} + {Space {switch to selecting area to define as new glyph}} + {Q {quit and abandon OCR run}} + } +} + +proc bind_leftright_q {var min max} { + bind_key Left [list leftright $var $min $max -1] + bind_key Right [list leftright $var $min $max +1] + bind_key q { + puts stderr "\nCharacter resolver quitting as you requested." + exit 1 + } +} +proc leftright {var min max inc} { + upvar #0 $var v + set vnew $v + incr vnew $inc + if {$vnew < $min || $vnew > $max} return + set v $vnew + recursor +} + +proc recursor {} { + global csrh cur_mode cur_0 cur_1 mul + foreach z1 {0 1} { + place .d.mi.csr_$z1 -y 0 -x [expr {[set cur_$z1] * $mul}] + } + recursor/$cur_mode +} + +#---------- character database read and write ---------- + +# OUT OF DATE +# database format: +# series of glyphs: +# ... +# width +# + +# $database($context 0x 0x...) = $hex + +set database_magic/char "# ypp-sc-tools pctb font v3 depth=$aadepth" + +proc read_database_header/char {f} { + global rows + set l [db_getsl $f] + if {$l+0 != $rows} { error "wrong h $l $rows ?" } +} +proc read_database_entry/char {f context} { + global database + set bm $context + set strq [db_getsl $f] + while 1 { + set l [db_getsl $f] + if {![string length $l]} break + lappend bm $l + } + set database($bm) $strq +} + +proc write_database_header/char {f} { + global rows + puts $f "$rows\n" +} +proc format_database_entry/char {bm strq} { + global database rows + set o "[lindex $bm 0]\n$strq\n" + foreach x [lrange $bm 1 end] { append o "$x\n" } + return $o +} + +proc dbkey {ctx l r} { + global charkey + set bm $ctx + for {set x $l} {$x <= $r} {incr x} { + lappend bm $charkey($x) + } + return $bm +} + +proc char_get_definition_cursors {} { + global cur_0 cur_1 + if {$cur_0 <= $cur_1} { + set cl $cur_0; set cr $cur_1 + } else { + set cl $cur_1; set cr $cur_0 + } + incr cr -1 + debug "CGD CURSORS $cl $cr" + return [list $cl $cr] +} + +proc char_get_definition_contexts {} { + global glyphsdone unk_l unk_contexts database + + manyset [char_get_definition_cursors] c0 c1 + + if {$c0 == $unk_l} { + set ncontexts $unk_contexts + } else { + foreach {l r context contexts got} $glyphsdone { + if {$l==$c0} { set ncontexts $contexts; break } + } + if {![info exists ncontexts]} { + set ncontexts {} + } + } + debug "CGD CONTEXTS $ncontexts" + return $ncontexts +} + +proc char_get_definition_context_actual {} { + global new_context + set ncontexts [char_get_definition_contexts] + if {[llength $ncontexts]==1} { + set c [lindex $ncontexts 0] + } elseif {[lsearch -exact $ncontexts $new_context]>=0} { + set c $new_context + } else { + set c {} + } + debug "CDG CONTEXT ACTUAL $c FROM NEW $new_context ALLOW $ncontexts" + return $c +} + +proc update_database/DEFINE {strq} { + manyset [char_get_definition_cursors] c0 c1 + set c [char_get_definition_context_actual] + if {![string length $c]} { + error "Selected context is not one of the many possibilities." + } + debug "DEFINE $strq" + set bm [dbkey $c $c0 $c1] + do_database_update $bm $strq +} + +proc update_database/DELETE {l r ctxs} { + global database + if {[llength $ctxs]!=1} { error "$ctxs ?" } + foreach ctx $ctxs { + set bm [dbkey $ctx $l $r] + catch { unset database($bm) } + } + write_database +} + +proc RETURN_RESULT {how what} { + return_result_start + + place forget .d.csr.csr + pack forget .d.csr.csr.e + + debug "$how $what" + eval update_database/$how $what + + return_result_finish +} + +#========== server for approving updates ========== + +proc remote-serv-log {dict pirate caller file event} { + global remoteserv_logf + set t [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S %Z}] + set s [format "%s %-6s %-31s %-31s %s %s\n" \ + $t $dict $pirate $caller [file tail $file] $event] + puts -nonewline $remoteserv_logf $s +} + +proc remote-serv/list {} { + global dropdir + foreach file [glob -nocomplain -type f -directory $dropdir _update.*.rdy] { + puts yes + puts $file + set f [open $file] + set d [read $f] + close $f + puts_counted stdout d + } + puts end +} + +proc remote-serv/take {yesno file dict} { + global dropdir dictdir rows reqkind database + set rows "" + debug "TAKE [list $yesno $file $dict]" + read_counted stdin pirate + read_counted stdin caller + read_counted stdin key + read_counted stdin val + + must_gets_exactly stdin confirmed + + manyset [dict2_reqkind_rows $dict] reqkind rows + + if {$yesno} { + set fnbase $dictdir/master-$dict.txt + read_database $fnbase + set database($key) $val + write_database + + exec gzip --rsyncable -7 < $fnbase > $fnbase.gz.new + exec mv -f -- $fnbase.gz.new $fnbase.gz + + set desc approve + } else { + set desc reject + } + remote-serv-log $dict $pirate $caller $file "$desc $reqkind $rows" + file delete -force $file + + puts done +} + +proc remote-serv/noop {} { + puts ok +} + +set remoteserv_banner {ypp-sc-tools pctb remote-server v1} + +proc main/remoteserv {} { + global argv dropdir remoteserv_banner remoteserv_logf dictdir + manyset $argv dropdir dictdir + puts $remoteserv_banner + set remoteserv_logf [open $dropdir/_dict.log a] + fconfigure $remoteserv_logf -buffering line + while 1 { + flush stdout + if {[gets stdin l] < 0} break + eval remote-serv/$l + } +} + +#========== updates approver ========== + +proc puts_server {s} { + global server + debug ">> $s" + puts $server $s +} +proc must_gets_server {lv} { + upvar 1 $lv l + global server + must_gets $server l + debug "<< $l" +} + +proc must_gets_exactly_server {expected} { + must_gets_server got + if {[string compare $expected $got]} { error "$expected $got ?" } +} + +proc regsub-data {exp subspec args} { + global data + if {![eval regsub $args -- [list $exp $data $subspec data]]} { + error "$exp >$data< ?" + } +} + +proc dict2_reqkind_rows {dict} { + if {![string compare pixmap $dict]} { + return {pixmap {}} + debug "DICT PIXMAP" + } elseif {[regexp {^(char)([1-9]\d*)$} $dict dummy reqkind rows]} { + debug "DICT CHAR rqk=$reqkind r=$rows." + return [list $reqkind $rows] + } else { + error "$dict ?" + } +} + +proc chop_counted {var} { + upvar 1 $var val + global data + if {![regexp {^(0|[1-9]\d*)\n} $data dummy l]} { error "$data ?" } + regsub-data {^.*\n} {} -line + set val [string range $data 0 [expr {$l-1}]] + set data [string range $data $l end] + debug "CHOP_COUNTED $l $var" + regsub-data {^\n} {} +} + +proc approve_decompose_data {specdata} { + global data aadepth + set data $specdata + + regsub-data "^ypp-sc-tools dictionary update v3 depth=$aadepth\\n" {} + uplevel 1 chop_counted pirate + uplevel 1 chop_counted caller + uplevel 1 chop_counted dict + uplevel 1 chop_counted ctx + uplevel 1 chop_counted def + uplevel 1 chop_counted image + uplevel 1 chop_counted key + uplevel 1 chop_counted val + + return [uplevel 1 {list $dict $ctx $def $image}] +} + +proc approve_compare {fd1 fd2} { + manyset $fd1 file data; set sv1 [approve_decompose_data $data] + manyset $fd2 file data; set sv2 [approve_decompose_data $data] + return [string compare $sv1 $sv2] +} + +proc string2unicodenames {str} { + return [exec perl -e { + use Unicode::CharName qw(uname); + $ARGV[0] =~ s/^ //; + foreach $_ (split //,$ARGV[0]) { + print uname(ord),"\n" or die $! + } + } " $str"] +} + +proc approve_showentry {ix file specdata} { + global approve_ixes reqkind approve_entryhow + + approve_decompose_data $specdata + + set wb .app.e$ix + + frame $wb-inf + label $wb-inf.who -text $pirate + + entry $wb-inf.file -font fixed -relief flat + $wb-inf.file insert end [file tail $file] + $wb-inf.file configure -state readonly -width -1 + + pack $wb-inf.who $wb-inf.file -side top + + frame $wb-def + label $wb-def.scope -text "$dict $ctx" + label $wb-def.def -text $def + pack $wb-def.scope $wb-def.def -side bottom + + if {[regexp {^P2} $image]} { + set image [exec pgmtoppm {#008-white} << $image | pnmnoraw] + append image "\n" + } + execpnm_createphoto approve/$ix pnmscale 3 << $image + label $wb-image -image approve/$ix -bd 2 -relief flat -bg black + + manyset [dict2_reqkind_rows $dict] reqkind + approve_showentry_xinfo/$reqkind $wb-xinfo $def + + if {$ix} { + label $wb-div -bd 1 -relief sunken -image image/empty + grid configure $wb-div -columnspan 5 -sticky ew -padx 5 + } + + frame $wb-act -bd 2 -relief groove + set approve_entryhow($ix) approve + foreach how {approve reject defer} { + set w $wb-act.$how + radiobutton $w -variable approve_entryhow($ix) \ + -text [string totitle $how] -value $how + pack $w -side left + } + + grid $wb-def $wb-image $wb-xinfo $wb-act $wb-inf -padx 3 + grid configure $wb-image -ipadx 3 -ipady 3 -sticky w + + lappend approve_ixes $ix +} + +proc approve_approve_reject_one {ix yesno} { + global approve_list server + manyset [lindex $approve_list $ix] file tdata + approve_decompose_data $tdata + puts_server "take $yesno $file $dict" + puts_counted $server pirate + puts_counted $server caller + puts_counted $server key + puts_counted $server val + puts_server confirmed + flush $server + must_gets_exactly_server done +} + +proc approve_check_server {} { + global server + puts_server noop + flush $server + must_gets_exactly_server ok +} + +proc approve_confirm {} { + global approve_ixes approve_entryhow + .ok configure -state disabled + update idletasks + approve_check_server + foreach ix $approve_ixes { + set how $approve_entryhow($ix) + switch -exact $how { + approve { approve_approve_reject_one $ix 1 } + reject { approve_approve_reject_one $ix 0 } + defer { } + default { error $how? } + } + } + approve_fetch_list +} + +proc approve_fetch_list {} { + global server approve_list + set approve_list {} + puts_server list + flush $server + while 1 { + must_gets_server more + switch -exact $more { + yes { } + end { break } + default { error "$more ?" } + } + must_gets_server file + read_counted $server data + lappend approve_list [list $file $data] + } + + if {![llength $approve_list]} { puts "Nothing (more) to approve."; exit 0 } + + set approve_list [lsort -command approve_compare $approve_list] + approve_show_page 0 + .ok configure -state normal +} + +proc main/approve {} { + global argv server remoteserv_banner data approve_list approve_page + global userhost directory dictdir debug + + if {[llength $argv] != 3} { error "wrong # args" } + manyset $argv userhost directory dictdir + debug "APPROVER FOR $userhost $directory $dictdir" + + set cmd [list tclsh $directory/dictionary-manager] + if {$debug} { lappend cmd --debug-server } + lappend cmd --remote-server-1 $directory $dictdir + switch -glob $userhost { + {} { } + {* *} { set cmd $userhost } + * { set cmd [concat [list ssh -o compression=yes $userhost] $cmd] } + } + debug "APPROVER RUNS $cmd" + + lappend cmd 2>@ stderr + set server [open |$cmd r+] + must_gets_exactly_server $remoteserv_banner + + button .left -text {<<} -command {approve_show_page -1} + button .right -text {>>} -command {approve_show_page +1} + + label .title -text {} + frame .app -bd 2 -relief groove + button .ok -text "Confirm" -command approve_confirm + pack .title .app -side top + pack .left -side left + pack .right -side right + pack .ok -side bottom + + image create bitmap image/empty + + set approve_page 0 + approve_fetch_list +} + +proc approve_show_page {delta} { + global approve_page approve_ixes approve_list userhost directory dictdir + + eval destroy [winfo children .app] + set approve_ixes {} + + set per_page 10 + incr approve_page $delta + + set ll [llength $approve_list] + set npages [expr {($ll+$per_page-1)/$per_page}] + if {$approve_page >= $npages} { incr approve_page -1 } + + set page_start [expr {$approve_page*$per_page}] + set page_end [expr {$page_start+$per_page-1}] + + for {set ix $page_start} {$ix < $ll && $ix <= $page_end} {incr ix} { + set fd [lindex $approve_list $ix] + eval approve_showentry $ix $fd + } + + .title configure -text \ + "$userhost\n$directory => $dictdir\nPage [expr {$approve_page+1}]/$npages" + + .left configure -state disabled + .right configure -state disabled + if {$approve_page>0} { .left configure -state normal } + if {$approve_page<$npages-1} { .right configure -state normal } +} + +#========== main program ========== + +proc return_result_start {} { + helptext {{{ Processing }}} + unbind_all_keys + update idletasks +} +proc return_result_finish {} { + global mainkind + done/$mainkind +} + +proc main/default {} { + puts stderr "Do not run this program directly." + exit 12 +} +proc done/default {} { +} + +proc required {} { + global reqkind + + fileevent stdin readable {} + fconfigure stdin -blocking yes + + if {[gets stdin reqkind]<0} { + if {[eof stdin]} { fconfigure stdin -blocking yes; exit 0 } + return + } + init_widgets + + required/$reqkind +} + +proc main/automatic {} { + fconfigure stdin -blocking no + fileevent stdin readable required +} +proc done/automatic {} { + exec sh -c {printf \\0 >&4} + main/automatic +} + +proc debug {m} { } + +set mainkind default +set ai 0 +set debug 0 +set debug_rect 0 +set quiet 0 +foreach arg $argv { + incr ai + switch -exact -- $arg { + {--quiet} { set quiet 1 } + {--debug} { set debug 1 } + {--debug-rect} { set debug_rect 1 } + {--debug-server} { proc debug {m} { puts stderr "DICT-MGR-SVR $m" }} + {--noop-arg} { } + {--approve-updates} { set mainkind approve; break } + {--automatic-1} { set mainkind automatic; break } + {--remote-server-1} { set mainkind remoteserv; break } + {--automatic*} - {--remote-server} + { error "incompatible versions - install problem" } + default { error "huh $argv ?" } + } +} +if {$debug} { + proc debug {m} { puts stderr "DICT-MGR $m" } +} +set argv [lrange $argv $ai end] + +main/$mainkind