//
// "$Id: HelpView.cxx,v 1.30 2000/06/29 01:15:55 mike Exp $"
//
//   Help Viewer widget routines.
//
//   Copyright 1997-2000 by Easy Software Products.
//   Image support donated by Matthias Melcher, Copyright 2000.
//
//   These coded instructions, statements, and computer programs are the
//   property of Easy Software Products and are protected by Federal
//   copyright law.  Distribution and use rights are outblockd in the file
//   "COPYING" which should have been included with this file.  If this
//   file is missing or damaged please contact Easy Software Products
//   at:
//
//       Attn: ESP Licensing Information
//       Easy Software Products
//       44141 Airport View Drive, Suite 204
//       Hollywood, Maryland 20636-3111 USA
//
//       Voice: (301) 373-9600
//       EMail: info@easysw.com
//         WWW: http://www.easysw.com
//
// Contents:
//
//   HelpView::add_block()       - Add a text block to the list.
//   HelpView::add_image()       - Add an image to the image cache.
//   HelpView::add_link()        - Add a new link to the list.
//   HelpView::add_target()      - Add a new target to the list.
//   HelpView::compare_targets() - Compare two targets.
//   HelpView::do_align()        - Compute the alignment for a line in a block.
//   HelpView::draw()            - Draw the HelpView widget.
//   HelpView::find_image()      - Find an image by name 
//   HelpView::format()          - Format the help text.
//   HelpView::get_align()       - Get an alignment attribute.
//   HelpView::get_attr()        - Get an attribute value from the string.
//   HelpView::get_color()       - Get an alignment attribute.
//   HelpView::handle()          - Handle events in the widget.
//   HelpView::HelpView()        - Build a HelpView widget.
//   HelpView::~HelpView()       - Destroy a HelpView widget.
//   HelpView::load()            - Load the specified file.
//   HelpView::load_gif()        - Load a GIF image file...
//   HelpView::load_jpeg()       - Load a JPEG image file.
//   HelpView::load_png()        - Load a PNG image file.
//   HelpView::resize()          - Resize the help widget.
//   HelpView::topline()         - Set the top line to the named target.
//   HelpView::topline()         - Set the top line by number.
//   HelpView::value()           - Set the help text directly.
//   gif_read_cmap()             - Read the colormap from a GIF file...
//   gif_get_block()             - Read a GIF data block...
//   gif_get_code()              - Get a LZW code from the file...
//   gif_read_lzw()              - Read a byte from the LZW stream...
//   gif_read_image()            - Read a GIF image stream...
//   scrollbar_callback()        - A callback for the scrollbar.
//

//
// Include necessary header files...
//

#include "HelpView.h"
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>

#include <FL/Fl_Image.H>
#include <FL/Fl_Pixmap.H>

#if defined(WIN32)
#  include <io.h>
#  include <direct.h>
#  define strcasecmp(s,t)	stricmp((s), (t))
#  define strncasecmp(s,t,n)	strnicmp((s), (t), (n))
#elif defined(__EMX__)
#  define strcasecmp(s,t)	stricmp((s), (t))
#  define strncasecmp(s,t,n)	strnicmp((s), (t), (n))
#else
#  include <unistd.h>
#endif // WIN32

#ifdef HAVE_LIBPNG
#  include <zlib.h>
#  include <png.h>
#endif // HAVE_LIBPNG

#ifdef HAVE_LIBJPEG
extern "C"
{
#  include <jpeglib.h>
}
#endif // HAVE_LIBJPEG


//
// GIF definitions...
//

#define GIF_INTERLACE	0x40
#define GIF_COLORMAP	0x80

typedef unsigned char	gif_cmap_t[256][3];


//
// Local globals...
//

static char	*broken_xpm[] =
		{
		  "16 24 4 1",
		  "@ c #000000",
		  "  c #ffffff",
		  "+ c none",
		  "x c #ff0000",
		  // pixels
		  "@@@@@@@+++++++++",
		  "@    @++++++++++",
		  "@   @+++++++++++",
		  "@   @++@++++++++",
		  "@    @@+++++++++",
		  "@     @+++@+++++",
		  "@     @++@@++++@",
		  "@ xxx  @@  @++@@",
		  "@  xxx    xx@@ @",
		  "@   xxx  xxx   @",
		  "@    xxxxxx    @",
		  "@     xxxx     @",
		  "@    xxxxxx    @",
		  "@   xxx  xxx   @",
		  "@  xxx    xxx  @",
		  "@ xxx      xxx @",
		  "@              @",
		  "@              @",
		  "@              @",
		  "@              @",
		  "@              @",
		  "@              @",
		  "@              @",
		  "@@@@@@@@@@@@@@@@",
		  NULL
		};

static Fl_Pixmap *broken_image = (Fl_Pixmap *)0;
static int	gif_eof = 0;		// Did we hit EOF?
static unsigned	fltk_colors[] =
		{
		  0x00000000,
		  0xff000000,
		  0x00ff0000,
		  0xffff0000,
		  0x0000ff00,
		  0xff00ff00,
		  0x00ffff00,
		  0xffffff00,
		  0x55555500,
		  0xc6717100,
		  0x71c67100,
		  0x8e8e3800,
		  0x7171c600,
		  0x8e388e00,
		  0x388e8e00,
		  0xaaaaaa00,
		  0x55555500,
		  0x55555500,
		  0x55555500,
		  0x55555500,
		  0x55555500,
		  0x55555500,
		  0x55555500,
		  0x55555500,
		  0x55555500,
		  0x55555500,
		  0x55555500,
		  0x55555500,
		  0x55555500,
		  0x55555500,
		  0x55555500,
		  0x55555500,
		  0x00000000,
		  0x0d0d0d00,
		  0x1a1a1a00,
		  0x26262600,
		  0x31313100,
		  0x3d3d3d00,
		  0x48484800,
		  0x55555500,
		  0x5f5f5f00,
		  0x6a6a6a00,
		  0x75757500,
		  0x80808000,
		  0x8a8a8a00,
		  0x95959500,
		  0xa0a0a000,
		  0xaaaaaa00,
		  0xb5b5b500,
		  0xc0c0c000,
		  0xcbcbcb00,
		  0xd5d5d500,
		  0xe0e0e000,
		  0xeaeaea00,
		  0xf5f5f500,
		  0xffffff00,
		  0x00000000,
		  0x00240000,
		  0x00480000,
		  0x006d0000,
		  0x00910000,
		  0x00b60000,
		  0x00da0000,
		  0x00ff0000,
		  0x3f000000,
		  0x3f240000,
		  0x3f480000,
		  0x3f6d0000,
		  0x3f910000,
		  0x3fb60000,
		  0x3fda0000,
		  0x3fff0000,
		  0x7f000000,
		  0x7f240000,
		  0x7f480000,
		  0x7f6d0000,
		  0x7f910000,
		  0x7fb60000,
		  0x7fda0000,
		  0x7fff0000,
		  0xbf000000,
		  0xbf240000,
		  0xbf480000,
		  0xbf6d0000,
		  0xbf910000,
		  0xbfb60000,
		  0xbfda0000,
		  0xbfff0000,
		  0xff000000,
		  0xff240000,
		  0xff480000,
		  0xff6d0000,
		  0xff910000,
		  0xffb60000,
		  0xffda0000,
		  0xffff0000,
		  0x00003f00,
		  0x00243f00,
		  0x00483f00,
		  0x006d3f00,
		  0x00913f00,
		  0x00b63f00,
		  0x00da3f00,
		  0x00ff3f00,
		  0x3f003f00,
		  0x3f243f00,
		  0x3f483f00,
		  0x3f6d3f00,
		  0x3f913f00,
		  0x3fb63f00,
		  0x3fda3f00,
		  0x3fff3f00,
		  0x7f003f00,
		  0x7f243f00,
		  0x7f483f00,
		  0x7f6d3f00,
		  0x7f913f00,
		  0x7fb63f00,
		  0x7fda3f00,
		  0x7fff3f00,
		  0xbf003f00,
		  0xbf243f00,
		  0xbf483f00,
		  0xbf6d3f00,
		  0xbf913f00,
		  0xbfb63f00,
		  0xbfda3f00,
		  0xbfff3f00,
		  0xff003f00,
		  0xff243f00,
		  0xff483f00,
		  0xff6d3f00,
		  0xff913f00,
		  0xffb63f00,
		  0xffda3f00,
		  0xffff3f00,
		  0x00007f00,
		  0x00247f00,
		  0x00487f00,
		  0x006d7f00,
		  0x00917f00,
		  0x00b67f00,
		  0x00da7f00,
		  0x00ff7f00,
		  0x3f007f00,
		  0x3f247f00,
		  0x3f487f00,
		  0x3f6d7f00,
		  0x3f917f00,
		  0x3fb67f00,
		  0x3fda7f00,
		  0x3fff7f00,
		  0x7f007f00,
		  0x7f247f00,
		  0x7f487f00,
		  0x7f6d7f00,
		  0x7f917f00,
		  0x7fb67f00,
		  0x7fda7f00,
		  0x7fff7f00,
		  0xbf007f00,
		  0xbf247f00,
		  0xbf487f00,
		  0xbf6d7f00,
		  0xbf917f00,
		  0xbfb67f00,
		  0xbfda7f00,
		  0xbfff7f00,
		  0xff007f00,
		  0xff247f00,
		  0xff487f00,
		  0xff6d7f00,
		  0xff917f00,
		  0xffb67f00,
		  0xffda7f00,
		  0xffff7f00,
		  0x0000bf00,
		  0x0024bf00,
		  0x0048bf00,
		  0x006dbf00,
		  0x0091bf00,
		  0x00b6bf00,
		  0x00dabf00,
		  0x00ffbf00,
		  0x3f00bf00,
		  0x3f24bf00,
		  0x3f48bf00,
		  0x3f6dbf00,
		  0x3f91bf00,
		  0x3fb6bf00,
		  0x3fdabf00,
		  0x3fffbf00,
		  0x7f00bf00,
		  0x7f24bf00,
		  0x7f48bf00,
		  0x7f6dbf00,
		  0x7f91bf00,
		  0x7fb6bf00,
		  0x7fdabf00,
		  0x7fffbf00,
		  0xbf00bf00,
		  0xbf24bf00,
		  0xbf48bf00,
		  0xbf6dbf00,
		  0xbf91bf00,
		  0xbfb6bf00,
		  0xbfdabf00,
		  0xbfffbf00,
		  0xff00bf00,
		  0xff24bf00,
		  0xff48bf00,
		  0xff6dbf00,
		  0xff91bf00,
		  0xffb6bf00,
		  0xffdabf00,
		  0xffffbf00,
		  0x0000ff00,
		  0x0024ff00,
		  0x0048ff00,
		  0x006dff00,
		  0x0091ff00,
		  0x00b6ff00,
		  0x00daff00,
		  0x00ffff00,
		  0x3f00ff00,
		  0x3f24ff00,
		  0x3f48ff00,
		  0x3f6dff00,
		  0x3f91ff00,
		  0x3fb6ff00,
		  0x3fdaff00,
		  0x3fffff00,
		  0x7f00ff00,
		  0x7f24ff00,
		  0x7f48ff00,
		  0x7f6dff00,
		  0x7f91ff00,
		  0x7fb6ff00,
		  0x7fdaff00,
		  0x7fffff00,
		  0xbf00ff00,
		  0xbf24ff00,
		  0xbf48ff00,
		  0xbf6dff00,
		  0xbf91ff00,
		  0xbfb6ff00,
		  0xbfdaff00,
		  0xbfffff00,
		  0xff00ff00,
		  0xff24ff00,
		  0xff48ff00,
		  0xff6dff00,
		  0xff91ff00,
		  0xffb6ff00,
		  0xffdaff00,
		  0xffffff00
		};


