/**********************************************************************
 * File:        charcut.cpp  (Formerly charclip.c)
 * Description: Code for character clipping
 * Author:      Phil Cheatle
 * Created:     Wed Nov 11 08:35:15 GMT 1992
 *
 * (C) Copyright 1992, 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 "mfcpch.h"
#include          "charcut.h"
#include          "imgs.h"

#define LARGEST(a,b) ( (a) > (b) ? (a) : (b) )
#define SMALLEST(a,b) ( (a) > (b) ? (b) : (a) )
#define BUG_OFFSET 1
#define EXTERN

EXTERN INT_VAR (pix_word_margin, 3, "How far outside word BB to grow");

extern IMAGE page_image;

ELISTIZE (PIXROW)
/*************************************************************************
 * PIXROW::PIXROW()
 *
 * Constructor for a specified size PIXROW from a blob
 *************************************************************************/
PIXROW::PIXROW(inT16 pos, inT16 count, PBLOB *blob) {
  OUTLINE_LIST *outline_list;
  OUTLINE_IT outline_it;
  POLYPT_LIST *pts_list;
  POLYPT_IT pts_it;
  inT16 i;
  FCOORD pt;
  FCOORD vec;
  float y_coord;
  inT16 x_coord;

  row_offset = pos;
  row_count = count;
  min = (inT16 *) alloc_mem (count * sizeof (inT16));
  max = (inT16 *) alloc_mem (count * sizeof (inT16));
  outline_list = blob->out_list ();
  outline_it.set_to_list (outline_list);

  for (i = 0; i < count; i++) {
    min[i] = MAX_INT16 - 1;
    max[i] = -MAX_INT16 + 1;
    y_coord = row_offset + i + 0.5;
    for (outline_it.mark_cycle_pt ();
    !outline_it.cycled_list (); outline_it.forward ()) {
      pts_list = outline_it.data ()->polypts ();
      pts_it.set_to_list (pts_list);
      for (pts_it.mark_cycle_pt ();
      !pts_it.cycled_list (); pts_it.forward ()) {
        pt = pts_it.data ()->pos;
        vec = pts_it.data ()->vec;
        if ((vec.y () != 0) &&
          (((pt.y () <= y_coord) && (pt.y () + vec.y () >= y_coord))
          || ((pt.y () >= y_coord)
        && (pt.y () + vec.y () <= y_coord)))) {
          /* The segment crosses y_coord so find x-point and check for min/max. */
          x_coord = (inT16) floor ((y_coord -
            pt.y ()) * vec.x () / vec.y () +
            pt.x () + 0.5);
          if (x_coord < min[i])
            min[i] = x_coord;
          x_coord--;             //to get pix to left of line
          if (x_coord > max[i])
            max[i] = x_coord;
        }
      }
    }
  }
}

/*************************************************************************
 * PIXROW::bounding_box()
 *
 * Generate bounding box for blob image
 *************************************************************************/

bool PIXROW::bad_box(  //return true if box exceeds image
                     int xsize,
                     int ysize) const {
  TBOX bbox = bounding_box ();
  if (bbox.left () < 0 || bbox.right () > xsize
  || bbox.top () > ysize || bbox.bottom () < 0) {
    tprintf("Box (%d,%d)->(%d,%d) bad compared to %d,%d\n",
            bbox.left(),bbox.bottom(), bbox.right(), bbox.top(),
            xsize, ysize);
    return true;
  }
  return false;
}


/*************************************************************************
 * PIXROW::bounding_box()
 *
 * Generate bounding box for blob image
 *************************************************************************/

TBOX PIXROW::bounding_box() const {
  inT16 i;
  inT16 y_coord;
  inT16 min_x = MAX_INT16 - 1;
  inT16 min_y = MAX_INT16 - 1;
  inT16 max_x = -MAX_INT16 + 1;
  inT16 max_y = -MAX_INT16 + 1;

  for (i = 0; i < row_count; i++) {
    y_coord = row_offset + i;
    if (min[i] <= max[i]) {
      if (y_coord < min_y)
        min_y = y_coord;
      if (y_coord + 1 > max_y)
        max_y = y_coord + 1;
      if (min[i] < min_x)
        min_x = min[i];
      if (max[i] + 1 > max_x)
        max_x = max[i] + 1;
    }
  }
  if (min_x > max_x || min_y > max_y)
    return TBOX ();
  else
    return TBOX (ICOORD (min_x, min_y), ICOORD (max_x, max_y));
}


