From 5d9961c1c356ad971b5dc084ab957bc82926d8c0 Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Sat, 5 Jul 2025 15:25:11 +0200 Subject: Fl_Help_View: Finl formatting and documentation - restructure header file - sort source file reflecting header - documentation of text selection --- src/Fl_Help_View.cxx | 5015 +++++++++++++++++++++++++------------------------- 1 file changed, 2533 insertions(+), 2482 deletions(-) (limited to 'src') diff --git a/src/Fl_Help_View.cxx b/src/Fl_Help_View.cxx index 283e92f09..8bd3a8aa6 100644 --- a/src/Fl_Help_View.cxx +++ b/src/Fl_Help_View.cxx @@ -64,10 +64,19 @@ static constexpr int MAX_COLUMNS = 200; // -// global flag for image loading (see get_image). +// global and class static values // static char initial_load = 0; +// We don't put the offscreen buffer in the help view class because +// we'd need to include platform.H in the header... +static Fl_Offscreen fl_help_view_buffer; +int Fl_Help_View::selection_push_first_ = 0; +int Fl_Help_View::selection_push_last_ = 0; +int Fl_Help_View::selection_drag_first_ = 0; +int Fl_Help_View::selection_drag_last_ = 0; +Fl_Help_View::Mode Fl_Help_View::draw_mode_ = Mode::DRAW; +int Fl_Help_View::current_pos_ = 0; // // Local functions declarations, implementations are at the end of the file @@ -76,6 +85,13 @@ static char initial_load = 0; static int quote_char(const char *); static std::string to_lower(const std::string &str); static size_t url_scheme(const std::string &url, bool skip_slashes=false); +static const char *vanilla(const char *p, const char *end); +static uint32_t command(const char *cmd); + +static constexpr uint32_t CMD(char a, char b, char c, char d) +{ + return ((a<<24)|(b<<16)|(c<<8)|d); +} // // Static data. @@ -306,95 +322,168 @@ size_t Fl_Help_View::Font_Stack::count() const { } -// ---- Implementation of text selection in Fl_Help_View +// ------ Fl_Help_View Private methods -/* TODO: matt: - The selection code was implemented with binary compatibility within 1.4 - in mind. This means that I was limited to adding static variables - only to not enlarge the Fl_Help_View class. +// ---- HTML source and raw data, getter - The implementation of selection should be change to use class local variables - so text in multiple Fl_Help_View widgets can be selected independently. +/** + \brief Frees memory used for the document. + */ +void Fl_Help_View::free_data() { + // Release all images... + if (value_) { + const char *ptr, // Pointer into block + *attrs; // Pointer to start of element attributes + HV_Edit_Buffer buf; // Text buffer + char attr[1024], // Attribute buffer + wattr[1024], // Width attribute buffer + hattr[1024]; // Height attribute buffer - Still to do: - - &word; style characters mess up our count inside a word boundary - - we can only select words, no individual characters - - no dragging of the selection into another widget - - selection must be cleared if another widget get focus! - - write a comment for every new function - */ + DEBUG_FUNCTION(__LINE__,__FUNCTION__); -/* - The following functions are also used to draw stuff and should be replaced with - local copies that are much faster when merely counting: - - fl_color(Fl_Color); - fl_rectf(int, int, int, int); - fl_push_clip(int, int, int, int); - fl_xyline(int, int, int); - fl_rect() - fl_line() - img->draw() -*/ + for (ptr = value_; *ptr;) + { + if (*ptr == '<') + { + ptr ++; -// We don't put the offscreen buffer in the help view class because -// we'd need to include platform.H in the header... -static Fl_Offscreen fl_help_view_buffer; -int Fl_Help_View::selection_push_first_ = 0; -int Fl_Help_View::selection_push_last_ = 0; -int Fl_Help_View::selection_drag_first_ = 0; -int Fl_Help_View::selection_drag_last_ = 0; -Fl_Help_View::Mode Fl_Help_View::draw_mode_ = Mode::DRAW; -int Fl_Help_View::current_pos_ = 0; + if (strncmp(ptr, "!--", 3) == 0) + { + // Comment... + ptr += 3; + if ((ptr = strstr(ptr, "-->")) != nullptr) + { + ptr += 3; + continue; + } + else + break; + } + + buf.clear(); + + while (*ptr && *ptr != '>' && !isspace((*ptr)&255)) + buf += *ptr++; + + attrs = ptr; + while (*ptr && *ptr != '>') + ptr ++; + + if (*ptr == '>') + ptr ++; + + if (buf.cmp("IMG")) + { + Fl_Shared_Image *img; + int width; + int height; + + get_attr(attrs, "WIDTH", wattr, sizeof(wattr)); + get_attr(attrs, "HEIGHT", hattr, sizeof(hattr)); + width = get_length(wattr); + height = get_length(hattr); + + if (get_attr(attrs, "SRC", attr, sizeof(attr))) { + // Get and release the image to free it from memory... + img = get_image(attr, width, height); + if ((void*)img != &broken_image) { + img->release(); + } + } + } + } + else + ptr++; + } + + free((void *)value_); + value_ = 0; + } + + blocks_ .clear(); + link_list_.clear(); + target_line_map_.clear(); +} /** - \brief Draws a text string in the help view. + \brief Find out if the mouse is over a hyperlink and return the link data. + \parm[in] xx, yy Pixel coordinates inside the widget. + \return Shared pointer to the link if found, nullptr otherwise. + */ +std::shared_ptr Fl_Help_View::find_link(int xx, int yy) +{ + for (auto &link : link_list_) { + if (link->box.contains(xx, yy)) { + return link; + } + } + return nullptr; +} - This function draws the text string \p t at position (\p x, \p y) in the help view. - If the text is selected, it draws a selection rectangle around it and changes the text color. - \param[in] t Text to draw - \param[in] x X position to draw at - \param[in] y Y position to draw at - \param[in] entity_extra_length (unclear) +/** + \brief Follow a link and load the target document or scroll to the target. + This function clears the current selection and loads a new document or + scrolls to a target line in the current document. + \param linkp Shared pointer to the link to follow. */ -void Fl_Help_View::hv_draw(const char *t, int x, int y, int entity_extra_length) +void Fl_Help_View::follow_link(std::shared_ptr linkp) { - if (draw_mode_ == Mode::DRAW) { - if (selected_ && current_pos_=selection_first_) { - Fl_Color c = fl_color(); - fl_color(tmp_selection_color_); - int w = (int)fl_width(t); - if (current_pos_+(int)strlen(t)target;; // Current target + + if ( (linkp->filename_ != filename_) && !linkp->filename_.empty() ) { + // Load the new document, if the filename is different + std::string url; + size_t directory_scheme_length = url_scheme(directory_); + size_t filename_scheme_length = url_scheme(linkp->filename_); + if ( (directory_scheme_length > 0) && (filename_scheme_length == 0) ) { + // If directory_ starts with a scheme (e.g.ftp:), but linkp->filename_ does not: + if (linkp->filename_[0] == '/') { + // If linkp->filename_ is absolute... + url = directory_.substr(0, directory_scheme_length) + linkp->filename_;; + } else { + // If linkp->filename_ is relative, the URL is the directory_ plus the filename + url = directory_ + "/" + linkp->filename_; + } + } else if (linkp->filename_[0] != '/' && (filename_scheme_length == 0)) { + // If the filename is relative and does not start with a scheme (ftp: , etc.)... + if (!directory_.empty()) { + // If we have a current directory, use that as the base for the URL + url = directory_ + "/" + linkp->filename_; + } else { + // If we do not have a current directory, use the application's current working directory + char dir[FL_PATH_MAX]; // Current directory (static size ok until we have fl_getcwd_std() + fl_getcwd(dir, sizeof(dir)); + url = "file:" + std::string(dir) + "/" + linkp->filename_; + } } else { - fl_draw(t, x, y); + // If the filename is absolute or starts with a protocol (e.g.ftp:), use it as is + url = linkp->filename_; } - } else { - // If draw_mode_ is not DRAW, we don't actually draw anything, but instead - // measure where text blocks are on screen during a mouse selection process. - int w = (int)fl_width(t); - if ( (Fl::event_x() >= x) && (Fl::event_x() < x+w) ) { - if ( (Fl::event_y() >= y-fl_height()+fl_descent()) && (Fl::event_y() <= y+fl_descent()) ) { - int f = (int) current_pos_; - int l = (int) (f+strlen(t)); // use 'quote_char' to calculate the true length of the HTML string - if (draw_mode_ == Mode::PUSH) { - selection_push_first_ = f; - selection_push_last_ = l; - } else { // Mode::DRAG - selection_drag_first_ = f; - selection_drag_last_ = l + entity_extra_length; - } - } + + // If a target is specified, append it to the URL + if (!linkp->target.empty()) { + url += "#" + linkp->target; } + + load(url.c_str()); + + } else if (!target.empty()) { + // Keep the same document, scroll to the target line + topline(target.c_str()); + } else { + // No target, no filename, just scroll to the top of the document + topline(0); } + + // Scroll the content horizontally to the left + leftline(0); } +// ---- HTML interpretation and formatting /** \brief Adds a text block to the list. @@ -517,808 +606,671 @@ int Fl_Help_View::do_align( /** - \brief Draws the Fl_Help_View widget. + \brief Formats the help text. */ -void Fl_Help_View::draw() -{ - int i; // Looping var - const Text_Block *block; // Pointer to current block - const char *ptr, // Pointer to text in block - *attrs; // Pointer to start of element attributes - HV_Edit_Buffer buf; // Text buffer - char attr[1024]; // Attribute buffer - int xx, yy, ww, hh; // Current positions and sizes - int line; // Current line - Fl_Font font; - Fl_Fontsize fsize; // Current font and size - Fl_Color fcolor; // current font color - int head, pre, // Flags for text - needspace; // Do we need whitespace? - Fl_Boxtype b = box() ? box() : FL_DOWN_BOX; - // Box to draw... - int underline, // Underline text? - xtra_ww; // Extra width for underlined space between words - - DEBUG_FUNCTION(__LINE__,__FUNCTION__); - - // Draw the scrollbar(s) and box first... - ww = w(); - hh = h(); - i = 0; - - draw_box(b, x(), y(), ww, hh, bgcolor_); - - if ( hscrollbar_.visible() || scrollbar_.visible() ) { - int scrollsize = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size(); - int hor_vis = hscrollbar_.visible(); - int ver_vis = scrollbar_.visible(); - // Scrollbar corner - int scorn_x = x() + ww - (ver_vis?scrollsize:0) - Fl::box_dw(b) + Fl::box_dx(b); - int scorn_y = y() + hh - (hor_vis?scrollsize:0) - Fl::box_dh(b) + Fl::box_dy(b); - if ( hor_vis ) { - if ( hscrollbar_.h() != scrollsize ) { // scrollsize changed? - hscrollbar_.resize(x(), scorn_y, scorn_x - x(), scrollsize); - init_sizes(); - } - draw_child(hscrollbar_); - hh -= scrollsize; - } - if ( ver_vis ) { - if ( scrollbar_.w() != scrollsize ) { // scrollsize changed? - scrollbar_.resize(scorn_x, y(), scrollsize, scorn_y - y()); - init_sizes(); - } - draw_child(scrollbar_); - ww -= scrollsize; - } - if ( hor_vis && ver_vis ) { - // Both scrollbars visible? Draw little gray box in corner - fl_color(FL_GRAY); - fl_rectf(scorn_x, scorn_y, scrollsize, scrollsize); - } - } +void Fl_Help_View::format() { + int i; // Looping var + int done; // Are we done yet? + Text_Block *block, // Current block + *cell; // Current table cell + int cells[MAX_COLUMNS], + // Cells in the current row... + 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 + HV_Edit_Buffer buf; // Text buffer + char attr[1024], // Attribute buffer + wattr[1024], // Width attribute buffer + hattr[1024], // Height attribute buffer + linkdest[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 + Fl_Font font; + Fl_Fontsize fsize; // Current font and size + Fl_Color fcolor; // Current font color + unsigned char border; // Draw border? + Align talign; // Current alignment + Align newalign; // New alignment + int head, // In the section? + pre, //
 text?
+                needspace;      // Do we need whitespace?
+  int           table_width,    // Width of table
+                table_offset;   // Offset of table
+  int           column,         // Current table column number
+                columns[MAX_COLUMNS];
+                                // Column widths
+  Fl_Color      tc, rc;         // Table/row background color
+  Fl_Boxtype    b = box() ? box() : FL_DOWN_BOX;
+                                // Box to draw...
+  Margin_Stack  margins;        // Left margin stack...
+  std::vector OL_num;         // if nonnegative, in OL mode and this is the item number
 
-  if (!value_)
-    return;
+  OL_num.push_back(-1);
 
-  if (selected_) {
-    if (Fl::focus() == this) {
-      // If this widget has the focus, we use the selection color directly
-      tmp_selection_color_ = selection_color();
-    } else {
-      // Otherwise we blend the selection color with the background color
-      tmp_selection_color_ = fl_color_average(bgcolor_, selection_color(), 0.8f);
-    }
-    selection_text_color_ = fl_contrast(textcolor_, tmp_selection_color_);
-  }
-  current_pos_ = 0;
+  DEBUG_FUNCTION(__LINE__,__FUNCTION__);
 
-  // Clip the drawing to the inside of the box...
-  fl_push_clip(x() + Fl::box_dx(b), y() + Fl::box_dy(b),
-               ww - Fl::box_dw(b), hh - Fl::box_dh(b));
-  fl_color(textcolor_);
+  // Reset document width...
+  int scrollsize = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size();
+  hsize_ = w() - scrollsize - Fl::box_dw(b);
 
