/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 2009,2010 Free Software Foundation, Inc.
 *
 *  GRUB 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 3 of the License, or
 *  (at your option) any later version.
 *
 *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>
#include <grub/types.h>
#include <grub/misc.h>
#include <grub/emu/misc.h>
#include <grub/util/misc.h>
#include <grub/misc.h>
#include <grub/i18n.h>
#include <grub/fontformat.h>
#include <grub/font.h>
#include <grub/unicode.h>

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

#ifndef GRUB_BUILD
#define _GNU_SOURCE	1
#pragma GCC diagnostic ignored "-Wmissing-prototypes"
#pragma GCC diagnostic ignored "-Wmissing-declarations"
#include <argp.h>
#pragma GCC diagnostic error "-Wmissing-prototypes"
#pragma GCC diagnostic error "-Wmissing-declarations"
#endif
#include <assert.h>

#include <errno.h>

#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_TRUETYPE_TAGS_H
#include FT_TRUETYPE_TABLES_H
#include FT_SYNTHESIS_H

#undef __FTERRORS_H__
#define FT_ERROR_START_LIST   const char *ft_errmsgs[] = { 
#define FT_ERRORDEF(e, v, s)  [e] = s,
#define FT_ERROR_END_LIST     };
#include FT_ERRORS_H   

#ifndef GRUB_BUILD
#include "progname.h"
#endif

#ifdef GRUB_BUILD
#define grub_util_fopen fopen
#endif

#define GRUB_FONT_DEFAULT_SIZE		16

#define GRUB_FONT_RANGE_BLOCK		1024

struct grub_glyph_info
{
  struct grub_glyph_info *next;
  grub_uint32_t char_code;
  int width;
  int height;
  int x_ofs;
  int y_ofs;
  int device_width;
  int bitmap_size;
  grub_uint8_t *bitmap;
};

enum file_formats
{
  PF2
};

enum
  {
    GRUB_FONT_FLAG_BOLD	= 1,
    GRUB_FONT_FLAG_NOBITMAP = 2,
    GRUB_FONT_FLAG_NOHINTING = 4,
    GRUB_FONT_FLAG_FORCEHINT = 8
  };

struct grub_font_info
{
  const char *name;
  int style;
  int desc;
  int asce;
  int size;
  int max_width;
  int max_height;
  int min_y;
  int max_y;
  int flags;
  int num_range;
  grub_uint32_t *ranges;
  struct grub_glyph_info *glyphs_unsorted;
  struct grub_glyph_info *glyphs_sorted;
  int num_glyphs;
};

static int font_verbosity;

static void
add_pixel (grub_uint8_t **data, int *mask, int not_blank)
{
  if (*mask == 0)
    {
      (*data)++;
      **data = 0;
      *mask = 128;
    }

  if (not_blank)
    **data |= *mask;

  *mask >>= 1;
}

static void
add_glyph (struct grub_font_info *font_info, FT_UInt glyph_idx, FT_Face face,
	   grub_uint32_t char_code, int nocut)
{
  struct grub_glyph_info *glyph_info;
  int width, height;
  int cuttop, cutbottom, cutleft, cutright;
  grub_uint8_t *data;
  int mask, i, j, bitmap_size;
  FT_GlyphSlot glyph;
  int flag = FT_LOAD_RENDER | FT_LOAD_MONOCHROME;
  FT_Error err;

  if (font_info->flags & GRUB_FONT_FLAG_NOBITMAP)
    flag |= FT_LOAD_NO_BITMAP;

  if (font_info->flags & GRUB_FONT_FLAG_NOHINTING)
    flag |= FT_LOAD_NO_HINTING;
  else if (font_info->flags & GRUB_FONT_FLAG_FORCEHINT)
    flag |= FT_LOAD_FORCE_AUTOHINT;

  err = FT_Load_Glyph (face, glyph_idx, flag);
  if (err)
    {
      printf (_("Freetype Error %d loading glyph 0x%x for U+0x%x%s"),
	      err, glyph_idx, char_code & GRUB_FONT_CODE_CHAR_MASK,
	      char_code & GRUB_FONT_CODE_RIGHT_JOINED
	      /* TRANSLATORS: These qualifiers are used for cursive typography,
		 mainly Arabic. Note that the terms refer to the visual position
		 and not logical order and if used in left-to-right script then
		 leftmost is initial but with right-to-left script like Arabic
		 rightmost is the initial.  */
	      ? ((char_code & GRUB_FONT_CODE_LEFT_JOINED) ? _(" (medial)"):
		 _(" (leftmost)"))
	      : ((char_code & GRUB_FONT_CODE_LEFT_JOINED) ? _(" (rightmost)"):
		 ""));

      if (err > 0 && err < (signed) ARRAY_SIZE (ft_errmsgs))
	printf (": %s\n", ft_errmsgs[err]);
      else
	printf ("\n");
      return;
    }

  glyph = face->glyph;

  if (font_info->flags & GRUB_FONT_FLAG_BOLD)
    FT_GlyphSlot_Embolden (glyph);

  if (glyph->next)
    printf ("%x\n", char_code);

  if (nocut)
    cuttop = cutbottom = cutleft = cutright = 0;
  else
    {
      for (cuttop = 0; cuttop < glyph->bitmap.rows; cuttop++)
	{
	  for (j = 0; j < glyph->bitmap.width; j++)
	    if (glyph->bitmap.buffer[j / 8 + cuttop * glyph->bitmap.pitch]
		& (1 << (7 - (j & 7))))
	      break;
	  if (j != glyph->bitmap.width)
	    break;
	}

      for (cutbottom = glyph->bitmap.rows - 1; cutbottom >= 0; cutbottom--)
	{
	  for (j = 0; j < glyph->bitmap.width; j++)
	    if (glyph->bitmap.buffer[j / 8 + cutbottom * glyph->bitmap.pitch]
		& (1 << (7 - (j & 7))))
	      break;
	  if (j != glyph->bitmap.width)
	    break;
	}
      cutbottom = glyph->bitmap.rows - 1 - cutbottom;
      if (cutbottom + cuttop >= glyph->bitmap.rows)
	cutbottom = 0;

      for (cutleft = 0; cutleft < glyph->bitmap.width; cutleft++)
	{
	  for (j = 0; j < glyph->bitmap.rows; j++)
	    if (glyph->bitmap.buffer[cutleft / 8 + j * glyph->bitmap.pitch]
		& (1 << (7 - (cutleft & 7))))
	      break;
	  if (j != glyph->bitmap.rows)
	    break;
	}
      for (cutright = glyph->bitmap.width - 1; cutright >= 0; cutright--)
	{
	  for (j = 0; j < glyph->bitmap.rows; j++)
	    if (glyph->bitmap.buffer[cutright / 8 + j * glyph->bitmap.pitch]
		& (1 << (7 - (cutright & 7))))
	      break;
	  if (j != glyph->bitmap.rows)
	    break;
	}
      cutright = glyph->bitmap.width - 1 - cutright;
      if (cutright + cutleft >= glyph->bitmap.width)
	cutright = 0;
    }

  width = glyph->bitmap.width - cutleft - cutright;
  height = glyph->bitmap.rows - cutbottom - cuttop;

  bitmap_size = ((width * height + 7) / 8);
  glyph_info = xmalloc (sizeof (struct grub_glyph_info));
  glyph_info->bitmap = xmalloc (bitmap_size);
  glyph_info->bitmap_size = bitmap_size;

  glyph_info->next = font_info->glyphs_unsorted;
  font_info->glyphs_unsorted = glyph_info;
  font_info->num_glyphs++;

  glyph_info->char_code = char_code;
  glyph_info->width = width;
  glyph_info->height = height;
  glyph_info->x_ofs = glyph->bitmap_left + cutleft;
  glyph_info->y_ofs = glyph->bitmap_top - height - cuttop;
  glyph_info->device_width = glyph->metrics.horiAdvance / 64;

  if (width > font_info->max_width)
    font_info->max_width = width;

  if (height > font_info->max_height)
    font_info->max_height = height;

  if (glyph_info->y_ofs < font_info->min_y && glyph_info->y_ofs > -font_info->size)
    font_info->min_y = glyph_info->y_ofs;

  if (glyph_info->y_ofs + height > font_info->max_y)
    font_info->max_y = glyph_info->y_ofs + height;

  mask = 0;
  data = &glyph_info->bitmap[0] - 1;
  for (j = cuttop; j < height + cuttop; j++)
    for (i = cutleft; i < width + cutleft; i++)
      add_pixel (&data, &mask,
		 glyph->bitmap.buffer[i / 8 + j * glyph->bitmap.pitch] &
		 (1 << (7 - (i & 7))));
}

struct glyph_replace *subst_rightjoin, *subst_leftjoin, *subst_medijoin;

struct glyph_replace
{
  struct glyph_replace *next;
  grub_uint32_t from, to;
};

/* TODO: sort glyph_replace and use binary search if necessary.  */
static void
add_char (struct grub_font_info *font_info, FT_Face face,
	  grub_uint32_t char_code, int nocut)
{
  FT_UInt glyph_idx;
  struct glyph_replace *cur;

  glyph_idx = FT_Get_Char_Index (face, char_code);
  if (!glyph_idx)
    return;
  add_glyph (font_info, glyph_idx, face, char_code, nocut);
  for (cur = subst_rightjoin; cur; cur = cur->next)
    if (cur->from == glyph_idx)
      {
	add_glyph (font_info, cur->to, face,
		   char_code | GRUB_FONT_CODE_RIGHT_JOINED, nocut);
	break;
      }
  if (!cur && char_code >= GRUB_UNICODE_ARABIC_START
      && char_code < GRUB_UNICODE_ARABIC_END)
    {
      int i;
      for (i = 0; grub_unicode_arabic_shapes[i].code; i++)
	if (grub_unicode_arabic_shapes[i].code == char_code
	    && grub_unicode_arabic_shapes[i].right_linked)
	  {
	    FT_UInt idx2;
	    idx2 = FT_Get_Char_Index (face, grub_unicode_arabic_shapes[i]
				      .right_linked);
	    if (idx2)
	      add_glyph (font_info, idx2, face,
			 char_code | GRUB_FONT_CODE_RIGHT_JOINED, nocut);
	    break;
	  }
	      
    }

  for (cur = subst_leftjoin; cur; cur = cur->next)
    if (cur->from == glyph_idx)
      {
	add_glyph (font_info, cur->to, face,
		   char_code | GRUB_FONT_CODE_LEFT_JOINED, nocut);
	break;
      }
  if (!cur && char_code >= GRUB_UNICODE_ARABIC_START
      && char_code < GRUB_UNICODE_ARABIC_END)
    {
      int i;
      for (i = 0; grub_unicode_arabic_shapes[i].code; i++)
	if (grub_unicode_arabic_shapes[i].code == char_code
	    && grub_unicode_arabic_shapes[i].left_linked)
	  {
	    FT_UInt idx2;
	    idx2 = FT_Get_Char_Index (face, grub_unicode_arabic_shapes[i]
				      .left_linked);
	    if (idx2)
	      add_glyph (font_info, idx2, face,
			 char_code | GRUB_FONT_CODE_LEFT_JOINED, nocut);
	    break;
	  }
	      
    }
  for (cur = subst_medijoin; cur; cur = cur->next)
    if (cur->from == glyph_idx)
      {
	add_glyph (font_info, cur->to, face,
		   char_code | GRUB_FONT_CODE_LEFT_JOINED
		   | GRUB_FONT_CODE_RIGHT_JOINED, nocut);
	break;
      }
  if (!cur && char_code >= GRUB_UNICODE_ARABIC_START
      && char_code < GRUB_UNICODE_ARABIC_END)
    {
      int i;
      for (i = 0; grub_unicode_arabic_shapes[i].code; i++)
	if (grub_unicode_arabic_shapes[i].code == char_code
	    && grub_unicode_arabic_shapes[i].both_linked)
	  {
	    FT_UInt idx2;
	    idx2 = FT_Get_Char_Index (face, grub_unicode_arabic_shapes[i]
				      .both_linked);
	    if (idx2)
	      add_glyph (font_info, idx2, face,
			 char_code | GRUB_FONT_CODE_LEFT_JOINED
			 | GRUB_FONT_CODE_RIGHT_JOINED, nocut);
	    break;
	  }
	      
    }
}

struct gsub_header
{
  grub_uint32_t version;
  grub_uint16_t scripts_off;
  grub_uint16_t features_off;
  grub_uint16_t lookups_off;
} GRUB_PACKED;

struct gsub_features
{
  grub_uint16_t count;
  struct
  {
#define FEATURE_FINA 0x66696e61
#define FEATURE_INIT 0x696e6974
#define FEATURE_MEDI 0x6d656469
#define FEATURE_AALT 0x61616c74
#define FEATURE_LIGA 0x6c696761
#define FEATURE_RLIG 0x726c6967
    grub_uint32_t feature_tag;
    grub_uint16_t offset;
  } GRUB_PACKED features[0];
} GRUB_PACKED;

struct gsub_feature
{
  grub_uint16_t params;
  grub_uint16_t lookupcount;
  grub_uint16_t lookupindices[0];
} GRUB_PACKED;

struct gsub_lookup_list
{
  grub_uint16_t count;
  grub_uint16_t offsets[0];
} GRUB_PACKED;

struct gsub_lookup
{
  grub_uint16_t type;
  grub_uint16_t flag;
  grub_uint16_t subtablecount;
  grub_uint16_t subtables[0];
} GRUB_PACKED;

struct gsub_substitution
{
  grub_uint16_t type;
  grub_uint16_t coverage_off;
  union
  {
    grub_int16_t delta;
    struct
    {
      grub_int16_t count;
      grub_uint16_t repl[0];
    };
  };
} GRUB_PACKED;

struct gsub_coverage_list
{
  grub_uint16_t type;
  grub_uint16_t count;
  grub_uint16_t glyphs[0];
} GRUB_PACKED;

struct gsub_coverage_ranges
{
  grub_uint16_t type;
  grub_uint16_t count;
  struct 
  {
    grub_uint16_t start;
    grub_uint16_t end;
    grub_uint16_t start_index;
  } GRUB_PACKED ranges[0];
} GRUB_PACKED;

#define GSUB_SINGLE_SUBSTITUTION 1

#define GSUB_SUBSTITUTION_DELTA 1
#define GSUB_SUBSTITUTION_MAP 2

#define GSUB_COVERAGE_LIST 1
#define GSUB_COVERAGE_RANGE 2

#define GSUB_RTL_CHAR 1

static void
add_subst (grub_uint32_t from, grub_uint32_t to, struct glyph_replace **target)
{
  struct glyph_replace *new = xmalloc (sizeof (*new));
  new->next = *target;
  new->from = from;
  new->to = to;
  *target = new;
}

static void
subst (const struct gsub_substitution *sub, grub_uint32_t glyph,
       struct glyph_replace **target, int *i)
{
  grub_uint16_t substtype;
  substtype = grub_be_to_cpu16 (sub->type);

  if (substtype == GSUB_SUBSTITUTION_DELTA)
    add_subst (glyph, glyph + grub_be_to_cpu16 (sub->delta), target);
  else if (*i >= grub_be_to_cpu16 (sub->count))
    printf (_("Out of range substitution (%d, %d)\n"), *i,
	    grub_be_to_cpu16 (sub->count));
  else
    add_subst (glyph, grub_be_to_cpu16 (sub->repl[(*i)++]), target);
}

