User:Luigi.scarso/luatex lunatic

From Wiki
Jump to navigation Jump to search

Introduction


!! W A R N I N G !!


!! THIS CODE IS HIGHLY EXPERIMENTAL !!


!! THIS CODE WORKS ONLY UNDER LINUX !!


!! THIS CODE WORKS ONLY WITH luatex 0.42 !!


!! THIS CODE WORKS ONLY WITH ConTeXt version: 2009.07.17 17:23



luatex_lunatic is a modification of lua side of luatex to host a python interpreter inside lua (see lunatic-python) .

I have made a set of patches because :

  • by design, lua in luatex doesn't permit dynamic loading ("Dynamic loading of .so and .dll files is disabled on all platforms." see "LUA changes" in manual/luatexref-t.pdf of src and/or svn dist.)
  • to prevent symbols collisions between luatex and an arbitrary library

A python interpreter hosted in luatex can make easier to use existing python's bindings; also the ctype modules (included in python releases at from 2.5 version) permits a binding to a .so without using SWIG or similar (almost like loadlib of lua) .

As general rule, I want the smallest set of patches ; so, for example, I have choose to not use a system libpng, even if it's easy to modify build.sh. to do so.

Also, I'm not concerned about portability: I will talk only about Linux .

Please note that luatex_lunatic was born to answer to (my) question "Can I apply lunatic to luatex ?" and nothing else.

Every others meanings can be interestring, but are not mine , and I will not partecipate to any discussion about that.

How to

I prefear to put a bash script that can help to compile a luatex-lunatic binary. It's clear that some skills are needed, so I hope in this way to avoid useless questions . Of course, I will try to correct all errors .

The trick is hide all, then unhide what is needed, then make only what is needed and in the end re-compile luatex .

The changes are :

  • in build.sh.linux add
28a29,36
> export CONFIG_SHELL=/bin/bash
> CFLAGS="-g -O2 -Wno-write-strings -fvisibility=hidden"
> CXXFLAGS="$CFLAGS -fvisibility-inlines-hidden"
> export CFLAGS
> export CXXFLAGS
> 
> 
> 
  • in source/texk/web2c/luatexdir/lua51/loadlib.c
69c69
<   void *lib = dlopen(path, RTLD_NOW);
---
>   void *lib = dlopen(path, RTLD_NOW|RTLD_GLOBAL);
  • in source/texk/web2c/Makefile.in:
98c98
< @MINGW32_FALSE@am__append_14 = -DLUA_USE_POSIX
---
> @MINGW32_FALSE@am__append_14 = -DLUA_USE_LINUX
1674c1674
<       $(CXXLINK) $(luatex_OBJECTS) $(luatex_LDADD) $(LIBS)
---
>       $(CXXLINK) $(luatex_OBJECTS) $(luatex_LDADD) $(LIBS) -Wl,-E -uluaL_openlibs -fvisibility=hidden -fvisibility-inlines-hidden -ldl -lreadline -lhistory -lncurses

So, first compile as usual

$>build.sh

Then use trick.zip" to unhide symbols from liblua51.a and recompile luatex (put it at the same level of build.sh) .

Python packages

These are python packages that are not in standard libraries ; "✔" means that I have made only small lua wrapper .

  • numpy,scipy matplot ✔ here
  • odfpy ✔
  • PIL, python imaging library ✔ here

