/*
 * File: dw_page.c
 *
 * Copyright (C) 1997       Raph Levien <raph@acm.org>
 * Copyright (C) 1999       Luca Rota <drake@freemail.it>
 * Copyright (C) 2001-2003  Sebastian Geerken <s.geerken@ping.de>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

/*
 * This module contains the Dw_page widget, which is the "back end" to
 * Web text widgets including html.
 */

#include <ctype.h>
#include <string.h>
#include <stdio.h>

#include <gtk/gtk.h>

#include "msg.h"
#include "list.h"
#include "dw_page.h"
#include "dw_marshal.h"
#include "dw_gtk_viewport.h"

#include "prefs.h"

#define DEBUG_REWRAP_LEVEL    0
#define DEBUG_SIZE_LEVEL     10
#define DEBUG_EVENT_LEVEL     0
#define DEBUG_ITERATOR_LEVEL  0

/*#define DEBUG_LEVEL 10*/
#include "debug.h"

#define MAX3(a, b, c) (MAX (a, MAX (b, c)))

static void Dw_page_init          (DwPage *page);
static void Dw_page_class_init    (DwPageClass *klass);

static void Dw_page_destroy       (GtkObject *object);

static void Dw_page_size_request      (DwWidget *widget,
                                      DwRequisition *requisition);
static void Dw_page_get_extremes      (DwWidget *widget,
                                       DwExtremes *extremes);
static void Dw_page_size_allocate     (DwWidget *widget,
                                       DwAllocation *allocation);
static void Dw_page_mark_change       (DwWidget *widget,
                                       gint ref);
static void Dw_page_set_width         (DwWidget *widget,
                                       gint32 width);
static void Dw_page_set_ascent        (DwWidget *widget,
                                       gint32 ascent);
static void Dw_page_set_descent       (DwWidget *widget,
                                       gint32 descent);
static void Dw_page_draw              (DwWidget *page,
                                       DwRectangle *area,
                                       GdkEventExpose *event);
static gboolean Dw_page_button_press  (DwWidget *widget,
                                       gint32 x,
                                       gint32 y,
                                       GdkEventButton *event);
static gboolean Dw_page_button_release(DwWidget *widget,
                                       gint32 x,
                                       gint32 y,
                                       GdkEventButton *event);
static gboolean Dw_page_motion_notify (DwWidget *widget,
                                       gint32 x,
                                       gint32 y,
                                       GdkEventMotion *event);
static gboolean Dw_page_leave_notify  (DwWidget *widget,
                                       DwWidget *next_widget,
                                       GdkEventMotion *event);


static void Dw_page_add               (DwContainer *container,
                                       DwWidget *widget);
static void Dw_page_remove            (DwContainer *container,
                                       DwWidget *widget);
static void Dw_page_forall            (DwContainer *container,
                                       DwCallback callback,
                                       gpointer callback_data);
static void Dw_page_real_word_wrap    (DwPage *page,
                                       gint word_ind);

static DwIterator* Dw_page_iterator           (DwWidget *widget,
                                               gint mask,
                                               gboolean at_end);
static gboolean    Dw_page_iterator_next      (DwIterator *it);
static gboolean    Dw_page_iterator_prev      (DwIterator *it);
static void        Dw_page_iterator_highlight (DwIterator *it,
                                               gint start,
                                               gint end,
                                               DwHighlightLayer layer);
static void        Dw_page_iterator_scroll_to (DwIterator *it,
                                               DwPosition pos);

static void Dw_page_rewrap            (DwPage *page);

/*
 * Returns the x offset (the indentation plus any offset needed for
 * centering or right justification) for the line. The offset returned
 * is relative to the page *content* (i.e. without border etc.).
 */
#define Dw_page_line_x_offset(page, line) \
   ( (page)->inner_padding + (line)->left_offset + \
     ((line) == (page)->lines ? (page)->line1_offset_eff : 0) )

/*
 * Like Dw_style_box_offset_x, but relative to the allocation (i.e.
 * including border etc.).
 */
#define Dw_page_line_total_x_offset(page, line) \
   ( Dw_page_line_x_offset (page, line) + \
     p_Dw_style_box_offset_x (((DwWidget*)(page))->style) )

/*
 * Returns the y offset (within the widget) of a line.
 */
#define Dw_page_line_total_y_offset(page, line) \
   Dw_page_line_total_y_offset_allocation(page, line, \
                                          &((DwWidget*)(page))->allocation)

/*
 * Like Dw_page_line_total_y_offset, but with the allocation as parameter.
 */
#define Dw_page_line_total_y_offset_allocation(page, line, allocation) \
   ( (allocation)->ascent - (page)->lines[0].ascent + (line)->top )

/*
 * Similar to Dw_page_line_total_y_offset.
 */
#define Dw_page_line_total_y_offset_i(page, line_index) \
   Dw_page_line_total_y_offset(page, &(page)->lines[line_index])

#define Dw_page_word_wrap(page, word_ind) \
   (((DwPageClass*)(((GtkObject*)(page))->klass))->word_wrap (page, word_ind))

enum
{
   LINK_ENTERED,
   LINK_PRESSED,
   LINK_RELEASED,
   LINK_CLICKED,
   LAST_SIGNAL
};

static DwContainerClass *parent_class;
static guint page_signals[LAST_SIGNAL] = { 0 };


/*
 * Return the type of DwPage
 */
GtkType a_Dw_page_get_type (void)
{
   static GtkType type = 0;

   if (!type) {
      GtkTypeInfo info = {
         "DwPage",
         sizeof (DwPage),
         sizeof (DwPageClass),
         (GtkClassInitFunc) Dw_page_class_init,
         (GtkObjectInitFunc) Dw_page_init,
         (GtkArgSetFunc) NULL,
         (GtkArgGetFunc) NULL,
         (GtkClassInitFunc) NULL
      };

      type = gtk_type_unique (DW_TYPE_CONTAINER, &info);
   }

   return type;
}


/*
 * Create a new DwPage
 */
DwWidget* a_Dw_page_new (void)
{
   GtkObject *object;

   object = gtk_object_new (DW_TYPE_PAGE, NULL);
   return DW_WIDGET (object);
}


/*
 * Initialize a DwPage
 */
static void Dw_page_init (DwPage *page)
{
   DBG_CREATE (page, "DwPage");

   DW_WIDGET_SET_FLAGS (page, DW_USES_HINTS);

   page->list_item = FALSE;
   page->inner_padding = 0;
   page->line1_offset = 0;
   page->line1_offset_eff = 0;
   page->ignore_line1_offset_sometimes = FALSE;

   /*
    * The initial sizes of page->lines and page->words should not be
    * too high, since this will waste much memory with tables
    * containing many small cells. The few more calls to realloc
    * should not decrease the speed considerably.
    * (Current setting is for minimal memory usage. An interesting fact
    * is that high values decrease speed due to memory handling overhead!)
    * todo: Some tests would be useful.
    */
   page->num_lines_max = 1; /* 2 */
   page->num_lines = 0;
   page->lines = NULL;      /* g_new(DwPageLine, page->num_lines_max); */

   page->num_words_max = 1; /* 8 */
   page->num_words = 0;
   page->words = NULL;      /* g_new(DwPageWord, page->num_words_max); */

   page->last_line_width = 0;
   page->last_line_par_min = 0;
   page->last_line_par_max = 0;
   page->wrap_ref = -1;

   //DBG_SET_NUM(page, "last_line_width", page->last_line_width);
   DBG_SET_NUM(page, "last_line_par_min", page->last_line_par_min);
   DBG_SET_NUM(page, "last_line_par_max", page->last_line_par_max);
   //DBG_SET_NUM(page, "wrap_ref", page->wrap_ref);

   page->hover_link = -1;

   /* random values */
   page->avail_width = 100;
   page->avail_ascent = 100;
   page->avail_descent = 0;
}

/*
 * Initialize the DwPage's class
 */
static void Dw_page_class_init (DwPageClass *klass)
{
   GtkObjectClass *object_class;
   DwWidgetClass *widget_class;
   DwContainerClass *container_class;

   object_class = (GtkObjectClass*) klass;
   widget_class = (DwWidgetClass*) klass;
   container_class = (DwContainerClass*) klass;
   parent_class = gtk_type_class (DW_TYPE_CONTAINER);

   page_signals[LINK_ENTERED] =
      gtk_signal_new ("link_entered",
                      GTK_RUN_LAST,
                      object_class->type,
                      GTK_SIGNAL_OFFSET (DwPageClass, link_entered),
                      p_Dw_marshal_link_enter,
                      GTK_TYPE_BOOL,
                      3, GTK_TYPE_INT, GTK_TYPE_INT, GTK_TYPE_INT);
   page_signals[LINK_PRESSED] =
      gtk_signal_new ("link_pressed",
                      GTK_RUN_LAST,
                      object_class->type,
                      GTK_SIGNAL_OFFSET (DwPageClass, link_pressed),
                      p_Dw_marshal_link_button,
                      GTK_TYPE_BOOL,
                      4, GTK_TYPE_INT, GTK_TYPE_INT, GTK_TYPE_INT,
                      GTK_TYPE_GDK_EVENT);
   page_signals[LINK_RELEASED] =
      gtk_signal_new ("link_released",
                      GTK_RUN_LAST,
                      object_class->type,
                      GTK_SIGNAL_OFFSET (DwPageClass, link_released),
                      p_Dw_marshal_link_button,
                      GTK_TYPE_BOOL,
                      4, GTK_TYPE_INT, GTK_TYPE_INT, GTK_TYPE_INT,
                      GTK_TYPE_GDK_EVENT);
   page_signals[LINK_CLICKED] =
      gtk_signal_new ("link_clicked",
                      GTK_RUN_LAST,
                      object_class->type,
                      GTK_SIGNAL_OFFSET (DwPageClass, link_clicked),
                      p_Dw_marshal_link_button,
                      GTK_TYPE_BOOL,
                      4, GTK_TYPE_INT, GTK_TYPE_INT, GTK_TYPE_INT,
                      GTK_TYPE_GDK_EVENT);
   gtk_object_class_add_signals (object_class, page_signals, LAST_SIGNAL);

   object_class->destroy = Dw_page_destroy;

   widget_class->size_request = Dw_page_size_request;
   widget_class->get_extremes = Dw_page_get_extremes;
   widget_class->size_allocate = Dw_page_size_allocate;
   widget_class->mark_size_change = Dw_page_mark_change;
   widget_class->mark_extremes_change = Dw_page_mark_change;
   widget_class->set_width = Dw_page_set_width;
   widget_class->set_ascent = Dw_page_set_ascent;
   widget_class->set_descent = Dw_page_set_descent;
   widget_class->draw = Dw_page_draw;
   widget_class->button_press_event = Dw_page_button_press;
   widget_class->button_release_event = Dw_page_button_release;
   widget_class->motion_notify_event = Dw_page_motion_notify;
   widget_class->leave_notify_event = Dw_page_leave_notify;
   widget_class->iterator = Dw_page_iterator;

   container_class->add = Dw_page_add;
   container_class->remove = Dw_page_remove;
   container_class->forall = Dw_page_forall;

   klass->link_entered = NULL;
   klass->link_pressed = NULL;
   klass->link_released = NULL;
   klass->link_clicked = NULL;
   klass->word_wrap = Dw_page_real_word_wrap;
}


