Updated 2013-01-11 23:23:45 by RLE

Fun and games with Tkhtml 3.0

Tkhtml 3.0 is a new html rendering widget for Tk. It is a rewrite of the existing Tkhtml widget (2.0) that is included with ActiveTcl and other batteries included distributions. This page is an informal description of the new widget that aims to showcase the capabilities and design of the new widget and stimulate discussion without getting bogged down in details. So instead of explaining interfaces there are just code fragments. Please leap in to ask questions, make suggestions or heap ridicule.

The full widget API reference can be found online here: http://tkhtml.tcl.tk/tkhtml.html .

Introduction edit

By itself Tkhtml 3.0 doesn't provide much of the functionality that embedding a web browser would. Tkhtml has no idea that there is anything special about an <a> tag, let alone the "href" attribute, so clicking on a hyper-link does nothing by default. Without extra configuration, Tkhtml doesn't understand the significance of a <style>, <title> or <img> tag.

Instead, the widget concentrates on providing interfaces that allow the majority of policy decisions to be made by the user. For example, the widget can be queried for the document node that generated the content at any point in the viewport. When an end-user makes a mouse-click, the script can query the html widget for the relevant document node and decide for itself if a hyper-link should be followed, some javascript executed, the selection cleared, or some other application specific action.

If an application designer deems that the contents of any <style> tags in the document should be parsed as CSS style sheets, (s)he configures Tkhtml to pass the contents of each parsed <style> tag to a handler script that in turn passes it back to the Tkhtml style command.

Tkhtml doesn't really have too much of a dependancy on Html either. Most Html behaviour (i.e. the fact that a <b> tag means use bold font) is configured using a built-in default stylesheet document. Execute the following script if you want to see this document:
  package require Tkhtml 3.0
  html .h
  .h cget -defaultstyle

Right now the parser is a holdover from Tkhtml 2.0 and only recognizes valid html tags. But in the long run it will be possible to display any old XML document formatted using CSS stylesheets.

Tkhtml is designed with the expectation that most users will use a mega-widget that provides many of these features. A mega-widget to support common html constructs, "Hv3", is being developed and distributed as part of Tkhtml itself. But Tkhtml is useful by itself as well, as this page tries to explain.

Running the Examples

First up, go get the widget so you can run the fragments below. A snapshot is available at http://tkhtml.tcl.tk/tkhtml3-alpha-4.tar.gz . To build, read the COMPILE.txt file in the distribution. Just kidding - it's a TEA package, just use the regular configure, make, make install sequence.

While you're there, take a look at the demo app available at http://tkhtml.tcl.tk/hv3.html . This page contains starkits that wrap Tkhtml into a mimimal web browser so you can see how it goes on complicated web pages available via http (aka. the new-fangled "world wide web"). There are screenshots there too.

Note: There's a bug in the alpha 3 release that means trying to load the Tkhtml package before Tk causes an error. Either load Tk first or run the examples with "wish" instead of "tclsh" to workaround the bug.

A Fancy Label Widget edit

The simplest use of the html widget is as a fancy label widget. For example:
  # Load the Tkhtml package
  package require Tkhtml 3.0

  # Create and populate an html widget.
  html .label -shrink 1
  .label parse -final {
    <b>Hello <i>world</i></b> example
  }

  # Pack the new html widget
  pack .label
  bind .label <KeyPress-q> exit
  focus .label

This code creates and packs a somewhat unremarkable label containing the html formatted text. There are no default bindings, those all have to be supplied by the user.

The most visible difference between Tkhtml 3.0 and it's predecessor is that Tkhtml supports stylesheets and the "style" attribute. To illustrate:
  package require Tkhtml 3.0

  # Create and populate an html widget
  html .button -shrink 1
  .button parse -final {<html><div>Exit!</div> Click to Exit :)}
  .button style {
    /* This is a CSS style sheet */
    div {
      border:solid 2px;
      border-color: white grey30 grey30 white;
      background: grey85;
      padding: 5px 0px;
      margin: 0px auto;
      text-align: center;
      width: 10ex;
    }
    html {
      background: grey85;
      font-family: Helvetica;
      font-weight: bold;
      padding: 5px;
    }
  }

  # Pack the new html widget
  pack .button
  bind .button <1> exit
  focus .button