/*************************************************************************
 * PIXROW::contract()
 *
 * Reduce the mins and maxs so that they end on black pixels
 *************************************************************************/

void PIXROW::contract(                         //image array
                      IMAGELINE *imlines,
                      inT16 x_offset,          //of pixels[0]
                      inT16 foreground_colour  //0 or 1
                     ) {
  inT16 i;
  uinT8 *line_pixels;

  for (i = 0; i < row_count; i++) {
    if (min[i] > max[i])
      continue;

    line_pixels = imlines[i].pixels;
    while (line_pixels[min[i] - x_offset] != foreground_colour) {
      if (min[i] == max[i]) {
        min[i] = MAX_INT16 - 1;
        max[i] = -MAX_INT16 + 1;
        goto nextline;
      }
      else
        min[i]++;
    }
    while (line_pixels[max[i] - x_offset] != foreground_colour) {
      if (min[i] == max[i]) {
        min[i] = MAX_INT16 - 1;
        max[i] = -MAX_INT16 + 1;
        goto nextline;
      }
      else
        max[i]--;
    }
    nextline:;
    //goto label!
  }
}


/*************************************************************************
 * PIXROW::extend()
 *
 * 1 pixel extension in each direction to cover extra black area
 *************************************************************************/

BOOL8 PIXROW::extend(               //image array
                     IMAGELINE *imlines,
                     TBOX &imbox,
                     PIXROW *prev,  //for prev blob
                     PIXROW *next,  //for next blob
                     inT16 foreground_colour) {
  inT16 i;
  inT16 x_offset = imbox.left ();
  inT16 limit;
  inT16 left_limit;
  inT16 right_limit;
  uinT8 *pixels = NULL;
  uinT8 *pixels_below = NULL;    //row below current
  uinT8 *pixels_above = NULL;    //row above current
  BOOL8 changed = FALSE;

  pixels_above = imlines[0].pixels;
  for (i = 0; i < row_count; i++) {
    pixels_below = pixels;
    pixels = pixels_above;
    if (i < (row_count - 1))
      pixels_above = imlines[i + 1].pixels;
    else
      pixels_above = NULL;

    /* Extend Left by one pixel*/
    if (prev == NULL || prev->max[i] < prev->min[i])
      limit = imbox.left ();
    else
      limit = prev->max[i] + 1;
    if ((min[i] <= max[i]) &&
      (min[i] > limit) &&
    (pixels[min[i] - 1 - x_offset] == foreground_colour)) {
      min[i]--;
      changed = TRUE;
    }

    /* Extend Right by one pixel*/
    if (next == NULL || next->min[i] > next->max[i])
      limit = imbox.right () - 1;//-1 to index inside pix
    else
      limit = next->min[i] - 1;
    if ((min[i] <= max[i]) &&
      (max[i] < limit) &&
    (pixels[max[i] + 1 - x_offset] == foreground_colour)) {
      max[i]++;
      changed = TRUE;
    }

    /* Extend down by one row */
    if (pixels_below != NULL) {
      if (min[i] < min[i - 1]) { //row goes left of row below
        if (prev == NULL || prev->max[i - 1] < prev->min[i - 1])
          left_limit = min[i];
        else
          left_limit = LARGEST (min[i], prev->max[i - 1] + 1);
      }
      else
        left_limit = min[i - 1];

      if (max[i] > max[i - 1]) { //row goes right of row below
        if (next == NULL || next->min[i - 1] > next->max[i - 1])
          right_limit = max[i];
        else
          right_limit = SMALLEST (max[i], next->min[i - 1] - 1);
      }
      else
        right_limit = max[i - 1];

      while ((left_limit <= right_limit) &&
        (pixels_below[left_limit - x_offset] != foreground_colour))
        left_limit++;            //find black extremity

      if ((left_limit <= right_limit) && (left_limit < min[i - 1])) {
        min[i - 1] = left_limit; //widen left if poss
        changed = TRUE;
      }

      while ((left_limit <= right_limit) &&
        (pixels_below[right_limit - x_offset] != foreground_colour))
        right_limit--;           //find black extremity

      if ((left_limit <= right_limit) && (right_limit > max[i - 1])) {
        max[i - 1] = right_limit;//widen right if poss
        changed = TRUE;
      }
    }

    /* Extend up by one row */
    if (pixels_above != NULL) {
      if (min[i] < min[i + 1]) { //row goes left of row above
        if (prev == NULL || prev->min[i + 1] > prev->max[i + 1])
          left_limit = min[i];
        else
          left_limit = LARGEST (min[i], prev->max[i + 1] + 1);
      }
      else
        left_limit = min[i + 1];

      if (max[i] > max[i + 1]) { //row goes right of row above
        if (next == NULL || next->min[i + 1] > next->max[i + 1])
          right_limit = max[i];
        else
          right_limit = SMALLEST (max[i], next->min[i + 1] - 1);
      }
      else
        right_limit = max[i + 1];

      while ((left_limit <= right_limit) &&
        (pixels_above[left_limit - x_offset] != foreground_colour))
        left_limit++;            //find black extremity

      if ((left_limit <= right_limit) && (left_limit < min[i + 1])) {
        min[i + 1] = left_limit; //widen left if poss
        changed = TRUE;
      }

      while ((left_limit <= right_limit) &&
        (pixels_above[right_limit - x_offset] != foreground_colour))
        right_limit--;           //find black extremity

      if ((left_limit <= right_limit) && (right_limit > max[i + 1])) {
        max[i + 1] = right_limit;//widen right if poss
        changed = TRUE;
      }
    }
  }
  return changed;
}


