# print.tcl -- # This file defines the 'tk print' command for printing of the canvas # widget and text on X11, Windows, and macOS. It implements an abstraction # layer that presents a consistent API across the three platforms. # Copyright © 2009 Michael I. Schwartz. # Copyright © 2021 Kevin Walzer/WordTech Communications LLC. # Copyright © 2021 Harald Oehlmann, Elmicron GmbH # Copyright © 2022 Emiliano Gavilan # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. namespace eval ::tk::print { namespace import -force ::tk::msgcat::* # makeTempFile: # Create a temporary file and populate its contents # Arguments: # filename - base of the name of the file to create # contents - what to put in the file; defaults to empty # Returns: # Full filename for created file # proc makeTempFile {filename {contents ""}} { set f [file tempfile filename $filename] try { puts -nonewline $f $contents return $filename } finally { close $f } } if {[tk windowingsystem] eq "win32"} { variable printer_name variable copies variable dpi_x variable dpi_y variable paper_width variable paper_height variable margin_left variable margin_top variable printargs array set printargs {} # Multiple utility procedures for printing text based on the # C printer primitives. # _set_dc: # Select printer and set device context and other parameters # for print job. # proc _set_dc {} { variable printargs variable printer_name variable paper_width variable paper_height variable dpi_x variable dpi_y variable copies #First, we select the printer. _selectprinter #Next, set values. Some are taken from the printer, #some are sane defaults. if {[info exists printer_name] && $printer_name ne ""} { set printargs(hDC) $printer_name set printargs(pw) $paper_width set printargs(pl) $paper_height set printargs(lm) 1000 set printargs(tm) 1000 set printargs(rm) 1000 set printargs(bm) 1000 set printargs(resx) $dpi_x set printargs(resy) $dpi_y set printargs(copies) $copies set printargs(resolution) [list $dpi_x $dpi_y] } } # _print_data # This function prints multiple-page files, using a line-oriented # function, taking advantage of knowing the character widths. # Arguments: # data - Text data for printing # breaklines - If non-zero, keep newlines in the string as # newlines in the output. # font - Font for printing proc _print_data {data {breaklines 1} {font ""}} { variable printargs variable printer_name _set_dc if {![info exists printer_name]} { return } if {$font eq ""} { _gdi characters $printargs(hDC) -array printcharwid } else { _gdi characters $printargs(hDC) -font $font -array printcharwid } set pagewid [expr {($printargs(pw) - $printargs(rm) ) / 1000 * $printargs(resx)}] set pagehgt [expr {($printargs(pl) - $printargs(bm) ) / 1000 * $printargs(resy)}] set totallen [string length $data] set curlen 0 set curhgt [expr {$printargs(tm) * $printargs(resy) / 1000}] _opendoc _openpage while {$curlen < $totallen} { set linestring [string range $data $curlen end] if {$breaklines} { set endind [string first "\n" $linestring] if {$endind >= 0} { set linestring [string range $linestring 0 $endind] # handle blank lines.... if {$linestring eq ""} { set linestring " " } } } set result [_print_page_nextline $linestring \ printcharwid printargs $curhgt $font] incr curlen [lindex $result 0] incr curhgt [lindex $result 1] if {$curhgt + [lindex $result 1] > $pagehgt} { _closepage _openpage set curhgt [expr {$printargs(tm) * $printargs(resy) / 1000}] } } _closepage _closedoc } # _print_file # This function prints multiple-page files # It will either break lines or just let them run over the # margins (and thus truncate). # The font argument is JUST the font name, not any additional # arguments. # Arguments: # filename - File to open for printing # breaklines - 1 to break lines as done on input, 0 to ignore newlines # font - Optional arguments to supply to the text command proc _print_file {filename {breaklines 1} {font ""}} { set fn [open $filename r] set data [read $fn] close $fn _print_data $data $breaklines $font } # _print_page_nextline # Returns the pair "chars y" # where chars is the number of characters printed on the line # and y is the height of the line printed # Arguments: # string - Data to print # pdata - Array of values for printer characteristics # cdata - Array of values for character widths # y - Y value to begin printing at # font - if non-empty specifies a font to draw the line in proc _print_page_nextline {string carray parray y font} { upvar #0 $carray charwidths upvar #0 $parray printargs variable printargs set endindex 0 set totwidth 0 set maxwidth [expr { (($printargs(pw) - $printargs(rm)) / 1000) * $printargs(resx) }] set maxstring [string length $string] set lm [expr {$printargs(lm) * $printargs(resx) / 1000}] for {set i 0} {($i < $maxstring) && ($totwidth < $maxwidth)} {incr i} { incr totwidth $charwidths([string index $string $i]) # set width($i) $totwidth } set endindex $i set startindex $endindex if {$i < $maxstring} { # In this case, the whole data string is not used up, and we # wish to break on a word. Since we have all the partial # widths calculated, this should be easy. set endindex [expr {[string wordstart $string $endindex] - 1}] set startindex [expr {$endindex + 1}] # If the line is just too long (no word breaks), print as much # as you can.... if {$endindex <= 1} { set endindex $i set startindex $i } } set txt [string trim [string range $string 0 $endindex] "\r\n"] if {$font ne ""} { set result [_gdi text $printargs(hDC) $lm $y \ -anchor nw -justify left \ -text $txt -font $font] } else { set result [_gdi text $printargs(hDC) $lm $y \ -anchor nw -justify left -text $txt] } return "$startindex $result" } # These procedures read in the canvas widget, and write all of # its contents out to the Windows printer. variable option variable vtgPrint proc _init_print_canvas {} { variable option variable vtgPrint variable printargs set vtgPrint(printer.bg) white } proc _is_win {} { variable printargs return [info exist tk_patchLevel] } # _print_widget # Main procedure for printing a widget. Currently supports # canvas widgets. Handles opening and closing of printer. # Arguments: # wid - The widget to be printed. # printer - Flag whether to use the default printer. # name - App name to pass to printer. proc _print_widget {wid {printer default} {name "Tk Print Output"}} { variable printargs variable printer_name _set_dc if {![info exists printer_name]} { return } _opendoc _openpage # Here is where any scaling/gdi mapping should take place # For now, scale so the dimensions of the window are sized to the # width of the page. Scale evenly. # For normal windows, this may be fine--but for a canvas, one # wants the canvas dimensions, and not the WINDOW dimensions. if {[winfo class $wid] eq "Canvas"} { set sc [$wid cget -scrollregion] # if there is no scrollregion, use width and height. if {$sc eq ""} { set window_x [$wid cget -width] set window_y [$wid cget -height] } else { set window_x [lindex $sc 2] set window_y [lindex $sc 3] } } else { set window_x [winfo width $wid] set window_y [winfo height $wid] } set printer_x [expr { ( $printargs(pw) - $printargs(lm) - $printargs(rm) ) * $printargs(resx) / 1000.0 }] set printer_y [expr { ( $printargs(pl) - $printargs(tm) - $printargs(bm) ) * $printargs(resy) / 1000.0 }] set factor_x [expr {$window_x / $printer_x}] set factor_y [expr {$window_y / $printer_y}] if {$factor_x < $factor_y} { set lo $window_y set ph $printer_y } else { set lo $window_x set ph $printer_x } _gdi map $printargs(hDC) -logical $lo -physical $ph \ -offset $printargs(resolution) # Handling of canvas widgets. switch [winfo class $wid] { Canvas { _print_canvas $printargs(hDC) $wid } default { puts "Can't print items of type [winfo class $wid]. No handler registered" } } # End printing process. _closepage _closedoc } # _print_canvas # Main procedure for writing canvas widget items to printer. # Arguments: # hdc - The printer handle. # cw - The canvas widget. proc _print_canvas {hdc cw} { variable vtgPrint variable printargs # Get information about page being printed to # print_canvas.CalcSizing $cw set vtgPrint(canvas.bg) [string tolower [$cw cget -background]] # Re-write each widget from cw to printer foreach id [$cw find all] { set type [$cw type $id] if {[info commands _print_canvas.$type] eq "_print_canvas.$type"} { _print_canvas.[$cw type $id] $printargs(hDC) $cw $id } else { puts "Omitting canvas item of type $type since there is no handler registered for it" } } } # These procedures support the various canvas item types, reading the # information about the item on the real canvas and then writing a # similar item to the printer. # _print_canvas.line # Description: # Prints a line item. # Arguments: # hdc - The printer handle. # cw - The canvas widget. # id - The id of the canvas item. proc _print_canvas.line {hdc cw id} { variable vtgPrint variable printargs set color [_print_canvas.TransColor [$cw itemcget $id -fill]] if {[string match $vtgPrint(printer.bg) $color]} { return } set coords [$cw coords $id] set wdth [$cw itemcget $id -width] set arrow [$cw itemcget $id -arrow] set arwshp [$cw itemcget $id -arrowshape] set dash [$cw itemcget $id -dash] set smooth [$cw itemcget $id -smooth] set splinesteps [$cw itemcget $id -splinesteps] set cmdargs {} if {$wdth > 1} { lappend cmdargs -width $wdth } if {$dash ne ""} { lappend cmdargs -dash $dash } if {$smooth ne ""} { lappend cmdargs -smooth $smooth } if {$splinesteps ne ""} { lappend cmdargs -splinesteps $splinesteps } set result [_gdi line $hdc {*}$coords \ -fill $color -arrow $arrow -arrowshape $arwshp \ {*}$cmdargs] if {$result ne ""} { puts $result } } # _print_canvas.arc # Prints a arc item. # Args: # hdc - The printer handle. # cw - The canvas widget. # id - The id of the canvas item. proc _print_canvas.arc {hdc cw id} { variable vtgPrint variable printargs set color [_print_canvas.TransColor [$cw itemcget $id -outline]] if {[string match $vtgPrint(printer.bg) $color]} { return } set coords [$cw coords $id] set wdth [$cw itemcget $id -width] set style [$cw itemcget $id -style] set start [$cw itemcget $id -start] set extent [$cw itemcget $id -extent] set fill [$cw itemcget $id -fill] set cmdargs {} if {$wdth > 1} { lappend cmdargs -width $wdth } if {$fill ne ""} { lappend cmdargs -fill $fill } _gdi arc $hdc {*}$coords \ -outline $color -style $style -start $start -extent $extent \ {*}$cmdargs } # _print_canvas.polygon # Prints a polygon item. # Arguments: # hdc - The printer handle. # cw - The canvas widget. # id - The id of the canvas item. proc _print_canvas.polygon {hdc cw id} { variable vtgPrint variable printargs set fcolor [_print_canvas.TransColor [$cw itemcget $id -fill]] if {$fcolor eq ""} { set fcolor $vtgPrint(printer.bg) } set ocolor [_print_canvas.TransColor [$cw itemcget $id -outline]] if {$ocolor eq ""} { set ocolor $vtgPrint(printer.bg) } set coords [$cw coords $id] set wdth [$cw itemcget $id -width] set smooth [$cw itemcget $id -smooth] set splinesteps [$cw itemcget $id -splinesteps] set cmdargs {} if {$smooth ne ""} { lappend cmdargs -smooth $smooth } if {$splinesteps ne ""} { lappend cmdargs -splinesteps $splinesteps } _gdi polygon $hdc {*}$coords \ -width $wdth -fill $fcolor -outline $ocolor {*}$cmdargs } # _print_canvas.oval # Prints an oval item. # Arguments: # hdc - The printer handle. # cw - The canvas widget. # id - The id of the canvas item. proc _print_canvas.oval {hdc cw id} { variable vtgPrint set fcolor [_print_canvas.TransColor [$cw itemcget $id -fill]] if {$fcolor eq ""} { set fcolor $vtgPrint(printer.bg) } set ocolor [_print_canvas.TransColor [$cw itemcget $id -outline]] if {$ocolor eq ""} { set ocolor $vtgPrint(printer.bg) } set coords [$cw coords $id] set wdth [$cw itemcget $id -width] _gdi oval $hdc {*}$coords \ -width $wdth -fill $fcolor -outline $ocolor } # _print_canvas.rectangle # Prints a rectangle item. # Arguments: # hdc - The printer handle. # cw - The canvas widget. # id - The id of the canvas item. proc _print_canvas.rectangle {hdc cw id} { variable vtgPrint set fcolor [_print_canvas.TransColor [$cw itemcget $id -fill]] if {$fcolor eq ""} { set fcolor $vtgPrint(printer.bg) } set ocolor [_print_canvas.TransColor [$cw itemcget $id -outline]] if {$ocolor eq ""} { set ocolor $vtgPrint(printer.bg) } set coords [$cw coords $id] set wdth [$cw itemcget $id -width] _gdi rectangle $hdc {*}$coords \ -width $wdth -fill $fcolor -outline $ocolor } # _print_canvas.text # Prints a text item. # Arguments: # hdc - The printer handle. # cw - The canvas widget. # id - The id of the canvas item. proc _print_canvas.text {hdc cw id} { variable vtgPrint variable printargs set color [_print_canvas.TransColor [$cw itemcget $id -fill]] # if {"white" eq [string tolower $color]} {return} # set color black set txt [$cw itemcget $id -text] if {$txt eq ""} { return } set coords [$cw coords $id] set anchr [$cw itemcget $id -anchor] set bbox [$cw bbox $id] set wdth [expr {[lindex $bbox 2] - [lindex $bbox 0]}] set just [$cw itemcget $id -justify] # Get the real canvas font info and create a compatible font, # suitable for printer name extraction. set font [font create {*}[font actual [$cw itemcget $id -font]]] # Just get the name and family, or some of the _gdi commands will # fail. set font [list [font configure $font -family] \ -[font configure $font -size]] _gdi text $hdc {*}$coords \ -fill $color -text $txt -font $font \ -anchor $anchr -width $wdth -justify $just } # _print_canvas.image # Prints an image item. # Arguments: # hdc - The printer handle. # cw - The canvas widget. # id - The id of the canvas item. proc _print_canvas.image {hdc cw id} { # First, we have to get the image name. set imagename [$cw itemcget $id -image] # Now we get the size. set wid [image width $imagename] set hgt [image height $imagename] # Next, we get the location and anchor set anchor [$cw itemcget $id -anchor] set coords [$cw coords $id] _gdi photo $hdc -destination $coords -photo $imagename } # _print_canvas.bitmap # Prints a bitmap item. # Arguments: # hdc - The printer handle. # cw - The canvas widget. # id - The id of the canvas item. proc _print_canvas.bitmap {hdc cw id} { variable option variable vtgPrint # First, we have to get the bitmap name. set imagename [$cw itemcget $id -image] # Now we get the size. set wid [image width $imagename] set hgt [image height $imagename] #Next, we get the location and anchor. set anchor [$cw itemcget $id -anchor] set coords [$cw coords $id] # Since the GDI commands don't yet support images and bitmaps, # and since this represents a rendered bitmap, we CAN use # copybits IF we create a new temporary toplevel to hold the beast. # If this is too ugly, change the option! if {[info exist option(use_copybits)]} { set firstcase $option(use_copybits) } else { set firstcase 0 } if {$firstcase > 0} { set tl [toplevel .tmptop[expr {int( rand() * 65535 )}] \ -height $hgt -width $wid \ -background $vtgPrint(canvas.bg)] canvas $tl.canvas -width $wid -height $hgt $tl.canvas create image 0 0 -image $imagename -anchor nw pack $tl.canvas -side left -expand false -fill none tkwait visibility $tl.canvas update set srccoords [list 0 0 [expr {$wid - 1}] [expr {$hgt - 1}]] set dstcoords [list [lindex $coords 0] [lindex $coords 1] [expr {$wid - 1}] [expr {$hgt - 1}]] _gdi copybits $hdc -window $tl -client \ -source $srccoords -destination $dstcoords destroy $tl } else { _gdi bitmap $hdc {*}$coords \ -anchor $anchor -bitmap $imagename } } # These procedures transform attribute setting from the real # canvas to the appropriate setting for printing to paper. # _print_canvas.TransColor # Does the actual transformation of colors from the # canvas widget to paper. # Arguments: # color - The color value to be transformed. proc _print_canvas.TransColor {color} { variable vtgPrint variable printargs switch [string toupper $color] { $vtgPrint(canvas.bg) {return $vtgPrint(printer.bg)} } return $color } # Initialize all the variables once. _init_print_canvas } #end win32 procedures #begin X11 procedures # X11 procedures wrap standard Unix shell commands such as lp/lpr and # lpstat for printing. Some output configuration that on other platforms # is managed through the printer driver/dialog is configured through the # canvas postscript command. if {[tk windowingsystem] eq "x11"} { variable printcmd {} # print options variable optlist set optlist(printer) {} set optlist(paper) [list [mc "Letter"] [mc "Legal"] [mc "A4"]] set optlist(orient) [list [mc "Portrait"] [mc "Landscape"]] set optlist(color) [list [mc "Grayscale"] [mc "RGB"]] set optlist(zoom) {100 90 80 70 60 50 40 30 20 10} # selected options variable sel array set sel { printer {} copies {} paper {} orient {} color {} zoom {} } # default values for dialog widgets option add *Printdialog*TLabel.anchor e option add *Printdialog*TMenubutton.Menu.tearOff 0 option add *Printdialog*TMenubutton.width 12 option add *Printdialog*TSpinbox.width 12 # this is tempting to add, but it's better to leave it to user's taste # option add *Printdialog*Menu.background snow # returns the full qualified var name proc myvar {varname} { set fqvar [uplevel 1 [list namespace which -variable $varname]] # assert var existence if {$fqvar eq ""} { return -code error "Wrong varname \"$varname\"" } return $fqvar } # ttk version of [tk_optionMenu] # var should be a full qualified varname proc ttk_optionMenu {w var args} { ttk::menubutton $w \ -textvariable $var \ -menu $w.menu menu $w.menu foreach option $args { $w.menu add command \ -label $option \ -command [list set $var $option] } # return the same value as tk_optionMenu return $w.menu } # _setprintenv # Set the print environtment - print command, and list of printers. # Arguments: # none. proc _setprintenv {} { variable printcmd variable optlist #Test for existence of lpstat command to obtain list of printers. Return error #if not found. catch {exec lpstat -a} msg set notfound "command not found" if {[string first $notfound $msg] >= 0} { error "Unable to obtain list of printers. Please install the CUPS package \ for your system." return } set notfound "No destinations added" if {[string first $notfound $msg] != -1} { error "Please check or update your CUPS installation." return } # Select print command. We prefer lpr, but will fall back to lp if # necessary. if {[auto_execok lpr] ne ""} { set printcmd lpr } else { set printcmd lp } #Build list of printers set printers {} set printdata [exec lpstat -a] foreach item [split $printdata \n] { lappend printers [lindex [split $item] 0] } # filter out duplicates set optlist(printer) [lsort -unique $printers] } # _print # Main printer dialog. Select printer, set options, and # fire print command. # Arguments: # w - widget with contents to print. # proc _print {w} { # TODO: revise padding variable optlist variable sel # should this be called with every invocaton? _setprintenv if {$sel(printer) eq "" && [llength $optlist(printer)] > 0} { set sel(printer) [lindex $optlist(printer) 0] } set p ._print catch {destroy $p} # copy the current values to a dialog's temorary variable # this allow us to cancel the dialog discarding any changes # made to the options namespace eval dlg {variable sel} array set dlg::sel [array get sel] # The toplevel of our dialog toplevel $p -class Printdialog place [ttk::frame $p.background] -x 0 -y 0 -relwidth 1.0 -relheight 1.0 wm title $p [mc "Print"] wm resizable $p 0 0 wm attributes $p -type dialog # The printer to use set pf [ttk::frame $p.printerf] pack $pf -side top -fill x -expand no -padx 9p -pady 9p ttk::label $pf.printerl -text "[mc "Printer"] :" ttk::combobox $pf.printer \ -textvariable [myvar dlg::sel](printer) \ -state readonly \ -values $optlist(printer) pack $pf.printerl -side left -padx {0 4.5p} pack $pf.printer -side left # Start of printing options set of [ttk::labelframe $p.optionsframe -text [mc "Options"]] pack $of -fill x -padx 9p -pady {0 9p} -ipadx 2p -ipady 2p # COPIES ttk::label $of.copiesl -text "[mc "Copies"] :" ttk::spinbox $of.copies -from 1 -to 1000 \ -textvariable [myvar dlg::sel](copies) grid $of.copiesl $of.copies -sticky ew -padx 2p -pady 2p # PAPER SIZE ttk::label $of.paperl -text "[mc "Paper"] :" ttk_optionMenu $of.paper [myvar dlg::sel](paper) {*}$optlist(paper) grid $of.paperl $of.paper -sticky ew -padx 2p -pady 2p # additional options for canvas output if {[winfo class $w] eq "Canvas"} { # SCALE ttk::label $of.percentl -text "[mc "Scale"] :" ttk_optionMenu $of.percent [myvar dlg::sel](zoom) {*}$optlist(zoom) grid $of.percentl $of.percent -sticky ew -padx 2p -pady 2p # ORIENT ttk::label $of.orientl -text "[mc "Orientation"] :" ttk_optionMenu $of.orient [myvar dlg::sel](orient) {*}$optlist(orient) grid $of.orientl $of.orient -sticky ew -padx 2p -pady 2p # COLOR ttk::label $of.colorl -text "[mc "Output"] :" ttk_optionMenu $of.color [myvar dlg::sel](color) {*}$optlist(color) grid $of.colorl $of.color -sticky ew -padx 2p -pady 2p } # The buttons frame. set bf [ttk::frame $p.buttonf] pack $bf -fill x -expand no -side bottom -padx 9p -pady {0 9p} ttk::button $bf.print -text [mc "Print"] \ -command [namespace code [list _runprint $w $p]] ttk::button $bf.cancel -text [mc "Cancel"] \ -command [namespace code [list _cancel $p]] pack $bf.print -side right pack $bf.cancel -side right -padx {0 4.5p} #Center the window as a dialog. ::tk::PlaceWindow $p } proc _cancel {p} { namespace delete dlg destroy $p } # _runprint - # Execute the print command--print the file. # Arguments: # w - widget with contents to print. # proc _runprint {w p} { variable printcmd variable sel # copy the values back from the dialog array set sel [array get dlg::sel] namespace delete dlg #First, generate print file. if {[winfo class $w] eq "Text"} { set file [makeTempFile tk_text.txt [$w get 1.0 end]] } if {[winfo class $w] eq "Canvas"} { if {$sel(color) eq [mc "RGB"]} { set colormode color } else { set colormode gray } if {$sel(orient) eq [mc "Landscape"]} { set willrotate "1" } else { set willrotate "0" } #Scale based on size of widget, not size of paper. set printwidth [expr {$sel(zoom) / 100.00 * [winfo width $w]}] set file [makeTempFile tk_canvas.ps] $w postscript -file $file -colormode $colormode \ -rotate $willrotate -pagewidth $printwidth } #Build list of args to pass to print command. set printargs {} if {$printcmd eq "lpr"} { lappend printargs -P $sel(printer) -# $sel(copies) } else { lappend printargs -d $sel(printer) -n $sel(copies) } # launch the job in the background after 0 [list exec $printcmd {*}$printargs -o PageSize=$sel(paper) $file] destroy $p } # Initialize with sane defaults. set sel(copies) 1 set sel(paper) [mc "A4"] set sel(orient) [mc "Portrait"] set sel(color) [mc "RGB"] set sel(zoom) 100 } #end X11 procedures #begin macOS Aqua procedures if {[tk windowingsystem] eq "aqua"} { # makePDF - # Convert a file to PDF # Arguments: # inFilename - file containing the data to convert; format is # autodetected. # outFilename - base for filename to write to; conventionally should # have .pdf as suffix # Returns: # The full pathname of the generated PDF. # proc makePDF {inFilename outFilename} { set out [::tk::print::makeTempFile $outFilename] try { exec /usr/sbin/cupsfilter $inFilename > $out } trap NONE {msg} { # cupsfilter produces a lot of debugging output, which we # don't want. regsub -all -line {^(?:DEBUG|INFO):.*$} $msg "" msg set msg [string trimleft [regsub -all {\n+} $msg "\n"] "\n"] if {$msg ne ""} { # Lines should be prefixed with WARN or ERROR now puts $msg } } return $out } } #end macOS Aqua procedures namespace export canvas text namespace ensemble create } # tk print -- # This procedure prints the canvas and text widgets using platform- # native API's. # Arguments: # w: Widget to print. proc ::tk::print {w} { switch [winfo class $w],[tk windowingsystem] { "Canvas,win32" { tailcall ::tk::print::_print_widget $w 0 "Tk Print Output" } "Canvas,x11" { tailcall ::tk::print::_print $w } "Canvas,aqua" { set psfile [::tk::print::makeTempFile tk_canvas.ps] try { $w postscript -file $psfile set printfile [::tk::print::makePDF $psfile tk_canvas.pdf] ::tk::print::_print $printfile } finally { file delete $psfile } } "Text,win32" { tailcall ::tk::print::_print_data [$w get 1.0 end] 1 {Arial 12} } "Text,x11" { tailcall ::tk::print::_print $w } "Text,aqua" { set txtfile [::tk::print::makeTempFile tk_text.txt [$w get 1.0 end]] try { set printfile [::tk::print::makePDF $txtfile tk_text.pdf] ::tk::print::_print $printfile } finally { file delete $txtfile } } default { return -code error -errorcode {TK PRINT CLASS_UNSUPPORTED} \ "widgets of class [winfo class $w] are not supported on\ this platform" } } } #Add this command to the tk command ensemble: tk print #Thanks to Christian Gollwitzer for the guidance here namespace ensemble configure tk -map \ [dict merge [namespace ensemble configure tk -map] \ {print ::tk::print}] return # Local Variables: # mode: tcl # fill-column: 78 # End: