/* gf_output.c: write objects to one GF file.

Copyright (C) 1992 Free Software Foundation, Inc.

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, or (at your option)
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "config.h"

#include "file-output.h"
#include "gf.h"
#include "scaled-num.h"

#include "gf_opcodes.h"


/* The file we are writing to.  */
static FILE *gf_output_file = NULL;
static string gf_output_filename;


/* The bounding box of all the characters we have written.  The values
   here should be the largest and smallest possible integers.  */
static bounding_box_type font_bb
  = { INT_MAX, INT_MIN, INT_MAX, INT_MIN };


/* We keep track of the character locator information as we output
   characters, to save the caller from doing the bookkeeping.  */
static gf_char_locator_type char_loc[MAX_CHARCODE + 1];

static void init_locators (void);
static void put_locators (void);


/* Other parts of the GF file that deserve their own routines.  */
static void put_boc (gf_char_type);
static void put_bitmap (bitmap_type);


/* Subroutines for writing the bitmap.  */
static void put_paint (unsigned);
static void put_skip (unsigned);
static void put_new_row (unsigned);

/* Low-level output.  These macros call the corresponding routines in
   kbase, using the static variables for the input file and filename.  */

#define GF_FTELL() xftell (gf_output_file, gf_output_filename)
#define GF_PUT_BYTE(b) put_byte (b, gf_output_file, gf_output_filename)
#define GF_PUT_TWO(b) put_two (b, gf_output_file, gf_output_filename)
#define GF_PUT_THREE(n) put_three (n, gf_output_file, gf_output_filename)
#define GF_PUT_FOUR(n) put_four (n, gf_output_file, gf_output_filename)
#define GF_PUT_SIGNED_FOUR(n) \
  put_signed_four (n, gf_output_file, gf_output_filename);

/* Routines to start and end writing a file.  (For the user to call.)
   We make sure the caller can't have two output files open
   simultaneously. */

boolean
gf_open_output_file (string filename)
{
  assert (gf_output_file == NULL);

  gf_output_filename = filename;
  gf_output_file = fopen (filename, "w");

  if (gf_output_file != NULL)
    {
      init_locators ();
      return true;
    }
  else
    return false;
}


void
gf_close_output_file ()
{
  assert (gf_output_file != NULL);

  xfclose (gf_output_file, gf_output_filename);

  gf_output_filename = NULL;
  gf_output_file = NULL;
}

/* Write the preamble.  */

void
gf_put_preamble (string comment)
{
  unsigned comment_length, c;

  assert (gf_output_file != NULL);

  GF_PUT_BYTE (PRE);
  GF_PUT_BYTE (GF_ID);

  comment_length = strlen (comment);

  if (comment_length > 255)
    comment_length = 255;

  GF_PUT_BYTE (comment_length);
  /* We can't just output all the characters in the string, since the
     string might have been too long.  */
  for (c = 0; c < comment_length; c++)
    GF_PUT_BYTE (*(comment + c));
}

/* Write the postamble.  */

void
gf_put_postamble (fix_word design_size, real h_resolution, real v_resolution)
{
  byte_count_type post_ptr;
  unsigned i;

  assert (gf_output_file != NULL);

  fflush (gf_output_file);
  post_ptr = GF_FTELL ();
  GF_PUT_BYTE (POST);
  GF_PUT_FOUR (post_ptr);   /* No specials before the postamble.  */
  GF_PUT_FOUR (design_size);
  GF_PUT_FOUR (0);		/* Don't bother with a checksum.  */

  /* The resolution values are given to us in pixels per inch, but we
     must write them in pixels per point (as well as scaling them).  */
  GF_PUT_FOUR (real_to_scaled (h_resolution / POINTS_PER_INCH));
  GF_PUT_FOUR (real_to_scaled (h_resolution / POINTS_PER_INCH));

  GF_PUT_SIGNED_FOUR (MIN_COL (font_bb));
  GF_PUT_SIGNED_FOUR (MAX_COL (font_bb));
  GF_PUT_SIGNED_FOUR (MIN_ROW (font_bb));
  GF_PUT_SIGNED_FOUR (MAX_ROW (font_bb));

  put_locators ();

  GF_PUT_BYTE (POST_POST);
  GF_PUT_FOUR (post_ptr);
  GF_PUT_BYTE (GF_ID);

  for (i = 0; i < 4; i++)
    GF_PUT_BYTE (GF_SIGNATURE);
}


/* Here we output the information we have accumulated in the `char_loc'
   array.  GF format allows for two different `char_loc' commands, one
   more efficient (but less general) than the other.  We use the
   appropriate one.  */

