/**********************************************************************
 * File:        pgedit.cpp (Formerly pgeditor.c)
 * Description: Page structure file editor
 * Author:      Phil Cheatle
 *
 *(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 automatically generated configuration file if running autoconf.
#ifdef HAVE_CONFIG_H
#include "config_auto.h"
#endif

#include "pgedit.h"

#include "blread.h"
#include "control.h"
#include "paramsd.h"
#include "pageres.h"
#include "tordmain.h"
#include "scrollview.h"
#include "svmnode.h"
#include "statistc.h"
#include "tesseractclass.h"
#include "werdit.h"

#include <cctype>
#include <cmath>

#ifndef GRAPHICS_DISABLED
namespace tesseract {
#define ASC_HEIGHT     (2 * kBlnBaselineOffset + kBlnXHeight)
#define X_HEIGHT       (kBlnBaselineOffset + kBlnXHeight)
#define BL_HEIGHT     kBlnBaselineOffset
#define DESC_HEIGHT     0

enum CMD_EVENTS
{
  NULL_CMD_EVENT,
  CHANGE_DISP_CMD_EVENT,
  DUMP_WERD_CMD_EVENT,
  SHOW_POINT_CMD_EVENT,
  SHOW_BLN_WERD_CMD_EVENT,
  DEBUG_WERD_CMD_EVENT,
  BLAMER_CMD_EVENT,
  BOUNDING_BOX_CMD_EVENT,
  CORRECT_TEXT_CMD_EVENT,
  POLYGONAL_CMD_EVENT,
  BL_NORM_CMD_EVENT,
  BITMAP_CMD_EVENT,
  IMAGE_CMD_EVENT,
  BLOCKS_CMD_EVENT,
  BASELINES_CMD_EVENT,
  UNIFORM_DISP_CMD_EVENT,
  REFRESH_CMD_EVENT,
  QUIT_CMD_EVENT,
  RECOG_WERDS,
  RECOG_PSEUDO,
  SHOW_BLOB_FEATURES,
  SHOW_SUBSCRIPT_CMD_EVENT,
  SHOW_SUPERSCRIPT_CMD_EVENT,
  SHOW_ITALIC_CMD_EVENT,
  SHOW_BOLD_CMD_EVENT,
  SHOW_UNDERLINE_CMD_EVENT,
  SHOW_FIXEDPITCH_CMD_EVENT,
  SHOW_SERIF_CMD_EVENT,
  SHOW_SMALLCAPS_CMD_EVENT,
  SHOW_DROPCAPS_CMD_EVENT,
};

enum ColorationMode {
  CM_RAINBOW,
  CM_SUBSCRIPT,
  CM_SUPERSCRIPT,
  CM_ITALIC,
  CM_BOLD,
  CM_UNDERLINE,
  CM_FIXEDPITCH,
  CM_SERIF,
  CM_SMALLCAPS,
  CM_DROPCAPS
};

/*
 *
 *  Some global data
 *
 */

static ScrollView* image_win;
static ParamsEditor* pe;
static bool stillRunning = false;

static ScrollView* bln_word_window = nullptr; // baseline norm words

static CMD_EVENTS mode = CHANGE_DISP_CMD_EVENT; // selected words op

static bool recog_done = false; // recog_all_words was called

// These variables should remain global, since they are only used for the
// debug mode (in which only a single Tesseract thread/instance will exist).
static BITS16 word_display_mode;
static ColorationMode color_mode = CM_RAINBOW;
static bool display_image = false;
static bool display_blocks = false;
static bool display_baselines = false;

static PAGE_RES *current_page_res = nullptr;

STRING_VAR(editor_image_win_name, "EditorImage",
           "Editor image window name");
INT_VAR(editor_image_xpos, 590, "Editor image X Pos");
INT_VAR(editor_image_ypos, 10, "Editor image Y Pos");
static INT_VAR(editor_image_menuheight, 50, "Add to image height for menu bar");
INT_VAR(editor_image_word_bb_color, ScrollView::BLUE,
        "Word bounding box colour");
INT_VAR(editor_image_blob_bb_color, ScrollView::YELLOW,
        "Blob bounding box colour");
INT_VAR(editor_image_text_color, ScrollView::WHITE,
        "Correct text colour");

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

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

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

