Arjen Markus (26 October 2009) Having experimented a bit with the pdf4tcl package I found it a very useful package for generating PDF files, but there were too many details to take care of: the commands it offers are somewhat low-level. Luckily it is easy to wrap them into commands that hide the details.
(14 December 2009) Since the very first, incomplete version I have worked out some more ideas. The text I use in the example is simply nonsense and it does not cover all current possibilities, but you get the idea anyway.
Note: I have used Snit to create the wrapper interface, as pdf4tcl itself uses Snit too. As it is my first serious use of Snit, I can probably improve on it.
Second note: Things that are not quite satisfactory at the moment are the positioning of page breaks and the occurrence of empty lines at the start of a page. This makes the layout of the pages somewhat sloppy. But I am working on it.
Third note: I am using this to convert the text files I have for my "young programmers' project" - Teach programming to children
::snit::type document { variable pdf variable yCoord variable lineHeight variable header_even variable header_odd variable header_font variable footer_even variable footer_odd variable footer_font variable yHeader variable yFooter variable page_number variable doc_font constructor {args} { #set pdf [::pdf4tcl::new %AUTO% {*}$args] set pdf [::pdf4tcl::new %AUTO%] $pdf configure -margin {10m 10m 10m 10m} set lineHeight [expr {1.3*12}] set yCoord 0 set doc_font "12 Times-Roman" $pdf setFont 12 Times-Roman set header_font "8 Times-Roman" set footer_font "8 Times-Roman" set header_even {"" "" ""} set header_odd {"" "" ""} set footer_even {"" "" "#"} set footer_odd {"#" "" ""} foreach {pageWidth pageHeight} [$pdf getDrawableArea] {break} set yHeader 0 set yFooter [expr {$pageHeight - $lineHeight}] set page_number 1 } method chapter {title} { $self startPage 0 $pdf setFont 24 Helvetica-Bold set yCoord [$pdf getFontMetric ascend] $pdf setTextPosition 0 $yCoord $pdf text $title $pdf setFont 12 Times-Roman set yCoord 36 } method section {title} { $pdf setFont 18 Times-Bold set lineHeight [expr {1.3*18}] $self paragraph $title $pdf setFont 12 Times-Roman set lineHeight [expr {1.3*12}] } method subsection {title} { $pdf setFont 14 Times-Italic set lineHeight [expr {1.3*14}] $self paragraph $title $pdf setFont 12 Times-Roman set lineHeight [expr {1.3*12}] } method paragraph {text {margin 0}} { set text [string map {\n " "} $text] foreach {pageWidth pageHeight} [$pdf getDrawableArea] {break} while { $text != "" } { set text [$pdf drawTextBox $margin $yCoord [expr {$pageWidth-$margin}] $lineHeight $text -align justify] set yCoord [expr {$yCoord + $lineHeight}] if { $yCoord > $pageHeight-$lineHeight } { $self startPage 1 set yCoord [expr {2*$lineHeight}] } } $pdf newLine } method newLine {} { set yCoord [expr {$yCoord + $lineHeight}] } method bullet {text} { set xcentre [expr {0.5 * $lineHeight}] set ycentre [expr {$yCoord + 0.3 * $lineHeight}] $pdf circle $xcentre $ycentre 2p -filled 1 $self paragraph $text $lineHeight } method bullet2 {text} { set xcentre [expr {1.5 * $lineHeight}] set ycentre [expr {$yCoord + 0.3 * $lineHeight}] $pdf circle $xcentre $ycentre 1.2p -filled 1 $self paragraph $text [expr {2.0*$lineHeight}] } method code {text} { set doc_font "10 Courier" $pdf setFont 10 Courier set lineHeight [expr {1.3 * 10}] set charWidth [$pdf getCharWidth " "] foreach {pageWidth pageHeight} [$pdf getDrawableArea] {break} foreach line [split $text \n] { set margin [expr {$charWidth * ([string length [regexp -inline {^ *} $line]] - 2)}] $pdf drawTextBox $margin $yCoord [expr {$pageWidth-$margin}] $lineHeight $line set yCoord [expr {$yCoord + $lineHeight}] if { $yCoord > $pageHeight } { $self startPage 1 set yCoord $lineHeight } } $pdf newLine set doc_font "12 Times-Roman" $pdf setFont 12 Times-Roman set lineHeight [expr {1.3 * 12}] } method setFont {size font} { $pdf setFont $size $font set lineHeight [expr {1.3*$size}] } method canvas {cnv width} { tkwait visibility $cnv set cwidth [$cnv cget -width] set cheight [$cnv cget -height] set width [expr {25.4*$width}] ;# Unit in pdf4tcl is unclear set height [expr {$width * $cheight / double($cwidth)}] # -sticky not honoured? set paperwidth [lindex [$pdf getDrawableArea] 0] $pdf canvas $cnv -sticky n -x [expr {0.5*($paperwidth-$width)}] -y $yCoord -width $width set yCoord [expr {$yCoord + $height}] destroy $cnv } method getpdf {} { return $pdf } method write {filename} { $pdf write -file $filename $pdf destroy } method {header font} {font} { set header_font $font } method {header both} {left middle right} { set header_even [list $left $middle $right] set header_odd $header_even } method {header even} {left middle right} { set header_even [list $left $middle $right] } method {header odd} {left middle right} { set header_odd [list $left $middle $right] } method {footer font} {font} { set footer_font $font } method {footer both} {left middle right} { set footer_even [list $left $middle $right] set footer_odd $footer_even } method {footer even} {left middle right} { set footer_even [list $left $middle $right] } method {footer odd} {left middle right} { set footer_odd [list $left $middle $right] } method startPage {addHeader} { $pdf startPage set margin 0 foreach {pageWidth pageHeight} [$pdf getDrawableArea] {break} if { $addHeader } { $pdf setFont {*}$header_font set yCoord $yHeader if { $page_number %2 == 0 } { set texts $header_even } else { set texts $header_odd } foreach text $texts align {left center right} { set text [string map [list # $page_number] $text] $pdf drawTextBox $margin $yCoord [expr {$pageWidth-$margin}] $lineHeight $text -align $align } } $pdf setFont {*}$footer_font set yCoord $yFooter if { $page_number %2 == 0 } { set texts $footer_even } else { set texts $footer_odd } foreach text $texts align {left center right} { set text [string map [list # $page_number] $text] $pdf drawTextBox $margin $yCoord [expr {$pageWidth-$margin}] $lineHeight $text -align $align } $pdf setFont {*}$doc_font incr page_number } } document doc doc chapter "Chapter 1" doc paragraph "Some longish text that clearly wraps around the right margin of the page, so that we can demonstrate the aligning behaviour of this command. And of course how you put in a paragraph of text into the document, using a simple, high-level method." doc section "Demonstration of a section" doc subsection "Demonstration of a subsection" doc paragraph "Does it do pages?" for { set i 0 } { $i < 50 } { incr i } { doc paragraph "Line $i" } doc chapter "Chapter 2" doc paragraph "Again some text - mostly for fun" doc paragraph "What about two paragraphs?" doc bullet "Bullet 1, but let's demonstrate the wrapping for this type of text entity too. After all we do want it to be useful in a fairly general context" doc newLine doc bullet "Bullet 2" doc bullet "Bullet 3" doc bullet2 "Second level bullet - no more levels" doc code { What about writing some text in the style of "code"? proc putTitle {string} { global currentY book setFont 24 Times-BoldItalic book text [string range $string 3 end-3] book setFont 12 Times-Roman set currentY [expr {$currentY + 24}] } } ;# End of "doc code" doc write "mydocument.pdf"
drkoru - 2017-06-18 19:09:12
I agree that the above can be a useful wrapper for those who would like to have some high level functions above those provided by pdf4tcl. Below, I provide the same script with TclOO.
::oo::class create document { variable pdf variable yCoord variable lineHeight variable header_even variable header_odd variable header_font variable footer_even variable footer_odd variable footer_font variable yHeader variable yFooter variable page_number variable doc_font constructor {args} { #set pdf [::pdf4tcl::new %AUTO% {*}$args] set pdf [::pdf4tcl::new %AUTO%] $pdf configure -margin {10m 10m 10m 10m} set lineHeight [expr {1.3*12}] set yCoord 0 set doc_font "12 Times-Roman" $pdf setFont 12 Times-Roman set header_font "8 Times-Roman" set footer_font "8 Times-Roman" set header_even {"" "" ""} set header_odd {"" "" ""} set footer_even {"" "" "#"} set footer_odd {"#" "" ""} foreach {pageWidth pageHeight} [$pdf getDrawableArea] {break} set yHeader 0 set yFooter [expr {$pageHeight - $lineHeight}] set page_number 1 } method chapter {title} { my startPage 0 $pdf setFont 24 Helvetica-Bold set yCoord [$pdf getFontMetric ascend] $pdf setTextPosition 0 $yCoord $pdf text $title $pdf setFont 12 Times-Roman set yCoord 36 } method section {title} { $pdf setFont 18 Times-Bold set lineHeight [expr {1.3*18}] my paragraph $title $pdf setFont 12 Times-Roman set lineHeight [expr {1.3*12}] } method subsection {title} { $pdf setFont 14 Times-Italic set lineHeight [expr {1.3*14}] my paragraph $title $pdf setFont 12 Times-Roman set lineHeight [expr {1.3*12}] } method paragraph {text {margin 0}} { set text [string map {\n " "} $text] foreach {pageWidth pageHeight} [$pdf getDrawableArea] {break} while { $text != "" } { set text [$pdf drawTextBox $margin $yCoord [expr {$pageWidth-$margin}] $lineHeight $text -align justify] set yCoord [expr {$yCoord + $lineHeight}] if { $yCoord > $pageHeight-$lineHeight } { my startPage 1 set yCoord [expr {2*$lineHeight}] } } $pdf newLine } method newLine {} { set yCoord [expr {$yCoord + $lineHeight}] } method bullet {text} { set xcentre [expr {0.5 * $lineHeight}] set ycentre [expr {$yCoord + 0.3 * $lineHeight}] $pdf circle $xcentre $ycentre 2p -filled 1 my paragraph $text $lineHeight } method bullet2 {text} { set xcentre [expr {1.5 * $lineHeight}] set ycentre [expr {$yCoord + 0.3 * $lineHeight}] $pdf circle $xcentre $ycentre 1.2p -filled 1 my paragraph $text [expr {2.0*$lineHeight}] } method code {text} { set doc_font "10 Courier" $pdf setFont 10 Courier set lineHeight [expr {1.3 * 10}] set charWidth [$pdf getCharWidth " "] foreach {pageWidth pageHeight} [$pdf getDrawableArea] {break} foreach line [split $text \n] { set margin [expr {$charWidth * ([string length [regexp -inline {^ *} $line]] - 2)}] $pdf drawTextBox $margin $yCoord [expr {$pageWidth-$margin}] $lineHeight $line set yCoord [expr {$yCoord + $lineHeight}] if { $yCoord > $pageHeight } { my startPage 1 set yCoord $lineHeight } } $pdf newLine set doc_font "12 Times-Roman" $pdf setFont 12 Times-Roman set lineHeight [expr {1.3 * 12}] } method setFont {size font} { $pdf setFont $size $font set lineHeight [expr {1.3*$size}] } method canvas {cnv width} { tkwait visibility $cnv set cwidth [$cnv cget -width] set cheight [$cnv cget -height] set width [expr {25.4*$width}] ;# Unit in pdf4tcl is unclear set height [expr {$width * $cheight / double($cwidth)}] # -sticky not honoured? set paperwidth [lindex [$pdf getDrawableArea] 0] $pdf canvas $cnv -sticky n -x [expr {0.5*($paperwidth-$width)}] -y $yCoord -width $width set yCoord [expr {$yCoord + $height}] destroy $cnv } method getpdf {} { return $pdf } method write {filename} { $pdf write -file $filename $pdf destroy } method {header font} {font} { set header_font $font } method {header both} {left middle right} { set header_even [list $left $middle $right] set header_odd $header_even } method {header even} {left middle right} { set header_even [list $left $middle $right] } method {header odd} {left middle right} { set header_odd [list $left $middle $right] } method {footer font} {font} { set footer_font $font } method {footer both} {left middle right} { set footer_even [list $left $middle $right] set footer_odd $footer_even } method {footer even} {left middle right} { set footer_even [list $left $middle $right] } method {footer odd} {left middle right} { set footer_odd [list $left $middle $right] } method startPage {addHeader} { $pdf startPage set margin 0 foreach {pageWidth pageHeight} [$pdf getDrawableArea] {break} if { $addHeader } { $pdf setFont {*}$header_font set yCoord $yHeader if { $page_number %2 == 0 } { set texts $header_even } else { set texts $header_odd } foreach text $texts align {left center right} { set text [string map [list # $page_number] $text] $pdf drawTextBox $margin $yCoord [expr {$pageWidth-$margin}] $lineHeight $text -align $align } } $pdf setFont {*}$footer_font set yCoord $yFooter if { $page_number %2 == 0 } { set texts $footer_even } else { set texts $footer_odd } foreach text $texts align {left center right} { set text [string map [list # $page_number] $text] $pdf drawTextBox $margin $yCoord [expr {$pageWidth-$margin}] $lineHeight $text -align $align } $pdf setFont {*}$doc_font incr page_number } } document create doc doc chapter "Chapter 1" doc paragraph "Some longish text that clearly wraps around the right margin of the page, so that we can demonstrate the aligning behaviour of this command. And of course how you put in a paragraph of text into the document, using a simple, high-level method." doc section "Demonstration of a section" doc subsection "Demonstration of a subsection" doc paragraph "Does it do pages?" for { set i 0 } { $i < 50 } { incr i } { doc paragraph "Line $i" } doc chapter "Chapter 2" doc paragraph "Again some text - mostly for fun" doc paragraph "What about two paragraphs?" doc bullet "Bullet 1, but let's demonstrate the wrapping for this type of text entity too. After all we do want it to be useful in a fairly general context" doc newLine doc bullet "Bullet 2" doc bullet "Bullet 3" doc bullet2 "Second level bullet - no more levels" doc code { What about writing some text in the style of "code"? proc putTitle {string} { global currentY book setFont 24 Times-BoldItalic book text [string range $string 3 end-3] book setFont 12 Times-Roman set currentY [expr {$currentY + 24}] } } ;# End of "doc code" doc write "mydocument.pdf"
APE (24 July 2023) Add a method to add an image (no error checking on path by now)
method image {filePath} { image create photo img -file $filePath set imgdata [img data] set id [$pdf addRawImage $imgdata] set iwidth [$pdf getImageWidth $id] set iheight [$pdf getImageHeight $id] lassign [$pdf getDrawableArea] pageWidth pageHeight # take the minimum width between image width and paper width set width [expr min($iwidth,$pageWidth)] set height [expr {$width * $iheight / double($iwidth)}] # check height space, start a new page if necessary set yImgCoord [expr {$yCoord + $height}] if { $yImgCoord > $pageHeight } { my startPage 0 set yCoord 0 } $pdf putImage $id 0 $yCoord -width $width set yCoord [expr {$yCoord + $height}] }