Printing a canvas under Windows

Purpose: Demonstrate one way of printing a canvas under Windows/Win32.


AMucha 2009/09/21 Use the excellent little package pdf4tcl. It can directly transform a canvas into a pdf-file. Leave the rest of the hard work for the people from Adobe :-) (See Printing under Windows)

  # Example printing a canvas with pdf4info
  package require pdf4tcl                       ; # Get the package
  pdf4tcl::new mypdf -paper a4 -margin 15mm     ; # make a new pdf-object
  mypdf startPage                               ; # make a new page
  # make and fill a canvas .canv 
  # ...
  mypdf canvas .canv                            ; # then put the canvas on your pdf-page
  mypdf write -file canvas.pdf                  ; # write to file
  mypdf destroy                                 ; # clean up
  # printing:
  eval exec [auto_execok start] AcroRd32.exe /p /h [list [file normalize canvas.pdf ]]

KBK: I've seen a number of requests for ways to print the contents of a canvas under Windows. Alas, there is no graceful way. Nevertheless, there are approaches that work sometimes, and can be useful. For the most part, they work by using the 'postscript' command of the canvas widget to render the canvas as PostScript, and then either sending the result directly to a PostScript printer, or piping it through Ghostscript to interpret it and pushing the result at a Winprinter.

One tool that helps a lot is PrFile32.exe, available from: [L1 ]. Without this clever little program, Windows makes it insanely difficult to send a PostScript file to a PostScript printer. With it, it's a simple as saying

    exec prfile32.exe /q fileName.ps &

IDG Prfile is a great utility. In addition to the above, it will automate piping through ghostscript to print PS on non-PS printers, and lets you set up automatic spooling, so that any file written to a designated directory automagically prints.

RA Just to make it clear: prfile does not contain a postscript renderer, you have to install that too and configure pfile to use it to let prfile print postscript in the above example.

I also find it useful to be able to carve up a canvas that's bigger than a page and print it in overlapping tiles. I often use the following code to do it.

The code has obvious bugs that it doesn't allow a user-specified scale factor but rather assumes that the printout should show 150 screen pixels to the inch. It also hard-codes the assumption of US letter-sized paper, although the margins are wide enough that it should work with A4. I present it as a starting point for anyone who wants to try to do better.


    #------------------------------------------------------------------
    #
    # printcanvas.tcl --
    #
    #   Tcl procedure to spool a canvas via PrFile32.
    #
    #------------------------------------------------------------------

    package provide printcanvas 1.0

    namespace eval printcanvas {

        namespace export printcanvas

        # CONFIGURATION -- Path name of the PrintFile program.

        variable prfile {C:\Program Files\PrintFile\PrFile32.exe}

    }

    #------------------------------------------------------------------
    #
    # printcanvas::printcanvas --
    #
    #   Print the contents of a canvas
    #
    # Parameters:
    #   w -- Path name of the canvas
    #
    # Results:
    #   None.
    #
    # Side effects:
    #   Canvas content is converted to PostScript and spooled via
    #   PrintFile.  If the canvas is larger than a printer page,
    #   a file is created and spooled for each page.
    #
    #------------------------------------------------------------------

    proc printcanvas::printcanvas { w } {

        variable prfile

        set name [file join $::env(TEMP) [pid]page]
        set cmd [list exec $prfile /q /delete]
        set i 0
        foreach { xmin ymin xmax ymax } [$w cget -scrollregion] {}
        for { set x $xmin } { $x < $xmax } { incr x 1200 } {
            for { set y $ymin } { $y < $ymax } { incr y 825 } {
                set fname $name[incr i].ps
                $w create text [expr { $x+1210 }] [expr { $y+835 }] \
                    -text "Page $i" -anchor nw \
                    -tags printCanvasPageNo \
                    -font {Helvetica -18}
                eval [list $w create rectangle] \
                    [$w bbox printCanvasPageNo] \
                    [list -fill white -outline {} \
                         -tags printCanvasPageNoBg]
                $w raise printCanvasPageNo printCanvasPageNoBg
                $w postscript \
                    -file $fname \
                    -colormode gray \
                    -x $x -y $y -width 1350 -height 975 \
                    -pageheight 6.5i -pagewidth 9.0i \
                    -rotate true 
                lappend cmd $fname
                $w delete printCanvasPageNo
                $w delete printCanvasPageNoBg
            }
        }
        eval $cmd

        return
    }

GPS: How does this compare to Tkprint?


KBK (7 November 2000): I've never been able to get tkprint to generate output at other than microscopic size; maybe I'm missing something in how it's set up. Also, if you change the value of prfile to the appropriate 'lpr' command, the code works on Unix as well.


ClassyTk [L2 ] has support for native printing of the Canvas on Windows. (It is a hack, but works for me)


JD: Michael Schwartz's extensions GDI/HDC/Printer [L3 ] provide a general context for modelling a printer as a generic graphics context and sending arbitrary data to it. I think it's the most general solution if you don't mind getting a bit low-level. A canvas printing utility is included (prntcanv.tcl) as a convenience, but does not handle embedded windows (i.e. canvas window objects) - but then again none of the other solutions seem to handle embedded windows either.

If you try to generate postscript out of a canvas (via $canvas -postscript) that has embedded windows in win32, you get a nasty error and wish aborts.

Update to the above statement: The problem with generating postscript from a canvas with embedded windows has been fixed as of release 8.4.1. The only remaining issue now is that only embedded windows that are mapped to the screen (i.e. visible) are included in the postscript output. But as long as you can fit your output in a single non-scrollable canvas, the canvas -postscript output is the best multiplatform way to print. The PrintFile program + Ghostscript/GSview make a perfect companion under windows.

JCG: Not strictly true, apparently. With Tk 8.4.5 under Windows, windows embedded directly within a canvas are printed fine, but if you embed a frame and then a button within the frame the generated PostScript contains a black box instead of the button. We are currently using a BLT graph with an embedded frame or canvas and printing the whole thing with BLT's postscript method (.graph postscript ...)


mjk: Contrary to popular(?) belief, using GhostScript to print a canvas under Windows is pretty easy. All you need is a GhostScript [L4 ] package installed.

The following example simply opens a pipe to gswin32c.exe, sends PostScript data from the canvas to the pipe and closes the pipe. There is no need for temporary files. The best thing with GS is that it offers printer dialog (native Win32 Common Dialog), where you can select a printer and change printer settings.

 package require Tk

 # Path to the gswin32c.exe:
 set gspath {C:\\Program Files\\gs\\gs8.14\\bin\\gswin32c.exe}

 # Create a canvas:
 canvas .test -background white -width 400 -height 400
 pack .test

 # Add something for printing:
 .test create text      250 250 -text "This is a test."
 .test create arc       100 100 40 40
 .test create rectangle 100 300 150 350
 .test create line      300 300 350 350
 .test create line      300 350 350 300

 # Open pipe to gs:
 set gs [open "|\"$gspath\" -q -sDEVICE=mswinpr2 -dNOPAUSE -dBATCH -" w]

 # Print the page:
 update
 .test postscript -channel $gs

 # Close the pipe, sends page to printer:
 close $gs

SVH: Because printing using GhostScript is a very powerfull feature, I created a separate page for it. See Printing a canvas using GhostScript .