static void
put_locators ()
{
  unsigned this_char;

  for (this_char = 0; this_char <= MAX_CHARCODE; this_char++)
    {
      gf_char_locator_type this_char_loc = char_loc[this_char];

      if (this_char_loc.char_pointer != NULL_BYTE_PTR)
	{
	  int h_escapement = this_char_loc.h_escapement;

	  /* Decide if we can use CHAR_LOC0.  */
	  if (h_escapement >= 0 && h_escapement < ONE_BYTE_BIG)
	    {
	      GF_PUT_BYTE (CHAR_LOC0);
	      GF_PUT_BYTE (this_char);
	      GF_PUT_BYTE (h_escapement);
	    }
	  else
	    {
	      GF_PUT_BYTE (CHAR_LOC);
	      GF_PUT_BYTE (this_char);
	      GF_PUT_FOUR (real_to_scaled ((real) h_escapement));
	      GF_PUT_FOUR (0);	/* No vertical escapement.  */
	    }

	  GF_PUT_FOUR (this_char_loc.tfm_width);
	  GF_PUT_SIGNED_FOUR (this_char_loc.char_pointer);
	}
    }
}


/* We must initialize the character locators to all null values.  */

static void
init_locators ()
{
  unsigned this_char;

  for (this_char = 0; this_char <= MAX_CHARCODE; this_char++)
    char_loc[this_char].char_pointer = NULL_BYTE_PTR;
}

/* Do what's necessary to prepare for outputting a character.  */

static void
start_put_char (charcode_type charcode, unsigned h_escapement,
                fix_word tfm_width, bounding_box_type char_bb)
{
  gf_char_locator_type *this_char_loc = &(char_loc[charcode]);

  assert (gf_output_file != NULL);

  if (this_char_loc->char_pointer != NULL_BYTE_PTR)
    {
      WARNING1 ("gf_put_char: Character %u already output", charcode);
      return;
    }

  /* Update the character locator information (part of the postamble).  */
  this_char_loc->char_pointer = GF_FTELL ();
  this_char_loc->h_escapement = h_escapement;
  this_char_loc->tfm_width = tfm_width;
  this_char_loc->charcode = charcode;

  /* Update the font bounding box.  */
  if (MIN_COL (char_bb) < MIN_COL (font_bb))
    MIN_COL (font_bb) = MIN_COL (char_bb);
  if (MAX_COL (char_bb) > MAX_COL (font_bb))
    MAX_COL (font_bb) = MAX_COL (char_bb);
  if (MIN_ROW (char_bb) < MIN_ROW (font_bb))
    MIN_ROW (font_bb) = MIN_ROW (char_bb);
  if (MAX_ROW (char_bb) > MAX_ROW (font_bb))
    MAX_ROW (font_bb) = MAX_ROW (char_bb);
}


/* Output the character GF_CHAR.  */

void
gf_put_char (gf_char_type gf_char)
{
  start_put_char (GF_CHARCODE (gf_char), GF_H_ESCAPEMENT (gf_char),
                  GF_TFM_WIDTH (gf_char), GF_CHAR_BB (gf_char));
  put_boc (gf_char);
  put_bitmap (GF_BITMAP (gf_char));
  GF_PUT_BYTE (EOC);
}


/* Output the BOC command that begins each character.  BOC commands come
   in two flavors, one of which takes less space but is less general.  */

static void
put_boc (gf_char_type gf_char)
{
  signed_4_bytes row_delta = (GF_CHAR_MAX_ROW (gf_char)
                              - GF_CHAR_MIN_ROW (gf_char));
  signed_4_bytes col_delta = (GF_CHAR_MAX_COL (gf_char)
                              - GF_CHAR_MIN_COL (gf_char));

  /* Decide if we can use BOC1, the abbreviated form.  */
  if (row_delta < ONE_BYTE_BIG && row_delta >= 0
      && col_delta < ONE_BYTE_BIG && col_delta >= 0
      && GF_CHAR_MAX_ROW (gf_char) < ONE_BYTE_BIG
      && GF_CHAR_MAX_ROW (gf_char) >= 0
      && GF_CHAR_MAX_COL (gf_char) < ONE_BYTE_BIG
      && GF_CHAR_MAX_COL (gf_char) >= 0)
    {
      GF_PUT_BYTE (BOC1);
      GF_PUT_BYTE (GF_CHARCODE (gf_char));
      GF_PUT_BYTE (col_delta);
      GF_PUT_BYTE (GF_CHAR_MAX_COL (gf_char));
      GF_PUT_BYTE (row_delta);
      GF_PUT_BYTE (GF_CHAR_MAX_ROW (gf_char));
    }
  else
    {
      GF_PUT_BYTE (BOC);
      GF_PUT_FOUR (GF_CHARCODE (gf_char));
      GF_PUT_SIGNED_FOUR (NULL_BYTE_PTR);   /* We never have a backpointer.  */
      GF_PUT_SIGNED_FOUR (GF_CHAR_MIN_COL (gf_char));
      GF_PUT_SIGNED_FOUR (GF_CHAR_MAX_COL (gf_char));
      GF_PUT_SIGNED_FOUR (GF_CHAR_MIN_ROW (gf_char));
      GF_PUT_SIGNED_FOUR (GF_CHAR_MAX_ROW (gf_char));
    }
}


