| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343 |
- # 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 dumpfile [file join /tmp rawprint.txt]
- set tmpfile [file join /tmp $filename]
- set f [open $dumpfile w]
- try {
- puts -nonewline $f $contents
- } finally {
- close $f
- if {[file extension $filename] == ".ps"} {
- #don't apply formatting to PostScript
- file rename -force $dumpfile $tmpfile
- } else {
- #Make text fixed width for improved printed output
- exec fmt -w 75 $dumpfile > $tmpfile
- }
- return $tmpfile
- }
- }
- 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. They depends on Cups being installed.
- # X11 procedures abstracts print management with a "cups" ensemble command
- # cups defaultprinter returns the default printer
- # cups getprinters returns a dictionary of printers along
- # with printer info
- # cups print $printer $data ?$options?
- # print the data (binary) on a given printer
- # with the provided (supported) options:
- # -colormode -copies -format -margins
- # -media -nup -orientation
- # -prettyprint -title -tzoom
- # 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"} {
- if {[info commands ::tk::print::cups] eq ""} {
- namespace eval ::tk::print::cups {
- # Pure Tcl cups ensemble command implementation
- variable pcache
- }
- proc ::tk::print::cups::defaultprinter {} {
- set default {}
- regexp {: ([^[:space:]]+)$} [exec lpstat -d] _ default
- return $default
- }
- proc ::tk::print::cups::getprinters {} {
- variable pcache
- # Test for existence of lpstat command to obtain the list of
- # printers.
- # Return an error if not found.
- set res {}
- try {
- set printers [lsort -unique [split [exec lpstat -e] \n]]
- foreach printer $printers {
- set options [Parseoptions [exec lpoptions -p $printer]]
- dict set res $printer $options
- }
- } trap {POSIX ENOENT} {e o} {
- # no such command in PATH
- set cmd [lindex [dict get $o -errorstack ] 1 2]
- return -code error "Unable to obtain the list of printers.\
- Command \"$cmd\" not found.\
- Please install the CUPS package for your system."
- } trap {CHILDSTATUS} {} {
- # command returns a non-0 exit status. Wrong print system?
- set cmd [lindex [dict get $o -errorstack ] 1 2]
- return -code error "Command \"$cmd\" return with errors"
- }
- return [set pcache $res]
- }
- # Parseoptions
- # Parse lpoptions -d output. It has three forms
- # option-key
- # option-key=option-value
- # option-key='option value with spaces'
- # Arguments:
- # data - data to process.
- #
- proc ::tk::print::cups::Parseoptions {data} {
- set res {}
- set re {[^ =]+|[^ ]+='[^']+'|[^ ]+=[^ ']+}
- foreach tok [regexp -inline -all $re $data] {
- lassign [split $tok "="] k v
- dict set res $k [string trim $v "'"]
- }
- return $res
- }
- proc ::tk::print::cups::print {printer data args} {
- variable pcache
- if {$printer ni [dict keys $pcache]} {
- return -code error "unknown printer or class \"$printer\""
- }
- set title "Tk print job"
- set options {
- -colormode -copies -format -margins -media -nup -orientation
- -prettyprint -title -tzoom
- }
- while {[llength $args]} {
- set opt [tcl::prefix match $options [lpop args 0]]
- switch $opt {
- -colormode {
- set opts {auto monochrome color}
- set val [tcl::prefix match $opts [lpop args 0]]
- lappend printargs -o print-color-mode=$val
- }
- -copies {
- set val [lpop args 0]
- if {![string is integer -strict $val] ||
- $val < 0 || $val > 100
- } {
- # save paper !!
- return -code error "copies must be an integer\
- between 0 and 100"
- }
- lappend printargs -o copies=$val
- }
- -format {
- set opts {auto pdf postscript text}
- set val [tcl::prefix match $opts [lpop args 0]]
- # lpr uses auto always
- }
- -margins {
- set val [lpop args 0]
- if {[llength $val] != 4 ||
- ![string is integer -strict [lindex $val 0]] ||
- ![string is integer -strict [lindex $val 1]] ||
- ![string is integer -strict [lindex $val 2]] ||
- ![string is integer -strict [lindex $val 3]]
- } {
- return -code error "margins must be a list of 4\
- integers: top left bottom right"
- }
- lappend printargs -o page-top=[lindex $val 0]
- lappend printargs -o page-left=[lindex $val 1]
- lappend printargs -o page-bottom=[lindex $val 2]
- lappend printargs -o page-right=[lindex $val 3]
- }
- -media {
- set opts {a4 legal letter}
- set val [tcl::prefix match $opts [lpop args 0]]
- lappend printargs -o media=$val
- }
- -nup {
- set val [lpop args 0]
- if {$val ni {1 2 4 6 9 16}} {
- return -code error "number-up must be 1, 2, 4, 6, 9 or\
- 16"
- }
- lappend printargs -o number-up=$val
- }
- -orientation {
- set opts {portrait landscape}
- set val [tcl::prefix match $opts [lpop args 0]]
- if {$val eq "landscape"}
- lappend printargs -o landscape=true
- }
- -prettyprint {
- lappend printargs -o prettyprint=true
- # prettyprint mess with these default values if set
- # so we force them.
- # these will be overriden if set after this point
- if {[lsearch $printargs {cpi=*}] == -1} {
- lappend printargs -o cpi=10.0
- lappend printargs -o lpi=6.0
- }
- }
- -title {
- set title [lpop args 0]
- }
- -tzoom {
- set val [lpop args 0]
- if {![string is double -strict $val] ||
- $val < 0.5 || $val > 2.0
- } {
- return -code error "text zoom must be a number between\
- 0.5 and 2.0"
- }
- # CUPS text filter defaults to lpi=6 and cpi=10
- lappend printargs -o cpi=[expr {10.0 / $val}]
- lappend printargs -o lpi=[expr {6.0 / $val}]
- }
- default {
- # shouldn't happen
- }
- }
- }
- # build our options
- lappend printargs -T $title
- lappend printargs -P $printer
- # open temp file
- set fd [file tempfile fname tk_print]
- chan configure $fd -translation binary
- chan puts $fd $data
- chan close $fd
- # add -r to automatically delete temp files
- exec lpr {*}$printargs -r $fname &
- }
- namespace eval ::tk::print::cups {
- namespace export defaultprinter getprinters print
- namespace ensemble create
- }
- };# ::tk::print::cups
- namespace eval ::tk::print {
- variable mcmap
- set mcmap(media) [dict create \
- [mc "Letter"] letter \
- [mc "Legal"] legal \
- [mc "A4"] a4]
- set mcmap(orient) [dict create \
- [mc "Portrait"] portrait \
- [mc "Landscape"] landscape]
- set mcmap(color) [dict create \
- [mc "RGB"] color \
- [mc "Grayscale"] gray]
- # available print options
- variable optlist
- set optlist(printer) {}
- set optlist(media) [dict keys $mcmap(media)]
- set optlist(orient) [dict keys $mcmap(orient)]
- set optlist(color) [dict keys $mcmap(color)]
- set optlist(number-up) {1 2 4 6 9 16}
- # selected options
- variable option
- set option(printer) {}
- # Initialize with sane defaults.
- set option(copies) 1
- set option(media) [mc "A4"]
- # Canvas options
- set option(orient) [mc "Portrait"]
- set option(color) [mc "RGB"]
- set option(czoom) 100
- # Text options.
- # See libcupsfilter's cfFilterTextToPDF() and cups-filters's texttopdf
- # known options:
- # prettyprint, wrap, columns, lpi, cpi
- set option(number-up) 1
- set option(tzoom) 100; # we derive lpi and cpi from this value
- set option(pprint) 0 ; # pretty print
- set option(margin-top) 20 ; # ~ 7mm (~ 1/4")
- set option(margin-left) 20 ; # ~ 7mm (~ 1/4")
- set option(margin-right) 20 ; # ~ 7mm (~ 1/4")
- set option(margin-bottom) 20 ; # ~ 7mm (~ 1/4")
- # array to collect printer information
- variable pinfo
- array set pinfo {}
- # a map for printer state -> human readable message
- variable statemap
- dict set statemap 3 [mc "Idle"]
- dict set statemap 4 [mc "Printing"]
- dict set statemap 5 [mc "Printer stopped"]
- }
- # ttk version of [tk_optionMenu]
- # var should be a full qualified varname
- proc ::tk::print::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 - list of printers, state and options.
- # Arguments:
- # none.
- #
- proc ::tk::print::_setprintenv {} {
- variable option
- variable optlist
- variable pinfo
- set optlist(printer) {}
- dict for {printer options} [cups getprinters] {
- lappend optlist(printer) $printer
- set pinfo($printer) $options
- }
- # It's an error to not have any printer configured
- if {[llength $optlist(printer)] == 0} {
- return -code error "No installed printers found.\
- Please check or update your CUPS installation."
- }
- # If no printer is selected, check for the default one
- # If none found, use the first one from the list
- if {$option(printer) eq ""} {
- set option(printer) [cups defaultprinter]
- if {$option(printer) eq ""} {
- set option(printer) [lindex $optlist(printer) 0]
- }
- }
- }
- # _print
- # Main printer dialog.
- # Select printer, set options, and fire print command.
- # Arguments:
- # w - widget with contents to print.
- #
- proc ::tk::print::_print {w} {
- variable optlist
- variable option
- variable pinfo
- variable statemap
- # 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
- set class [winfo class $w]
- if {$class ni {Text Canvas}} {
- return -code error "printing windows of class \"$class\"\
- is not supported"
- }
- # Should this be called with every invocaton?
- # Yes. It allows dynamic discovery of newly added printers
- # whithout having to restart the app
- _setprintenv
- set p ._print
- destroy $p
- # Copy the current values to a dialog's temporary variable.
- # This allow us to cancel the dialog discarding any changes
- # made to the options
- namespace eval dlg {variable option}
- array set dlg::option [array get option]
- set var [namespace which -variable dlg::option]
- # 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
- wm transient $p [winfo toplevel $w]
- # 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"]"
- set tv [ttk::treeview $pf.prlist -height 5 \
- -columns {printer location state} \
- -show headings \
- -selectmode browse]
- $tv configure \
- -yscrollcommand [namespace code [list _scroll $pf.sy]] \
- -xscrollcommand [namespace code [list _scroll $pf.sx]]
- ttk::scrollbar $pf.sy -command [list $tv yview]
- ttk::scrollbar $pf.sx -command [list $tv xview] -orient horizontal
- $tv heading printer -text [mc "Printer"]
- $tv heading location -text [mc "Location"]
- $tv heading state -text [mc "State"]
- $tv column printer -width 200 -stretch 0
- $tv column location -width 100 -stretch 0
- $tv column state -width 250 -stretch 0
- foreach printer $optlist(printer) {
- set location [dict getdef $pinfo($printer) printer-location ""]
- set nstate [dict getdef $pinfo($printer) printer-state 0]
- set state [dict getdef $statemap $nstate ""]
- switch -- $nstate {
- 3 - 4 {
- set accepting [dict getdef $pinfo($printer) \
- printer-is-accepting-jobs ""]
- if {$accepting ne ""} {
- append state ". " [mc "Printer is accepting jobs"]
- }
- }
- 5 {
- set reason [dict getdef $pinfo($printer) \
- printer-state-reasons ""]
- if {$reason ne ""} {
- append state ". (" $reason ")"
- }
- }
- }
- set id [$tv insert {} end \
- -values [list $printer $location $state]]
- if {$option(printer) eq $printer} {
- $tv selection set $id
- }
- }
- grid $pf.printerl -sticky w
- grid $pf.prlist $pf.sy -sticky news
- grid $pf.sx -sticky ew
- grid remove $pf.sy $pf.sx
- bind $tv <<TreeviewSelect>> [namespace code {_onselect %W}]
- # 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 -textvariable ${var}(copies) \
- -from 1 -to 1000
- grid $of.copiesl $of.copies -sticky ew -padx 2p -pady 2p
- $of.copies state readonly
- # PAPER SIZE
- ttk::label $of.medial -text "[mc "Paper"] :"
- ttk_optionMenu $of.media ${var}(media) {*}$optlist(media)
- grid $of.medial $of.media -sticky ew -padx 2p -pady 2p
- if {$class eq "Canvas"} {
- # additional options for Canvas output
- # SCALE
- ttk::label $of.percentl -text "[mc "Scale"] :"
- ttk::spinbox $of.percent -textvariable ${var}(czoom) \
- -from 5 -to 500 -increment 5
- grid $of.percentl $of.percent -sticky ew -padx 2p -pady 2p
- $of.percent state readonly
- # ORIENT
- ttk::label $of.orientl -text "[mc "Orientation"] :"
- ttk_optionMenu $of.orient ${var}(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 ${var}(color) {*}$optlist(color)
- grid $of.colorl $of.color -sticky ew -padx 2p -pady 2p
- } elseif {$class eq "Text"} {
- # additional options for Text output
- # NUMBER-UP
- ttk::label $of.nupl -text "[mc "Pages per sheet"] :"
- ttk_optionMenu $of.nup ${var}(number-up) {*}$optlist(number-up)
- grid $of.nupl $of.nup -sticky ew -padx 2p -pady 2p
- # TEXT SCALE
- ttk::label $of.tzooml -text "[mc "Text scale"] :"
- ttk::spinbox $of.tzoom -textvariable ${var}(tzoom) \
- -from 50 -to 200 -increment 5
- grid $of.tzooml $of.tzoom -sticky ew -padx 2p -pady 2p
- $of.tzoom state readonly
- # PRETTY PRINT (banner on top)
- ttk::checkbutton $of.pprint -onvalue 1 -offvalue 0 \
- -text [mc "Pretty print"] \
- -variable ${var}(pprint)
- grid $of.pprint - -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 $class $p]]
- ttk::button $bf.cancel -text [mc "Cancel"] \
- -command [list destroy $p]
- pack $bf.print -side right
- pack $bf.cancel -side right -padx {0 4.5p}
- # cleanup binding
- bind $bf <Destroy> [namespace code [list _cleanup $p]]
- # Center the window as a dialog.
- ::tk::PlaceWindow $p
- }
- # _onselect
- # Updates the selected printer when treeview selection changes.
- # Arguments:
- # tv - treeview pathname.
- #
- proc ::tk::print::_onselect {tv} {
- variable dlg::option
- set id [$tv selection]
- if {$id eq ""} {
- # is this even possible?
- set option(printer) ""
- } else {
- set option(printer) [$tv set $id printer]
- }
- }
- # _scroll
- # Implements autoscroll for the printers view
- #
- proc ::tk::print::_scroll {sbar from to} {
- if {$from == 0.0 && $to == 1.0} {
- grid remove $sbar
- } else {
- grid $sbar
- $sbar set $from $to
- }
- }
- # _cleanup
- # Perform cleanup when the dialog is destroyed.
- # Arguments:
- # p - print dialog pathname (not used).
- #
- proc ::tk::print::_cleanup {p} {
- namespace delete dlg
- }
- # _runprint -
- # Execute the print command--print the file.
- # Arguments:
- # w - widget with contents to print.
- # class - class of the widget to print (Canvas or Text).
- # p - print dialog pathname.
- #
- proc ::tk::print::_runprint {w class p} {
- variable option
- variable mcmap
- # copy the values back from the dialog
- array set option [array get dlg::option]
- # get (back) name of media from the translated one
- set media [dict get $mcmap(media) $option(media)]
- set printargs {}
- lappend printargs -title "[tk appname]: Tk window $w"
- lappend printargs -copies $option(copies)
- lappend printargs -media $media
- if {$class eq "Canvas"} {
- set colormode [dict get $mcmap(color) $option(color)]
- set rotate 0
- if {[dict get $mcmap(orient) $option(orient)] eq "landscape"} {
- set rotate 1
- }
- # Scale based on size of widget, not size of paper.
- # TODO: is this correct??
- set printwidth [expr {
- $option(czoom) / 100.0 * [winfo width $w]
- }]
- set data [encoding convertto iso8859-1 [$w postscript \
- -colormode $colormode -rotate $rotate -pagewidth $printwidth]]
- } elseif {$class eq "Text"} {
- set tzoom [expr {$option(tzoom) / 100.0}]
- if {$option(tzoom) != 100} {
- lappend printargs -tzoom $tzoom
- }
- if {$option(pprint)} {
- lappend printargs -prettyprint
- }
- if {$option(number-up) != 1} {
- lappend printargs -nup $option(number-up)
- }
- # these are hardcoded. Should we allow the user to control
- # margins?
- lappend printargs -margins [list \
- $option(margin-top) $option(margin-left) \
- $option(margin-bottom) $option(margin-right) ]
- # get the data in shape. Cupsfilter's text filter wraps lines
- # at character level, not words, so we do it by ourselves.
- # compute usable page width in inches
- set pw [dict get {a4 8.27 legal 8.5 letter 8.5} $media]
- set pw [expr {
- $pw - ($option(margin-left) + $option(margin-right)) / 72.0
- }]
- # set the wrap length at 98% of computed page width in chars
- # the 9.8 constant is the product 10.0 (default cpi) * 0.95
- set wl [expr {int( 9.8 * $pw / $tzoom )}]
- set data [encoding convertto utf-8 [_wrapLines [$w get 1.0 end] $wl]]
- }
- # launch the job in the background
- after idle [namespace code \
- [list cups print $option(printer) $data {*}$printargs]]
- destroy $p
- }
- # _wrapLines -
- # wrap long lines into lines of at most length wl at word boundaries
- # Arguments:
- # str - string to be wrapped
- # wl - wrap length
- #
- proc ::tk::print::_wrapLines {str wl} {
- # This is a really simple algorithm: it breaks a line on space or tab
- # character, collapsing them only at the breaking point.
- # Leading space is left as-is.
- # For a full fledged line breaking algorithm see
- # Unicode® Standard Annex #14 "Unicode Line Breaking Algorithm"
- set res {}
- incr wl -1
- set re [format {((?:^|[^[:blank:]]).{0,%d})(?:[[:blank:]]|$)} $wl]
- foreach line [split $str \n] {
- lappend res {*}[lmap {_ l} [regexp -all -inline -- $re $line] {
- set l
- }]
- }
- return [join $res \n]
- }
- }
- #end X11 procedures
- namespace eval ::tk::print {
- #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" {
- ::tk::print::_printcanvas $w
- set printfile /tmp/tk_canvas.pdf
- ::tk::print::_print $printfile
- }
- "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 [file join /tmp 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:
|