Assume I have a TK canvas witch contains a text item. I insert several lines of text. I want to know the pixel coordinants of a specific letter so I can align a graphic with the text. I know the line number and the column number of the letter. I know I can easily use bbox to find the coordinants of the text item but I can't figure out how to get the coordinants of a specific letter.One possible answer to his question is the following procedure. The demo main program that follows it illustrates what it does by creating a canvas, creating a text item within it, and outlining the bounding box of each character within the item.--KBK (22 Jan 2001)
Bryan Oakley 22 Nov 2005 - Kevin, this rocks! I added one tiny bit of code to normalize the index; without this it chokes if you pass in "insert" or any of the other non-numeric forms of indices.
Usman Muzaffar 15 Jun 2006 - I think there's a bug in the calculation of charYMax: it's incorrectly using itemYMin instead of itemXMin as the x-coordinate. What's surprising is how often this doesn't make a difference!}
#----------------------------------------------------------------------
#
# canvCharBBox --
#
# Compute the bounding box of a single character within a
# text item on a canvas.
#
# Parameters:
# canvas -- Path name of the canvas widget
# tagOrId -- Tag or ID of the text item
# index -- Character index within the text item. It is legal
# for the character index to be equal to the length
# of the string, in which case the returned bounding
# box will give the dimensions of the 'dead space'
# at the right of the bottom line.
#
# Results:
# Returns the bounding box of the selected character, or an empty
# list if the character is not found.
#
# Bugs:
# If a line does not begin at the leftmost extent of the text
# item, the bounding box of the leftmost character in the line
# includes the 'dead space' to the left of the line.
#
# Author:
# Kevin Kenny <kennykb@acm.org>
#
#----------------------------------------------------------------------
proc canvCharBBox { canvas tagOrId index } {
# Begin by locating the bounding box of the entire item
foreach { itemXMin itemYMin itemXMax itemYMax } [$canvas bbox $tagOrId] {
break
}
# Normalize the index
if {![string is integer -strict $index]} {
set index [$canvas index $tagOrId $index]
}
# Locate the greatest Y for which the character index of the
# right edge of the box is less than $index. This co-ordinate
# is the lower bound of Y
set charYMin [expr { $itemYMin - 1 }]
set y0 $itemYMin
set y1 $itemYMax
while { $y1 >= $y0 } {
set y2 [expr { ( $y0 + $y1 ) / 2 }]
if { [$canvas index $tagOrId @$itemXMax,$y2] < $index } {
set charYMin $y2
set y0 [expr { $y2 + 1 }]
} else {
set y1 [expr { $y2 - 1 }]
}
}
# Locate the least Y for which the character index of the left edge
# of the box is greater than $index. This co-ordinate is the upper
# bound of Y
set charYMax [expr { $itemYMax + 1 }]
set y0 [expr { $charYMin + 1 }]
set y1 $itemYMax
while { $y1 >= $y0 } {
set y2 [expr { ( $y0 + $y1 ) / 2 }]
if { [$canvas index $tagOrId @$itemYMin,$y2] > $index } {
set charYMax $y2
set y1 [expr { $y2 - 1 }]
} else {
set y0 [expr { $y2 + 1 }]
}
}
# Now go probing on the midpoint of the line of characters for
# the greatest X for which the character index is less than
# $index. This co-ordinate is the lower bound for X
set charXMin [expr { $itemXMin - 1 }]
set y [expr { ( $charYMin + $charYMax ) / 2 }]
set x0 $itemXMin
set x1 $itemXMax
while { $x1 >= $x0 } {
set x2 [expr { ( $x0 + $x1 ) / 2 }]
if { [$canvas index $tagOrId @$x2,$y] < $index } {
set charXMin $x2
set x0 [expr { $x2 + 1 }]
} else {
set x1 [expr { $x2 - 1 }]
}
}
# Finally, probe on the midpoint of the line of characters for
# the smallest X for which the character index is greater than
# $index. This co-ordinate is the upper bound of X.
set charXMax [expr { $itemXMax + 1 }]
set x0 [expr { $charXMin + 1 }]
set x1 $itemXMax
while { $x1 >= $x0 } {
set x2 [expr { ( $x0 + $x1 ) / 2 }]
if { [$canvas index $tagOrId @$x2,$y] > $index } {
set charXMax $x2
set x1 [expr { $x2 - 1 }]
} else {
set x0 [expr { $x2 + 1 }]
}
}
# Return the computed bounding box.
return [list \
[expr { $charXMin + 1 }] \
[expr { $charYMin + 1 }] \
[expr { $charXMax - 1 }] \
[expr { $charYMax - 1 }] ]
}
# DEMO
grid [canvas .c -width 200 -height 200]
set string "Joe's\nBar and\nGrill"
.c create text 100 100 -text $string \
-justify center \
-font {Helvetica 36 bold} -fill white -tags theText
for { set i 0 } { $i <= [string length $string] } { incr i } {
foreach { x0 y0 x1 y1 } [canvCharBBox .c theText $i] break
puts [format "char %2d: %3d %3d %3d %3d" $i $x0 $y0 $x1 $y1]
set x2 [expr { ( $x0 + $x1 ) / 2 }]
set y2 [expr { ( $y0 + $y1 ) / 2 }]
.c create rectangle $x0 $y0 $x1 $y1 -fill {} -outline black
.c create text $x2 $y2 -text $i -font {Helvetica 8} -fill black
}