//
// Local functions...
//

static int	gif_read_cmap(FILE *fp, int ncolors, gif_cmap_t cmap);
static int	gif_get_block(FILE *fp, unsigned char *buffer);
static int	gif_get_code (FILE *fp, int code_size, int first_time);
static int	gif_read_lzw(FILE *fp, int first_time, int input_code_size);
static int	gif_read_image(FILE *fp, HelpImage *img, gif_cmap_t cmap,
		               int interlace);
static void	scrollbar_callback(Fl_Widget *s, void *);


//
// 'HelpView::add_block()' - Add a text block to the list.
//

HelpBlock *				// O - Pointer to new block
HelpView::add_block(const char *s,	// I - Pointer to start of block text
                    int        xx,	// I - X position of block
		    int        yy,	// I - Y position of block
		    int        ww,	// I - Right margin of block
		    int        hh,	// I - Height of block
		    unsigned char      border)	// I - Draw border?
{
  HelpBlock	*temp;			// New block


  if (nblocks_ >= ablocks_)
  {
    ablocks_ += 16;

    if (ablocks_ == 16)
      blocks_ = (HelpBlock *)malloc(sizeof(HelpBlock) * ablocks_);
    else
      blocks_ = (HelpBlock *)realloc(blocks_, sizeof(HelpBlock) * ablocks_);
  }

  temp = blocks_ + nblocks_;
  temp->start  = s;
  temp->x      = xx;
  temp->y      = yy;
  temp->w      = ww;
  temp->h      = hh;
  temp->border = border;
  nblocks_ ++;

  return (temp);
}


//
// 'HelpView::add_image()' - Add an image to the image cache.
//

HelpImage *				// O - Image or NULL if not found
HelpView::add_image(const char *name,	// I - Path of image
                    const char *wattr,	// I - Width attribute
		    const char *hattr,	// I - Height attribute
                    int        make)	// I - Make the image?
{
  HelpImage	*img,			// New image
		*orig;			// Original image
  FILE		*fp;			// File pointer
  unsigned char	header[16];		// First 16 bytes of file
  int		status;			// Status of load...
  const char	*localname;		// Local filename
  char		dir[1024];		// Current directory
  char		temp[1024],		// Temporary filename
		*tempptr;		// Pointer into temporary name
  int		width,			// Desired width of image
		height;			// Desired height of image


  // See if the image has already been loaded...
  if ((img = find_image(name, wattr, hattr)) != (HelpImage *)0)
  {
    // Make the image if needed...
    if (!img->image)
      img->image = new Fl_Image(img->data, img->w, img->h, img->d);

    return (img);
  }

  // See if the image exists with the default size info...
  orig = find_image(name, "", "");

  // Allocate memory as needed...
  if (aimage_ == nimage_)
  {
    aimage_ += 16;
    image_  = (HelpImage *)realloc(image_, sizeof(HelpImage) * aimage_);
  }

  img       = image_ + nimage_;
  img->name = strdup(name);

  if (!orig)
  {
    // See if the image can be found...
    if (strchr(directory_, ':') != NULL && strchr(name, ':') == NULL)
    {
      if (name[0] == '/')
      {
        strcpy(temp, directory_);
        if ((tempptr = strrchr(strchr(directory_, ':') + 3, '/')) != NULL)
	  strcpy(tempptr, name);
	else
	  strcat(temp, name);
      }
      else
	sprintf(temp, "%s/%s", directory_, name);

      if (link_)
	localname = (*link_)(temp);
      else
	localname = temp;
    }
    else if (name[0] != '/' && strchr(name, ':') == NULL)
    {
      if (directory_[0])
	sprintf(temp, "%s/%s", directory_, name);
      else
      {
	getcwd(dir, sizeof(dir));
	sprintf(temp, "file:%s/%s", dir, name);
      }

      if (link_)
	localname = (*link_)(temp);
      else
	localname = temp;
    }
    else if (link_)
      localname = (*link_)(name);
    else
      localname = name;

    if (!localname)
      return ((HelpImage *)0);

    if (strncmp(localname, "file:", 5) == 0)
      localname += 5;

    // Figure out the file type...
    if ((fp = fopen(localname, "rb")) == NULL)
      return ((HelpImage *)0);

    if (fread(header, 1, sizeof(header), fp) == 0)
      return ((HelpImage *)0);

    rewind(fp);

    // Load the image as appropriate...
    if (memcmp(header, "GIF87a", 6) == 0 ||
	memcmp(header, "GIF89a", 6) == 0)
      status = load_gif(img,  fp);
  #ifdef HAVE_LIBPNG
    else if (memcmp(header, "\211PNG", 4) == 0)
      status = load_png(img, fp);
  #endif // HAVE_LIBPNG
  #ifdef HAVE_LIBJPEG
    else if (memcmp(header, "\377\330\377", 3) == 0 &&	// Start-of-Image
	     header[3] >= 0xe0 && header[3] <= 0xef)	// APPn
      status = load_jpeg(img, fp);
  #endif // HAVE_LIBJPEG
    else
      status = 0;

    fclose(fp);

    if (!status)
    {
      free(img->name);
      return ((HelpImage *)0);
    }

    img->wattr[0] = '\0';
    img->hattr[0] = '\0';

    nimage_ ++;

    // Allocate memory as needed for the new copy...
    if (aimage_ == nimage_)
    {
      aimage_ += 16;
      image_  = (HelpImage *)realloc(image_, sizeof(HelpImage) * aimage_);
    }

    orig      = image_ + nimage_ - 1;
    img       = image_ + nimage_;
    img->name = strdup(name);
  }

  // Copy image data from original image...
  img->data = orig->data;
  img->w    = orig->w;
  img->h    = orig->h;
  img->d    = orig->d;

  // Figure out the size of the image...
  if (wattr[0])
  {
    if (wattr[strlen(wattr) - 1] == '%')
      width = atoi(wattr) * (w() - 24) / 100;
    else
      width = atoi(wattr);
  }
  else
    width = 0;

  if (hattr[0])
  {
    if (hattr[strlen(hattr) - 1] == '%')
      height = atoi(hattr) * h() / 100;
    else
      height = atoi(hattr);
  }
  else
    height = 0;

  if (width == 0 && height == 0)
  {
    // Use image size...
    width  = img->w;
    height = img->h;
  }
  else if (width == 0)
    // Scale width to height
    width = img->w * height / img->h;
  else if (height == 0)
    // Scale height to width
    height = img->h * width / img->w;

  // Scale the image as needed...
  if (width != img->w && height != img->h)
  {
    unsigned char	*scaled,	// Scaled image data
			*sptr,		// Source image data pointer
			*dptr;		// Destination image data pointer
    int			sy,		// Source coordinates
			dx, dy,		// Destination coordinates
			xerr, yerr,	// X & Y errors
			xmod, ymod,	// X & Y moduli
			xstep, ystep;	// X & Y step increments


     xmod   = img->w % width;
     xstep  = (img->w / width) * img->d;
     ymod   = img->h % height;
     ystep  = img->h / height;

     if ((scaled = (unsigned char *)malloc(width * height * img->d)) != NULL)
     {
       // Scale the image...
       for (dy = height, sy = 0, yerr = height / 2, dptr = scaled; dy > 0; dy --)
       {
         for (dx = width, xerr = width / 2,
	          sptr = img->data + sy * img->w * img->d;
	      dx > 0;
	      dx --)
         {
	   *dptr++ = sptr[0];
	   if (img->d > 1)
	   {
	     *dptr++ = sptr[1];
	     *dptr++ = sptr[2];
	   }

           sptr += xstep;
	   xerr -= xmod;
	   if (xerr <= 0)
	   {
	     xerr += width;
	     sptr += img->d;
	   }
	 }

         sy   += ystep;
	 yerr -= ymod;
	 if (yerr <= 0)
	 {
	   yerr += height;
	   sy ++;
	 }
       }

       // Finally, copy the new size and data to the image structure...
       if (!orig)
         free(img->data);

       img->w    = width;
       img->h    = height;
       img->data = scaled;
     }
  }

  strncpy(img->wattr, wattr, sizeof(img->wattr) - 1);
  img->wattr[sizeof(img->wattr) - 1] = '\0';
  strncpy(img->hattr, hattr, sizeof(img->hattr) - 1);
  img->hattr[sizeof(img->hattr) - 1] = '\0';

  if (make)
    img->image = new Fl_Image(img->data, img->w, img->h, img->d);
  else
    img->image = (Fl_Image *)0;

  nimage_ ++;

  return (img);
}


//
// 'HelpView::add_link()' - Add a new link to the list.
//

void
HelpView::add_link(const char *n,	// I - Name of link
                   int        xx,	// I - X position of link
		   int        yy,	// I - Y position of link
		   int        ww,	// I - Width of link text
		   int        hh)	// I - Height of link text
{
  HelpLink	*temp;			// New link
  char		*target;		// Pointer to target name


  if (nlinks_ >= alinks_)
  {
    alinks_ += 16;

    if (alinks_ == 16)
      links_ = (HelpLink *)malloc(sizeof(HelpLink) * alinks_);
    else
      links_ = (HelpLink *)realloc(links_, sizeof(HelpLink) * alinks_);
  }

  temp = links_ + nlinks_;

  temp->x       = xx;
  temp->y       = yy;
  temp->w       = xx + ww;
  temp->h       = yy + hh;

  strncpy(temp->filename, n, sizeof(temp->filename));
  temp->filename[sizeof(temp->filename) - 1] = '\0';

  if ((target = strrchr(temp->filename, '#')) != NULL)
  {
    *target++ = '\0';
    strncpy(temp->name, target, sizeof(temp->name));
    temp->name[sizeof(temp->name) - 1] = '\0';
  }
  else
    temp->name[0] = '\0';

  nlinks_ ++;
}