static void
process_cursive (struct gsub_feature *feature,
		 struct gsub_lookup_list *lookups,
		 grub_uint32_t feattag)
{
  int j, k;
  int i;
  struct glyph_replace **target = NULL;
  struct gsub_substitution *sub;

  for (j = 0; j < grub_be_to_cpu16 (feature->lookupcount); j++)
    {
      int lookup_index = grub_be_to_cpu16 (feature->lookupindices[j]);
      struct gsub_lookup *lookup;
      if (lookup_index >= grub_be_to_cpu16 (lookups->count))
	{
	  /* TRANSLATORS: "lookup" is taken directly from font specifications
	   which are formulated as "Under condition X replace LOOKUP with 
	   SUBSTITUITION".  "*/
	  printf (_("Out of range lookup: %d\n"), lookup_index);
	  continue;
	}
      lookup = (struct gsub_lookup *)
	((grub_uint8_t *) lookups 
	 + grub_be_to_cpu16 (lookups->offsets[lookup_index]));
      if (grub_be_to_cpu16 (lookup->type) != GSUB_SINGLE_SUBSTITUTION)
	{
	  printf (_("Unsupported substitution type: %d\n"),
		  grub_be_to_cpu16 (lookup->type));
	  continue;
	}		      
      if (grub_be_to_cpu16 (lookup->flag) & ~GSUB_RTL_CHAR)
	{
	  grub_util_info ("unsupported substitution flag: 0x%x",
			  grub_be_to_cpu16 (lookup->flag));
	}
      switch (feattag)
	{
	case FEATURE_INIT:
	  if (grub_be_to_cpu16 (lookup->flag) & GSUB_RTL_CHAR)
	    target = &subst_leftjoin;
	  else
	    target = &subst_rightjoin;
	  break;
	case FEATURE_FINA:
	  if (grub_be_to_cpu16 (lookup->flag) & GSUB_RTL_CHAR)
	    target = &subst_rightjoin;
	  else
	    target = &subst_leftjoin;
	  break;
	case FEATURE_MEDI:
	  target = &subst_medijoin;
	  break;	  
	}
      for (k = 0; k < grub_be_to_cpu16 (lookup->subtablecount); k++)
	{
	  sub = (struct gsub_substitution *)
	    ((grub_uint8_t *) lookup + grub_be_to_cpu16 (lookup->subtables[k]));
	  grub_uint16_t substtype;
	  substtype = grub_be_to_cpu16 (sub->type);
	  if (substtype != GSUB_SUBSTITUTION_MAP
	      && substtype != GSUB_SUBSTITUTION_DELTA)
	    {
	      printf (_("Unsupported substitution specification: %d\n"),
		      substtype);
	      continue;
	    }
	  void *coverage = (grub_uint8_t *) sub
	    + grub_be_to_cpu16 (sub->coverage_off);
	  grub_uint32_t covertype;
	  covertype = grub_be_to_cpu16 (grub_get_unaligned16 (coverage));
	  i = 0;
	  if (covertype == GSUB_COVERAGE_LIST)
	    {
	      struct gsub_coverage_list *cover = coverage;
	      int l;
	      for (l = 0; l < grub_be_to_cpu16 (cover->count); l++)
		subst (sub, grub_be_to_cpu16 (cover->glyphs[l]), target, &i);
	    }
	  else if (covertype == GSUB_COVERAGE_RANGE)
	    {
	      struct gsub_coverage_ranges *cover = coverage;
	      int l, m;
	      for (l = 0; l < grub_be_to_cpu16 (cover->count); l++)
		for (m = grub_be_to_cpu16 (cover->ranges[l].start);
		     m <= grub_be_to_cpu16 (cover->ranges[l].end); m++)
		  subst (sub, m, target, &i);
	    }
	  else
	    /* TRANSLATORS: most font transformations apply only to
	       some glyphs. Those glyphs are described as "coverage".
	       There are 2 coverage specifications: list and range.
	       This warning is thrown when another coverage specification
	       is detected.  */
	    fprintf (stderr,
		     _("Unsupported coverage specification: %d\n"), covertype);
	}
    }
}

static void
add_font (struct grub_font_info *font_info, FT_Face face, int nocut)
{
  struct gsub_header *gsub = NULL;
  FT_ULong gsub_len = 0;

  if (!FT_Load_Sfnt_Table (face, TTAG_GSUB, 0, NULL, &gsub_len))
    {
      gsub = xmalloc (gsub_len);
      if (FT_Load_Sfnt_Table (face, TTAG_GSUB, 0, (void *) gsub, &gsub_len))
	{
	  free (gsub);
	  gsub = NULL;
	  gsub_len = 0;
	}
    }
  if (gsub)
    {
      struct gsub_features *features 
	= (struct gsub_features *) (((grub_uint8_t *) gsub)
				    + grub_be_to_cpu16 (gsub->features_off));
      struct gsub_lookup_list *lookups
	= (struct gsub_lookup_list *) (((grub_uint8_t *) gsub)
				       + grub_be_to_cpu16 (gsub->lookups_off));
      int i;
      int nfeatures = grub_be_to_cpu16 (features->count);
      for (i = 0; i < nfeatures; i++)
	{
	  struct gsub_feature *feature = (struct gsub_feature *)
	    ((grub_uint8_t *) features
	     + grub_be_to_cpu16 (features->features[i].offset));
	  grub_uint32_t feattag
	    = grub_be_to_cpu32 (features->features[i].feature_tag);
	  if (feature->params)
	    fprintf (stderr,
		     _("WARNING: unsupported font feature parameters: %x\n"),
		    grub_be_to_cpu16 (feature->params));
	  switch (feattag)
	    {
	      /* Used for retrieving all possible variants. Useless in grub.  */
	    case FEATURE_AALT:
	      break;

	      /* FIXME: Add ligature support.  */
	    case FEATURE_LIGA:
	    case FEATURE_RLIG:
	      break;

	      /* Cursive form variants.  */
	    case FEATURE_FINA:
	    case FEATURE_INIT:
	    case FEATURE_MEDI:
	      process_cursive (feature, lookups, feattag);
	      break;

	    default:
	      {
		char str[5];
		int j;
		memcpy (str, &features->features[i].feature_tag,
			sizeof (features->features[i].feature_tag));
		str[4] = 0;
		for (j = 0; j < 4; j++)
		  if (!grub_isgraph (str[j]))
		    str[j] = '?';
		/* TRANSLATORS: It's gsub feature, not gsub font.  */
		grub_util_info ("Unknown gsub font feature 0x%x (%s)",
				feattag, str);
	      }
	    }
	}
    }

  if (font_info->num_range)
    {
      int i;
      grub_uint32_t j;

      for (i = 0; i < font_info->num_range; i++)
	for (j = font_info->ranges[i * 2]; j <= font_info->ranges[i * 2 + 1];
	     j++)
	  add_char (font_info, face, j, nocut);
    }
  else
    {
      grub_uint32_t char_code, glyph_index;

      for (char_code = FT_Get_First_Char (face, &glyph_index);
	   glyph_index;
	   char_code = FT_Get_Next_Char (face, char_code, &glyph_index))
	add_char (font_info, face, char_code, nocut);
    }
}

static void
write_string_section (const char *name, const char *str,
		      int *offset, FILE *file,
		      const char *filename)
{
  grub_uint32_t leng, leng_be32;

  leng = strlen (str) + 1;
  leng_be32 = grub_cpu_to_be32 (leng);

  grub_util_write_image (name, 4, file, filename);
  grub_util_write_image ((char *) &leng_be32, 4, file, filename);
  grub_util_write_image (str, leng, file, filename);

  *offset += 8 + leng;
}

static void
write_be16_section (const char *name, grub_uint16_t data, int* offset,
		    FILE *file, const char *filename)
{
  grub_uint32_t leng;

  leng = grub_cpu_to_be32_compile_time (2);
  data = grub_cpu_to_be16 (data);
  grub_util_write_image (name, 4, file, filename);
  grub_util_write_image ((char *) &leng, 4, file, filename);
  grub_util_write_image ((char *) &data, 2, file, filename);

  *offset += 10;
}

static void
print_glyphs (struct grub_font_info *font_info)
{
  int num;
  struct grub_glyph_info *glyph;
  char line[512];

  for (glyph = font_info->glyphs_sorted, num = 0; num < font_info->num_glyphs;
       glyph++, num++)
    {
      int x, y, xmax, xmin, ymax, ymin;
      grub_uint8_t *bitmap, mask;

      printf ("\nGlyph #%d, U+%04x\n", num, glyph->char_code);
      printf ("Width %d, Height %d, X offset %d, Y offset %d, Device width %d\n",
	      glyph->width, glyph->height, glyph->x_ofs, glyph->y_ofs,
	      glyph->device_width);

      xmax = glyph->x_ofs + glyph->width;
      if (xmax < glyph->device_width)
	xmax = glyph->device_width;

      xmin = glyph->x_ofs;
      if (xmin > 0)
	xmin = 0;

      ymax = glyph->y_ofs + glyph->height;
      if (ymax < font_info->asce)
	ymax = font_info->asce;

      ymin = glyph->y_ofs;
      if (ymin > - font_info->desc)
	ymin = - font_info->desc;

      bitmap = glyph->bitmap;
      mask = 0x80;
      for (y = ymax - 1; y > ymin - 1; y--)
	{
	  int line_pos;

	  line_pos = 0;
	  for (x = xmin; x < xmax; x++)
	    {
	      if ((x >= glyph->x_ofs) &&
		  (x < glyph->x_ofs + glyph->width) &&
		  (y >= glyph->y_ofs) &&
		  (y < glyph->y_ofs + glyph->height))
		{
		  line[line_pos++] = (*bitmap & mask) ? '#' : '_';
		  mask >>= 1;
		  if (mask == 0)
		    {
		      mask = 0x80;
		      bitmap++;
		    }
		}
	      else if ((x >= 0) &&
		       (x < glyph->device_width) &&
		       (y >= - font_info->desc) &&
		       (y < font_info->asce))
		{
		  line[line_pos++] = ((x == 0) || (y == 0)) ? '+' : '.';
		}
	      else
		line[line_pos++] = '*';
	    }
	  line[line_pos] = 0;
	  printf ("%s\n", line);
	}
    }
}

static void
write_font_pf2 (struct grub_font_info *font_info, char *output_file)
{
  FILE *file;
  grub_uint32_t leng;
  char style_name[20], *font_name, *ptr;
  int offset;
  struct grub_glyph_info *cur;

  file = grub_util_fopen (output_file, "wb");
  if (! file)
    grub_util_error (_("cannot write to `%s': %s"), output_file,
		     strerror (errno));

  offset = 0;

  leng = grub_cpu_to_be32_compile_time (4);
  grub_util_write_image (FONT_FORMAT_SECTION_NAMES_FILE,
  			 sizeof(FONT_FORMAT_SECTION_NAMES_FILE) - 1, file,
			 output_file);
  grub_util_write_image ((char *) &leng, 4, file, output_file);
  grub_util_write_image (FONT_FORMAT_PFF2_MAGIC, 4, file, output_file);
  offset += 12;

  if (! font_info->name)
    font_info->name = "Unknown";

  if (font_info->flags & GRUB_FONT_FLAG_BOLD)
    font_info->style |= FT_STYLE_FLAG_BOLD;

  style_name[0] = 0;
  if (font_info->style & FT_STYLE_FLAG_BOLD)
    strcpy (style_name, " Bold");

  if (font_info->style & FT_STYLE_FLAG_ITALIC)
    strcat (style_name, " Italic");

  if (! style_name[0])
    strcpy (style_name, " Regular");

  font_name = xmalloc (strlen (font_info->name) + strlen (&style_name[1])
		       + 3 + 20);
  ptr = grub_stpcpy (font_name, font_info->name);
  *ptr++ = ' ';
  ptr = grub_stpcpy (ptr, &style_name[1]);
  *ptr++ = ' ';
  snprintf (ptr, 20, "%d", font_info->size);

  write_string_section (FONT_FORMAT_SECTION_NAMES_FONT_NAME,
  			font_name, &offset, file, output_file);
  write_string_section (FONT_FORMAT_SECTION_NAMES_FAMILY,
  			font_info->name, &offset, file, output_file);
  write_string_section (FONT_FORMAT_SECTION_NAMES_WEIGHT,
			(font_info->style & FT_STYLE_FLAG_BOLD) ?
			"bold" : "normal",
			&offset, file, output_file);
  write_string_section (FONT_FORMAT_SECTION_NAMES_SLAN,
			(font_info->style & FT_STYLE_FLAG_ITALIC) ?
			"italic" : "normal",
			&offset, file, output_file);

  write_be16_section (FONT_FORMAT_SECTION_NAMES_POINT_SIZE,
  		      font_info->size, &offset, file, output_file);
  write_be16_section (FONT_FORMAT_SECTION_NAMES_MAX_CHAR_WIDTH,
  		      font_info->max_width, &offset, file, output_file);
  write_be16_section (FONT_FORMAT_SECTION_NAMES_MAX_CHAR_HEIGHT,
  		      font_info->max_height, &offset, file, output_file);

  if (! font_info->desc)
    {
      if (font_info->min_y >= 0)
	font_info->desc = 1;
      else
	font_info->desc = - font_info->min_y;
    }

  if (! font_info->asce)
    {
      if (font_info->max_y <= 0)
	font_info->asce = 1;
      else
	font_info->asce = font_info->max_y;
    }

  write_be16_section (FONT_FORMAT_SECTION_NAMES_ASCENT,
  		      font_info->asce, &offset, file, output_file);
  write_be16_section (FONT_FORMAT_SECTION_NAMES_DESCENT,
  		      font_info->desc, &offset, file, output_file);

  if (font_verbosity > 0)
    {
      printf ("Font name: %s\n", font_name);
      printf ("Max width: %d\n", font_info->max_width);
      printf ("Max height: %d\n", font_info->max_height);
      printf ("Font ascent: %d\n", font_info->asce);
      printf ("Font descent: %d\n", font_info->desc);
    }

  if (font_verbosity > 0)
    printf ("Number of glyph: %d\n", font_info->num_glyphs);

  leng = grub_cpu_to_be32 (font_info->num_glyphs * 9);
  grub_util_write_image (FONT_FORMAT_SECTION_NAMES_CHAR_INDEX,
  			 sizeof(FONT_FORMAT_SECTION_NAMES_CHAR_INDEX) - 1,
			 file, output_file);
  grub_util_write_image ((char *) &leng, 4, file, output_file);
  offset += 8 + font_info->num_glyphs * 9 + 8;

  for (cur = font_info->glyphs_sorted;
       cur < font_info->glyphs_sorted + font_info->num_glyphs; cur++)
    {
      grub_uint32_t data32;
      grub_uint8_t data8;
      data32 = grub_cpu_to_be32 (cur->char_code);
      grub_util_write_image ((char *) &data32, 4, file, output_file);
      data8 = 0;
      grub_util_write_image ((char *) &data8, 1, file, output_file);
      data32 = grub_cpu_to_be32 (offset);
      grub_util_write_image ((char *) &data32, 4, file, output_file);
      offset += 10 + cur->bitmap_size;
    }

  leng = 0xffffffff;
  grub_util_write_image (FONT_FORMAT_SECTION_NAMES_DATA,
  			 sizeof(FONT_FORMAT_SECTION_NAMES_DATA) - 1,
			 file, output_file);
  grub_util_write_image ((char *) &leng, 4, file, output_file);

  for (cur = font_info->glyphs_sorted;
       cur < font_info->glyphs_sorted + font_info->num_glyphs; cur++)
    {
      grub_uint16_t data;
      data = grub_cpu_to_be16 (cur->width);
      grub_util_write_image ((char *) &data, 2, file, output_file);
      data = grub_cpu_to_be16 (cur->height);
      grub_util_write_image ((char *) &data, 2, file, output_file);
      data = grub_cpu_to_be16 (cur->x_ofs);
      grub_util_write_image ((char *) &data, 2, file, output_file);
      data = grub_cpu_to_be16 (cur->y_ofs);
      grub_util_write_image ((char *) &data, 2, file, output_file);
      data = grub_cpu_to_be16 (cur->device_width);
      grub_util_write_image ((char *) &data, 2, file, output_file);
      grub_util_write_image ((char *) &cur->bitmap[0], cur->bitmap_size,
			     file, output_file);
    }

  fclose (file);
}

