/**********************************************************************
 * File:        pgedit.cpp (Formerly pgeditor.c)
 * Description: Page structure file editor
 * Author:      Phil Cheatle
 * Created:     Thu Oct 10 16:25:24 BST 1991
 *
 *(C) Copyright 1991, Hewlett-Packard Ltd.
 ** Licensed under the Apache License, Version 2.0(the "License");
 ** you may not use this file except in compliance with the License.
 ** You may obtain a copy of the License at
 ** http:// www.apache.org/licenses/LICENSE-2.0
 ** Unless required by applicable law or agreed to in writing, software
 ** distributed under the License is distributed on an "AS IS" BASIS,
 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ** See the License for the specific language governing permissions and
 ** limitations under the License.
 *
 **********************************************************************/

#include          "pgedit.h"

#include          <ctype.h>
#include          <math.h>

#include          "genblob.h"
#include          "tessio.h"
#include          "tessout.h"
#include          "tordmain.h"
#include          "statistc.h"
#include          "mainblk.h"
#include          "string.h"

#include          "control.h"

#define ASC_HEIGHT     (2 * bln_baseline_offset + bln_x_height)
#define X_HEIGHT       (bln_baseline_offset + bln_x_height)
#define BL_HEIGHT     bln_baseline_offset
#define DESC_HEIGHT     0
#define MAXSPACING      128      /*max expected spacing in pix */

const ERRCODE EMPTYBLOCKLIST = "No blocks to edit";

/**********************************************************************
 *
 *  Some global data
 *
 **********************************************************************/

                                 // baseline norm words
                                 // Selected words op

BITS16 word_display_mode;
BOOL8 display_image = FALSE;
BOOL8 display_blocks = FALSE;
BOOL8 display_baselines = FALSE;
BOOL8 viewing_source = TRUE;

BLOCK_LIST *source_block_list = NULL;    // image blocks
BLOCK_LIST target_block_list;    // target blocks
BLOCK_LIST *other_block_list = &target_block_list;

BOOL8 source_changed = FALSE;    // Changes not saved
BOOL8 target_changed = FALSE;    // Changes not saved
BOOL8 *other_image_changed = &target_changed;


/* Public globals */

#define EXTERN

EXTERN BLOCK_LIST *current_block_list = NULL;
EXTERN BOOL8 *current_image_changed = &source_changed;

/* Variables */

EXTERN STRING_VAR(editor_image_win_name, "EditorImage",
"Editor image window name");
EXTERN INT_VAR(editor_image_xpos, 590, "Editor image X Pos");
EXTERN INT_VAR(editor_image_ypos, 10, "Editor image Y Pos");
EXTERN INT_VAR(editor_image_menuheight, 50, "Add to image height for menu bar");

EXTERN STRING_VAR(editor_dbwin_name, "EditorDBWin",
"Editor debug window name");
EXTERN INT_VAR(editor_dbwin_xpos, 50, "Editor debug window X Pos");
EXTERN INT_VAR(editor_dbwin_ypos, 500, "Editor debug window Y Pos");
EXTERN INT_VAR(editor_dbwin_height, 24, "Editor debug window height");
EXTERN INT_VAR(editor_dbwin_width, 80, "Editor debug window width");

EXTERN STRING_VAR(editor_word_name, "BlnWords", "BL normalised word window");
EXTERN INT_VAR(editor_word_xpos, 60, "Word window X Pos");
EXTERN INT_VAR(editor_word_ypos, 510, "Word window Y Pos");
EXTERN INT_VAR(editor_word_height, 240, "Word window height");
EXTERN INT_VAR(editor_word_width, 655, "Word window width");

EXTERN double_VAR(editor_smd_scale_factor, 1.0, "Scaling for smd image");

