diff toolboxes/graph_visualisation/share/graphviz/lefty/dotty.lefty @ 0:e9a9cd732c1e tip

first hg version after svn
author wolffd
date Tue, 10 Feb 2015 15:05:51 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toolboxes/graph_visualisation/share/graphviz/lefty/dotty.lefty	Tue Feb 10 15:05:51 2015 +0000
@@ -0,0 +1,748 @@
+#
+# DOTTY
+#
+dotty = [
+    'keys' = [
+        'nid'    = 'nid';
+        'eid'    = 'eid';
+        'gid'    = 'gid';
+        'name'   = 'name';
+        'attr'   = 'attr';
+        'gattr'  = 'graphattr';
+        'eattr'  = 'edgeattr';
+        'nattr'  = 'nodeattr';
+        'edges'  = 'edges';
+        'tail'   = 'tail';
+        'tport'  = 'tport';
+        'head'   = 'head';
+        'hport'  = 'hport';
+        'pos'    = 'pos';
+        'size'   = 'size';
+        'rect'   = 'rect';
+        'fname'  = 'fontname';
+        'fsize'  = 'fontsize';
+        'fcolor' = 'fontcolor';
+        'dcolor' = 'drawcolor';
+        'bcolor' = 'fillcolor';
+    ];
+    'maps' = [
+        'X11' = [
+            'fontmap' = [
+                'Times-Roman'    = '-*-times-medium-r-*--%d-*-*-*-*-*-*-1';
+                'Times-Italic'   = '-*-times-medium-i-*--%d-*-*-*-*-*-*-1';
+                'Times-Bold'     = '-*-times-bold-r-*--%d-*-*-*-*-*-*-1';
+                'Courier'        = '-*-courier-bold-r-*--%d-*-*-*-*-*-*-1';
+                'Courier-Bold'   = '-*-courier-bold-r-*--%d-*-*-*-*-*-*-1';
+                'Helvetica'      = (
+                     '-*-helvetica-medium-r-normal--%d-*-*-*-p-*-iso8859-1'
+                );
+                'Helvetica-Bold' = (
+                    '-*-helvetica-bold-r-normal--%d-*-*-*-p-*-iso8859-1'
+                );
+            ];
+            'psfontmap' = [
+                'Times-Roman'    = 'Times-Roman';
+                'Times-Italic'   = 'Times-Italic';
+                'Times-Bold'     = 'Times-Bold';
+                'Courier'        = 'Courier';
+                'Courier-Bold'   = 'Courier-Bold';
+                'Helvetica'      = 'Helvetica';
+                'Helvetica-Bold' = 'Helvetica-Bold';
+            ];
+        ];
+        'mswin' = [
+            'fontmap' = [
+                'Times-Roman'    = 'Times New Roman';
+                'Times-Italic'   = 'Times New Roman Italic';
+                'Times-Bold'     = 'Times New Roman Bold';
+                'Courier'        = 'Courier New';
+                'Courier-Bold'   = 'Courier New Bold';
+                'Helvetica'      = 'Arial';
+                'Helvetica-Bold' = 'Arial Bold';
+            ];
+            'psfontmap' = [
+                'Times-Roman'    = 'Times New Roman';
+                'Times-Italic'   = 'Times New Roman Italic';
+                'Times-Bold'     = 'Times New Roman Bold';
+                'Courier'        = 'Courier New';
+                'Courier-Bold'   = 'Courier New Bold';
+                'Helvetica'      = 'Arial';
+                'Helvetica-Bold' = 'Arial Bold';
+            ];
+        ];
+    ];
+    'protogt' = [
+        'graph' = [
+            'graphattr' = [
+                'fontsize' = '14';
+                'fontname' = 'Times-Roman';
+                'fontcolor' = 'black';
+            ];
+            'nodeattr' = [
+                'shape' = 'ellipse';
+                'fontsize' = '14';
+                'fontname' = 'Times-Roman';
+                'fontcolor' = 'black';
+                'style' = 'solid';
+            ];
+            'edgeattr' = [
+                'fontsize' = '14';
+                'fontname' = 'Times-Roman';
+                'fontcolor' = 'black';
+                'style' = 'solid';
+            ];
+            'graphdict' = [];
+            'nodedict' = [];
+            'graphs' = [];
+            'nodes' = [];
+            'edges' = [];
+            'maxgid' = 0;
+            'maxnid' = 0;
+            'maxeid' = 0;
+            'type' = 'digraph';
+        ];
+        'layoutmode' = 'sync';
+        'lserver' = 'dot';
+        'edgehandles' = 1;
+        'noundo' = 0;
+    ];
+    'lservers' = [];
+    'mlevel' = 0;
+    'graphs' = [];
+    'views' = [];
+    'protovt' = [
+        'normal' = [
+            'name' = 'DOTTY';
+            'orig' = ['x' = 1; 'y' = 1;];
+            'size' = ['x' = 420; 'y' = 520;];
+            'wrect' = [
+                0 = ['x' = 0; 'y' = 0;];
+                1 = ['x' = 400; 'y' = 500;];
+            ];
+            'vsize' = ['x' = 400; 'y' = 500;];
+            'w2v' = 1;
+        ];
+        'birdseye' = [
+            'type' = 'birdseye';
+            'name' = 'DOTTY birdseye view';
+            'orig' = ['x' = 1; 'y' = 1;];
+            'size' = ['x' = 220; 'y' = 260;];
+            'wrect' = [
+                0 = ['x' = 0; 'y' = 0;];
+                1 = ['x' = 200; 'y' = 250;];
+            ];
+            'vsize' = ['x' = 200; 'y' = 250;];
+            'w2v' = 1;
+        ];
+    ];
+    'pagesizes' = [
+        '8.5x11' = ['x' =    8; 'y' = 10.5;];
+        '11x17'  = ['x' = 10.5; 'y' = 16.5;];
+        '36x50'  = ['x' = 35.5; 'y' = 49.5;];
+    ];
+];
+load ('dotty_draw.lefty');
+load ('dotty_edit.lefty');
+load ('dotty_layout.lefty');
+load ('dotty_ui.lefty');
+#
+# initialization functions
+#
+dotty.init = function () {
+    dotty.fontmap = dotty.maps[getenv ('LEFTYWINSYS')].fontmap;
+    dotty.clipgt = dotty.protogt.creategraph (['noundo' = 1;]);
+    dotty.inited = 1;
+};
+dotty.simple = function (file) {
+    if (dotty.inited ~= 1)
+        dotty.init ();
+    dotty.createviewandgraph (file, 'file', null, null);
+    txtview ('off');
+};
+#
+# main operations
+#
+dotty.protogt.creategraph = function (protogt) {
+    local gt, id, gtid;
+
+    if (~protogt)
+        protogt = dotty.protogt;
+    for (gtid = 0; dotty.graphs[gtid]; gtid = gtid + 1)
+        ;
+    gt = (dotty.graphs[gtid] = []);
+    if (protogt.mode ~= 'replace') {
+        for (id in dotty.protogt)
+            gt[id] = copy (dotty.protogt[id]);
+    }
+    for (id in protogt)
+        gt[id] = copy (protogt[id]);
+    gt.gtid = gtid;
+    gt.views = [];
+    gt.undoarray = ['level' = 0; 'entries' = [];];
+    gt.busy = 0;
+    return gt;
+};
+dotty.protogt.copygraph = function (ogt) {
+    local gt, gtid, id;
+
+    for (gtid = 0; dotty.graphs[gtid]; gtid = gtid + 1)
+        ;
+    gt = (dotty.graphs[gtid] = []);
+    for (id in ogt)
+        gt[id] = copy (ogt[id]);
+    gt.gtid = gtid;
+    gt.views = [];
+    gt.undoarray = ['level' = 0; 'entries' = [];];
+    gt.busy = 0;
+    return gt;
+};
+dotty.protogt.destroygraph = function (gt) {
+    local vid, vlist;
+
+    if (gt.layoutpending > 0)
+        gt.cancellayout (gt);
+    for (vid in gt.views)
+        vlist[vid] = gt.views[vid];
+    for (vid in gt.views)
+        gt.destroyview (gt, vlist[vid]);
+    remove (gt.gtid, dotty.graphs);
+};
+dotty.protogt.loadgraph = function (gt, name, type, protograph, layoutflag) {
+    local fd, vid, vt, graph, nid, eid, gid;
+
+    if (gt.layoutpending > 0)
+        gt.cancellayout (gt);
+    if (~name)
+        if (~(name = ask ('file name:', 'file', '')))
+            return;
+    dotty.pushbusy (gt, gt.views);
+    dotty.message (1, 'loading');
+    if (~protograph)
+        protograph = dotty.protogt.graph;
+    if (
+        ~((fd = dotty.openio (name, type, 'r')) >= 0) |
+        ~(graph = readgraph (fd, protograph))
+    ) {
+        dotty.message (0, 'cannot load graph');
+        dotty.popbusy (gt, gt.views);
+        return;
+    }
+    for (vid in gt.views) {
+        vt = gt.views[vid];
+        vt.colors = [];
+        vt.colorn = 2;
+    }
+    gt.graph = graph;
+    gt.name = name;
+    gt.type = type;
+    gt.undoarray = ['level' = 0; 'entries' = [];];
+    if (~(type == 'file' & name == '-'))
+        closeio (fd);
+    graph.maxgid = tablesize (graph.graphs);
+    graph.maxnid = tablesize (graph.nodes);
+    graph.maxeid = tablesize (graph.edges);
+    for (nid in graph.nodes)
+        graph.nodes[nid][dotty.keys.nid] = nid;
+    for (eid in graph.edges)
+        graph.edges[eid][dotty.keys.eid] = eid;
+    for (gid in graph.graphs)
+        graph.graphs[gid][dotty.keys.gid] = gid;
+    gt.unpackattr (gt);
+    if (layoutflag) {
+        dotty.message (1, 'generating layout');
+        gt.layoutgraph (gt);
+    }
+    dotty.popbusy (gt, gt.views);
+    return gt.graph;
+};
+dotty.protogt.savegraph = function (gt, name, type) {
+    local fd;
+
+    if (~name)
+        if (~(name = ask ('file name:', 'file', '')))
+            return;
+    if (
+        ~((fd = dotty.openio (name, type, 'w')) >= 0) |
+        ~writegraph (fd, gt.graph, 0)
+    ) {
+        dotty.message (0, 'cannot save graph');
+        return;
+    }
+    if (~(type == 'file' & name == '-'))
+        closeio (fd);
+};
+dotty.protogt.setgraph = function (gt, graph) {
+    local vid, vt, nid, eid, gid;
+
+    if (gt.layoutpending > 0)
+        gt.cancellayout (gt);
+    for (vid in gt.views) {
+        vt = gt.views[vid];
+        vt.colors = [];
+        vt.colorn = 2;
+    }
+    gt.graph = copy (graph);
+    gt.undoarray = ['level' = 0; 'entries' = [];];
+    gt.unpackattr (gt);
+    gt.graph.maxgid = tablesize (graph.graphs);
+    gt.graph.maxnid = tablesize (graph.nodes);
+    gt.graph.maxeid = tablesize (graph.edges);
+    for (nid in gt.graph.nodes)
+        gt.graph.nodes[nid][dotty.keys.nid] = nid;
+    for (eid in gt.graph.edges)
+        gt.graph.edges[eid][dotty.keys.eid] = eid;
+    for (gid in gt.graph.graphs)
+        gt.graph.graphs[gid][dotty.keys.gid] = gid;
+    gt.unpackattr (gt);
+    dotty.message (1, 'generating layout');
+    gt.layoutgraph (gt);
+    return gt.graph;
+};
+dotty.protogt.erasegraph = function (gt, protogt, protovt) {
+    local vid, vt;
+
+    if (gt.layoutpending > 0)
+        gt.cancellayout (gt);
+    for (vid in gt.views) {
+        vt = gt.views[vid];
+        vt.colors = [];
+        vt.colorn = 2;
+        clear (vt.canvas);
+    }
+    if (~protogt)
+        protogt = dotty.protogt;
+    gt.graph = copy (protogt.graph);
+    gt.undoarray = ['level' = 0; 'entries' = [];];
+};
+dotty.protogt.layoutgraph = function (gt) {
+    if (gt.graph.graphattr.xdotversion) {
+        gt.unpacklayout (gt, gt.graph);
+        gt.setviewsize (gt.views, gt.graph.rect);
+        gt.redrawgraph (gt, gt.views);
+        return;
+    }
+    if (gt.layoutmode == 'async') {
+        if (~gt.haveinput) {
+            gt.startlayout (gt);
+            return;
+        }
+        if (~gt.finishlayout (gt))
+            return;
+        gt.setviewsize (gt.views, gt.graph.rect);
+        gt.redrawgraph (gt, gt.views);
+    } else {
+        if (~gt.startlayout (gt))
+            return;
+        else
+            while (~gt.finishlayout (gt))
+                ;
+        gt.setviewsize (gt.views, gt.graph.rect);
+        gt.redrawgraph (gt, gt.views);
+    }
+};
+dotty.protogt.createview = function (gt, protovt) {
+    local vt, ovt, id, t;
+
+    vt = [];
+    vt.colors = [];
+    vt.colorn = 2;
+    if (~protovt)
+        protovt = dotty.protovt.normal;
+    if (protovt.mode ~= 'replace') {
+        for (id in dotty.protovt[protovt.type])
+            vt[id] = copy (dotty.protovt[protovt.type][id]);
+    }
+    for (id in protovt)
+        vt[id] = copy (protovt[id]);
+    if (~(vt.parent >= 0)) {
+        vt.view = createwidget (-1, [
+            'type'   = 'view';
+            'name'   = vt.name;
+            'origin' = vt.orig;
+            'size'   = vt.size;
+        ]);
+        vt.scroll = createwidget (vt.view, ['type' = 'scroll';]);
+    } else {
+        vt.view = -1;
+        vt.scroll = createwidget (vt.parent, [
+            'type' = 'scroll';
+            'size' = vt.size;
+        ]);
+    }
+    vt.canvas = createwidget (vt.scroll, [
+        'type' = 'canvas';
+        'color' = [0 = protovt.bgcolor; 1 = protovt.fgcolor;];
+    ]);
+    setwidgetattr (vt.canvas, [
+        'window' = vt.wrect;
+        'viewport' = vt.vsize;
+    ]);
+    clear (vt.canvas);
+    dotty.views[vt.canvas] = vt;
+    vt.vtid = vt.canvas;
+    vt.gtid = gt.gtid;
+    gt.views[vt.vtid] = vt;
+    dotty.views[vt.scroll] = vt;
+    if (vt.view ~= -1)
+        dotty.views[vt.view] = vt;
+    if (protovt.colors & tablesize (protovt.colors) > 0) {
+        for (id in protovt.colors)
+            if (id == '_bgcolor_')
+                setwidgetattr (vt.canvas, [
+                    'color' = [0 = protovt.colors[id];];
+                ]);
+            else if (setwidgetattr (vt.canvas, ['color' = [
+                protovt.colors[id] = id;
+            ];]) ~= 1) {
+                t = split (id, ' ');
+                if (tablesize (t) ~= 3 | setwidgetattr (vt.canvas, [
+                    'color' = [protovt.colors[id] = [
+                        'h' = ston (t[0]); 's' = ston (t[1]); 'v' = ston (t[2]);
+                    ];];
+                ]) ~= 1) {
+                    dotty.message (
+                        0, concat ('unknown color ', id, ' using #1')
+                    );
+                }
+            }
+        vt.colors = copy (protovt.colors);
+        vt.colorn = protovt.colorn;
+    } else if (tablesize (gt.views) > 1) {
+        for (id in gt.views)
+            if (gt.views[id] ~= vt)
+                break;
+        ovt = gt.views[id];
+        for (id in ovt.colors)
+            if (id == '_bgcolor_')
+                setwidgetattr (vt.canvas, ['color' = [0 = ovt.colors[id];];]);
+            else if (setwidgetattr (vt.canvas, ['color' = [
+                ovt.colors[id] = id;
+            ];]) ~= 1) {
+                t = split (id, ' ');
+                if (tablesize (t) ~= 3 | setwidgetattr (vt.canvas, [
+                    'color' = [ovt.colors[id] = [
+                        'h' = ston (t[0]); 's' = ston (t[1]); 'v' = ston (t[2]);
+                    ];];
+                ]) ~= 1) {
+                    dotty.message (
+                        0, concat ('unknown color ', id, ' using #1')
+                    );
+                }
+            }
+        vt.colors = copy (ovt.colors);
+        vt.colorn = ovt.colorn;
+    }
+    if (gt.graph.rect)
+        gt.setviewsize ([vt.vtid = vt;], gt.graph.rect);
+    gt.drawgraph (gt, [vt.vtid = vt;]);
+    for (id in vt.uifuncs)
+        if (id == 'closeview')
+            widgets[vt.view][id] = vt.uifuncs[id];
+        else
+            widgets[vt.canvas][id] = vt.uifuncs[id];
+    return vt;
+};
+dotty.protogt.destroyview = function (gt, vt) {
+    destroywidget (vt.canvas);
+    destroywidget (vt.scroll);
+    if (vt.view ~= -1) {
+        destroywidget (vt.view);
+        remove (vt.view, dotty.views);
+    }
+    remove (vt.scroll, dotty.views);
+    remove (vt.canvas, dotty.views);
+    if (vt.gtid >= 0)
+        remove (vt.vtid, gt.views);
+    if (tablesize (dotty.views) == 0)
+        exit ();
+};
+dotty.protogt.zoom = function (gt, vt, factor, pos) {
+    gt.setviewscale ([vt.vtid = vt;], factor);
+    if (pos)
+        gt.setviewcenter ([vt.vtid = vt;], pos);
+    gt.redrawgraph (gt, [vt.vtid = vt;]);
+};
+dotty.protogt.findnode = function (gt, vt) {
+    local key, node, node1, nid;
+
+    if (~(key = ask ('give node name or label')))
+        return;
+    if (gt.graph.nodedict[key] >= 0)
+        node = gt.graph.nodes[gt.graph.nodedict[key]];
+    else if (gt.graph.nodedict[ston (key)] >= 0)
+        node = gt.graph.nodes[gt.graph.nodedict[ston (key)]];
+    else {
+        for (nid in gt.graph.nodes) {
+            node1 = gt.graph.nodes[nid];
+            if (node1.attr.label == key | node1.attr.label == ston (key)) {
+                node = node1;
+                break;
+            }
+        }
+    }
+    if (~node) {
+        dotty.message (0, concat ('cannot find node: ', key));
+        return;
+    }
+    gt.setviewcenter ([vt.vtid = vt;], node.pos);
+};
+dotty.protogt.setattr = function (gt, obj) {
+    local kv, t, attr, value, n, i, s;
+
+    if (~(kv = ask ('give attr/value, eg. color=blue')))
+        return;
+    t = split (kv, '=');
+    attr = t[0];
+    value = t[1];
+    if ((n = tablesize (t)) > 2)
+        for (i = 2; i < n; i = i + 1)
+            value = concat (value, '=', t[i]);
+    # Check for HTML string and convert using lefty convention
+    s = split (value, '');
+    n = tablesize (s);
+    if ((s[0] == '<') & (s[n-1] == '>')) {
+        s[0] = '>';
+        s[n-1] = '<';
+        value = s[0]; 
+        for (i = 1; i < n; i = i + 1)
+            value = concat (value, s[i]);
+    }
+    if (
+        obj.attr == gt.graph.graphattr |
+        obj.attr == gt.graph.edgeattr |
+        obj.attr == gt.graph.nodeattr
+    ) {
+        obj.attr[attr] = value;
+        return;
+    }
+    if (obj.nid >= 0) {
+        gt.undrawnode (gt, gt.views, obj);
+        obj.attr[attr] = value;
+        gt.unpacknodeattr (gt, obj);
+        gt.drawnode (gt, gt.views, obj);
+    } else if (obj.eid >= 0) {
+        gt.undrawedge (gt, gt.views, obj);
+        obj.attr[attr] = value;
+        gt.unpackedgeattr (gt, obj);
+        gt.drawedge (gt, gt.views, obj);
+    }
+};
+dotty.protogt.getattr = function (gt, node) {
+    local kv;
+
+    if (~(kv.key = ask ('give attr name')))
+        return null;
+    if ((kv.val = node.attr[kv.key]))
+        return kv;
+    return null;
+};
+#
+# utilities
+#
+dotty.createviewandgraph = function (name, type, protogt, protovt) {
+    local vt, gt;
+
+    if (~protogt)
+        protogt = dotty.protogt;
+    if (protogt.creategraph)
+        gt = protogt.creategraph (protogt);
+    else
+        gt = dotty.protogt.creategraph (protogt);
+    vt = gt.createview (gt, protovt);
+    if (~protogt.graph)
+        protogt.graph = copy (dotty.protogt.graph);
+    if (name)
+        gt.loadgraph (gt, name, type, protogt.graph, 1);
+    return ['gt' = gt; 'vt' = vt;];
+};
+dotty.openio = function (name, type, mode) {
+    local fd;
+
+    if (~name)
+        return null;
+    if (type == 'file') {
+        if (name == '-') {
+            if (mode == 'r' | mode == 'r+')
+                fd = 0;
+            else
+                fd = 1;
+        } else if (~((fd = openio ('file', name, mode)) >= 0)) {
+            dotty.message (0, concat ('cannot open file: ', name));
+            return null;
+        }
+    } else if (type == 'pipe') {
+        if (~((fd = openio (
+            'pipe', 'ksh', mode, concat ("%e ", name)
+        )) >= 0)) {
+            dotty.message (0, concat ('cannot run command: ', name));
+            return null;
+        }
+    } else
+        return null;
+    return fd;
+};
+dotty.pushbusy = function (gt, views) {
+    local vid;
+
+    if (gt.busy == 0)
+        for (vid in gt.views)
+            setwidgetattr (vid, ['cursor' = 'watch';]);
+    gt.busy = gt.busy + 1;
+};
+dotty.popbusy = function (gt, views) {
+    local vid;
+
+    gt.busy = gt.busy - 1;
+    if (gt.busy == 0)
+        for (vid in gt.views)
+            setwidgetattr (vid, ['cursor' = 'default';]);
+};
+dotty.message = function (level, text) {
+    if (level <= dotty.mlevel)
+        echo ('dotty.lefty: ', text);
+};
+#
+# printing or saving to file
+#
+dotty.protogt.printorsave = function (gt, vt, otype, name, mode, ptype) {
+    local pr, wrect, vsize, xy, psize, canvas, pscanvas, cid, cname, t;
+    local graph, edgehandles, fontmap, eid, edge, nid, node, gid, sgraph;
+    local did, draw, i;
+
+    if (~otype)
+        if (~(otype = ask ('print to', 'choice', 'file|printer')))
+            return;
+    if (otype == 'printer') {
+        if (~getenv ('TMPDIR'))
+            name = concat (getenv ('HOME'), '/.dottyout.ps');
+        else
+            name = concat (getenv ('TMPDIR'), '/.dottyout.ps', random (10000));
+        if (getenv ('LEFTYWINSYS') ~= 'mswin' & ~pr)
+            if (~(pr = ask ('printer command', 'string', 'lpr')))
+                return;
+    }
+    if (~name)
+        if (~(name = ask ('postscript file', 'file', 'out.ps')))
+            return;
+    if (~ptype)
+        if (~(ptype = ask ('page size', 'choice', '8.5x11|11x17|36x50')))
+            return;
+    if (~mode)
+        if (~(mode = ask ('mode', 'choice', 'portrait|landscape|best fit')))
+            return;
+    wrect = copy (vt.wrect);
+    wrect[0].x = wrect[0].x - 1;
+    wrect[1].x = wrect[1].x + 1;
+    wrect[0].y = wrect[0].y - 1;
+    wrect[1].y = wrect[1].y + 1;
+    vsize = copy (vt.vsize);
+    if (vsize.x == 0)
+        vsize.x = 1;
+    if (vsize.y == 0)
+        vsize.y = 1;
+    xy = vsize.x / vsize.y;
+    if (mode == 'best fit') {
+        if (xy < 1)
+            mode = 'portrait';
+        else
+            mode = 'landscape';
+    }
+    psize = dotty.pagesizes[ptype];
+    if (mode == 'portrait') {
+        if (xy < psize.x / psize.y) {
+            vsize.y = psize.y * 300;
+            vsize.x = vsize.y * xy;
+        } else {
+            vsize.x = psize.x * 300;
+            vsize.y = vsize.x / xy;
+        }
+    } else {
+        if (xy < psize.y / psize.x) {
+            vsize.y = psize.x * 300;
+            vsize.x = vsize.y * xy;
+        } else {
+            vsize.x = psize.y * 300;
+            vsize.y = vsize.x / xy;
+        }
+    }
+    if (~((pscanvas = createwidget (-1, [
+        'type'   = 'ps';
+        'origin' = ['x' = 0; 'y' = 0;];
+        'size'   = vsize;
+        'mode'   = mode;
+        'name'   = name;
+    ])) >= 0)) {
+        dotty.message (0, 'cannot open printer device');
+        return;
+    }
+    for (cname in vt.colors) {
+        cid = vt.colors[cname];
+        if (cname == '_bgcolor_')
+            setwidgetattr (pscanvas, ['color' = [0 = cid;];]);
+        else if (setwidgetattr (pscanvas, ['color' = [cid = cname;];]) ~= 1) {
+            t = split (cname, ' ');
+            if (tablesize (t) ~= 3 | setwidgetattr (pscanvas, [
+                'color' = [cid = [
+                    'h' = ston (t[0]); 's' = ston (t[1]); 'v' = ston (t[2]);
+                ];];
+            ]) ~= 1) {
+                dotty.message (
+                    0, concat ('unknown color ', cname, ' using #1')
+                );
+            }
+        }
+    }
+    setwidgetattr (pscanvas, ['window' = wrect;]);
+    graph = copy (gt.graph);
+    canvas = vt.canvas;
+    vt.canvas = pscanvas;
+    edgehandles = gt.edgehandles;
+    gt.edgehandles = 0;
+    fontmap = dotty.maps[getenv ('LEFTYWINSYS')].psfontmap;
+    for (eid in graph.edges) {
+        edge = graph.edges[eid];
+        edge.fontname = fontmap[edge.attr.fontname];
+        for (did in edge.draws) {
+            if (did == 'ep')
+                continue;
+            draw = edge.draws[did];
+            for (i = 0; draw[i]; i = i + 1)
+                if (draw[i].type == 'F')
+                    draw[i].fn = fontmap[draw[i].ofn];
+        }
+        gt.drawedge (gt, [0 = vt;], edge);
+    }
+    for (nid in graph.nodes) {
+        node = graph.nodes[nid];
+        node.fontname = fontmap[node.attr.fontname];
+        for (did in node.draws) {
+            if (did == 'ep')
+                continue;
+            draw = node.draws[did];
+            for (i = 0; draw[i]; i = i + 1)
+                if (draw[i].type == 'F')
+                    draw[i].fn = fontmap[draw[i].ofn];
+        }
+        gt.drawnode (gt, [0 = vt;], node);
+    }
+    for (gid in graph.graphs) {
+        sgraph = graph.graphs[gid];
+        sgraph.fontname = fontmap[sgraph.graphattr.fontname];
+        for (did in sgraph.draws) {
+            if (did == 'ep')
+                continue;
+            draw = sgraph.draws[did];
+            for (i = 0; draw[i]; i = i + 1)
+                if (draw[i].type == 'F')
+                    draw[i].fn = fontmap[draw[i].ofn];
+        }
+        gt.drawsgraph (gt, [0 = vt;], sgraph);
+    }
+    graph.fontname = fontmap[graph.graphattr.fontname];
+    gt.drawsgraph (gt, [0 = vt;], graph);
+    gt.edgehandles = edgehandles;
+    vt.canvas = canvas;
+    destroywidget (pscanvas);
+    if (otype == 'printer' & getenv ('LEFTYWINSYS') ~= 'mswin')
+        system (concat (pr, ' ', name, '; rm ',name));
+};