Version 0 of A crude, but working simple fmt.tcl (word wrap in long strings)

Updated 2011-03-06 16:58:35 by JDG

#!/usr/local/bin/tclsh8.4

# fmt.tcl by Jim Graham, N5IAL/4. # # Feel free to use this in your code. I would, however, ask that you # leave these comments in place, and if you improve it, please send your # changes to me at spooky130u AT gmail DOT com. Thanks! # # I wrote this to use with my as-of-yet unnamed brewer's inventory / # grain/hop/yeast database (it's both), which I plan on integrating with # my brewer's recipe formulator (also written with Tcl/Tk) called # GTbrew2. # # This is a crude attempt at implementing word-wrap in Tcl. I'm writing # this because when I tried to use "-wrap word" in a text widget, as the # docs suggest, it didn't wrap by word boundaries at all.... This script # makes some limited attempts to avoid wrapping at bad places (e.g., # don't wrap "20 deg." between 20 and deg....wrap the 20 along with the # unit that follows. Sadly, I don't have (or, at least, am not aware of # having) a typography document that lists bad points to wrap a line, so # I'm just having to use the ones I DO know (and that I thought about as # I wrote this).

# from jdglib proc streq {s1 s2} { return expr {[string compare $s1 $s2 == 0}] }

# from jdglib # Compare multiple strings with s1 ... return number of matches proc streqx {s1 s2 args} {

   set ret 0
   foreach s "$s2 $args" { if {[streq $s1 $s]} { incr ret } }
   return $ret

}

# NOT all-inclusive, but check for some obvious bad breaks

proc check_return {s1 s2} {

   regsub {  *$} $s1 {} s3 ; set s1 $s3
   regsub {^  *} $s2 {} s3 ; set s2 $s3
   set cut1 [string last " " $s1] ; incr cut1
   set cut2 [string first " " $s2] ; incr cut2 -1
   set w1 [string range $s1 $cut1 end]
   set w2 [string range $s2 0 $cut2]
   set goodbreak 1

   if {[streqx $w1 Mr. Mr Mrs. Mrs Miss Dr Dr. Prof.]} { set goodbreak 0 }

# Here we try to prevent wrapping between a number and its unit.

# if {regexp {^[0-9\.*$} $w1] && regexp {^[a-zA-Za-zA-Z\.?$} $w2]} { # set goodbreak 0 # } # if {regexp {^[0-9\.*$} $w1] && \ # streqx $w2 mL L cc gal gal. deg deg. lb lb. IBU SRM °P °F} { # set goodbreak 0 # }

# Better still, don't wrap after ANY number...wrap the number, too.

   if {[regexp {^[0-9\.]*$} $w1]} { set goodbreak 0 }

   if {$goodbreak} { return [list $s1 $s2] }
   incr cut1 -2
   set s1 [string range $s1 0 $cut1]
   set s2 "$w1 $s2"

   return [list $s1 $s2]

}

proc fmt_core {n s} {

   set maxlen $n
   if {[string length $s] < $maxlen} { return $s }
   set s1 [string range $s 0 $maxlen]
   set cut [string last " " $s1]
   set s1 [string range $s 0 $cut]
   incr cut 1
   set s2 [string range $s $cut end]
   regsub {  *$} $s1 {} s3 ; set s1 $s3
   regsub {^  *} $s2 {} s3 ; set s2 $s3
   set retval [list $s1 $s2]

# Before we return this, check for bad breakpoints (e.g., Mr/Mrs, after a # number and before the unit (e.g., don't break between 20 mL), etc.

   set slist [check_return $s1 $s2]
   return $slist

}

proc fmt {n s} {

   set maxlen $n
   if {[string length $s] < $maxlen} { return $s }
   set finished 0
   set newstring ""
   while {!$finished} {
      set slist [fmt_core $maxlen $s]
      if {[llength $slist] == 2} {
         foreach {s1 s2} $slist { break }
      } else {
         set s1 [lindex $slist 0]
         set s2 ""
      }
      if {[string length $newstring] == 0} {
         set newstring $s1
      } else {
         set newstring "$newstring\n$s1"
      }
      if {[string length $s2]} {
         if {[string length $s2] < $maxlen} {
            return "$newstring\n$s2"
         } else {
            set s $s2
         }
      } else {
         return $newstring
      }
   }

}

# A few simple tests... And it passed.