/**********************************************************************
 *  add_word()
 *
 *  Inserts the a word into a specified block list. The list is searched for a
 *  block and row of the same file as those of the word to be added, which
 *  contain the bounding box of the word.  If such a row is found, the new
 *  word is inserted into the row in the correct X order.  If the
 *  block is found, but not the row, a copy of the word's old row is added to
 *  the block in the correct Y order, and the word is put in that row.
 *  If neither the row nor the block are found, then the word's old block is
 *  copied with only the word's row. It is added to the block list in the
 *  correct Y order.
 **********************************************************************/

void add_word(                            // to block list
              WERD *word,                  // word to be added
              ROW *src_row,                // source row
              BLOCK *src_block,            // source block
              BLOCK_LIST *dest_block_list  // add to this
             ) {
  BLOCK_IT block_it(dest_block_list);
  BLOCK *block;                  // current block
  BLOCK *dest_block = NULL;      // destination block
  ROW_IT row_it;
  ROW *row;                      // destination row
  ROW *dest_row = NULL;          // destination row
  WERD_IT word_it;
  TBOX word_box = word->bounding_box();
  TBOX insert_point_word_box;
  BOOL8 seen_blocks_for_current_file = FALSE;

  block_it.mark_cycle_pt();
  while(!block_it.cycled_list() &&(dest_block == NULL)) {
    block = block_it.data();
    if ((block->bounding_box().contains(word_box)) &&
   (strcmp(block->name(), src_block->name()) == 0)) {
      dest_block = block;        // found dest block
      row_it.set_to_list(block->row_list());
      row_it.mark_cycle_pt();
      while((!row_it.cycled_list()) &&(dest_row == NULL)) {
        row = row_it.data();
        if (row->bounding_box().contains(word_box))
          dest_row = row;        // found dest row
        else
          row_it.forward();
      }
    }
    else
      block_it.forward();
  }

  if (dest_block == NULL) {      // make a new one
    dest_block = new BLOCK;
    *dest_block = *src_block;

    block_it.set_to_list(dest_block_list);
    for (block_it.mark_cycle_pt();
    !block_it.cycled_list(); block_it.forward()) {
      block = block_it.data();

      if (!seen_blocks_for_current_file &&
       (strcmp(block->name(), dest_block->name()) == 0))
        seen_blocks_for_current_file = TRUE;

      if (seen_blocks_for_current_file &&
       ((strcmp(block->name(), dest_block->name()) != 0) ||
       (block->bounding_box().top() <
        dest_block->bounding_box().top())))
        break;
    }

    if (block_it.cycled_list())
                                 // didn't find insrt pt
      block_it.add_to_end(dest_block);
    else
                                 // did find insert pt
      block_it.add_before_stay_put(dest_block);
  }

  if (dest_row == NULL) {        // make a new one
    dest_row = new ROW;
    *dest_row = *src_row;

    row_it.set_to_list(dest_block->row_list());
    for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
      if (row_it.data()->bounding_box().top() <
        dest_row->bounding_box().top())
        break;
    }

    if (row_it.cycled_list())
                                 // didn't find insrt pt
        row_it.add_to_end(dest_row);
    else
                                 // did find insert pt
      row_it.add_before_stay_put(dest_row);
  }

  /* dest_block and dest_row are now found or built and inserted as necessesary
    so add the word to dest row */

  word_it.set_to_list(dest_row->word_list());
  for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.forward()) {
    if (word_it.data()->bounding_box().right() >= word_box.left())
      break;
  }

  if (word_it.cycled_list())
    word_it.add_to_end(word);   // didn't find insrt pt
  else {                         // did find insert pt
    insert_point_word_box = word_it.data()->bounding_box();
    if (insert_point_word_box.contains(word_box) ||
		word_box.contains(insert_point_word_box)){
      //image_win->AddMessage("Refusing to add words which obliterate,"
      //                         " or are obliterated by, others");
	}
    else {
      if (word_it.data()->bounding_box().left() >
        word->bounding_box().left())
                                 // infront of insert pt
        word_it.add_before_stay_put(word);
      else
                                 // behind insert pt
        word_it.add_after_stay_put(word);
    }
  }
}


/**********************************************************************
 *  do_re_display()
 *
 *  Redisplay page
 **********************************************************************/

