Flopp
Flopp

Reputation: 1947

Highlight parts of text items in a canvas

I want to fill a canvas widget with some text items and highlight parts of these texts (think of highlighting a search phrase in all text items of a canvas).

Of course, one could split the individual texts into highlighted and non-highlighted parts and add separate text items for these parts, e.g.

#!/usr/bin/env wish

proc _drawText {canvas x y text highlighted} {
    if {$text != {}} {
        set cmd [list $canvas create text $x $y -anchor nw -text $text]
        if {$highlighted == "1"} {
            lappend cmd -fill red
        }
        set t [{*}$cmd]
        # advance x to the bbox' right side
        set x [lindex [$canvas bbox $t] 2]
        # adjust x, because the bbox seems to be 1 pixel too wide :(
        incr x -1
    }
    return $x
}

proc addText {canvas x y text {highlightedChars {}}} {
    set highlighted "0"
    set chars ""
    foreach c [split $text {}] h [split $highlightedChars {}] {
        if {$h != $highlighted} {           
            set x [_drawText $canvas $x $y $chars $highlighted]
            set highlighted $h
            set chars ""
        }
        append chars $c
    }
    _drawText $canvas $x $y $chars $highlighted
}

canvas .c
pack .c -expand 1 -fill both

# add "some example text" and highlight the two occurrences of "ex"
addText .c 0 0 \
    "some example text" \
    "00000110000000110"

This is a very limited approach (-> only works for nw anchored texts, line breaking would have to be implemented manually, etc.), so I wonder if there's a better way to do that. Any ideas?

Upvotes: 0

Views: 508

Answers (1)

Donal Fellows
Donal Fellows

Reputation: 137717

First off, the text widget might be more suitable for your problem as described as it has support for more complex tagging of text. Canvases only support a single selected range of a single item, and it sounds like they're not going to provide a lot of support for the more complex stuff that you're doing. Canvases are similarly complex to text widgets, but focus their complexity elsewhere; to do complicated text display on them, you have to do quite a bit of scripting.

However, you can fairly easily find out what the positioning of the text is. Firstly, (as long as the text isn't rotated) you can ignore the anchor point and anchoring control and just use the overall bounding box. Secondly, to handle multi-line text, you do need to know the justification rule, but that really just says how to place each line horizontally within it's subsection of the bounding box.

Thirdly, as canvas text items have a single font, you can easily do the text computations with the help of the font measure and font metrics. The font measure command lets you determine how wide a piece of text is in a particular font without displaying it on the screen (provided the text has no newlines in it). The font metrics command lets you find out what the -linespace is for the font, i.e., how much space should there be between lines, and what the -ascent and -descent are (where the top and bottom of the line are relative to the text's baseline). These commands call into exactly the same font engine that Tk uses to actually display text; you do not need to hard-code any knowledge of the size and shape of the font you're using.

Surprisingly, there's no simple mechanism for converting an item index into a canvas coordinate pair.

Upvotes: 2

Related Questions