For these, I still have to decide what todo.

  • TO FIX ; pygegl (I like its syntax, but need too much and it's easy only in python)
  • TODO: binding of libtiff with ctypes (or use gdal ?)
  • TODO :gle (for Massimiliano "Max" Dominici, GUIT)
  • TODO : using the binding of VIPS

Bindings

These are shared lib that come with native python binding, or eventually I have made a wrapper using ctypes .

  • ghostscript 8.64 ✔ here
  • graphviz 2.24.0 ✔ here
  • ImageMagick-6.4.9 with pythonmagickwand ✔
  • fontforge 20090224 ✔ here Useful to check symbols collision, and if one want to play with the last fontforge, eg to draw the outline of a glyph .
  • R-2.8.1 with rpy2-2.0.3 (For Maurizio "Mau" Himmelman , GUIT) ✔ here (see also here) .
  • quantlib 0.9.7 ✔ need an example with output in pdf
  • dbxml-2.4.16 ✔

Dedicated systems

When we have a huge and coordinate set of *so with python binding, it' better to dedicate a specific luatex-lunatic . As example I choose sagemath and ROOT .


SageMath

From sagemath :
Sage is a free open-source mathematics software system licensed under the GPL. It combines the power of many existing open-source packages into a common Python-based interface.
Given that sagemath is rooted on a CPtyhon , we can try to use sagemath as "plugin" for luatex instead of use latex as external process of sagemath.

sagemath comes with own python, and we must use it; in this case I put all stuffs under a separated lunatic-python-SAGEMATH .

HOME_LUN=/opt/luatex/luatex-lunatic
cd $HOME_LUN
cp -r lunatic-python lunatic-python-SAGEMATH
cd lunatic-python-SAGEMATH
##
## We prepare new bridge
##
rm -r build
$HOME_LUN/sage/local/bin/python setup.py build
cd $HOME_LUN
mkdir tests-SAGEMATH
##
## I have already installed prev. python.so, I don't want mess things
##
ln -s ../lunatic-python-SAGEMATH/build/lib.linux-i686-2.5/python.so
ln -s  ../luatex/build/texk/web2c/luatex
##
## now we need to setup sagemath
##
cd $HOME_LUN/sage/local/bin
. sage-env
cd $HOME_LUN

We need also a first run of sagemath:

cd sage
./sage
##
## now exit from sage
##

That is .

Now we need a stub, because usually sage is used as an interactive shell:

## sagestub.py
from sage.all_cmdline import *

OK let's start with an example with mixed code (or, better, 'messed code'):

\startluacode
function test_ode(graphout)
  require("python")
  pg = python.globals()
  SAGESTUB = python.import("sagestub")
  sage = SAGESTUB.sage 
  python.execute([[
def f_1(t,y,params):
   return[y[1],-y[0]-params[0]*y[1]*(y[0]**2-1)]
]])
python.execute([[
def j_1(t,y,params):
    return [ [0,1.0],[-2.0*params[0]*y[0]*y[1]-1.0,-params[0]*(y[0]*y[0]-1.0)], [0,0] ]
]])

  T=sage.gsl.ode.ode_solver()
  T.algorithm="rk8pd"

  f_1 = pg.f_1  
  j_1 = pg.j_1
  pg.T=T

  python.execute("T.function=f_1")
  T.jacobian=j_1
  python.execute("T.ode_solve(y_0=[1,0],t_span=[0,100],params=[10],num_points=1000)")
  python.execute(string.format("T.plot_solution(filename='%s')",graphout ))
end
\stopluacode

\def\TestODE#1{%
\ctxlua{test_ode("#1")}%
\startcombination[2*1]
{%foo
\vbox{\hsize=8cm
Consider solving the Van der pol oscillator 

$x''(t) +ux'(t)(x(t)^2-1)+x(t)=0 $

between $t=0$ and $t= 100$.

As a first order system it is 

$x'=y$

$y'=-x+uy(1-x^2)$
Let us take $u=10$ and use initial conditions $(x,y)=(1,0)$ and use the
\emphsl{\hbox{runga-kutta} \hbox{prince-dormand}}  algorithm.
}%
}{\ss \ }
{\externalfigure[#1][width=9cm]}{\ss Result for 1000 points}
\stopcombination
}

\starttext
\startTEXpage
\TestODE{ode1.pdf}
\stopTEXpage
\stoptext

Test-ode.png


(other examples follows...)

ROOT (CERN)

ROOT -- an Oject-Oriented Data Analysis Framework -- is explained in Wikipedia:
ROOT is an object-oriented program and library developed by CERN. It was originally designed for particle physics data analysis and contains several features specific to this field, but it is also commonly used in other applications such as astronomy and data mining.

For more infos, see here (here for python stuffs).

Under Linux installation is not difficult at all, so in this case I choose to not create a luatex-lunatic apart, as done above for sagemath.
See an example here .

ConTeXt mkIV examples

Here I will collect some tex snippets, just to show some ideas.

Scipy

\startluacode
function testSCIPY(figname,dpi)
  require("python")
  pg = python.globals()
  python.apply = python.eval('apply') or {}
  np = python.import("numpy")
  mlab =  python.import("matplotlib.mlab")
  griddata = mlab.griddata 
  plt = python.import("matplotlib.pyplot")
  ma = np.ma
  random =   python.import("numpy.random")
  uniform = random.uniform

  -- make up some randomly distributed data 
  npts = 200
  x = uniform(-2,2,npts)
  y = uniform(-2,2,npts)
  -- z = x*np.exp(-x**2-y**2)
  z = x.__mul__( np.exp( (x.__pow__(2).__add__(y.__pow__(2))).__neg__() ) )
  --  define grid. 
  xi = np.linspace(-2.1,2.1,100)
  yi = np.linspace(-2.1,2.1,100)
  -- grid the data.
  zi = griddata(x,y,z,xi,yi)
  --  contour the gridded data, plotting dots 
  --  at the randomly spaced data points. 
  --  we put this in python globals space  
  -- CS = plt.contour(xi,yi,zi,15,linewidths=0.5,colors='k')
  pg.xi = xi ; pg.yi = yi ; pg.zi = zi
  args = python.eval("[xi,yi,zi,15]")
  kv = python.eval("{'linewidth': 0.5 ,'colors' :'k'}")
  CS = python.apply(plt.contour, args,kv)
  --
  pg.jet = plt.cm.jet
  args = python.eval("[xi,yi,zi,15]")
  kv = python.eval("{'cmap': jet}")
  CS = python.apply(plt.contourf, args,kv)
  --  draw colorbar 
  plt.colorbar() 
  -- plot data points. 
  pg.x = x; pg.y = y 
  args = python.eval("[x,y]")
  kv = python.eval("{'marker': 'o', 'c':'b','s':5}")
  CS = python.apply(plt.scatter, args,kv)
  plt.xlim(-2,2)
  plt.ylim(-2,2)
  plt.title(string.format('griddata test (%i points)',npts))
  --plt.savefig(figname, dpi, 'white')
  --
  pg.figname = figname ; pg.dpi = dpi
  args = python.eval("[figname]")
  kv = python.eval("{'dpi': dpi ,'facecolor' :'white'}")
  CS = python.apply(plt.savefig, args,kv)
end
\stopluacode



\def\testSCIPY[#1]{%
\getparameters[scipy][#1]%
\ctxlua{testSCIPY("\csname scipyfigname\endcsname","\csname scipydpi\endcsname")}%
\externalfigure[\csname scipyfigname\endcsname]%
}

\starttext
\startTEXpage
\testSCIPY[figname={test-scipy-1.pdf},dpi={150}]
\stopTEXpage
\stoptext

Python Imaging Library (PIL)

\startluacode
function testPIL(imageorig,imagesepia)
  require("python")
  PIL_Image = python.import("PIL.Image") 
  PIL_ImageOps = python.import("PIL.ImageOps") 
  python.execute([[
def make_linear_ramp(white):
    ramp = []
    r, g, b = white
    for i in range(255):
        ramp.extend((r*i/255, g*i/255, b*i/255))
    return ramp
]])
        
    -- make sepia ramp (tweak color as necessary)
    sepia = python.eval("make_linear_ramp((255, 240, 192))") 
    im = PIL_Image.open(imageorig)
    
    -- convert to grayscale
    if not(im.mode == "L")
     then 
        im = im.convert("L")
    end
    
    --  optional: apply contrast enhancement here, e.g.
    im = PIL_ImageOps.autocontrast(im)

    --  apply sepia palette
    im.putpalette(sepia)

    --  convert back to RGB so we can save it as JPEG
    --  (alternatively, save it in PNG or similar)
    im = im.convert("RGB")

    im.save(imagesepia)
end
\stopluacode

\def\SepiaImage#1#2{%
\ctxlua{testPIL("#1","#2")}%
\startcombination[2*1]
{\externalfigure[#1]}{\ss Orig.}
{\externalfigure[#2]}{\ss Sepia}
\stopcombination
}


\starttext
\startTEXpage
\SepiaImage{lena.jpg}{lena-sepia.jpg}
\stopTEXpage
\stoptext
Test-PIL.png

ROOT

This example shot how to literally embed original python source code .

\startluacode
function test_ROOT(filename)
  require("python")
  pg = python.globals()

  python.execute([[
def run(filename):
    from ROOT import TCanvas, TGraph
    from ROOT import gROOT
    from math import sin
    from array import array


    gROOT.Reset()

    c1 = TCanvas( 'c1', 'A Simple Graph Example', 200, 10, 700, 500 )

    c1.SetFillColor( 42 )
    c1.SetGrid()

    n = 20
    x, y = array( 'd' ), array( 'd' )

    for i in range( n ):
          x.append( 0.1*i )
          y.append( 10*sin( x[i]+0.2 ) )

    gr = TGraph( n, x, y )
    gr.SetLineColor( 2 )
    gr.SetLineWidth( 4 )
    gr.SetMarkerColor( 4 )
    gr.SetMarkerStyle( 21 )
    gr.SetTitle( 'a simple graph' )
    gr.GetXaxis().SetTitle( 'X title' )
    gr.GetYaxis().SetTitle( 'Y title' )
    gr.Draw( 'ACP' )
    c1.Update()
    c1.Print(filename)
]])
  run = pg.run
  run(filename)
end
\stopluacode

\starttext
\startTEXpage
\ctxlua{test_ROOT("testsin.pdf")}
\rotate[rotation=90]{\externalfigure[testsin.pdf][width=5cm]}
\stopTEXpage
\stoptext
Testsin.jpg

We can do a bit better: separate python code from lua code .
Save this in test-ROOT1.py (so it's also easy to test) :

from ROOT import TCanvas, TGraph ,TGraphErrors,TMultiGraph
from ROOT import gROOT
from math import sin
from array import array

def run(filename):
    c1 = TCanvas("c1","multigraph",200,10,700,500)
    c1.SetGrid()

    # draw a frame to define the range
    mg = TMultiGraph()
    #   create first graph
    n = 24;
    x = array('d',range(24))
    data = file('data').readlines()
    for line in data:
        line = line.strip()
        y  = array('d',[float(d) for d in line.split()])
        gr =  TGraph(n,x,y)
        gr.Fit("pol6","q")
        mg.Add(gr)

    mg.Draw("ap")

    #force drawing of canvas to generate the fit TPaveStats
    c1.Update()
    c1.Print(filename)

Here file 'data' is a 110 lines file with 24 floats values space separated, ie
20.6000 19.4000 19.4000 18.3000 17.8000 16.1000 16.7000 21.1000 23.3000 26.1000 26.1000 27.2000 27.8000 28.3000 28.3000 27.2000 25.6000 22.8000 21.7000 21.7000 21.7000 21.7000 21.7000 21.7000 .
Now a tex file, with a simple layer in lua as interface for python:

\startluacode
function test_ROOT(filename)
  require("python")
  test = python.import('test-ROOT1')
  test.run(filename)
end
\stopluacode

\starttext
\startTEXpage
\ctxlua{test_ROOT("data.pdf")}
\rotate[rotation=90]{\externalfigure[data.pdf]}
\stopTEXpage
\stoptext
Test-ROOT1.jpg

Fontforge

In this example, we will use Metapost to draw a bezier curve of a glyph (Note: starting from Metapost 1.200 it is now possible to get the actual path drawing routines from a font glyph, so this example is only to show how to translate a path in metapost).
We will use 3-layer approach:

  1. a python layer that export a class,
  2. a lua layer to manage objects of this class
  3. a (con)TeX(t) layer that exports macros, because tex works very well with macros .

Let's start with python code, test-fontforge.py:

import sys
import fontforge


class simpledraw(object):

    def __init__(self,font_file):
        self.font = fontforge.open(font_file)

    def getcurve(self,letter):
        self.glname = letter
        res = dict()
        try :
            glyph_letter = [ g  for g in self.font.glyphs() if g.glyphname == self.glname][0]
        except Exception ,e :
            res['err'] = str(e)
            return res
        cnt= glyph_letter.layers[1][0]
        res['is_quadratic'] = cnt.is_quadratic
        res['closed'] = cnt.closed
        res['points'] = [(p.x,p.y,"%i" %p.on_curve) for p in cnt ]
        res['design_size'] = self.font.design_size
        res['em'] = self.font.em
        return res


    def getmpostoutline(self,letter):
        res = self.getcurve(letter) 
        path = '..'.join( [str((p[0],p[1])) for p in  res['points'] if p[2] == '1'] )
        return path

    def getmpostpoints(self,letter):
        res = self.getcurve(letter) 
        path = [str((p[0],p[1])) for p in  res['points'] if p[2] == '1'] 
        return path

    def getmpostpointsSugar(self,letter):
        res = self.getcurve(letter) 
        path = 'drawdot '.join( ["%s;" %str((p[0],p[1])) for p in  res['points'] if p[2] == '1'] )
        return 'drawdot ' +path



if __name__ == '__main__':
    s = simpledraw("koeieletters.pfb")
    res = s.getmpostpointsSugar('C')
    print res

Note the '__main__' check, so we can test this class from python.
Next lua layer, which in this case is embed in a tex file:

\startluacode
function testFontforge(fontfile,letter)
   require("python")    
   testoutlines = python.import("test-fontforge")
   s = testoutlines.simpledraw(fontfile)
   g = s.getmpostoutline(letter)
   p = s.getmpostpointsSugar(letter)
   tex.sprint(tex.ctxcatcodes,"\\startMPcode")
   tex.sprint(tex.ctxcatcodes,"pickup pencircle scaled 1pt;")
   tex.sprint(tex.ctxcatcodes,string.format("draw %s .. cycle;",g) )
   tex.sprint(tex.ctxcatcodes,"pickup pencircle scaled 8pt;")
   tex.sprint(tex.ctxcatcodes,string.format("%s",p) )
   tex.sprint(tex.ctxcatcodes,"\\stopMPcode")
end
\stopluacode


\def\Outline[#1]{%
\getparameters[test][#1]%
\ctxlua{testFontforge("\testfontfile", "\testletter")}%
}

\starttext
\startTEXpage
\Outline[letter={C}, fontfile={lmmono10-regular.otf}]%
\Outline[letter={o}, fontfile={lmmono10-regular.otf}]%
\Outline[letter={n}, fontfile={lmmono10-regular.otf}]%
\Outline[letter={T}, fontfile={lmmono10-regular.otf}]%
\Outline[letter={e}, fontfile={lmmono10-regular.otf}]%
\Outline[letter={X}, fontfile={lmmono10-regular.otf}]%
\Outline[letter={t}, fontfile={lmmono10-regular.otf}]%
\stopTEXpage
\stoptext

Here we use tex.sprint(tex.ctxcatcodes,"\\stopMPcode") to inject tex code (actually Metapost code) into TeX parser .
\Outline is the TeX layer: of course one can write \Outline and testFontforge in a different manner to avoid use of tex.sprint(..)
And this is the result:
Test-fontforge.png

...ok,it's not correct (why?), but it looks funny :)

Ghostscript

There are essentially 2 kind of use of ghostscript :

  • convert an existing eps / ps file in pdf ;
  • use a program in postscript that take an input, do something and make a ps output ( e.g. a barcode/label generator ).

For the first case, we consider an implementation of eps2pdf, being ps2pdf virtually the same .
Actually there is not a python binding of ghostscript, so we build a simple wrapper using ctypes module.

import ctypes
import sys


class gs(object):

   def __init__(self):

      self.ierrors = dict()
      self.ierrors['e_unknownerror'] = -1       
      self.ierrors['e_dictfull'] = -2
      self.ierrors['e_dictstackoverflow'] = -3
      self.ierrors['e_dictstackunderflow'] = -4
      self.ierrors['e_execstackoverflow'] = -5
      self.ierrors['e_interrupt'] = -6
      self.ierrors['e_invalidaccess'] = -7
      self.ierrors['e_invalidexit'] = -8
      self.ierrors['e_invalidfileaccess'] = -9
      self.ierrors['e_invalidfont'] = -10
      self.ierrors['e_invalidrestore'] = -11
      self.ierrors['e_ioerror'] = -12
      self.ierrors['e_limitcheck'] = -13
      self.ierrors['e_nocurrentpoint'] = -14
      self.ierrors['e_rangecheck'] = -15
      self.ierrors['e_stackoverflow'] = -16
      self.ierrors['e_stackunderflow'] = -17
      self.ierrors['e_syntaxerror'] = -18
      self.ierrors['e_timeout'] = -19
      self.ierrors['e_typecheck'] = -20
      self.ierrors['e_undefined'] = -21
      self.ierrors['e_undefinedfilename'] = -22
      self.ierrors['e_undefinedresult'] = -23
      self.ierrors['e_unmatchedmark'] = -24
      self.ierrors['e_VMerror'] = -25
      self.ierrors['e_configurationerror'] = -26
      self.ierrors['e_undefinedresource'] = -27
      self.ierrors['e_unregistered'] = -28
      self.ierrors['e_invalidcontext'] = -29
      self.ierrors['e_invalidid'] = -30
      self.ierrors['e_Fatal'] = -100
      self.ierrors['e_Quit'] = -101
      self.ierrors['e_InterpreterExit'] = -102
      self.ierrors['e_RemapColor'] = -103
      self.ierrors['e_ExecStackUnderflow'] = -104
      self.ierrors['e_VMreclaim'] = -105
      self.ierrors['e_NeedInput'] = -106
      self.ierrors['e_Info'] = -110

      self.libgspath = '/opt/luatex/luatex-lunatic/lib/libgs.so'

      self.OutFile = ''
      self.InFile = ''

      self.args = []

      
  
   def appendargs(self,arg):
     if arg.find('-sOutputFile')>= 0: return
     if arg.find('-c quit')>= 0: return
     self.args.append(arg)


   def rawappendargs(self,arg):
     self.args.append(arg)



   def run(self):
      libgs = ctypes.CDLL(self.libgspath)

      exit_status   = ctypes.c_int()
      code          = ctypes.c_int(1)
      code1         = ctypes.c_int()
      instance      = ctypes.c_void_p(None)
      exit_code     = ctypes.c_int()


      code.value = libgs.gsapi_new_instance(ctypes.byref(instance), None)
      if code.value  == 0 :
              libgs.gsapi_set_stdio(instance, None, None, None)
              self.args.insert(0,'')
              #
              if len(self.OutFile) > 0:
                 self.args.append('-sOutputFile=%s' %self.OutFile)
              if len(self.InFile) > 0:
                 self.args.append("%s" %self.InFile)
              self.args.append('-c quit')
              arguments = self.args
              #
              argc = ctypes.c_int(len(arguments))
              argv = (ctypes.c_char_p * argc.value)(*arguments)
              code.value = libgs.gsapi_init_with_args(instance, argc, argv)

              code1.value = libgs.gsapi_exit(instance)

              if code.value == 0 or code.value == self.ierrors['e_Quit']:
                  code.value  = code1.value

              if code.value == self.ierrors['e_Quit']:
                  code.value = 0 

              libgs.gsapi_delete_instance(instance)

      exit_status.value = 0

if __name__ == '__main__':
   pyctyps = gs()
   pyctyps.appendargs('-q')
   pyctyps.appendargs('-dNOPAUSE')
   pyctyps.appendargs('-dEPSCrop')
   pyctyps.appendargs('-sDEVICE=pdfwrite')
   pyctyps.InFile = 'test.eps'
   pyctyps.OutFile = 'test.pdf'
   pyctyps.run()

The tex code

\startluacode
function testgs(epsin,pdfout)
  require("python")
  gsmodule = python.import("testgs") 
  ghost = gsmodule.gs()
  ghost.appendargs('-q')
  ghost.appendargs('-dNOPAUSE')
  ghost.appendargs('-dEPSCrop')
  ghost.appendargs('-sDEVICE=pdfwrite')
  ghost.InFile = epsin
  ghost.OutFile = pdfout
  ghost.run()
end
\stopluacode

\def\epstopdf#1#2{\ctxlua{testgs("#1","#2")}}
\def\EPSfigure[#1]{%lazy way to load eps
\epstopdf{#1.eps}{#1.pdf}%
\externalfigure[#1.pdf]%
}

\starttext
\startTEXpage
\startcombination[2*1]
{\EPSfigure[tiger]}{\ss tiger.eps}
{\EPSfigure[golfer]}{\ss golfer.eps}
\stopcombination
\stopTEXpage
\stoptext

and the result :
Testgs.png

Another example:
here we use a library to generate barcodes (see here) .

\startluacode
function epstopdf(epsin,pdfout)
  require("python")
  gsmodule = python.import("testgs") 
  ghost = gsmodule.gs()
  ghost.appendargs('-q')
  ghost.appendargs('-dNOPAUSE')
  ghost.appendargs('-dEPSCrop')
  ghost.appendargs('-sDEVICE=pdfwrite')
  ghost.InFile = epsin
  ghost.OutFile = pdfout
  ghost.run()
end

function barcode(text,type,options,savefile)
  require("python")
  gsmodule = python.import("testgs") 

  barcode_string = string.format("%%!\n100 100 moveto (%s) (%s) %s barcode showpage" ,text,options,type)

  psfile = string.format("%s.ps",savefile)
  epsfile = string.format("%s.eps",savefile)
  pdffile = string.format("%s.pdf",savefile)

  temp = io.open(psfile,'w')
  temp:write(tostring(barcode_string),"\n")
  temp:flush()
  io.close(temp)
  ghost = gsmodule.gs()
  ghost.rawappendargs('-q')
  ghost.rawappendargs('-dNOPAUSE')
  ghost.rawappendargs('-sDEVICE=epswrite')
  ghost.rawappendargs(string.format('-sOutputFile=%s',epsfile))
  ghost.rawappendargs('barcode.ps')
  ghost.InFile= psfile
  ghost.run()
end
\stopluacode

\def\epstopdf#1#2{\ctxlua{epstopdf("#1","#2")}}
\def\EPSfigure[#1]{%lazy way to load eps
\epstopdf{#1.eps}{#1.pdf}%
\externalfigure[#1.pdf]%
}

\def\PutBarcode[#1]{%
\getparameters[bc][#1]%
\ctxlua{barcode("\csname bctext\endcsname","\csname bctype\endcsname","\csname bcoptions\endcsname","\csname bcsavefile\endcsname" )}%
\expanded{\EPSfigure[\csname bcsavefile\endcsname]}%
}

\starttext
\startTEXpage
\startcombination[2*2]
{\PutBarcode[text={CODE 39},type={code39},options={includecheck includetext},savefile={TEMP1}]}{\ss code39}
{\PutBarcode[text={CONTEXT},type={code93},options={includecheck includetext},savefile={TEMP2}]}{\ss code93}
{\PutBarcode[text={977147396801},type={ean13},options={includetext},savefile={TEMP3}]}{\ss ean13}
{\PutBarcode[text={0123456789},type={interleaved2of5},options={includecheck includetext},savefile={TEMP4}]}{\ss interleaved2of5}
\stopcombination
\stopTEXpage
\stoptext

Test-ghostscript-barcode.png

Graphviz

Graphviz is a Graph Visualization Software .
Standard distribution comes with several binding (lua and python among others) so it's not difficult to integrate in luatex lunatic .
In this example, we draw a graph of the nodes of

\def\StudyBox#1{%
\startluacode
require "python"
gv = python.import("gv")
g = gv.digraph("G")
gv.setv(g,'rankdir','LR')

nodes = nodes or {}

local nd = {};
local kd = {};

local k = 0;
local function nodesprint(head,n)
  while head do
    local id = head.id
    local oldn = n
    ndlbl = string.format("nd_\%03d",k)
    texio.write_nl(string.format("id=\%s, ndlbl=\%s",id,ndlbl))

    if id == node.id("vlist") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)
     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. 
                          "vlist" .. "\|id:" .. tostring(head.id) .. 
                          "\|subtype:" .. tostring(head.subtype) .. 
                          "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
                          "\|width:" .. tostring(head.width) .. 
                          "\|depth:" .. tostring(head.depth) .. "\|height:" .. tostring(head.height) .. 
                          "\|dir:" .. tostring(head.dir) .. "\|shift:" .. tostring(head.shift) .. 
                          "\|glue_order:" .. tostring(head.glue_order) .. 
                          "\|glue_sign:" .. tostring(head.glue_sign) .. 
                          "\|glue_set:" .. tostring(head.glue_set) .. 
                          "\|list:" .. string.gsub(tostring(head.list),"([><])","\\\%1") .. 
                          "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
                          "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end

    if id == node.id("rule") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)
     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "rule" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|width:" .. tostring(head.width) .. 
             "\|depth:" .. tostring(head.depth) .. 
             "\|height:" .. tostring(head.height) .. 
             "\|dir:" .. tostring(head.dir) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("ins") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)
     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "ins" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|cost:" .. tostring(head.cost) .. 
             "\|depth:" .. tostring(head.depth) .. 
             "\|height:" .. tostring(head.height) .. 
             "\|spec:" .. string.gsub(tostring(head.spec),"([><])","\\\%1") .. 
             "\|list:" .. string.gsub(tostring(head.list),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("mark") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)
     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "mark" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|class:" .. tostring(head.class) .. 
             "\|mark:" .. tostring(head.mark) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("adjust") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "adjust" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|list:" .. string.gsub(tostring(head.list),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("disc") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "disc" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|pre:" .. string.gsub(tostring(head.pre),"([><])","\\\%1") .. 
             "\|post:" .. string.gsub(tostring(head.post),"([><])","\\\%1") .. 
             "\|replace:" .. string.gsub(tostring(head.replace),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("whatsit") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     if head.subtype == node.subtype("write") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:write" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|stream:" .. tostring(head.stream) .. 
             "\|data:" .. tostring(head.data) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("close") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:close" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|stream:" .. tostring(head.stream) .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("special") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:special" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|data:" .. tostring(head.data) .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("local_par") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:local_par" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|pen_inter:" .. tostring(head.pen_inter) .. 
             "\|pen_broken:" .. tostring(head.pen_broken) .. 
             "\|dir:" .. tostring(head.dir) .. 
             "\|box_left:" .. tostring(head.box_left) .. 
             "\|box_left_width:" .. tostring(head.box_left_width) .. 
             "\|box_right:" .. tostring(head.box_right) .. 
             "\|box_right_width:" .. tostring(head.box_right_width) .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("dir") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:dir" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|dir:" .. tostring(head.dir) .. 
             "\|level:" .. tostring(head.level) .. 
             "\|dvi_ptr:" .. tostring(head.dvi_ptr) .. 
             "\|dvi_h:" .. tostring(head.dvi_h) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("pdf_literal") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:pdf_literal" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|mode:" .. tostring(head.mode) .. 
             "\|data:" .. tostring(head.data) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("pdf_refobj") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:pdf_refobj" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|objnum:" .. tostring(head.objnum) .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("pdf_refxform") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:pdf_refxform" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|width:" .. tostring(head.width) .. 
             "\|height:" .. tostring(head.height) .. 
             "\|depth:" .. tostring(head.depth) .. 
             "\|objnum:" .. tostring(head.objnum) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("pdf_refximage") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:pdf_refximage" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|width:" .. tostring(head.width) .. 
             "\|height:" .. tostring(head.height) .. 
             "\|depth:" .. tostring(head.depth) .. 
             "\|objnum:" .. tostring(head.objnum) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("pdf_annot") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:pdf_annot" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|width:" .. tostring(head.width) .. 
             "\|height:" .. tostring(head.height) .. 
             "\|depth:" .. tostring(head.depth) .. 
             "\|objnum:" .. tostring(head.objnum) .. 
             "\|data:" .. tostring(head.data) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("pdf_start_link") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:pdf_start_link" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|width:" .. tostring(head.width) .. 
             "\|height:" .. tostring(head.height) .. 
             "\|depth:" .. tostring(head.depth) .. 
             "\|objnum:" .. tostring(head.objnum) .. 
             "\|link_attr:" .. tostring(head.link_attr) .. 
             "\|action:" .. tostring(head.action) .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("pdf_end_link") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:pdf_end_link" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("pdf_dest") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:pdf_dest" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|width:" .. tostring(head.width) .. 
             "\|height:" .. tostring(head.height) .. 
             "\|depth:" .. tostring(head.depth) .. 
             "\|named_id:" .. tostring(head.named_id) .. 
             "\|dest_id:" .. tostring(head.dest_id) .. 
             "\|dest_type:" .. tostring(head.dest_type) .. 
             "\|xyz_zoom:" .. tostring(head.xyz_zoom) .. 
             "\|objnum:" .. tostring(head.objnum) .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("pdf_thread") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:pdf_thread" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|width:" .. tostring(head.width) .. 
             "\|height:" .. tostring(head.height) .. 
             "\|depth:" .. tostring(head.depth) .. 
             "\|named_id:" .. tostring(head.named_id) .. 
             "\|thread_id:" .. tostring(head.thread_id) .. 
             "\|thread_attr:" .. tostring(head.thread_attr) .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("pdf_start_thread") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:pdf_start_thread" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|width:" .. tostring(head.width) .. 
             "\|height:" .. tostring(head.height) .. 
             "\|depth:" .. tostring(head.depth) .. 
             "\|named_id:" .. tostring(head.named_id) .. 
             "\|thread_id:" .. tostring(head.thread_id) .. 
             "\|thread_attr:" .. tostring(head.thread_attr) .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("pdf_end_thread") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:pdf_end_thread" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("pdf_save_pos") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:pdf_save_pos" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("pdf_thread_data") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:pdf_thread_data" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("pdf_link_data") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:pdf_link_data" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("open") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:open" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|stream:" .. tostring(head.stream) .. 
             "\|name:" .. tostring(head.name) .. 
             "\|area:" .. tostring(head.area) .. 
             "\|ext:" .. tostring(head.ext) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("late_lua") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:late_lua" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|reg:" .. tostring(head.reg) .. 
             "\|data:" .. tostring(head.data) .. 
             "\|name:" .. tostring(head.name) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("fake") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:fake" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("pdf_colorstack") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:pdf_colorstack" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|stack:" .. tostring(head.stack) .. 
             "\|cmd:" .. tostring(head.cmd) .. 
             "\|data:" .. tostring(head.data) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("pdf_save") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:pdf_save" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("cancel_boundary") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:cancel_boundary" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("close_lua") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:close_lua" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|reg:" .. tostring(head.reg) .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("pdf_setmatrix") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:pdf_setmatrix" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|data:" .. tostring(head.data) .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("pdf_restore") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:pdf_restore" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
     end
     if head.subtype == node.subtype("user_defined") then
       res = gv.setv(nd[n],"label",tostring(k) .. " " .."whatsit:user_defined" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|user_id:" .. tostring(head.user_id) .. 
             "\|type:" .. tostring(head.type) .. 
             "\|value:" .. tostring(head.value) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
     end
    end
    if id == node.id("math") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "math" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|surround:" .. tostring(head.surround) .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("glue") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "glue" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|spec:" .. string.gsub(tostring(head.spec),"([><])","\\\%1") .. 
             "\|leader:" .. tostring(head.leader) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("kern") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "kern" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|kern:" .. tostring(head.kern) .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("penalty") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "penalty" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|penalty:" .. tostring(head.penalty) .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("unset") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "unset" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|width:" .. tostring(head.width) .. 
             "\|depth:" .. tostring(head.depth) .. 
             "\|height:" .. tostring(head.height) .. 
             "\|dir:" .. tostring(head.dir) .. 
             "\|shrink:" .. tostring(head.shrink) .. 
             "\|glue_order:" .. tostring(head.glue_order) .. 
             "\|glue_sign:" .. tostring(head.glue_sign) .. 
             "\|stretch:" .. tostring(head.stretch) .. 
             "\|span:" .. tostring(head.span) .. 
             "\|list:" .. string.gsub(tostring(head.list),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("style") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "style" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|style:" .. tostring(head.style) .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("choice") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "choice" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|display:" .. tostring(head.display) .. 
             "\|text:" .. tostring(head.text) .. 
             "\|script:" .. tostring(head.script) .. 
             "\|scriptscript:" .. tostring(head.scriptscript) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("noad") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "noad" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|nucleus:" .. tostring(head.nucleus) .. 
             "\|sub:" .. tostring(head.sub) .. 
             "\|sup:" .. tostring(head.sup) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("op") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "op" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|nucleus:" .. tostring(head.nucleus) .. 
             "\|sub:" .. tostring(head.sub) .. 
             "\|sup:" .. tostring(head.sup) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("bin") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "bin" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|nucleus:" .. tostring(head.nucleus) .. 
             "\|sub:" .. tostring(head.sub) .. 
             "\|sup:" .. tostring(head.sup) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("rel") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "rel" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|nucleus:" .. tostring(head.nucleus) .. 
             "\|sub:" .. tostring(head.sub) .. 
             "\|sup:" .. tostring(head.sup) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("open") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "open" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|nucleus:" .. tostring(head.nucleus) .. 
             "\|sub:" .. tostring(head.sub) .. 
             "\|sup:" .. tostring(head.sup) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("close") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "close" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|nucleus:" .. tostring(head.nucleus) .. 
             "\|sub:" .. tostring(head.sub) .. 
             "\|sup:" .. tostring(head.sup) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("punct") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "punct" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|nucleus:" .. tostring(head.nucleus) .. 
             "\|sub:" .. tostring(head.sub) .. 
             "\|sup:" .. tostring(head.sup) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("inner") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "inner" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|nucleus:" .. tostring(head.nucleus) .. 
             "\|sub:" .. tostring(head.sub) .. 
             "\|sup:" .. tostring(head.sup) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("radical") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "radical" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|nucleus:" .. tostring(head.nucleus) .. 
             "\|sub:" .. tostring(head.sub) .. 
             "\|sup:" .. tostring(head.sup) .. 
             "\|left:" .. tostring(head.left) .. 
             "\|degree:" .. tostring(head.degree) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("fraction") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "fraction" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|width:" .. tostring(head.width) .. 
             "\|num:" .. tostring(head.num) .. 
             "\|denom:" .. tostring(head.denom) .. 
             "\|left:" .. tostring(head.left) .. 
             "\|right:" .. tostring(head.right) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("under") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "under" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|nucleus:" .. tostring(head.nucleus) .. 
             "\|sub:" .. tostring(head.sub) .. 
             "\|sup:" .. tostring(head.sup) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("over") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "over" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|nucleus:" .. tostring(head.nucleus) .. 
             "\|sub:" .. tostring(head.sub) .. 
             "\|sup:" .. tostring(head.sup) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("accent") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "accent" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|nucleus:" .. tostring(head.nucleus) .. 
             "\|sub:" .. tostring(head.sub) .. 
             "\|sup:" .. tostring(head.sup) .. 
             "\|accent:" .. tostring(head.accent) .. 
             "\|bot_accent:" .. tostring(head.bot_accent) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("vcenter") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "vcenter" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|nucleus:" .. tostring(head.nucleus) .. 
             "\|sub:" .. tostring(head.sub) .. 
             "\|sup:" .. tostring(head.sup) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("fence") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "fence" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|delim:" .. tostring(head.delim) .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("math_char") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "math_char" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|fam:" .. tostring(head.fam) .. 
             "\|char:" .. tostring(head.char) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("sub_box") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "sub_box" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|list:" .. string.gsub(tostring(head.list),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("sub_mlist") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "sub_mlist" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|list:" .. string.gsub(tostring(head.list),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("math_text_char") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "math_text_char" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|fam:" .. tostring(head.fam) .. 
             "\|char:" .. tostring(head.char) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("delim") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "delim" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|small_fam:" .. tostring(head.small_fam) .. 
             "\|small_char:" .. tostring(head.small_char) .. 
             "\|large_fam:" .. tostring(head.large_fam) .. 
             "\|large_char:" .. tostring(head.large_char) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("margin_kern") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "margin_kern" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|width:" .. tostring(head.width) .. 
             "\|glyph:" .. tostring(head.glyph) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("glyph") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "glyph" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|char:" .. tostring(head.char) .. 
             "\|font:" .. tostring(head.font) .. 
             "\|lang:" .. tostring(head.lang) .. 
             "\|left:" .. tostring(head.left) .. 
             "\|right:" .. tostring(head.right) .. 
             "\|uchyph:" .. tostring(head.uchyph) .. 
             "\|components:" .. string.gsub(tostring(head.components),"([><])","\\\%1") .. 
             "\|xoffset:" .. tostring(head.xoffset) .. 
             "\|yoffset:" .. tostring(head.yoffset) .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("align_record") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "align_record" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("pseudo_file") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "pseudo_file" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("pseudo_line") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "pseudo_line" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("page_insert") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "page_insert" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|height:" .. tostring(head.height) .. 
             "\|last_ins_ptr:" .. tostring(head.last_ins_ptr) .. 
             "\|best_ins_ptr:" .. tostring(head.best_ins_ptr) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("split_insert") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "split_insert" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|height:" .. tostring(head.height) .. 
             "\|last_ins_ptr:" .. tostring(head.last_ins_ptr) .. 
             "\|best_ins_ptr:" .. tostring(head.best_ins_ptr) .. 
             "\|broken_ptr:" .. tostring(head.broken_ptr) .. 
             "\|broken_ins:" .. tostring(head.broken_ins) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("expr_stack") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "expr_stack" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("nested_list") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "nested_list" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("span") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "span" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("attribute") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "attribute" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|number:" .. tostring(head.number) .. 
             "\|value:" .. tostring(head.value) .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("glue_spec") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "glue_spec" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|width:" .. tostring(head.width) .. 
             "\|stretch:" .. tostring(head.stretch) .. 
             "\|shrink:" .. tostring(head.shrink) .. 
             "\|stretch_order:" .. tostring(head.stretch_order) .. 
             "\|shrink_order:" .. tostring(head.shrink_order) .. 
             "\|ref_count:" .. tostring(head.ref_count) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("attribute_list") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "attribute_list" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1"))
    end
    if id == node.id("action") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "action" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|action_type:" .. tostring(head.action_type) .. 
             "\|named_id:" .. tostring(head.named_id) .. 
             "\|action_id:" .. tostring(head.action_id) .. 
             "\|file:" .. tostring(head.file) .. 
             "\|new_window:" .. tostring(head.new_window) .. 
             "\|data:" .. tostring(head.data) .. 
             "\|ref_count:" .. tostring(head.ref_count) .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("temp") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "temp" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("align_stack") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "align_stack" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("movement_stack") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "movement_stack" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("if_stack") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "if_stack" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("unhyphenated") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "unhyphenated" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("hyphenated") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "hyphenated" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("delta") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "delta" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("passive") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "passive" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("shape") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "shape" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("hlist") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "hlist" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|attr:" .. string.gsub(tostring(head.attr),"([><])","\\\%1") .. 
             "\|width:" .. tostring(head.width) .. 
             "\|depth:" .. tostring(head.depth) .. 
             "\|height:" .. tostring(head.height) .. 
             "\|dir:" .. tostring(head.dir) .. 
             "\|shift:" .. tostring(head.shift) .. 
             "\|glue_order:" .. tostring(head.glue_order) .. 
             "\|glue_sign:" .. tostring(head.glue_sign) .. 
             "\|glue_set:" .. tostring(head.glue_set) .. 
             "\|list:" .. string.gsub(tostring(head.list),"([><])","\\\%1") .. 
             "\|prev:" .. string.gsub(tostring(head.prev),"([><])","\\\%1") .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
    if id == node.id("fake") then
     k = k + 1
     nd[n] = gv.node(g,ndlbl)
     res = gv.setv(nd[n],"shape","record")
     res = gv.setv(nd[n],"label",tostring(k) .. " " .. "fake" .. 
             "\|id:" .. tostring(head.id) .. 
             "\|subtype:" .. tostring(head.subtype) .. 
             "\|next:" .. string.gsub(tostring(head.next),"([><])","\\\%1"))
    end
        kd[k] = nd[n] 
        e1 = gv.edge(kd[k-1],kd[k])
        if id == node.id('hlist')  or id == node.id('vlist') then
           %% If we want to connect nested (h|v)list
           %% 
           %%e = gv.edge(nd[n-1],nd[n])
           %%gv.setv(e,'arrowhead','diamond')
           nodesprint(head.list,( n or 0) +1)
        end
        head = head.next
    end
end

local head  = tex.box[#1]
nodesprint(head,0)
r = gv.layout(g,"dot")
r = gv.render(g,'pdf',string.format('box\%d.pdf',#1))
\stopluacode%%
}

\starttext
\startTEXpage
\setbox0=\hbox{\TeX}
\hbox to 29cm{\strut\hss\copy0\hss}
\StudyBox{0}%
\externalfigure[box0.pdf][width=29cm]
\stopTEXpage
\stoptext

Graphviz.png

R

R is a language and environment for statistical computing and graphics (see here) .
RPy is a very simple, yet robust, Python interface to the R Programming Language (see here) .
As example, let's try to plot a discrete distribution of probability for a set of pseudorandom number (around 100000 samples) .

import rpy2.robjects as robjects
import rpy2.rinterface as rinterface

class density(object):

   def __init__(self,samples,outpdf,w,h,kernel):
     self.samples = samples
     self.outpdf= outpdf
     self.kernel = kernel
     self.width=w
     self.height=h

   def run(self):
        r = robjects.r
        data = [int(k.strip()) for k in file(self.samples,'r').readlines()]
        x = robjects.IntVector(data)
        r.pdf(file=self.outpdf,width=self.width,height=self.height)
        z = r.density(x,kernel=self.kernel)
        #r.plot(z[0],z[1],xlab='',ylab='',xlim=robjects.IntVector([0,2**16-1]))
        r.plot(z[0],z[1],xlab='',ylab='')
        r['dev.off']()


if __name__ == '__main__' :
   dens = density('u-random-int','test-001.pdf',10,7,'o')
   dens.run()


\startluacode
function testR(samples,outpdf,w,h,kernel)
  require("python")
  testR  = python.import("test-R") 
  dens = testR.density(samples,outpdf,w,h,kernel)
  dens.run()
end
\stopluacode

\def\plotdenstiy[#1]{%
\getparameters[R][#1]%
\expanded{\ctxlua{testR("\Rsamples","\Routpdf",\Rwidth,\Rheight,"\Rkernel")}}%
}

\setupbodyfont[sans,10pt]
\starttext
\startTEXpage
\plotdenstiy[samples={u-random-int},outpdf={test-001.pdf},width={10},height={7},kernel={o}]
\setupcombinations[location=top]
\startcombination[1*2]
{\vbox{\hsize=400bp
This is a density plot of around {\tt 100 000} random numbers  between $0$ and $2^{16}-1$ generated from {\tt \hbox{/dev/urandom}}}}{}
{\externalfigure[test-001.pdf][width={400bp}]}{}
\stopcombination
\stopTEXpage
\stoptext

And here is the plot

Test-R.png