void
                                 // function to call
do_re_display(BOOL8 word_painter(
BLOCK *, ROW *, WERD *)) {
}


/**********************************************************************
 *  do_tidy_cmd()
 *
 *  Tidy TARGET page
 **********************************************************************/

const TBOX do_tidy_cmd() {  // tidy
  ICOORD shift_vector;
  TBOX tidy_box;                  // Just the tidy area
  TBOX source_box;                // source file area

  source_box = block_list_bounding_box(source_block_list);
  // find src area

  if (!target_block_list.empty()) {
    tidy_box = block_list_compress(&target_block_list);

    /* Shift tidied target above the source image area. */

    shift_vector = ICOORD(0, source_box.top() + BLOCK_SPACING)
      - tidy_box.botleft();
    block_list_move(&target_block_list, shift_vector);
    tidy_box.move(shift_vector);
  }
  source_box += tidy_box;
                                 // big enough for both
  do_view_cmd();
  return tidy_box;
}


/**********************************************************************
 *  do_view_cmd()
 *
 *  View TARGET/View SOURCE command
 **********************************************************************/

void do_view_cmd() {
  viewing_source = !viewing_source;
//  image_win->Clear();
  if (viewing_source) {
    current_block_list = source_block_list;
    current_image_changed = &source_changed;
    other_block_list = &target_block_list;
    other_image_changed = &target_changed;
    do_re_display(&word_display);
  }
  else {
    current_block_list = &target_block_list;
    current_image_changed = &target_changed;
    other_block_list = source_block_list;
    other_image_changed = &source_changed;
    do_re_display(&word_display);
  }
}


/**********************************************************************
 *  do_write_file()
 *
 *  Serialise a block list to file
 *
 *  If writing image, tidy page and move to(0,0) first
 **********************************************************************/

void do_write_file(           // serialise
                  ) {

  char* name = "";//image_win->ShowInputDialog("File Name");

  FILE *infp;                    // input file
  char msg_str[80];

  TBOX enclosing_box;

                                 // if file exists
  if ((infp = fopen(name, "r")) != NULL) {
    fclose(infp);
    sprintf(msg_str, "Overwrite file " "%s" "?(Y/N)", name);

    int a = 0;//image_win->ShowYesNoDialog(msg_str);
	if (a != 'y') { /*image_win->AddMessage("Write cancelled"); */delete[] name; return; }
  }

  infp = fopen(name, "w");      // can we write to it?
  if (infp == NULL) {

//  image_win->AddMessage("Cant write to file " "%s" "", name);
    delete[] name;
    return;
  }
  fclose(infp);

  delete [] name;

  if (!viewing_source && !target_block_list.empty()) {
                                 // Tidy & move to(0,0)
//    image_win->AddMessage("Automatic tidy...");
    viewing_source = TRUE;       // Stay viewing target!
    enclosing_box = do_tidy_cmd();
    block_list_move(&target_block_list, -enclosing_box.botleft());
//    image_win->AddMessage("Writing file...");
    pgeditor_write_file(name, &target_block_list);
                                 // move back
    block_list_move(&target_block_list,
      enclosing_box.botleft());
  }
  else {
 //   image_win->AddMessage("Writing file...");
    pgeditor_write_file(name, current_block_list);
  }
//  image_win->AddMessage("Writing file...Done");
  *current_image_changed = FALSE;

}


/**********************************************************************
 * pgeditor_read_file()
 *
 * Deserialise source file
 **********************************************************************/

void pgeditor_read_file( 
						BLOCK_LIST *blocks  // block list to add to
                       ) {
  BLOCK_IT block_it(blocks);  // iterator
  block_it.move_to_last();
  edges_and_textord(blocks);
}


/**********************************************************************
 * pgeditor_show_point()
 *
 * Display the coordinates of a point in the command window
 **********************************************************************/

void pgeditor_show_point( // display coords
                         SVEvent *event) {
//  image_win->AddMessage("Pointing at(%d, %d)", event->x, event->y);
}