Cute eh? Tkhtml 3.0 supports most of the CSS 1.0 properties and selectors, giving the programmer good control over the document layout. A precise description of what is supported and what is not may be found at http://tkhtml.tcl.tk/support.html .

A Geometry Manager edit

Tkhtml is good for more than just static text and borders, it can also manage other Tk widgets. Here's a simple example - this time featuring a real live button, not a fake like before.
  package require Tkhtml 3.0

  # Create and populate an html widget
  html .manager -shrink 1
  .manager parse -final {
    <html>
    <div
      widgetcmd="button .manager.button -text Exit! -command exit"
      style="margin: auto"
    />
    Click the above button to exit.
  }

  # The tricky bit!
  foreach nodeHandle [.manager search {[widgetcmd]}] {
    $nodeHandle replace [eval [$nodeHandle attr widgetcmd]]
  }

  # Pack the new html widget
  pack .manager

Once Tkhtml has parsed a document, it exposes the tree structure to the application via "node-handles". Each node-handle is itself a Tcl command, with a set of sub-commands that can be used to query and manipulate the document node. For example the command:
  $nodeHandle attr widgetcmd

returns the value of the "widgetcmd" attribute of node $nodeHandle. Assuming the variable $nodeHandle contains the node-handle for the <div> in the example above, this command would return the string "button .manager.button -text Exit! -command exit".

The command:
  $nodeHandle replace $widget

tells Tkhtml that instead of drawing content for the node $nodeHandle, map the Tk window $widget into the html display. The other new command introduced here is:
  $html search $selector

This command searches the document tree for nodes that match the criteria specified by $selector. The format for the search criteria is a CSS 1.0 selector. The selector used in the above example, "[widgetcmd]" matches all nodes that have an attribute named "widgetcmd".

Here's a more elaborate example of the same idea:
  package require Tkhtml 3.0

  # Create and populate an html widget
  html .manager -shrink 1
  .manager parse -final {
    <html>
    <body>
      <h1 style="text-align:center">XYZ Company Complaints Interface</h1>
      <table border=0 align=center style="border:1px solid">
        <tr>
          <td>First Name<span style="color:red">*</span>:
          <td><div widgetcmd="entry .manager.entry1">
        <tr>
          <td>Last Name<span style="color:red">*</span>:
          <td><div widgetcmd="entry .manager.entry2">
        <tr>
          <td>Employer:
          <td><div widgetcmd="entry .manager.entry3">
      </table>
      <hr>
      <div widgetcmd="text .manager.text"/>
      <hr>
      <table border=0 align=center cellpadding=10 style="border:1px solid"><tr>
      <td><div widgetcmd="button .manager.send -text Send -width 20"/>
      <td><div widgetcmd="button .manager.cancel -text Cancel -width 20"/>
      </table>
    </html>
  }

  # Trickery!
  foreach nodeHandle [.manager search {[widgetcmd]}] {
    $nodeHandle replace [eval [$nodeHandle attr widgetcmd]]
  }

  .manager.text insert 0.0 "Enter message here."

  # Pack the new html widget
  pack .manager -fill both -expand true

Please Note: The code in the above examples executes Tcl scripts embedded in the document passed to the html widget. This is Ok, because in this case the document is embedded in the application itself. There is no way for the html widget to get hold of an external document that may contain malicious scripts. It would be very foolhardy indeed to use the same techniques in an application that might obtain documents from external sources. Cough... internet explorer cough...