/*
 * Destroy the page (standard Gtk+ function)
 */
static void Dw_page_destroy (GtkObject *object)
{
   DwPage *page = DW_PAGE (object);
   DwPageWord *word;
   gint i;

   //DEBUG_MSG (10, "Dw_page_destroy\n");

   for (i = 0; i < page->num_words; i++) {
      word = &page->words[i];
      if (word->content.type == DW_CONTENT_WIDGET)
         gtk_object_unref (GTK_OBJECT(word->content.data.widget));
      else if (word->content.type == DW_CONTENT_TEXT)
         g_free (word->content.data.text);
      else if (word->content.type == DW_CONTENT_ANCHOR)
         g_free (word->content.data.anchor);

      a_Dw_style_unref (word->style);
      a_Dw_style_unref (word->space_style);
   }

   g_free (page->lines);
   g_free (page->words);

   /* Make sure we don't own widgets anymore. Necessary before call of
      parent_class->destroy. */
   page->num_words = 0;
   page->num_lines = 0;

   GTK_OBJECT_CLASS(parent_class)->destroy (object);
}


/*
 * Standard Dw function
 *
 * The ascent of a page is the ascent of the first line, plus
 * padding/border/margin. This can be used to align the first lines
 * of several pages in a horizontal line.
 */
static void Dw_page_size_request (DwWidget *widget,
                                  DwRequisition *requisition)
{
   DwPage *page = DW_PAGE (widget);
   DwPageLine *last_line;

   Dw_page_rewrap (page);

   if (page->num_lines > 0) {
      last_line = &page->lines[page->num_lines - 1];
      requisition->width =
         MAX (last_line->max_line_width, page->last_line_width);
      /* Note: the break_space of the last line is ignored, so breaks
         at the end of a page are not visible. */
      requisition->ascent = page->lines[0].ascent;
      requisition->descent = last_line->top
         + last_line->ascent + last_line->descent - page->lines[0].ascent;
   } else {
      requisition->width = page->last_line_width;
      requisition->ascent = 0;
      requisition->descent = 0;
   }

   requisition->width +=
      page->inner_padding + p_Dw_style_box_diff_width (widget->style);
   requisition->ascent += p_Dw_style_box_offset_y (widget->style);
   requisition->descent += p_Dw_style_box_rest_height (widget->style);

   if (requisition->width < page->avail_width)
      requisition->width = page->avail_width;
}


/*
 * Get the extremes of a word within a page.
 */
static void Dw_page_get_word_extremes (DwPageWord *word,
                                       DwExtremes *extremes)
{
   if (word->content.type == DW_CONTENT_WIDGET) {
      if (DW_WIDGET_USES_HINTS (word->content.data.widget))
         a_Dw_widget_get_extremes (word->content.data.widget, extremes);
      else {
         if (DW_STYLE_IS_PER_LENGTH(word->content.data.widget->style->width)) {
            extremes->min_width = 0;
            if (DW_WIDGET_HAS_CONTENT (word->content.data.widget))
               extremes->max_width = DW_INFINITY;
            else
               extremes->max_width = 0;
         } else if (DW_STYLE_IS_ABS_LENGTH
                    (word->content.data.widget->style->width)) {
            /* Fixed lengths are only applied to the content, so we have to
             * add padding, border and margin. */
            extremes->min_width = extremes->max_width =
               DW_STYLE_ABS_LENGTH_VAL(word->content.data.widget->style->width)
               + p_Dw_style_box_diff_width(word->style);
         } else
            a_Dw_widget_get_extremes (word->content.data.widget, extremes);
      }
   } else {
      extremes->min_width = word->size.width;
      extremes->max_width = word->size.width;
   }
}

/*
 * Standard Dw function
 */
static void Dw_page_get_extremes (DwWidget *widget,
                                  DwExtremes *extremes)
{
   DwPage *page = DW_PAGE (widget);
   DwExtremes word_extremes;
   DwPageLine *line;
   DwPageWord *word;
   gint word_index, line_index;
   gint32 par_min, par_max;
   gboolean nowrap;

   DBG_MSG (widget, "extremes", 0, "Dw_page_get_extremes");
   DBG_START (widget);

   if (page->num_lines == 0) {
      /* empty page */
      extremes->min_width = 0;
      extremes->max_width = 0;
   } else if (page->wrap_ref == -1) {
      /* no rewrap necessary -> values in lines are up to date */
      line = &page->lines[page->num_lines - 1];
      /* Historical note: The former distinction between lines with and without
       * words[first_word]->nowrap set is no longer necessary, since 
       * Dw_page_real_word_wrap sets max_word_min to the correct value in any
       * case. */
      extremes->min_width = line->max_word_min;
      extremes->max_width = MAX (line->max_par_max, page->last_line_par_max);
      DBG_MSG (widget, "extremes", 0, "simple case");
   } else {
      /* Calculate the extremes, based on the values in the line from
         where a rewrap is necessary. */
      DBG_MSG (widget, "extremes", 0, "complex case");

      if (page->wrap_ref == 0) {
         extremes->min_width = 0;
         extremes->max_width = 0;
         par_min = 0;
         par_max = 0;
      } else {
         line = &page->lines[page->wrap_ref];
         extremes->min_width = line->max_word_min;
         extremes->max_width = line->max_par_max;
         par_min = line->par_min;
         par_max = line->par_max;

         DBG_MSGF (widget, "extremes", 0, "par_min = %d", par_min);
      }

      _MSG ("*** par_min = %d\n", par_min);

      for (line_index = page->wrap_ref; line_index < page->num_lines;
           line_index++) {

         DBG_MSGF (widget, "extremes", 0, "line %d", line_index);
         DBG_START (widget);

         line = &page->lines[line_index];
         nowrap = page->words[line->first_word].style->white_space
            != DW_STYLE_WHITE_SPACE_NORMAL;

         DEBUG_MSG (DEBUG_SIZE_LEVEL, "   line %d (of %d), nowrap = %d\n",
                    line_index, page->num_lines, nowrap);

         for (word_index = line->first_word; word_index < line->last_word;
              word_index++) {
            word = &page->words[word_index];
            Dw_page_get_word_extremes (word, &word_extremes);

            /* For the first word, we simply add the line1_offset. */
            if (!page->ignore_line1_offset_sometimes && word_index == 0) {
               word_extremes.min_width += page->line1_offset;
               DEBUG_MSG (DEBUG_SIZE_LEVEL + 1,
                          "      (next plus %d)\n", page->line1_offset);
            }

            if (nowrap) {
               par_min += word_extremes.min_width + word->orig_space;
               DBG_MSGF (widget, "extremes", 0, "par_min = %d", par_min);
            } else
               if (extremes->min_width < word_extremes.min_width)
                  extremes->min_width = word_extremes.min_width;

            par_max += word_extremes.max_width + word->orig_space;

            DEBUG_MSG (DEBUG_SIZE_LEVEL + 1,
                       "      word %s: max_width = %d\n",
                       a_Dw_content_text (&word->content),
                       word_extremes.max_width);
         }

         if ( ( line->last_word > line->first_word &&
                page->words[line->last_word - 1].content.type
                == DW_CONTENT_BREAK ) ||
              line_index == page->num_lines - 1 ) {
            word = &page->words[line->last_word - 1];
            par_max -= word->orig_space;

            DEBUG_MSG (DEBUG_SIZE_LEVEL + 2,
                       "   par_max = %d, after word %d (%s)\n",
                       par_max, line->last_word - 1,
                       a_Dw_content_text (&word->content));

            if (extremes->max_width < par_max)
               extremes->max_width = par_max;

            if (nowrap) {
               par_min -= word->orig_space;
               DBG_MSGF (widget, "extremes", 0, "par_min = %d", par_min);
               if (extremes->min_width < par_min)
                  extremes->min_width = par_min;

               DEBUG_MSG (DEBUG_SIZE_LEVEL + 2,
                          "   par_min = %d, after word %d (%s)\n",
                          par_min, line->last_word - 1,
                          a_Dw_content_text (&word->content));
            }

            par_min = 0;
            par_max = 0;
         }

         DBG_END (widget);
      }

      DEBUG_MSG (DEBUG_SIZE_LEVEL + 3, "   Result: %d, %d\n",
                 extremes->min_width, extremes->max_width);
   }

   DBG_MSGF (widget, "extremes", 0, "width difference: %d + %d",
             page->inner_padding, p_Dw_style_box_diff_width (widget->style));

   extremes->min_width +=
      page->inner_padding + p_Dw_style_box_diff_width (widget->style);
   extremes->max_width +=
      page->inner_padding + p_Dw_style_box_diff_width (widget->style);

   DBG_END (widget);
}