/**********************************************************************
 *  pgeditor_write_file()
 *
 *  Serialise a block list to file
 *
 **********************************************************************/

void pgeditor_write_file(                   // serialise
                         char *name,         // file name
                         BLOCK_LIST *blocks  // block list to write
                        ) {
  FILE *infp;                    // input file
  BLOCK_IT block_it(blocks);  // block iterator
  BLOCK *block;                  // current block
  ROW_IT row_it;                 // row iterator

  infp = fopen(name, "w");      // create output file
  if (infp == NULL)
    CANTCREATEFILE.error("pgeditor_write_file", EXIT, name);

  for (block_it.mark_cycle_pt();
  !block_it.cycled_list(); block_it.forward()) {
    block = block_it.extract();

    row_it.set_to_list(block->row_list());
    for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward())
                                 // ensure correct
      row_it.data()->recalc_bounding_box();

    block->serialise(infp);     // serialize     non-empty
    block_it.add_after_then_move(block);
  }
  fclose(infp);
}

/**********************************************************************
 * re_scale_and_move_bln_word()
 *
 * Scale and move a bln word so that it fits in a specified bounding box.
 * Scale by width or height to generate the largest image
 **********************************************************************/

float re_scale_and_move_bln_word(                 // put bln word in       box
                                 WERD *norm_word,  // BL normalised word
                                 const TBOX &box    // destination box
                                ) {
  TBOX norm_box = norm_word->bounding_box();
  float width_scale_factor;
  float height_scale_factor;
  float selected_scale_factor;

  width_scale_factor = box.width() /(float) norm_box.width();
  height_scale_factor = box.height() /(float) ASC_HEIGHT;

  if ((ASC_HEIGHT * width_scale_factor) <= box.height())
    selected_scale_factor = width_scale_factor;
  else
    selected_scale_factor = height_scale_factor;

  norm_word->scale(selected_scale_factor);
  norm_word->move(ICOORD((box.left() + box.width() / 2), box.bottom()));
  return selected_scale_factor;
}


/**********************************************************************
 * re_segment_word()
 *
 * If all selected blobs are in the same row, remove them from their current
 * word(s) and put them in a new word.  Insert the new word in the row at the
 * appropriate point. Delete any empty words.
 *
 **********************************************************************/

void re_segment_word(                        // break/join words
                     BLOCK_LIST *block_list,  // blocks to check
                     TBOX &selection_box) {
  BLOCK_IT block_it(block_list);
  BLOCK *block;
  BLOCK *block_to_process = NULL;
  ROW_IT row_it;
  ROW *row;
  ROW *row_to_process = NULL;
  WERD_IT word_it;
  WERD *word;
  WERD *new_word = NULL;
  BOOL8 polyg = false;
  PBLOB_IT blob_it;
  PBLOB_LIST dummy;  // Just to initialize new_blob_it.
  PBLOB_IT new_blob_it = &dummy;
  PBLOB *blob;

  /* Find row to process - error if selections from more than one row */

  for (block_it.mark_cycle_pt();
  !block_it.cycled_list(); block_it.forward()) {
    block = block_it.data();
    if (block->bounding_box().overlap(selection_box)) {
      row_it.set_to_list(block->row_list());
      for (row_it.mark_cycle_pt();
      !row_it.cycled_list(); row_it.forward()) {
        row = row_it.data();
        if (row->bounding_box().overlap(selection_box)) {
          if (row_to_process == NULL) {
            block_to_process = block;
            row_to_process = row;
          }
          else {
	    	/*image_win->AddMessage("Cant resegment words "
                                     "in more than one row");
            */return;
          }
        }
      }
    }
  }
  /* Continue with row_to_process */

  word_it.set_to_list(row_to_process->word_list());
  for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.forward()) {
    word = word_it.data();
    polyg = word->flag(W_POLYGON);
    if (word->bounding_box().overlap(selection_box)) {
      blob_it.set_to_list(word->gblob_list());
      for (blob_it.mark_cycle_pt();
      !blob_it.cycled_list(); blob_it.forward()) {
        blob = blob_it.data();
        if (gblob_bounding_box(blob, polyg).overlap(selection_box)) {
          if (new_word == NULL) {
            new_word = word->shallow_copy();
            new_blob_it.set_to_list(new_word->gblob_list());
          }
          new_blob_it.add_to_end(blob_it.extract());
          // move blob
        }
      }
      if (blob_it.empty()) {    // no blobs in word
                                 // so delete word
        delete word_it.extract();
      }
    }
  }
  if (new_word != NULL) {
    gblob_sort_list(new_word->gblob_list(), polyg);
    word_it.add_to_end(new_word);
    word_it.sort(word_comparator);
    word_it.set_to_list(row_to_process->word_list());
    for (word_it.mark_cycle_pt();
      !word_it.cycled_list(); word_it.forward())
    word_display(block_to_process, row_to_process, word_it.data());
    *current_image_changed = TRUE;
  }
}


