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 --- FL/Fl_Help_View.H | 145 +- src/Fl_Help_View.cxx | 5015 +++++++++++++++++++++++++------------------------- 2 files changed, 2611 insertions(+), 2549 deletions(-) diff --git a/FL/Fl_Help_View.H b/FL/Fl_Help_View.H index e529253f6..2f45475f5 100644 --- a/FL/Fl_Help_View.H +++ b/FL/Fl_Help_View.H @@ -126,53 +126,51 @@ private: // classes, structs, and types /** Private struct to describe blocks of text. */ struct Text_Block { - const char *start, // Start of text - *end; // End of text - uchar border; // Draw border? - Fl_Color bgcolor; // Background color - int x, // Indentation/starting X coordinate - y, // Starting Y coordinate - w, // Width - h; // Height - int line[32]; // Left starting position for each line - int ol; // is ordered list
    element - int ol_num; // item number in ordered list + const char *start; // Start of text + const char *end; // End of text + uchar border; // Draw border? + Fl_Color bgcolor; // Background color + int x; // Indentation/starting X coordinate + int y; // Starting Y coordinate + int w; // Width + int h; // Height + int line[32]; // Left starting position for each line + int ol; // is ordered list
      element + int ol_num; // item number in ordered list }; /** Private class to hold a link with target and its position on screen. */ - class Link { - public: - std::string filename_; // Filename part of a link - std::string target; // Target part of a link - Fl_Rect box; // Clickable rectangle that defines the link area + struct Link { + std::string filename_; // Filename part of a link + std::string target; // Target part of a link + Fl_Rect box; // Clickable rectangle that defines the link area }; /** Private font stack element definition. */ struct Font_Style { - Fl_Font f; ///< Font - Fl_Fontsize s; ///< Font Size - Fl_Color c; ///< Font Color - void get(Fl_Font &afont, Fl_Fontsize &asize, Fl_Color &acolor); - void set(Fl_Font afont, Fl_Fontsize asize, Fl_Color acolor); + Fl_Font f; ///< Font + Fl_Fontsize s; ///< Font Size + Fl_Color c; ///< Font Color Font_Style(Fl_Font afont, Fl_Fontsize asize, Fl_Color acolor); - Font_Style() = default; ///< Default constructor + Font_Style() = default; ///< Default constructor + void get(Fl_Font &afont, Fl_Fontsize &asize, Fl_Color &acolor); + void set(Fl_Font afont, Fl_Fontsize asize, Fl_Color acolor); }; /** Private class to hold font information on a stack. */ struct Font_Stack { - void init(Fl_Font f, Fl_Fontsize s, Fl_Color c); - void top(Fl_Font &f, Fl_Fontsize &s, Fl_Color &c); - void push(Fl_Font f, Fl_Fontsize s, Fl_Color c); - void pop(Fl_Font &f, Fl_Fontsize &s, Fl_Color &c); - size_t count() const; - protected: + void init(Fl_Font f, Fl_Fontsize s, Fl_Color c); + void top(Fl_Font &f, Fl_Fontsize &s, Fl_Color &c); + void push(Fl_Font f, Fl_Fontsize s, Fl_Color c); + void pop(Fl_Font &f, Fl_Fontsize &s, Fl_Color &c); + size_t count() const; + private: std::vector elts_; ///< font elements }; enum class Align { RIGHT = -1, CENTER, LEFT }; ///< Alignments enum class Mode { DRAW, PUSH, DRAG }; ///< Draw modes - private: // data members // HTML source and raw data @@ -221,109 +219,122 @@ private: // data members // Callback - Fl_Help_Func *link_; ///< Link transform function + Fl_Help_Func *link_; ///< Link transform function // Scrollbars - Fl_Scrollbar scrollbar_; ///< Vertical scrollbar for document - Fl_Scrollbar hscrollbar_; ///< Horizontal scrollbar - int scrollbar_size_; ///< Size for both scrollbars + Fl_Scrollbar scrollbar_; ///< Vertical scrollbar for document + Fl_Scrollbar hscrollbar_; ///< Horizontal scrollbar + int scrollbar_size_; ///< Size for both scrollbars private: // methods - void initfont(Fl_Font &f, Fl_Fontsize &s, Fl_Color &c) { f = textfont_; s = textsize_; c = textcolor_; fstack_.init(f, s, c); } - void pushfont(Fl_Font f, Fl_Fontsize s) {fstack_.push(f, s, textcolor_);} - void pushfont(Fl_Font f, Fl_Fontsize s, Fl_Color c) {fstack_.push(f, s, c);} - void popfont(Fl_Font &f, Fl_Fontsize &s, Fl_Color &c) {fstack_.pop(f, s, c);} + // HTML source and raw data, getter + + void free_data(); + std::shared_ptr find_link(int, int); + void follow_link(std::shared_ptr); + + // HTML interpretation and formatting - Text_Block *add_block(const char *s, int xx, int yy, int ww, int hh, uchar border = 0); + Text_Block *add_block(const char *s, int xx, int yy, int ww, int hh, uchar border = 0); void add_link(const std::string &link, int xx, int yy, int ww, int hh); void add_target(const std::string &n, int yy); int do_align(Text_Block *block, int line, int xx, Align a, int &l); void format(); void format_table(int *table_width, int *columns, const char *table); - void free_data(); Align get_align(const char *p, Align a); const char *get_attr(const char *p, const char *n, char *buf, int bufsize); Fl_Color get_color(const char *n, Fl_Color c); Fl_Shared_Image *get_image(const char *name, int W, int H); int get_length(const char *l); + // Font and font stack + + /// Initialize the font stack with default values. + void initfont(Fl_Font &f, Fl_Fontsize &s, Fl_Color &c) { f = textfont_; s = textsize_; c = textcolor_; fstack_.init(f, s, c); } + /// Push the current font and size onto the stack. + void pushfont(Fl_Font f, Fl_Fontsize s) {fstack_.push(f, s, textcolor_);} + /// Push the current font, size, and color onto the stack. + void pushfont(Fl_Font f, Fl_Fontsize s, Fl_Color c) {fstack_.push(f, s, c);} + /// Get the current font, size, and color from the stack. + void popfont(Fl_Font &f, Fl_Fontsize &s, Fl_Color &c) {fstack_.pop(f, s, c);} + + // Text selection + void hv_draw(const char *t, int x, int y, int entity_extra_length = 0); char begin_selection(); char extend_selection(); void end_selection(); - void clear_global_selection(); - std::shared_ptr find_link(int, int); - void follow_link(std::shared_ptr); protected: + // Widget management + void draw() override; public: static const char *copy_menu_text; + // Widget management + Fl_Help_View(int xx, int yy, int ww, int hh, const char *l = 0); ~Fl_Help_View() override; - int handle(int) override; - void resize(int,int,int,int) override; + int handle(int) override; + void resize(int,int,int,int) override; + /** Changes the size of the widget. \see Fl_Widget::size(int, int) */ + void size(int W, int H) { Fl_Widget::size(W, H); } - const char *filename() const; - const char *directory() const; - const char *title() const; + // HTML source and raw data + void value(const char *val); + /** Return a pointer to the internal text buffer. */ + const char *value() const { return (value_); } + int load(const char *f); int find(const char *s, int p = 0); void link(Fl_Help_Func *fn); - int load(const char *f); - /** Return the document height in pixels. */ - int size() const { return (size_); } + const char *filename() const; + const char *directory() const; + const char *title() const; - /** Changes the size of the widget. \see Fl_Widget::size(int, int) */ - void size(int W, int H) { Fl_Widget::size(W, H); } + // Rendering attributes + /** Return the document height in pixels. */ + int size() const { return (size_); } /** Set the default text color. */ void textcolor(Fl_Color c) { if (textcolor_ == defcolor_) textcolor_ = c; defcolor_ = c; } - /** Return the current default text color. */ Fl_Color textcolor() const { return (defcolor_); } - /** Set the default text font. */ void textfont(Fl_Font f) { textfont_ = f; format(); } - /** Return the default text font. */ Fl_Font textfont() const { return (textfont_); } - /** Set the default text size. */ void textsize(Fl_Fontsize s) { textsize_ = s; format(); } - /** Get the default text size. */ - - Fl_Fontsize textsize() const { return (textsize_); } + Fl_Fontsize textsize() const { return (textsize_); } void topline(const char *n); void topline(int); - /** Get the current top line in pixels. */ int topline() const { return (topline_); } - void leftline(int); - /** Get the left position in pixels. */ int leftline() const { return (leftline_); } - void value(const char *val); + // Text selection - //** Return a pointer to the internal text buffer. */ - const char *value() const { return (value_); } void clear_selection(); void select_all(); + int text_selected(); + int copy(int clipboard=1); + + // Scroll bars + int scrollbar_size() const; void scrollbar_size(int newSize); - int text_selected(); - int copy(int clipboard=1); }; #endif // !Fl_Help_View_H 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