/*
 * Standard Dw function
 */
static void Dw_page_size_allocate (DwWidget *widget,
                                   DwAllocation *allocation)
{
   DwPage *page;
   int line_index, word_index;
   DwPageLine *line;
   DwPageWord *word;
   int x_cursor;
   DwAllocation child_allocation;

   page = DW_PAGE (widget);

   for (line_index = 0; line_index < page->num_lines; line_index++) {
      line = &(page->lines[line_index]);
      x_cursor = Dw_page_line_total_x_offset (page, line);

      for (word_index = line->first_word; word_index < line->last_word;
           word_index++) {
         word = &(page->words[word_index]);

         switch (word->content.type) {
         case DW_CONTENT_WIDGET:
            /* todo: justification within the line is done here! */
            child_allocation.x = x_cursor + allocation->x;
            /* align=top:
               child_allocation.y = line->top + allocation->y;
            */
            /* align=bottom (base line) */
            child_allocation.y = allocation->y +
               Dw_page_line_total_y_offset_allocation (page, line, allocation)
               + (line->ascent - word->size.ascent)
               - word->content.data.widget->style->margin.top;
            child_allocation.width = word->size.width;
            child_allocation.ascent = word->size.ascent +
               word->content.data.widget->style->margin.top;
            child_allocation.descent = word->size.descent +
               word->content.data.widget->style->margin.bottom;
            a_Dw_widget_size_allocate (word->content.data.widget,
                                       &child_allocation);
            break;
         case DW_CONTENT_ANCHOR:
            p_Dw_gtk_viewport_put_anchor
               (widget, word->content.data.anchor,
                Dw_page_line_total_y_offset_allocation (page, line,
                                                        allocation));
         default:
            /* make compiler happy */
            break;
         }

         x_cursor += (word->size.width + word->eff_space);
      }
   }
}


/*
 * Implementation for both mark_size_change and mark_extremes_change.
 */
static void Dw_page_mark_change (DwWidget *widget,
                                 gint ref)
{
   DwPage *page;

   if (ref != -1) {
      page = DW_PAGE (widget);

      DEBUG_MSG(DEBUG_REWRAP_LEVEL + 1,
                "Dw_page_mark_size_change (ref = %d): page->wrap_ref = %d -> ",
                ref, page->wrap_ref);

      if (page->wrap_ref == -1)
         page->wrap_ref = ref;
      else
         page->wrap_ref = MIN (page->wrap_ref, ref);

      DEBUG_MSG(DEBUG_REWRAP_LEVEL + 1, "%d, in page with %d word(s)\n",
                page->wrap_ref, page->num_words);
   }
}

/*
 * Standard Dw function
 */
static void Dw_page_set_width (DwWidget *widget,
                               gint32 width)
{
   DwPage *page;

   page = DW_PAGE (widget);

   /* If limit_text_width is set to YES, a queue_resize may also be
      necessary. */
   if (page->avail_width != width || prefs.limit_text_width) {
      DEBUG_MSG(DEBUG_REWRAP_LEVEL,
                "Dw_page_set_width: Calling p_Dw_widget_queue_resize, "
                "in page with %d word(s)\n",
                page->num_words);

      page->avail_width = width;
      p_Dw_widget_queue_resize (widget, 0, FALSE);
      page->must_queue_resize = FALSE;
   }
}


/*
 * Standard Dw function
 */
static void Dw_page_set_ascent (DwWidget *widget,
                                gint32 ascent)
{
   DwPage *page;

   page = DW_PAGE (widget);

   if (page->avail_ascent != ascent) {
      DEBUG_MSG(DEBUG_REWRAP_LEVEL,
                "Dw_page_set_ascent: Calling p_Dw_widget_queue_resize, "
                "in page with %d word(s)\n",
                page->num_words);

      page->avail_ascent = ascent;
      p_Dw_widget_queue_resize (widget, 0, FALSE);
      page->must_queue_resize = FALSE;
   }
}


/*
 * Standard Dw function
 */
static void Dw_page_set_descent (DwWidget *widget,
                                 gint32 descent)
{
   DwPage *page;

   page = DW_PAGE (widget);

   if (page->avail_descent != descent) {
      DEBUG_MSG(DEBUG_REWRAP_LEVEL,
                "Dw_page_set_descent: Calling p_Dw_widget_queue_resize, "
                "in page with %d word(s)\n",
                page->num_words);

      page->avail_descent = descent;
      p_Dw_widget_queue_resize (widget, 0, FALSE);
      page->must_queue_resize = FALSE;
   }
}


/*
 * Standard Dw function
 */
static void Dw_page_add (DwContainer *container,
                         DwWidget *widget)
{
   /* todo */
}


/*
 * Standard Dw function
 */
static void Dw_page_remove (DwContainer *container,
                            DwWidget *widget)
{
   /* todo */
}


/*
 * Standard Dw function
 */
static void Dw_page_forall (DwContainer *container,
                            DwCallback callback,
                            gpointer callback_data)
{
   DwPage *page;
   int word_index;
   DwPageWord *word;

   page = DW_PAGE (container);

   for (word_index = 0; word_index < page->num_words; word_index++) {
      word = &page->words[word_index];

      if (word->content.type == DW_CONTENT_WIDGET)
         (*callback) (word->content.data.widget, callback_data);
   }
}


/*
 * ...
 *
 * avail_line is passed from Dw_page_real_word_wrap, to avoid calculating
 * it twice.
 */
static void Dw_page_justify_line (DwPage *page, DwPageLine *line,
                                  gint32 avail_width)
{
   /* To avoid rounding errors, the calculation is based on accumulated
    * values (*_cum). */
   gint i;
   gint32 orig_space_sum, orig_space_cum;
   gint32 eff_space_diff_cum, last_eff_space_diff_cum;
   gint32 diff;

   diff = avail_width - page->last_line_width;
   if (diff > 0) {
      orig_space_sum = 0;
      for (i = line->first_word; i < line->last_word - 1; i++)
         orig_space_sum += page->words[i].orig_space;
      
      orig_space_cum = 0;
      last_eff_space_diff_cum = 0;
      for (i = line->first_word; i < line->last_word - 1; i++) {
         orig_space_cum += page->words[i].orig_space;
         
         if (orig_space_cum == 0)
            eff_space_diff_cum = last_eff_space_diff_cum;
         else
            eff_space_diff_cum = diff * orig_space_cum / orig_space_sum;
         
         page->words[i].eff_space = page->words[i].orig_space +
            (eff_space_diff_cum - last_eff_space_diff_cum);
         
         last_eff_space_diff_cum = eff_space_diff_cum;
      }
   }
}

/*
 * ...
 */
static void Dw_page_add_line (DwPage *page, gint word_ind, gboolean new_par)
{
   DwPageLine *last_line, *plast_line;

   page->num_lines++;
   a_List_add (page->lines, page->num_lines, page->num_lines_max);

   DEBUG_MSG (DEBUG_REWRAP_LEVEL, "--- new line %d in %p, with word %d of %d"
              "\n", page->num_lines - 1, page, word_ind, page->num_words);
   
   last_line = &page->lines[page->num_lines - 1];
   
   if (page->num_lines == 1)
      plast_line = NULL;
   else
      plast_line = &page->lines[page->num_lines - 2];
   
   if (plast_line) {
      /* second or more lines: copy values of last line */
      last_line->top =
         plast_line->top + plast_line->ascent +
         plast_line->descent + plast_line->break_space;
      last_line->max_line_width = plast_line->max_line_width;
      last_line->max_word_min = plast_line->max_word_min;
      last_line->max_par_max = plast_line->max_par_max;
      last_line->par_min = plast_line->par_min;
      last_line->par_max = plast_line->par_max;
   } else {
      /* first line: initialize values */
      last_line->top = 0;
      last_line->max_line_width = page->line1_offset_eff;
      last_line->max_word_min = 0;
      last_line->max_par_max = 0;
      last_line->par_min =  page->line1_offset_eff;
      last_line->par_max =  page->line1_offset_eff;
   }

   DBG_ARRSET_NUM (page, "par_min", page->num_lines - 1, last_line->par_min);
   DBG_ARRSET_NUM (page, "par_max", page->num_lines - 1, last_line->par_max);
   DBG_ARRSET_NUM (page, "max_word_min", page->num_lines - 1,
                   last_line->max_word_min);
   
   last_line->first_word = word_ind;
   last_line->ascent = 0;
   last_line->descent = 0;
   last_line->margin_descent = 0;
   last_line->break_space = 0;
   last_line->left_offset = 0;
   
   /* update values in line */
   last_line->max_line_width =
      MAX (last_line->max_line_width, page->last_line_width);
   
   if (page->num_lines > 1)
      page->last_line_width = 0;
   else
      page->last_line_width = page->line1_offset_eff;

   if (new_par) {
      last_line->max_par_max =
         MAX (last_line->max_par_max, page->last_line_par_max);
      if (page->num_lines > 1) {
         last_line->par_min = 0;
         last_line->par_max = 0;
      } else {
         last_line->par_min = page->line1_offset_eff;
         last_line->par_max = page->line1_offset_eff;
      }
      page->last_line_par_min = 0;
      page->last_line_par_max = 0;

      DBG_SET_NUM(page, "last_line_par_min", page->last_line_par_min);
      DBG_SET_NUM(page, "last_line_par_max", page->last_line_par_max);
   }

   last_line->par_min = page->last_line_par_min;
   last_line->par_max = page->last_line_par_max;

   DBG_ARRSET_NUM (page, "par_min", page->num_lines - 1, last_line->par_min);
   DBG_ARRSET_NUM (page, "par_max", page->num_lines - 1, last_line->par_max);
}