void block_space_stat(                        // show space stats
                      BLOCK_LIST *block_list,  // blocks to check
                      TBOX &selection_box) {
  BLOCK_IT block_it(block_list);
  BLOCK *block;
  ROW_IT row_it;
  ROW *row;
  int block_idx = 0;
  STATS all_gap_stats(0, MAXSPACING);
  WERD_IT word_it;
  WERD *word;
  PBLOB_IT blob_it;
  PBLOB *blob;
  C_BLOB_IT cblob_it;
  C_BLOB *cblob;
  TBOX box;
  inT16 prev_box_right;
  inT16 gap_width;
  inT16 min_inter_word_gap;
  inT16 max_inter_char_gap;

  /* Find blocks to process */

  for (block_it.mark_cycle_pt();
  !block_it.cycled_list(); block_it.forward()) {
    block_idx++;
    block = block_it.data();
    if (block->bounding_box().overlap(selection_box)) {
      /* Process a block */
      tprintf("\nBlock %d\n", block_idx);
      min_inter_word_gap = 3000;
      max_inter_char_gap = 0;
      all_gap_stats.clear();
      row_it.set_to_list(block->row_list());
      for (row_it.mark_cycle_pt();
      !row_it.cycled_list(); row_it.forward()) {
        row = row_it.data();
        prev_box_right = -1;
        word_it.set_to_list(row->word_list());
        for (word_it.mark_cycle_pt();
        !word_it.cycled_list(); word_it.forward()) {
          word = word_it.data();
          if (word->flag(W_POLYGON)) {
            blob_it.set_to_list(word->blob_list());
            for (blob_it.mark_cycle_pt();
            !blob_it.cycled_list(); blob_it.forward()) {
              blob = blob_it.data();
              box = blob->bounding_box();
              if (prev_box_right > -1) {
                gap_width = box.left() - prev_box_right;
                all_gap_stats.add(gap_width, 1);
                if (blob_it.at_first()) {
                  if (gap_width < min_inter_word_gap)
                    min_inter_word_gap = gap_width;
                }
                else {
                  if (gap_width > max_inter_char_gap)
                    max_inter_char_gap = gap_width;
                }
              }
              prev_box_right = box.right();
            }
          }
          else {
            cblob_it.set_to_list(word->cblob_list());
            for (cblob_it.mark_cycle_pt();
            !cblob_it.cycled_list(); cblob_it.forward()) {
              cblob = cblob_it.data();
              box = cblob->bounding_box();
              if (prev_box_right > -1) {
                gap_width = box.left() - prev_box_right;
                all_gap_stats.add(gap_width, 1);
                if (cblob_it.at_first()) {
                  if (gap_width < min_inter_word_gap)
                    min_inter_word_gap = gap_width;
                }
                else {
                  if (gap_width > max_inter_char_gap)
                    max_inter_char_gap = gap_width;
                }
              }
              prev_box_right = box.right();
            }
          }
        }
      }
      tprintf("Max inter char gap = %d.\nMin inter word gap = %d.\n",
        max_inter_char_gap, min_inter_word_gap);
      all_gap_stats.short_print(NULL, TRUE);
      all_gap_stats.smooth(2);
      tprintf("SMOOTHED DATA...\n");
      all_gap_stats.short_print(NULL, TRUE);
    }
  }
}