#ifndef GRUB_BUILD
static struct argp_option options[] = {
  {"output",  'o', N_("FILE"), 0, N_("save output in FILE [required]"), 0},
  /* TRANSLATORS: bitmaps are images like e.g. in JPEG.  */
  {"index",  'i', N_("NUM"), 0,
   /* TRANSLATORS: some font files may have multiple faces (fonts).
      This option is used to chose among them, the first face being '0'.
      Rarely used.  */
   N_("select face index"), 0},
  {"range",  'r', N_("FROM-TO[,FROM-TO]"), 0, 
   /* TRANSLATORS: It refers to the range of characters in font.  */
   N_("set font range"), 0},
  {"name",  'n', N_("NAME"), 0, 
   /* TRANSLATORS: "family name" for font is just a generic name without suffix
      like "Bold".  */
   N_("set font family name"), 0},
  {"size",  's', N_("SIZE"), 0, N_("set font size"), 0},
  {"desc",  'd', N_("NUM"), 0, N_("set font descent"), 0},
  {"asce",  'c', N_("NUM"), 0, N_("set font ascent"), 0},
  {"bold",  'b', 0, 0, N_("convert to bold font"), 0},
  {"force-autohint",  'a', 0, 0, N_("force autohint"), 0},
  {"no-hinting",  0x101, 0, 0, N_("disable hinting"), 0},
  {"no-bitmap",  0x100, 0, 0,
   /* TRANSLATORS: some fonts contain bitmap rendering for
      some sizes. This option forces rerendering even if
      pre-rendered bitmap is available.
    */
   N_("ignore bitmap strikes when loading"), 0},
  {"verbose",  'v', 0, 0, N_("print verbose messages."), 0},
  { 0, 0, 0, 0, 0, 0 }
};
#define my_argp_parse argp_parse
#define MY_ARGP_KEY_ARG ARGP_KEY_ARG
#define my_error_t error_t
#define MY_ARGP_ERR_UNKNOWN ARGP_ERR_UNKNOWN
#define my_argp_state argp_state

#else

#define my_error_t int
#define MY_ARGP_ERR_UNKNOWN -1
#define MY_ARGP_KEY_ARG -1
#define my_argp_parse(a, argc, argv, b, c, st) my_argp_parse_real(argc, argv, st)
struct my_argp_state
{
  void *input;
};

#endif

struct arguments
{
  struct grub_font_info font_info;
  size_t nfiles;
  size_t files_max;
  char **files;
  char *output_file;
  int font_index;
  int font_size;
  enum file_formats file_format;
};

#ifdef GRUB_BUILD

static int
has_argument (int v)
{
  return v =='o' || v == 'i' || v == 'r' || v == 'n' || v == 's'
    || v == 'd' || v == 'c';
}

#endif

static my_error_t
argp_parser (int key, char *arg, struct my_argp_state *state)
{
  /* Get the input argument from argp_parse, which we
     know is a pointer to our arguments structure. */
  struct arguments *arguments = state->input;

  switch (key)
    {
    case 'b':
      arguments->font_info.flags |= GRUB_FONT_FLAG_BOLD;
      break;

    case 0x100:
      arguments->font_info.flags |= GRUB_FONT_FLAG_NOBITMAP;
      break;

    case 0x101:
      arguments->font_info.flags |= GRUB_FONT_FLAG_NOHINTING;
      break;

    case 'a':
      arguments->font_info.flags |= GRUB_FONT_FLAG_FORCEHINT;
      break;

    case 'o':
      arguments->output_file = xstrdup (arg);
      break;

    case 'n':
      arguments->font_info.name = xstrdup (arg);
      break;

    case 'i':
      arguments->font_index = strtoul (arg, NULL, 0);
      break;

    case 's':
      arguments->font_size = strtoul (arg, NULL, 0);
      break;

    case 'r':
      {
	char *p = arg;

	while (1)
	  {
	    grub_uint32_t a, b;

	    a = strtoul (p, &p, 0);
	    if (*p != '-')
	      /* TRANSLATORS: It refers to the range of characters in font.  */
	      grub_util_error ("%s", _("invalid font range"));
	    b = strtoul (p + 1, &p, 0);
	    if ((arguments->font_info.num_range
		 & (GRUB_FONT_RANGE_BLOCK - 1)) == 0)
	      arguments->font_info.ranges = xrealloc (arguments->font_info.ranges,
						      (arguments->font_info.num_range +
						       GRUB_FONT_RANGE_BLOCK) *
						      sizeof (grub_uint32_t) * 2);

	    arguments->font_info.ranges[arguments->font_info.num_range * 2] = a;
	    arguments->font_info.ranges[arguments->font_info.num_range * 2 + 1] = b;
	    arguments->font_info.num_range++;

	    if (*p)
	      {
		if (*p != ',')
		  grub_util_error ("%s", _("invalid font range"));
		p++;
	      }
	    else
	      break;
	  }
	break;
      }

    case 'd':
      arguments->font_info.desc = strtoul (arg, NULL, 0);
      break;

    case 'c':
      arguments->font_info.asce = strtoul (arg, NULL, 0);
      break;

    case 'v':
      font_verbosity++;
      break;

    case MY_ARGP_KEY_ARG:
      assert (arguments->nfiles < arguments->files_max);
      arguments->files[arguments->nfiles++] = xstrdup(arg);
      break;

    default:
      return MY_ARGP_ERR_UNKNOWN;
    }
  return 0;
}

#ifdef GRUB_BUILD

/* We don't require host platform to have argp. In the same time configuring
   gnulib for build would result in even worse mess. So we have our
   minimalistic argp replacement just enough for build system. Most
   argp features are omitted.  */