//
// 'HelpView::add_target()' - Add a new target to the list.
//

void
HelpView::add_target(const char *n,	// I - Name of target
                     int        yy)	// I - Y position of target
{
  HelpTarget	*temp;			// New target


  if (ntargets_ >= atargets_)
  {
    atargets_ += 16;

    if (atargets_ == 16)
      targets_ = (HelpTarget *)malloc(sizeof(HelpTarget) * atargets_);
    else
      targets_ = (HelpTarget *)realloc(targets_, sizeof(HelpTarget) * atargets_);
  }

  temp = targets_ + ntargets_;

  temp->y = yy;
  strncpy(temp->name, n, sizeof(temp->name));
  temp->name[sizeof(temp->name) - 1] = '\0';

  ntargets_ ++;
}


//
// 'HelpView::compare_targets()' - Compare two targets.
//

int						// O - Result of comparison
HelpView::compare_targets(const HelpTarget *t0,	// I - First target
                          const HelpTarget *t1)	// I - Second target
{
  return (strcasecmp(t0->name, t1->name));
}


//
// 'HelpView::do_align()' - Compute the alignment for a line in a block.
//

int					// O - New line
HelpView::do_align(HelpBlock *block,	// I - Block to add to
                   int       line,	// I - Current line
		   int       xx,	// I - Current X position
		   int       a,		// I - Current alignment
		   int       &l)	// IO - Starting link
{
  int	offset;				// Alignment offset


  switch (a)
  {
    case RIGHT :	// Right align
	offset = block->w - xx;
	break;
    case CENTER :	// Center
	offset = (block->w - xx) / 2;
	break;
    default :		// Left align
	offset = 0;
	break;
  }

  block->line[line] = block->x + offset;

  if (line < 31)
    line ++;

  while (l < nlinks_)
  {
    links_[l].x += offset;
    links_[l].w += offset;
    l ++;
  }

  return (line);
}


//
// 'HelpView::draw()' - Draw the HelpView widget.
//

void
HelpView::draw()
{
  int		i;		// Looping var
  const HelpBlock *block;	// Pointer to current block
  const char	*ptr,		// Pointer to text in block
		*attrs;		// Pointer to start of element attributes
  char		*s,		// Pointer into buffer
		buf[1024],	// Text buffer
		attr[1024];	// Attribute buffer
  int		xx, yy, ww, hh;	// Current positions and sizes
  int		line;		// Current line
  unsigned char		font, size;	// Current font and size
  int		head, pre,	// Flags for text
		needspace;	// Do we need whitespace?
  Fl_Boxtype	b = box() ? box() : FL_DOWN_BOX;
				// Box to draw...
  Fl_Color	tc, c;		// Table/cell background color


  // Draw the scrollbar and box first...
  if (scrollbar_.visible())
  {
    draw_child(scrollbar_);
    draw_box(b, x(), y(), w() - 17, h(), bgcolor_);
  }
  else
    draw_box(b, x(), y(), w(), h(), bgcolor_);

  if (!value_)
    return;

  // Clip the drawing to the inside of the box...
  fl_push_clip(x() + 4, y() + 4, w() - 28, h() - 8);
  fl_color(textcolor_);

  tc = c = bgcolor_;

  // Draw all visible blocks...
  for (i = 0, block = blocks_; i < nblocks_ && (block->y - topline_) < h(); i ++, block ++)
    if ((block->y + block->h) >= topline_)
    {
      line      = 0;
      xx        = block->line[line];
      yy        = block->y - topline_;
      hh        = 0;
      pre       = 0;
      head      = 0;
      needspace = 0;

      initfont(font, size);

      for (ptr = block->start, s = buf; ptr < block->end;)
      {
	if ((*ptr == '<' || isspace(*ptr)) && s > buf)
	{
	  if (!head && !pre)
	  {
            // Check width...
            *s = '\0';
            s  = buf;
            ww = (int)fl_width(buf);

            if (needspace && xx > block->x)
	      xx += (int)fl_width(' ');

            if ((xx + ww) > block->w)
	    {
	      if (line < 31)
	        line ++;
	      xx = block->line[line];
	      yy += hh;
	      hh = 0;
	    }

            fl_draw(buf, xx + x(), yy + y());

            xx += ww;
	    if ((size + 2) > hh)
	      hh = size + 2;

	    needspace = 0;
	  }
	  else if (pre)
	  {
	    while (isspace(*ptr))
	    {
	      if (*ptr == '\n')
	      {
	        *s = '\0';
                s = buf;

                fl_draw(buf, xx + x(), yy + y());

		if (line < 31)
	          line ++;
		xx = block->line[line];
		yy += hh;
		hh = size + 2;
	      }
	      else if (*ptr == '\t')
	      {
		// Do tabs every 8 columns...
		while (((s - buf) & 7))
	          *s++ = ' ';
	      }
	      else
	        *s++ = ' ';

              if ((size + 2) > hh)
	        hh = size + 2;

              ptr ++;
	    }

            if (s > buf)
	    {
	      *s = '\0';
	      s = buf;

              fl_draw(buf, xx + x(), yy + y());
              xx += (int)fl_width(buf);
	    }

	    needspace = 0;
	  }
	  else
	  {
            s = buf;

	    while (isspace(*ptr))
              ptr ++;
	  }
	}

	if (*ptr == '<')
	{
	  ptr ++;
	  while (*ptr && *ptr != '>' && !isspace(*ptr))
            if (s < (buf + sizeof(buf) - 1))
	      *s++ = *ptr++;
	    else
	      ptr ++;

	  *s = '\0';
	  s = buf;

	  attrs = ptr;
	  while (*ptr && *ptr != '>')
            ptr ++;

	  if (*ptr == '>')
            ptr ++;

	  if (strcasecmp(buf, "HEAD") == 0)
            head = 1;
	  else if (strcasecmp(buf, "BR") == 0)
	  {
	    if (line < 31)
	      line ++;
	    xx = block->line[line];
            yy += hh;
	    hh = 0;
	  }
	  else if (strcasecmp(buf, "HR") == 0)
	  {
	    fl_line(block->x + x(), yy + y(), block->w + x(),
	            yy + y());

	    if (line < 31)
	      line ++;
	    xx = block->line[line];
            yy += 2 * hh;
	    hh = 0;
	  }
	  else if (strcasecmp(buf, "CENTER") == 0 ||
        	   strcasecmp(buf, "P") == 0 ||
        	   strcasecmp(buf, "H1") == 0 ||
		   strcasecmp(buf, "H2") == 0 ||
		   strcasecmp(buf, "H3") == 0 ||
		   strcasecmp(buf, "H4") == 0 ||
		   strcasecmp(buf, "H5") == 0 ||
		   strcasecmp(buf, "H6") == 0 ||
		   strcasecmp(buf, "UL") == 0 ||
		   strcasecmp(buf, "OL") == 0 ||
		   strcasecmp(buf, "DL") == 0 ||
		   strcasecmp(buf, "LI") == 0 ||
		   strcasecmp(buf, "DD") == 0 ||
		   strcasecmp(buf, "DT") == 0 ||
		   strcasecmp(buf, "PRE") == 0)
	  {
            if (tolower(buf[0]) == 'h')
	    {
	      font = FL_HELVETICA_BOLD;
	      size = textsize_ + '7' - buf[1];
	    }
	    else if (strcasecmp(buf, "DT") == 0)
	    {
	      font = textfont_ | FL_ITALIC;
	      size = textsize_;
	    }
	    else if (strcasecmp(buf, "PRE") == 0)
	    {
	      font = FL_COURIER;
	      size = textsize_;
	      pre  = 1;
	    }

            if (strcasecmp(buf, "LI") == 0)
	    {
	      fl_font(FL_SYMBOL, size);
	      fl_draw("\267", xx - size + x(), yy + y());
	    }

	    pushfont(font, size);

            if (c != bgcolor_)
	    {
	      fl_color(c);
              fl_rectf(block->x + x() - 4,
	               block->y - topline_ + y() - size - 3,
		       block->w - block->x + 7, block->h + size - 5);
              fl_color(textcolor_);
	    }
	  }
	  else if (strcasecmp(buf, "A") == 0 &&
	           get_attr(attrs, "HREF", attr, sizeof(attr)) != NULL)
	    fl_color(linkcolor_);
	  else if (strcasecmp(buf, "/A") == 0)
	    fl_color(textcolor_);
	  else if (strcasecmp(buf, "B") == 0)
	    pushfont(font |= FL_BOLD, size);
	  else if (strcasecmp(buf, "TABLE") == 0)
            tc = get_color(get_attr(attrs, "BGCOLOR", attr, sizeof(attr)), bgcolor_);
	  else if (strcasecmp(buf, "TD") == 0 ||
	           strcasecmp(buf, "TH") == 0)
          {
	    if (tolower(buf[1]) == 'h')
	      pushfont(font |= FL_BOLD, size);
	    else
	      pushfont(font = textfont_, size);

            c = get_color(get_attr(attrs, "BGCOLOR", attr, sizeof(attr)), tc);

            if (c != bgcolor_)
	    {
	      fl_color(c);
              fl_rectf(block->x + x() - 4,
	               block->y - topline_ + y() - size - 3,
		       block->w - block->x + 7, block->h + size - 5);
              fl_color(textcolor_);
	    }

            if (block->border)
              fl_rect(block->x + x() - 4,
	              block->y - topline_ + y() - size - 3,
		      block->w - block->x + 7, block->h + size - 5);
	  }
	  else if (strcasecmp(buf, "I") == 0)
	    pushfont(font |= FL_ITALIC, size);
	  else if (strcasecmp(buf, "CODE") == 0)
	    pushfont(font = FL_COURIER, size);
	  else if (strcasecmp(buf, "KBD") == 0)
	    pushfont(font = FL_COURIER_BOLD, size);
	  else if (strcasecmp(buf, "VAR") == 0)
	    pushfont(font = FL_COURIER_ITALIC, size);
	  else if (strcasecmp(buf, "/HEAD") == 0)
            head = 0;
	  else if (strcasecmp(buf, "/H1") == 0 ||
		   strcasecmp(buf, "/H2") == 0 ||
		   strcasecmp(buf, "/H3") == 0 ||
		   strcasecmp(buf, "/H4") == 0 ||
		   strcasecmp(buf, "/H5") == 0 ||
		   strcasecmp(buf, "/H6") == 0 ||
		   strcasecmp(buf, "/B") == 0 ||
		   strcasecmp(buf, "/I") == 0 ||
		   strcasecmp(buf, "/CODE") == 0 ||
		   strcasecmp(buf, "/KBD") == 0 ||
		   strcasecmp(buf, "/VAR") == 0)
	    popfont(font, size);
	  else if (strcasecmp(buf, "/TABLE") == 0)
	    tc = c = bgcolor_;
	  else if (strcasecmp(buf, "/TD") == 0 ||
	           strcasecmp(buf, "/TH") == 0)
	    c = tc;
	  else if (strcasecmp(buf, "/PRE") == 0)
	  {
	    popfont(font, size);
	    pre = 0;
	  }
	  else if (strcasecmp(buf, "IMG") == 0)
	  {
	    HelpImage	*img = (HelpImage *)0;
	    int		width = 16;
	    int		height = 24;
	    char	wattr[8], hattr[8];


            get_attr(attrs, "WIDTH", wattr, sizeof(wattr));
            get_attr(attrs, "HEIGHT", hattr, sizeof(hattr));

	    if (get_attr(attrs, "SRC", attr, sizeof(attr))) 
	      if ((img = find_image(attr, wattr, hattr)) != NULL && !img->image)
	        img = (HelpImage *)0;

	    if (img)
	    {
	      width  = img->w;
	      height = img->h;
	    }
	    else if (get_attr(attrs, "ALT", attr, sizeof(attr)) == NULL)
	      strcpy(attr, "IMG");

	    ww = width;

	    if (needspace && xx > block->x)
	      xx += (int)fl_width(' ');

	    if ((xx + ww) > block->w)
	    {
	      if (line < 31)
		line ++;

	      xx = block->line[line];
	      yy += hh;
	      hh = 0;
	    }

	    if (img) 
	      img->image->draw(xx + x(),
	                       yy + y() - fl_height() + fl_descent() + 2);
	    else
	      broken_image->draw(xx + x(),
	                         yy + y() - fl_height() + fl_descent() + 2);

	    xx += ww;
	    if ((height + 2) > hh)
	      hh = height + 2;

	    needspace = 0;
	  }
	}
	else if (*ptr == '\n' && pre)
	{
	  *s = '\0';
	  s = buf;

          fl_draw(buf, xx + x(), yy + y());

	  if (line < 31)
	    line ++;
	  xx = block->line[line];
	  yy += hh;
	  hh = size + 2;
	  needspace = 0;

	  ptr ++;
	}
	else if (isspace(*ptr))
	{
	  if (pre)
	  {
	    if (*ptr == ' ')
	      *s++ = ' ';
	    else
	    {
	      // Do tabs every 8 columns...
	      while (((s - buf) & 7))
	        *s++ = ' ';
            }
	  }

          ptr ++;
	  needspace = 1;
	}
	else if (*ptr == '&')
	{
	  ptr ++;

	  if (strncasecmp(ptr, "amp;", 4) == 0)
	  {
            *s++ = '&';
	    ptr += 4;
	  }
	  else if (strncasecmp(ptr, "lt;", 3) == 0)
	  {
            *s++ = '<';
	    ptr += 3;
	  }
	  else if (strncasecmp(ptr, "gt;", 3) == 0)
	  {
            *s++ = '>';
	    ptr += 3;
	  }
	  else if (strncasecmp(ptr, "nbsp;", 5) == 0)
	  {
            *s++ = ' ';
	    ptr += 5;
	  }
	  else if (strncasecmp(ptr, "copy;", 5) == 0)
	  {
            *s++ = '\251';
	    ptr += 5;
	  }
	  else if (strncasecmp(ptr, "reg;", 4) == 0)
	  {
            *s++ = '\256';
	    ptr += 4;
	  }
	  else if (strncasecmp(ptr, "quot;", 5) == 0)
	  {
            *s++ = '\"';
	    ptr += 5;
	  }

          if ((size + 2) > hh)
	    hh = size + 2;
	}
	else
	{
	  *s++ = *ptr++;

          if ((size + 2) > hh)
	    hh = size + 2;
        }
      }

      *s = '\0';

      if (s > buf && !pre && !head)
      {
	ww = (int)fl_width(buf);

        if (needspace && xx > block->x)
	  xx += (int)fl_width(' ');

	if ((xx + ww) > block->w)
	{
	  if (line < 31)
	    line ++;
	  xx = block->line[line];
	  yy += hh;
	  hh = 0;
	}
      }

      if (s > buf && !head)
        fl_draw(buf, xx + x(), yy + y());
    }

  fl_pop_clip();
}