-  // Draw all visible blocks...
-  for (i = 0, block = &blocks_[0]; i < (int)blocks_.size(); i ++, block ++)
-    if ((block->y + block->h) >= topline_ && block->y < (topline_ + h()))
-    {
-      line      = 0;
-      xx        = block->line[line];
-      yy        = block->y - topline_;
-      hh        = 0;
-      pre       = 0;
-      head      = 0;
-      needspace = 0;
-      underline = 0;
+  done = 0;
+  while (!done)
+  {
+    // Reset state variables...
+    done       = 1;
+    blocks_.clear();
+    link_list_.clear();
+    target_line_map_.clear();
+    size_      = 0;
+    bgcolor_   = color();
+    textcolor_ = textcolor();
+    linkcolor_ = fl_contrast(FL_BLUE, color());
 
-      initfont(font, fsize, fcolor);
-      // byte length difference between html entity (encoded by &...;) and
-      // UTF-8 encoding of same character
-      int entity_extra_length = 0;
-      for (ptr = block->start, buf.clear(); ptr < block->end;)
-      {
-        if ((*ptr == '<' || isspace((*ptr)&255)) && buf.size() > 0)
-        {
-          if (!head && !pre)
-          {
-            // Check width...
-            ww = buf.width();
+    tc = rc = bgcolor_;
 
-            if (needspace && xx > block->x)
-              xx += (int)fl_width(' ');
+    title_ = "Untitled";
 
-            if ((xx + ww) > block->w)
-            {
-              if (line < 31)
-                line ++;
-              xx = block->line[line];
-              yy += hh;
-              hh = 0;
-            }
+    if (!value_)
+      return;
 
-            hv_draw(buf.c_str(), xx + x() - leftline_, yy + y(), entity_extra_length);
-            buf.clear();
-            entity_extra_length = 0;
-            if (underline) {
-              xtra_ww = isspace((*ptr)&255)?(int)fl_width(' '):0;
-              fl_xyline(xx + x() - leftline_, yy + y() + 1,
-                        xx + x() - leftline_ + ww + xtra_ww);
-            }
-            current_pos_ = (int) (ptr-value_);
+    // Setup for formatting...
+    initfont(font, fsize, fcolor);
 
-            xx += ww;
-            if ((fsize + 2) > hh)
-              hh = fsize + 2;
+    line         = 0;
+    links        = 0;
+    margins.clear();
+    xx           = 4;
+    yy           = fsize + 2;
+    ww           = 0;
+    column       = 0;
+    border       = 0;
+    hh           = 0;
+    block        = add_block(value_, xx, yy, hsize_, 0);
+    row          = 0;
+    head         = 0;
+    pre          = 0;
+    talign       = Align::LEFT;
+    newalign     = Align::LEFT;
+    needspace    = 0;
+    linkdest[0]  = '\0';
+    table_offset = 0;
 
-            needspace = 0;
+    // Html text character loop
+    for (ptr = value_, buf.clear(); *ptr;)
+    {
+      // End of word?
+      if ((*ptr == '<' || isspace((*ptr)&255)) && buf.size() > 0)
+      {
+        // Get width of word parsed so far...
+        ww = buf.width();
+
+        if (!head && !pre)
+        {
+          // Check width...
+          if (ww > hsize_) {
+            hsize_ = ww;
+            done   = 0;
+            break;
           }
-          else if (pre)
-          {
-            while (isspace((*ptr)&255))
-            {
-              if (*ptr == '\n')
-              {
-                hv_draw(buf.c_str(), xx + x() - leftline_, yy + y());
-                if (underline) fl_xyline(xx + x() - leftline_, yy + y() + 1,
-                                         xx + x() - leftline_ + buf.width());
-                buf.clear();
-                current_pos_ = (int) (ptr-value_);
-                if (line < 31)
-                  line ++;
-                xx = block->line[line];
-                yy += hh;
-                hh = fsize + 2;
-              }
-              else if (*ptr == '\t')
-              {
-                // Do tabs every 8 columns...
-                buf += ' '; // add at least one space
-                while (buf.size() & 7)
-                  buf += ' ';
-              }
-              else {
-                buf += ' ';
-              }
-              if ((fsize + 2) > hh)
-                hh = fsize + 2;
 
-              ptr ++;
-            }
+          if (needspace && xx > block->x)
+            ww += (int)fl_width(' ');
 
-            if (buf.size() > 0)
-            {
-              hv_draw(buf.c_str(), xx + x() - leftline_, yy + y());
-              ww = buf.width();
-              buf.clear();
-              if (underline) fl_xyline(xx + x() - leftline_, yy + y() + 1,
-                                       xx + x() - leftline_ + ww);
-              xx += ww;
-              current_pos_ = (int) (ptr-value_);
-            }
+  //        printf("line = %d, xx = %d, ww = %d, block->x = %d, block->w = %d\n",
+  //           line, xx, ww, block->x, block->w);
 
-            needspace = 0;
-          }
-          else
+          if ((xx + ww) > block->w)
           {
-            buf.clear();
-
-            while (isspace((*ptr)&255))
-              ptr ++;
-            current_pos_ = (int) (ptr-value_);
+            line     = do_align(block, line, xx, newalign, links);
+            xx       = block->x;
+            yy       += hh;
+            block->h += hh;
+            hh       = 0;
           }
-        }
 
-        if (*ptr == '<')
+          if (linkdest[0])
+            add_link(linkdest, xx, yy - fsize, ww, fsize);
+
+          xx += ww;
+          if ((fsize + 2) > hh)
+            hh = fsize + 2;
+
+          needspace = 0;
+        }
+        else if (pre)
         {
-          ptr ++;
+          // Add a link as needed...
+          if (linkdest[0])
+            add_link(linkdest, xx, yy - hh, ww, hh);
 
-          if (strncmp(ptr, "!--", 3) == 0)
+          xx += ww;
+          if ((fsize + 2) > hh)
+            hh = fsize + 2;
+
+          // Handle preformatted text...
+          while (isspace((*ptr)&255))
           {
-            // Comment...
-            ptr += 3;
-            if ((ptr = strstr(ptr, "-->")) != nullptr)
+            if (*ptr == '\n')
             {
-              ptr += 3;
-              continue;
-            }
-            else
-              break;
-          }
+              if (xx > hsize_) break;
 
-          while (*ptr && *ptr != '>' && !isspace((*ptr)&255))
-            buf += *ptr++;
+              line     = do_align(block, line, xx, newalign, links);
+              xx       = block->x;
+              yy       += hh;
+              block->h += hh;
+              hh       = fsize + 2;
+            }
+            else
+              xx += (int)fl_width(' ');
+
+            if ((fsize + 2) > hh)
+              hh = fsize + 2;
 
-          attrs = ptr;
-          while (*ptr && *ptr != '>')
             ptr ++;
+          }
 
-          if (*ptr == '>')
+          if (xx > hsize_) {
+            hsize_ = xx;
+            done   = 0;
+            break;
+          }
+
+          needspace = 0;
+        }
+        else
+        {
+          // Handle normal text or stuff in the  section...
+          while (isspace((*ptr)&255))
             ptr ++;
+        }
 
-          // end of command reached, set the supposed start of printed eord here
-          current_pos_ = (int) (ptr-value_);
-          if (buf.cmp("HEAD"))
-            head = 1;
-          else if (buf.cmp("BR"))
+        buf.clear();
+      }
+
+      if (*ptr == '<')
+      {
+        // Handle html tags..
+        start = ptr;
+        ptr ++;
+
+        if (strncmp(ptr, "!--", 3) == 0)
+        {
+          // Comment...
+          ptr += 3;
+          if ((ptr = strstr(ptr, "-->")) != nullptr)
           {
-            if (line < 31)
-              line ++;
-            xx = block->line[line];
-            yy += hh;
-            hh = 0;
+            ptr += 3;
+            continue;
           }
-          else if (buf.cmp("HR"))
-          {
-            fl_line(block->x + x(), yy + y(), block->w + x(),
-                    yy + y());
+          else
+            break;
+        }
 
-            if (line < 31)
-              line ++;
-            xx = block->line[line];
-            yy += 2 * fsize;//hh;
-            hh = 0;
+        while (*ptr && *ptr != '>' && !isspace((*ptr)&255))
+          buf += *ptr++;
+
+        attrs = ptr;
+        while (*ptr && *ptr != '>')
+          ptr ++;
+
+        if (*ptr == '>')
+          ptr ++;
+
+        if (buf.cmp("HEAD"))
+          head = 1;
+        else if (buf.cmp("/HEAD"))
+          head = 0;
+        else if (buf.cmp("TITLE"))
+        {
+          // Copy the title in the document...
+          title_.clear();
+          for ( ; *ptr != '<' && *ptr; ptr++) {
+            title_.push_back(*ptr);
           }
-          else if (buf.cmp("CENTER") ||
-                   buf.cmp("P") ||
-                   buf.cmp("H1") ||
-                   buf.cmp("H2") ||
-                   buf.cmp("H3") ||
-                   buf.cmp("H4") ||
-                   buf.cmp("H5") ||
-                   buf.cmp("H6") ||
-                   buf.cmp("UL") ||
-                   buf.cmp("OL") ||
-                   buf.cmp("DL") ||
-                   buf.cmp("LI") ||
-                   buf.cmp("DD") ||
-                   buf.cmp("DT") ||
-                   buf.cmp("PRE"))
-          {
-            if (tolower(buf[0]) == 'h')
-            {
-              font  = FL_HELVETICA_BOLD;
-              fsize = textsize_ + '7' - buf[1];
-            }
-            else if (buf.cmp("DT"))
-            {
-              font  = textfont_ | FL_ITALIC;
-              fsize = textsize_;
+          buf.clear();
+        }
+        else if (buf.cmp("A"))
+        {
+          if (get_attr(attrs, "NAME", attr, sizeof(attr)) != nullptr)
+            add_target(attr, yy - fsize - 2);
+
+          if (get_attr(attrs, "HREF", attr, sizeof(attr)) != nullptr)
+            strlcpy(linkdest, attr, sizeof(linkdest));
+        }
+        else if (buf.cmp("/A"))
+          linkdest[0] = '\0';
+        else if (buf.cmp("BODY"))
+        {
+          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)),
+                                 fl_contrast(FL_BLUE, color()));
+        }
+        else if (buf.cmp("BR"))
+        {
+          line     = do_align(block, line, xx, newalign, links);
+          xx       = block->x;
+          block->h += hh;
+          yy       += hh;
+          hh       = 0;
+        }
+        else if (buf.cmp("CENTER") ||
+                 buf.cmp("P") ||
+                 buf.cmp("H1") ||
+                 buf.cmp("H2") ||
+                 buf.cmp("H3") ||
+                 buf.cmp("H4") ||
+                 buf.cmp("H5") ||
+                 buf.cmp("H6") ||
+                 buf.cmp("UL") ||
+                 buf.cmp("OL") ||
+                 buf.cmp("DL") ||
+                 buf.cmp("LI") ||
+                 buf.cmp("DD") ||
+                 buf.cmp("DT") ||
+                 buf.cmp("HR") ||
+                 buf.cmp("PRE") ||
+                 buf.cmp("TABLE"))
+        {
+          block->end = start;
+          line       = do_align(block, line, xx, newalign, links);
+          newalign   = buf.cmp("CENTER") ? Align::CENTER : Align::LEFT;
+          xx         = block->x;
+          block->h   += hh;
+
+          if (buf.cmp("OL")) {
+            int ol_num = 1;
+            if (get_attr(attrs, "START", attr, sizeof(attr)) != nullptr) {
+              errno = 0;
+              char *endptr = 0;
+              ol_num = (int)strtol(attr, &endptr, 10);
+              if (errno || endptr == attr || ol_num < 0)
+                ol_num = 1;
             }
-            else if (buf.cmp("PRE"))
-            {
-              font  = FL_COURIER;
-              fsize = textsize_;
-              pre   = 1;
+            OL_num.push_back(ol_num);
+          }
+          else if (buf.cmp("UL"))
+            OL_num.push_back(-1);
+
+          if (buf.cmp("UL") ||
+              buf.cmp("OL") ||
+              buf.cmp("DL"))
+          {
+            block->h += fsize + 2;
+            xx       = margins.push(4 * fsize);
+          }
+          else if (buf.cmp("TABLE"))
+          {
+            if (get_attr(attrs, "BORDER", attr, sizeof(attr)))
+              border = (uchar)atoi(attr);
+            else
+              border = 0;
+
+            tc = rc = get_color(get_attr(attrs, "BGCOLOR", attr, sizeof(attr)), bgcolor_);
+
+            block->h += fsize + 2;
+
+            format_table(&table_width, columns, start);
+
+            if ((xx + table_width) > hsize_) {
+#ifdef DEBUG
+              printf("xx=%d, table_width=%d, hsize_=%d\n", xx, table_width,
+                     hsize_);
+#endif // DEBUG
+              hsize_ = xx + table_width;
+              done   = 0;
+              break;
             }
 
-            if (buf.cmp("LI"))
+            switch (get_align(attrs, talign))
             {
-              if (block->ol) {
-                char buf[10];
-                snprintf(buf, sizeof(buf), "%d. ", block->ol_num);
-                hv_draw(buf, xx - (int)fl_width(buf) + x() - leftline_, yy + y());
-              }
-              else {
-                // draw bullet (•) Unicode: U+2022, UTF-8 (hex): e2 80 a2
-                unsigned char bullet[4] = { 0xe2, 0x80, 0xa2, 0x00 };
-                hv_draw((char *)bullet, xx - fsize + x() - leftline_, yy + y());
-              }
+              default :
+                  table_offset = 0;
+                  break;
+
+              case Align::CENTER :
+                  table_offset = (hsize_ - table_width) / 2 - textsize_;
+                  break;
+
+              case Align::RIGHT :
+                  table_offset = hsize_ - table_width - textsize_;
+                  break;
             }
 
-            pushfont(font, fsize);
-            buf.clear();
+            column = 0;
           }
-          else if (buf.cmp("A") &&
-                   get_attr(attrs, "HREF", attr, sizeof(attr)) != nullptr)
+
+          if (tolower(buf[0]) == 'h' && isdigit(buf[1]))
           {
-            fl_color(linkcolor_);
-            underline = 1;
+            font  = FL_HELVETICA_BOLD;
+            fsize = textsize_ + '7' - buf[1];
           }
-          else if (buf.cmp("/A"))
+          else if (buf.cmp("DT"))
           {
-            fl_color(textcolor_);
-            underline = 0;
+            font  = textfont_ | FL_ITALIC;
+            fsize = textsize_;
           }
-          else if (buf.cmp("FONT"))
+          else if (buf.cmp("PRE"))
           {
-            if (get_attr(attrs, "COLOR", attr, sizeof(attr)) != nullptr) {
-              textcolor_ = get_color(attr, textcolor_);
-            }
+            font  = FL_COURIER;
+            fsize = textsize_;
+            pre   = 1;
+          }
+          else
+          {
+            font  = textfont_;
+            fsize = textsize_;
+          }
 
-            if (get_attr(attrs, "FACE", attr, sizeof(attr)) != nullptr) {
-              if (!strncasecmp(attr, "helvetica", 9) ||
-                  !strncasecmp(attr, "arial", 5) ||
-                  !strncasecmp(attr, "sans", 4)) font = FL_HELVETICA;
-              else if (!strncasecmp(attr, "times", 5) ||
-                       !strncasecmp(attr, "serif", 5)) font = FL_TIMES;
-              else if (!strncasecmp(attr, "symbol", 6)) font = FL_SYMBOL;
-              else font = FL_COURIER;
-            }
+          pushfont(font, fsize);
 
-            if (get_attr(attrs, "SIZE", attr, sizeof(attr)) != nullptr) {
-              if (isdigit(attr[0] & 255)) {
-                // Absolute size
-                fsize = (int)(textsize_ * pow(1.2, atof(attr) - 3.0));
-              } else {
-                // Relative size
-                fsize = (int)(fsize * pow(1.2, atof(attr) - 3.0));
-              }
-            }
+          yy = block->y + block->h;
+          hh = 0;
 
-            pushfont(font, fsize);
-          }
-          else if (buf.cmp("/FONT"))
+          if ((tolower(buf[0]) == 'h' && isdigit(buf[1])) ||
+              buf.cmp("DD") ||
+              buf.cmp("DT") ||
+              buf.cmp("P"))
+            yy += fsize + 2;
+          else if (buf.cmp("HR"))
           {
-            popfont(font, fsize, textcolor_);
+            hh += 2 * fsize;
+            yy += fsize;
           }
-          else if (buf.cmp("U"))
-            underline = 1;
-          else if (buf.cmp("/U"))
-            underline = 0;
-          else if (buf.cmp("B") ||
-                   buf.cmp("STRONG"))
-            pushfont(font |= FL_BOLD, fsize);
-          else if (buf.cmp("TD") ||
-                   buf.cmp("TH"))
-          {
-            int tx, ty, tw, th;
-
-            if (tolower(buf[1]) == 'h')
-              pushfont(font |= FL_BOLD, fsize);
-            else
-              pushfont(font = textfont_, fsize);
 
-            tx = block->x - 4 - leftline_;
-            ty = block->y - topline_ - fsize - 3;
-            tw = block->w - block->x + 7;
-            th = block->h + fsize - 5;
+          if (row)
+            block = add_block(start, xx, yy, block->w, 0);
+          else
+            block = add_block(start, xx, yy, hsize_, 0);
 
-            if (tx < 0)
-            {
-              tw += tx;
-              tx  = 0;
+          if (buf.cmp("LI")) {
+            block->ol = 0;
+            if (OL_num.size() && (OL_num.back() >= 0)) {
+              block->ol = 1;
+              block->ol_num = OL_num.back();
+              OL_num.back()++;
             }
+          }
 
-            if (ty < 0)
-            {
-              th += ty;
-              ty  = 0;
-            }
+          needspace = 0;
+          line      = 0;
 
-            tx += x();
-            ty += y();
+          if (buf.cmp("CENTER"))
+            newalign = talign = Align::CENTER;
+          else
+            newalign = get_align(attrs, talign);
+        }
+        else if (buf.cmp("/CENTER") ||
+                 buf.cmp("/P") ||
+                 buf.cmp("/H1") ||
+                 buf.cmp("/H2") ||
+                 buf.cmp("/H3") ||
+                 buf.cmp("/H4") ||
+                 buf.cmp("/H5") ||
+                 buf.cmp("/H6") ||
+                 buf.cmp("/PRE") ||
+                 buf.cmp("/UL") ||
+                 buf.cmp("/OL") ||
+                 buf.cmp("/DL") ||
+                 buf.cmp("/TABLE"))
+        {
+          line       = do_align(block, line, xx, newalign, links);
+          xx         = block->x;
+          block->end = ptr;
 
-            if (block->bgcolor != bgcolor_)
-            {
-              fl_color(block->bgcolor);
-              fl_rectf(tx, ty, tw, th);
-              fl_color(textcolor_);
-            }
+          if (buf.cmp("/OL") ||
+              buf.cmp("/UL")) {
+            if (OL_num.size()) OL_num.pop_back();
+          }
 
-            if (block->border)
-              fl_rect(tx, ty, tw, th);
+          if (buf.cmp("/UL") ||
+              buf.cmp("/OL") ||
+              buf.cmp("/DL"))
+          {
+            xx       = margins.pop();
+            block->h += fsize + 2;
+          }
+          else if (buf.cmp("/TABLE"))
+          {
+            block->h += fsize + 2;
+            xx       = margins.current();
           }
-          else if (buf.cmp("I") ||
-                   buf.cmp("EM"))
-            pushfont(font |= FL_ITALIC, fsize);
-          else if (buf.cmp("CODE") ||
-                   buf.cmp("TT"))
-            pushfont(font = FL_COURIER, fsize);
-          else if (buf.cmp("KBD"))
-            pushfont(font = FL_COURIER_BOLD, fsize);
-          else if (buf.cmp("VAR"))
-            pushfont(font = FL_COURIER_ITALIC, fsize);
-          else if (buf.cmp("/HEAD"))
-            head = 0;
-          else if (buf.cmp("/H1") ||
-                   buf.cmp("/H2") ||
-                   buf.cmp("/H3") ||
-                   buf.cmp("/H4") ||
-                   buf.cmp("/H5") ||
-                   buf.cmp("/H6") ||
-                   buf.cmp("/B") ||
-                   buf.cmp("/STRONG") ||
-                   buf.cmp("/I") ||
-                   buf.cmp("/EM") ||
-                   buf.cmp("/CODE") ||
-                   buf.cmp("/TT") ||
-                   buf.cmp("/KBD") ||
-                   buf.cmp("/VAR"))
-            popfont(font, fsize, fcolor);
           else if (buf.cmp("/PRE"))
           {
-            popfont(font, fsize, fcolor);
             pre = 0;
+            hh  = 0;
           }
-          else if (buf.cmp("IMG"))
-          {
-            Fl_Shared_Image *img = 0;
-            int         width, height;
-            char        wattr[8], hattr[8];
+          else if (buf.cmp("/CENTER"))
+            talign = Align::LEFT;
 
+          popfont(font, fsize, fcolor);
 
-            get_attr(attrs, "WIDTH", wattr, sizeof(wattr));
-            get_attr(attrs, "HEIGHT", hattr, sizeof(hattr));
-            width  = get_length(wattr);
-            height = get_length(hattr);
+          //#if defined(__GNUC__)
+          //#warning FIXME this isspace & 255 test will probably not work on a utf8 stream... And we use it everywhere!
+          //#endif /*__GNUC__*/
+          while (isspace((*ptr)&255))
+            ptr ++;
 
-            if (get_attr(attrs, "SRC", attr, sizeof(attr))) {
-              img = get_image(attr, width, height);
-              if (!width) width = img->w();
-              if (!height) height = img->h();
-            }
+          block->h += hh;
+          yy       += hh;
 
-            if (!width || !height) {
-              if (get_attr(attrs, "ALT", attr, sizeof(attr)) == nullptr) {
-                strcpy(attr, "IMG");
-              }
-            }
+          if (tolower(buf[2]) == 'l')
+            yy += fsize + 2;
 
-            ww = width;
+          if (row)
+            block = add_block(ptr, xx, yy, block->w, 0);
+          else
+            block = add_block(ptr, xx, yy, hsize_, 0);
 
-            if (needspace && xx > block->x)
-              xx += (int)fl_width(' ');
+          needspace = 0;
+          hh        = 0;
+          line      = 0;
+          newalign  = talign;
+        }
+        else if (buf.cmp("TR"))
+        {
+          block->end = start;
+          line       = do_align(block, line, xx, newalign, links);
+          xx         = block->x;
+          block->h   += hh;
 
-            if ((xx + ww) > block->w)
-            {
-              if (line < 31)
-                line ++;
+          if (row)
+          {
+            yy = blocks_[row].y + blocks_[row].h;
 
-              xx = block->line[line];
-              yy += hh;
-              hh = 0;
-            }
+            for (cell = &blocks_[row + 1]; cell <= block; cell ++)
+              if ((cell->y + cell->h) > yy)
+                yy = cell->y + cell->h;
 
-            if (img) {
-              img->draw(xx + x() - leftline_,
-                        yy + y() - fl_height() + fl_descent() + 2);
-            }
+            block = &blocks_[row];
 
-            xx += ww;
-            if ((height + 2) > hh)
-              hh = height + 2;
+            block->h = yy - block->y + 2;
 
-            needspace = 0;
+            for (i = 0; i < column; i ++)
+              if (cells[i])
+              {
+                cell = &blocks_[cells[i]];
+                cell->h = block->h;
+              }
           }
-          buf.clear();
-        }
-        else if (*ptr == '\n' && pre)
-        {
-          hv_draw(buf.c_str(), xx + x() - leftline_, yy + y());
-          buf.clear();
 
-          if (line < 31)
-            line ++;
-          xx = block->line[line];
-          yy += hh;
-          hh = fsize + 2;
-          needspace = 0;
+          memset(cells, 0, sizeof(cells));
 
-          ptr ++;
-          current_pos_ = (int) (ptr-value_);
-        }
-        else if (isspace((*ptr)&255))
-        {
-          if (pre)
-          {
-            if (*ptr == ' ')
-              buf += ' ';
-            else
-            {
-              // Do tabs every 8 columns...
-              buf += ' '; // at least one space
-              while (buf.size() & 7)
-                buf += ' ';
-            }
-          }
+          yy        = block->y + block->h - 4;
+          hh        = 0;
+          block     = add_block(start, xx, yy, hsize_, 0);
+          row       = (int) (block - &blocks_[0]);
+          needspace = 0;
+          column    = 0;
+          line      = 0;
 
-          ptr ++;
-          if (!pre) current_pos_ = (int) (ptr-value_);
-          needspace = 1;
+          rc = get_color(get_attr(attrs, "BGCOLOR", attr, sizeof(attr)), tc);
         }
-        else if (*ptr == '&') // process html entity
+        else if (buf.cmp("/TR") && row)
         {
-          ptr ++;
-
-          int qch = quote_char(ptr);
+          line       = do_align(block, line, xx, newalign, links);
+          block->end = start;
+          block->h   += hh;
+          talign     = Align::LEFT;
 
-          if (qch < 0)
-            buf += '&';
-          else {
-            size_t utf8l = buf.size();
-            buf.add(qch);
-            utf8l = buf.size() - utf8l; // length of added UTF-8 text
-            const char *oldptr = ptr;
-            ptr = strchr(ptr, ';') + 1;
-            entity_extra_length += int(ptr - (oldptr-1)) - utf8l; // extra length between html entity and UTF-8
-          }
+          xx = blocks_[row].x;
+          yy = blocks_[row].y + blocks_[row].h;
 
-          if ((fsize + 2) > hh)
-            hh = fsize + 2;
-        }
-        else
-        {
-          buf += *ptr++;
+          for (cell = &blocks_[row + 1]; cell <= block; cell ++)
+            if ((cell->y + cell->h) > yy)
+              yy = cell->y + cell->h;
 
-          if ((fsize + 2) > hh)
-            hh = fsize + 2;
-        }
-      }
+          block = &blocks_[row];
 
-      if (buf.size() > 0 && !pre && !head)
-      {
-        ww = buf.width();
+          block->h = yy - block->y + 2;
 
-        if (needspace && xx > block->x)
-          xx += (int)fl_width(' ');
+          for (i = 0; i < column; i ++)
+            if (cells[i])
+            {
+              cell = &blocks_[cells[i]];
+              cell->h = block->h;
+            }
 
-        if ((xx + ww) > block->w)
-        {
-          if (line < 31)
-            line ++;
-          xx = block->line[line];
-          yy += hh;
-          hh = 0;
+          yy        = block->y + block->h /*- 4*/;
+          block     = add_block(start, xx, yy, hsize_, 0);
+          needspace = 0;
+          row       = 0;
+          line      = 0;
         }
-      }
-
-      if (buf.size() > 0 && !head)
-      {
-        hv_draw(buf.c_str(), xx + x() - leftline_, yy + y());
-        if (underline) fl_xyline(xx + x() - leftline_, yy + y() + 1,
-                                 xx + x() - leftline_ + ww);
-        current_pos_ = (int) (ptr-value_);
-      }
-    }
+        else if ((buf.cmp("TD") ||
+                  buf.cmp("TH")) && row)
+        {
+          int   colspan;                // COLSPAN attribute
 
-  fl_pop_clip();
-} // draw()
 
+          line       = do_align(block, line, xx, newalign, links);
+          block->end = start;
+          block->h   += hh;
 
-/**
-  \brief Skips over HTML tags in a text.
+          if (buf.cmp("TH"))
+            font = textfont_ | FL_BOLD;
+          else
+            font = textfont_;
 
-  In an html style text, set the character pointer p, skipping anything from a
-  leading '<' up to and including the closing '>'. If the end of the buffer is
-  reached, the function returns `end`.
+          fsize = textsize_;
 
-  No need to handle UTF-8 here.
+          xx = blocks_[row].x + fsize + 3 + table_offset;
+          for (i = 0; i < column; i ++)
+            xx += columns[i] + 6;
 
-  \param[in] p pointer to html text, UTF-8 characters possible
-  \param[in] end pointer to the end of the text (need nut be NUL)
-  \return new pointer to text after skipping over '<...>' blocks, or `end`
-    if NUL was found or a '<...>' block was not closed.
-*/
-static const char *vanilla(const char *p, const char *end) {
-  if (*p == '\0' || p >= end) return end;
-  for (;;) {
-    if (*p != '<') {
-      return p;
-    } else {
-      while (*p && p < end && *p != '>') p++;
-    }
-    p++;
-    if (*p == '\0' || p >= end) return end;
-  }
-}
+          margins.push(xx - margins.current());
 
+          if (get_attr(attrs, "COLSPAN", attr, sizeof(attr)) != nullptr)
+            colspan = atoi(attr);
+          else
+            colspan = 1;
 
-/**
-  \brief Finds the specified string \p s at starting position \p p.
+          for (i = 0, ww = -6; i < colspan; i ++)
+            ww += columns[column + i] + 6;
 
-  The argument \p p and the return value are offsets in Fl_Help_View::value(),
-  counting from 0. If \p p is out of range, 0 is used.
+          if (block->end == block->start && blocks_.size() > 1)
+          {
+            blocks_.pop_back();
+            block --;
+          }
 
-  The string comparison is simple but honors some special cases:
-  - the specified string \p s must be in UTF-8 encoding
-  - HTML tags in value() are filtered (not compared as such, they never match)
-  - HTML entities like '\<' or '\&x#20ac;' are converted to Unicode (UTF-8)
-  - ASCII characters (7-bit, \< 0x80) are compared case insensitive
-  - every newline (LF, '\\n') in value() is treated like a single space
-  - all other strings are compared as-is (byte by byte)
+          pushfont(font, fsize);
 
-  \param[in] s search string in UTF-8 encoding
-  \param[in] p starting position for search (0,...), Default = 0
-  \return the matching position or -1 if not found
-*/
-int Fl_Help_View::find(const char *s, int p)
-{
-  int           i,                              // Looping var
-                c;                              // Current character
-  Text_Block *b;                             // Current block
-  const char    *bp,                            // Block matching pointer
-                *bs,                            // Start of current comparison
-                *sp;                            // Search string pointer
+          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' ? Align::CENTER : Align::LEFT);
+          talign    = newalign;
 
-  DEBUG_FUNCTION(__LINE__,__FUNCTION__);
+          cells[column] = (int) (block - &blocks_[0]);
 
-  // Range check input and value...
-  if (!s || !value_) return -1;
+          column += colspan;
 
-  if (p < 0 || p >= (int)strlen(value_)) p = 0;
+          block->bgcolor = get_color(get_attr(attrs, "BGCOLOR", attr,
+                                              sizeof(attr)), rc);
+        }
+        else if ((buf.cmp("/TD") ||
+                  buf.cmp("/TH")) && row)
+        {
+          line = do_align(block, line, xx, newalign, links);
+          popfont(font, fsize, fcolor);
+          xx = margins.pop();
+          talign = Align::LEFT;
+        }
+        else if (buf.cmp("FONT"))
+        {
+          if (get_attr(attrs, "FACE", attr, sizeof(attr)) != nullptr) {
+            if (!strncasecmp(attr, "helvetica", 9) ||
+                !strncasecmp(attr, "arial", 5) ||
+                !strncasecmp(attr, "sans", 4)) font = FL_HELVETICA;
+            else if (!strncasecmp(attr, "times", 5) ||
+                     !strncasecmp(attr, "serif", 5)) font = FL_TIMES;
+            else if (!strncasecmp(attr, "symbol", 6)) font = FL_SYMBOL;
+            else font = FL_COURIER;
+          }
 
-  // Look for the string...
-  for (i = (int)blocks_.size(), b = &blocks_[0]; i > 0; i--, b++) {
-    if (b->end < (value_ + p))
-      continue;
+          if (get_attr(attrs, "SIZE", attr, sizeof(attr)) != nullptr) {
+            if (isdigit(attr[0] & 255)) {
+              // Absolute size
+              fsize = (int)(textsize_ * pow(1.2, atoi(attr) - 3.0));
+            } else {
+              // Relative size
+              fsize = (int)(fsize * pow(1.2, atoi(attr)));
+            }
+          }
 
-    if (b->start < (value_ + p))
-      bp = value_ + p;
-    else
-      bp = b->start;
+          pushfont(font, fsize);
+        }
+        else if (buf.cmp("/FONT"))
+          popfont(font, fsize, fcolor);
+        else if (buf.cmp("B") ||
+                 buf.cmp("STRONG"))
+          pushfont(font |= FL_BOLD, fsize);
+        else if (buf.cmp("I") ||
+                 buf.cmp("EM"))
+          pushfont(font |= FL_ITALIC, fsize);
+        else if (buf.cmp("CODE") ||
+                 buf.cmp("TT"))
+          pushfont(font = FL_COURIER, fsize);
+        else if (buf.cmp("KBD"))
+          pushfont(font = FL_COURIER_BOLD, fsize);
+        else if (buf.cmp("VAR"))
+          pushfont(font = FL_COURIER_ITALIC, fsize);
+        else if (buf.cmp("/B") ||
+                 buf.cmp("/STRONG") ||
+                 buf.cmp("/I") ||
+                 buf.cmp("/EM") ||
+                 buf.cmp("/CODE") ||
+                 buf.cmp("/TT") ||
+                 buf.cmp("/KBD") ||
+                 buf.cmp("/VAR"))
+          popfont(font, fsize, fcolor);
+        else if (buf.cmp("IMG"))
+        {
+          Fl_Shared_Image       *img = 0;
+          int           width;
+          int           height;
 
-    bp = vanilla(bp, b->end);
-    if (bp == b->end)
-      continue;
 
-    for (sp = s, bs = bp; *sp && *bp && bp < b->end; ) {
-      bool is_html_entity = false;
-      if (*bp == '&') {
-        // decode HTML entity...
-        if ((c = quote_char(bp + 1)) < 0) {
-          c = '&';
-        } else {
-          const char *entity_end = strchr(bp + 1, ';');
-          if (entity_end) {
-            is_html_entity = true; // c contains the unicode character
-            bp = entity_end;
-          } else {
-            c = '&';
-          }
-        }
-      } else {
-        c = *bp;
-      }
-
-      if (c == '\n') c = ' '; // treat newline as a single space
-
-      // *FIXME* *UTF-8* (A.S. 02/14/2016)
-      // At this point c may be an arbitrary Unicode Code Point corresponding
-      // to a quoted character (see above), i.e. it _can_ be a multi byte
-      // UTF-8 sequence and must be compared with the corresponding
-      // multi byte string in (*sp)...
-      // For instance: "€" == 0x20ac -> 0xe2 0x82 0xac (UTF-8: 3 bytes).
-      // Hint: use fl_utf8encode() [see below]
-
-      int utf_len = 1;
-      if (c > 0x20 && c < 0x80 && tolower(*sp) == tolower(c)) {
-        // Check for ASCII case insensitive match.
-        //printf("%ld text match %c/%c\n", bp-value_, *sp, c);
-        sp++;
-        bp = vanilla(bp+1, b->end);
-      } else if (is_html_entity && fl_utf8decode(sp, nullptr, &utf_len) == (unsigned int)c ) {
-        // Check if a < entity ini html matches a UTF-8 character in the
-        // search string.
-        //printf("%ld unicode match 0x%02X 0x%02X\n", bp-value_, *sp, c);
-        sp += utf_len;
-        bp = vanilla(bp+1, b->end);
-      } else if (*sp == c) {
-        // Check if UTF-8 bytes in html and the search string match.
-        //printf("%ld binary match %c/%c\n", bp-value_, *sp, c);
-        sp++;
-        bp = vanilla(bp+1, b->end);
-      } else {
-        // No match, so reset to start of search... .
-        //printf("reset search (%c/%c)\n", *sp, c);
-        sp = s;
-        bp = bs = vanilla(bs+1, b->end);
-      }
-    }
-
-    if (!*sp) { // Found a match!
-      topline(b->y - b->h);
-      return int(bs - value_);
-    }
-  }
-
-  // No match!
-  return (-1);
-}
-
-/**
-  \brief Formats the help text.
-*/
-void Fl_Help_View::format() {
-  int           i;              // Looping var
-  int           done;           // Are we done yet?
-  Text_Block *block,         // Current block
-                *cell;          // Current table cell
-  int           cells[MAX_COLUMNS],
-                                // Cells in the current row...
-                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
-  HV_Edit_Buffer buf;           // Text buffer
-  char          attr[1024],     // Attribute buffer
-                wattr[1024],    // Width attribute buffer
-                hattr[1024],    // Height attribute buffer
-                linkdest[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
-  Fl_Font       font;
-  Fl_Fontsize   fsize;          // Current font and size
-  Fl_Color      fcolor;         // Current font color
-  unsigned char border;         // Draw border?
-  Align talign;         // Current alignment
-  Align newalign;       // New alignment
-  int           head,           // In the  section?
-                pre,            // 
 text?
-                needspace;      // Do we need whitespace?
-  int           table_width,    // Width of table
-                table_offset;   // Offset of table
-  int           column,         // Current table column number
-                columns[MAX_COLUMNS];
-                                // Column widths
-  Fl_Color      tc, rc;         // Table/row background color
-  Fl_Boxtype    b = box() ? box() : FL_DOWN_BOX;
-                                // Box to draw...
-  Margin_Stack  margins;        // Left margin stack...
-  std::vector OL_num;         // if nonnegative, in OL mode and this is the item number
-
-  OL_num.push_back(-1);
-
-  DEBUG_FUNCTION(__LINE__,__FUNCTION__);
-
-  // Reset document width...
-  int scrollsize = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size();
-  hsize_ = w() - scrollsize - Fl::box_dw(b);
-
-  done = 0;
-  while (!done)
-  {
-    // Reset state variables...
-    done       = 1;
-    blocks_.clear();
-    link_list_.clear();
-    target_line_map_.clear();
-    size_      = 0;
-    bgcolor_   = color();
-    textcolor_ = textcolor();
-    linkcolor_ = fl_contrast(FL_BLUE, color());
-
-    tc = rc = bgcolor_;
-
-    title_ = "Untitled";
-
-    if (!value_)
-      return;
-
-    // Setup for formatting...
-    initfont(font, fsize, fcolor);
+          get_attr(attrs, "WIDTH", wattr, sizeof(wattr));
+          get_attr(attrs, "HEIGHT", hattr, sizeof(hattr));
+          width  = get_length(wattr);
+          height = get_length(hattr);
 
-    line         = 0;
-    links        = 0;
-    margins.clear();
-    xx           = 4;
-    yy           = fsize + 2;
-    ww           = 0;
-    column       = 0;
-    border       = 0;
-    hh           = 0;
-    block        = add_block(value_, xx, yy, hsize_, 0);
-    row          = 0;
-    head         = 0;
-    pre          = 0;
-    talign       = Align::LEFT;
-    newalign     = Align::LEFT;
-    needspace    = 0;
-    linkdest[0]  = '\0';
-    table_offset = 0;
+          if (get_attr(attrs, "SRC", attr, sizeof(attr))) {
+            img    = get_image(attr, width, height);
+            width  = img->w();
+            height = img->h();
+          }
 
-    // Html text character loop
-    for (ptr = value_, buf.clear(); *ptr;)
-    {
-      // End of word?
-      if ((*ptr == '<' || isspace((*ptr)&255)) && buf.size() > 0)
-      {
-        // Get width of word parsed so far...
-        ww = buf.width();
+          ww = width;
 
-        if (!head && !pre)
-        {
-          // Check width...
           if (ww > hsize_) {
             hsize_ = ww;
             done   = 0;
@@ -1328,9 +1280,6 @@ void Fl_Help_View::format() {
           if (needspace && xx > block->x)
             ww += (int)fl_width(' ');
 
-  //        printf("line = %d, xx = %d, ww = %d, block->x = %d, block->w = %d\n",
-  //           line, xx, ww, block->x, block->w);
-
           if ((xx + ww) > block->w)
           {
             line     = do_align(block, line, xx, newalign, links);
@@ -1341,1826 +1290,1680 @@ void Fl_Help_View::format() {
           }
 
           if (linkdest[0])
-            add_link(linkdest, xx, yy - fsize, ww, fsize);
-
-          xx += ww;
-          if ((fsize + 2) > hh)
-            hh = fsize + 2;
-
-          needspace = 0;
-        }
-        else if (pre)
-        {
-          // Add a link as needed...
-          if (linkdest[0])
-            add_link(linkdest, xx, yy - hh, ww, hh);
+            add_link(linkdest, xx, yy-fsize, ww, height);
 
           xx += ww;
-          if ((fsize + 2) > hh)
-            hh = fsize + 2;
-
-          // Handle preformatted text...
-          while (isspace((*ptr)&255))
-          {
-            if (*ptr == '\n')
-            {
-              if (xx > hsize_) break;
-
-              line     = do_align(block, line, xx, newalign, links);
-              xx       = block->x;
-              yy       += hh;
-              block->h += hh;
-              hh       = fsize + 2;
-            }
-            else
-              xx += (int)fl_width(' ');
-
-            if ((fsize + 2) > hh)
-              hh = fsize + 2;
-
-            ptr ++;
-          }
-
-          if (xx > hsize_) {
-            hsize_ = xx;
-            done   = 0;
-            break;
-          }
+          if ((height + 2) > hh)
+            hh = height + 2;
 
           needspace = 0;
         }
-        else
-        {
-          // Handle normal text or stuff in the  section...
-          while (isspace((*ptr)&255))
-            ptr ++;
-        }
-
         buf.clear();
       }
-
-      if (*ptr == '<')
+      else if (*ptr == '\n' && pre)
       {
-        // Handle html tags..
-        start = ptr;
-        ptr ++;
+        if (linkdest[0])
+          add_link(linkdest, xx, yy - hh, ww, hh);
 
-        if (strncmp(ptr, "!--", 3) == 0)
-        {
-          // Comment...
-          ptr += 3;
-          if ((ptr = strstr(ptr, "-->")) != nullptr)
-          {
-            ptr += 3;
-            continue;
-          }
-          else
-            break;
+        if (xx > hsize_) {
+          hsize_ = xx;
+          done   = 0;
+          break;
         }
 
-        while (*ptr && *ptr != '>' && !isspace((*ptr)&255))
-          buf += *ptr++;
-
-        attrs = ptr;
-        while (*ptr && *ptr != '>')
-          ptr ++;
-
-        if (*ptr == '>')
-          ptr ++;
-
-        if (buf.cmp("HEAD"))
-          head = 1;
-        else if (buf.cmp("/HEAD"))
-          head = 0;
-        else if (buf.cmp("TITLE"))
-        {
-          // Copy the title in the document...
-          title_.clear();
-          for ( ; *ptr != '<' && *ptr; ptr++) {
-            title_.push_back(*ptr);
-          }
-          buf.clear();
+        line      = do_align(block, line, xx, newalign, links);
+        xx        = block->x;
+        yy        += hh;
+        block->h  += hh;
+        needspace = 0;
+        ptr ++;
+      }
+      else if (isspace((*ptr)&255))
+      {
+        needspace = 1;
+        if ( pre ) {
+          xx += (int)fl_width(' ');
         }
-        else if (buf.cmp("A"))
-        {
-          if (get_attr(attrs, "NAME", attr, sizeof(attr)) != nullptr)
-            add_target(attr, yy - fsize - 2);
+        ptr ++;
+      }
+      else if (*ptr == '&')
+      {
+        // Handle html '&' codes, eg. "&"
+        ptr ++;
 
-          if (get_attr(attrs, "HREF", attr, sizeof(attr)) != nullptr)
-            strlcpy(linkdest, attr, sizeof(linkdest));
-        }
-        else if (buf.cmp("/A"))
-          linkdest[0] = '\0';
-        else if (buf.cmp("BODY"))
-        {
-          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)),
-                                 fl_contrast(FL_BLUE, color()));
-        }
-        else if (buf.cmp("BR"))
-        {
-          line     = do_align(block, line, xx, newalign, links);
-          xx       = block->x;
-          block->h += hh;
-          yy       += hh;
-          hh       = 0;
-        }
-        else if (buf.cmp("CENTER") ||
-                 buf.cmp("P") ||
-                 buf.cmp("H1") ||
-                 buf.cmp("H2") ||
-                 buf.cmp("H3") ||
-                 buf.cmp("H4") ||
-                 buf.cmp("H5") ||
-                 buf.cmp("H6") ||
-                 buf.cmp("UL") ||
-                 buf.cmp("OL") ||
-                 buf.cmp("DL") ||
-                 buf.cmp("LI") ||
-                 buf.cmp("DD") ||
-                 buf.cmp("DT") ||
-                 buf.cmp("HR") ||
-                 buf.cmp("PRE") ||
-                 buf.cmp("TABLE"))
-        {
-          block->end = start;
-          line       = do_align(block, line, xx, newalign, links);
-          newalign   = buf.cmp("CENTER") ? Align::CENTER : Align::LEFT;
-          xx         = block->x;
-          block->h   += hh;
+        int qch = quote_char(ptr);
 
-          if (buf.cmp("OL")) {
-            int ol_num = 1;
-            if (get_attr(attrs, "START", attr, sizeof(attr)) != nullptr) {
-              errno = 0;
-              char *endptr = 0;
-              ol_num = (int)strtol(attr, &endptr, 10);
-              if (errno || endptr == attr || ol_num < 0)
-                ol_num = 1;
-            }
-            OL_num.push_back(ol_num);
-          }
-          else if (buf.cmp("UL"))
-            OL_num.push_back(-1);
+        if (qch < 0)
+          buf += '&';
+        else {
+          buf.add(qch);
+          ptr = strchr(ptr, ';') + 1;
+        }
 
-          if (buf.cmp("UL") ||
-              buf.cmp("OL") ||
-              buf.cmp("DL"))
-          {
-            block->h += fsize + 2;
-            xx       = margins.push(4 * fsize);
-          }
-          else if (buf.cmp("TABLE"))
-          {
-            if (get_attr(attrs, "BORDER", attr, sizeof(attr)))
-              border = (uchar)atoi(attr);
-            else
-              border = 0;
+        if ((fsize + 2) > hh)
+          hh = fsize + 2;
+      }
+      else
+      {
+        buf += *ptr++;
 
-            tc = rc = get_color(get_attr(attrs, "BGCOLOR", attr, sizeof(attr)), bgcolor_);
+        if ((fsize + 2) > hh)
+          hh = fsize + 2;
+      }
+    }
 
-            block->h += fsize + 2;
+    if (buf.size() > 0 && !head)
+    {
+      ww = buf.width();
 
-            format_table(&table_width, columns, start);
+  //    printf("line = %d, xx = %d, ww = %d, block->x = %d, block->w = %d\n",
+  //       line, xx, ww, block->x, block->w);
 
-            if ((xx + table_width) > hsize_) {
-#ifdef DEBUG
-              printf("xx=%d, table_width=%d, hsize_=%d\n", xx, table_width,
-                     hsize_);
-#endif // DEBUG
-              hsize_ = xx + table_width;
-              done   = 0;
-              break;
-            }
+      if (ww > hsize_) {
+        hsize_ = ww;
+        done   = 0;
+        break;
+      }
 
-            switch (get_align(attrs, talign))
-            {
-              default :
-                  table_offset = 0;
-                  break;
+      if (needspace && xx > block->x)
+        ww += (int)fl_width(' ');
 
-              case Align::CENTER :
-                  table_offset = (hsize_ - table_width) / 2 - textsize_;
-                  break;
+      if ((xx + ww) > block->w)
+      {
+        line     = do_align(block, line, xx, newalign, links);
+        xx       = block->x;
+        yy       += hh;
+        block->h += hh;
+        hh       = 0;
+      }
 
-              case Align::RIGHT :
-                  table_offset = hsize_ - table_width - textsize_;
-                  break;
-            }
+      if (linkdest[0])
+        add_link(linkdest, xx, yy - fsize, ww, fsize);
 
-            column = 0;
-          }
+      xx += ww;
+    }
 
-          if (tolower(buf[0]) == 'h' && isdigit(buf[1]))
-          {
-            font  = FL_HELVETICA_BOLD;
-            fsize = textsize_ + '7' - buf[1];
-          }
-          else if (buf.cmp("DT"))
-          {
-            font  = textfont_ | FL_ITALIC;
-            fsize = textsize_;
-          }
-          else if (buf.cmp("PRE"))
-          {
-            font  = FL_COURIER;
-            fsize = textsize_;
-            pre   = 1;
-          }
-          else
-          {
-            font  = textfont_;
-            fsize = textsize_;
-          }
+    do_align(block, line, xx, newalign, links);
 
-          pushfont(font, fsize);
+    block->end = ptr;
+    size_      = yy + hh;
+  }
 
-          yy = block->y + block->h;
-          hh = 0;
+//  printf("margins.depth_=%d\n", margins.depth_);
 
-          if ((tolower(buf[0]) == 'h' && isdigit(buf[1])) ||
-              buf.cmp("DD") ||
-              buf.cmp("DT") ||
-              buf.cmp("P"))
-            yy += fsize + 2;
-          else if (buf.cmp("HR"))
-          {
-            hh += 2 * fsize;
-            yy += fsize;
-          }
+  int dx = Fl::box_dw(b) - Fl::box_dx(b);
+  int dy = Fl::box_dh(b) - Fl::box_dy(b);
+  int ss = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size();
+  int dw = Fl::box_dw(b) + ss;
+  int dh = Fl::box_dh(b);
 
-          if (row)
-            block = add_block(start, xx, yy, block->w, 0);
-          else
-            block = add_block(start, xx, yy, hsize_, 0);
+  if (hsize_ > (w() - dw)) {
+    hscrollbar_.show();
 
-          if (buf.cmp("LI")) {
-            block->ol = 0;
-            if (OL_num.size() && (OL_num.back() >= 0)) {
-              block->ol = 1;
-              block->ol_num = OL_num.back();
-              OL_num.back()++;
-            }
-          }
+    dh += ss;
 
-          needspace = 0;
-          line      = 0;
+    if (size_ < (h() - dh)) {
+      scrollbar_.hide();
+      hscrollbar_.resize(x() + Fl::box_dx(b), y() + h() - ss - dy,
+                         w() - Fl::box_dw(b), ss);
+    } else {
+      scrollbar_.show();
+      scrollbar_.resize(x() + w() - ss - dx, y() + Fl::box_dy(b),
+                        ss, h() - ss - Fl::box_dh(b));
+      hscrollbar_.resize(x() + Fl::box_dx(b), y() + h() - ss - dy,
+                         w() - ss - Fl::box_dw(b), ss);
+    }
+  } else {
+    hscrollbar_.hide();
 
-          if (buf.cmp("CENTER"))
-            newalign = talign = Align::CENTER;
-          else
-            newalign = get_align(attrs, talign);
-        }
-        else if (buf.cmp("/CENTER") ||
-                 buf.cmp("/P") ||
-                 buf.cmp("/H1") ||
-                 buf.cmp("/H2") ||
-                 buf.cmp("/H3") ||
-                 buf.cmp("/H4") ||
-                 buf.cmp("/H5") ||
-                 buf.cmp("/H6") ||
-                 buf.cmp("/PRE") ||
-                 buf.cmp("/UL") ||
-                 buf.cmp("/OL") ||
-                 buf.cmp("/DL") ||
-                 buf.cmp("/TABLE"))
-        {
-          line       = do_align(block, line, xx, newalign, links);
-          xx         = block->x;
-          block->end = ptr;
-
-          if (buf.cmp("/OL") ||
-              buf.cmp("/UL")) {
-            if (OL_num.size()) OL_num.pop_back();
-          }
-
-          if (buf.cmp("/UL") ||
-              buf.cmp("/OL") ||
-              buf.cmp("/DL"))
-          {
-            xx       = margins.pop();
-            block->h += fsize + 2;
-          }
-          else if (buf.cmp("/TABLE"))
-          {
-            block->h += fsize + 2;
-            xx       = margins.current();
-          }
-          else if (buf.cmp("/PRE"))
-          {
-            pre = 0;
-            hh  = 0;
-          }
-          else if (buf.cmp("/CENTER"))
-            talign = Align::LEFT;
-
-          popfont(font, fsize, fcolor);
-
-          //#if defined(__GNUC__)
-          //#warning FIXME this isspace & 255 test will probably not work on a utf8 stream... And we use it everywhere!
-          //#endif /*__GNUC__*/
-          while (isspace((*ptr)&255))
-            ptr ++;
+    if (size_ < (h() - dh)) scrollbar_.hide();
+    else {
+      scrollbar_.resize(x() + w() - ss - dx, y() + Fl::box_dy(b),
+                        ss, h() - Fl::box_dh(b));
+      scrollbar_.show();
+    }
+  }
 
-          block->h += hh;
-          yy       += hh;
+  // Reset scrolling if it needs to be...
+  if (scrollbar_.visible()) {
+    int temph = h() - Fl::box_dh(b);
+    if (hscrollbar_.visible()) temph -= ss;
+    if ((topline_ + temph) > size_) topline(size_ - temph);
+    else topline(topline_);
+  } else topline(0);
 
-          if (tolower(buf[2]) == 'l')
-            yy += fsize + 2;
+  if (hscrollbar_.visible()) {
+    int tempw = w() - ss - Fl::box_dw(b);
+    if ((leftline_ + tempw) > hsize_) leftline(hsize_ - tempw);
+    else leftline(leftline_);
+  } else leftline(0);
+}
 
-          if (row)
-            block = add_block(ptr, xx, yy, block->w, 0);
-          else
-            block = add_block(ptr, xx, yy, hsize_, 0);
 
-          needspace = 0;
-          hh        = 0;
-          line      = 0;
-          newalign  = talign;
-        }
-        else if (buf.cmp("TR"))
-        {
-          block->end = start;
-          line       = do_align(block, line, xx, newalign, links);
-          xx         = block->x;
-          block->h   += hh;
+/**
+  \brief Format a table
+  \param[out] table_width Total width of the table
+  \param[out] columns Array of column widths
+  \param[in] table Pointer to the start of the table in the HTML text
+  */
+void Fl_Help_View::format_table(
+  int *table_width,
+  int *columns,
+  const char *table)
+{
+  int           column,                                 // Current column
+                num_columns,                            // Number of columns
+                colspan,                                // COLSPAN attribute
+                width,                                  // Current width
+                temp_width,                             // Temporary width
+                max_width,                              // Maximum width
+                incell,                                 // In a table cell?
+                pre,                                    // 
 text?
+                needspace;                              // Need whitespace?
+  HV_Edit_Buffer buf;                                   // Text buffer
+  char          attr[1024],                             // Other attribute
+                wattr[1024],                            // WIDTH attribute
+                hattr[1024];                            // HEIGHT attribute
+  const char    *ptr,                                   // Pointer into table
+                *attrs,                                 // Pointer to attributes
+                *start;                                 // Start of element
+  int           minwidths[MAX_COLUMNS];                 // Minimum widths for each column
+  Fl_Font       font;
+  Fl_Fontsize   fsize;                                  // Current font and size
+  Fl_Color      fcolor;                                 // Currrent font color
 
-          if (row)
-          {
-            yy = blocks_[row].y + blocks_[row].h;
+  DEBUG_FUNCTION(__LINE__,__FUNCTION__);
 
-            for (cell = &blocks_[row + 1]; cell <= block; cell ++)
-              if ((cell->y + cell->h) > yy)
-                yy = cell->y + cell->h;
+  // Clear widths...
+  *table_width = 0;
+  for (column = 0; column < MAX_COLUMNS; column ++)
+  {
+    columns[column]   = 0;
+    minwidths[column] = 0;
+  }
 
-            block = &blocks_[row];
+  num_columns = 0;
+  colspan     = 0;
+  max_width   = 0;
+  pre         = 0;
+  needspace   = 0;
+  fstack_.top(font, fsize, fcolor);
 
-            block->h = yy - block->y + 2;
+  // Scan the table...
+  for (ptr = table, column = -1, width = 0, incell = 0; *ptr;)
+  {
+    if ((*ptr == '<' || isspace((*ptr)&255)) && buf.size() > 0 && incell)
+    {
+      // Check width...
+      if (needspace)
+      {
+        buf += ' ';
+        needspace = 0;
+      }
 
-            for (i = 0; i < column; i ++)
-              if (cells[i])
-              {
-                cell = &blocks_[cells[i]];
-                cell->h = block->h;
-              }
-          }
+      temp_width = buf.width();
+      buf.clear();
 
-          memset(cells, 0, sizeof(cells));
+      if (temp_width > minwidths[column])
+        minwidths[column] = temp_width;
 
-          yy        = block->y + block->h - 4;
-          hh        = 0;
-          block     = add_block(start, xx, yy, hsize_, 0);
-          row       = (int) (block - &blocks_[0]);
-          needspace = 0;
-          column    = 0;
-          line      = 0;
+      width += temp_width;
 
-          rc = get_color(get_attr(attrs, "BGCOLOR", attr, sizeof(attr)), tc);
-        }
-        else if (buf.cmp("/TR") && row)
-        {
-          line       = do_align(block, line, xx, newalign, links);
-          block->end = start;
-          block->h   += hh;
-          talign     = Align::LEFT;
+      if (width > max_width)
+        max_width = width;
+    }
 
-          xx = blocks_[row].x;
-          yy = blocks_[row].y + blocks_[row].h;
+    if (*ptr == '<')
+    {
+      start = ptr;
 
-          for (cell = &blocks_[row + 1]; cell <= block; cell ++)
-            if ((cell->y + cell->h) > yy)
-              yy = cell->y + cell->h;
+      for (buf.clear(), ptr ++; *ptr && *ptr != '>' && !isspace((*ptr)&255);)
+        buf += *ptr++;
 
-          block = &blocks_[row];
+      attrs = ptr;
+      while (*ptr && *ptr != '>')
+        ptr ++;
 
-          block->h = yy - block->y + 2;
+      if (*ptr == '>')
+        ptr ++;
 
-          for (i = 0; i < column; i ++)
-            if (cells[i])
-            {
-              cell = &blocks_[cells[i]];
-              cell->h = block->h;
-            }
+      if (buf.cmp("BR") ||
+          buf.cmp("HR"))
+      {
+        width     = 0;
+        needspace = 0;
+      }
+      else if (buf.cmp("TABLE") && start > table)
+        break;
+      else if (buf.cmp("CENTER") ||
+               buf.cmp("P") ||
+               buf.cmp("H1") ||
+               buf.cmp("H2") ||
+               buf.cmp("H3") ||
+               buf.cmp("H4") ||
+               buf.cmp("H5") ||
+               buf.cmp("H6") ||
+               buf.cmp("UL") ||
+               buf.cmp("OL") ||
+               buf.cmp("DL") ||
+               buf.cmp("LI") ||
+               buf.cmp("DD") ||
+               buf.cmp("DT") ||
+               buf.cmp("PRE"))
+      {
+        width     = 0;
+        needspace = 0;
 
-          yy        = block->y + block->h /*- 4*/;
-          block     = add_block(start, xx, yy, hsize_, 0);
-          needspace = 0;
-          row       = 0;
-          line      = 0;
+        if (tolower(buf[0]) == 'h' && isdigit(buf[1]))
+        {
+          font  = FL_HELVETICA_BOLD;
+          fsize = textsize_ + '7' - buf[1];
         }
-        else if ((buf.cmp("TD") ||
-                  buf.cmp("TH")) && row)
+        else if (buf.cmp("DT"))
         {
-          int   colspan;                // COLSPAN attribute
-
-
-          line       = do_align(block, line, xx, newalign, links);
-          block->end = start;
-          block->h   += hh;
-
-          if (buf.cmp("TH"))
-            font = textfont_ | FL_BOLD;
-          else
-            font = textfont_;
-
+          font  = textfont_ | FL_ITALIC;
           fsize = textsize_;
-
-          xx = blocks_[row].x + fsize + 3 + table_offset;
-          for (i = 0; i < column; i ++)
-            xx += columns[i] + 6;
-
-          margins.push(xx - margins.current());
-
-          if (get_attr(attrs, "COLSPAN", attr, sizeof(attr)) != nullptr)
-            colspan = atoi(attr);
-          else
-            colspan = 1;
-
-          for (i = 0, ww = -6; i < colspan; i ++)
-            ww += columns[column + i] + 6;
-
-          if (block->end == block->start && blocks_.size() > 1)
-          {
-            blocks_.pop_back();
-            block --;
-          }
-
-          pushfont(font, fsize);
-
-          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' ? Align::CENTER : Align::LEFT);
-          talign    = newalign;
-
-          cells[column] = (int) (block - &blocks_[0]);
-
-          column += colspan;
-
-          block->bgcolor = get_color(get_attr(attrs, "BGCOLOR", attr,
-                                              sizeof(attr)), rc);
         }
-        else if ((buf.cmp("/TD") ||
-                  buf.cmp("/TH")) && row)
+        else if (buf.cmp("PRE"))
         {
-          line = do_align(block, line, xx, newalign, links);
-          popfont(font, fsize, fcolor);
-          xx = margins.pop();
-          talign = Align::LEFT;
+          font  = FL_COURIER;
+          fsize = textsize_;
+          pre   = 1;
         }
-        else if (buf.cmp("FONT"))
+        else if (buf.cmp("LI"))
         {
-          if (get_attr(attrs, "FACE", attr, sizeof(attr)) != nullptr) {
-            if (!strncasecmp(attr, "helvetica", 9) ||
-                !strncasecmp(attr, "arial", 5) ||
-                !strncasecmp(attr, "sans", 4)) font = FL_HELVETICA;
-            else if (!strncasecmp(attr, "times", 5) ||
-                     !strncasecmp(attr, "serif", 5)) font = FL_TIMES;
-            else if (!strncasecmp(attr, "symbol", 6)) font = FL_SYMBOL;
-            else font = FL_COURIER;
-          }
-
-          if (get_attr(attrs, "SIZE", attr, sizeof(attr)) != nullptr) {
-            if (isdigit(attr[0] & 255)) {
-              // Absolute size
-              fsize = (int)(textsize_ * pow(1.2, atoi(attr) - 3.0));
-            } else {
-              // Relative size
-              fsize = (int)(fsize * pow(1.2, atoi(attr)));
-            }
-          }
-
-          pushfont(font, fsize);
+          width  += 4 * fsize;
+          font   = textfont_;
+          fsize  = textsize_;
         }
-        else if (buf.cmp("/FONT"))
-          popfont(font, fsize, fcolor);
-        else if (buf.cmp("B") ||
-                 buf.cmp("STRONG"))
-          pushfont(font |= FL_BOLD, fsize);
-        else if (buf.cmp("I") ||
-                 buf.cmp("EM"))
-          pushfont(font |= FL_ITALIC, fsize);
-        else if (buf.cmp("CODE") ||
-                 buf.cmp("TT"))
-          pushfont(font = FL_COURIER, fsize);
-        else if (buf.cmp("KBD"))
-          pushfont(font = FL_COURIER_BOLD, fsize);
-        else if (buf.cmp("VAR"))
-          pushfont(font = FL_COURIER_ITALIC, fsize);
-        else if (buf.cmp("/B") ||
-                 buf.cmp("/STRONG") ||
-                 buf.cmp("/I") ||
-                 buf.cmp("/EM") ||
-                 buf.cmp("/CODE") ||
-                 buf.cmp("/TT") ||
-                 buf.cmp("/KBD") ||
-                 buf.cmp("/VAR"))
-          popfont(font, fsize, fcolor);
-        else if (buf.cmp("IMG"))
+        else
         {
-          Fl_Shared_Image       *img = 0;
-          int           width;
-          int           height;
-
-
-          get_attr(attrs, "WIDTH", wattr, sizeof(wattr));
-          get_attr(attrs, "HEIGHT", hattr, sizeof(hattr));
-          width  = get_length(wattr);
-          height = get_length(hattr);
-
-          if (get_attr(attrs, "SRC", attr, sizeof(attr))) {
-            img    = get_image(attr, width, height);
-            width  = img->w();
-            height = img->h();
-          }
+          font  = textfont_;
+          fsize = textsize_;
+        }
 
-          ww = width;
+        pushfont(font, fsize);
+      }
+      else if (buf.cmp("/CENTER") ||
+               buf.cmp("/P") ||
+               buf.cmp("/H1") ||
+               buf.cmp("/H2") ||
+               buf.cmp("/H3") ||
+               buf.cmp("/H4") ||
+               buf.cmp("/H5") ||
+               buf.cmp("/H6") ||
+               buf.cmp("/PRE") ||
+               buf.cmp("/UL") ||
+               buf.cmp("/OL") ||
+               buf.cmp("/DL"))
+      {
+        width     = 0;
+        needspace = 0;
 
-          if (ww > hsize_) {
-            hsize_ = ww;
-            done   = 0;
-            break;
-          }
+        popfont(font, fsize, fcolor);
+      }
+      else if (buf.cmp("TR") || buf.cmp("/TR") ||
+               buf.cmp("/TABLE"))
+      {
+//        printf("%s column = %d, colspan = %d, num_columns = %d\n",
+//             buf.c_str(), column, colspan, num_columns);
 
-          if (needspace && xx > block->x)
-            ww += (int)fl_width(' ');
+        if (column >= 0)
+        {
+          // This is a hack to support COLSPAN...
+          max_width /= colspan;
 
-          if ((xx + ww) > block->w)
+          while (colspan > 0)
           {
-            line     = do_align(block, line, xx, newalign, links);
-            xx       = block->x;
-            yy       += hh;
-            block->h += hh;
-            hh       = 0;
-          }
-
-          if (linkdest[0])
-            add_link(linkdest, xx, yy-fsize, ww, height);
-
-          xx += ww;
-          if ((height + 2) > hh)
-            hh = height + 2;
+            if (max_width > columns[column])
+              columns[column] = max_width;
 
-          needspace = 0;
+            column ++;
+            colspan --;
+          }
         }
-        buf.clear();
-      }
-      else if (*ptr == '\n' && pre)
-      {
-        if (linkdest[0])
-          add_link(linkdest, xx, yy - hh, ww, hh);
 
-        if (xx > hsize_) {
-          hsize_ = xx;
-          done   = 0;
+        if (buf.cmp("/TABLE"))
           break;
-        }
 
-        line      = do_align(block, line, xx, newalign, links);
-        xx        = block->x;
-        yy        += hh;
-        block->h  += hh;
         needspace = 0;
-        ptr ++;
-      }
-      else if (isspace((*ptr)&255))
-      {
-        needspace = 1;
-        if ( pre ) {
-          xx += (int)fl_width(' ');
-        }
-        ptr ++;
+        column    = -1;
+        width     = 0;
+        max_width = 0;
+        incell    = 0;
       }
-      else if (*ptr == '&')
+      else if (buf.cmp("TD") ||
+               buf.cmp("TH"))
       {
-        // Handle html '&' codes, eg. "&"
-        ptr ++;
+//        printf("BEFORE column = %d, colspan = %d, num_columns = %d\n",
+//             column, colspan, num_columns);
 
-        int qch = quote_char(ptr);
+        if (column >= 0)
+        {
+          // This is a hack to support COLSPAN...
+          max_width /= colspan;
 
-        if (qch < 0)
-          buf += '&';
-        else {
-          buf.add(qch);
-          ptr = strchr(ptr, ';') + 1;
+          while (colspan > 0)
+          {
+            if (max_width > columns[column])
+              columns[column] = max_width;
+
+            column ++;
+            colspan --;
+          }
         }
+        else
+          column ++;
 
-        if ((fsize + 2) > hh)
-          hh = fsize + 2;
-      }
-      else
-      {
-        buf += *ptr++;
+        if (get_attr(attrs, "COLSPAN", attr, sizeof(attr)) != nullptr)
+          colspan = atoi(attr);
+        else
+          colspan = 1;
 
-        if ((fsize + 2) > hh)
-          hh = fsize + 2;
-      }
-    }
+//        printf("AFTER column = %d, colspan = %d, num_columns = %d\n",
+//             column, colspan, num_columns);
 
-    if (buf.size() > 0 && !head)
-    {
-      ww = buf.width();
+        if ((column + colspan) >= num_columns)
+          num_columns = column + colspan;
 
-  //    printf("line = %d, xx = %d, ww = %d, block->x = %d, block->w = %d\n",
-  //       line, xx, ww, block->x, block->w);
+        needspace = 0;
+        width     = 0;
+        incell    = 1;
 
-      if (ww > hsize_) {
-        hsize_ = ww;
-        done   = 0;
-        break;
-      }
+        if (buf.cmp("TH"))
+          font = textfont_ | FL_BOLD;
+        else
+          font = textfont_;
 
-      if (needspace && xx > block->x)
-        ww += (int)fl_width(' ');
+        fsize = textsize_;
 
-      if ((xx + ww) > block->w)
+        pushfont(font, fsize);
+
+        if (get_attr(attrs, "WIDTH", attr, sizeof(attr)) != nullptr)
+          max_width = get_length(attr);
+        else
+          max_width = 0;
+
+//        printf("max_width = %d\n", max_width);
+      }
+      else if (buf.cmp("/TD") ||
+               buf.cmp("/TH"))
       {
-        line     = do_align(block, line, xx, newalign, links);
-        xx       = block->x;
-        yy       += hh;
-        block->h += hh;
-        hh       = 0;
+        incell = 0;
+        popfont(font, fsize, fcolor);
       }
+      else if (buf.cmp("B") ||
+               buf.cmp("STRONG"))
+        pushfont(font |= FL_BOLD, fsize);
+      else if (buf.cmp("I") ||
+               buf.cmp("EM"))
+        pushfont(font |= FL_ITALIC, fsize);
+      else if (buf.cmp("CODE") ||
+               buf.cmp("TT"))
+        pushfont(font = FL_COURIER, fsize);
+      else if (buf.cmp("KBD"))
+        pushfont(font = FL_COURIER_BOLD, fsize);
+      else if (buf.cmp("VAR"))
+        pushfont(font = FL_COURIER_ITALIC, fsize);
+      else if (buf.cmp("/B") ||
+               buf.cmp("/STRONG") ||
+               buf.cmp("/I") ||
+               buf.cmp("/EM") ||
+               buf.cmp("/CODE") ||
+               buf.cmp("/TT") ||
+               buf.cmp("/KBD") ||
+               buf.cmp("/VAR"))
+        popfont(font, fsize, fcolor);
+      else if (buf.cmp("IMG") && incell)
+      {
+        Fl_Shared_Image *img = 0;
+        int             iwidth, iheight;
 
-      if (linkdest[0])
-        add_link(linkdest, xx, yy - fsize, ww, fsize);
-
-      xx += ww;
-    }
 
-    do_align(block, line, xx, newalign, links);
+        get_attr(attrs, "WIDTH", wattr, sizeof(wattr));
+        get_attr(attrs, "HEIGHT", hattr, sizeof(hattr));
+        iwidth  = get_length(wattr);
+        iheight = get_length(hattr);
 
-    block->end = ptr;
-    size_      = yy + hh;
-  }
+        if (get_attr(attrs, "SRC", attr, sizeof(attr))) {
+          img     = get_image(attr, iwidth, iheight);
+          iwidth  = img->w();
+          iheight = img->h();
+        }
 
-//  printf("margins.depth_=%d\n", margins.depth_);
+        if (iwidth > minwidths[column])
+          minwidths[column] = iwidth;
 
-  int dx = Fl::box_dw(b) - Fl::box_dx(b);
-  int dy = Fl::box_dh(b) - Fl::box_dy(b);
-  int ss = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size();
-  int dw = Fl::box_dw(b) + ss;
-  int dh = Fl::box_dh(b);
+        width += iwidth;
+        if (needspace)
+          width += (int)fl_width(' ');
 
-  if (hsize_ > (w() - dw)) {
-    hscrollbar_.show();
+        if (width > max_width)
+          max_width = width;
 
-    dh += ss;
+        needspace = 0;
+      }
+      buf.clear();
+    }
+    else if (*ptr == '\n' && pre)
+    {
+      width     = 0;
+      needspace = 0;
+      ptr ++;
+    }
+    else if (isspace((*ptr)&255))
+    {
+      needspace = 1;
 
-    if (size_ < (h() - dh)) {
-      scrollbar_.hide();
-      hscrollbar_.resize(x() + Fl::box_dx(b), y() + h() - ss - dy,
-                         w() - Fl::box_dw(b), ss);
-    } else {
-      scrollbar_.show();
-      scrollbar_.resize(x() + w() - ss - dx, y() + Fl::box_dy(b),
-                        ss, h() - ss - Fl::box_dh(b));
-      hscrollbar_.resize(x() + Fl::box_dx(b), y() + h() - ss - dy,
-                         w() - ss - Fl::box_dw(b), ss);
+      ptr ++;
     }
-  } else {
-    hscrollbar_.hide();
+    else if (*ptr == '&' )
+    {
+      ptr ++;
 
-    if (size_ < (h() - dh)) scrollbar_.hide();
-    else {
-      scrollbar_.resize(x() + w() - ss - dx, y() + Fl::box_dy(b),
-                        ss, h() - Fl::box_dh(b));
-      scrollbar_.show();
+      int qch = quote_char(ptr);
+
+      if (qch < 0)
+        buf += '&';
+      else {
+        buf.add(qch);
+        ptr = strchr(ptr, ';') + 1;
+      }
+    }
+    else
+    {
+      buf += *ptr++;
     }
   }
 
-  // Reset scrolling if it needs to be...
-  if (scrollbar_.visible()) {
-    int temph = h() - Fl::box_dh(b);
-    if (hscrollbar_.visible()) temph -= ss;
-    if ((topline_ + temph) > size_) topline(size_ - temph);
-    else topline(topline_);
-  } else topline(0);
+  // Now that we have scanned the entire table, adjust the table and
+  // cell widths to fit on the screen...
+  if (get_attr(table + 6, "WIDTH", attr, sizeof(attr)))
+    *table_width = get_length(attr);
+  else
+    *table_width = 0;
 
-  if (hscrollbar_.visible()) {
-    int tempw = w() - ss - Fl::box_dw(b);
-    if ((leftline_ + tempw) > hsize_) leftline(hsize_ - tempw);
-    else leftline(leftline_);
-  } else leftline(0);
-}
+#ifdef DEBUG
+  printf("num_columns = %d, table_width = %d\n", num_columns, *table_width);
+#endif // DEBUG
 
+  if (num_columns == 0)
+    return;
 
-/**
-  \brief Format a table
-  \param[out] table_width Total width of the table
-  \param[out] columns Array of column widths
-  \param[in] table Pointer to the start of the table in the HTML text
-  */
-void Fl_Help_View::format_table(
-  int *table_width,
-  int *columns,
-  const char *table)
-{
-  int           column,                                 // Current column
-                num_columns,                            // Number of columns
-                colspan,                                // COLSPAN attribute
-                width,                                  // Current width
-                temp_width,                             // Temporary width
-                max_width,                              // Maximum width
-                incell,                                 // In a table cell?
-                pre,                                    // 
 text?
-                needspace;                              // Need whitespace?
-  HV_Edit_Buffer buf;                                   // Text buffer
-  char          attr[1024],                             // Other attribute
-                wattr[1024],                            // WIDTH attribute
-                hattr[1024];                            // HEIGHT attribute
-  const char    *ptr,                                   // Pointer into table
-                *attrs,                                 // Pointer to attributes
-                *start;                                 // Start of element
-  int           minwidths[MAX_COLUMNS];                 // Minimum widths for each column
-  Fl_Font       font;
-  Fl_Fontsize   fsize;                                  // Current font and size
-  Fl_Color      fcolor;                                 // Currrent font color
+  // Add up the widths...
+  for (column = 0, width = 0; column < num_columns; column ++)
+    width += columns[column];
 
-  DEBUG_FUNCTION(__LINE__,__FUNCTION__);
+#ifdef DEBUG
+  printf("width = %d, w() = %d\n", width, w());
+  for (column = 0; column < num_columns; column ++)
+    printf("    columns[%d] = %d, minwidths[%d] = %d\n", column, columns[column],
+           column, minwidths[column]);
+#endif // DEBUG
 
-  // Clear widths...
-  *table_width = 0;
-  for (column = 0; column < MAX_COLUMNS; column ++)
-  {
-    columns[column]   = 0;
-    minwidths[column] = 0;
+  // Adjust the width if needed...
+  int scale_width = *table_width;
+
+  int scrollsize = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size();
+  if (scale_width == 0) {
+    if (width > (hsize_ - scrollsize)) scale_width = hsize_ - scrollsize;
+    else scale_width = width;
   }
 
-  num_columns = 0;
-  colspan     = 0;
-  max_width   = 0;
-  pre         = 0;
-  needspace   = 0;
-  fstack_.top(font, fsize, fcolor);
+  if (width < scale_width) {
+#ifdef DEBUG
+    printf("Scaling table up to %d from %d...\n", scale_width, width);
+#endif // DEBUG
 
-  // Scan the table...
-  for (ptr = table, column = -1, width = 0, incell = 0; *ptr;)
-  {
-    if ((*ptr == '<' || isspace((*ptr)&255)) && buf.size() > 0 && incell)
-    {
-      // Check width...
-      if (needspace)
-      {
-        buf += ' ';
-        needspace = 0;
-      }
+    *table_width = 0;
 
-      temp_width = buf.width();
-      buf.clear();
+    scale_width = (scale_width - width) / num_columns;
 
-      if (temp_width > minwidths[column])
-        minwidths[column] = temp_width;
+#ifdef DEBUG
+    printf("adjusted scale_width = %d\n", scale_width);
+#endif // DEBUG
 
-      width += temp_width;
+    for (column = 0; column < num_columns; column ++) {
+      columns[column] += scale_width;
 
-      if (width > max_width)
-        max_width = width;
+      (*table_width) += columns[column];
     }
+  }
+  else if (width > scale_width) {
+#ifdef DEBUG
+    printf("Scaling table down to %d from %d...\n", scale_width, width);
+#endif // DEBUG
 
-    if (*ptr == '<')
-    {
-      start = ptr;
-
-      for (buf.clear(), ptr ++; *ptr && *ptr != '>' && !isspace((*ptr)&255);)
-        buf += *ptr++;
-
-      attrs = ptr;
-      while (*ptr && *ptr != '>')
-        ptr ++;
+    for (column = 0; column < num_columns; column ++) {
+      width       -= minwidths[column];
+      scale_width -= minwidths[column];
+    }
 
-      if (*ptr == '>')
-        ptr ++;
+#ifdef DEBUG
+    printf("adjusted width = %d, scale_width = %d\n", width, scale_width);
+#endif // DEBUG
 
-      if (buf.cmp("BR") ||
-          buf.cmp("HR"))
-      {
-        width     = 0;
-        needspace = 0;
+    if (width > 0) {
+      for (column = 0; column < num_columns; column ++) {
+        columns[column] -= minwidths[column];
+        columns[column] = scale_width * columns[column] / width;
+        columns[column] += minwidths[column];
       }
-      else if (buf.cmp("TABLE") && start > table)
-        break;
-      else if (buf.cmp("CENTER") ||
-               buf.cmp("P") ||
-               buf.cmp("H1") ||
-               buf.cmp("H2") ||
-               buf.cmp("H3") ||
-               buf.cmp("H4") ||
-               buf.cmp("H5") ||
-               buf.cmp("H6") ||
-               buf.cmp("UL") ||
-               buf.cmp("OL") ||
-               buf.cmp("DL") ||
-               buf.cmp("LI") ||
-               buf.cmp("DD") ||
-               buf.cmp("DT") ||
-               buf.cmp("PRE"))
-      {
-        width     = 0;
-        needspace = 0;
-
-        if (tolower(buf[0]) == 'h' && isdigit(buf[1]))
-        {
-          font  = FL_HELVETICA_BOLD;
-          fsize = textsize_ + '7' - buf[1];
-        }
-        else if (buf.cmp("DT"))
-        {
-          font  = textfont_ | FL_ITALIC;
-          fsize = textsize_;
-        }
-        else if (buf.cmp("PRE"))
-        {
-          font  = FL_COURIER;
-          fsize = textsize_;
-          pre   = 1;
-        }
-        else if (buf.cmp("LI"))
-        {
-          width  += 4 * fsize;
-          font   = textfont_;
-          fsize  = textsize_;
-        }
-        else
-        {
-          font  = textfont_;
-          fsize = textsize_;
-        }
+    }
 
-        pushfont(font, fsize);
-      }
-      else if (buf.cmp("/CENTER") ||
-               buf.cmp("/P") ||
-               buf.cmp("/H1") ||
-               buf.cmp("/H2") ||
-               buf.cmp("/H3") ||
-               buf.cmp("/H4") ||
-               buf.cmp("/H5") ||
-               buf.cmp("/H6") ||
-               buf.cmp("/PRE") ||
-               buf.cmp("/UL") ||
-               buf.cmp("/OL") ||
-               buf.cmp("/DL"))
-      {
-        width     = 0;
-        needspace = 0;
+    *table_width = 0;
+    for (column = 0; column < num_columns; column ++) {
+      (*table_width) += columns[column];
+    }
+  }
+  else if (*table_width == 0)
+    *table_width = width;
 
-        popfont(font, fsize, fcolor);
-      }
-      else if (buf.cmp("TR") || buf.cmp("/TR") ||
-               buf.cmp("/TABLE"))
-      {
-//        printf("%s column = %d, colspan = %d, num_columns = %d\n",
-//             buf.c_str(), column, colspan, num_columns);
+#ifdef DEBUG
+  printf("FINAL table_width = %d\n", *table_width);
+  for (column = 0; column < num_columns; column ++)
+    printf("    columns[%d] = %d\n", column, columns[column]);
+#endif // DEBUG
+}
 
-        if (column >= 0)
-        {
-          // This is a hack to support COLSPAN...
-          max_width /= colspan;
 
-          while (colspan > 0)
-          {
-            if (max_width > columns[column])
-              columns[column] = max_width;
+/**
+  \brief Gets an alignment attribute.
+  \param[in] p Pointer to start of attributes.
+  \param[in] a Default alignment.
+  \return Alignment value, either CENTER, RIGHT, or LEFT.
+*/
+Fl_Help_View::Align Fl_Help_View::get_align(const char *p, Align a)
+{
+  char  buf[255];                       // Alignment value
 
-            column ++;
-            colspan --;
-          }
-        }
+  if (get_attr(p, "ALIGN", buf, sizeof(buf)) == nullptr)
+    return (a);
 
-        if (buf.cmp("/TABLE"))
-          break;
+  if (strcasecmp(buf, "CENTER") == 0)
+    return Align::CENTER;
+  else if (strcasecmp(buf, "RIGHT") == 0)
+    return Align::RIGHT;
+  else
+    return Align::LEFT;
+}
 
-        needspace = 0;
-        column    = -1;
-        width     = 0;
-        max_width = 0;
-        incell    = 0;
-      }
-      else if (buf.cmp("TD") ||
-               buf.cmp("TH"))
-      {
-//        printf("BEFORE column = %d, colspan = %d, num_columns = %d\n",
-//             column, colspan, num_columns);
 
-        if (column >= 0)
-        {
-          // This is a hack to support COLSPAN...
-          max_width /= colspan;
+/**
+  \brief Gets an attribute value from the string.
+  \param[in] p Pointer to start of attributes.
+  \param[in] n Name of attribute.
+  \param[out] buf Buffer for attribute value.
+  \param[in] bufsize Size of buffer.
+  \return Pointer to buf or nullptr if not found.
+  */
+const char *Fl_Help_View::get_attr(
+  const char *p,
+  const char *n,
+  char *buf,
+  int bufsize)
+{
+  char  name[255],                              // Name from string
+        *ptr,                                   // Pointer into name or value
+        quote;                                  // Quote
 
-          while (colspan > 0)
-          {
-            if (max_width > columns[column])
-              columns[column] = max_width;
 
-            column ++;
-            colspan --;
-          }
-        }
-        else
-          column ++;
+  buf[0] = '\0';
 
-        if (get_attr(attrs, "COLSPAN", attr, sizeof(attr)) != nullptr)
-          colspan = atoi(attr);
-        else
-          colspan = 1;
+  while (*p && *p != '>')
+  {
+    while (isspace((*p)&255))
+      p ++;
 
-//        printf("AFTER column = %d, colspan = %d, num_columns = %d\n",
-//             column, colspan, num_columns);
+    if (*p == '>' || !*p)
+      return (nullptr);
 
-        if ((column + colspan) >= num_columns)
-          num_columns = column + colspan;
+    for (ptr = name; *p && !isspace((*p)&255) && *p != '=' && *p != '>';)
+      if (ptr < (name + sizeof(name) - 1))
+        *ptr++ = *p++;
+      else
+        p ++;
 
-        needspace = 0;
-        width     = 0;
-        incell    = 1;
+    *ptr = '\0';
 
-        if (buf.cmp("TH"))
-          font = textfont_ | FL_BOLD;
-        else
-          font = textfont_;
+    if (isspace((*p)&255) || !*p || *p == '>')
+      buf[0] = '\0';
+    else
+    {
+      if (*p == '=')
+        p ++;
 
-        fsize = textsize_;
+      for (ptr = buf; *p && !isspace((*p)&255) && *p != '>';)
+        if (*p == '\'' || *p == '\"')
+        {
+          quote = *p++;
 
-        pushfont(font, fsize);
+          while (*p && *p != quote)
+            if ((ptr - buf + 1) < bufsize)
+              *ptr++ = *p++;
+            else
+              p ++;
 
-        if (get_attr(attrs, "WIDTH", attr, sizeof(attr)) != nullptr)
-          max_width = get_length(attr);
+          if (*p == quote)
+            p ++;
+        }
+        else if ((ptr - buf + 1) < bufsize)
+          *ptr++ = *p++;
         else
-          max_width = 0;
-
-//        printf("max_width = %d\n", max_width);
-      }
-      else if (buf.cmp("/TD") ||
-               buf.cmp("/TH"))
-      {
-        incell = 0;
-        popfont(font, fsize, fcolor);
-      }
-      else if (buf.cmp("B") ||
-               buf.cmp("STRONG"))
-        pushfont(font |= FL_BOLD, fsize);
-      else if (buf.cmp("I") ||
-               buf.cmp("EM"))
-        pushfont(font |= FL_ITALIC, fsize);
-      else if (buf.cmp("CODE") ||
-               buf.cmp("TT"))
-        pushfont(font = FL_COURIER, fsize);
-      else if (buf.cmp("KBD"))
-        pushfont(font = FL_COURIER_BOLD, fsize);
-      else if (buf.cmp("VAR"))
-        pushfont(font = FL_COURIER_ITALIC, fsize);
-      else if (buf.cmp("/B") ||
-               buf.cmp("/STRONG") ||
-               buf.cmp("/I") ||
-               buf.cmp("/EM") ||
-               buf.cmp("/CODE") ||
-               buf.cmp("/TT") ||
-               buf.cmp("/KBD") ||
-               buf.cmp("/VAR"))
-        popfont(font, fsize, fcolor);
-      else if (buf.cmp("IMG") && incell)
-      {
-        Fl_Shared_Image *img = 0;
-        int             iwidth, iheight;
+          p ++;
 
+      *ptr = '\0';
+    }
 
-        get_attr(attrs, "WIDTH", wattr, sizeof(wattr));
-        get_attr(attrs, "HEIGHT", hattr, sizeof(hattr));
-        iwidth  = get_length(wattr);
-        iheight = get_length(hattr);
+    if (strcasecmp(n, name) == 0)
+      return (buf);
+    else
+      buf[0] = '\0';
 
-        if (get_attr(attrs, "SRC", attr, sizeof(attr))) {
-          img     = get_image(attr, iwidth, iheight);
-          iwidth  = img->w();
-          iheight = img->h();
-        }
+    if (*p == '>')
+      return (nullptr);
+  }
 
-        if (iwidth > minwidths[column])
-          minwidths[column] = iwidth;
+  return (nullptr);
+}
 
-        width += iwidth;
-        if (needspace)
-          width += (int)fl_width(' ');
 
-        if (width > max_width)
-          max_width = width;
+/**
+  \brief Gets a color attribute.
+  \param[in] n the color name, either a name or a hex value.
+  \param[in] c the default color value.
+  \return the color value, either the color from the name or the default value.
+  */
+Fl_Color Fl_Help_View::get_color(const char *n, Fl_Color c)
+{
+  int   i;                              // Looping var
+  int   rgb, r, g, b;                   // RGB values
+  static const struct {                 // Color name table
+    const char *name;
+    int r, g, b;
+  }     colors[] = {
+    { "black",          0x00, 0x00, 0x00 },
+    { "red",            0xff, 0x00, 0x00 },
+    { "green",          0x00, 0x80, 0x00 },
+    { "yellow",         0xff, 0xff, 0x00 },
+    { "blue",           0x00, 0x00, 0xff },
+    { "magenta",        0xff, 0x00, 0xff },
+    { "fuchsia",        0xff, 0x00, 0xff },
+    { "cyan",           0x00, 0xff, 0xff },
+    { "aqua",           0x00, 0xff, 0xff },
+    { "white",          0xff, 0xff, 0xff },
+    { "gray",           0x80, 0x80, 0x80 },
+    { "grey",           0x80, 0x80, 0x80 },
+    { "lime",           0x00, 0xff, 0x00 },
+    { "maroon",         0x80, 0x00, 0x00 },
+    { "navy",           0x00, 0x00, 0x80 },
+    { "olive",          0x80, 0x80, 0x00 },
+    { "purple",         0x80, 0x00, 0x80 },
+    { "silver",         0xc0, 0xc0, 0xc0 },
+    { "teal",           0x00, 0x80, 0x80 }
+  };
 
-        needspace = 0;
-      }
-      buf.clear();
-    }
-    else if (*ptr == '\n' && pre)
-    {
-      width     = 0;
-      needspace = 0;
-      ptr ++;
-    }
-    else if (isspace((*ptr)&255))
-    {
-      needspace = 1;
 
-      ptr ++;
-    }
-    else if (*ptr == '&' )
-    {
-      ptr ++;
+  if (!n || !n[0]) return c;
 
-      int qch = quote_char(ptr);
+  if (n[0] == '#') {
+    // Do hex color lookup
+    rgb = (int)strtol(n + 1, nullptr, 16);
 
-      if (qch < 0)
-        buf += '&';
-      else {
-        buf.add(qch);
-        ptr = strchr(ptr, ';') + 1;
-      }
-    }
-    else
-    {
-      buf += *ptr++;
+    if (strlen(n) > 4) {
+      r = rgb >> 16;
+      g = (rgb >> 8) & 255;
+      b = rgb & 255;
+    } else {
+      r = (rgb >> 8) * 17;
+      g = ((rgb >> 4) & 15) * 17;
+      b = (rgb & 15) * 17;
     }
+    return (fl_rgb_color((uchar)r, (uchar)g, (uchar)b));
+  } else {
+    for (i = 0; i < (int)(sizeof(colors) / sizeof(colors[0])); i ++)
+      if (!strcasecmp(n, colors[i].name)) {
+        return fl_rgb_color(colors[i].r, colors[i].g, colors[i].b);
+      }
+    return c;
   }
+}
 
-  // Now that we have scanned the entire table, adjust the table and
-  // cell widths to fit on the screen...
-  if (get_attr(table + 6, "WIDTH", attr, sizeof(attr)))
-    *table_width = get_length(attr);
-  else
-    *table_width = 0;
-
-#ifdef DEBUG
-  printf("num_columns = %d, table_width = %d\n", num_columns, *table_width);
-#endif // DEBUG
-
-  if (num_columns == 0)
-    return;
 
-  // Add up the widths...
-  for (column = 0, width = 0; column < num_columns; column ++)
-    width += columns[column];
+/* Implementation note: (A.S. Apr 05, 2009)
 
-#ifdef DEBUG
-  printf("width = %d, w() = %d\n", width, w());
-  for (column = 0; column < num_columns; column ++)
-    printf("    columns[%d] = %d, minwidths[%d] = %d\n", column, columns[column],
-           column, minwidths[column]);
-#endif // DEBUG
+  Fl_Help_View::get_image() uses a static global flag (initial_load)
+  to determine, if it is called from the initial loading of a document
+  (load() or value()), or from resize() or draw().
 
-  // Adjust the width if needed...
-  int scale_width = *table_width;
+  A better solution would be to manage all loaded images in an own
+  structure like Fl_Help_Target (Fl_Help_Image ?) to avoid using this
+  global flag, but this would break the ABI !
 
-  int scrollsize = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size();
-  if (scale_width == 0) {
-    if (width > (hsize_ - scrollsize)) scale_width = hsize_ - scrollsize;
-    else scale_width = width;
-  }
+  This should be fixed in FLTK 1.3 !
 
-  if (width < scale_width) {
-#ifdef DEBUG
-    printf("Scaling table up to %d from %d...\n", scale_width, width);
-#endif // DEBUG
 
-    *table_width = 0;
+  If initial_load is true, then Fl_Shared_Image::get() is called to
+  load the image, and the reference count of the shared image is
+  increased by one.
 
-    scale_width = (scale_width - width) / num_columns;
+  If initial_load is false, then Fl_Shared_Image::find() is called to
+  load the image, and the image is released immediately. This avoids
+  increasing the reference count when calling get_image() from draw()
+  or resize().
 
-#ifdef DEBUG
-    printf("adjusted scale_width = %d\n", scale_width);
-#endif // DEBUG
+  Calling Fl_Shared_Image::find() instead of Fl_Shared_Image::get() avoids
+  doing unnecessary i/o for "broken images" within each resize/redraw.
 
-    for (column = 0; column < num_columns; column ++) {
-      columns[column] += scale_width;
+  Each image must be released exactly once in the destructor or before
+  a new document is loaded: see free_data().
+*/
 
-      (*table_width) += columns[column];
-    }
-  }
-  else if (width > scale_width) {
-#ifdef DEBUG
-    printf("Scaling table down to %d from %d...\n", scale_width, width);
-#endif // DEBUG
+/**
+  \brief Gets an inline image.
 
-    for (column = 0; column < num_columns; column ++) {
-      width       -= minwidths[column];
-      scale_width -= minwidths[column];
-    }
+  The image reference count is maintained accordingly, such that
+  the image can be released exactly once when the document is closed.
 
-#ifdef DEBUG
-    printf("adjusted width = %d, scale_width = %d\n", width, scale_width);
-#endif // DEBUG
+  \param[in] name the image name, either a local filename or a URL.
+  \param[in] W, H the size of the image, or 0 if not specified.
+  \return a pointer to a cached Fl_Shared_Image, if the image can be loaded,
+          otherwise a pointer to an internal Fl_Pixmap (broken_image).
 
-    if (width > 0) {
-      for (column = 0; column < num_columns; column ++) {
-        columns[column] -= minwidths[column];
-        columns[column] = scale_width * columns[column] / width;
-        columns[column] += minwidths[column];
-      }
-    }
+  \todo Fl_Help_View::get_image() returns a pointer to the internal
+  Fl_Pixmap broken_image, but this is _not_ compatible with the
+  return type Fl_Shared_Image (release() must not be called).
+*/
+Fl_Shared_Image *Fl_Help_View::get_image(const char *name, int W, int H)
+{
+  std::string url;
+  Fl_Shared_Image *ip;                  // Image pointer...
 
-    *table_width = 0;
-    for (column = 0; column < num_columns; column ++) {
-      (*table_width) += columns[column];
-    }
+  if (!name || !name[0]) {
+    // No image name given, return broken image
+    return (Fl_Shared_Image *)&broken_image;
   }
-  else if (*table_width == 0)
-    *table_width = width;
-
-#ifdef DEBUG
-  printf("FINAL table_width = %d\n", *table_width);
-  for (column = 0; column < num_columns; column ++)
-    printf("    columns[%d] = %d\n", column, columns[column]);
-#endif // DEBUG
-}
+  std::string imagename = name;
 
+  size_t directory_scheme_length = url_scheme(directory_);
+  size_t imagename_scheme_length = url_scheme(imagename);
 
-/**
-  \brief Frees memory used for the document.
-  */
-void Fl_Help_View::free_data() {
-  // Release all images...
-  if (value_) {
-    const char  *ptr,           // Pointer into block
-                *attrs;         // Pointer to start of element attributes
-    HV_Edit_Buffer buf;         // Text buffer
-    char        attr[1024],     // Attribute buffer
-                wattr[1024],    // Width attribute buffer
-                hattr[1024];    // Height attribute buffer
+  // See if the image can be found...
+  if ( (directory_scheme_length > 0) && (imagename_scheme_length == 0) ) {
+    // If directory_ starts with a scheme (e.g.ftp:), but linkp->filename_ does not:
+    if (imagename[0] == '/') {
+      // If linkp->filename_ is absolute...
+      url = directory_.substr(0, directory_scheme_length) + imagename;;
+    } else {
+      // If linkp->filename_ is relative, the URL is the directory_ plus the filename
+      url = directory_ + "/" + imagename;
+    }
+  } else if (imagename[0] != '/' && (imagename_scheme_length == 0)) {
+    // If the filename is relative and does not start with a scheme (ftp: , etc.)...
+    if (!directory_.empty()) {
+      // If we have a current directory, use that as the base for the URL
+      url = directory_ + "/" + imagename;
+    } else {
+      // If we do not have a current directory, use the application's current working directory
+      char dir[FL_PATH_MAX];       // Current directory (static size ok until we have fl_getcwd_std()
+      fl_getcwd(dir, sizeof(dir));
+      url = "file:" + std::string(dir) + "/" + imagename;
+    }
+  } else {
+    // If the filename is absolute or starts with a protocol (e.g.ftp:), use it as is
+    url = imagename;
+  }
+  if (link_) {
+    const char *n = (*link_)(this, url.c_str());
+    if (n == nullptr)
+      return 0;
+    url = n;
+  }
+  if (url.empty()) return 0;
 
-    DEBUG_FUNCTION(__LINE__,__FUNCTION__);
+  // If the URL starts with "file:", remove it
+  if (url.find("file:") == 0) {
+    url = url.substr(5);
+  }
 
-    for (ptr = value_; *ptr;)
-    {
-      if (*ptr == '<')
-      {
-        ptr ++;
+  if (initial_load) {
+    if ((ip = Fl_Shared_Image::get(url.c_str(), W, H)) == nullptr) {
+      ip = (Fl_Shared_Image *)&broken_image;
+    }
+  } else { // draw or resize
+    if ((ip = Fl_Shared_Image::find(url.c_str(), W, H)) == nullptr) {
+      ip = (Fl_Shared_Image *)&broken_image;
+    } else {
+      ip->release();
+    }
+  }
 
-        if (strncmp(ptr, "!--", 3) == 0)
-        {
-          // Comment...
-          ptr += 3;
-          if ((ptr = strstr(ptr, "-->")) != nullptr)
-          {
-            ptr += 3;
-            continue;
-          }
-          else
-            break;
-        }
+  return ip;
+}
 
-        buf.clear();
 
-        while (*ptr && *ptr != '>' && !isspace((*ptr)&255))
-          buf += *ptr++;
+/**
+  \brief Gets a length value, either absolute or %.
+  \param[in] l string containing the length value
+  \return the length in pixels, or 0 if the string is empty.
+*/
+int Fl_Help_View::get_length(const char *l) {
+  int val;
 
-        attrs = ptr;
-        while (*ptr && *ptr != '>')
-          ptr ++;
+  if (!l[0]) return 0;
 
-        if (*ptr == '>')
-          ptr ++;
+  val = atoi(l);
+  if (l[strlen(l) - 1] == '%') {
+    if (val > 100) val = 100;
+    else if (val < 0) val = 0;
 
-        if (buf.cmp("IMG"))
-        {
-          Fl_Shared_Image       *img;
-          int           width;
-          int           height;
+    int scrollsize = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size();
+    val = val * (hsize_ - scrollsize) / 100;
+  }
 
-          get_attr(attrs, "WIDTH", wattr, sizeof(wattr));
-          get_attr(attrs, "HEIGHT", hattr, sizeof(hattr));
-          width  = get_length(wattr);
-          height = get_length(hattr);
+  return val;
+}
 
-          if (get_attr(attrs, "SRC", attr, sizeof(attr))) {
-            // Get and release the image to free it from memory...
-            img = get_image(attr, width, height);
-            if ((void*)img != &broken_image) {
-              img->release();
-            }
-          }
+// ---- Text selection
+
+
+/*
+  About selecting text:
+
+  Still to do:
+  - &word; style characters mess up our count inside a word boundary
+  - we can only select words, no individual characters
+  - no dragging of the selection into another widget
+  - we are using the draw() function to measure screen postion of text
+    by rerouting the code via draw_mode_. Some drawing functions are
+    still called which is slow and requires a fake graphics context.
+    It may help to get rid of those calls if not in DRAW mode.
+
+  matt.
+ */
+
+/**
+  \brief Draws a text string in the help view.
+
+  This function draws the text string \p t at position (\p x, \p y) in the help view.
+  If the text is selected, it draws a selection rectangle around it and changes the text color.
+
+  \param[in] t Text to draw
+  \param[in] x X position to draw at
+  \param[in] y Y position to draw at
+  \param[in] entity_extra_length (unclear)
+ */
+void Fl_Help_View::hv_draw(const char *t, int x, int y, int entity_extra_length)
+{
+  if (draw_mode_ == Mode::DRAW) {
+    if (selected_ && current_pos_=selection_first_) {
+      Fl_Color c = fl_color();
+      fl_color(tmp_selection_color_);
+      int w = (int)fl_width(t);
+      if (current_pos_+(int)strlen(t)= x) && (Fl::event_x() < x+w) ) {
+      if ( (Fl::event_y() >= y-fl_height()+fl_descent()) && (Fl::event_y() <= y+fl_descent()) ) {
+        int f = (int) current_pos_;
+        int l = (int) (f+strlen(t)); // use 'quote_char' to calculate the true length of the HTML string
+        if (draw_mode_ == Mode::PUSH) {
+          selection_push_first_ = f;
+          selection_push_last_ = l;
+        } else { // Mode::DRAG
+          selection_drag_first_ = f;
+          selection_drag_last_ = l + entity_extra_length;
         }
       }
-      else
-        ptr++;
     }
-
-    free((void *)value_);
-    value_ = 0;
   }
-
-  blocks_ .clear();
-  link_list_.clear();
-  target_line_map_.clear();
 }
 
 
 /**
-  \brief Gets an alignment attribute.
-  \param[in] p Pointer to start of attributes.
-  \param[in] a Default alignment.
-  \return Alignment value, either CENTER, RIGHT, or LEFT.
+  \brief Called from `handle()>FL_PUSH`, starts new text selection process.
+
+  This method return 1 if the user clicks on selectable text. It sets
+  selection_push_first_ and selection_push_last_ to the current
+  selection start and end positions, respectively.
+
+  \return 1 if the selection was started, 0 if not.
 */
-Fl_Help_View::Align Fl_Help_View::get_align(const char *p, Align a)
+char Fl_Help_View::begin_selection()
 {
-  char  buf[255];                       // Alignment value
+  clear_selection();
+  selection_push_first_ = selection_push_last_ = 0;
+  selection_drag_first_ = selection_drag_last_ = 0;
 
-  if (get_attr(p, "ALIGN", buf, sizeof(buf)) == nullptr)
-    return (a);
+  if (!fl_help_view_buffer) fl_help_view_buffer = fl_create_offscreen(1, 1);
 
-  if (strcasecmp(buf, "CENTER") == 0)
-    return Align::CENTER;
-  else if (strcasecmp(buf, "RIGHT") == 0)
-    return Align::RIGHT;
-  else
-    return Align::LEFT;
+  draw_mode_ = Mode::PUSH;
+
+    fl_begin_offscreen(fl_help_view_buffer);
+    draw();
+    fl_end_offscreen();
+
+  draw_mode_ = Mode::DRAW;
+
+  if (selection_push_last_) return 1;
+  else return 0;
 }
 
 
 /**
-  \brief Gets an attribute value from the string.
-  \param[in] p Pointer to start of attributes.
-  \param[in] n Name of attribute.
-  \param[out] buf Buffer for attribute value.
-  \param[in] bufsize Size of buffer.
-  \return Pointer to buf or nullptr if not found.
-  */
-const char *Fl_Help_View::get_attr(
-  const char *p,
-  const char *n,
-  char *buf,
-  int bufsize)
+  \brief Called from `handle()>FL_DRAG`, extending text selection.
+  \return 1 if more than just the initial text is selected.
+*/
+char Fl_Help_View::extend_selection()
 {
-  char  name[255],                              // Name from string
-        *ptr,                                   // Pointer into name or value
-        quote;                                  // Quote
+  if (Fl::event_is_click())
+    return 0;
 
+  // Give this widget the focus during the selection process. This will
+  // deselect other text selection and make sure, we receive the Copy
+  // keyboard shortcut.
+  if (Fl::focus()!=this)
+    Fl::focus(this);
 
-  buf[0] = '\0';
+//  printf("old selection_first_=%d, selection_last_=%d\n",
+//         selection_first_, selection_last_);
 
-  while (*p && *p != '>')
-  {
-    while (isspace((*p)&255))
-      p ++;
+  int sf = selection_first_, sl = selection_last_;
 
-    if (*p == '>' || !*p)
-      return (nullptr);
+  selected_ = true;
 
-    for (ptr = name; *p && !isspace((*p)&255) && *p != '=' && *p != '>';)
-      if (ptr < (name + sizeof(name) - 1))
-        *ptr++ = *p++;
-      else
-        p ++;
+  draw_mode_ = Mode::DRAG;
 
-    *ptr = '\0';
+    fl_begin_offscreen(fl_help_view_buffer);
+    draw();
+    fl_end_offscreen();
 
-    if (isspace((*p)&255) || !*p || *p == '>')
-      buf[0] = '\0';
-    else
-    {
-      if (*p == '=')
-        p ++;
-
-      for (ptr = buf; *p && !isspace((*p)&255) && *p != '>';)
-        if (*p == '\'' || *p == '\"')
-        {
-          quote = *p++;
-
-          while (*p && *p != quote)
-            if ((ptr - buf + 1) < bufsize)
-              *ptr++ = *p++;
-            else
-              p ++;
+  draw_mode_ = Mode::DRAW;
 
-          if (*p == quote)
-            p ++;
-        }
-        else if ((ptr - buf + 1) < bufsize)
-          *ptr++ = *p++;
-        else
-          p ++;
+  if (selection_push_first_ < selection_drag_first_) {
+    selection_first_ = selection_push_first_;
+  } else {
+    selection_first_ = selection_drag_first_;
+  }
 
-      *ptr = '\0';
-    }
+  if (selection_push_last_ > selection_drag_last_) {
+    selection_last_ = selection_push_last_;
+  } else {
+    selection_last_ = selection_drag_last_;
+  }
 
-    if (strcasecmp(n, name) == 0)
-      return (buf);
-    else
-      buf[0] = '\0';
+//  printf("new selection_first_=%d, selection_last_=%d\n",
+//         selection_first_, selection_last_);
 
-    if (*p == '>')
-      return (nullptr);
+  if (sf!=selection_first_ || sl!=selection_last_) {
+//    puts("REDRAW!!!\n");
+    return 1;
+  } else {
+//    puts("");
+    return 0;
   }
+}
 
-  return (nullptr);
+
+/**
+  \brief Called from `handle()>FL_RELEASE`, ends text selection process.
+  This method clears the static selection helper member variables.
+*/
+void Fl_Help_View::end_selection()
+{
+  selection_push_first_ = 0;
+  selection_push_last_ = 0;
+  selection_drag_first_ = 0;
+  selection_drag_last_ = 0;
 }
 
 
+// ------ Fl_Help_View Protected and Public methods
+
+// ---- Widget management
+
 /**
-  \brief Gets a color attribute.
-  \param[in] n the color name, either a name or a hex value.
-  \param[in] c the default color value.
-  \return the color value, either the color from the name or the default value.
-  */
-Fl_Color Fl_Help_View::get_color(const char *n, Fl_Color c)
+  \brief Draws the Fl_Help_View widget.
+*/
+void Fl_Help_View::draw()
 {
-  int   i;                              // Looping var
-  int   rgb, r, g, b;                   // RGB values
-  static const struct {                 // Color name table
-    const char *name;
-    int r, g, b;
-  }     colors[] = {
-    { "black",          0x00, 0x00, 0x00 },
-    { "red",            0xff, 0x00, 0x00 },
-    { "green",          0x00, 0x80, 0x00 },
-    { "yellow",         0xff, 0xff, 0x00 },
-    { "blue",           0x00, 0x00, 0xff },
-    { "magenta",        0xff, 0x00, 0xff },
-    { "fuchsia",        0xff, 0x00, 0xff },
-    { "cyan",           0x00, 0xff, 0xff },
-    { "aqua",           0x00, 0xff, 0xff },
-    { "white",          0xff, 0xff, 0xff },
-    { "gray",           0x80, 0x80, 0x80 },
-    { "grey",           0x80, 0x80, 0x80 },
-    { "lime",           0x00, 0xff, 0x00 },
-    { "maroon",         0x80, 0x00, 0x00 },
-    { "navy",           0x00, 0x00, 0x80 },
-    { "olive",          0x80, 0x80, 0x00 },
-    { "purple",         0x80, 0x00, 0x80 },
-    { "silver",         0xc0, 0xc0, 0xc0 },
-    { "teal",           0x00, 0x80, 0x80 }
-  };
+  int                   i;              // Looping var
+  const Text_Block   *block;         // Pointer to current block
+  const char            *ptr,           // Pointer to text in block
+                        *attrs;         // Pointer to start of element attributes
+  HV_Edit_Buffer        buf;            // Text buffer
+  char                  attr[1024];     // Attribute buffer
+  int                   xx, yy, ww, hh; // Current positions and sizes
+  int                   line;           // Current line
+  Fl_Font               font;
+  Fl_Fontsize           fsize;          // Current font and size
+  Fl_Color              fcolor;         // current font color
+  int                   head, pre,      // Flags for text
+                        needspace;      // Do we need whitespace?
+  Fl_Boxtype            b = box() ? box() : FL_DOWN_BOX;
+                                        // Box to draw...
+  int                   underline,      // Underline text?
+                        xtra_ww;        // Extra width for underlined space between words
 
+  DEBUG_FUNCTION(__LINE__,__FUNCTION__);
 
-  if (!n || !n[0]) return c;
+  // Draw the scrollbar(s) and box first...
+  ww = w();
+  hh = h();
+  i  = 0;
 
-  if (n[0] == '#') {
-    // Do hex color lookup
-    rgb = (int)strtol(n + 1, nullptr, 16);
+  draw_box(b, x(), y(), ww, hh, bgcolor_);
 
-    if (strlen(n) > 4) {
-      r = rgb >> 16;
-      g = (rgb >> 8) & 255;
-      b = rgb & 255;
-    } else {
-      r = (rgb >> 8) * 17;
-      g = ((rgb >> 4) & 15) * 17;
-      b = (rgb & 15) * 17;
+  if ( hscrollbar_.visible() || scrollbar_.visible() ) {
+    int scrollsize = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size();
+    int hor_vis = hscrollbar_.visible();
+    int ver_vis = scrollbar_.visible();
+    // Scrollbar corner
+    int scorn_x = x() + ww - (ver_vis?scrollsize:0) - Fl::box_dw(b) + Fl::box_dx(b);
+    int scorn_y = y() + hh - (hor_vis?scrollsize:0) - Fl::box_dh(b) + Fl::box_dy(b);
+    if ( hor_vis ) {
+      if ( hscrollbar_.h() != scrollsize ) {            // scrollsize changed?
+        hscrollbar_.resize(x(), scorn_y, scorn_x - x(), scrollsize);
+        init_sizes();
+      }
+      draw_child(hscrollbar_);
+      hh -= scrollsize;
     }
-    return (fl_rgb_color((uchar)r, (uchar)g, (uchar)b));
-  } else {
-    for (i = 0; i < (int)(sizeof(colors) / sizeof(colors[0])); i ++)
-      if (!strcasecmp(n, colors[i].name)) {
-        return fl_rgb_color(colors[i].r, colors[i].g, colors[i].b);
+    if ( ver_vis ) {
+      if ( scrollbar_.w() != scrollsize ) {             // scrollsize changed?
+        scrollbar_.resize(scorn_x, y(), scrollsize, scorn_y - y());
+        init_sizes();
       }
-    return c;
+      draw_child(scrollbar_);
+      ww -= scrollsize;
+    }
+    if ( hor_vis && ver_vis ) {
+      // Both scrollbars visible? Draw little gray box in corner
+      fl_color(FL_GRAY);
+      fl_rectf(scorn_x, scorn_y, scrollsize, scrollsize);
+    }
   }
-}
-
-
-/* Implementation note: (A.S. Apr 05, 2009)
-
-  Fl_Help_View::get_image() uses a static global flag (initial_load)
-  to determine, if it is called from the initial loading of a document
-  (load() or value()), or from resize() or draw().
 
-  A better solution would be to manage all loaded images in an own
-  structure like Fl_Help_Target (Fl_Help_Image ?) to avoid using this
-  global flag, but this would break the ABI !
+  if (!value_)
+    return;
 
-  This should be fixed in FLTK 1.3 !
+  if (selected_) {
+    if (Fl::focus() == this) {
+      // If this widget has the focus, we use the selection color directly
+      tmp_selection_color_ = selection_color();
+    } else {
+      // Otherwise we blend the selection color with the background color
+      tmp_selection_color_ = fl_color_average(bgcolor_, selection_color(), 0.8f);
+    }
+    selection_text_color_ = fl_contrast(textcolor_, tmp_selection_color_);
+  }
+  current_pos_ = 0;
 
+  // Clip the drawing to the inside of the box...
+  fl_push_clip(x() + Fl::box_dx(b), y() + Fl::box_dy(b),
+               ww - Fl::box_dw(b), hh - Fl::box_dh(b));
+  fl_color(textcolor_);
 
-  If initial_load is true, then Fl_Shared_Image::get() is called to
-  load the image, and the reference count of the shared image is
-  increased by one.
+  // Draw all visible blocks...
+  for (i = 0, block = &blocks_[0]; i < (int)blocks_.size(); i ++, block ++)
+    if ((block->y + block->h) >= topline_ && block->y < (topline_ + h()))
+    {
+      line      = 0;
+      xx        = block->line[line];
+      yy        = block->y - topline_;
+      hh        = 0;
+      pre       = 0;
+      head      = 0;
+      needspace = 0;
+      underline = 0;
 
-  If initial_load is false, then Fl_Shared_Image::find() is called to
-  load the image, and the image is released immediately. This avoids
-  increasing the reference count when calling get_image() from draw()
-  or resize().
+      initfont(font, fsize, fcolor);
+      // byte length difference between html entity (encoded by &...;) and
+      // UTF-8 encoding of same character
+      int entity_extra_length = 0;
+      for (ptr = block->start, buf.clear(); ptr < block->end;)
+      {
+        if ((*ptr == '<' || isspace((*ptr)&255)) && buf.size() > 0)
+        {
+          if (!head && !pre)
+          {
+            // Check width...
+            ww = buf.width();
 
-  Calling Fl_Shared_Image::find() instead of Fl_Shared_Image::get() avoids
-  doing unnecessary i/o for "broken images" within each resize/redraw.
+            if (needspace && xx > block->x)
+              xx += (int)fl_width(' ');
 
-  Each image must be released exactly once in the destructor or before
-  a new document is loaded: see free_data().
-*/
+            if ((xx + ww) > block->w)
+            {
+              if (line < 31)
+                line ++;
+              xx = block->line[line];
+              yy += hh;
+              hh = 0;
+            }
 
-/**
-  \brief Gets an inline image.
+            hv_draw(buf.c_str(), xx + x() - leftline_, yy + y(), entity_extra_length);
+            buf.clear();
+            entity_extra_length = 0;
+            if (underline) {
+              xtra_ww = isspace((*ptr)&255)?(int)fl_width(' '):0;
+              fl_xyline(xx + x() - leftline_, yy + y() + 1,
+                        xx + x() - leftline_ + ww + xtra_ww);
+            }
+            current_pos_ = (int) (ptr-value_);
 
-  The image reference count is maintained accordingly, such that
-  the image can be released exactly once when the document is closed.
+            xx += ww;
+            if ((fsize + 2) > hh)
+              hh = fsize + 2;
 
-  \param[in] name the image name, either a local filename or a URL.
-  \param[in] W, H the size of the image, or 0 if not specified.
-  \return a pointer to a cached Fl_Shared_Image, if the image can be loaded,
-          otherwise a pointer to an internal Fl_Pixmap (broken_image).
+            needspace = 0;
+          }
+          else if (pre)
+          {
+            while (isspace((*ptr)&255))
+            {
+              if (*ptr == '\n')
+              {
+                hv_draw(buf.c_str(), xx + x() - leftline_, yy + y());
+                if (underline) fl_xyline(xx + x() - leftline_, yy + y() + 1,
+                                         xx + x() - leftline_ + buf.width());
+                buf.clear();
+                current_pos_ = (int) (ptr-value_);
+                if (line < 31)
+                  line ++;
+                xx = block->line[line];
+                yy += hh;
+                hh = fsize + 2;
+              }
+              else if (*ptr == '\t')
+              {
+                // Do tabs every 8 columns...
+                buf += ' '; // add at least one space
+                while (buf.size() & 7)
+                  buf += ' ';
+              }
+              else {
+                buf += ' ';
+              }
+              if ((fsize + 2) > hh)
+                hh = fsize + 2;
 
-  \todo Fl_Help_View::get_image() returns a pointer to the internal
-  Fl_Pixmap broken_image, but this is _not_ compatible with the
-  return type Fl_Shared_Image (release() must not be called).
-*/
-Fl_Shared_Image *Fl_Help_View::get_image(const char *name, int W, int H)
-{
-  std::string url;
-  Fl_Shared_Image *ip;                  // Image pointer...
+              ptr ++;
+            }
 
-  if (!name || !name[0]) {
-    // No image name given, return broken image
-    return (Fl_Shared_Image *)&broken_image;
-  }
-  std::string imagename = name;
+            if (buf.size() > 0)
+            {
+              hv_draw(buf.c_str(), xx + x() - leftline_, yy + y());
+              ww = buf.width();
+              buf.clear();
+              if (underline) fl_xyline(xx + x() - leftline_, yy + y() + 1,
+                                       xx + x() - leftline_ + ww);
+              xx += ww;
+              current_pos_ = (int) (ptr-value_);
+            }
 
-  size_t directory_scheme_length = url_scheme(directory_);
-  size_t imagename_scheme_length = url_scheme(imagename);
+            needspace = 0;
+          }
+          else
+          {
+            buf.clear();
 
-  // See if the image can be found...
-  if ( (directory_scheme_length > 0) && (imagename_scheme_length == 0) ) {
-    // If directory_ starts with a scheme (e.g.ftp:), but linkp->filename_ does not:
-    if (imagename[0] == '/') {
-      // If linkp->filename_ is absolute...
-      url = directory_.substr(0, directory_scheme_length) + imagename;;
-    } else {
-      // If linkp->filename_ is relative, the URL is the directory_ plus the filename
-      url = directory_ + "/" + imagename;
-    }
-  } else if (imagename[0] != '/' && (imagename_scheme_length == 0)) {
-    // If the filename is relative and does not start with a scheme (ftp: , etc.)...
-    if (!directory_.empty()) {
-      // If we have a current directory, use that as the base for the URL
-      url = directory_ + "/" + imagename;
-    } else {
-      // If we do not have a current directory, use the application's current working directory
-      char dir[FL_PATH_MAX];       // Current directory (static size ok until we have fl_getcwd_std()
-      fl_getcwd(dir, sizeof(dir));
-      url = "file:" + std::string(dir) + "/" + imagename;
-    }
-  } else {
-    // If the filename is absolute or starts with a protocol (e.g.ftp:), use it as is
-    url = imagename;
-  }
-  if (link_) {
-    const char *n = (*link_)(this, url.c_str());
-    if (n == nullptr)
-      return 0;
-    url = n;
-  }
-  if (url.empty()) return 0;
+            while (isspace((*ptr)&255))
+              ptr ++;
+            current_pos_ = (int) (ptr-value_);
+          }
+        }
 
-  // If the URL starts with "file:", remove it
-  if (url.find("file:") == 0) {
-    url = url.substr(5);
-  }
+        if (*ptr == '<')
+        {
+          ptr ++;
 
-  if (initial_load) {
-    if ((ip = Fl_Shared_Image::get(url.c_str(), W, H)) == nullptr) {
-      ip = (Fl_Shared_Image *)&broken_image;
-    }
-  } else { // draw or resize
-    if ((ip = Fl_Shared_Image::find(url.c_str(), W, H)) == nullptr) {
-      ip = (Fl_Shared_Image *)&broken_image;
-    } else {
-      ip->release();
-    }
-  }
+          if (strncmp(ptr, "!--", 3) == 0)
+          {
+            // Comment...
+            ptr += 3;
+            if ((ptr = strstr(ptr, "-->")) != nullptr)
+            {
+              ptr += 3;
+              continue;
+            }
+            else
+              break;
+          }
 
-  return ip;
-}
+          while (*ptr && *ptr != '>' && !isspace((*ptr)&255))
+            buf += *ptr++;
 
+          attrs = ptr;
+          while (*ptr && *ptr != '>')
+            ptr ++;
 
-/**
-  \brief Gets a length value, either absolute or %.
-  \param[in] l string containing the length value
-  \return the length in pixels, or 0 if the string is empty.
-*/
-int Fl_Help_View::get_length(const char *l) {
-  int val;
+          if (*ptr == '>')
+            ptr ++;
 
-  if (!l[0]) return 0;
+          // end of command reached, set the supposed start of printed eord here
+          current_pos_ = (int) (ptr-value_);
+          if (buf.cmp("HEAD"))
+            head = 1;
+          else if (buf.cmp("BR"))
+          {
+            if (line < 31)
+              line ++;
+            xx = block->line[line];
+            yy += hh;
+            hh = 0;
+          }
+          else if (buf.cmp("HR"))
+          {
+            fl_line(block->x + x(), yy + y(), block->w + x(),
+                    yy + y());
 
-  val = atoi(l);
-  if (l[strlen(l) - 1] == '%') {
-    if (val > 100) val = 100;
-    else if (val < 0) val = 0;
+            if (line < 31)
+              line ++;
+            xx = block->line[line];
+            yy += 2 * fsize;//hh;
+            hh = 0;
+          }
+          else if (buf.cmp("CENTER") ||
+                   buf.cmp("P") ||
+                   buf.cmp("H1") ||
+                   buf.cmp("H2") ||
+                   buf.cmp("H3") ||
+                   buf.cmp("H4") ||
+                   buf.cmp("H5") ||
+                   buf.cmp("H6") ||
+                   buf.cmp("UL") ||
+                   buf.cmp("OL") ||
+                   buf.cmp("DL") ||
+                   buf.cmp("LI") ||
+                   buf.cmp("DD") ||
+                   buf.cmp("DT") ||
+                   buf.cmp("PRE"))
+          {
+            if (tolower(buf[0]) == 'h')
+            {
+              font  = FL_HELVETICA_BOLD;
+              fsize = textsize_ + '7' - buf[1];
+            }
+            else if (buf.cmp("DT"))
+            {
+              font  = textfont_ | FL_ITALIC;
+              fsize = textsize_;
+            }
+            else if (buf.cmp("PRE"))
+            {
+              font  = FL_COURIER;
+              fsize = textsize_;
+              pre   = 1;
+            }
 
-    int scrollsize = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size();
-    val = val * (hsize_ - scrollsize) / 100;
-  }
+            if (buf.cmp("LI"))
+            {
+              if (block->ol) {
+                char buf[10];
+                snprintf(buf, sizeof(buf), "%d. ", block->ol_num);
+                hv_draw(buf, xx - (int)fl_width(buf) + x() - leftline_, yy + y());
+              }
+              else {
+                // draw bullet (•) Unicode: U+2022, UTF-8 (hex): e2 80 a2
+                unsigned char bullet[4] = { 0xe2, 0x80, 0xa2, 0x00 };
+                hv_draw((char *)bullet, xx - fsize + x() - leftline_, yy + y());
+              }
+            }
 
-  return val;
-}
+            pushfont(font, fsize);
+            buf.clear();
+          }
+          else if (buf.cmp("A") &&
+                   get_attr(attrs, "HREF", attr, sizeof(attr)) != nullptr)
+          {
+            fl_color(linkcolor_);
+            underline = 1;
+          }
+          else if (buf.cmp("/A"))
+          {
+            fl_color(textcolor_);
+            underline = 0;
+          }
+          else if (buf.cmp("FONT"))
+          {
+            if (get_attr(attrs, "COLOR", attr, sizeof(attr)) != nullptr) {
+              textcolor_ = get_color(attr, textcolor_);
+            }
 
+            if (get_attr(attrs, "FACE", attr, sizeof(attr)) != nullptr) {
+              if (!strncasecmp(attr, "helvetica", 9) ||
+                  !strncasecmp(attr, "arial", 5) ||
+                  !strncasecmp(attr, "sans", 4)) font = FL_HELVETICA;
+              else if (!strncasecmp(attr, "times", 5) ||
+                       !strncasecmp(attr, "serif", 5)) font = FL_TIMES;
+              else if (!strncasecmp(attr, "symbol", 6)) font = FL_SYMBOL;
+              else font = FL_COURIER;
+            }
 
-std::shared_ptr Fl_Help_View::find_link(int xx, int yy)
-{
-  for (auto &link : link_list_) {
-    if (link->box.contains(xx, yy)) {
-      return link;
-    }
-  }
-  return nullptr;
-}
+            if (get_attr(attrs, "SIZE", attr, sizeof(attr)) != nullptr) {
+              if (isdigit(attr[0] & 255)) {
+                // Absolute size
+                fsize = (int)(textsize_ * pow(1.2, atof(attr) - 3.0));
+              } else {
+                // Relative size
+                fsize = (int)(fsize * pow(1.2, atof(attr) - 3.0));
+              }
+            }
 
+            pushfont(font, fsize);
+          }
+          else if (buf.cmp("/FONT"))
+          {
+            popfont(font, fsize, textcolor_);
+          }
+          else if (buf.cmp("U"))
+            underline = 1;
+          else if (buf.cmp("/U"))
+            underline = 0;
+          else if (buf.cmp("B") ||
+                   buf.cmp("STRONG"))
+            pushfont(font |= FL_BOLD, fsize);
+          else if (buf.cmp("TD") ||
+                   buf.cmp("TH"))
+          {
+            int tx, ty, tw, th;
 
-void Fl_Help_View::follow_link(std::shared_ptr linkp)
-{
-  clear_selection();
-  set_changed();
-  std::string target = linkp->target;;     // Current target
+            if (tolower(buf[1]) == 'h')
+              pushfont(font |= FL_BOLD, fsize);
+            else
+              pushfont(font = textfont_, fsize);
 
-  if ( (linkp->filename_ != filename_) && !linkp->filename_.empty() ) {
-    // Load the new document, if the filename is different
-    std::string url;
-    size_t directory_scheme_length = url_scheme(directory_);
-    size_t filename_scheme_length = url_scheme(linkp->filename_);
-    if ( (directory_scheme_length > 0) && (filename_scheme_length == 0) ) {
-      // If directory_ starts with a scheme (e.g.ftp:), but linkp->filename_ does not:
-      if (linkp->filename_[0] == '/') {
-        // If linkp->filename_ is absolute...
-        url = directory_.substr(0, directory_scheme_length) + linkp->filename_;;
-      } else {
-        // If linkp->filename_ is relative, the URL is the directory_ plus the filename
-        url = directory_ + "/" + linkp->filename_;
-      }
-    } else if (linkp->filename_[0] != '/' && (filename_scheme_length == 0)) {
-      // If the filename is relative and does not start with a scheme (ftp: , etc.)...
-      if (!directory_.empty()) {
-        // If we have a current directory, use that as the base for the URL
-        url = directory_ + "/" + linkp->filename_;
-      } else {
-        // If we do not have a current directory, use the application's current working directory
-        char dir[FL_PATH_MAX];       // Current directory (static size ok until we have fl_getcwd_std()
-        fl_getcwd(dir, sizeof(dir));
-        url = "file:" + std::string(dir) + "/" + linkp->filename_;
-      }
-    } else {
-      // If the filename is absolute or starts with a protocol (e.g.ftp:), use it as is
-      url = linkp->filename_;
-    }
+            tx = block->x - 4 - leftline_;
+            ty = block->y - topline_ - fsize - 3;
+            tw = block->w - block->x + 7;
+            th = block->h + fsize - 5;
 
-    // If a target is specified, append it to the URL
-    if (!linkp->target.empty()) {
-      url += "#" + linkp->target;
-    }
+            if (tx < 0)
+            {
+              tw += tx;
+              tx  = 0;
+            }
+
+            if (ty < 0)
+            {
+              th += ty;
+              ty  = 0;
+            }
+
+            tx += x();
+            ty += y();
 
-    load(url.c_str());
+            if (block->bgcolor != bgcolor_)
+            {
+              fl_color(block->bgcolor);
+              fl_rectf(tx, ty, tw, th);
+              fl_color(textcolor_);
+            }
 
-  } else if (!target.empty()) {
-    // Keep the same document, scroll to the target line
-    topline(target.c_str());
-  } else {
-    // No target, no filename, just scroll to the top of the document
-    topline(0);
-  }
+            if (block->border)
+              fl_rect(tx, ty, tw, th);
+          }
+          else if (buf.cmp("I") ||
+                   buf.cmp("EM"))
+            pushfont(font |= FL_ITALIC, fsize);
+          else if (buf.cmp("CODE") ||
+                   buf.cmp("TT"))
+            pushfont(font = FL_COURIER, fsize);
+          else if (buf.cmp("KBD"))
+            pushfont(font = FL_COURIER_BOLD, fsize);
+          else if (buf.cmp("VAR"))
+            pushfont(font = FL_COURIER_ITALIC, fsize);
+          else if (buf.cmp("/HEAD"))
+            head = 0;
+          else if (buf.cmp("/H1") ||
+                   buf.cmp("/H2") ||
+                   buf.cmp("/H3") ||
+                   buf.cmp("/H4") ||
+                   buf.cmp("/H5") ||
+                   buf.cmp("/H6") ||
+                   buf.cmp("/B") ||
+                   buf.cmp("/STRONG") ||
+                   buf.cmp("/I") ||
+                   buf.cmp("/EM") ||
+                   buf.cmp("/CODE") ||
+                   buf.cmp("/TT") ||
+                   buf.cmp("/KBD") ||
+                   buf.cmp("/VAR"))
+            popfont(font, fsize, fcolor);
+          else if (buf.cmp("/PRE"))
+          {
+            popfont(font, fsize, fcolor);
+            pre = 0;
+          }
+          else if (buf.cmp("IMG"))
+          {
+            Fl_Shared_Image *img = 0;
+            int         width, height;
+            char        wattr[8], hattr[8];
 
-  // Scroll the content horizontally to the left
-  leftline(0);
-}
 
+            get_attr(attrs, "WIDTH", wattr, sizeof(wattr));
+            get_attr(attrs, "HEIGHT", hattr, sizeof(hattr));
+            width  = get_length(wattr);
+            height = get_length(hattr);
 
-/**
-  \brief Removes the current text selection.
-*/
-void Fl_Help_View::clear_selection()
-{
-  selected_ = false;
-  selection_first_ = 0;
-  selection_last_ = 0;
-  redraw();
-}
+            if (get_attr(attrs, "SRC", attr, sizeof(attr))) {
+              img = get_image(attr, width, height);
+              if (!width) width = img->w();
+              if (!height) height = img->h();
+            }
 
+            if (!width || !height) {
+              if (get_attr(attrs, "ALT", attr, sizeof(attr)) == nullptr) {
+                strcpy(attr, "IMG");
+              }
+            }
 
-/**
-  \brief Selects all the text in the view.
-*/
-void Fl_Help_View::select_all()
-{
-  clear_global_selection();
-  if (!value_) return;
-  selection_drag_last_ = selection_last_ = (int) strlen(value_);
-  selected_ = true;
-}
+            ww = width;
 
+            if (needspace && xx > block->x)
+              xx += (int)fl_width(' ');
 
-void Fl_Help_View::clear_global_selection()
-{
-  if (selected_) redraw();
-  selection_push_first_ = selection_push_last_ = 0;
-  selection_drag_first_ = selection_drag_last_ = 0;
-  selection_first_ = selection_last_ = 0;
-  selected_ = false;
-}
+            if ((xx + ww) > block->w)
+            {
+              if (line < 31)
+                line ++;
 
+              xx = block->line[line];
+              yy += hh;
+              hh = 0;
+            }
 
-char Fl_Help_View::begin_selection()
-{
-  clear_global_selection();
+            if (img) {
+              img->draw(xx + x() - leftline_,
+                        yy + y() - fl_height() + fl_descent() + 2);
+            }
 
-  if (!fl_help_view_buffer) fl_help_view_buffer = fl_create_offscreen(1, 1);
+            xx += ww;
+            if ((height + 2) > hh)
+              hh = height + 2;
 
-  draw_mode_ = Mode::PUSH;
+            needspace = 0;
+          }
+          buf.clear();
+        }
+        else if (*ptr == '\n' && pre)
+        {
+          hv_draw(buf.c_str(), xx + x() - leftline_, yy + y());
+          buf.clear();
 
-    fl_begin_offscreen(fl_help_view_buffer);
-    draw();
-    fl_end_offscreen();
+          if (line < 31)
+            line ++;
+          xx = block->line[line];
+          yy += hh;
+          hh = fsize + 2;
+          needspace = 0;
 
-  draw_mode_ = Mode::DRAW;
+          ptr ++;
+          current_pos_ = (int) (ptr-value_);
+        }
+        else if (isspace((*ptr)&255))
+        {
+          if (pre)
+          {
+            if (*ptr == ' ')
+              buf += ' ';
+            else
+            {
+              // Do tabs every 8 columns...
+              buf += ' '; // at least one space
+              while (buf.size() & 7)
+                buf += ' ';
+            }
+          }
 
-  if (selection_push_last_) return 1;
-  else return 0;
-}
+          ptr ++;
+          if (!pre) current_pos_ = (int) (ptr-value_);
+          needspace = 1;
+        }
+        else if (*ptr == '&') // process html entity
+        {
+          ptr ++;
 
+          int qch = quote_char(ptr);
 
-char Fl_Help_View::extend_selection()
-{
-  if (Fl::event_is_click())
-    return 0;
+          if (qch < 0)
+            buf += '&';
+          else {
+            size_t utf8l = buf.size();
+            buf.add(qch);
+            utf8l = buf.size() - utf8l; // length of added UTF-8 text
+            const char *oldptr = ptr;
+            ptr = strchr(ptr, ';') + 1;
+            entity_extra_length += int(ptr - (oldptr-1)) - utf8l; // extra length between html entity and UTF-8
+          }
 
-  // Give this widget the focus during the selection process. This will
-  // deselect other text selection and make sure, we receive the Copy
-  // keyboard shortcut.
-  if (Fl::focus()!=this)
-    Fl::focus(this);
+          if ((fsize + 2) > hh)
+            hh = fsize + 2;
+        }
+        else
+        {
+          buf += *ptr++;
 
-//  printf("old selection_first_=%d, selection_last_=%d\n",
-//         selection_first_, selection_last_);
+          if ((fsize + 2) > hh)
+            hh = fsize + 2;
+        }
+      }
 
-  int sf = selection_first_, sl = selection_last_;
+      if (buf.size() > 0 && !pre && !head)
+      {
+        ww = buf.width();
 
-  selected_ = true;
+        if (needspace && xx > block->x)
+          xx += (int)fl_width(' ');
 
-  draw_mode_ = Mode::DRAG;
+        if ((xx + ww) > block->w)
+        {
+          if (line < 31)
+            line ++;
+          xx = block->line[line];
+          yy += hh;
+          hh = 0;
+        }
+      }
 
-    fl_begin_offscreen(fl_help_view_buffer);
-    draw();
-    fl_end_offscreen();
+      if (buf.size() > 0 && !head)
+      {
+        hv_draw(buf.c_str(), xx + x() - leftline_, yy + y());
+        if (underline) fl_xyline(xx + x() - leftline_, yy + y() + 1,
+                                 xx + x() - leftline_ + ww);
+        current_pos_ = (int) (ptr-value_);
+      }
+    }
 
-  draw_mode_ = Mode::DRAW;
+  fl_pop_clip();
+} // draw()
 
-  if (selection_push_first_ < selection_drag_first_) {
-    selection_first_ = selection_push_first_;
-  } else {
-    selection_first_ = selection_drag_first_;
-  }
 
-  if (selection_push_last_ > selection_drag_last_) {
-    selection_last_ = selection_push_last_;
-  } else {
-    selection_last_ = selection_drag_last_;
-  }
+/**
+  \brief Creates the Fl_Help_View widget at the specified position and size.
+  \param[in] xx, yy, ww, hh Position and size of the widget
+  \param[in] l Label for the widget, can be nullptr
+*/
+Fl_Help_View::Fl_Help_View(int xx, int yy, int ww, int hh, const char *l)
+: Fl_Group(xx, yy, ww, hh, l),
+  scrollbar_(xx + ww - Fl::scrollbar_size(), yy, Fl::scrollbar_size(), hh - Fl::scrollbar_size()),
+  hscrollbar_(xx, yy + hh - Fl::scrollbar_size(), ww - Fl::scrollbar_size(), Fl::scrollbar_size())
+{
+  color(FL_BACKGROUND2_COLOR, FL_SELECTION_COLOR);
+
+  title_[0]     = '\0';
+  defcolor_     = FL_FOREGROUND_COLOR;
+  bgcolor_      = FL_BACKGROUND_COLOR;
+  textcolor_    = FL_FOREGROUND_COLOR;
+  linkcolor_    = FL_SELECTION_COLOR;
+  textfont_     = FL_TIMES;
+  textsize_     = 12;
+  value_        = nullptr;
 
-//  printf("new selection_first_=%d, selection_last_=%d\n",
-//         selection_first_, selection_last_);
+  blocks_.clear();
 
-  if (sf!=selection_first_ || sl!=selection_last_) {
-//    puts("REDRAW!!!\n");
-    return 1;
-  } else {
-//    puts("");
-    return 0;
-  }
-}
+  link_         = (Fl_Help_Func *)0;
 
-// convert a command with up to four letters into an unsigned int
-static uint32_t command(const char *cmd)
-{
-  uint32_t ret = (tolower(cmd[0])<<24);
-  char c = cmd[1];
-  if (c=='>' || c==' ' || c==0) return ret;
-  ret |= (tolower(c)<<16);
-  c = cmd[2];
-  if (c=='>' || c==' ' || c==0) return ret;
-  ret |= (tolower(c)<<8);
-  c = cmd[3];
-  if (c=='>' || c==' ' || c==0) return ret;
-  ret |= tolower(c);
-  c = cmd[4];
-  if (c=='>' || c==' ' || c==0) return ret;
-  return 0;
-}
+  link_list_.clear();
 
+  directory_.clear();
+  filename_.clear();
 
-static constexpr uint32_t CMD(char a, char b, char c, char d)
-{
-  return ((a<<24)|(b<<16)|(c<<8)|d);
-}
+  topline_      = 0;
+  leftline_     = 0;
+  size_         = 0;
+  hsize_        = 0;
 
+  selection_mode_ = Mode::DRAW;
+  selected_ = false;
+  selection_first_ = 0;
+  selection_last_ = 0;
 
-void Fl_Help_View::end_selection()
-{
-  selection_push_first_ = 0;
-  selection_push_last_ = 0;
-  selection_drag_first_ = 0;
-  selection_drag_last_ = 0;
-}
+  scrollbar_size_ = 0;
 
+  scrollbar_.value(0, hh, 0, 1);
+  scrollbar_.step(8.0);
+  scrollbar_.show();
+  scrollbar_.callback( [](Fl_Widget *s, void *u) {
+      ((Fl_Help_View*)u)->topline((int)(((Fl_Scrollbar*)s)->value()));
+    }, this );
 
-/**
-  \brief Check if the user selected text in this view.
-  \return 1 if text is selected, 0 if no text is selected
- */
-int Fl_Help_View::text_selected() {
-  return selected_;
+  hscrollbar_.value(0, ww, 0, 1);
+  hscrollbar_.step(8.0);
+  hscrollbar_.show();
+  hscrollbar_.type(FL_HORIZONTAL);
+  hscrollbar_.callback( [](Fl_Widget *s, void *u) {
+      ((Fl_Help_View*)u)->leftline(int(((Fl_Scrollbar*)s)->value()));
+    }, this );
+
+  end();
+
+  resize(xx, yy, ww, hh);
 }
 
 
 /**
-  \brief If text is selected in this view, copy it to a clipboard.
-  \param[in] clipboard for x11 only, 0=selection buffer, 1=clipboard, 2=both
-  \return 1 if text is selected, 0 if no text is selected
- */
-int Fl_Help_View::copy(int clipboard) {
-  if (!selected_)
-    return 0;
+  \brief Destroys the Fl_Help_View widget.
 
-  // convert the select part of our html text into some kind of somewhat readable UTF-8
-  // and store it in the selection buffer
-  int p = 0;
-  char pre = 0;
-  int len = (int) strlen(value_);
-  char *txt = (char*)malloc(len+1), *d = txt;
-  const char *s = value_, *cmd, *src;
-  for (;;) {
-    int c = (*s++) & 0xff;
-    if (c==0) break;
-    if (c=='<') { // begin of some html command. Skip until we find a '>'
-      cmd = s;
-      for (;;) {
-        c = (*s++) & 0xff;
-        if (c==0 || c=='>') break;
-      }
-      if (c==0) break;
-      // do something with this command... .
-      // The replacement string must not be longer than the command
-      // itself plus '<' and '>'
-      src = 0;
-      switch (command(cmd)) {
-        case CMD('p','r','e', 0 ): pre = 1; break;
-        case CMD('/','p','r','e'): pre = 0; break;
-        case CMD('t','d', 0 , 0 ):
-        case CMD('p', 0 , 0 , 0 ):
-        case CMD('/','p', 0 , 0 ):
-        case CMD('b','r', 0 , 0 ): src = "\n"; break;
-        case CMD('l','i', 0 , 0 ): src = "\n * "; break;
-        case CMD('/','h','1', 0 ):
-        case CMD('/','h','2', 0 ):
-        case CMD('/','h','3', 0 ):
-        case CMD('/','h','4', 0 ):
-        case CMD('/','h','5', 0 ):
-        case CMD('/','h','6', 0 ): src = "\n\n"; break;
-        case CMD('t','r', 0 , 0 ):
-        case CMD('h','1', 0 , 0 ):
-        case CMD('h','2', 0 , 0 ):
-        case CMD('h','3', 0 , 0 ):
-        case CMD('h','4', 0 , 0 ):
-        case CMD('h','5', 0 , 0 ):
-        case CMD('h','6', 0 , 0 ): src = "\n\n"; break;
-        case CMD('d','t', 0 , 0 ): src = "\n "; break;
-        case CMD('d','d', 0 , 0 ): src = "\n - "; break;
-      }
-      int n = (int) (s-value_);
-      if (src && n>selection_first_ && n<=selection_last_) {
-        while (*src) {
-          *d++ = *src++;
-        }
-        c = src[-1] & 0xff;
-        p = isspace(c) ? ' ' : c;
-      }
-      continue;
-    }
-    const char *s2 = s;
-    if (c=='&') { // special characters (HTML entities)
-      int xx = quote_char(s);
-      if (xx >= 0) {
-        c = xx;
-        for (;;) {
-          char cc = *s++;
-          if (!cc || cc==';') break;
-        }
-      }
-    }
-    int n = (int) (s2-value_);
-    if (n>selection_first_ && n<=selection_last_) {
-      if (!pre && c < 256 && isspace(c)) c = ' ';
-      if (p != ' ' || c != ' ') {
-        if (s2 != s) { // c was an HTML entity
-          d += fl_utf8encode(c, d);
-        }
-        else *d++ = c;
-      }
-      p = c;
-    }
-    if (n>selection_last_) break; // stop parsing html after end of selection
-  }
-  *d = 0;
-  Fl::copy(txt, (int) strlen(txt), clipboard);
-  // printf("copy [%s]\n", txt);
-  free(txt);
-  return 1;
+  The destructor destroys the widget and frees all memory that has been
+  allocated for the current document.
+*/
+Fl_Help_View::~Fl_Help_View()
+{
+  clear_selection();
+  free_data();
 }
 
 
@@ -3282,163 +3085,64 @@ int Fl_Help_View::handle(int event)
           case 'a': select_all(); redraw(); return 1;
           case 'c':
           case 'x': copy(1); return 1;
-        }
-      }
-      break; }
-  }
-  return (Fl_Group::handle(event));
-}
-
-
-/**
-  \brief Creates the Fl_Help_View widget at the specified position and size.
-  \param[in] xx, yy, ww, hh Position and size of the widget
-  \param[in] l Label for the widget, can be nullptr
-*/
-Fl_Help_View::Fl_Help_View(int xx, int yy, int ww, int hh, const char *l)
-: Fl_Group(xx, yy, ww, hh, l),
-  scrollbar_(xx + ww - Fl::scrollbar_size(), yy, Fl::scrollbar_size(), hh - Fl::scrollbar_size()),
-  hscrollbar_(xx, yy + hh - Fl::scrollbar_size(), ww - Fl::scrollbar_size(), Fl::scrollbar_size())
-{
-  color(FL_BACKGROUND2_COLOR, FL_SELECTION_COLOR);
-
-  title_[0]     = '\0';
-  defcolor_     = FL_FOREGROUND_COLOR;
-  bgcolor_      = FL_BACKGROUND_COLOR;
-  textcolor_    = FL_FOREGROUND_COLOR;
-  linkcolor_    = FL_SELECTION_COLOR;
-  textfont_     = FL_TIMES;
-  textsize_     = 12;
-  value_        = nullptr;
-
-  blocks_.clear();
-
-  link_         = (Fl_Help_Func *)0;
-
-  link_list_.clear();
-
-  directory_.clear();
-  filename_.clear();
-
-  topline_      = 0;
-  leftline_     = 0;
-  size_         = 0;
-  hsize_        = 0;
-
-  selection_mode_ = Mode::DRAW;
-  selected_ = false;
-  selection_first_ = 0;
-  selection_last_ = 0;
-
-  scrollbar_size_ = 0;
-
-  scrollbar_.value(0, hh, 0, 1);
-  scrollbar_.step(8.0);
-  scrollbar_.show();
-  scrollbar_.callback( [](Fl_Widget *s, void *u) {
-      ((Fl_Help_View*)u)->topline((int)(((Fl_Scrollbar*)s)->value()));
-    }, this );
-
-  hscrollbar_.value(0, ww, 0, 1);
-  hscrollbar_.step(8.0);
-  hscrollbar_.show();
-  hscrollbar_.type(FL_HORIZONTAL);
-  hscrollbar_.callback( [](Fl_Widget *s, void *u) {
-      ((Fl_Help_View*)u)->leftline(int(((Fl_Scrollbar*)s)->value()));
-    }, this );
-
-  end();
-
-  resize(xx, yy, ww, hh);
-}
-
-
-/**
-  \brief Destroys the Fl_Help_View widget.
-
-  The destructor destroys the widget and frees all memory that has been
-  allocated for the current document.
-*/
-Fl_Help_View::~Fl_Help_View()
-{
-  clear_selection();
-  free_data();
+        }
+      }
+      break; }
+  }
+  return (Fl_Group::handle(event));
 }
 
 
 /**
-  \brief Return the current filename for the text in the buffer.
+  \brief Override the superclass's resize method.
+  \param[in] xx, yy, ww, hh New position and size of the widget
+ */
+void Fl_Help_View::resize(int xx, int yy, int ww, int hh)
+{
+  Fl_Boxtype b = box() ? box() : FL_DOWN_BOX; // Box to draw...
 
-  Fl_Help_View remains the owner of the allocated memory. If the filename
-  changes, the returned pointer will become stale.
+  Fl_Widget::resize(xx, yy, ww, hh);
 
-  \return nullptr if the filename is empty
-*/
-const char *Fl_Help_View::filename() const {
-  if (filename_.empty())
-    return nullptr;
-  else
-    return filename_.c_str();
+  int scrollsize = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size();
+  scrollbar_.resize(x() + w() - scrollsize - Fl::box_dw(b) + Fl::box_dx(b),
+                    y() + Fl::box_dy(b), scrollsize, h() - scrollsize - Fl::box_dh(b));
+  hscrollbar_.resize(x() + Fl::box_dx(b),
+                     y() + h() - scrollsize - Fl::box_dh(b) + Fl::box_dy(b),
+                     w() - scrollsize - Fl::box_dw(b), scrollsize);
+  format();
 }
 
 
-/**
-  \brief Return the current directory for the text in the buffer.
-
-  Fl_Help_View remains the owner of the allocated memory. If the directory
-  changes, the returned pointer will become stale.
-
-  \return nullptr if the directory name is empty
-*/
-const char *Fl_Help_View::directory() const {
-  if (directory_.empty())
-    return nullptr;
-  else
-    return directory_.c_str();
-}
-
+// ---- HTML source and raw data
 
 /**
-  \brief Return the title of the current document.
-
-  Fl_Help_View remains the owner of the allocated memory. If the document
-  changes, the returned pointer will become stale.
-
-  \return empty string if the directory name is empty
- */
-const char *Fl_Help_View::title() const {
-  return title_.c_str();
-}
-
+  \brief Sets the current help text buffer to the string provided and reformats the text.
 
-/**
-  \brief Set a callback function for following links.
+  The provided character string \p val is copied internally and will be
+  freed when value() is called again, or when the widget is destroyed.
 
-  This method assigns a callback function to use when a link is
-  followed or a file is loaded (via Fl_Help_View::load()) that
-  requires a different file or path.
+  If \p val is nullptr, then the widget is cleared.
 
-  The callback function receives a pointer to the Fl_Help_View
-  widget and the URI or full pathname for the file in question.
-  It must return a pathname that can be opened as a local file or nullptr:
+  \param[in] val Text to view, or nullptr to clear the widget,
+      Fl_Help_View will creat a local copy of the string.
+*/
+void Fl_Help_View::value(const char *val)
+{
+  clear_selection();
+  free_data();
+  set_changed();
 
-  \code
-  const char *fn(Fl_Widget *w, const char *uri);
-  \endcode
+  if (!val)
+    return;
 
-  The link function can be used to retrieve remote or virtual
-  documents, returning a temporary file that contains the actual
-  data. If the link function returns nullptr, the value of
-  the Fl_Help_View widget will remain unchanged.
+  value_ = fl_strdup(val);
 
-  If the link callback cannot handle the URI scheme, it should
-  return the uri value unchanged or set the value() of the widget
-  before returning nullptr.
+  initial_load = 1;
+  format();
+  initial_load = 0;
 
-  \param[in] fn Pointer to the callback function
-*/
-void Fl_Help_View::link(Fl_Help_Func *fn) {
-  link_ = fn;
+  topline(0);
+  leftline(0);
 }
 
 
@@ -3585,35 +3289,206 @@ int Fl_Help_View::load(const char *f)
   format();
   initial_load = 0;
 
-  if (!target.empty())
-    topline(target.c_str());
+  if (!target.empty())
+    topline(target.c_str());
+  else
+    topline(0);
+
+  return ret;
+}
+
+
+/**
+  \brief Finds the specified string \p s at starting position \p p.
+
+  The argument \p p and the return value are offsets in Fl_Help_View::value(),
+  counting from 0. If \p p is out of range, 0 is used.
+
+  The string comparison is simple but honors some special cases:
+  - the specified string \p s must be in UTF-8 encoding
+  - HTML tags in value() are filtered (not compared as such, they never match)
+  - HTML entities like '\<' or '\&x#20ac;' are converted to Unicode (UTF-8)
+  - ASCII characters (7-bit, \< 0x80) are compared case insensitive
+  - every newline (LF, '\\n') in value() is treated like a single space
+  - all other strings are compared as-is (byte by byte)
+
+  \param[in] s search string in UTF-8 encoding
+  \param[in] p starting position for search (0,...), Default = 0
+  \return the matching position or -1 if not found
+*/
+int Fl_Help_View::find(const char *s, int p)
+{
+  int           i,                              // Looping var
+                c;                              // Current character
+  Text_Block *b;                             // Current block
+  const char    *bp,                            // Block matching pointer
+                *bs,                            // Start of current comparison
+                *sp;                            // Search string pointer
+
+  DEBUG_FUNCTION(__LINE__,__FUNCTION__);
+
+  // Range check input and value...
+  if (!s || !value_) return -1;
+
+  if (p < 0 || p >= (int)strlen(value_)) p = 0;
+
+  // Look for the string...
+  for (i = (int)blocks_.size(), b = &blocks_[0]; i > 0; i--, b++) {
+    if (b->end < (value_ + p))
+      continue;
+
+    if (b->start < (value_ + p))
+      bp = value_ + p;
+    else
+      bp = b->start;
+
+    bp = vanilla(bp, b->end);
+    if (bp == b->end)
+      continue;
+
+    for (sp = s, bs = bp; *sp && *bp && bp < b->end; ) {
+      bool is_html_entity = false;
+      if (*bp == '&') {
+        // decode HTML entity...
+        if ((c = quote_char(bp + 1)) < 0) {
+          c = '&';
+        } else {
+          const char *entity_end = strchr(bp + 1, ';');
+          if (entity_end) {
+            is_html_entity = true; // c contains the unicode character
+            bp = entity_end;
+          } else {
+            c = '&';
+          }
+        }
+      } else {
+        c = *bp;
+      }
+
+      if (c == '\n') c = ' '; // treat newline as a single space
+
+      // *FIXME* *UTF-8* (A.S. 02/14/2016)
+      // At this point c may be an arbitrary Unicode Code Point corresponding
+      // to a quoted character (see above), i.e. it _can_ be a multi byte
+      // UTF-8 sequence and must be compared with the corresponding
+      // multi byte string in (*sp)...
+      // For instance: "€" == 0x20ac -> 0xe2 0x82 0xac (UTF-8: 3 bytes).
+      // Hint: use fl_utf8encode() [see below]
+
+      int utf_len = 1;
+      if (c > 0x20 && c < 0x80 && tolower(*sp) == tolower(c)) {
+        // Check for ASCII case insensitive match.
+        //printf("%ld text match %c/%c\n", bp-value_, *sp, c);
+        sp++;
+        bp = vanilla(bp+1, b->end);
+      } else if (is_html_entity && fl_utf8decode(sp, nullptr, &utf_len) == (unsigned int)c ) {
+        // Check if a < entity ini html matches a UTF-8 character in the
+        // search string.
+        //printf("%ld unicode match 0x%02X 0x%02X\n", bp-value_, *sp, c);
+        sp += utf_len;
+        bp = vanilla(bp+1, b->end);
+      } else if (*sp == c) {
+        // Check if UTF-8 bytes in html and the search string match.
+        //printf("%ld binary match %c/%c\n", bp-value_, *sp, c);
+        sp++;
+        bp = vanilla(bp+1, b->end);
+      } else {
+        // No match, so reset to start of search... .
+        //printf("reset search (%c/%c)\n", *sp, c);
+        sp = s;
+        bp = bs = vanilla(bs+1, b->end);
+      }
+    }
+
+    if (!*sp) { // Found a match!
+      topline(b->y - b->h);
+      return int(bs - value_);
+    }
+  }
+
+  // No match!
+  return (-1);
+}
+
+
+/**
+  \brief Set a callback function for following links.
+
+  This method assigns a callback function to use when a link is
+  followed or a file is loaded (via Fl_Help_View::load()) that
+  requires a different file or path.
+
+  The callback function receives a pointer to the Fl_Help_View
+  widget and the URI or full pathname for the file in question.
+  It must return a pathname that can be opened as a local file or nullptr:
+
+  \code
+  const char *fn(Fl_Widget *w, const char *uri);
+  \endcode
+
+  The link function can be used to retrieve remote or virtual
+  documents, returning a temporary file that contains the actual
+  data. If the link function returns nullptr, the value of
+  the Fl_Help_View widget will remain unchanged.
+
+  If the link callback cannot handle the URI scheme, it should
+  return the uri value unchanged or set the value() of the widget
+  before returning nullptr.
+
+  \param[in] fn Pointer to the callback function
+*/
+void Fl_Help_View::link(Fl_Help_Func *fn) {
+  link_ = fn;
+}
+
+
+/**
+  \brief Return the current filename for the text in the buffer.
+
+  Fl_Help_View remains the owner of the allocated memory. If the filename
+  changes, the returned pointer will become stale.
+
+  \return nullptr if the filename is empty
+*/
+const char *Fl_Help_View::filename() const {
+  if (filename_.empty())
+    return nullptr;
   else
-    topline(0);
+    return filename_.c_str();
+}
 
-  return ret;
+
+/**
+  \brief Return the current directory for the text in the buffer.
+
+  Fl_Help_View remains the owner of the allocated memory. If the directory
+  changes, the returned pointer will become stale.
+
+  \return nullptr if the directory name is empty
+*/
+const char *Fl_Help_View::directory() const {
+  if (directory_.empty())
+    return nullptr;
+  else
+    return directory_.c_str();
 }
 
 
 /**
-  \brief Override the superclass's resize method.
-  \param[in] xx, yy, ww, hh New position and size of the widget
- */
-void Fl_Help_View::resize(int xx, int yy, int ww, int hh)
-{
-  Fl_Boxtype b = box() ? box() : FL_DOWN_BOX; // Box to draw...
+  \brief Return the title of the current document.
 
-  Fl_Widget::resize(xx, yy, ww, hh);
+  Fl_Help_View remains the owner of the allocated memory. If the document
+  changes, the returned pointer will become stale.
 
-  int scrollsize = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size();
-  scrollbar_.resize(x() + w() - scrollsize - Fl::box_dw(b) + Fl::box_dx(b),
-                    y() + Fl::box_dy(b), scrollsize, h() - scrollsize - Fl::box_dh(b));
-  hscrollbar_.resize(x() + Fl::box_dx(b),
-                     y() + h() - scrollsize - Fl::box_dh(b) + Fl::box_dy(b),
-                     w() - scrollsize - Fl::box_dw(b), scrollsize);
-  format();
+  \return empty string if the directory name is empty
+ */
+const char *Fl_Help_View::title() const {
+  return title_.c_str();
 }
 
 
+// ---- Rendering attributes
+
 /**
   \brief Scroll the text to the given anchor.
   \param[in] anchor scroll to this named anchor
@@ -3688,37 +3563,139 @@ void Fl_Help_View::leftline(int left)
 }
 
 
-/**
-  \brief Sets the current help text buffer to the string provided and reformats the text.
+// ---- Text selection
 
-  The provided character string \p val is copied internally and will be
-  freed when value() is called again, or when the widget is destroyed.
+/**
+  \brief Removes the current text selection.
+*/
+void Fl_Help_View::clear_selection()
+{
+  selected_ = false;
+  selection_first_ = 0;
+  selection_last_ = 0;
+  redraw();
+}
 
-  If \p val is nullptr, then the widget is cleared.
 
-  \param[in] val Text to view, or nullptr to clear the widget,
-      Fl_Help_View will creat a local copy of the string.
+/**
+  \brief Selects all the text in the view.
 */
-void Fl_Help_View::value(const char *val)
+void Fl_Help_View::select_all()
 {
   clear_selection();
-  free_data();
-  set_changed();
+  if (!value_) return;
+  selection_drag_last_ = selection_last_ = (int) strlen(value_);
+  selected_ = true;
+}
 
-  if (!val)
-    return;
 
-  value_ = fl_strdup(val);
+/**
+  \brief Check if the user selected text in this view.
+  \return 1 if text is selected, 0 if no text is selected
+ */
+int Fl_Help_View::text_selected() {
+  return selected_;
+}
 
-  initial_load = 1;
-  format();
-  initial_load = 0;
 
-  topline(0);
-  leftline(0);
+/**
+  \brief If text is selected in this view, copy it to a clipboard.
+  \param[in] clipboard for x11 only, 0=selection buffer, 1=clipboard, 2=both
+  \return 1 if text is selected, 0 if no text is selected
+ */
+int Fl_Help_View::copy(int clipboard) {
+  if (!selected_)
+    return 0;
+
+  // convert the select part of our html text into some kind of somewhat readable UTF-8
+  // and store it in the selection buffer
+  int p = 0;
+  char pre = 0;
+  int len = (int) strlen(value_);
+  char *txt = (char*)malloc(len+1), *d = txt;
+  const char *s = value_, *cmd, *src;
+  for (;;) {
+    int c = (*s++) & 0xff;
+    if (c==0) break;
+    if (c=='<') { // begin of some html command. Skip until we find a '>'
+      cmd = s;
+      for (;;) {
+        c = (*s++) & 0xff;
+        if (c==0 || c=='>') break;
+      }
+      if (c==0) break;
+      // do something with this command... .
+      // The replacement string must not be longer than the command
+      // itself plus '<' and '>'
+      src = 0;
+      switch (command(cmd)) {
+        case CMD('p','r','e', 0 ): pre = 1; break;
+        case CMD('/','p','r','e'): pre = 0; break;
+        case CMD('t','d', 0 , 0 ):
+        case CMD('p', 0 , 0 , 0 ):
+        case CMD('/','p', 0 , 0 ):
+        case CMD('b','r', 0 , 0 ): src = "\n"; break;
+        case CMD('l','i', 0 , 0 ): src = "\n * "; break;
+        case CMD('/','h','1', 0 ):
+        case CMD('/','h','2', 0 ):
+        case CMD('/','h','3', 0 ):
+        case CMD('/','h','4', 0 ):
+        case CMD('/','h','5', 0 ):
+        case CMD('/','h','6', 0 ): src = "\n\n"; break;
+        case CMD('t','r', 0 , 0 ):
+        case CMD('h','1', 0 , 0 ):
+        case CMD('h','2', 0 , 0 ):
+        case CMD('h','3', 0 , 0 ):
+        case CMD('h','4', 0 , 0 ):
+        case CMD('h','5', 0 , 0 ):
+        case CMD('h','6', 0 , 0 ): src = "\n\n"; break;
+        case CMD('d','t', 0 , 0 ): src = "\n "; break;
+        case CMD('d','d', 0 , 0 ): src = "\n - "; break;
+      }
+      int n = (int) (s-value_);
+      if (src && n>selection_first_ && n<=selection_last_) {
+        while (*src) {
+          *d++ = *src++;
+        }
+        c = src[-1] & 0xff;
+        p = isspace(c) ? ' ' : c;
+      }
+      continue;
+    }
+    const char *s2 = s;
+    if (c=='&') { // special characters (HTML entities)
+      int xx = quote_char(s);
+      if (xx >= 0) {
+        c = xx;
+        for (;;) {
+          char cc = *s++;
+          if (!cc || cc==';') break;
+        }
+      }
+    }
+    int n = (int) (s2-value_);
+    if (n>selection_first_ && n<=selection_last_) {
+      if (!pre && c < 256 && isspace(c)) c = ' ';
+      if (p != ' ' || c != ' ') {
+        if (s2 != s) { // c was an HTML entity
+          d += fl_utf8encode(c, d);
+        }
+        else *d++ = c;
+      }
+      p = c;
+    }
+    if (n>selection_last_) break; // stop parsing html after end of selection
+  }
+  *d = 0;
+  Fl::copy(txt, (int) strlen(txt), clipboard);
+  // printf("copy [%s]\n", txt);
+  free(txt);
+  return 1;
 }
 
 
+// ---- Scroll bars
+
 /**
   \brief Get the current size of the scrollbars' troughs, in pixels.
 
@@ -3757,6 +3734,80 @@ void Fl_Help_View::scrollbar_size(int newSize) {
 }
 
 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/**
+  \brief Skips over HTML tags in a text.
+
+  In an html style text, set the character pointer p, skipping anything from a
+  leading '<' up to and including the closing '>'. If the end of the buffer is
+  reached, the function returns `end`.
+
+  No need to handle UTF-8 here.
+
+  \param[in] p pointer to html text, UTF-8 characters possible
+  \param[in] end pointer to the end of the text (need nut be NUL)
+  \return new pointer to text after skipping over '<...>' blocks, or `end`
+    if NUL was found or a '<...>' block was not closed.
+*/
+static const char *vanilla(const char *p, const char *end) {
+  if (*p == '\0' || p >= end) return end;
+  for (;;) {
+    if (*p != '<') {
+      return p;
+    } else {
+      while (*p && p < end && *p != '>') p++;
+    }
+    p++;
+    if (*p == '\0' || p >= end) return end;
+  }
+}
+
+
+
+// convert a command with up to four letters into an unsigned int
+static uint32_t command(const char *cmd)
+{
+  uint32_t ret = (tolower(cmd[0])<<24);
+  char c = cmd[1];
+  if (c=='>' || c==' ' || c==0) return ret;
+  ret |= (tolower(c)<<16);
+  c = cmd[2];
+  if (c=='>' || c==' ' || c==0) return ret;
+  ret |= (tolower(c)<<8);
+  c = cmd[3];
+  if (c=='>' || c==' ' || c==0) return ret;
+  ret |= tolower(c);
+  c = cmd[4];
+  if (c=='>' || c==' ' || c==0) return ret;
+  return 0;
+}
+
+
+// ------ Some more helper functions
+
+
 /*
   \brief Returns the Unicode Code Point associated with a quoted character (aka "HTML Entity").
 
-- 
cgit v1.2.3