/* Bitmaps in GF format are run-encoded.  There are three commands:
   `paint', which writes a run of pixels (either black or white);
   `skip', which leaves entirely blank rows; and `new_row', which begins
   a new row with the first black pixel some number of pixels from the
   left edge.  */

static void
put_bitmap (bitmap_type b)
{
  unsigned run = 0;		/* Length of the current run.  */
  one_byte current_color = WHITE;
  boolean recent_eol = false;	/* Have we seen only white since eol?  */
  unsigned all_white = 0;	/* Count of entirely blank rows.  */
  unsigned this_row, this_col;

  for (this_row = 0; this_row < BITMAP_HEIGHT (b); this_row++)
    {
      for (this_col = 0; this_col < BITMAP_WIDTH (b); this_col++)
	{
	  one_byte p = BITMAP_PIXEL (b, this_row, this_col);

	  if (p == current_color)
            run++;
	  else
	    {
	      if (recent_eol)
		{		/* First transition to black on new row?  */
		  recent_eol = false;
		  put_skip (all_white);
		  all_white = 0;
		  /* We've seen only white up to now, since
                     `current_color' is zero after each row.  */
		  put_new_row (run);
		}
	      else
               put_paint (run);

	      current_color = p;
	      run = 1;
	    }
	}

      if (current_color == BLACK)
	{
	  put_paint (run);	/* The row ended with black.  */
	  current_color = WHITE;
	}

      else if (recent_eol)
        all_white++;		/* The row was entirely white.  */

      recent_eol = true;
      run = 0;
    }
}


/* Routines to output the actual GF commands that describe the bitmap. 
   We use the shortest possible command.  */

static void
put_paint (unsigned run)
{
   if (run < 64)
     GF_PUT_BYTE (run);
   else if (run < ONE_BYTE_BIG)
     {
       GF_PUT_BYTE (PAINT1);
       GF_PUT_BYTE (run);
     }
   else if (run < TWO_BYTES_BIG)
     {
       GF_PUT_BYTE (PAINT2);
       GF_PUT_TWO (run);
     }
   else if (run < THREE_BYTES_BIG)
     {
       GF_PUT_BYTE (PAINT3);
       GF_PUT_THREE (run);
     }
   else
     FATAL1 ("put_paint: Run of %u pixels too long for GF format", run);
}


static void
put_skip (unsigned all_white)
{
  if (all_white == 0)
    ; /* Do nothing.  */
  else if (all_white == 1)
    GF_PUT_BYTE (SKIP0);
  else if (all_white < ONE_BYTE_BIG)
    {
      GF_PUT_BYTE (SKIP1);
      GF_PUT_BYTE (all_white - 1);
    }
  else if (all_white < TWO_BYTES_BIG)
    {
      GF_PUT_BYTE (SKIP2);
      GF_PUT_TWO (all_white - 1);
    }
  else if (all_white < THREE_BYTES_BIG)
    {
      GF_PUT_BYTE (SKIP3);
      GF_PUT_THREE (all_white - 1);
    }
  else
    FATAL1 ("put_skip: %u rows is too many for GF format", all_white);
}


static void
put_new_row (unsigned indent)
{
  if (indent <= 164)
    GF_PUT_BYTE (NEW_ROW_0 + indent);
  else
    {
      /* Too large for a new_row command; have to skip and then paint.  */
      GF_PUT_BYTE (SKIP0);
      put_paint (indent);
    }
}


/* Output a raw character.  */

void
gf_put_raw_char (raw_char_type raw_char)
{
  one_byte charcode = GF_CHARCODE (raw_char);
  
  start_put_char (charcode, GF_H_ESCAPEMENT (raw_char),
                  GF_TFM_WIDTH (raw_char), GF_CHAR_BB (raw_char));
  put_n_bytes (RAW_CHAR_USED (raw_char), RAW_CHAR_BYTES (raw_char),
               gf_output_file, gf_output_filename);
}