static void show_point(PAGE_RES* page_res, float x, float y) {
  FCOORD pt(x, y);
  PAGE_RES_IT pr_it(page_res);

  const int kBufsize = 512;
  char msg[kBufsize];
  char *msg_ptr = msg;

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

  for (WERD_RES* word = pr_it.word(); word != nullptr; word = pr_it.forward()) {
    if (pr_it.row() != pr_it.prev_row() &&
        pr_it.row()->row->bounding_box().contains(pt)) {
      msg_ptr += sprintf(msg_ptr, "BL(x)=%0.3f ",
                         pr_it.row()->row->base_line(x));
    }
    if (word->word->bounding_box().contains(pt)) {
      TBOX box = word->word->bounding_box();
      msg_ptr += sprintf(msg_ptr, "Wd(%d, %d)/(%d, %d) ",
                         box.left(), box.bottom(),
                         box.right(), box.top());
      C_BLOB_IT cblob_it(word->word->cblob_list());
      for (cblob_it.mark_cycle_pt();
           !cblob_it.cycled_list();
           cblob_it.forward()) {
        C_BLOB* 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);
}

/**
 * pgeditor_msg()
 *
 * Display a message - in the command window if there is one, or to stdout
 */

static void pgeditor_msg( // message display
                  const char *msg) {
    image_win->AddMessage(msg);
}

class BlnEventHandler : public SVEventHandler {
 public:
  void Notify(const SVEvent* sv_event) override {
    if (sv_event->type == SVET_DESTROY)
      bln_word_window = nullptr;
    else if (sv_event->type == SVET_CLICK)
      show_point(current_page_res, sv_event->x, sv_event->y);
  }
};

/**
 *  bln_word_window_handle()
 *
 *  @return a WINDOW for the word window, creating it if necessary
 */
static ScrollView* bln_word_window_handle() {  // return handle
                                 // not opened yet
  if (bln_word_window == nullptr) {
    pgeditor_msg("Creating BLN word window...");
    bln_word_window = new ScrollView(editor_word_name.c_str(),
      editor_word_xpos, editor_word_ypos, editor_word_width,
      editor_word_height, 4000, 4000, true);
    auto* a = new BlnEventHandler();
    bln_word_window->AddEventHandler(a);
    pgeditor_msg("Creating BLN word window...Done");
  }
  return bln_word_window;
}

/**
 *  build_image_window()
 *
 *  Destroy the existing image window if there is one.  Work out how big the
 *  new window needs to be. Create it and re-display.
 */

static void build_image_window(int width, int height) {
  delete image_win;
  image_win = new ScrollView(editor_image_win_name.c_str(),
                             editor_image_xpos, editor_image_ypos,
                             width + 1,
                             height + editor_image_menuheight + 1,
                             width,
                             height,
                             true);
}

/**
 *  display_bln_lines()
 *
 *  Display normalized baseline, x-height, ascender limit and descender limit
 */

static void display_bln_lines(ScrollView* window, ScrollView::Color colour,
                              float scale_factor, float y_offset,
                              float minx, float maxx) {
  window->Pen(colour);
  window->Line(minx, y_offset + scale_factor * DESC_HEIGHT,
               maxx, y_offset + scale_factor * DESC_HEIGHT);
  window->Line(minx, y_offset + scale_factor * BL_HEIGHT,
               maxx, y_offset + scale_factor * BL_HEIGHT);
  window->Line(minx, y_offset + scale_factor * X_HEIGHT,
               maxx, y_offset + scale_factor * X_HEIGHT);
  window->Line(minx, y_offset + scale_factor * ASC_HEIGHT,
               maxx, y_offset + scale_factor * ASC_HEIGHT);
}

/**
 *  notify()
 *
 *  Event handler that processes incoming events, either forwarding
 *  them to process_cmd_win_event or process_image_event.
 *
 */

void PGEventHandler::Notify(const SVEvent* event) {
  char myval = '0';
  if (event->type == SVET_POPUP) {
    pe->Notify(event);
  } // These are handled by ParamsEditor
  else if (event->type == SVET_EXIT) { stillRunning = false; }
  else if (event->type == SVET_MENU) {
     if (strcmp(event->parameter, "true") == 0) { myval = 'T'; }
     else if (strcmp(event->parameter, "false") == 0) { myval = 'F'; }
     tess_->process_cmd_win_event(event->command_id, &myval);
  }
  else {
    tess_->process_image_event(*event);
  }
}

/**
 *  build_menu()
 *
 *  Construct the menu tree used by the command window
 */
SVMenuNode *Tesseract::build_menu_new() {
  SVMenuNode* parent_menu;
  auto* root_menu_item = new SVMenuNode();

  SVMenuNode* modes_menu_item = root_menu_item->AddChild("MODES");

  modes_menu_item->AddChild("Change Display", CHANGE_DISP_CMD_EVENT);
  modes_menu_item->AddChild("Dump Word", DUMP_WERD_CMD_EVENT);
  modes_menu_item->AddChild("Show Point", SHOW_POINT_CMD_EVENT);
  modes_menu_item->AddChild("Show BL Norm Word", SHOW_BLN_WERD_CMD_EVENT);
  modes_menu_item->AddChild("Config Words", DEBUG_WERD_CMD_EVENT);
  modes_menu_item->AddChild("Recog Words", RECOG_WERDS);
  modes_menu_item->AddChild("Recog Blobs", RECOG_PSEUDO);
  modes_menu_item->AddChild("Show Blob Features", SHOW_BLOB_FEATURES);

  parent_menu = root_menu_item->AddChild("DISPLAY");

  parent_menu->AddChild("Blamer", BLAMER_CMD_EVENT, false);
  parent_menu->AddChild("Bounding Boxes", BOUNDING_BOX_CMD_EVENT, false);
  parent_menu->AddChild("Correct Text", CORRECT_TEXT_CMD_EVENT, false);
  parent_menu->AddChild("Polygonal Approx", POLYGONAL_CMD_EVENT, false);
  parent_menu->AddChild("Baseline Normalized", BL_NORM_CMD_EVENT, false);
  parent_menu->AddChild("Edge Steps", BITMAP_CMD_EVENT, true);
  parent_menu->AddChild("Subscripts", SHOW_SUBSCRIPT_CMD_EVENT);
  parent_menu->AddChild("Superscripts", SHOW_SUPERSCRIPT_CMD_EVENT);
  parent_menu->AddChild("Italics", SHOW_ITALIC_CMD_EVENT);
  parent_menu->AddChild("Bold", SHOW_BOLD_CMD_EVENT);
  parent_menu->AddChild("Underline", SHOW_UNDERLINE_CMD_EVENT);
  parent_menu->AddChild("FixedPitch", SHOW_FIXEDPITCH_CMD_EVENT);
  parent_menu->AddChild("Serifs", SHOW_SERIF_CMD_EVENT);
  parent_menu->AddChild("SmallCaps", SHOW_SMALLCAPS_CMD_EVENT);
  parent_menu->AddChild("DropCaps", SHOW_DROPCAPS_CMD_EVENT);


  parent_menu = root_menu_item->AddChild("OTHER");

  parent_menu->AddChild("Quit", QUIT_CMD_EVENT);
  parent_menu->AddChild("Show Image", IMAGE_CMD_EVENT, false);
  parent_menu->AddChild("ShowBlock Outlines", BLOCKS_CMD_EVENT, false);
  parent_menu->AddChild("Show Baselines", BASELINES_CMD_EVENT, false);
  parent_menu->AddChild("Uniform Display", UNIFORM_DISP_CMD_EVENT);
  parent_menu->AddChild("Refresh Display", REFRESH_CMD_EVENT);

  return root_menu_item;
}

/**
 *  do_re_display()
 *
 *  Redisplay page
 */
void Tesseract::do_re_display(
        bool (tesseract::Tesseract::* word_painter)(PAGE_RES_IT* pr_it)) {
  int block_count = 1;

  image_win->Clear();
  if (display_image) {
    image_win->Image(pix_binary_, 0, 0);
  }

  image_win->Brush(ScrollView::NONE);
  PAGE_RES_IT pr_it(current_page_res);
  for (WERD_RES* word = pr_it.word(); word != nullptr; word = pr_it.forward()) {
    (this->*word_painter)(&pr_it);
    if (display_baselines && pr_it.row() != pr_it.prev_row())
      pr_it.row()->row->plot_baseline(image_win, ScrollView::GREEN);
    if (display_blocks && pr_it.block() != pr_it.prev_block())
      pr_it.block()->block->pdblk.plot(image_win, block_count++, ScrollView::RED);
  }
  image_win->Update();
}

/**
 *  pgeditor_main()
 *
 *  Top level editor operation:
 *  Setup a new window and an according event handler
 *
 */

void Tesseract::pgeditor_main(int width, int height, PAGE_RES *page_res) {
  current_page_res = page_res;
  if (current_page_res->block_res_list.empty())
    return;

  recog_done = false;
  stillRunning = true;

  build_image_window(width, height);
  word_display_mode.set(DF_EDGE_STEP);
  do_re_display(&tesseract::Tesseract::word_set_display);
#ifndef GRAPHICS_DISABLED
  pe = new ParamsEditor(this, image_win);
#endif
  PGEventHandler pgEventHandler(this);

  image_win->AddEventHandler(&pgEventHandler);
  image_win->AddMessageBox();

  SVMenuNode* svMenuRoot = build_menu_new();

  svMenuRoot->BuildMenu(image_win);
  image_win->SetVisible(true);

  image_win->AwaitEvent(SVET_DESTROY);
  image_win->AddEventHandler(nullptr);
}

/**
 *  process_cmd_win_event()
 *
 *  Process a command returned from the command window
 * (Just call the appropriate command handler)
 */

bool Tesseract::process_cmd_win_event(                 // UI command semantics
        int32_t cmd_event,  // which menu item?
        char* new_value   // any prompt data
) {
  char msg[160];
  bool exit = false;

  color_mode = CM_RAINBOW;

  // Run recognition on the full page if needed.
  switch (cmd_event) {
    case BLAMER_CMD_EVENT:
    case SHOW_SUBSCRIPT_CMD_EVENT:
    case SHOW_SUPERSCRIPT_CMD_EVENT:
    case SHOW_ITALIC_CMD_EVENT:
    case SHOW_BOLD_CMD_EVENT:
    case SHOW_UNDERLINE_CMD_EVENT:
    case SHOW_FIXEDPITCH_CMD_EVENT:
    case SHOW_SERIF_CMD_EVENT:
    case SHOW_SMALLCAPS_CMD_EVENT:
    case SHOW_DROPCAPS_CMD_EVENT:
      if (!recog_done) {
        recog_all_words(current_page_res, nullptr, nullptr, nullptr, 0);
        recog_done = true;
      }
      break;
    default:
      break;
  }

  char* parameter;

  switch (cmd_event) {
    case NULL_CMD_EVENT:
      break;

    case CHANGE_DISP_CMD_EVENT:
    case DUMP_WERD_CMD_EVENT:
    case SHOW_POINT_CMD_EVENT:
    case SHOW_BLN_WERD_CMD_EVENT:
    case RECOG_WERDS:
    case RECOG_PSEUDO:
    case SHOW_BLOB_FEATURES:
      mode =static_cast<CMD_EVENTS>(cmd_event);
      break;
    case DEBUG_WERD_CMD_EVENT:
      mode = DEBUG_WERD_CMD_EVENT;
      parameter = image_win->ShowInputDialog("Config File Name");
      word_config_ = parameter;
      delete[] parameter;
      break;
    case BOUNDING_BOX_CMD_EVENT:
      if (new_value[0] == 'T')
        word_display_mode.set(DF_BOX);
      else
        word_display_mode.reset(DF_BOX);
      mode = CHANGE_DISP_CMD_EVENT;
      break;
    case BLAMER_CMD_EVENT:
      if (new_value[0] == 'T')
        word_display_mode.set(DF_BLAMER);
      else
        word_display_mode.reset(DF_BLAMER);
      do_re_display(&tesseract::Tesseract::word_display);
      mode = CHANGE_DISP_CMD_EVENT;
      break;
    case CORRECT_TEXT_CMD_EVENT:
      if (new_value[0] == 'T')
        word_display_mode.set(DF_TEXT);
      else
        word_display_mode.reset(DF_TEXT);
      mode = CHANGE_DISP_CMD_EVENT;
      break;
    case POLYGONAL_CMD_EVENT:
      if (new_value[0] == 'T')
        word_display_mode.set(DF_POLYGONAL);
      else
        word_display_mode.reset(DF_POLYGONAL);
      mode = CHANGE_DISP_CMD_EVENT;
      break;
    case BL_NORM_CMD_EVENT:
      if (new_value[0] == 'T')
        word_display_mode.set(DF_BN_POLYGONAL);
      else
        word_display_mode.reset(DF_BN_POLYGONAL);
      mode = CHANGE_DISP_CMD_EVENT;
      break;
    case BITMAP_CMD_EVENT:
      if (new_value[0] == 'T')
        word_display_mode.set(DF_EDGE_STEP);
      else
        word_display_mode.reset(DF_EDGE_STEP);
      mode = CHANGE_DISP_CMD_EVENT;
      break;
    case UNIFORM_DISP_CMD_EVENT:
      do_re_display(&tesseract::Tesseract::word_set_display);
      break;
    case IMAGE_CMD_EVENT:
      display_image =(new_value[0] == 'T');
      do_re_display(&tesseract::Tesseract::word_display);
      break;
    case BLOCKS_CMD_EVENT:
      display_blocks =(new_value[0] == 'T');
      do_re_display(&tesseract::Tesseract::word_display);
      break;
    case BASELINES_CMD_EVENT:
      display_baselines =(new_value[0] == 'T');
      do_re_display(&tesseract::Tesseract::word_display);
      break;
    case SHOW_SUBSCRIPT_CMD_EVENT:
      color_mode = CM_SUBSCRIPT;
      do_re_display(&tesseract::Tesseract::word_display);
      break;
    case SHOW_SUPERSCRIPT_CMD_EVENT:
      color_mode = CM_SUPERSCRIPT;
      do_re_display(&tesseract::Tesseract::word_display);
      break;
    case SHOW_ITALIC_CMD_EVENT:
      color_mode = CM_ITALIC;
      do_re_display(&tesseract::Tesseract::word_display);
      break;
    case SHOW_BOLD_CMD_EVENT:
      color_mode = CM_BOLD;
      do_re_display(&tesseract::Tesseract::word_display);
      break;
    case SHOW_UNDERLINE_CMD_EVENT:
      color_mode = CM_UNDERLINE;
      do_re_display(&tesseract::Tesseract::word_display);
      break;
    case SHOW_FIXEDPITCH_CMD_EVENT:
      color_mode = CM_FIXEDPITCH;
      do_re_display(&tesseract::Tesseract::word_display);
      break;
    case SHOW_SERIF_CMD_EVENT:
      color_mode = CM_SERIF;
      do_re_display(&tesseract::Tesseract::word_display);
      break;
    case SHOW_SMALLCAPS_CMD_EVENT:
      color_mode = CM_SMALLCAPS;
      do_re_display(&tesseract::Tesseract::word_display);
      break;
    case SHOW_DROPCAPS_CMD_EVENT:
      color_mode = CM_DROPCAPS;
      do_re_display(&tesseract::Tesseract::word_display);
      break;
    case REFRESH_CMD_EVENT:
      do_re_display(&tesseract::Tesseract::word_display);
      break;
    case QUIT_CMD_EVENT:
      exit = true;
      ScrollView::Exit();
      break;

    default:
      snprintf(msg, sizeof(msg), "Unrecognised event %" PRId32 "(%s)",
               cmd_event, new_value);
      image_win->AddMessage(msg);
    break;
  }
  return exit;
}


/**
 * process_image_event()
 *
 * User has done something in the image window - mouse down or up.  Work out
 * what it is and do something with it.
 * If DOWN - just remember where it was.
 * If UP - for each word in the selected area do the operation defined by
 * the current mode.
 */
void Tesseract::process_image_event( // action in image win
                                    const SVEvent &event) {
   // The following variable should remain static, since it is used by
   // debug editor, which uses a single Tesseract instance.
  static ICOORD down;
  ICOORD up;
  TBOX selection_box;
  char msg[80];

  switch(event.type) {

    case SVET_SELECTION:
      if (event.type == SVET_SELECTION) {
        down.set_x(event.x + event.x_size);
        down.set_y(event.y + event.y_size);
        if (mode == SHOW_POINT_CMD_EVENT)
          show_point(current_page_res, event.x, event.y);
      }

      up.set_x(event.x);
      up.set_y(event.y);

      selection_box = TBOX(down, up);

      switch(mode) {
        case CHANGE_DISP_CMD_EVENT:
          process_selected_words(
              current_page_res,
              selection_box,
              &tesseract::Tesseract::word_blank_and_set_display);
          break;
       case DUMP_WERD_CMD_EVENT:
          process_selected_words(current_page_res,
                                 selection_box,
                                 &tesseract::Tesseract::word_dumper);
          break;
        case SHOW_BLN_WERD_CMD_EVENT:
          process_selected_words(current_page_res,
                                 selection_box,
                                 &tesseract::Tesseract::word_bln_display);
          break;
        case DEBUG_WERD_CMD_EVENT:
          debug_word(current_page_res, selection_box);
          break;
        case SHOW_POINT_CMD_EVENT:
          break;                 // ignore up event

        case RECOG_WERDS:
        #ifndef DISABLED_LEGACY_ENGINE
          image_win->AddMessage("Recogging selected words");
          this->process_selected_words(current_page_res,
                                       selection_box,
                                       &Tesseract::recog_interactive);
        #endif  // ndef DISABLED_LEGACY_ENGINE
          break;
        case RECOG_PSEUDO:
          image_win->AddMessage("Recogging selected blobs");
          recog_pseudo_word(current_page_res, selection_box);
          break;
        case SHOW_BLOB_FEATURES:
          blob_feature_display(current_page_res, selection_box);
          break;

        default:
          sprintf(msg, "Mode %d not yet implemented", mode);
          image_win->AddMessage(msg);
          break;
      }
    default:
      break;
  }
}

/**
 * debug_word
 *
 * Process the whole image, but load word_config_ for the selected word(s).
 */
void Tesseract::debug_word(PAGE_RES* page_res, const TBOX &selection_box) {
#ifndef DISABLED_LEGACY_ENGINE
  ResetAdaptiveClassifier();
#endif
  recog_all_words(page_res, nullptr, &selection_box, word_config_.c_str(), 0);
}


/**********************************************************************
 * WERD PROCESSOR FUNCTIONS
 * ========================
 *
 * These routines are invoked by one or more 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
 */

bool Tesseract::word_blank_and_set_display(PAGE_RES_IT* pr_it) {
  pr_it->word()->word->bounding_box().plot(image_win, ScrollView::BLACK,
                                           ScrollView::BLACK);
  return word_set_display(pr_it);
}


/**
 * word_bln_display()
 *
 * Normalize word and display in word window
 */
bool Tesseract::word_bln_display(PAGE_RES_IT* pr_it) {
  WERD_RES* word_res = pr_it->word();
  if (word_res->chopped_word == nullptr) {
    // Setup word normalization parameters.
    word_res->SetupForRecognition(unicharset, this, BestPix(),
                                  tessedit_ocr_engine_mode, nullptr,
                                  classify_bln_numeric_mode,
                                  textord_use_cjk_fp_model,
                                  poly_allow_detailed_fx,
                                  pr_it->row()->row, pr_it->block()->block);
  }
  bln_word_window_handle()->Clear();
  display_bln_lines(bln_word_window_handle(), ScrollView::CYAN,
                     1.0, 0.0f, -1000.0f, 1000.0f);
  C_BLOB_IT it(word_res->word->cblob_list());
  ScrollView::Color color = WERD::NextColor(ScrollView::BLACK);
  for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
    it.data()->plot_normed(word_res->denorm, color, ScrollView::BROWN,
                           bln_word_window_handle());
    color = WERD::NextColor(color);
  }
  bln_word_window_handle()->Update();
  return true;
}



/**
 *  word_display()  Word Processor
 *
 *  Display a word according to its display modes
 */
bool Tesseract::word_display(PAGE_RES_IT* pr_it) {
  WERD_RES* word_res = pr_it->word();
  WERD* word = word_res->word;
  TBOX word_bb;                   // word bounding box
  int word_height;               // ht of word BB
  bool displayed_something = false;
  float shift;                   // from bot left

  if (color_mode != CM_RAINBOW && word_res->box_word != nullptr) {
  #ifndef DISABLED_LEGACY_ENGINE
    BoxWord* box_word = word_res->box_word;
    WERD_CHOICE* best_choice = word_res->best_choice;
    int length = box_word->length();
    if (word_res->fontinfo == nullptr) return false;
    const FontInfo& font_info = *word_res->fontinfo;
    for (int i = 0; i < length; ++i) {
      ScrollView::Color color = ScrollView::GREEN;
      switch (color_mode) {
        case CM_SUBSCRIPT:
          if (best_choice->BlobPosition(i) == SP_SUBSCRIPT)
            color = ScrollView::RED;
          break;
        case CM_SUPERSCRIPT:
          if (best_choice->BlobPosition(i) == SP_SUPERSCRIPT)
            color = ScrollView::RED;
          break;
        case CM_ITALIC:
          if (font_info.is_italic())
            color = ScrollView::RED;
          break;
        case CM_BOLD:
          if (font_info.is_bold())
            color = ScrollView::RED;
          break;
        case CM_FIXEDPITCH:
          if (font_info.is_fixed_pitch())
            color = ScrollView::RED;
          break;
        case CM_SERIF:
          if (font_info.is_serif())
            color = ScrollView::RED;
          break;
        case CM_SMALLCAPS:
          if (word_res->small_caps)
            color = ScrollView::RED;
          break;
        case CM_DROPCAPS:
          if (best_choice->BlobPosition(i) == SP_DROPCAP)
            color = ScrollView::RED;
          break;
          // TODO(rays) underline is currently completely unsupported.
        case CM_UNDERLINE:
        default:
          break;
      }
      image_win->Pen(color);
      TBOX box = box_word->BlobBox(i);
      image_win->Rectangle(box.left(), box.bottom(), box.right(), box.top());
    }
    return true;
  #else
    return false;
  #endif  // ndef DISABLED_LEGACY_ENGINE
  }
  /*
    Note the double coercions of(COLOUR)((int32_t)editor_image_word_bb_color)
    etc. are to keep the compiler happy.
  */
                                 // display bounding box
  if (word->display_flag(DF_BOX)) {
    word->bounding_box().plot(image_win,
     static_cast<ScrollView::Color>((int32_t)
      editor_image_word_bb_color),
     static_cast<ScrollView::Color>((int32_t)
      editor_image_word_bb_color));

    auto c = static_cast<ScrollView::Color>((int32_t) editor_image_blob_bb_color);
    image_win->Pen(c);
    // cblob iterator
    C_BLOB_IT c_it(word->cblob_list());
    for (c_it.mark_cycle_pt(); !c_it.cycled_list(); c_it.forward())
      c_it.data()->bounding_box().plot(image_win);
    displayed_something = true;
  }

                                 // display edge steps
  if (word->display_flag(DF_EDGE_STEP)) {     // edgesteps available
    word->plot(image_win);      // rainbow colors
    displayed_something = true;
  }

                                 // display poly approx
  if (word->display_flag(DF_POLYGONAL)) {
                                 // need to convert
    TWERD* tword = TWERD::PolygonalCopy(poly_allow_detailed_fx, word);
    tword->plot(image_win);
    delete tword;
    displayed_something = true;
  }

  // Display correct text and blamer information.
  STRING text;
  STRING blame;
  if (word->display_flag(DF_TEXT) && word->text() != nullptr) {
    text = word->text();
  }
  if (word->display_flag(DF_BLAMER) &&
      !(word_res->blamer_bundle != nullptr &&
        word_res->blamer_bundle->incorrect_result_reason() == IRR_CORRECT)) {
    text = "";
    const BlamerBundle *blamer_bundle = word_res->blamer_bundle;
    if (blamer_bundle == nullptr) {
      text += "NULL";
    } else {
      text = blamer_bundle->TruthString();
    }
    text += " -> ";
    STRING best_choice_str;
    if (word_res->best_choice == nullptr) {
      best_choice_str = "NULL";
    } else {
      word_res->best_choice->string_and_lengths(&best_choice_str, nullptr);
    }
    text += best_choice_str;
    IncorrectResultReason reason = (blamer_bundle == nullptr) ?
        IRR_PAGE_LAYOUT : blamer_bundle->incorrect_result_reason();
    ASSERT_HOST(reason < IRR_NUM_REASONS);
    blame += " [";
    blame += BlamerBundle::IncorrectReasonName(reason);
    blame += "]";
  }
  if (text.length() > 0) {
    word_bb = word->bounding_box();
    image_win->Pen(ScrollView::RED);
    word_height = word_bb.height();
    int text_height = 0.50 * word_height;
    if (text_height > 20) text_height = 20;
    image_win->TextAttributes("Arial", text_height, false, false, false);
    shift = (word_height < word_bb.width()) ? 0.25 * word_height : 0.0f;
    image_win->Text(word_bb.left() + shift,
                    word_bb.bottom() + 0.25 * word_height, text.c_str());
    if (blame.length() > 0) {
      image_win->Text(word_bb.left() + shift,
                      word_bb.bottom() + 0.25 * word_height - text_height,
                      blame.c_str());
    }

    displayed_something = true;
  }

  if (!displayed_something)      // display BBox anyway
    word->bounding_box().plot(image_win,
     static_cast<ScrollView::Color>((int32_t) editor_image_word_bb_color),
     static_cast<ScrollView::Color>((int32_t)
      editor_image_word_bb_color));
  return true;
}
}  // namespace tesseract
#endif // !GRAPHICS_DISABLED