/*
 * This function is called in two cases: (i) when a word is added (by
 * Dw_page_add_word), and (ii) when a page has to be (partially)
 * rewrapped. It does word wrap, and adds new lines, if necesary.
 */
static void Dw_page_real_word_wrap (DwPage *page, gint word_ind)
{
   DwPageLine *last_line;
   DwPageWord *word, *prev_word;
   gint32 avail_width, last_space, left_offset;
   gboolean new_line = FALSE, new_par = FALSE;
   DwWidget *widget = DW_WIDGET (page);
   DwExtremes word_extremes;

   DBG_MSGF (page, "wrap", 0, "word_wrap (%d): %s, width = %d",
             word_ind, a_Dw_content_html (&page->words[word_ind].content),
             page->words[word_ind].size.width);
   DBG_START (page);

   avail_width =
      page->avail_width - p_Dw_style_box_diff_width (widget->style)
      - page->inner_padding;
   if (prefs.limit_text_width &&
       avail_width > widget->viewport->allocation.width - 10)
      avail_width = widget->viewport->allocation.width - 10;

   word = &page->words[word_ind];

   if (page->num_lines == 0) {
      DBG_MSG (page, "wrap", 0, "first line");
      new_line = TRUE;
      new_par = TRUE;
      last_line = NULL;
   } else {
      last_line = &page->lines[page->num_lines - 1];

      if (page->num_words > 0) {
         prev_word = &page->words[word_ind - 1];
         if (prev_word->content.type == DW_CONTENT_BREAK) {
            DBG_MSG (page, "wrap", 0, "after a break");
            /* previous word is a break */
            new_line = TRUE;
            new_par = TRUE;
         } else if (word->style->white_space != DW_STYLE_WHITE_SPACE_NORMAL) {
            DBG_MSGF (page, "wrap", 0, "no wrap (white_space = %d)",
                      word->style->white_space);
            new_line = FALSE;
            new_par = FALSE;
         } else {
            if (last_line->first_word != word_ind) {
               /* Does new word fit into the last line? */
               DBG_MSGF (page, "wrap", 0,
                         "word %d (%s) fits? (%d + %d + %d &lt;= %d)...",
                         word_ind, a_Dw_content_html (&word->content),
                         page->last_line_width, prev_word->orig_space,
                         word->size.width, avail_width);
               new_line = (page->last_line_width + prev_word->orig_space
                           + word->size.width > avail_width);
               DBG_MSGF (page, "wrap", 0, "... %s.",
                         new_line ? "No" : "Yes");
            }
         }
      }
   }

   /* Has sometimes the wrong value. */
   word->eff_space = word->orig_space;

   /* Test, whether line1_offset can be used. */
   if (word_ind == 0) {
      if (page->ignore_line1_offset_sometimes) {
         if (page->line1_offset + word->size.width > avail_width)
            page->line1_offset_eff = 0;
         else
            page->line1_offset_eff = page->line1_offset;
      } else
         page->line1_offset_eff = page->line1_offset;
   }

   if (last_line != NULL && new_line && !new_par &&
       word->style->text_align == DW_STYLE_TEXT_ALIGN_JUSTIFY)
      Dw_page_justify_line (page, last_line, avail_width);

   if (new_line) {
      Dw_page_add_line (page, word_ind, new_par);
      last_line = &page->lines[page->num_lines - 1];
   }

   last_line->last_word = word_ind + 1;
   last_line->ascent = MAX (last_line->ascent, word->size.ascent);
   last_line->descent = MAX (last_line->descent, word->size.descent);

   if (word->content.type == DW_CONTENT_WIDGET) {
      last_line->margin_descent =
         MAX (last_line->margin_descent, 
              word->size.descent +
              word->content.data.widget->style->margin.bottom);
      
      /* If the widget is not in the first line of the paragraph, its top
       * margin may make the line higher.
       */
      if (page->num_lines > 1) {
         /* Here, we know already what the break and the bottom margin
          * contributed to the space before this line.
          */
         last_line->ascent =
            MAX (last_line->ascent,
                 word->size.ascent
                 + word->content.data.widget->style->margin.top);
      }
   } else
      last_line->margin_descent =
         MAX (last_line->margin_descent, last_line->descent);

   Dw_page_get_word_extremes (word, &word_extremes);
   last_space = (word_ind > 0) ? page->words[word_ind - 1].orig_space : 0;

   if (word->content.type == DW_CONTENT_BREAK)
      last_line->break_space =
         MAX3 (word->content.data.break_space,
               last_line->margin_descent - last_line->descent,
               last_line->break_space);

   page->last_line_width += word->size.width;
   if (!new_line)
      page->last_line_width += last_space;

   page->last_line_par_min += word_extremes.max_width;
   page->last_line_par_max += word_extremes.max_width;
   if (!new_par) {
      page->last_line_par_min += last_space;
      page->last_line_par_max += last_space;
   }

   if (word->style->white_space != DW_STYLE_WHITE_SPACE_NORMAL) {
      last_line->par_min += word_extremes.min_width + last_space;
      /* This may also increase the accumulated minimum word width.  */
      last_line->max_word_min =
         MAX (last_line->max_word_min, last_line->par_min);
      /* NOTE: Most code relies on that all values of nowrap are equal for all
       * words within one line. */
   } else
      /* Simple case. */
      last_line->max_word_min =
         MAX (last_line->max_word_min, word_extremes.min_width);

   DBG_SET_NUM(page, "last_line_par_min", page->last_line_par_min);
   DBG_SET_NUM(page, "last_line_par_max", page->last_line_par_max);
   DBG_ARRSET_NUM (page, "par_min", page->num_lines - 1, last_line->par_min);
   DBG_ARRSET_NUM (page, "par_max", page->num_lines - 1, last_line->par_max);
   DBG_ARRSET_NUM (page, "max_word_min", page->num_lines - 1,
                   last_line->max_word_min);

   /* Finally, justify the line. Breaks are ignored, since the HTML
    * parser sometimes assignes the wrong style to them. (todo: ) */
   if (word->content.type != DW_CONTENT_BREAK) {
      switch (word->style->text_align) {
      case DW_STYLE_TEXT_ALIGN_LEFT:
      case DW_STYLE_TEXT_ALIGN_JUSTIFY:  /* see some lines above */
      case DW_STYLE_TEXT_ALIGN_STRING:   /* handled elsewhere (in future) */
         left_offset = 0;
         break;

      case DW_STYLE_TEXT_ALIGN_RIGHT:
         left_offset = avail_width - page->last_line_width;
         break;

      case DW_STYLE_TEXT_ALIGN_CENTER:
         left_offset = (avail_width - page->last_line_width) / 2;
         break;

      default:
         /* compiler happiness */
         left_offset = 0;
      }

      /* For large lines (images etc), which do not fit into the viewport: */
      if (left_offset < 0)
         left_offset = 0;

      if (page->list_item && last_line == page->lines) {
         /* List item markers are always on the left. */
         last_line->left_offset = 0;
         page->words[0].eff_space = page->words[0].orig_space + left_offset;
      } else
         last_line->left_offset = left_offset;
   }

   page->must_queue_resize = TRUE;

   DBG_END (page);
}


/*
 * Calculate the size of a widget within the page.
 * (Subject of change in the near future!)
 */
static void Dw_page_calc_widget_size (DwPage *page,
                                      DwWidget *widget,
                                      DwRequisition *size)
{
   DwRequisition requisition;
   gint32 avail_width, avail_ascent, avail_descent;

   /* We ignore line1_offset[_eff]. */
   avail_width =
      page->avail_width - p_Dw_style_box_diff_width (DW_WIDGET(page)->style)
      - page->inner_padding;
   avail_ascent =
      page->avail_ascent - p_Dw_style_box_diff_height (DW_WIDGET(page)->style);
   avail_descent = page->avail_descent;

   if (DW_WIDGET_USES_HINTS (widget)) {
      a_Dw_widget_set_width (widget, avail_width);
      a_Dw_widget_set_ascent (widget, avail_ascent);
      a_Dw_widget_set_descent (widget, avail_descent);
      a_Dw_widget_size_request (widget, size);
      size->ascent -= widget->style->margin.top;
      size->descent -= widget->style->margin.bottom;
   } else {
      /* TODO: Use margin.{top|bottom} here, like above.
       * (No harm for the next future.) */
      if (widget->style->width == DW_STYLE_LENGTH_AUTO ||
          widget->style->height == DW_STYLE_LENGTH_AUTO)
         a_Dw_widget_size_request (widget, &requisition);

      if (widget->style->width == DW_STYLE_LENGTH_AUTO)
         size->width = requisition.width;
      else if (DW_STYLE_IS_ABS_LENGTH (widget->style->width))
         /* Fixed lengths are only applied to the content, so we have to
          * add padding, border and margin. */
         size->width = DW_STYLE_ABS_LENGTH_VAL (widget->style->width)
            + p_Dw_style_box_diff_width (widget->style);
      else
         size->width =
            DW_STYLE_PER_LENGTH_VAL (widget->style->width) * avail_width;

      if (widget->style->height == DW_STYLE_LENGTH_AUTO) {
         size->ascent = requisition.ascent;
         size->descent = requisition.descent;
      } else if (DW_STYLE_IS_ABS_LENGTH (widget->style->height)) {
         /* Fixed lengths are only applied to the content, so we have to
          * add padding, border and margin. */
         size->ascent =
            DW_STYLE_ABS_LENGTH_VAL (widget->style->height)
            + p_Dw_style_box_diff_height (widget->style);
         size->descent = 0;
      } else {
         size->ascent =
            DW_STYLE_PER_LENGTH_VAL (widget->style->height) * avail_ascent;
         size->descent =
            DW_STYLE_PER_LENGTH_VAL (widget->style->height) * avail_descent;
      }
   }
}


/*
 * Rewrap the page from the line from which this is necessary.
 * There are basically two times we'll want to do this:
 * either when the viewport is resized, or when the size changes on one
 * of the child widgets.
 */