//
// 'HelpView::find_image()' - Find an image by name 
//

HelpImage *				// O - Image or NULL if not found
HelpView::find_image(const char *name,	// I - Path and name of image
                     const char *wattr,	// I - Width attribute of image
		     const char	*hattr)	// I - Height attribute of image
{
  int		i;			// Looping var
  HelpImage	*img;			// Current image


  for (i = nimage_, img = image_; i > 0; i --, img ++) 
    if (strcmp(img->name, name) == 0 &&
        strcmp(img->wattr, wattr) == 0 &&
        strcmp(img->hattr, hattr) == 0)
      return (img);

  return ((HelpImage *)0);
}


//
// 'HelpView::format()' - Format the help text.
//

void
HelpView::format()
{
  int		i;		// Looping var
  HelpBlock	*block,		// Current block
		*cell;		// Current table cell
  int		row;		// Current table row (block number)
  const char	*ptr,		// Pointer into block
		*start,		// Pointer to start of element
		*attrs;		// Pointer to start of element attributes
  char		*s,		// Pointer into buffer
		buf[1024],	// Text buffer
		attr[1024],	// Attribute buffer
		wattr[1024],	// Width attribute buffer
		hattr[1024],	// Height attribute buffer
		link[1024];	// Link destination
  int		xx, yy, ww, hh;	// Size of current text fragment
  int		line;		// Current line in block
  int		links;		// Links for current line
  unsigned char	font, size;	// Current font and size
  unsigned char	border;		// Draw border?
  int		align,		// Current alignment
		newalign,	// New alignment
		head,		// In the <HEAD> section?
		pre,		// <PRE> text?
		needspace;	// Do we need whitespace?
  int		table_width;	// Width of table
  int		column,		// Current table column number
		columns[200];	// Column widths


  // Reset state variables...
  nblocks_   = 0;
  nlinks_    = 0;
  ntargets_  = 0;
  size_      = 0;
  bgcolor_   = color();
  textcolor_ = textcolor();
  linkcolor_ = selection_color();

  strcpy(title_, "Untitled");

  if (!value_)
    return;

  // Flush images that are scaled by percentage...
  for (i = 0; i < nimage_; i ++)
    if (strchr(image_[i].wattr, '%') != NULL ||
        strchr(image_[i].hattr, '%') != NULL)
    {
      // Flush this one...
      free(image_[i].name);
      free(image_[i].data);
      delete image_[i].image;
      nimage_ --;
      if (i < nimage_)
        memcpy(image_ + i, image_ + i + 1, (nimage_ - i) * sizeof(HelpImage));
      i --;
    }

  // Setup for formatting...
  initfont(font, size);

  line      = 0;
  links     = 0;
  xx        = 4;
  yy        = size + 2;
  ww        = 0;
  column    = 0;
  border    = 0;
  hh        = 0;
  block     = add_block(value_, xx, yy, w() - 24, 0);
  row       = 0;
  head      = 0;
  pre       = 0;
  align     = LEFT;
  newalign  = LEFT;
  needspace = 0;
  link[0]   = '\0';

  for (ptr = value_, s = buf; *ptr;)
  {
    if ((*ptr == '<' || isspace(*ptr)) && s > buf)
    {
      if (!head && !pre)
      {
        // Check width...
        *s = '\0';
        ww = (int)fl_width(buf);

        if (needspace && xx > block->x)
	  ww += (int)fl_width(' ');

        if ((xx + ww) > block->w)
	{
          line     = do_align(block, line, xx, newalign, links);
	  xx       = block->x;
	  yy       += hh;
	  block->h += hh;
	  hh       = 0;
	}

        if (link[0])
	  add_link(link, xx, yy - size, ww, size);

	xx += ww;
	if ((size + 2) > hh)
	  hh = size + 2;

	needspace = 0;
      }
      else if (pre)
      {
        // Handle preformatted text...
	while (isspace(*ptr))
	{
	  if (*ptr == '\n')
	  {
            if (link[0])
	      add_link(link, xx, yy - hh, ww, hh);

            line     = do_align(block, line, xx, newalign, links);
            xx       = block->x;
	    yy       += hh;
	    block->h += hh;
	    hh       = size + 2;
	  }

          if ((size + 2) > hh)
	    hh = size + 2;

          ptr ++;
	}

	needspace = 0;
      }
      else
      {
        // Handle normal text or stuff in the <HEAD> section...
	while (isspace(*ptr))
          ptr ++;
      }

      s = buf;
    }

    if (*ptr == '<')
    {
      start = ptr;
      ptr ++;
      while (*ptr && *ptr != '>' && !isspace(*ptr))
        if (s < (buf + sizeof(buf) - 1))
          *s++ = *ptr++;
	else
	  ptr ++;

      *s = '\0';
      s = buf;

      attrs = ptr;
      while (*ptr && *ptr != '>')
        ptr ++;

      if (*ptr == '>')
        ptr ++;

      if (strcasecmp(buf, "HEAD") == 0)
        head = 1;
      else if (strcasecmp(buf, "/HEAD") == 0)
        head = 0;
      else if (strcasecmp(buf, "TITLE") == 0)
      {
        // Copy the title in the document...
        for (s = title_;
	     *ptr != '<' && *ptr && s < (title_ + sizeof(title_) - 1);
	     *s++ = *ptr++);

	*s = '\0';
	s = buf;
      }
      else if (strcasecmp(buf, "A") == 0)
      {
        if (get_attr(attrs, "NAME", attr, sizeof(attr)) != NULL)
	  add_target(attr, yy - size - 2);
	else if (get_attr(attrs, "HREF", attr, sizeof(attr)) != NULL)
	{
	  strncpy(link, attr, sizeof(link) - 1);
	  link[sizeof(link) - 1] = '\0';
	}
      }
      else if (strcasecmp(buf, "/A") == 0)
        link[0] = '\0';
      else if (strcasecmp(buf, "BODY") == 0)
      {
        bgcolor_   = get_color(get_attr(attrs, "BGCOLOR", attr, sizeof(attr)),
	                       color());
        textcolor_ = get_color(get_attr(attrs, "TEXT", attr, sizeof(attr)),
	                       textcolor());
        linkcolor_ = get_color(get_attr(attrs, "LINK", attr, sizeof(attr)),
	                       selection_color());
      }
      else if (strcasecmp(buf, "BR") == 0)
      {
        line     = do_align(block, line, xx, newalign, links);
        xx       = block->x;
	block->h += hh;
        yy       += hh;
	hh       = 0;
      }
      else if (strcasecmp(buf, "CENTER") == 0 ||
               strcasecmp(buf, "P") == 0 ||
               strcasecmp(buf, "H1") == 0 ||
	       strcasecmp(buf, "H2") == 0 ||
	       strcasecmp(buf, "H3") == 0 ||
	       strcasecmp(buf, "H4") == 0 ||
	       strcasecmp(buf, "H5") == 0 ||
	       strcasecmp(buf, "H6") == 0 ||
	       strcasecmp(buf, "UL") == 0 ||
	       strcasecmp(buf, "OL") == 0 ||
	       strcasecmp(buf, "DL") == 0 ||
	       strcasecmp(buf, "LI") == 0 ||
	       strcasecmp(buf, "DD") == 0 ||
	       strcasecmp(buf, "DT") == 0 ||
	       strcasecmp(buf, "HR") == 0 ||
	       strcasecmp(buf, "PRE") == 0 ||
	       strcasecmp(buf, "TABLE") == 0)
      {
        block->end = start;
        line       = do_align(block, line, xx, newalign, links);
        xx         = block->x;
        block->h   += hh;

        if (!block->h && nblocks_ > 1)
	{
	  nblocks_ --;
	  block --;
	}

        if (strcasecmp(buf, "UL") == 0 ||
	    strcasecmp(buf, "OL") == 0 ||
	    strcasecmp(buf, "DL") == 0)
	{
	  xx += 4 * size;
	  block->h += size + 2;
	}
        else if (strcasecmp(buf, "TABLE") == 0)
	{
	  if (get_attr(attrs, "BORDER", attr, sizeof(attr)))
	    border = atoi(attr);
	  else
	    border = 0;

	  block->h += size + 2;

          if (get_attr(attrs, "WIDTH", attr, sizeof(attr)))
	  {
	    if (attr[strlen(attr) - 1] == '%')
	      table_width = atoi(attr) * w() / 100;
	    else
	      table_width = atoi(attr);
	  }
	  else
	    table_width = w();

          for (column = 0; column < 200; column ++)
	    columns[column] = table_width / 3;

	  column = 0;
	}

        if (tolower(buf[0]) == 'h' && isdigit(buf[1]))
	{
	  font = FL_HELVETICA_BOLD;
	  size = textsize_ + '7' - buf[1];
	}
	else if (strcasecmp(buf, "DT") == 0)
	{
	  font = textfont_ | FL_ITALIC;
	  size = textsize_;
	}
	else if (strcasecmp(buf, "PRE") == 0)
	{
	  font = FL_COURIER;
	  size = textsize_;
	  pre  = 1;
	}
	else
	{
	  font = textfont_;
	  size = textsize_;
	}

	pushfont(font, size);

        yy = block->y + block->h;
        hh = 0;

        if ((tolower(buf[0]) == 'h' && isdigit(buf[1])) ||
	    strcasecmp(buf, "DD") == 0 ||
	    strcasecmp(buf, "DT") == 0 ||
	    strcasecmp(buf, "UL") == 0 ||
	    strcasecmp(buf, "OL") == 0 ||
	    strcasecmp(buf, "P") == 0)
          yy += size + 2;
	else if (strcasecmp(buf, "HR") == 0)
	{
	  hh += 2 * size;
	  yy += size;
	}

        if (row)
	  block = add_block(start, block->x, yy, block->w, 0);
	else
	  block = add_block(start, xx, yy, w() - 24, 0);

	needspace = 0;
	line      = 0;

	if (strcasecmp(buf, "CENTER") == 0)
	  newalign = align = CENTER;
	else
	  newalign = get_align(attrs, align);
      }
      else if (strcasecmp(buf, "/CENTER") == 0 ||
	       strcasecmp(buf, "/P") == 0 ||
	       strcasecmp(buf, "/H1") == 0 ||
	       strcasecmp(buf, "/H2") == 0 ||
	       strcasecmp(buf, "/H3") == 0 ||
	       strcasecmp(buf, "/H4") == 0 ||
	       strcasecmp(buf, "/H5") == 0 ||
	       strcasecmp(buf, "/H6") == 0 ||
	       strcasecmp(buf, "/PRE") == 0 ||
	       strcasecmp(buf, "/UL") == 0 ||
	       strcasecmp(buf, "/OL") == 0 ||
	       strcasecmp(buf, "/DL") == 0 ||
	       strcasecmp(buf, "/TABLE") == 0)
      {
        line       = do_align(block, line, xx, newalign, links);
        xx         = block->x;
        block->end = ptr;

        if (strcasecmp(buf, "/UL") == 0 ||
	    strcasecmp(buf, "/OL") == 0 ||
	    strcasecmp(buf, "/DL") == 0)
	{
	  xx       -= 4 * size;
	  block->h += size + 2;
	}
	else if (strcasecmp(buf, "/TABLE") == 0)
	  block->h += size + 2;
	else if (strcasecmp(buf, "/PRE") == 0)
	{
	  pre = 0;
	  hh  = 0;
	}
	else if (strcasecmp(buf, "/CENTER") == 0)
	  align = LEFT;

        initfont(font, size);

        while (isspace(*ptr))
	  ptr ++;

        block->h += hh;
        yy       += hh;

        if (tolower(buf[2]) == 'l')
          yy += size + 2;

        block     = add_block(ptr, xx, yy, w() - 24, 0);
	needspace = 0;
	hh        = 0;
	line      = 0;
	newalign  = align;
      }
      else if (strcasecmp(buf, "TR") == 0)
      {
        block->end = start;
        line       = do_align(block, line, xx, newalign, links);
        xx         = block->x;
        block->h   += hh;

        if (row)
	{
          yy = blocks_[row].y + blocks_[row].h;

	  for (cell = blocks_ + row + 1; cell <= block; cell ++)
	    if ((cell->y + cell->h) > yy)
	      yy = cell->y + cell->h;

          block->h = yy - block->y + 2;

	  for (cell = blocks_ + row + 1; cell < block; cell ++)
	    cell->h = block->h;
	}

	yy        = block->y + block->h - 4;
	hh        = 0;
        block     = add_block(start, xx, yy, w() - 24, 0);
	row       = block - blocks_;
	needspace = 0;
	column    = 0;
	line      = 0;
      }
      else if (strcasecmp(buf, "/TR") == 0 && row)
      {
        line       = do_align(block, line, xx, newalign, links);
        block->end = start;
	block->h   += hh;

        xx = blocks_[row].x;

        if (block->end == block->start && nblocks_ > 1)
	{
	  nblocks_ --;
	  block --;
	}

        yy = blocks_[row].y + blocks_[row].h;

	for (cell = blocks_ + row + 1; cell <= block; cell ++)
	  if ((cell->y + cell->h) > yy)
	    yy = cell->y + cell->h;

        block->h = yy - block->y + 2;

	for (cell = blocks_ + row + 1; cell < block; cell ++)
	  cell->h = block->h;

	yy        = block->y + block->h - 4;
        block     = add_block(start, xx, yy, w() - 24, 0);
	needspace = 0;
	row       = 0;
	line      = 0;
      }
      else if ((strcasecmp(buf, "TD") == 0 ||
                strcasecmp(buf, "TH") == 0) && row)
      {
        line       = do_align(block, line, xx, newalign, links);
        block->end = start;
	block->h   += hh;

        if (strcasecmp(buf, "TH") == 0)
	  font = textfont_ | FL_BOLD;
	else
	  font = textfont_;

        size = textsize_;

        if (column == 0)
          xx = block->x + size + 3;
	else
          xx = block->w + 6;

        if (block->end == block->start && nblocks_ > 1)
	{
	  nblocks_ --;
	  block --;
	}

	pushfont(font, size);

        if (get_attr(attrs, "WIDTH", attr, sizeof(attr)) != NULL)
	{
	  ww = atoi(attr);

	  if (attr[strlen(attr) - 1] == '%')
	    ww = ww * w() / 100;

          columns[column] = ww;
	}
	else
	  ww = columns[column];

	yy        = blocks_[row].y;
	hh        = 0;
        block     = add_block(start, xx, yy, xx + ww, 0, border);
	needspace = 0;
	line      = 0;
	newalign  = get_align(attrs, tolower(buf[1]) == 'h' ? CENTER : LEFT);

	column ++;
      }
      else if ((strcasecmp(buf, "/TD") == 0 ||
                strcasecmp(buf, "/TH") == 0) && row)
        popfont(font, size);
      else if (strcasecmp(buf, "B") == 0)
	pushfont(font |= FL_BOLD, size);
      else if (strcasecmp(buf, "I") == 0)
	pushfont(font |= FL_ITALIC, size);
      else if (strcasecmp(buf, "CODE") == 0)
	pushfont(font = FL_COURIER, size);
      else if (strcasecmp(buf, "KBD") == 0)
	pushfont(font = FL_COURIER_BOLD, size);
      else if (strcasecmp(buf, "VAR") == 0)
	pushfont(font = FL_COURIER_ITALIC, size);
      else if (strcasecmp(buf, "/B") == 0 ||
	       strcasecmp(buf, "/I") == 0 ||
	       strcasecmp(buf, "/CODE") == 0 ||
	       strcasecmp(buf, "/KBD") == 0 ||
	       strcasecmp(buf, "/VAR") == 0)
	popfont(font, size);
      else if (strcasecmp(buf, "IMG") == 0)
      {
	HelpImage	*img = (HelpImage *)0;
	int		width = 16;
	int		height = 24;


        get_attr(attrs, "WIDTH", wattr, sizeof(wattr));
        get_attr(attrs, "HEIGHT", hattr, sizeof(hattr));

	if (get_attr(attrs, "SRC", attr, sizeof(attr))) 
	  if ((img = add_image(attr, wattr, hattr)) != (HelpImage *)0 &&
	      img->image == NULL)
	    img = (HelpImage *)0;

	if (img)
	{
	  width  = img->w;
	  height = img->h;
	}
	else if (get_attr(attrs, "ALT", attr, sizeof(attr)) == NULL)
	  strcpy(attr, "IMG");

	ww = width;

	if (needspace && xx > block->x)
	  ww += (int)fl_width(' ');

	if ((xx + ww) > block->w)
	{
	  line     = do_align(block, line, xx, newalign, links);
	  xx       = block->x;
	  yy       += hh;
	  block->h += hh;
	  hh       = 0;
	}

	if (link[0])
	  add_link(link, xx, yy - height, ww, height);

	xx += ww;
	if ((height + 2) > hh)
	  hh = height + 2;

	needspace = 0;
      }
    }
    else if (*ptr == '\n' && pre)
    {
      if (link[0])
	add_link(link, xx, yy - hh, ww, hh);

      line      = do_align(block, line, xx, newalign, links);
      xx        = block->x;
      yy        += hh;
      block->h  += hh;
      needspace = 0;
      ptr ++;
    }
    else if (isspace(*ptr))
    {
      needspace = 1;

      ptr ++;
    }
    else if (*ptr == '&' && s < (buf + sizeof(buf) - 1))
    {
      ptr ++;

      if (strncasecmp(ptr, "amp;", 4) == 0)
      {
        *s++ = '&';
	ptr += 4;
      }
      else if (strncasecmp(ptr, "lt;", 3) == 0)
      {
        *s++ = '<';
	ptr += 3;
      }
      else if (strncasecmp(ptr, "gt;", 3) == 0)
      {
        *s++ = '>';
	ptr += 3;
      }
      else if (strncasecmp(ptr, "nbsp;", 5) == 0)
      {
        *s++ = '\240';
	ptr += 5;
      }
      else if (strncasecmp(ptr, "copy;", 5) == 0)
      {
        *s++ = '\251';
	ptr += 5;
      }
      else if (strncasecmp(ptr, "reg;", 4) == 0)
      {
        *s++ = '\256';
	ptr += 4;
      }
      else if (strncasecmp(ptr, "quot;", 5) == 0)
      {
        *s++ = '\"';
	ptr += 5;
      }

      if ((size + 2) > hh)
        hh = size + 2;
    }
    else
    {
      if (s < (buf + sizeof(buf) - 1))
        *s++ = *ptr++;
      else
        ptr ++;

      if ((size + 2) > hh)
        hh = size + 2;
    }
  }

  if (s > buf && !pre && !head)
  {
    *s = '\0';
    ww = (int)fl_width(buf);

    if (needspace && xx > block->x)
      ww += (int)fl_width(' ');

    if ((xx + ww) > block->w)
    {
      line     = do_align(block, line, xx, newalign, links);
      xx       = block->x;
      yy       += hh;
      block->h += hh;
      hh       = 0;
    }

    if (link[0])
      add_link(link, xx, yy - size, ww, size);

    xx += ww;
    if ((size + 2) > hh)
      hh = size + 2;

    needspace = 0;
  }

  block->end = ptr;
  size_      = yy + hh;

  if (ntargets_ > 1)
    qsort(targets_, ntargets_, sizeof(HelpTarget),
          (int (*)(const void *, const void *))compare_targets);

  if (size_ < (h() - 8))
    scrollbar_.hide();
  else
    scrollbar_.show();

  topline(topline_);
}