/*************************************************************************
 * PIXROW::char_clip_image()
 * Cut out a sub image for a character
 *************************************************************************/

void PIXROW::char_clip_image(                     //box of imlines extnt
                             IMAGELINE *imlines,
                             TBOX &im_box,
                             ROW *row,            //row containing word
                             IMAGE &clip_image,   //unscaled sq subimage
                             float &baseline_pos  //baseline ht in image
                            ) {
  inT16 clip_image_xsize;        //sub image x size
  inT16 clip_image_ysize;        //sub image y size
  inT16 x_shift;                 //from pixrow to subim
  inT16 y_shift;                 //from pixrow to subim
  TBOX char_pix_box;              //bbox of char pixels
  inT16 y_dest;
  inT16 x_min;
  inT16 x_max;
  inT16 x_min_dest;
  inT16 x_max_dest;
  inT16 x_width;
  inT16 y;

  clip_image_xsize = clip_image.get_xsize ();
  clip_image_ysize = clip_image.get_ysize ();

  char_pix_box = bounding_box ();
  /*
    The y shift is calculated by first finding the coord of the bottom of the
    image relative to the image lines. Then reducing this so by the amount
    relative to the clip image size, necessary to vertically position the
    character.
  */
  y_shift = char_pix_box.bottom () - row_offset -
    (inT16) floor ((clip_image_ysize - char_pix_box.height () + 0.5) / 2);

  /*
    The x_shift is the shift to be applied to the page coord in the pixrow to
    generate a centred char in the clip image.  Thus the left hand edge of the
    char is shifted to the margin width of the centred character.
  */
  x_shift = char_pix_box.left () -
    (inT16) floor ((clip_image_xsize - char_pix_box.width () + 0.5) / 2);

  for (y = 0; y < row_count; y++) {
    /*
      Check that there is something in this row of the source that will fit in the
      sub image.  If there is, reduce x range if necessary, then copy it
    */
    y_dest = y - y_shift;
    if ((min[y] <= max[y]) && (y_dest >= 0) && (y_dest < clip_image_ysize)) {
      x_min = min[y];
      x_min_dest = x_min - x_shift;
      if (x_min_dest < 0) {
        x_min = x_min - x_min_dest;
        x_min_dest = 0;
      }
      x_max = max[y];
      x_max_dest = x_max - x_shift;
      if (x_max_dest > clip_image_xsize - 1) {
        x_max = x_max - (x_max_dest - (clip_image_xsize - 1));
        x_max_dest = clip_image_xsize - 1;
      }
      x_width = x_max - x_min + 1;
      if (x_width > 0) {
        x_min -= im_box.left ();
                                 //offset pixel ptr
        imlines[y].pixels += x_min;
        clip_image.put_line (x_min_dest, y_dest, x_width, imlines + y,
          0);
        imlines[y].init ();      //reset pixel ptr
      }
    }
  }
  /*
    Baseline position relative to clip image: First find the baseline relative
    to the page origin at the x coord of the centre of the character. Then
    make this relative to the character bottom. Finally shift by the margin
    between the bottom of the character and the bottom of the clip image.
  */
  if (row == NULL)
    baseline_pos = 0;            //Not needed
  else
    baseline_pos = row->base_line ((char_pix_box.left () +
      char_pix_box.right ()) / 2.0)
      - char_pix_box.bottom ()
      + ((clip_image_ysize - char_pix_box.height ()) / 2);
}