void row_space_stat(                        // show space stats
                    BLOCK_LIST *block_list,  // blocks to check
                    TBOX &selection_box) {
  BLOCK_IT block_it(block_list);
  BLOCK *block;
  ROW_IT row_it;
  ROW *row;
  int block_idx = 0;
  int row_idx;
  STATS all_gap_stats(0, MAXSPACING);
  WERD_IT word_it;
  WERD *word;
  PBLOB_IT blob_it;
  PBLOB *blob;
  C_BLOB_IT cblob_it;
  C_BLOB *cblob;
  TBOX box;
  inT16 prev_box_right;
  inT16 gap_width;
  inT16 min_inter_word_gap;
  inT16 max_inter_char_gap;

  /* Find rows to process */

  for (block_it.mark_cycle_pt();
  !block_it.cycled_list(); block_it.forward()) {
    block_idx++;
    block = block_it.data();
    if (block->bounding_box().overlap(selection_box)) {
      row_it.set_to_list(block->row_list());
      row_idx = 0;
      for (row_it.mark_cycle_pt();
      !row_it.cycled_list(); row_it.forward()) {
        row_idx++;
        row = row_it.data();
        if (row->bounding_box().overlap(selection_box)) {
          /* Process a row */

          tprintf("\nBlock %d Row %d\n", block_idx, row_idx);
          min_inter_word_gap = 3000;
          max_inter_char_gap = 0;
          prev_box_right = -1;
          all_gap_stats.clear();
          word_it.set_to_list(row->word_list());
          for (word_it.mark_cycle_pt();
          !word_it.cycled_list(); word_it.forward()) {
            word = word_it.data();
            if (word->flag(W_POLYGON)) {
              blob_it.set_to_list(word->blob_list());
              for (blob_it.mark_cycle_pt();
              !blob_it.cycled_list(); blob_it.forward()) {
                blob = blob_it.data();
                box = blob->bounding_box();
                if (prev_box_right > -1) {
                  gap_width = box.left() - prev_box_right;
                  all_gap_stats.add(gap_width, 1);
                  if (blob_it.at_first()) {
                    if (gap_width < min_inter_word_gap)
                      min_inter_word_gap = gap_width;
                  }
                  else {
                    if (gap_width > max_inter_char_gap)
                      max_inter_char_gap = gap_width;
                  }
                }
                prev_box_right = box.right();
              }
            }
            else {
              cblob_it.set_to_list(word->cblob_list());
              for (cblob_it.mark_cycle_pt();
              !cblob_it.cycled_list(); cblob_it.forward()) {
                cblob = cblob_it.data();
                box = cblob->bounding_box();
                if (prev_box_right > -1) {
                  gap_width = box.left() - prev_box_right;
                  all_gap_stats.add(gap_width, 1);
                  if (cblob_it.at_first()) {
                    if (gap_width < min_inter_word_gap)
                      min_inter_word_gap = gap_width;
                  }
                  else {
                    if (gap_width > max_inter_char_gap)
                      max_inter_char_gap = gap_width;
                  }
                }
                prev_box_right = box.right();
              }
            }
          }
          tprintf
           ("Max inter char gap = %d.\nMin inter word gap = %d.\n",
            max_inter_char_gap, min_inter_word_gap);
          all_gap_stats.short_print(NULL, TRUE);
          all_gap_stats.smooth(2);
          tprintf("SMOOTHED DATA...\n");
          all_gap_stats.short_print(NULL, TRUE);
        }
      }
    }
  }
}


/**********************************************************************
 * show_point()
 *
 * Show coords of point, blob bounding box, word bounding box and offset from
 * row baseline
 **********************************************************************/