//
// 'HelpView::get_align()' - Get an alignment attribute.
//

int					// O - Alignment
HelpView::get_align(const char *p,	// I - Pointer to start of attrs
                    int        a)	// I - Default alignment
{
  char	buf[255];			// Alignment value


  if (get_attr(p, "ALIGN", buf, sizeof(buf)) == NULL)
    return (a);

  if (strcasecmp(buf, "CENTER") == 0)
    return (CENTER);
  else if (strcasecmp(buf, "RIGHT") == 0)
    return (RIGHT);
  else
    return (LEFT);
}


//
// 'HelpView::get_attr()' - Get an attribute value from the string.
//

const char *				// O - Pointer to buf or NULL
HelpView::get_attr(const char *p,	// I - Pointer to start of attributes
                   const char *n,	// I - Name of attribute
		   char       *buf,	// O - Buffer for attribute value
		   int        bufsize)	// I - Size of buffer
{
  char	name[255],			// Name from string
	*ptr,				// Pointer into name or value
	quote;				// Quote


  buf[0] = '\0';

  while (*p && *p != '>')
  {
    while (isspace(*p))
      p ++;

    if (*p == '>' || !*p)
      return (NULL);

    for (ptr = name; *p && !isspace(*p) && *p != '=' && *p != '>';)
      if (ptr < (name + sizeof(name) - 1))
        *ptr++ = *p++;
      else
        p ++;

    *ptr = '\0';

    if (isspace(*p) || !*p || *p == '>')
      buf[0] = '\0';
    else
    {
      if (*p == '=')
        p ++;

      for (ptr = buf; *p && !isspace(*p) && *p != '>';)
        if (*p == '\'' || *p == '\"')
	{
	  quote = *p++;

	  while (*p && *p != quote)
	    if ((ptr - buf + 1) < bufsize)
	      *ptr++ = *p++;
	    else
	      p ++;

          if (*p == quote)
	    p ++;
	}
	else if ((ptr - buf + 1) < bufsize)
	  *ptr++ = *p++;
	else
	  p ++;

      *ptr = '\0';
    }

    if (strcasecmp(n, name) == 0)
      return (buf);
    else
      buf[0] = '\0';

    if (*p == '>')
      return (NULL);
  }

  return (NULL);
}