static void Dw_page_rewrap (DwPage *page)
{
   DwWidget *widget;
   gint i, word_index;
   DwPageWord *word;
   DwPageLine *last_line;

   if (page->wrap_ref == -1)
      /* page does not have to be rewrapped */
      return;

   widget = DW_WIDGET (page);

   DEBUG_MSG(DEBUG_REWRAP_LEVEL,
             "Dw_page_rewrap: page->wrap_ref = %d, in page with %d word(s)\n",
             page->wrap_ref, page->num_words);

   /* All lines up from page->wrap_ref will be rebuild from the word list,
    * the line list up from this position is rebuild. */
   page->num_lines = page->wrap_ref;
   page->last_line_width = 0;

   /* In the word list, we start at the last word, plus one (see definition
    * of last_word), in the line before. */
   if (page->wrap_ref > 0) {
      /* Note: In this case, Dw_page_real_word_wrap will immediately find
       * the need to rewrap the line, since we start with the last one (plus
       * one). This is also the reason, why page->last_line_width is set
       * to the length of the line. */
      last_line = &page->lines[page->num_lines - 1];
      word_index = last_line->last_word;
      for (i = last_line->first_word; i < last_line->last_word - 1; i++)
         page->last_line_width += (page->words[i].size.width +
                                   page->words[i].orig_space);
      page->last_line_width +=
         page->words[last_line->last_word - 1].size.width;
   } else
      word_index = 0;

   for (; word_index < page->num_words; word_index++) {
      word = &page->words[word_index];

      if (word->content.type == DW_CONTENT_WIDGET)
         Dw_page_calc_widget_size (page, word->content.data.widget,
                                   &word->size);
      Dw_page_word_wrap (page, word_index);

      if (word->content.type == DW_CONTENT_WIDGET)
         word->content.data.widget->parent_ref = page->num_lines - 1;

      DEBUG_MSG(DEBUG_REWRAP_LEVEL,
                "Assigning parent_ref = %d to rewrapped word %d, "
                "in page with %d word(s)\n",
                page->num_lines - 1, word_index, page->num_words);

      if ( word->content.type == DW_CONTENT_ANCHOR )
         p_Dw_gtk_viewport_put_anchor
            (widget, word->content.data.anchor,
             Dw_page_line_total_y_offset (page,
                                          &page->lines[page->num_lines - 1]));
   }

   /* Next time, the page will not have to be rewrapped. */
   page->wrap_ref = -1;
}

/*
 * Paint a line
 * - x and y are toplevel dw coordinates (Question: what Dw? Changed. Test!)
 * - area is used always (ev. set it to event->area)
 * - event is only used when is_expose
 */
static void Dw_page_draw_line (DwPage *page,
                               DwPageLine *line,
                               DwRectangle *area,
                               GdkEventExpose *event)
{
   DwWidget *widget;
   DwPageWord *word;
   gint word_index;
   gint32 x_widget, y_widget, x_viewport, y_viewport, y_viewport_base;
   gint32 start_hl, width_hl;
   gint32 diff, word_len, eff_hl_end, layer;
   DwWidget *child;
   DwRectangle child_area;
   GdkWindow *window;
   GdkGC *gc, *hl_gc;
   DwStyleColor *page_bg_color, *word_bg_color;

   /* Here's an idea on how to optimize this routine to minimize the number
    * of calls to gdk_draw_string:
    *
    * Copy the text from the words into a buffer, adding a new word
    * only if: the attributes match, and the spacing is either zero or
    * equal to the width of ' '. In the latter case, copy a " " into
    * the buffer. Then draw the buffer. */

   widget = DW_WIDGET (page);
   window = DW_WIDGET_WINDOW (widget);
   page_bg_color = p_Dw_widget_get_bg_color (widget);

   x_widget = Dw_page_line_total_x_offset(page,line);
   x_viewport =
      p_Dw_widget_x_world_to_viewport (widget,
                                       widget->allocation.x + x_widget);
   y_widget = Dw_page_line_total_y_offset(page,line);
   y_viewport =
      p_Dw_widget_y_world_to_viewport (widget,
                                       widget->allocation.y + y_widget);
   y_viewport_base = y_viewport + line->ascent;

   for (word_index = line->first_word; word_index < line->last_word;
        word_index++) {
      word = &page->words[word_index];
      diff = 0;
      gc = word->style->color->gc;

      switch (word->content.type) {
      case DW_CONTENT_TEXT:
         if (word->style->background_color)
            word_bg_color = word->style->background_color;
         else
            word_bg_color = page_bg_color;

         /* Adjust the text baseline if the word is <SUP>-ed or <SUB>-ed. */
         if (word->style->valign == DW_STYLE_VALIGN_SUB)
            diff = word->size.ascent / 2;
         else if (word->style->valign == DW_STYLE_VALIGN_SUPER)
            diff -= word->size.ascent / 3;

         /* Draw background (color, image), when given. */
         /* todo: Test (word->style->background_color) is incomplete, and
          * should in future include background images. */
         if (word->style->background_color &&
             word->size.width > 0)
            p_Dw_widget_draw_box (widget, word->style, area,
                                  x_widget,
                                  y_widget + line->ascent - word->size.ascent,
                                  word->size.width,
                                  word->size.ascent + word->size.descent,
                                  FALSE);

         /* Draw space background (color, image), when given. */
         /* todo: Test (word->space_style->background_color) is incomplete, and
          * should in future include background images. */
         if (word->space_style->background_color &&
             word->eff_space > 0)
            p_Dw_widget_draw_box (widget, word->space_style, area,
                                  x_widget + word->size.width,
                                  y_widget + line->ascent - word->size.ascent,
                                  word->eff_space,
                                  word->size.ascent + word->size.descent,
                                  FALSE);

         gdk_draw_string (window, word->style->font->font, gc,
                          x_viewport, y_viewport_base + diff,
                          word->content.data.text);

         /* underline */
         if (word->style->text_decoration & DW_STYLE_TEXT_DECORATION_UNDERLINE)
            gdk_draw_line (window, gc,
                           x_viewport, y_viewport_base + 1 + diff,
                           x_viewport + word->size.width - 1,
                           y_viewport_base + 1 + diff);
         if (word_index + 1 < line->last_word &&
             (word->space_style->text_decoration
              & DW_STYLE_TEXT_DECORATION_UNDERLINE))
            gdk_draw_line (window, gc,
                           x_viewport + word->size.width,
                           y_viewport_base + 1 + diff,
                           x_viewport + word->size.width + word->eff_space - 1,
                           y_viewport_base + 1 + diff);

         /* strike-through */
         if (word->style->text_decoration
             & DW_STYLE_TEXT_DECORATION_LINE_THROUGH)
            gdk_draw_line (window, gc,
                           x_viewport,
                           y_viewport_base - word->size.ascent / 2 + diff,
                           x_viewport + word->size.width - 1,
                           y_viewport_base - word->size.ascent / 2 + diff);
         if (word_index + 1 < line->last_word &&
             (word->space_style->text_decoration
              & DW_STYLE_TEXT_DECORATION_LINE_THROUGH))
            gdk_draw_line (window, gc,
                           x_viewport + word->size.width,
                           y_viewport_base - word->size.ascent / 2 + diff,
                           x_viewport + word->size.width + word->eff_space - 1,
                           y_viewport_base - word->size.ascent / 2 + diff);

         for (layer = 0; layer < DW_HIGHLIGHT_NUM_LAYERS; layer++) {
            if (word->hl_start[layer] != -1) {
               word_len = strlen (word->content.data.text);
               eff_hl_end = MIN (word_len, word->hl_end[layer]);
               start_hl = x_viewport +
                  gdk_text_width (word->style->font->font,
                                  word->content.data.text,
                                  word->hl_start[layer]);
               width_hl =
                  gdk_text_width (word->style->font->font,
                                  word->content.data.text
                                  + word->hl_start[layer],
                                  eff_hl_end - word->hl_start[layer]);

               /* If the space after this word highlighted, and this word
                * is not the last one in this line, highlight also the
                * space. */
               /* todo: This should also be done with spaces after non-text
                * words, but this is not yet defined very well. */
               if (word->hl_end[layer] > eff_hl_end &&
                   word_index < page->num_words &&
                   word_index != line->last_word - 1)
                  width_hl += word->eff_space;


               if (width_hl != 0) {
                  /* Draw background for highlighted text. */
                  gdk_draw_rectangle (window, word_bg_color->inverse_gc,
                                      TRUE, start_hl,
                                      y_viewport_base - word->size.ascent,
                                      width_hl,
                                      word->size.ascent + word->size.descent);

                  /* Highlight the text. */
                  hl_gc = word->style->color->inverse_gc;
                  gdk_draw_text (window, word->style->font->font, hl_gc,
                                 start_hl, y_viewport_base + diff,
                                 word->content.data.text
                                 + word->hl_start[layer],
                                 eff_hl_end - word->hl_start[layer]);

                  /* underline and strike-through */
                  if (word->style->text_decoration
                      & DW_STYLE_TEXT_DECORATION_UNDERLINE)
                     gdk_draw_line (window, hl_gc,
                                    start_hl, y_viewport_base + 1 + diff,
                                    start_hl + width_hl - 1,
                                    y_viewport_base + 1 + diff);
                  if (word->style->text_decoration
                      & DW_STYLE_TEXT_DECORATION_LINE_THROUGH)
                     gdk_draw_line (window, hl_gc,
                                    start_hl,
                                    y_viewport_base
                                    - word->size.ascent / 2 + diff,
                                    start_hl + width_hl - 1,
                                    y_viewport_base
                                    - word->size.ascent / 2 + diff);
               }
            }
         }
         break;

      case DW_CONTENT_WIDGET:
         child = word->content.data.widget;
         if (p_Dw_widget_intersect (child, area, &child_area))
            a_Dw_widget_draw (child, &child_area, event);
         break;

      case DW_CONTENT_ANCHOR: case DW_CONTENT_BREAK:
         /* nothing - an anchor/break isn't seen */
         /* BUG: sometimes anchors have x_space;
          * we subtract that just in case --EG */
         x_viewport -= word->size.width + word->eff_space;
         x_widget -= word->size.width + word->eff_space;
#if 0
         /* Useful for testing: draw breaks. */
         if (word->content.type == DW_CONTENT_BREAK)
            gdk_draw_rectangle (window, gc, TRUE,
                                p_Dw_widget_x_world_to_viewport (widget,
                                   widget->allocation.x +
                                      Dw_page_line_total_x_offset(page, line)),
                                y_viewport_base + line->descent,
                                DW_WIDGET_CONTENT_WIDTH(widget),
                                word->content.data.break_space);
#endif
         break;

      default:
         g_warning ("BUG!!! at (%d, %d).", x_viewport, y_viewport_base + diff);
         break;
      }

      x_viewport += word->size.width + word->eff_space;
      x_widget += word->size.width + word->eff_space;
   }
}