namespace tesseract {
/**
 * word_dumper()
 *
 * Dump members to the debug window
 */
bool Tesseract::word_dumper(PAGE_RES_IT* pr_it) {
  if (pr_it->block()->block != nullptr) {
    tprintf("\nBlock data...\n");
    pr_it->block()->block->print(nullptr, false);
  }
  tprintf("\nRow data...\n");
  pr_it->row()->row->print(nullptr);
  tprintf("\nWord data...\n");
  WERD_RES* word_res = pr_it->word();
  word_res->word->print();
  if (word_res->blamer_bundle != nullptr && wordrec_debug_blamer &&
      word_res->blamer_bundle->incorrect_result_reason() != IRR_CORRECT) {
    tprintf("Current blamer debug: %s\n",
            word_res->blamer_bundle->debug().c_str());
  }
  return true;
}

#ifndef GRAPHICS_DISABLED
/**
 * word_set_display()  Word processor
 *
 * Display word according to current display mode settings
 */
bool Tesseract::word_set_display(PAGE_RES_IT* pr_it) {
  WERD* word = pr_it->word()->word;
  word->set_display_flag(DF_BOX, word_display_mode[DF_BOX]);
  word->set_display_flag(DF_TEXT, word_display_mode[DF_TEXT]);
  word->set_display_flag(DF_POLYGONAL, word_display_mode[DF_POLYGONAL]);
  word->set_display_flag(DF_EDGE_STEP, word_display_mode[DF_EDGE_STEP]);
  word->set_display_flag(DF_BN_POLYGONAL,
    word_display_mode[DF_BN_POLYGONAL]);
  word->set_display_flag(DF_BLAMER, word_display_mode[DF_BLAMER]);
  return word_display(pr_it);
}


// page_res is non-const because the iterator doesn't know if you are going
// to change the items it points to! Really a const here though.
void Tesseract::blob_feature_display(PAGE_RES* page_res,
                                     const TBOX& selection_box) {
#ifndef DISABLED_LEGACY_ENGINE
  PAGE_RES_IT* it = make_pseudo_word(page_res, selection_box);
  if (it != nullptr) {
    WERD_RES* word_res = it->word();
    word_res->x_height = it->row()->row->x_height();
    word_res->SetupForRecognition(unicharset, this, BestPix(),
                                  tessedit_ocr_engine_mode, nullptr,
                                  classify_bln_numeric_mode,
                                  textord_use_cjk_fp_model,
                                  poly_allow_detailed_fx,
                                  it->row()->row, it->block()->block);
    TWERD* bln_word = word_res->chopped_word;
    TBLOB* bln_blob = bln_word->blobs[0];
    INT_FX_RESULT_STRUCT fx_info;
    std::vector<INT_FEATURE_STRUCT> bl_features;
    std::vector<INT_FEATURE_STRUCT> cn_features;
    Classify::ExtractFeatures(*bln_blob, classify_nonlinear_norm, &bl_features,
                              &cn_features, &fx_info, nullptr);
    // Display baseline features.
    ScrollView* bl_win = CreateFeatureSpaceWindow("BL Features", 512, 0);
    ClearFeatureSpaceWindow(baseline, bl_win);
    for (int f = 0; f < bl_features.size(); ++f)
      RenderIntFeature(bl_win, &bl_features[f], ScrollView::GREEN);
    bl_win->Update();
    // Display cn features.
    ScrollView* cn_win = CreateFeatureSpaceWindow("CN Features", 512, 0);
    ClearFeatureSpaceWindow(character, cn_win);
    for (int f = 0; f < cn_features.size(); ++f)
      RenderIntFeature(cn_win, &cn_features[f], ScrollView::GREEN);
    cn_win->Update();

    it->DeleteCurrentWord();
    delete it;
  }
#endif  // ndef DISABLED_LEGACY_ENGINE
}

#endif // !GRAPHICS_DISABLED

}  // namespace tesseract