//
// 'HelpView::get_color()' - Get an alignment attribute.
//

Fl_Color				// O - Color value
HelpView::get_color(const char *n,	// I - Color name
                    Fl_Color   c)	// I - Default color value
{
  int	rgb, r, g, b;			// RGB values


  if (!n)
    return (c);

  if (n[0] == '#')
  {
    // Do hex color lookup
    rgb = strtol(n + 1, NULL, 16);

    r = rgb >> 16;
    g = (rgb >> 8) & 255;
    b = rgb & 255;

    if (r == g && g == b)
      return (fl_gray_ramp(FL_NUM_GRAY * r / 256));
    else
      return (fl_color_cube((FL_NUM_RED - 1) * r / 255,
                            (FL_NUM_GREEN - 1) * g / 255,
			    (FL_NUM_BLUE - 1) * b / 255));
  }
  else if (strcasecmp(n, "black") == 0)
    return (FL_BLACK);
  else if (strcasecmp(n, "red") == 0)
    return (FL_RED);
  else if (strcasecmp(n, "green") == 0)
    return (fl_color_cube(0, 4, 0));
  else if (strcasecmp(n, "yellow") == 0)
    return (FL_YELLOW);
  else if (strcasecmp(n, "blue") == 0)
    return (FL_BLUE);
  else if (strcasecmp(n, "magenta") == 0 || strcasecmp(n, "fuchsia") == 0)
    return (FL_MAGENTA);
  else if (strcasecmp(n, "cyan") == 0 || strcasecmp(n, "aqua") == 0)
    return (FL_CYAN);
  else if (strcasecmp(n, "white") == 0)
    return (FL_WHITE);
  else if (strcasecmp(n, "gray") == 0 || strcasecmp(n, "grey") == 0)
    return (FL_GRAY);
  else if (strcasecmp(n, "lime") == 0)
    return (FL_GREEN);
  else if (strcasecmp(n, "maroon") == 0)
    return (fl_color_cube(2, 0, 0));
  else if (strcasecmp(n, "navy") == 0)
    return (fl_color_cube(0, 0, 2));
  else if (strcasecmp(n, "olive") == 0)
    return (fl_color_cube(2, 4, 0));
  else if (strcasecmp(n, "purple") == 0)
    return (fl_color_cube(2, 0, 2));
  else if (strcasecmp(n, "silver") == 0)
    return (FL_LIGHT2);
  else if (strcasecmp(n, "teal") == 0)
    return (fl_color_cube(0, 4, 2));
  else
    return (c);
}


//
// 'HelpView::handle()' - Handle events in the widget.
//

int				// O - 1 if we handled it, 0 otherwise
HelpView::handle(int event)	// I - Event to handle
{
  int		i;		// Looping var
  int		xx, yy;		// Adjusted mouse position
  HelpLink	*link;		// Current link
  char		target[32];	// Current target


  switch (event)
  {
    case FL_MOVE :
    case FL_PUSH :
        if (Fl::event_button() == 4)
	{
	  // XFree86 maps button 4 to the "wheel up" motion...
	  topline(topline() - textsize_ * 3);
	  return (1);
	}
	else if (Fl::event_button() == 5)
	{
	  // XFree86 maps button 5 to the "wheel down" motion...
	  topline(topline() + textsize_ * 3);
	  return (1);
	}

        xx = Fl::event_x() - x();
	yy = Fl::event_y() - y() + topline_;
        if (!scrollbar_.visible() || xx < (w() - 20))
	  break;

    default :
	// Use the Fl_Group handler...
	return (Fl_Group::handle(event));
  }

  // Handle mouse clicks on links...
  for (i = nlinks_, link = links_; i > 0; i --, link ++)
    if (xx >= link->x && xx < link->w &&
        yy >= link->y && yy < link->h)
      break;

  if (!i)
  {
    fl_cursor(FL_CURSOR_DEFAULT);
    return (1);
  }

  // Change the cursor for FL_MOTION events, and go to the link for
  // clicks...
  if (event == FL_MOVE)
    fl_cursor(FL_CURSOR_HAND);
  else
  {
    fl_cursor(FL_CURSOR_DEFAULT);

    strncpy(target, link->name, sizeof(target) - 1);
    target[sizeof(target) - 1] = '\0';

    set_changed();

    if (strcmp(link->filename, filename_) != 0 && link->filename[0])
    {
      char	dir[1024];	// Current directory
      char	temp[1024],	// Temporary filename
		*tempptr;	// Pointer into temporary filename


      if (strchr(directory_, ':') != NULL && strchr(link->filename, ':') == NULL)
      {
	if (link->filename[0] == '/')
	{
          strcpy(temp, directory_);
          if ((tempptr = strrchr(strchr(directory_, ':') + 3, '/')) != NULL)
	    strcpy(tempptr, link->filename);
	  else
	    strcat(temp, link->filename);
	}
	else
	  sprintf(temp, "%s/%s", directory_, link->filename);

	load(temp);
      }
      else if (link->filename[0] != '/' && strchr(link->filename, ':') == NULL)
      {
	if (directory_[0])
	  sprintf(temp, "%s/%s", directory_, link->filename);
	else
	{
	  getcwd(dir, sizeof(dir));
	  sprintf(temp, "file:%s/%s", dir, link->filename);
	}

        load(temp);
      }
      else
        load(link->filename);
    }
    else if (target[0])
      topline(target);
    else
      topline(0);
  }

  return (1);
}