/*
 * Find the first line index that includes y, relative to top of widget.
 */
static gint Dw_page_find_line_index (DwPage *page, gint y)
{
   gint max_index = page->num_lines - 1;
   gint step, index, low = 0;

   step = (page->num_lines+1) >> 1;
   while ( step > 1 ) {
      index = low + step;
      if (index <= max_index &&
          Dw_page_line_total_y_offset_i (page, index) < y)
         low = index;
      step = (step + 1) >> 1;
   }

   if (low < max_index && Dw_page_line_total_y_offset_i (page, low + 1) < y)
      low++;

   /*
    * This new routine returns the line number between (top) and
    * (top + size.ascent + size.descent + break_space): the space
    * _below_ the line is considered part of the line.  Old routine
    * returned line number between (top - previous_line->break_space)
    * and (top + size.ascent + size.descent): the space _above_ the
    * line was considered part of the line.  This is important for
    * Dw_page_find_link() --EG
    */
   return low;
}

/*
 * Find the line of word <word_index>.
 */
static gint Dw_page_find_line_of_word (DwPage *page, gint word_index)
{
   gint high = page->num_lines - 1, index, low = 0;

   g_return_val_if_fail (word_index >= 0, -1);
   g_return_val_if_fail (word_index < page->num_words, -1);

   while (TRUE) {
      index = (low + high) / 2;
      if (word_index >= page->lines[index].first_word) {
         if (word_index < page->lines[index].last_word)
            return index;
         else
            low = index + 1;
      } else
         high = index - 1;
   }
}

/*
 * Draw the actual lines, starting at (x, y) in toplevel Dw coords.
 * (former Dw_page_expose_lines)
 */
static void Dw_page_draw (DwWidget *widget,
                          DwRectangle *area,
                          GdkEventExpose *event)
{
   DwPage *page;
   gint line_index;
   DwPageLine *line;

   p_Dw_widget_draw_widget_box (widget, area, FALSE);

   page = DW_PAGE (widget);
   line_index = Dw_page_find_line_index (page, area->y);

   for (; line_index < page->num_lines; line_index++) {
      line = &(page->lines[line_index]);
      if (Dw_page_line_total_y_offset(page, line) >= area->y + area->height)
         break;

      Dw_page_draw_line (page, line, area, event);
   }
}


/*
 * Find the index of the word, or -1.
 */
static gint Dw_page_find_word (DwPage *page, gint x, gint y)
{
   gint line_index, word_index;
   gint x_cursor, last_x_cursor;
   DwPageLine *line;
   DwPageWord *word;

   if ( (line_index = Dw_page_find_line_index (page, y)) >= page->num_lines )
      return -1;
   line = &page->lines[line_index];
   if (Dw_page_line_total_y_offset(page, line) + line->ascent + line->descent
       <= y)
      return -1;

   x_cursor = Dw_page_line_total_x_offset (page, line);
   for (word_index = line->first_word; word_index < line->last_word;
        word_index++) {
      word = &page->words[word_index];
      last_x_cursor = x_cursor;
      x_cursor += word->size.width + word->eff_space;
      if (last_x_cursor <= x && x_cursor > x)
         return word_index;
   }
   return -1;
}

/*
 * Find a link given a coordinate location relative to the window
 */
static gint Dw_page_find_link (DwPage *page, gint x, gint y)
{
   gint word_index;

   if ((word_index = Dw_page_find_word (page, x, y)) == -1)
      return -1;
   else
      return page->words[word_index].style->x_link;
}

/*
 * Construct an iterator for a word.
 */
static DwIterator *Dw_page_construct_iterator (DwPage *page, gint word_index)
{
   DwIterator *it;

   it = Dw_page_iterator (DW_WIDGET (page), DW_CONTENT_ALL, FALSE);
   //DW_CONTENT_TEXT | DW_CONTENT_WIDGET, FALSE);
   ((DwIteratorInt*)it)->pos = word_index;
   it->content = page->words[word_index].content;
   return it;
}

/*
 * Send event to selection.
 */
static gboolean Dw_page_send_selection_event (DwPage *page,
                                              gint (*fn) (Selection*,
                                                          DwIterator *,
                                                          gint, gint,
                                                          GdkEventButton*,
                                                          gboolean
                                                             within_content),
                                              gint32 x, gint32 y,
                                              GdkEventButton *event)
{
   DwIterator *it;
   DwPageLine *line, *last_line;
   gint32 next_word_start_x, word_start_x, word_x, next_word_x, yfirst, ylast;
   gint char_pos = 0, word_index, line_index, link;
   DwPageWord *word;
   gboolean found;
   gboolean within_content;

   DEBUG_MSG (DEBUG_EVENT_LEVEL,
              "Dw_page_send_selection_event: x = %d, y = %d\n", x, y);

   if (page->num_words == 0) {
      DEBUG_MSG (DEBUG_EVENT_LEVEL, "  no words\n");
      return FALSE;
   }

   /* In most cases true, so set here: */
   link = -1;
   within_content = TRUE;

   last_line = &page->lines[page->num_lines - 1];
   yfirst = Dw_page_line_total_y_offset_i (page, 0);
   ylast =
      Dw_page_line_total_y_offset (page, last_line) +
      last_line->ascent + last_line->descent;
   if (y < yfirst) {
      /* Above the first line: take the first word. */
      DEBUG_MSG (DEBUG_EVENT_LEVEL, "  above first line (at %d)\n", yfirst);
      within_content = FALSE;
      word_index = 0;
      char_pos = 0;
   } else if (y >= ylast) {
      /* Below the last line: take the last word. */
      DEBUG_MSG (DEBUG_EVENT_LEVEL, "  below last line (at %d)\n", ylast);
      within_content = FALSE;
      word_index = page->num_words - 1;
      word = &page->words[word_index];
      char_pos = word->content.type == DW_CONTENT_TEXT ?
         strlen (word->content.data.text) : 0;
   } else {
      line_index = Dw_page_find_line_index (page, y);
      line = &page->lines[line_index];
      DEBUG_MSG (DEBUG_EVENT_LEVEL, "  in line %d\n", line_index);

      /* Pointer within the break space? */
      if (y > (Dw_page_line_total_y_offset(page, line) + line->ascent +
               line->descent)) {
         /* Choose this break. */
         DEBUG_MSG (DEBUG_EVENT_LEVEL, "    break\n");
         within_content = FALSE;
         word_index = line->last_word - 1;
         char_pos = 0;
      } else if (x < Dw_page_line_total_x_offset (page, line)) {
         /* Left of the first word in the line. */
         DEBUG_MSG (DEBUG_EVENT_LEVEL, "    left of this line\n");
         word_index = line->first_word;
         within_content = FALSE;
         char_pos = 0;
      } else {
         next_word_start_x = Dw_page_line_total_x_offset (page, line);
         found = FALSE;
         for (word_index = line->first_word;
              !found && word_index < line->last_word;
              word_index++) {
            word = &page->words[word_index];
            word_start_x = next_word_start_x;
            next_word_start_x += word->size.width + word->eff_space;
            DEBUG_MSG (DEBUG_EVENT_LEVEL,
                       "    word %d (%s) from %d to %d, delta = %d + %d\n",
                       word_index, a_Dw_content_text (&word->content),
                       word_start_x, next_word_start_x,
                       word->size.width, word->eff_space);
            if (x >= word_start_x && x < next_word_start_x) {
               DEBUG_MSG (DEBUG_EVENT_LEVEL,
                          "    found word %d (%s)\n",
                          word_index, a_Dw_content_text (&word->content));

               /* We have found the word. */
               if (word->content.type == DW_CONTENT_TEXT) {
                  /* Search the character the mouse pointer is in.
                   * next_word_x is the right side of this character. */
                  char_pos = 0;
                  while ((next_word_x = word_start_x +
                          gdk_text_width(word->style->font->font,
                                         word->content.data.text,
                                         char_pos))
                         <= x)
                     char_pos++;

                  /* The left side of this character. */
                  word_x =
                     word_start_x + gdk_text_width (word->style->font->font,
                                                    word->content.data.text,
                                                    char_pos - 1);

                  /* If the mouse pointer is left from the middle, use the left
                   * position, otherwise, use the right one. */
                  if (x <= (word_x + next_word_x) / 2)
                     char_pos--;
               } else {
                  /* Depends on whether the pointer is within the left or
                   * right half of the (non-text) word. */
                  if (x >= (word_start_x + next_word_start_x) / 2)
                     char_pos = SELECTION_EOW;
                  else
                     char_pos = 0;
               }

               found = TRUE;
               link = word->style ? word->style->x_link : -1;
               break;
            }
         }

         if (!found) {
            /* No word found in this line (i.e. we are on the right side),
             * take the last of this line. */
            DEBUG_MSG (DEBUG_EVENT_LEVEL, "    not found\n");
            within_content = FALSE;
            word_index = line->last_word - 1;
            if (word_index >= page->num_words)
               word_index--;
            word = &page->words[word_index];
            char_pos = word->content.type == DW_CONTENT_TEXT ?
               strlen (word->content.data.text) : SELECTION_EOW;
         }
      }
   }

   word = &page->words[word_index];
   it = Dw_page_construct_iterator (page, word_index);
   DEBUG_MSG (DEBUG_EVENT_LEVEL, "-> word %d (of %d), char %d, link %d: %s\n",
              word_index, page->num_words, char_pos, link,
              a_Dw_iterator_text (it));
   return fn (GTK_DW_VIEWPORT(DW_WIDGET(page)->viewport)->selection,
              it, char_pos, link, event, within_content);
}