Tkhtml provides no special support for html forms other than the ability to replace document nodes with Tkhtml windows as demonstrated above. Implementing that sort of thing is up to applications or mega-widget frameworks. This allows the same "replace-node" interface to be used for web plugin implementations, or to embed a canvas widget configured to render an embedded SVG image.

Tkhtml does provide ways to register scripts for execution:

  • When a replacement window is no longer required (e.g. because a newdocument is loaded),
  • To configure the colors, fonts etc. of replaced windows according to the document and stylesheets, and
  • As soon a document node that matches a specified selector has been parsed (allows for incremental parsing of documents with embedded forms).

See the full widget man page at http://tkhtml.tcl.tk/tkhtml.html for details.

Displaying Images edit

Html documents aren't much fun without images. With a little help, Tkhtml can support background-images, list-marker images and images used to replace entire nodes. Here's an example:
  image create photo idir -data {
      R0lGODdhEAAQAPIAAAAAAHh4eLi4uPj4APj4+P///wAAAAAAACwAAAAAEAAQAAADPVi63P4w
      LkKCtTTnUsXwQqBtAfh910UU4ugGAEucpgnLNY3Gop7folwNOBOeiEYQ0acDpp6pGAFArVqt
      hQQAO///
  }
  image create photo ibg -data {
      R0lGODdhIAAbAJEAAAAAAP///wAA/wAAACwAAAAAIAAbAAACeoyPqYvgD6Oc
      LwAhHN758v6BgMVpn1l6KXmqG/q6mQVjNdi+7M3LImvz9UIOYIwolI0eyGbO
      tks6UzPX8LmSYo8lmpaKXQanXKq4jB63jGqwG8rctsvn63t8nutxWfK+tmSX
      xlf3NaiRx3eYxKb4p+Tl50joRWF5OVIAADs=
  }

  package require Tkhtml 3.0

  # Create and populate an html widget
  html .images -shrink 1 -imagecmd get_image -width 200
  .images parse -final {
    <html>
    <body style="background-image: url(ibg); color: white ; font-weight:bold">
    <img src="idir" width=100 height=50 align=left>
    Html documents aren't much fun without images. With a little help, Tkhtml
    can support background-images, list-marker images and images used to
    replace entire nodes. The example only shows backgrounds and replaced
    images.
    </body>
    </html>
  }
  proc get_image {uri} {return $uri}

  pack .images
  bind .images <KeyPress-q> exit
  focus .images

The -imagecmd option of the html widget is configured to a script. When the widget finds an image URI, it appends the URI to the script and executes it. The script should return the name of a Tk image to be used by the html widget. The image may be populated or overwritten by the script at a later time and the widget display is automatically updated.

By default, Tkhtml deletes images when it has finished using them but it can be configured to invoke a user script instead. There are also interfaces to ensure that relative URIs can be interpreted unambiguously (for example a relative URI from an external stylesheet may be interpreted differently to a relative URI embedded in the html document itself).

Here's a slightly fancier, more complete get_image proc that will let you specify a tk image, a file, or a http url. it needs some error checking incase the file or url specified is not actually an image. It has the added bonus of not creating a new Tk image for repeat use of the same file or url.
  package require Img
  package require http

  proc get_url { url } {
         set token [::http::geturl $url]
         set data [::http::data $token]
         ::http::cleanup $token
         return $data
  }

  proc get_image {uri} {
       #if the 'url' passed is an image name
       if { [lsearch [image names]  $uri] > -1 } {
            return $uri
       }

       # if the 'url' passed is a file on disk
       if { [file exists $uri] } {
            #create image using file
            image create photo  $uri -file $uri
            return $uri
       }

       #if the 'url' is an http url.
       if { [string equal -length 7 $uri http://] } {
            image create photo $uri -data [get_url $uri]
            return $uri
       }
  }

Please post any bugs you find with Tkhtml at this website:

http://tkhtml.tcl.tk/cvstrac/

rdt I built this on a Mandrake 2006.0 distro and checked out all these snippets. It's nice. Thanks.

See also edit