Updated 2012-05-13 04:28:11 by RLE

Arjen Markus (18 december 2002) The script below is a first (well, second) attempt to render mathematical formulae in a canvas. It is far from complete, and if I ever finish it, it will probably end up totally differently, but it works to a certain degree with a certain class of formulae.

Several people, among whom Lars H and Kevin Kenny, provided useful information on existing algorithms, such as the one used in TeX, and pointers to articles by Brian Kernighan [1].

Note: the picture shows the result of an earlier version. The one appearing below uses unicodes which have a very nice look, certainly on Windows. Again, far from complete.

See also: Binomial coefficients for a quick-and-dirty solution.

AM (21 march 2005) I realised the other day that the code in Drawing Diagrams (or at least the sofar unpublished version) is a suitable replacement - it will allow me to follow Kernighan's footsteps ... For now, I will have to sit down and implement these ideas!

AM (10 january 2007) I finally started implementing these ideas ... The program I have now is not ready yet, but it is getting into shape nicely.

FF (25 february 2007) I suggest you looking at the W3C MathML Page [2] where you can find much useful resources. I seem cannot find a program that directly generates an image from a MathML expression; thus this may be a good start for doing a MathML widget. Is anyone seriously interested?

AM (26 februari 2007) I am ;) even though I find MathML to be, well, rather verbose for the job.

FF (27 february 2007) well, in fact I meant to point you to some of its implementations: it should give you nice ideas looking at the existing renderer's code (not properly at the Mozilla's one, which could be surprisingly huge ;)).