/*
 * Standard Dw function.
 */
static gboolean Dw_page_button_press  (DwWidget *widget,
                                       gint32 x,
                                       gint32 y,
                                       GdkEventButton *event)
{
   return Dw_page_send_selection_event (DW_PAGE (widget),
                                        a_Selection_button_press, x, y,
                                        event);
}


/*
 * Standard Dw function.
 */
static gboolean Dw_page_button_release (DwWidget *widget,
                                        gint32 x,
                                        gint32 y,
                                        GdkEventButton *event)
{
   return Dw_page_send_selection_event (DW_PAGE (widget),
                                        a_Selection_button_release, x, y,
                                        event);
}


/*
 * Standard Dw function.
 */
static gboolean Dw_page_motion_notify (DwWidget *widget,
                                       gint32 x,
                                       gint32 y,
                                       GdkEventMotion *event)
{
   DwPage *page = DW_PAGE (widget);
   gint link_old;
   gboolean return_val = FALSE;

   if (event && (event->state & GDK_BUTTON1_MASK))
      return Dw_page_send_selection_event (page,
                                           a_Selection_button_motion, x, y,
                                           NULL);
   else {
      /*DEBUG_MSG (DEBUG_EVENT_LEVEL, "Dw_page_motion_notify\n");*/

      link_old = page->hover_link;
      page->hover_link = Dw_page_find_link (page, x, y);

      if (page->hover_link != link_old) {
         gtk_signal_emit (GTK_OBJECT (widget), page_signals[LINK_ENTERED],
                          page->hover_link, -1, -1, &return_val);
         return return_val;
      } else
         return (page->hover_link != -1);
   }
}


/*
 * Standard Dw function.
 */
static gboolean Dw_page_leave_notify (DwWidget *widget,
                                      DwWidget *next_widget,
                                      GdkEventMotion *event)
{
   DwPage *page = DW_PAGE (widget);
   gboolean return_val = FALSE;

   if (page->hover_link != -1) {
      page->hover_link = -1;
      gtk_signal_emit (GTK_OBJECT (widget), page_signals[LINK_ENTERED],
                       -1, -1, -1, &return_val);
      return return_val;
   }

   return FALSE;
}


/*
 * Add a new word (text, widget etc.) to a page.
 */
static DwPageWord *Dw_page_add_word (DwPage *page,
                                     gint width,
                                     gint ascent,
                                     gint descent,
                                     DwStyle *style)
{
   DwPageWord *word;
   int i;

   page->num_words++;
   a_List_add (page->words, page->num_words, page->num_words_max);

   word = &page->words[page->num_words - 1];
   word->size.width = width;
   word->orig_space = 0;
   word->eff_space = 0;
   word->content.space = FALSE;
   word->size.ascent = ascent;
   word->size.descent = descent;
   for (i = 0; i < DW_HIGHLIGHT_NUM_LAYERS; i++) {
      word->hl_start[i] = -1;
      word->hl_end[i] = -1;
   }

   word->style = style;
   word->space_style = style;
   a_Dw_style_ref (style);
   a_Dw_style_ref (style);

   return word;
}

/*
 * Calculate the size of a text word.
 */
static void Dw_page_calc_text_size  (DwPage *page, char *text, DwStyle *style,
                                     DwRequisition *size)
{
   size->width = gdk_string_width (style->font->font, text);
   size->ascent = style->font->font->ascent;
   size->descent = style->font->font->descent;

   /* In case of a sub or super script we increase the word's height and
    * potentially the line's height.
    */
   if (style->valign == DW_STYLE_VALIGN_SUB)
      size->descent += (size->ascent / 2);
   else if (style->valign == DW_STYLE_VALIGN_SUPER)
      size->ascent += (size->ascent / 3);
}


/*
 * Add a word to the page structure. Stashes the argument pointer in
 * the page data structure so that it will be deallocated on destroy.
 */
void a_Dw_page_add_text (DwPage *page, char *text, DwStyle *style)
{
   DwPageWord *word;
   DwRequisition size;

   Dw_page_calc_text_size (page, text, style, &size);
   word = Dw_page_add_word (page, size.width, size.ascent, size.descent,
                            style);
   word->content.type = DW_CONTENT_TEXT;
   word->content.data.text = text;
   Dw_page_word_wrap (page, page->num_words - 1);
}

/*
 * Add a widget (word type) to the page.
 */
void a_Dw_page_add_widget (DwPage *page,
                           DwWidget *widget,
                           DwStyle *style)
{
   DwPageWord *word;
   DwRequisition size;

   /* We first assign -1 as parent_ref, since the call of widget->size_request
    * will otherwise let this DwPage be rewrapped from the beginning.
    * (parent_ref is actually undefined, but likely has the value 0.) At the,
    * end of this function, the correct value is assigned. */
   widget->parent_ref = -1;

   p_Dw_widget_set_parent (widget, DW_WIDGET (page));
   a_Dw_widget_set_style (widget, style);

   Dw_page_calc_widget_size (page, widget, &size);
   word =
      Dw_page_add_word (page, size.width, size.ascent, size.descent, style);

   word->content.type = DW_CONTENT_WIDGET;
   word->content.data.widget = widget;

   Dw_page_word_wrap (page, page->num_words - 1);
   word->content.data.widget->parent_ref = page->num_lines - 1;

   DEBUG_MSG(DEBUG_REWRAP_LEVEL,
             "Assigning parent_ref = %d to added word %d, "
             "in page with %d word(s)\n",
             page->num_lines - 1, page->num_words - 1, page->num_words);
}


/*
 * Add an anchor to the page. name is copied, so no strdup is neccessary for
 * the caller.
 */
void a_Dw_page_add_anchor (DwPage *page, const char *name, DwStyle *style)
{
   DwPageWord *word;

   word = Dw_page_add_word (page, 0, 0, 0, style);
   word->content.type = DW_CONTENT_ANCHOR;
   word->content.data.anchor = g_strdup (name);
   Dw_page_word_wrap (page, page->num_words - 1);

   p_Dw_gtk_viewport_put_anchor
      (DW_WIDGET(page), word->content.data.anchor,
       Dw_page_line_total_y_offset_i (page, page->num_lines - 1));
}


/*
 * ?
 */
void a_Dw_page_add_space (DwPage *page, DwStyle *style)
{
   gint nl, nw;
   gint space;

   nl = page->num_lines - 1;
   if (nl >= 0) {
      nw = page->num_words - 1;
      if (nw >= 0) {
         space = style->font->space_width;
         page->words[nw].orig_space = space;
         page->words[nw].eff_space = space;
         page->words[nw].content.space = TRUE;

         a_Dw_style_unref (page->words[nw].space_style);
         page->words[nw].space_style = style;
         a_Dw_style_ref (style);
      }
   }
}


/*
 * Cause a paragraph break
 */
void a_Dw_page_add_parbreak (DwPage *page, gint space, DwStyle *style)
{
   DwPageWord *word, *word2;
   DwWidget *widget;
   DwPage *page2;
   gboolean isfirst;
   gint lineno;

   /* A break may not be the first word of a page, or directly after
      the bullet/number (which is the first word) in a list item. (See
      also comment in Dw_page_size_request.) */
   if (page->num_words == 0 ||
       (page->list_item && page->num_words == 1)) {
      /* This is a bit hackish: If a break is added as the
         first/second word of a page, and the parent widget is also a
         DwPage, and there is a break before -- this is the case when
         a widget is used as a text box (lists, blockquotes, list
         items etc) -- then we simply adjust the break before, in a
         way that the space is in any case visible. */

      /* Find the widget where to adjust the break_space. */
      for (widget = DW_WIDGET (page);
           widget->parent && DW_IS_PAGE (widget->parent);
           widget = widget->parent) {
         page2 = DW_PAGE (widget->parent);
         if (page2->list_item)
            isfirst = (page2->words[1].content.type == DW_CONTENT_WIDGET
                       && page2->words[1].content.data.widget == widget);
         else
            isfirst = (page2->words[0].content.type == DW_CONTENT_WIDGET
                        && page2->words[0].content.data.widget == widget);
         if (!isfirst) {
            /* The page we searched for has been found. */
            lineno = widget->parent_ref;
            if (lineno > 0 &&
                (word2 = &page2->words[page2->lines[lineno - 1].first_word]) &&
                word2->content.type == DW_CONTENT_BREAK) {
               if (word2->content.data.break_space < space) {
                  word2->content.data.break_space = space;
                  p_Dw_widget_queue_resize (DW_WIDGET (page2), lineno, FALSE);
                  page2->must_queue_resize = FALSE;
               }
            }
            return;
         }
         /* Otherwise continue to examine parents. */
      }
      /* Return in any case. */
      return;
   }

   /* Another break before? */
   if ((word = &page->words[page->num_words - 1]) &&
       word->content.type == DW_CONTENT_BREAK) {
      word->content.data.break_space =
         MAX (word->content.data.break_space, space);
      return;
   }

   word = Dw_page_add_word (page, 0, 0, 0, style);
   word->content.type = DW_CONTENT_BREAK;
   word->content.data.break_space = space;
   Dw_page_word_wrap (page, page->num_words - 1);
}

/*
 * Cause a line break.
 */