void show_point(                        // display posn of bloba word
                BLOCK_LIST *block_list,  // blocks to check
                float x,
                float y) {
  FCOORD pt(x, y);
  TBOX box;
  BLOCK_IT block_it(block_list);
  BLOCK *block;
  ROW_IT row_it;
  ROW *row;
  WERD_IT word_it;
  WERD *word;
  PBLOB_IT blob_it;
  PBLOB *blob;
  C_BLOB_IT cblob_it;
  C_BLOB *cblob;

  char msg[160];
  char *msg_ptr = msg;

  msg_ptr += sprintf(msg_ptr, "Pt:(%0.3f, %0.3f) ", x, y);

  for (block_it.mark_cycle_pt();
  !block_it.cycled_list(); block_it.forward()) {
    block = block_it.data();
    if (block->bounding_box().contains(pt)) {
      row_it.set_to_list(block->row_list());
      for (row_it.mark_cycle_pt();
      !row_it.cycled_list(); row_it.forward()) {
        row = row_it.data();
        if (row->bounding_box().contains(pt)) {
          msg_ptr += sprintf(msg_ptr, "BL(x)=%0.3f ",
            row->base_line(x));

          word_it.set_to_list(row->word_list());
          for (word_it.mark_cycle_pt();
          !word_it.cycled_list(); word_it.forward()) {
            word = word_it.data();
            box = word->bounding_box();
            if (box.contains(pt)) {
              msg_ptr += sprintf(msg_ptr,
                "Wd(%d, %d)/(%d, %d) ",
                box.left(), box.bottom(),
                box.right(), box.top());

              if (word->flag(W_POLYGON)) {
                blob_it.set_to_list(word->blob_list());
                for (blob_it.mark_cycle_pt();
                  !blob_it.cycled_list();
                blob_it.forward()) {
                  blob = blob_it.data();
                  box = blob->bounding_box();
                  if (box.contains(pt)) {
                    msg_ptr += sprintf(msg_ptr,
                      "Blb(%d, %d)/(%d, %d) ",
                      box.left(),
                      box.bottom(),
                      box.right(),
                      box.top());
                  }
                }
              }
              else {
                cblob_it.set_to_list(word->cblob_list());
                for (cblob_it.mark_cycle_pt();
                  !cblob_it.cycled_list();
                cblob_it.forward()) {
                  cblob = cblob_it.data();
                  box = cblob->bounding_box();
                  if (box.contains(pt)) {
                    msg_ptr += sprintf(msg_ptr,
                      "CBlb(%d, %d)/(%d, %d) ",
                      box.left(),
                      box.bottom(),
                      box.right(),
                      box.top());
                  }
                }
              }
            }
          }
        }
      }
    }
  }
  //image_win->AddMessage(msg);
}


/**********************************************************************
 * WERD PROCESSOR FUNCTIONS
 * ========================
 *
 * These routines are invoked by one or mode of:
 *    process_all_words()
 *    process_selected_words()
 * or
 *    process_all_words_it()
 *    process_selected_words_it()
 * for each word to be processed
 **********************************************************************/

/**********************************************************************
 * word_blank_and_set_display()  Word processor
 *
 * Blank display of word then redisplay word according to current display mode
 * settings
 **********************************************************************/

BOOL8 word_blank_and_set_display(              // display a word
                                 BLOCK *block,  // block holding word
                                 ROW *row,      // row holding word
                                 WERD *word     // word to be processed
                                ) {
  return word_set_display(block, row, word);
}


/**********************************************************************
 * word_bln_display()
 *
 * Normalise word and display in word window
 **********************************************************************/

BOOL8 word_bln_display(           // bln & display
                       BLOCK *,    // block holding word
                       ROW *row,   // row holding word
                       WERD *word  // word to be processed
                      ) {
 return TRUE;
}



/**********************************************************************
 * word_copy()
 *
 * Copy a word to other display list
 **********************************************************************/