//
// 'HelpView::HelpView()' - Build a HelpView widget.
//

HelpView::HelpView(int        xx,	// I - Left position
                   int        yy,	// I - Top position
		   int        ww,	// I - Width in pixels
		   int        hh,	// I - Height in pixels
		   const char *l)
    : Fl_Group(xx, yy, ww, hh, l),
      scrollbar_(xx + ww - 17, yy, 17, hh)
{
  link_        = (HelpFunc *)0;

  filename_[0] = '\0';
  value_       = NULL;

  ablocks_     = 0;
  nblocks_     = 0;
  blocks_      = (HelpBlock *)0;

  nimage_      = 0;
  aimage_      = 0;
  image_       = (HelpImage *)0;

  if (!broken_image)
    broken_image = new Fl_Pixmap(broken_xpm);

  alinks_      = 0;
  nlinks_      = 0;
  links_       = (HelpLink *)0;

  atargets_    = 0;
  ntargets_    = 0;
  targets_     = (HelpTarget *)0;

  nfonts_      = 0;
  textfont_    = FL_TIMES;
  textsize_    = 12;

  topline_     = 0;
  size_        = 0;

  color(FL_WHITE);
  textcolor(FL_BLACK);
  selection_color(FL_BLUE);

  scrollbar_.value(0, hh, 0, 1);
  scrollbar_.step(8.0);
  scrollbar_.show();
  scrollbar_.callback(scrollbar_callback);

  end();
}


//
// 'HelpView::~HelpView()' - Destroy a HelpView widget.
//

HelpView::~HelpView()
{
  int		i;		// Looping var
  HelpImage	*img;		// Current image


  if (nblocks_)
    free(blocks_);
  if (nlinks_)
    free(links_);
  if (ntargets_)
    free(targets_);
  if (value_)
    free((void *)value_);
  if (image_)
  {
    for (i = nimage_, img = image_; i > 0; i --, img ++)
    {
      delete img->image;
      free(img->data);
      free(img->name);
    }
  }
}


//
// 'HelpView::load()' - Load the specified file.
//

int				// O - 0 on success, -1 on error
HelpView::load(const char *f)	// I - Filename to load (may also have target)
{
  FILE		*fp;		// File to read from
  long		len;		// Length of file
  char		*target;	// Target in file
  char		*slash;		// Directory separator
  const char	*localname;	// Local filename
  char		error[1024];	// Error buffer


  strcpy(filename_, f);
  strcpy(directory_, filename_);

  if ((slash = strrchr(directory_, '/')) == NULL)
    directory_[0] = '\0';
  else if (slash > directory_ && slash[-1] != '/')
    *slash = '\0';

  if ((target = strrchr(filename_, '#')) != NULL)
    *target++ = '\0';

  if (link_)
    localname = (*link_)(filename_);
  else
    localname = filename_;

  if (localname != NULL &&
      (strncmp(localname, "ftp:", 4) == 0 ||
       strncmp(localname, "http:", 5) == 0 ||
       strncmp(localname, "https:", 6) == 0 ||
       strncmp(localname, "ipp:", 4) == 0 ||
       strncmp(localname, "mailto:", 7) == 0 ||
       strncmp(localname, "news:", 5) == 0))
    localname = NULL;	// Remote link wasn't resolved...
  else if (localname != NULL &&
           strncmp(localname, "file:", 5) == 0)
    localname += 5;	// Adjust for local filename...
      
  if (value_ != NULL)
  {
    free((void *)value_);
    value_ = NULL;
  }

  if (localname)
  {
    if ((fp = fopen(localname, "rb")) != NULL)
    {
      fseek(fp, 0, SEEK_END);
      len = ftell(fp);
      rewind(fp);

      value_ = (const char *)calloc(len + 1, 1);
      fread((void *)value_, 1, len, fp);
      fclose(fp);
    }
    else
    {
      sprintf(error, "%s: %s\n", localname, strerror(errno));
      value_ = strdup(error);
    }
  }
  else
  {
    sprintf(error, "%s: %s\n", filename_, strerror(errno));
    value_ = strdup(error);
  }

  format();

  if (target)
    topline(target);
  else
    topline(0);

  return (0);
}


//
// 'HelpView::load_gif()' - Load a GIF image file...
//

int					// O - 0 = success, -1 = fail
HelpView::load_gif(HelpImage *img,	// I - Image pointer
        	   FILE      *fp)	// I - File to load from
{
  unsigned char	buf[1024];		// Input buffer
  gif_cmap_t	cmap;			// Colormap
  int		ncolors,		// Bits per pixel
		transparent;		// Transparent color index


  // Read the header; we already know it is a GIF file...
  fread(buf, 13, 1, fp);

  img->w  = (buf[7] << 8) | buf[6];
  img->h  = (buf[9] << 8) | buf[8];
  ncolors = 2 << (buf[10] & 0x07);

  if (buf[10] & GIF_COLORMAP)
    if (!gif_read_cmap(fp, ncolors, cmap))
      return (0);

  transparent = -1;

  while (1)
  {
    switch (getc(fp))
    {
      case ';' :	// End of image
          return (0);	// Early end of file

      case '!' :	// Extension record
          buf[0] = getc(fp);
          if (buf[0] == 0xf9)	// Graphic Control Extension
          {
            gif_get_block(fp, buf);
            if (buf[0] & 1)	// Get transparent color index
              transparent = buf[3];
          }

          while (gif_get_block(fp, buf) != 0);
          break;

      case ',' :	// Image data
          fread(buf, 9, 1, fp);

          if (buf[8] & GIF_COLORMAP)
          {
            ncolors = 2 << (buf[8] & 0x07);

	    if (!gif_read_cmap(fp, ncolors, cmap))
	      return (0);
	  }

          if (transparent >= 0)
          {
	    unsigned	rgba = fltk_colors[bgcolor_];


            // Map transparent color to background color...
	    cmap[transparent][0] = rgba >> 24;
            cmap[transparent][1] = rgba >> 16;
            cmap[transparent][2] = rgba >> 8;
          }

          img->w    = (buf[5] << 8) | buf[4];
          img->h    = (buf[7] << 8) | buf[6];
          img->d    = 3;
          img->data = (unsigned char *)malloc(img->w * img->h * img->d);
          if (img->data == NULL)
            return (0);

	  return (gif_read_image(fp, img, cmap, buf[8] & GIF_INTERLACE));
    }
  }
}


#ifdef HAVE_LIBJPEG
//
// 'HelpView::load_jpeg()' - Load a JPEG image file.
//

int					// O - 0 = success, -1 = fail
HelpView::load_jpeg(HelpImage *img,	// I - Image pointer
                    FILE      *fp)	// I - File to load from
{
  struct jpeg_decompress_struct	cinfo;	// Decompressor info
  struct jpeg_error_mgr		jerr;	// Error handler info
  JSAMPROW			row;	// Sample row pointer


  cinfo.err = jpeg_std_error(&jerr);
  jpeg_create_decompress(&cinfo);
  jpeg_stdio_src(&cinfo, fp);
  jpeg_read_header(&cinfo, 1);

  cinfo.quantize_colors      = 0;
  cinfo.out_color_space      = JCS_RGB;
  cinfo.out_color_components = 3;
  cinfo.output_components    = 3;

  jpeg_calc_output_dimensions(&cinfo);

  img->w  = cinfo.output_width;
  img->h = cinfo.output_height;
  img->d  = cinfo.output_components;
  img->data = (unsigned char *)malloc(img->w * img->h * img->d);

  if (img->data == NULL)
  {
    jpeg_destroy_decompress(&cinfo);
    return (0);
  }

  jpeg_start_decompress(&cinfo);

  while (cinfo.output_scanline < cinfo.output_height)
  {
    row = (JSAMPROW)(img->data +
                     cinfo.output_scanline * cinfo.output_width *
                     cinfo.output_components);
    jpeg_read_scanlines(&cinfo, &row, (JDIMENSION)1);
  }

  jpeg_finish_decompress(&cinfo);
  jpeg_destroy_decompress(&cinfo);

  return (1);
}
#endif // HAVE_LIBJPEG


#ifdef HAVE_LIBPNG
//
// 'HelpView::load_png()' - Load a PNG image file.
//

int					// O - 0 = success, -1 = fail
HelpView::load_png(HelpImage *img,	// I - Image pointer
        	   FILE      *fp)	// I - File to read from
{
  int		i;			// Looping var
  png_structp	pp;			// PNG read pointer
  png_infop	info;			// PNG info pointers
  png_bytep	*rows;			// PNG row pointers
  png_color_16	bg;			// Background color


  // Setup the PNG data structures...
  pp   = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  info = png_create_info_struct(pp);

  // Initialize the PNG read "engine"...
  png_init_io(pp, fp);

  // Get the image dimensions and convert to grayscale or RGB...
  png_read_info(pp, info);

  if (info->color_type == PNG_COLOR_TYPE_PALETTE)
    png_set_expand(pp);

  if (info->color_type == PNG_COLOR_TYPE_GRAY)
    img->d = 1;
  else
    img->d = 3;

  img->w  = info->width;
  img->h = info->height;
  img->data = (unsigned char *)malloc(img->w * img->h * 3);

  if (info->bit_depth < 8)
  {
    png_set_packing(pp);
    png_set_expand(pp);

    if (info->valid & PNG_INFO_sBIT)
      png_set_shift(pp, &(info->sig_bit));
  }
  else if (info->bit_depth == 16)
    png_set_strip_16(pp);

  // Handle transparency...
  if (png_get_valid(pp, info, PNG_INFO_tRNS))
    png_set_tRNS_to_alpha(pp);

  // Background color...
  unsigned	rgba = fltk_colors[bgcolor_];

  bg.red   = 65535 * (rgba >> 24) / 255;
  bg.green = 65535 * ((rgba >> 16) & 255) / 255;
  bg.blue  = 65535 * ((rgba >> 8) & 255) / 255;

  png_set_background(pp, &bg, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);

  // Allocate pointers...
  rows = (png_bytep *)calloc(info->height, sizeof(png_bytep));

  for (i = 0; i < (int)info->height; i ++)
    if (info->color_type == PNG_COLOR_TYPE_GRAY)
      rows[i] = img->data + i * img->w;
    else
      rows[i] = img->data + i * img->w * 3;

  // Read the image, handling interlacing as needed...
  for (i = png_set_interlace_handling(pp); i > 0; i --)
    png_read_rows(pp, rows, NULL, img->h);

  // Free memory and return...
  free(rows);

  png_read_end(pp, info);
  png_read_destroy(pp, info, NULL);

  return (1);
}
#endif // HAVE_LIBPNG