void a_Dw_page_add_linebreak (DwPage *page, DwStyle *style)
{
   DwPageWord *word;

   if (page->num_words == 0 ||
       page->words[page->num_words - 1].content.type == DW_CONTENT_BREAK)
       /* An <BR> in an empty line gets the height of the current font
        * (why would someone else place it here?), ...  */
      word = Dw_page_add_word (page, 0, style->font->font->ascent,
                               style->font->font->descent, style);
   else
      /* ... otherwise, it has no size (and does not enlarge the line). */
      word = Dw_page_add_word (page, 0, 0, 0, style);
   word->content.type = DW_CONTENT_BREAK;
   word->content.data.break_space = 0;
   word->style = style;
   Dw_page_word_wrap (page, page->num_words - 1);
}

/*
 * This function "hands" the last break of a page "over" to a parent
 * page. This is used for "collapsing spaces".
 */
void a_Dw_page_hand_over_break (DwPage *page, DwStyle *style)
{
   DwPageLine *last_line;
   DwWidget *parent;

   if (page->num_lines == 0)
      return;

   last_line = &page->lines[page->num_lines - 1];
   if (last_line->break_space != 0 &&
       (parent = DW_WIDGET(page)->parent) && DW_IS_PAGE (parent))
      a_Dw_page_add_parbreak (DW_PAGE (parent), last_line->break_space, style);
}

/*
 * Any words added by a_Dw_page_add_... are not immediately (queued to
 * be) drawn, instead, this function must be called. This saves some
 * calls to p_Dw_widget_queue_resize.
 *
 */
void a_Dw_page_flush (DwPage *page)
{
   if (page->must_queue_resize) {
      p_Dw_widget_queue_resize (DW_WIDGET (page), -1, TRUE);
      page->must_queue_resize = FALSE;
   }
}

/*
 * Change the color of all words with a specific link. Used to
 * visualize links opened in a new window.
 */
void a_Dw_page_change_link_color (DwPage *page,
                                  gint link,
                                  gint32 new_color)
{
   DwStyle *old_style, style_attrs;
   gint i, j, changed;
   DwWidget *widget = DW_WIDGET (page);

   for (i = 0; i < page->num_lines; i++) {
      changed = FALSE;

      for (j = page->lines[i].first_word; j < page->lines[i].last_word; j++)
         if (page->words[j].style->x_link == link) {
            old_style = page->words[j].style;

            style_attrs = *old_style;
            style_attrs.color =
               a_Dw_style_color_new (new_color, widget->viewport->window);
            page->words[j].style =
               a_Dw_style_new (&style_attrs, widget->viewport->window);
            /* unref'ing it before may crash dillo! */
            a_Dw_style_unref (old_style);
            changed = TRUE;
         }

      if (changed)
         p_Dw_widget_queue_draw_area (widget, 0,
                                      Dw_page_line_total_y_offset_i (page, i),
                                      widget->allocation.width,
                                      page->lines[i].ascent
                                      + page->lines[i].descent);
   }
}

/*
 * Assign a new style to some text words. Other words than text are not
 * affected.
 */
void a_Dw_page_change_word_style (DwPage *page, gint from, gint to,
                                  DwStyle *style, gboolean include_first_space,
                                  gboolean include_last_space)
{
   int i;
   DwStyle *old_style = NULL, *old_space_style;
   gint line, break_line = DW_INFINITY;
   DwWidget *widget = DW_WIDGET (page);
   gboolean diffs = FALSE, must_queue_resize = FALSE;
   DwPageWord *word;
   DwRequisition size;

   if (from == to)
      return;

   line = Dw_page_find_line_of_word (page, MAX (from - 1, 0));

   /* We also have to include the space before the first word. */
   for (i = MAX (from - 1, 0); i < to; i++) {
      word = &page->words[i];

      if (word->content.type == DW_CONTENT_TEXT) {
         if (i >= from) {
            old_style = word->style;
            word->style = style;
            a_Dw_style_ref (style);
            
            if ((diffs = a_Dw_style_size_diffs (old_style, style))) {
               must_queue_resize = TRUE;
               break_line = MIN (break_line, line);
               Dw_page_calc_text_size (page, word->content.data.text,
                                       style, &size);
               word->size = size;
            }
         }


         if ((i >= from || include_first_space) &&
             (i < to -1 || include_last_space)) {
            old_space_style = word->space_style;
            word->space_style = style;
            a_Dw_style_ref (style);

            /* In most cases, old_space_style will be old_style, so
             * that a_Dw_style_size_diffs does not have to be called.
             * Note: For the space before the first word, old_style is
             * NULL.
             */
            if (old_space_style == old_style ?
                diffs : a_Dw_style_size_diffs (old_space_style, style)) {
               must_queue_resize = TRUE;
               break_line = MIN (break_line, line);
               if (word->content.space)
                  word->orig_space = style->font->space_width;
            }

            a_Dw_style_unref (old_space_style);
         }

         if (old_style)
            a_Dw_style_unref (old_style);
      }

      /* Resizing will also imply redrawing, so if we resize anyway,
       * redrawing can be skipped. Otherwise, whole lines are redrawn,
       * i.e. when we have completed the whole task, or if this word
       * is the last one in the line.
       */
      if (!must_queue_resize &&
          (i == to - 1 || i == page->lines[line].last_word - 1)) {
         p_Dw_widget_queue_draw_area
            (widget, 0, Dw_page_line_total_y_offset_i (page, line),
             widget->allocation.width,
             page->lines[line].ascent + page->lines[line].descent);
         
         if (i < to - 1)
            line = Dw_page_find_line_of_word (page, i + 1);
      }
   }

   if (must_queue_resize)
      p_Dw_widget_queue_resize (widget, break_line, TRUE);
}


/*
 * Standard Dw function.
 */
static DwIterator *Dw_page_iterator (DwWidget *widget,
                                     gint mask,
                                     gboolean at_end)
{
   DwIteratorInt *it = g_new (DwIteratorInt, 1);
   it->it.widget = widget;
   it->it.mask = mask;
   it->it.next = Dw_page_iterator_next;
   it->it.prev = Dw_page_iterator_prev;
   it->it.clone = p_Dw_iterator_clone_std_int;
   it->it.compare = p_Dw_iterator_compare_std_int;
   it->it.free = p_Dw_iterator_free_std;
   it->it.highlight = Dw_page_iterator_highlight;
   it->it.scroll_to = Dw_page_iterator_scroll_to;

   if (at_end) {
      it->it.content.type = DW_CONTENT_END;
      it->pos = DW_PAGE(widget)->num_words;
   } else {
      it->it.content.type = DW_CONTENT_START;
      it->pos = -1;
   }

   DEBUG_MSG (DEBUG_ITERATOR_LEVEL, "Dw_page_iterator -> %p, at %s\n",
              it, at_end ? "end" : "start");

   return (DwIterator*)it;
}

/*
 * Standard Dw function.
 */
static gboolean Dw_page_iterator_next (DwIterator *it)
{
   DwPage *page = DW_PAGE (it->widget);
   DwIteratorInt *ii = (DwIteratorInt*)it;

   DEBUG_MSG (DEBUG_ITERATOR_LEVEL, "Dw_page_iterator_next (%p): %d of %d\n",
              it, ii->pos, page->num_words);

   if (it->content.type == DW_CONTENT_END)
      return FALSE;

   do {
      ii->pos++;
      if (ii->pos >= page->num_words) {
         it->content.type = DW_CONTENT_END;
         return FALSE;
      }
   } while ((page->words[ii->pos].content.type & it->mask) == 0);

   it->content = page->words[ii->pos].content;
   return TRUE;
}

/*
 * Standard Dw function.
 */
static gboolean Dw_page_iterator_prev (DwIterator *it)
{
   DwPage *page = DW_PAGE (it->widget);
   DwIteratorInt *ii = (DwIteratorInt*)it;

   DEBUG_MSG (DEBUG_ITERATOR_LEVEL, "Dw_page_iterator_prev (%p): %d of %d\n",
              it, ii->pos, page->num_words);

   if (it->content.type == DW_CONTENT_START)
      return FALSE;

   do {
      ii->pos--;
      if (ii->pos < 0) {
         it->content.type = DW_CONTENT_START;
         return FALSE;
      }
   } while ((page->words[ii->pos].content.type & it->mask) == 0);

   it->content = page->words[ii->pos].content;
   return TRUE;
}

/*
 * Standard Dw function.
 */
void Dw_page_iterator_highlight (DwIterator *it,
                                 gint start,
                                 gint end,
                                 DwHighlightLayer layer)
{
   DwIteratorInt *ii = (DwIteratorInt*)it;
   DwPage *page = DW_PAGE(it->widget);
   gint line;

   page->words[ii->pos].hl_start[layer] = start;
   page->words[ii->pos].hl_end[layer] = end;

   line = Dw_page_find_line_of_word (page, ii->pos);
   p_Dw_widget_queue_draw_area (it->widget, 0,
                                Dw_page_line_total_y_offset_i (page, line),
                                it->widget->allocation.width,
                                page->lines[line].ascent
                                + page->lines[line].descent);
}

/*
 * Standard Dw function.
 */
void Dw_page_iterator_scroll_to (DwIterator *it,
                                 DwPosition pos)
{
   DwIteratorInt *ii = (DwIteratorInt*)it;
   DwPage *page = DW_PAGE(it->widget);
   DwPageLine *line;
   gint32 line_top;

   line = &page->lines[Dw_page_find_line_of_word (page, ii->pos)];
   line_top = Dw_page_line_total_y_offset (page, line);
   switch(pos) {
   case DW_POSITION_TOP:
      a_Dw_widget_scroll_to (it->widget, pos, line_top);
      break;
   case DW_POSITION_CENTER:
      a_Dw_widget_scroll_to (it->widget, pos,
                             line_top + (line->ascent + line->descent) / 2);
      break;
   case DW_POSITION_BOTTOM:
      a_Dw_widget_scroll_to (it->widget, pos,
                             line_top + line->ascent + line->descent);
      break;
   }
}