BOOL8 word_copy(              // copy a word
                BLOCK *block,  // block holding word
                ROW *row,      // row holding word
                WERD *word     // word to be processed
               ) {
  WERD *copy_word = new WERD;

  *copy_word = *word;
  add_word(copy_word, row, block, other_block_list);
  *other_image_changed = TRUE;
  return TRUE;
}


/**********************************************************************
 * word_delete()
 *
 * Delete a word
 **********************************************************************/

BOOL8 word_delete(                    // delete a word
                  BLOCK *block,        // block holding word
                  ROW *row,            // row holding word
                  WERD *word,          // word to be processed
                  BLOCK_IT &block_it,  // block list iterator
                  ROW_IT &row_it,      // row list iterator
                  WERD_IT &word_it     // word list iterator
                 ) {
  word_it.extract();
  delete(word);

  if (word_it.empty()) {        // no words left in row
                                 // so delete row
    row_it.extract();
    delete(row);

    if (row_it.empty()) {       // no rows left in blk
                                 // so delete block
      block_it.extract();
      delete(block);
    }
  }
  *current_image_changed = TRUE;
  return TRUE;
}


/**********************************************************************
 *  word_display()  Word Processor
 *
 *  Display a word according to its display modes
 **********************************************************************/

BOOL8 word_display(           // display a word
                   BLOCK *,    // block holding word
                   ROW *row,   // row holding word
                   WERD *word  // word to be processed
                  ) {
  TBOX word_bb;                   // word bounding box
  int word_height;               // ht of word BB
  BOOL8 displayed_something = FALSE;
  BOOL8 displayed_rainbow = FALSE;
  float shift;                   // from bot left
  PBLOB_IT it;                   // blob iterator
  C_BLOB_IT c_it;                // cblob iterator
  WERD *word_ptr;                // poly copy
  WERD temp_word;
  float scale_factor;            // for BN_POLYGON

  /*
    Note the double coercions of(COLOUR)((inT32)editor_image_word_bb_color)
    etc. are to keep the compiler happy.
  */
  return TRUE;
}


/**********************************************************************
 * word_dumper()
 *
 * Dump members to the debug window
 **********************************************************************/

BOOL8 word_dumper(              // dump word
                  BLOCK *block,  // block holding word
                  ROW *row,      // row holding word
                  WERD *word     // word to be processed
                 ) {

  tprintf("\nBlock data...\n");
  block->print(NULL, FALSE);
  tprintf("\nRow data...\n");
  row->print(NULL);
  tprintf("\nWord data...\n");
  word->print(NULL);
  return TRUE;
}


/**********************************************************************
 * word_set_display()  Word processor
 *
 * Display word according to current display mode settings
 **********************************************************************/

BOOL8 word_set_display(              // display a word
                       BLOCK *block,  // block holding word
                       ROW *row,      // row holding word
                       WERD *word     // word to be processed
                      ) {
  TBOX word_bb;                   // word bounding box

  word->set_display_flag(DF_BOX, word_display_mode.bit(DF_BOX));
  word->set_display_flag(DF_TEXT, word_display_mode.bit(DF_TEXT));
  word->set_display_flag(DF_POLYGONAL, word_display_mode.bit(DF_POLYGONAL));
  word->set_display_flag(DF_EDGE_STEP, word_display_mode.bit(DF_EDGE_STEP));
  word->set_display_flag(DF_BN_POLYGONAL,
    word_display_mode.bit(DF_BN_POLYGONAL));
  *current_image_changed = TRUE;
  return word_display(block, row, word);
}


/**********************************************************************
 * word_toggle_seg()
 *
 * Toggle the correct segmentation flag
 **********************************************************************/

BOOL8 word_toggle_seg(           // toggle seg flag
                      BLOCK *,    // block holding word
                      ROW *,      // row holding word
                      WERD *word  // word to be processed
                     ) {
  word->set_flag(W_SEGMENTED, !word->flag(W_SEGMENTED));
  *current_image_changed = TRUE;
  return TRUE;
}


/* DEBUG ONLY */

void do_check_mem( // do it
                  inT32 level) {
  check_mem("Doing it", level);
}