FF (13 may 2007) I added a little proc in the example MimeTexPreview for interfacing mimetex with Tcl canvas. Give it a check!
 # mathform.tcl --
 #    Simple script to render mathematical formulae
 #

 namespace eval ::MathFormula:: {
    variable poshoriz   0
    variable offhoriz   0
    variable posvert    0
    variable offvert    0
    variable offset_add 0
    variable symbol
    array set symbol {
       ALPHA   \u391
       BETA    \u392
       GAMMA   \u393
       DELTA   \u394
       EPSILON \u395
       ZETA    \u396
       ETA     \u397
       THETA   \u398
       IOTA    \u399
       KAPPA   \u39a
       LAMBDA  \u39b
       MU      \u39c
       NU      \u39d
       XI      \u39e
       OMICRON \u39f
       PI      \u400
       RHO     \u401
       SIGMA   \u403
       TAU     \u404
       UPSILON \u405
       PHI     \u406
       CHI     \u407
       PSI     \u408
       OMEGA   \u409
       alpha   \u3b1
       beta    \u3b2
       gamma   \u3b3
       delta   \u3b4
       epsilon \u3b5
       zeta    \u3b6
       eta     \u3b7
       theta   \u3b8
       iota    \u3b9
       kappa   \u3ba
       lambda  \u3bb
       mu      \u3bc
       nu      \u3bd
       xi      \u3be
       omicron \u3bf
       pi      \u3c0
       rho     \u3c1
       sigma   \u3c3
       tau     \u3c4
       upsilon \u3c5
       phi     \u3c6
       chi     \u3c7
       psi     \u3c8
       omega   \u3c9
       NABLA   \u2207
       DEL     \u2207
       SUM     \u2211
       PROD    \u220f
       INT     \u222b
       Inf     \u221e
       PARTIAL \u2202
       ALL     \u2200
       EXIST   \u2203
       EMPTY   \u2205
       MEMBER  \u2208
       NOTMEMBER \u2209
       APPROX  \u224b
       !=      \u2260
       <=      \u2264
       >=      \u2265
       <<      \u226a
       >>      \u226b
       SQRT    \u221a
       //      \u221a
       3//     \u221b
       4//     \u221c
       DOT     \u2219
       INTERSECT  \u2229
       UNION   \u222a
       ORDER   \u223e
       AND     \u22c0
       OR      \u22c1
    }
 }

 # drawFormula --
 #    Draw the formula at a given position (lower-left)
 #
 # Arguments:
 #    canvas   The canvas to use
 #    xpos     X-position to start
 #    ypos     Y-position to start
 #    formula  String holding the formula
 #
 # Result:
 #    None
 #
 # Side effect:
 #    The formula is rendered on the screen
 #
 #
 proc ::MathFormula::drawFormula {canvas xpos ypos formula} {
    variable poshoriz
    variable offhoriz
    variable posvert
    variable offvert
    variable offset_add

    set poshoriz   $xpos
    set posvert    $ypos
    set offhoriz   0
    set offvert    0
    set offset_add 0

    set tokens [analyseFormula $formula]
    foreach {token xp yp advance} $tokens {
       renderToken $canvas $token $xp $yp $advance
    }
 }

 # renderToken --
 #    Render the given token
 #
 # Arguments:
 #    canvas   Canvas in which to draw
 #    token    Token to be drawn
 #    xp       X-position relative to current drawing position
 #    yp       Y-position relative to current drawing position
 #
 # Result:
 #    None
 #
 # Side effect:
 #    The token is drawn on the canvas
 #
 proc ::MathFormula::renderToken {canvas token xp yp advance} {
    variable poshoriz
    variable posvert
    variable offhoriz
    variable offvert
    variable symbol

    set xpos [expr {$poshoriz+$xp}]
    set ypos [expr {$posvert+$yp}]

    if { [info exists symbol($token)] } {
       set unicode $symbol($token)
    } else {
       set unicode $token
    }

    switch -- $token {
    "INT" { # Integral
          set item [$canvas create text $xpos $ypos -font "Times 12" -text $unicode]
          incr xpos 5
        }
    "SUM" { # Sum
          set item [$canvas create text $xpos $ypos -font "Times 12" -text $unicode]
          incr xpos 8
       }
    default { # Letters and other ordinary symbols
          set item [$canvas create text $xpos $ypos -text $unicode -fill black -anchor w -font "Times 12"]
          set bbox [$canvas bbox $item]
          set width [expr {[lindex $bbox 2]-[lindex $bbox 0]}]
          set xp   $width
          set xpos [expr {$xpos+$width}]
       }
    }

    if { $advance } {
       set poshoriz $xpos
    } else {
       set offhoriz [expr {$xp>$offhoriz? $xp : $offhoriz}]
    }
 }

 # analyseFormula --
 #    Analyse the formula and determine the positions of the tokens
 #
 # Arguments:
 #    formula  Formula to be drawn
 #
 # Result:
 #    List of tokens with relative positions and advancing information
 #
 proc ::MathFormula::analyseFormula {formula} {

    set result  [list]
    set advance 1
    set xp      0
    set yp      0

    foreach token $formula {
       switch -- $token {
       "_" { # Subscript
             set yp 5
             set advance 0
             continue
           }
       "^" { # Superscript
             set yp -3
             set advance 0
             continue
           }
       "~" { # Force space
             set token   " "
             set advance 1
           }
       "`" { # Force extra space
             incr xp     1
             set advance 1
             continue
           }
       "INT" { # Integral
             set xp  0
             set yp  0
             set xtop  -5
             set ytop -12
             set xbot  -8
             set ybot  14
             set advance 1
           }
       "SUM" -
       "PROD" { # Sum & Product
             set xp  0
             set yp  0
             set xtop -20
             set ytop -12
             set xbot -12
             set ybot  16
             set advance 1
           }
       "from" { # Lower bound
             set xp  $xbot
             set yp  $ybot
             set advance 0
             continue
           }
       "to" { # Upper bound
             set xp  $xtop
             set yp  $ytop
             set advance 0
             continue
           }
       default { # Letters
             set advance 1
          }
       }
       lappend result $token $xp $yp $advance
       if { $advance } {
          set xp 0
          set yp 0
       }
    }

    return $result
 }

 # Main --
 #   Set up a canvas, analyse the formula and draw it
 #

 canvas .c -background white -height 400
 pack   .c -fill both

 set string "SUM from i=0 to Inf ~ A _ i ~ = ~ INT from 0 to pi cos ^ 2 x dx"

 ::MathFormula::drawFormula .c 10 20  "Not ~ p ` erfect ~ yet, ~ but ..."
 ::MathFormula::drawFormula .c 10 60  $string
 ::MathFormula::drawFormula .c 10 100 "0.1 = e ^ -kT90"
 ::MathFormula::drawFormula .c 10 140 "f(x) = x ^ 3 - x ^ 2 + x - 1"
 ::MathFormula::drawFormula .c 10 180 "h(t) = cos( omega t - alpha )"
 ::MathFormula::drawFormula .c 10 220 \
    "PARTIAL phi / PARTIAL t = D NABLA ^ 2 phi"
 ::MathFormula::drawFormula .c 10 260 "PROD (1-1/n) ^ n"
 ::MathFormula::drawFormula .c 10 300 "ALL x MEMBER Z ~ EXIST y MEMBER Z ~ x >= y"