static int
my_argp_parse_real (int argc, char **argv, void *st)
{
  int curar;
  struct my_argp_state p;

  p.input = st;

  for (curar = 1; curar < argc; )
    {
      if (argv[curar][0] == '-')
	{
	  if (has_argument (argv[curar][1])
	      && curar + 1 >= argc)
	    return 1;
	  if (has_argument (argv[curar][1]))
	    {
	      if (argp_parser (argv[curar][1], argv[curar + 1], &p))
		return 1;
	      curar += 2;
	      continue;
	    }
	  if (argp_parser (argv[curar][1], NULL, &p))
	    return 1;
	  curar++;
	  continue;
	}
      if (argp_parser (MY_ARGP_KEY_ARG, argv[curar], &p))
	return 1;
      curar++;
    }
  return 0;
}
#endif

#ifndef GRUB_BUILD
static struct argp argp = {
  options, argp_parser, N_("[OPTIONS] FONT_FILES"),
  N_("Convert common font file formats into PF2"),
  NULL, NULL, NULL
};
#endif

int
main (int argc, char *argv[])
{
  FT_Library ft_lib;
  struct arguments arguments;

#ifndef GRUB_BUILD
  grub_util_host_init (&argc, &argv);
#endif

  memset (&arguments, 0, sizeof (struct arguments));
  arguments.file_format = PF2;
  arguments.files_max = argc + 1;
  arguments.files = xmalloc ((arguments.files_max + 1)
			     * sizeof (arguments.files[0]));
  memset (arguments.files, 0, (arguments.files_max + 1)
	  * sizeof (arguments.files[0]));

  if (my_argp_parse (&argp, argc, argv, 0, 0, &arguments) != 0)
    {
      fprintf (stderr, "%s", _("Error in parsing command line arguments\n"));
      exit(1);
    }

  if (! arguments.output_file)
    grub_util_error ("%s", _("output file must be specified"));

  if (FT_Init_FreeType (&ft_lib))
    grub_util_error ("%s", _("FT_Init_FreeType fails"));

  {
    size_t i;      
    for (i = 0; i < arguments.nfiles; i++)
      {
	FT_Face ft_face;
	int size;
	FT_Error err;

	err = FT_New_Face (ft_lib, arguments.files[i],
			   arguments.font_index, &ft_face);
	if (err)
	  {
	    printf (_("can't open file %s, index %d: error %d"),
		    arguments.files[i],
		    arguments.font_index, err);
	    if (err > 0 && err < (signed) ARRAY_SIZE (ft_errmsgs))
	      printf (": %s\n", ft_errmsgs[err]);
	    else
	      printf ("\n");

	    continue;
	  }

	if ((! arguments.font_info.name) && (ft_face->family_name))
	  arguments.font_info.name = xstrdup (ft_face->family_name);

	size = arguments.font_size;
	if (! size)
	  {
	    if ((ft_face->face_flags & FT_FACE_FLAG_SCALABLE) ||
		(! ft_face->num_fixed_sizes))
	      size = GRUB_FONT_DEFAULT_SIZE;
	    else
	      size = ft_face->available_sizes[0].height;
	  }

	arguments.font_info.style = ft_face->style_flags;
	arguments.font_info.size = size;

	err = FT_Set_Pixel_Sizes (ft_face, size, size);

	if (err)
	  grub_util_error (_("can't set %dx%d font size: Freetype error %d: %s"),
			   size, size, err,
			   (err > 0 && err < (signed) ARRAY_SIZE (ft_errmsgs))
			   ? ft_errmsgs[err] : "");
	add_font (&arguments.font_info, ft_face, arguments.file_format != PF2);
	FT_Done_Face (ft_face);
      }
  }

  FT_Done_FreeType (ft_lib);

  {
    int counter[65537];
    struct grub_glyph_info *tmp, *cur;
    int i;

    memset (counter, 0, sizeof (counter));

    for (cur = arguments.font_info.glyphs_unsorted; cur; cur = cur->next)
      counter[(cur->char_code & 0xffff) + 1]++;
    for (i = 0; i < 0x10000; i++)
      counter[i+1] += counter[i];
    tmp = xmalloc (arguments.font_info.num_glyphs
		   * sizeof (tmp[0]));
    for (cur = arguments.font_info.glyphs_unsorted; cur; cur = cur->next)
      tmp[counter[(cur->char_code & 0xffff)]++] = *cur;

    memset (counter, 0, sizeof (counter));

    for (cur = tmp; cur < tmp + arguments.font_info.num_glyphs; cur++)
      counter[((cur->char_code & 0xffff0000) >> 16) + 1]++;
    for (i = 0; i < 0x10000; i++)
      counter[i+1] += counter[i];
    arguments.font_info.glyphs_sorted = xmalloc (arguments.font_info.num_glyphs
						 * sizeof (arguments.font_info.glyphs_sorted[0]));
    for (cur = tmp; cur < tmp + arguments.font_info.num_glyphs; cur++)
      arguments.font_info.glyphs_sorted[counter[(cur->char_code & 0xffff0000)
						>> 16]++] = *cur;
    free (tmp);
  }

  switch (arguments.file_format)
    {
    case PF2:
      write_font_pf2 (&arguments.font_info, arguments.output_file);
      break;
    }

  if (font_verbosity > 1)
    print_glyphs (&arguments.font_info);

  {
    size_t i;
    for (i = 0; i < arguments.nfiles; i++)
      free (arguments.files[i]);
  }

  return 0;
}