//
// 'HelpView::resize()' - Resize the help widget.
//

void
HelpView::resize(int xx,	// I - New left position
                 int yy,	// I - New top position
		 int ww,	// I - New width
		 int hh)	// I - New height
{
  Fl_Widget::resize(xx, yy, ww, hh);
  scrollbar_.resize(xx + ww - 17, yy, 17, hh);

  format();
}


//
// 'HelpView::topline()' - Set the top line to the named target.
//

void
HelpView::topline(const char *n)	// I - Target name
{
  HelpTarget	key,			// Target name key
		*target;		// Pointer to matching target


  if (ntargets_ == 0)
    return;

  strncpy(key.name, n, sizeof(key.name) - 1);
  key.name[sizeof(key.name) - 1] = '\0';

  target = (HelpTarget *)bsearch(&key, targets_, ntargets_, sizeof(HelpTarget),
                                 (int (*)(const void *, const void *))compare_targets);

  if (target != NULL)
    topline(target->y);
}


//
// 'HelpView::topline()' - Set the top line by number.
//

void
HelpView::topline(int t)	// I - Top line number
{
  if (!value_)
    return;

  if (size_ < (h() - 8) || t < 0)
    t = 0;
  else if (t > size_)
    t = size_;

  topline_ = t;

  scrollbar_.value(topline_, h(), 0, size_);

  do_callback();
  clear_changed();

  redraw();
}


//
// 'HelpView::value()' - Set the help text directly.
//

void
HelpView::value(const char *v)	// I - Text to view
{
  if (!v)
    return;

  if (value_ != NULL)
    free((void *)value_);

  value_ = strdup(v);

  format();

  set_changed();
  topline(0);
}


//
// 'gif_read_cmap()' - Read the colormap from a GIF file...
//

static int				// O - -1 = error, 0 = success
gif_read_cmap(FILE       *fp,		// I - File to read from
  	      int        ncolors,	// I - Number of colors
	      gif_cmap_t cmap)		// O - Colormap
{
  // Read the colormap...
  if (fread(cmap, 3, ncolors, fp) < (size_t)ncolors)
    return (0);

  return (1);
}


//
// 'gif_get_block()' - Read a GIF data block...
//

static int				// O - Number characters read
gif_get_block(FILE  *fp,		// I - File to read from
	      unsigned char *buf)	// I - Input buffer
{
  int	count;				// Number of character to read


  // Read the count byte followed by the data from the file...
  if ((count = getc(fp)) == EOF)
  {
    gif_eof = 1;
    return (-1);
  }
  else if (count == 0)
    gif_eof = 1;
  else if (fread(buf, 1, count, fp) < (size_t)count)
  {
    gif_eof = 1;
    return (-1);
  }
  else
    gif_eof = 0;

  return (count);
}


//
// 'gif_get_code()' - Get a LZW code from the file...
//

static int				// O - LZW code
gif_get_code(FILE *fp,			// I - File to read from
	     int  code_size,		// I - Size of code in bits
	     int  first_time)		// I - 1 = first time, 0 = not first time
{
  unsigned		i, j,		// Looping vars
			ret;		// Return value
  int			count;		// Number of bytes read
  static unsigned char	buf[280];	// Input buffer
  static unsigned	curbit,		// Current bit
			lastbit,	// Last bit in buffer
			done,		// Done with this buffer?
			last_byte;	// Last byte in buffer
  static unsigned	bits[8] =	// Bit masks for codes
			{
			  0x01, 0x02, 0x04, 0x08,
			  0x10, 0x20, 0x40, 0x80
			};


  if (first_time)
  {
    // Just initialize the input buffer...
    curbit  = 0;
    lastbit = 0;
    done    = 0;

    return (0);
  }


  if ((curbit + code_size) >= lastbit)
  {
    // Don't have enough bits to hold the code...
    if (done)
      return (-1);	// Sorry, no more...

    // Move last two bytes to front of buffer...
    if (last_byte > 1)
    {
      buf[0]    = buf[last_byte - 2];
      buf[1]    = buf[last_byte - 1];
      last_byte = 2;
    }
    else if (last_byte == 1)
    {
      buf[0]    = buf[last_byte - 1];
      last_byte = 1;
    }

    // Read in another buffer...
    if ((count = gif_get_block (fp, buf + last_byte)) <= 0)
    {
      // Whoops, no more data!
      done = 1;
      return (-1);
    }

    // Update buffer state...
    curbit    = (curbit - lastbit) + 8 * last_byte;
    last_byte += count;
    lastbit   = last_byte * 8;
  }

  ret = 0;
  for (ret = 0, i = curbit + code_size - 1, j = code_size;
       j > 0;
       i --, j --)
    ret = (ret << 1) | ((buf[i / 8] & bits[i & 7]) != 0);

  curbit += code_size;

  return ret;
}


//
// 'gif_read_lzw()' - Read a byte from the LZW stream...
//

static int				// I - Byte from stream
gif_read_lzw(FILE *fp,			// I - File to read from
	     int  first_time,		// I - 1 = first time, 0 = not first time
 	     int  input_code_size)	// I - Code size in bits
{
  int		i,			// Looping var
		code,			// Current code
		incode;			// Input code
  static short	fresh = 0,		// 1 = empty buffers
		code_size,		// Current code size
		set_code_size,		// Initial code size set
		max_code,		// Maximum code used
		max_code_size,		// Maximum code size
		firstcode,		// First code read
		oldcode,		// Last code read
		clear_code,		// Clear code for LZW input
		end_code,		// End code for LZW input
		table[2][4096],		// String table
		stack[8192],		// Output stack
		*sp;			// Current stack pointer


  if (first_time)
  {
    // Setup LZW state...
    set_code_size = input_code_size;
    code_size     = set_code_size + 1;
    clear_code    = 1 << set_code_size;
    end_code      = clear_code + 1;
    max_code_size = 2 * clear_code;
    max_code      = clear_code + 2;

    // Initialize input buffers...
    gif_get_code(fp, 0, 1);

    // Wipe the decompressor table...
    fresh = 1;

    for (i = 0; i < clear_code; i ++)
    {
      table[0][i] = 0;
      table[1][i] = i;
    }

    for (; i < 4096; i ++)
      table[0][i] = table[1][0] = 0;

    sp = stack;

    return (0);
  }
  else if (fresh)
  {
    fresh = 0;

    do
      firstcode = oldcode = gif_get_code(fp, code_size, 0);
    while (firstcode == clear_code);

    return (firstcode);
  }

  if (sp > stack)
    return (*--sp);

  while ((code = gif_get_code (fp, code_size, 0)) >= 0)
  {
    if (code == clear_code)
    {
      for (i = 0; i < clear_code; i ++)
      {
	table[0][i] = 0;
	table[1][i] = i;
      }

      for (; i < 4096; i ++)
	table[0][i] = table[1][i] = 0;

      code_size     = set_code_size + 1;
      max_code_size = 2 * clear_code;
      max_code      = clear_code + 2;

      sp = stack;

      firstcode = oldcode = gif_get_code(fp, code_size, 0);

      return (firstcode);
    }
    else if (code == end_code)
    {
      unsigned char	buf[260];


      if (!gif_eof)
        while (gif_get_block(fp, buf) > 0);

      return (-2);
    }

    incode = code;

    if (code >= max_code)
    {
      *sp++ = firstcode;
      code  = oldcode;
    }

    while (code >= clear_code)
    {
      *sp++ = table[1][code];
      if (code == table[0][code])
	return (255);

      code = table[0][code];
    }

    *sp++ = firstcode = table[1][code];
    code  = max_code;

    if (code < 4096)
    {
      table[0][code] = oldcode;
      table[1][code] = firstcode;
      max_code ++;

      if (max_code >= max_code_size && max_code_size < 4096)
      {
	max_code_size *= 2;
	code_size ++;
      }
    }

    oldcode = incode;

    if (sp > stack)
      return (*--sp);
  }

  return (code);
}


//
// 'gif_read_image()' - Read a GIF image stream...
//

static int				// I - 0 = success, -1 = failure
gif_read_image(FILE       *fp,		// I - Input file
	       HelpImage  *img,		// I - Image pointer
	       gif_cmap_t cmap,		// I - Colormap
	       int        interlace)	// I - Non-zero = interlaced image
{
  unsigned char	code_size,		// Code size
		*temp;			// Current pixel
  int		xpos,			// Current X position
		ypos,			// Current Y position
		pass;			// Current pass
  int		pixel;			// Current pixel
  static int	xpasses[4] = { 8, 8, 4, 2 },
		ypasses[5] = { 0, 4, 2, 1, 999999 };


  xpos      = 0;
  ypos      = 0;
  pass      = 0;
  code_size = getc(fp);

  if (gif_read_lzw(fp, 1, code_size) < 0)
    return (0);

  temp = img->data;

  while ((pixel = gif_read_lzw(fp, 0, code_size)) >= 0)
  {
    temp[0] = cmap[pixel][0];

    if (img->d > 1)
    {
      temp[1] = cmap[pixel][1];
      temp[2] = cmap[pixel][2];
    }

    xpos ++;
    temp += img->d;
    if (xpos == img->w)
    {
      xpos = 0;

      if (interlace)
      {
        ypos += xpasses[pass];
        temp += (xpasses[pass] - 1) * img->w * img->d;

        if (ypos >= img->h)
	{
	  pass ++;

          ypos = ypasses[pass];
          temp = img->data + ypos * img->w * img->d;
	}
      }
      else
	ypos ++;
    }

    if (ypos >= img->h)
      break;
  }

  return (1);
}


//
// 'scrollbar_callback()' - A callback for the scrollbar.
//

static void
scrollbar_callback(Fl_Widget *s, void *)
{
  ((HelpView *)(s->parent()))->topline(int(((Fl_Scrollbar*)s)->value()));
}


//
// End of "$Id: HelpView.cxx,v 1.30 2000/06/29 01:15:55 mike Exp $".
//