/*************************************************************************
 * char_clip_word()
 *
 * Generate a PIXROW_LIST with one element for each blob in the word, together
 * with the image lines for the whole word.
 *************************************************************************/

void char_clip_word(                            //
                    WERD *word,                 //word to be processed
                    IMAGE &bin_image,           //whole image
                    PIXROW_LIST *&pixrow_list,  //pixrows built
                    IMAGELINE *&imlines,        //lines cut from image
                    TBOX &pix_box                //box defining imlines
                   ) {
  TBOX word_box = word->bounding_box ();
  PBLOB_LIST *blob_list;
  PBLOB_IT blob_it;
  PIXROW_IT pixrow_it;
  inT16 pix_offset;              //Y pos of pixrow[0]
  inT16 row_height;              //No of pix rows
  inT16 imlines_x_offset;
  PIXROW *prev;
  PIXROW *next;
  PIXROW *current;
  BOOL8 changed;                 //still improving
  BOOL8 just_changed;            //still improving
  inT16 iteration_count = 0;
  inT16 foreground_colour;

  if (word->flag (W_INVERSE))
    foreground_colour = 1;
  else
    foreground_colour = 0;

  /* Define region for max pixrow expansion */
  pix_box = word_box;
  pix_box.move_bottom_edge (-pix_word_margin);
  pix_box.move_top_edge (pix_word_margin);
  pix_box.move_left_edge (-pix_word_margin);
  pix_box.move_right_edge (pix_word_margin);
  pix_box -= TBOX (ICOORD (0, 0 + BUG_OFFSET),
    ICOORD (bin_image.get_xsize (),
    bin_image.get_ysize () - BUG_OFFSET));

  /* Generate pixrows list */

  pix_offset = pix_box.bottom ();
  row_height = pix_box.height ();
  blob_list = word->blob_list ();
  blob_it.set_to_list (blob_list);

  pixrow_list = new PIXROW_LIST;
  pixrow_it.set_to_list (pixrow_list);

  for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) {
    PIXROW *row = new PIXROW (pix_offset, row_height, blob_it.data ());
    ASSERT_HOST (!row->
      bad_box (bin_image.get_xsize (), bin_image.get_ysize ()));
    pixrow_it.add_after_then_move (row);
  }

  imlines = generate_imlines (bin_image, pix_box);

  /* Contract pixrows - shrink min and max back to black pixels */

  imlines_x_offset = pix_box.left ();

  pixrow_it.move_to_first ();
  for (pixrow_it.mark_cycle_pt ();
  !pixrow_it.cycled_list (); pixrow_it.forward ()) {
    ASSERT_HOST (!pixrow_it.data ()->
      bad_box (bin_image.get_xsize (), bin_image.get_ysize ()));
    pixrow_it.data ()->contract (imlines, imlines_x_offset,
      foreground_colour);
    ASSERT_HOST (!pixrow_it.data ()->
      bad_box (bin_image.get_xsize (), bin_image.get_ysize ()));
  }

  /* Expand pixrows iteratively 1 pixel at a time */
  do {
    changed = FALSE;
    pixrow_it.move_to_first ();
    prev = NULL;
    current = NULL;
    next = pixrow_it.data ();
    for (pixrow_it.mark_cycle_pt ();
    !pixrow_it.cycled_list (); pixrow_it.forward ()) {
      prev = current;
      current = next;
      if (pixrow_it.at_last ())
        next = NULL;
      else
        next = pixrow_it.data_relative (1);
      just_changed = current->extend (imlines, pix_box, prev, next,
        foreground_colour);
      ASSERT_HOST (!current->
        bad_box (bin_image.get_xsize (),
        bin_image.get_ysize ()));
      changed = changed || just_changed;
    }
    iteration_count++;
  }
  while (changed);
}


/*************************************************************************
 * generate_imlines()
 * Get an array of IMAGELINES  holding a portion of an image
 *************************************************************************/

IMAGELINE *generate_imlines(                   //get some imagelines
                            IMAGE &bin_image,  //from here
                            TBOX &pix_box) {
  IMAGELINE *imlines;            //array of lines
  int i;

  imlines = new IMAGELINE[pix_box.height ()];
  for (i = 0; i < pix_box.height (); i++) {
    imlines[i].init (pix_box.width ());
                                 //coord to start at
    bin_image.fast_get_line (pix_box.left (),
      pix_box.bottom () + i + BUG_OFFSET,
    //line to get
      pix_box.width (),          //width to get
      imlines + i);              //dest imline
  }
  return imlines;
}

