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}]
}