/** 
 *  Yudit Unicode Editor Source File
 *
 *  GNU Copyright (C) 1997-2006  Gaspar Sinai <gaspar@yudit.org>  
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, version 2,
 *  dated June 1991. See file COPYYING for details.
 *
 *  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.
 */


/* 0xffffffff */
static int SD_TTF_NAN=-1;
 
#include "swindow/SFontTTF.h"
#include "swindow/SScriptProcessor.h"
#include "swindow/STTables.h"

#include "stoolkit/SStringVector.h"
#include "stoolkit/SExcept.h"
#include "stoolkit/SUniMap.h"
#include "stoolkit/SUtil.h"
#include "stoolkit/SCluster.h"
#include "stoolkit/SCharClass.h"
/*
 * ntohl 
 */
#ifndef USE_WINAPI
#include <netinet/in.h>
#else
#include <winsock.h>
#endif

#include <stdio.h>
#include <ctype.h>


/*
 * These glyphs should not exists khmm.. I said these glyphs should not exist.
 * Unicode magic :)
 */
#define SD_G_INDIC_ZWNJ 0xfffe
#define SD_G_INDIC_ZWJ 0xffff



/**
 * This is a hash width key platform '~' encoding 
 * and value SUnimap.
 */
static const SString SS_TB_NAME("name");
static const SString SS_TB_HEAD("head");
static const SString SS_TB_HHEA("hhea");
static const SString SS_TB_POST("post");
static const SString SS_TB_GLYF("glyf");
static const SString SS_TB_CMAP("cmap");
static const SString SS_TB_KERN("kern");
static const SString SS_TB_MAXP("maxp");
static const SString SS_TB_HTMX("hmtx");
static const SString SS_TB_LOCA("loca");
static const SString SS_TB_OS2("OS/2");

static const long SS_TN_NOTICE=0;
static const long SS_TN_FAMILY=1;
static const long SS_TN_WEIGHT=2;
static const long SS_TN_X3=3;
static const long SS_TN_FULLNAME=4;
static const long SS_TN_VERSION=5;
static const long SS_TN_FONTNAME=6;
static const long SS_TN_X7=7;
static const long SS_TN_MAX=8;



static void debugChars (const char* msg, 
  const SS_GlyphIndex* gchars, unsigned int len);
static SS_GlyphIndex findGlyph0 (TTF_CMAP_FMT0* encoding0, SS_UCS4 ucs4);
static SS_GlyphIndex findGlyph4 (TTF_CMAP_FMT4* encoding4, SS_UCS4 ucs4);
static SS_GlyphIndex findGlyph12 (TTF_CMAP_FMT12* encoding12, SS_UCS4 ucs4);
static SS_GlyphIndex findGlyph2 (TTF_CMAP_FMT2* encoding2, SS_UCS4 ucs4);

static double f2dot14 (short x);
static void moveto (SCanvas* canvas, const SS_Matrix2D &m,
  SD_SHORT _x, SD_SHORT _y);
static void lineto (SCanvas* canvas, const SS_Matrix2D &m,
  SD_SHORT _x, SD_SHORT _y);
static void cureveto (SCanvas* canvas, const SS_Matrix2D &m,
  SD_SHORT _x0, SD_SHORT _y0,
  SD_SHORT _x1, SD_SHORT _y1,
  SD_SHORT _x2, SD_SHORT _y2);

/**
 * @author: Gaspar Sinai <gaspar@yudit.org>
 * @version: 2000-04-23
 * Many parts of this file are originally written by Andrew Weeks.
 */

SS_UCS4 SFontTTF::setBaseCharacter = 0;

/**
 * Initialize a TTF
 * @param name is the font file
 */
SFontTTF::SFontTTF (const SFile& _file,  const SString& _fontencoding) 
  : file (_file)
{
  ok = true;
  fontencoding = _fontencoding;
  hardWire = SS_NONE;
  defaultGlyph = 0;
  baseGlyph = 0;
}

SFontTTF::~SFontTTF ()
{
}

bool
SFontTTF::isOK()
{
  if (!ok) return false;

  /* initialize */
  if (name.size()==0)
  {
    name = file.getName();
    if (fontencoding == "mslvt")
    {
      hardWire = SS_MSLVT;
      fontencoding.clear();
    }
    else if (fontencoding == "nojamo")
    {
      hardWire = SS_NOJAMO;
      fontencoding.clear();
    }
    else if (fontencoding == "jamo")
    {
      hardWire = SS_JAMO;
      fontencoding.clear();
    }
    /* Experimental filters for whole ranges */
    else if (fontencoding == "indic")
    {
      hardWire = SS_INDIC;
      fontencoding.clear();
    }
    else if (fontencoding == "deva")
    {
      hardWire = SS_DEVANAGARI;
      fontencoding.clear();
    }
    else if (fontencoding == "beng")
    {
      hardWire = SS_BENGALI;
      fontencoding.clear();
    }
    else if (fontencoding == "guru")
    {
      hardWire = SS_GURMUKHI;
      fontencoding.clear();
    }
    else if (fontencoding == "gujr")
    {
      hardWire = SS_GUJARATI;
      fontencoding.clear();
    }
    else if (fontencoding == "orya")
    {
      hardWire = SS_ORIYA;
      fontencoding.clear();
    }
    else if (fontencoding == "taml")
    {
      hardWire = SS_TAMIL;
      fontencoding.clear();
    }
    else if (fontencoding == "telu")
    {
      hardWire = SS_TELUGU;
      fontencoding.clear();
    }
    else if (fontencoding == "knda")
    {
      hardWire = SS_KANNADA;
      fontencoding.clear();
    }
    else if (fontencoding == "mlym")
    {
      hardWire = SS_MALAYALAM;
      fontencoding.clear();
    }
    else if (fontencoding == "sinh")
    {
      hardWire = SS_SINHALA;
      fontencoding.clear();
    }
    else if (fontencoding == "thai")
    {
      hardWire = SS_THAI;
      fontencoding.clear();
    }
    else if (fontencoding == "lao")
    {
      hardWire = SS_LAO;
      fontencoding.clear();
    }
    else if (fontencoding == "tibt")
    {
      hardWire = SS_TIBETAN;
      fontencoding.clear();
    }
    else
    {
      SString n = name; n.lower();
      /* only Ogulim may work. forget any prefix.*/
      if (n.match("*ogulim.ttf"))
      {
        hardWire = SS_MSLVT;
        fontencoding.clear();
      }
    }
    if (file.size() < 0)
    {
      ok = false;
    }
    else
    {
      image = file.getFileImage();
      ok = image.size()>0 && init();
    }
  }
  return ok;
}

/**
 * initialize all numbers.
 * return false if something is wrong with this font.
 */
bool
SFontTTF::init ()
{
  TTF_DIRECTORY* directory = (TTF_DIRECTORY *) image.array();
  if (ntohl (directory->sfntVersion) != 0x00010000)
  {
    fprintf (stderr, "SFontTTF: BAD TTF file [%*.*s].\n", SSARGS(name));
    return false;
  }
  TTF_DIR_ENTRY* dir_entry = &(directory->list);
  
  char tag[5];
  unsigned int i;
  for (i=0; i < (unsigned short)ntohs(directory->numTables); i++)
  {
    for (unsigned int j=0; j<4; j++)
    {
      tag[j] = dir_entry->tag[j];
    }
    tag[4] = 0;
    tables.put (tag, image.array() + ntohl (dir_entry->offset));
    if (memcmp(tag, "EBDT", 4)==0 || memcmp(tag, "EBLC", 4)==0
        || memcmp(tag, "EBSC", 4)==0)
    {
      //fprintf (stderr, "SFontTTF info: TTF file [%*.*s] contains bitmaps.\n",
       //  SSARGS(name));
    }
    dir_entry++;
  }
  if (!processName())
  {
     return false;
  }
  if (!checkTables())
  {
     return false;
  }
  broken = false;

  TTF_CMAP* cmap_table = (TTF_CMAP*) tables[SS_TB_CMAP];
  int num_tables = ntohs(cmap_table->numberOfEncodingTables);

  TTF_OS2* os2table = (TTF_OS2*) tables[SS_TB_OS2];
  if (os2table)
  {
    //defaultGlyph = ntohs(os2table->usDefaultChar);
    //unsigned int rangel = ntohl (os2table->ulUnicodeRange1);
    //unsigned int rangeh = ntohl (os2table->ulUnicodeRange2);
    //fprintf (stderr, "%*.*s defaultGlyph=%u %u %u\n", 
     //   SSARGS(name), defaultGlyph, rangel, rangeh);
  }

  /* We go through all the tables and choose the bes table */
  int bestType = 0;
  if (fontencoding.size())
  {
    charEncoder = SUniMap(fontencoding);
    if (!charEncoder.isOK())
    {
      fprintf (stderr, "SFontTTF: umap '%*.*s' not found for '%*.*s'.\n",
        SSARGS(fontencoding), SSARGS(name));
    }
  }
  charEncoderTable = (unsigned int) num_tables; 
  for (i=0; i < (unsigned int) num_tables; i++)
  {
    TTF_CMAP_ENTRY* table_entry = &(cmap_table->encodingTable[i]);
    int offset = ntohl(table_entry->offset);
    TTF_CMAP_FMT4* encoding4 = (TTF_CMAP_FMT4 *) ((SD_BYTE *)cmap_table + offset);
    int format = ntohs(encoding4->format);
    int platform = ntohs(table_entry->platformID);
    int encoding_id = ntohs(table_entry->encodingID);
    /**
     * All 
     *  platform == TT_PLAT_ID_MICROSOFT(3)
     *  encoding_id == TT_ENC_ID_ISO_10646 (1)
     * will have TTF_CMAP_FMT4 (format==4
     * character map. Others *might* have. 
     *
     * TODO: support more cmap formats like 32 bit unicode.
     *  currently 32 bit unicode is done through external map.
     */
    if (format != 4) continue;

    /* could not find any good table */
    if (charEncoderTable ==(unsigned int) num_tables)
    {
      charEncoderTable = i;
    }
/*
    fprintf (stderr, "%*.*s platform=%d encoding=%d at %u\n", SSARGS(name),
       platform, encoding_id, i);
*/
    switch (platform)
    {
    case TT_PLAT_ID_MICROSOFT:
      switch (encoding_id)
      { 
      case TT_ENC_ID_MS_SYMBOL:
        break;
      case TT_ENC_ID_MS_UNICODE:
        bestType = 9; /* these mystic numbers are my scores */
        charEncoderTable = i;
        break;
      case TT_ENC_ID_MS_SURROGATES:
        bestType = 10; /* these mystic numbers are my scores */
        charEncoderTable = i;
        break;
      case TT_ENC_ID_MS_SHIFT_JIS:
      case TT_ENC_ID_MS_BIG5:
      case TT_ENC_ID_MS_RPC:
      case TT_ENC_ID_MS_WANSUNG:
      case TT_ENC_ID_MS_JOHAB:
      default:
         break;
     }
     break;
    case TT_PLAT_ID_ISO:
      switch (encoding_id)
      { 
      case TT_ENC_ID_ANY:
        break;
      case TT_ENC_ID_ISO_ASCII:
        if (bestType < 2) 
        {
           bestType = 2;
           charEncoderTable = i;
        }
        break;
      case TT_ENC_ID_ISO_10646:
        if (bestType < 8) 
        {
           bestType = 8;
           charEncoderTable = i;
        }
        break;
      case TT_ENC_ID_ISO_8859_1:
        if (bestType < 4) 
        {
           bestType = 4;
           charEncoderTable = i;
        }
        break;
      default:
        break;
     }
     break;
    case TT_PLAT_ID_APPLE:
      switch (encoding_id)
      { 
      case TT_ENC_ID_APPLE_DEFAULT:
        break;
      case TT_ENC_ID_APPLE_UNICODE_1_1:
      case TT_ENC_ID_APPLE_ISO_10646:
      case TT_ENC_ID_APPLE_UNICODE_2_0:
        if (bestType < 7) 
        {
           bestType = 8;
           charEncoderTable = i;
        }
      default:
         break;
     }
     break;
    case TT_PLAT_ID_MACINTOSH:
      switch (encoding_id)
      { 
      case TT_ENC_ID_MAC_ROMAN:
      /* a lot of other encodings missing */
      default:
       break;
     }
    default:
      break;
    }
  }
  /* look at all tables SGC */
  if (fontencoding.size()!=0) charEncoderTable =(unsigned int) num_tables;
  //fprintf (stderr, "SGC %*.*s besttype = %d table=%d count=%d\n", 
  //     SSARGS (name), bestType, charEncoderTable, num_tables);
  //SString chk = name; chk.lower();
  return true;
}


/**
 * Process the name table
 */
bool
SFontTTF::processName ()
{
  TTF_NAME*  name_table = (TTF_NAME*) tables[SS_TB_NAME];
  if (name_table==0)
  {
    getName (SS_TN_FONTNAME, name.array(), name.size());
    fprintf (stderr, "SFontTTF: No name fields in %*.*s\n", SSARGS(name));
    fprintf (stderr, "SFontTTF: records:[");
    for (unsigned int i=0; i<tables.size(); i++)
    {
      for (unsigned int j=0; j<tables.size(i); j++)
      {
        SString key = tables.key(i, j);
        fprintf (stderr, " %*.*s", SSARGS(key));
        
      }
    }
    fprintf (stderr, " ]\n");
    return false;
  }

  TTF_NAME_REC* name_record = &(name_table->nameRecords);
  char* string_area = (char *)name_table + ntohs(name_table->offset);
    
  int found=0;
  int i;
  for (i=0; i < ntohs (name_table->numberOfNameRecords); i++) 
  {
    short platform = ntohs(name_record->platformID);
    if (platform == 3)
    {
      found = 1;
      short len = ntohs(name_record->stringLength);
      short strOffset = ntohs(name_record->stringOffset);
      long nameId = ntohs(name_record->nameID);
      if (nameId < SS_TN_MAX)
      {
        getName (nameId, &string_area[strOffset], len);
      }
    }
    name_record++;
  }

  name_record = &(name_table->nameRecords);
  if (!found) for (i=0; i < ntohs(name_table->numberOfNameRecords); i++) 
  {
   short platform = ntohs(name_record->platformID); if (platform ==1)
   {
      found = 1;
      short len = ntohs(name_record->stringLength);
      short strOffset = ntohs(name_record->stringOffset);
      long nameId = ntohs(name_record->nameID);
      if (nameId < SS_TN_MAX)
      {
        getName (nameId, &string_area[strOffset], len);
      }
    }
    name_record++;
  }
  if (!found)
  {
    fprintf (stderr, "SFontTTF: BAD Name fields in %*.*s\n", SSARGS(name));
    return false;
  }

  if (names.get (SS_TN_FONTNAME) == 0 || names[SS_TN_FONTNAME].size() == 0)
  {
    getName (SS_TN_FONTNAME, names[SS_TN_FULLNAME].array(),
         names[SS_TN_FULLNAME].size());
  }
  return true;
}

/**
 * put the string from str into names. 
 * @param id is SS_TN_ something.
 * @param str is the input string
 * @param len is the size of the stirng
 */
void
SFontTTF::getName (long id, const char* str, int len)
{
  SString s;
  for (int i=0; i<len; i++)
  {
    if (str[i] == 0) continue;
    if (id==SS_TN_FONTNAME)
    {
      if (isalnum(str[i]))
      {
        s.append (str[i]);
      }
      else
      {
        s.append ((char)'_');
      }
    }
    /* This is to make postscript files clean */
    else switch (str[i])
    {
    case '(':
      s.append ((char)'[');
      break;
    case ')':
      s.append ((char)']');
      break;
    default:
      s.append (str[i]);
    }
  }
  names.put (id, s);
}

/**
 * Do a sanity check.all tables
 */
bool
SFontTTF::checkTables ()
{

  if (tables[SS_TB_HEAD] == 0)
  {
    fprintf (stderr, "SFontTTF: BAD head table in %*.*s\n", SSARGS(name));
    return false;
  }
  TTF_HEAD* head_table = (TTF_HEAD*) tables[SS_TB_HEAD];
  longOffsets = ntohs (head_table->indexToLocFormat);

  if (longOffsets != 0 && longOffsets != 1)
  {
    fprintf (stderr, "SFontTTF: BAD TTF file [%*.*s] - indexToLocFormat.\n",
       SSARGS(name));
    return false;
  }

  if (tables[SS_TB_HHEA] == 0)
  {
    fprintf (stderr, "SFontTTF: BAD hhea table in %*.*s\n", SSARGS(name));
    return false;
  }
  if (tables[SS_TB_GLYF] == 0)
  {
    fprintf (stderr, "SFontTTF: BAD glyf table in %*.*s\n", SSARGS(name));
    return false;
  }
  if (tables[SS_TB_CMAP] == 0)
  {
    fprintf (stderr, "SFontTTF: BAD cmap table in %*.*s\n", SSARGS(name));
    return false;
  }
  if (tables[SS_TB_HTMX] == 0)
  {
    fprintf (stderr, "SFontTTF: BAD htmx table in %*.*s\n", SSARGS(name));
    return false;
  }
  if (tables[SS_TB_LOCA] == 0)
  {
    fprintf (stderr, "SFontTTF: BAD loca table in %*.*s\n", SSARGS(name));
    return false;
  }
  TTF_POST_HEAD*  post_table = (TTF_POST_HEAD*) tables[SS_TB_POST];
  if (post_table == 0)
  {
    fprintf (stderr, "SFontTTF: missing post table in %*.*s. Using defaults\n",
         SSARGS(name));
    italicAngle = 0.0;
    underlineThickness = 100;
    underlinePosition = 0.0;
    isFixedPitch = 0.0;
  }
  else
  {
    italicAngle = (double) (ntohs(post_table->italicAngle.upper)) +
      (ntohs(post_table->italicAngle.lower) / 65536.0);
    underlineThickness = (double)ntohs(post_table->underlineThickness);
    underlinePosition = (double)ntohs(post_table->underlinePosition);
    isFixedPitch = (ntohl(post_table->isFixedPitch))? true : false;
  }
  TTF_HHEA* hhea_table = (TTF_HHEA*) tables[SS_TB_HHEA];

  lineGap = (double) ((short)htons (hhea_table->lineGap));
  charWidth = (double) ((short)(ntohs(head_table->xMax)+ ntohs(head_table->xMin)));
  charAscent = (double) ((short)htons (hhea_table->ascender));
  /* Be aware charDescent is negative! */
  charDescent = (double) ((short)htons (hhea_table->descender));

  /* kairali-S: dscale=1000 charAscent=298 charDescent=-202 
        dscale_factor dscale/(charAscent-charDescent) */

  /* charDescent is negative - take charAscent aonly to determine size */
  double charheight = charAscent;
  if (charheight < 1) charheight = 1;
  scaleFactor = 1.0 / charheight;
/*
  short unitsPerEM  = ntohs (head_table->unitsPerEm);
  fprintf (stderr, "font=%*.*s charheight=%g unitsPerEM=%d\n", 
      SSARGS(name), charheight, unitsPerEM);
*/
  return true;
}

/**
 * set the base character for better glyph positioning
 * @param base is the base character relative to which 
 * we will position all of out composing marks.
 * When the draw routine is called with non-base 
 * character, then the position will be at the end
 * of the glyph visually, except for U+0500..U+0900 
 * composing marks, where matrix will be set at the 
 * beginning of the base glyph, viaually
 */
void
SFontTTF::setBase(SS_UCS4 base)
{
  setBaseCharacter = base;
  /* we do this font unicode fonts only now */
  
}

/**
 * Get the x,y offset for better positioning of diacritical marks.
  * This routine is supposed to be
 */
void
SFontTTF::getBaseOffsets (const SS_Matrix2D& m, 
  SS_UCS4 _uch, double* offx, double* offy)
{
  *offx = 0.0;
  *offy = 0.0;
  if (!isOK() || setBaseCharacter==0 
      || setBaseCharacter==_uch) return;

  if (setBaseCharacter == baseCharacter && baseGlyph==0)
  {
      return;
  }

  if (setBaseCharacter != baseCharacter)
  {
    baseCharacter = setBaseCharacter;
    if (fontencoding.size()!=0 || baseCharacter >= 0x80000000 
       || !isOK() || hardWire == SS_MSLVT || hardWire == SS_NOJAMO)
    {
      baseGlyph = 0;
      return;
    }
    baseGlyph = findGlyph (baseCharacter);
    if (baseGlyph==0) 
    {
      return;
    }
//fprintf (stderr, "baseCharacter=%04X glyph=%04X\n", baseCharacter, baseGlyph);
    /* get the width of the base char */  
    baseWidth = getGlyphWidth (baseGlyph);
  }
  SString key ((char*)&baseGlyph, sizeof (SS_GlyphIndex));
  key.append (SString ((char*)&_uch, sizeof (SS_UCS4)));

  int cxy = mark2Base.get (key);
  if (cxy == SD_TTF_NAN) return;

  /* we had it not found. SGC */
  int cx = cxy & 0xffff;
  if (cx > 0x7fff) cx -= 0x10000 ;
  int cy = (cxy >> 16) & 0xffff;
  if (cy > 0x7fff) cy -= 0x10000;

  if (cx != 0 || cy != 0)
  {
    *offx =  cx * m.x0;
    *offy =  cy * m.y1;
    return;
  }
  SS_GlyphIndex gi = findGlyph(_uch);
  if (gi==0)
  {
    mark2Base.put (key, SD_TTF_NAN);
    return;
  }
  getOTFMarkToBase (baseGlyph, gi, &cx, &cy);
  if (cx==0 && cy==0)
  {
    mark2Base.put (key, SD_TTF_NAN);
    return;
  }
  /* In sync with SFont.cpp - we need to undo fallback positioning  */
  if (!isLeftAligned(_uch))
  {
    cx -= (baseWidth - getGlyphWidth (gi));
  }
  cxy = (cy << 16) & 0xffff0000;
  cxy = cxy | (cx & 0xffff);
  mark2Base.put (key, cxy);

  *offx =  (double)cx * m.x0;
  *offy =  (double)cy * m.y1;
  return;
}

/**
 * Get the unadjusted width of the glyph
 */
int 
SFontTTF::getGlyphWidth (SS_GlyphIndex glyph)
{
    TTF_HHEA* hhea_table = (TTF_HHEA*) tables[SS_TB_HHEA];
    LONGHORMETRIC* hmtx_entry = (LONGHORMETRIC*) tables[SS_TB_HTMX];
    int n_hmetrics = ntohs(hhea_table->numberOfHMetrics);
    short _bw;
    /* left side bearing is grossly ignored */
    if (glyph >= n_hmetrics)
    {
      _bw = ntohs (hmtx_entry[n_hmetrics-1].advanceWidth);
    }
    else
    {
      _bw = ntohs(hmtx_entry[glyph].advanceWidth);
    }
    int w = (int) ((_bw>0) ? _bw : -_bw);
    return w;
}

/**
 * Draw a single unicode character on canvas using the pen.
 * before calling this, you should call a newpath and
 * after calling this you may want to call fill.
 * @param canvas is the canvas to draw to
 * @param m is the transformation matrix.
 * @param uch is the unicode character
 * @param len is the length of unicode array
 * @return true if drawn
 */
bool
SFontTTF::draw (SCanvas* canvas, const SS_Matrix2D& matrix, SS_UCS4 _uch, 
  bool isLRContext)
{
  if (!isOK()) return false;
  SV_GlyphIndex gi;
  if (!findGlyphs (_uch, &gi)) return false;
  /* This makes things a bit faster */
  if (gi.size()==1)
  {
    SS_GlyphIndex g = gi[0];
    if (g == SD_G_INDIC_ZWJ || g== SD_G_INDIC_ZWNJ) return true;
    drawGlyph (canvas, matrix, g);
    return true;
  }

  SS_Matrix2D mo = matrix;

  /* a cluster can defined positions to fine adjust */
  const SV_INT *positions = mark2BaseList.get (
        SString((char*) &_uch, sizeof (SS_UCS4)));

  /* do we have fine-grained positions ?*/
  if (positions!= 0 && positions->size() >= gi.size())
  {
    mo = matrix;
    const SS_INT* posarray = positions->array();
    for (unsigned int i=0; i<gi.size(); i++)
    {
       // first is 0, or width.
      int xydiff = posarray[i];
      int xdiff = xydiff & 0xffff;
      if (xdiff > 0x7fff) xdiff -= 0x10000 ;
      int ydiff = (xydiff >> 16) & 0xffff;
      if (ydiff > 0x7fff) ydiff -= 0x10000;
#ifdef DEBUG_POSITION 
      fprintf (stderr, "%u->Positions[%u]=%d,%d\n", _uch, gi[i], xdiff, ydiff);
#endif /* DEBUG_LIGATURE */
      /* we have absolute positions */
      mo.t0 = matrix.t0 + matrix.x0 * xdiff;
      mo.t1 = matrix.t1 + matrix.y1 * ydiff;
      /* draw the glyph at this position */
      SS_GlyphIndex g = gi[i];
      if (g != SD_G_INDIC_ZWJ && g != SD_G_INDIC_ZWNJ)
      {
        drawGlyph (canvas, mo, g);
      }
    }
  }
  else
  { 
    for (unsigned int i=0; i<gi.size(); i++)
    {
      bool isRovas = getLigatureScriptCode (_uch) == SD_ROVASIRAS
           || getLigatureScriptCode (_uch) == SD_PUA_ROVAS;
      // For failsafe we preserve old behaviour for non SD_ROVASIRAS
      SS_GlyphIndex g = 
         (!isRovas  
          || (isLRContext && mo.x0 >= 0.0)
          || (!isLRContext && mo.x0 < 0.0)) ? gi[i] : gi[gi.size()-i-1];

      if (g != SD_G_INDIC_ZWJ && g != SD_G_INDIC_ZWNJ)drawGlyph (canvas, mo, g);
      double nwidth = widthGlyph (mo, g);
      if (nwidth < 0) nwidth = -nwidth;
      // For failsafe we preserve old behaviour for non SD_ROVASIRAS
      // Mirroring (mo.x0 < 0) will reverse the glyph order.
      if (!isRovas || mo.x0 >= 0.0)
      {
        mo.translate (nwidth, 0.0);
      }
      else
      {
        mo.translate (-nwidth, 0.0);
      }
    }
  }
  return true;
}

/**
 * Return the advacnce width of the glyphs
 * The value is the value that is multipied with matrix.
 * @param m is the transformation matrix.
 * @param uch is the unicode character
 * @param len is the length of unicode array
 * @param used will show how many characters were used in uch
 * @return the calibrated advace width, if with_ is passed and 
 *  true if it exists.
 */
bool
SFontTTF::width (const SS_Matrix2D& m, SS_UCS4 _uch, double* width_)
{
  if (width_) *width_ = 0.0;
  if (!isOK()) return false;

  /* a cluster can define positions to fine adjust */
  SV_GlyphIndex gi;
  if (!findGlyphs (_uch, &gi)) return false;

  if (_uch > 0x7fffffff)
  {
    const SV_INT* positions = mark2BaseList.get (
          SString((char*) &_uch, sizeof (SS_UCS4)));

    /* do we have fine-grained positions ?*/
    if (positions!= 0 && positions->size() > gi.size())
    {
      const SS_INT* arr = positions->array();
      if (width_)
      {
        int wid = arr[positions->size()-1];
        *width_ =  double (wid) * m.x0;
      }
      return true;
    }
  }
  if (!width_) return true;
  /**
   * Multiple glyphs draw on top of each other. 
   */
  double max = 0;
  /* We draw one after the other.*/
  for (unsigned int i=0; i<gi.size(); i++)
  {
    /* do not draw them */
    double nwidth = widthGlyph (m, gi[i]);
    if (nwidth < 0) nwidth = -nwidth;
    max += nwidth;
  }
  *width_ = max;
  return true;
}

/**
 * \brief Try to make a fuzzy guess if we need to align the diacritics to
 *    the left or to the right.
 * left aligned marks will be rendered this way:
 *    x----basewith----x
 *         x-markwidth-x 
 * right aligned marks will be rendered this way:
 *    x----basewith----x
 *    x-markwidth-x 
 * There are no docs available that this is the right way to do.
 * This is purely guesswork - most fonts will have negative bearing
 * for an overhang.
 */
bool
SFontTTF::isLeftAligned (SS_UCS4 c) const
{
  if (c > 0x7ffffff) return false;
  if (c == 0x0c55) return false;

  SS_GlyphIndex glyphno = ((SFontTTF*)this)->findGlyph (c);
  if (glyphno == 0) return false;
  if (glyphno == SD_G_INDIC_ZWJ) return false;
  if (glyphno == SD_G_INDIC_ZWNJ) return false;

  int lsb = getLeftSideBearing (glyphno);
  return (lsb >= 0);
}

/**
 * Find out the width, knowing the local glyph number
 * @param m is the transformation matrix
 * @param glyphno is the local glyph index in the glyph table.
 */
double
SFontTTF::widthGlyph (const SS_Matrix2D& m, SS_GlyphIndex glyphno)
{
  if (!isOK()) return 0.0;
  if (glyphno == SD_G_INDIC_ZWJ) return 0.0;
  if (glyphno == SD_G_INDIC_ZWNJ) return 0.0;

  SString key ((char*)&glyphno, sizeof (SS_GlyphIndex));
  int cw = char2Width.get (key);
  if (cw == SD_TTF_NAN) return 0.0;
  if (cw != 0) return (double)cw * m.x0;

  TTF_HHEA* hhea_table = (TTF_HHEA*) tables[SS_TB_HHEA];
  LONGHORMETRIC* hmtx_entry = (LONGHORMETRIC*) tables[SS_TB_HTMX];

  int n_hmetrics = ntohs(hhea_table->numberOfHMetrics);
  //SD_FWORD* lsblist = (SD_FWORD *) &hmtx_entry[n_hmetrics];
  
  unsigned short w;

  /* left side bearing is grossly ignored */
  if (glyphno >= n_hmetrics)
  {
    /* get the last one */
    w = ntohs (hmtx_entry[n_hmetrics-1].advanceWidth);
  }
  else
  {
    w = ntohs(hmtx_entry[glyphno].advanceWidth);
  }
  int wi = (int) w;
  /* replace 0.0 with SD_NAN */
  if (wi==0)
  {
    char2Width.put (key, SD_TTF_NAN);
  }
  else 
  {
    char2Width.put (key, (int)w);
  }
  return double (wi) * m.x0;
}

/**
 * \brief Find out raw, unscaled width of glyph.
 * \glyphno is th glyph 
 * \return a with that can be negative.
 */
int
SFontTTF::getWidth (SS_GlyphIndex glyphno)
{
  if (!isOK()) return 0;
  if (glyphno == SD_G_INDIC_ZWJ) return 0;
  if (glyphno == SD_G_INDIC_ZWNJ) return 0;

  SString key ((char*)&glyphno, sizeof (SS_GlyphIndex));

  TTF_HHEA* hhea_table = (TTF_HHEA*) tables[SS_TB_HHEA];
  LONGHORMETRIC* hmtx_entry = (LONGHORMETRIC*) tables[SS_TB_HTMX];
  unsigned short n_hmetrics = ntohs(hhea_table->numberOfHMetrics);
  unsigned short w;
  /* left side bearing is grossly ignored */
  if (glyphno >= n_hmetrics)
  {
    /* get the last one */
    w = ntohs (hmtx_entry[n_hmetrics-1].advanceWidth);
  }
  else
  {
    w = ntohs(hmtx_entry[glyphno].advanceWidth);
  }
  int wi = (int) w;
  int lsb = getLeftSideBearing (glyphno);
  if (lsb < 0) return -wi;
  return wi;
}

/**
 * \brief Find out raw, unscaled left-side bearing.
 * \glyphno is th glyph 
 * \return a with that can be negative.
 */
int
SFontTTF::getLeftSideBearing (SS_GlyphIndex glyphno) const
{
  if (glyphno == SD_G_INDIC_ZWJ) return 0;
  if (glyphno == SD_G_INDIC_ZWNJ) return 0;

  SString key ((char*)&glyphno, sizeof (SS_GlyphIndex));

  TTF_HHEA* hhea_table = (TTF_HHEA*) tables[SS_TB_HHEA];
  LONGHORMETRIC* hmtx_entry = (LONGHORMETRIC*) tables[SS_TB_HTMX];

  if (hmtx_entry == 0 || hhea_table==0) return 0;
  TTF_MAXP* maxp_table = (TTF_MAXP*) tables[SS_TB_MAXP];

  unsigned short numg = (maxp_table) 
     ? htons (maxp_table->numGlyphs) : 0;

  unsigned short n_hmetrics = ntohs(hhea_table->numberOfHMetrics);
  short lsb;
  if (glyphno >= n_hmetrics)
  {
    if (numg == 0)
    {
      lsb = ntohs (hmtx_entry[n_hmetrics-1].lsb);
    }
    else
    {
      short* arr =  (short*) &hmtx_entry[n_hmetrics];
      lsb = htons (arr[glyphno-n_hmetrics]);
    }
  }
  else
  {
    lsb = ntohs(hmtx_entry[glyphno].lsb);
  }
  return (int) lsb;
}

/*!
 * \brief Get the raw (unscaled) bounding box.
 * \return true if such a box exists.
 */
bool
SFontTTF::getBBOX (SS_GlyphIndex glyphno, 
  int* xMin, int* yMin, int* xMax, int* yMax) const
{
  if (glyphno == SD_G_INDIC_ZWJ) return false;
  if (glyphno == SD_G_INDIC_ZWNJ) return false;

  SD_BYTE* gstart = (SD_BYTE *) tables[SS_TB_GLYF];
  if (gstart == 0) return false;

  TTF_GLYF* gtable;
  int len =0;
  if (longOffsets)
  {
     SD_ULONG* lloca = (SD_ULONG *) tables[SS_TB_LOCA];
    if (lloca == 0) return false;
     unsigned int offs1 = ntohl (lloca[glyphno]);
     unsigned int offs2 = ntohl (lloca[glyphno+1]);
     gtable = (TTF_GLYF *) ((char*)gstart + offs1);
     len = offs2-offs1;
  }
  else
  {
     SD_USHORT* sloca = (SD_USHORT *) tables[SS_TB_LOCA];
     if (sloca == 0) return false;
     gtable = (TTF_GLYF *) (gstart + (ntohs (sloca[glyphno]) << 1));
     len = (ntohs (sloca[glyphno+1]) - ntohs (sloca[glyphno])) << 1;
  }
  if (len <= 0)
  {
    return false;
  }
  TTF_GLYF* kludge = 0;
  if ((((unsigned long) gtable) & 1) != 0)
  {
    kludge = new TTF_GLYF[len]; 
    CHECK_NEW (kludge);
    memcpy (kludge, gtable, len * sizeof (TTF_GLYF));
    gtable = kludge;
  }
  
  short xmin =  ntohs (gtable->xMin);
  short xmax =  ntohs (gtable->xMax);
  short ymin =  ntohs (gtable->yMin);
  short ymax =  ntohs (gtable->yMax);
  *xMin = xmin;
  *yMin = ymin;
  *xMax = xmax;
  *yMax = ymax;
  if (kludge) delete kludge;
  return true;
}

/**
 * Return the calibrated ascent
 * @param m is the transformation matrix.
 */
double
SFontTTF::ascent (const SS_Matrix2D& m)
{
  if (!isOK()) return 0.0;
  double rvle =  charAscent * m.y1;
  if (rvle < 0)
  {
//    fprintf (stderr, "FIXME negative ascent: SFontTTF.cpp\n");
    return 1;
  }
  return rvle;
}

/**
 * Return the calibrated descent
 * @param m is the transformation matrix.
 */
double
SFontTTF::descent (const SS_Matrix2D& m)
{
  if (!isOK()) return 0.0;
  double rvle =  - charDescent * m.y1;
  if (rvle < 0)
  {
//  fprintf (stderr, "FIXME negative descent: SFontTTF.cpp\n");
    return 1;
  }
  return rvle;
}

/**
 * Return the calibrated avarage width
 * @param m is the transformation matrix.
 */
double
SFontTTF::width (const SS_Matrix2D& m)
{
  if (!isOK()) return 0.0;
  double rvle =  charWidth * m.x0;
  if (rvle < 0) return -rvle;
  return rvle;
}

/**
 * Return the calibrated gap
 * @param m is the transformation matrix.
 */
double
SFontTTF::gap (const SS_Matrix2D& m) 
{
  if (!isOK()) return 0.0;
  double rvle =  lineGap * m.y1;
  if (rvle < 0) return -rvle;
  return rvle;
}

/**
 * Return the scale factor. You multiply this with point size you want.
 * matrix diagonals for a 10 point font is scale, scale
 */
double
SFontTTF::scale ()
{
  if (!isOK()) return 0.0;
  return scaleFactor;
}


/**
 * This routine tries to find the glyph indeces of a unicode input stream
 * @param in is the input stream
 * @param len is the length if in
 * @param out is the output 
 * @return the nuber of characters processed in 'in'
 */
bool
SFontTTF::findGlyphs (SS_UCS4 in, SV_GlyphIndex* out)
{
  if (!isOK()) return false;
  SString key ((char*)&in, sizeof (SS_UCS4));
  const SString* cached = char2Glyphs.get (key);
  if (cached)
  {
    if (cached->size()<sizeof (SS_GlyphIndex)) return false;
    unsigned int usize = cached->size();
    for (unsigned int i=0; i<usize; i+= sizeof (SS_GlyphIndex))
    {
       out->append (*((SS_GlyphIndex*)&cached->array()[i]));
    }
    return true;
  }
  

  TTF_CMAP* cmap_table = (TTF_CMAP*) tables[SS_TB_CMAP];
  int num_tables = ntohs(cmap_table->numberOfEncodingTables);
  if (num_tables ==0)
  {
    SString chc; char2Glyphs.put (key, chc);
    return false;
  }

  /* Try to get the ligature index from OTF */
  SS_UCS4 lig = in;
  unsigned int liglen = 0;
  unsigned int scriptcode = getLigatureScriptCode (lig);
  /* no support yet for this monster */

  if ( (scriptcode == SD_ROVASIRAS  || scriptcode == SD_PUA_ROVAS)
       
       && (liglen=getLigatureUnicode(lig, 0)) > 0)
  {
     if (fontencoding.size()!=0)
     {
        SString chc; char2Glyphs.put (key, chc);
        return false;
     }
     SS_UCS4* chars =  new SS_UCS4[liglen];
     CHECK_NEW (chars);
     getLigatureUnicode (lig, chars);
     SS_GlyphIndex* gi = new SS_GlyphIndex [liglen];
     CHECK_NEW (gi);

     unsigned int count = 0;
     unsigned int i;
     for (i=0; i<liglen; i++)
     {
        gi[count] = findGlyph (chars[i], false);
        if (gi[count] == 0) 
        {
            if (chars[i] != 0x200d) 
            {
               SString chc; char2Glyphs.put (key, chc);
               delete chars;
               delete gi;
               return false;
            }
        } 
        else
        {
          count++;
        }
     }
     if (count == 1)
     {
       for (i=0; i<count; i++) out->append (gi[i]); 
       SString chc ((char*)gi, count * sizeof (SS_GlyphIndex));
       char2Glyphs.put (key, chc);
       delete chars;
       delete gi;
       return true;
     }
    
     SS_GlyphIndex lig = 0;

     // I dont know yet what feature is best. 

     // We dont know the script code, so we pass 0.
     // We dont know if it should be alig, dlig or rlig. 
     // Use all that is available.
     unsigned int ligs = getOTFLigature (0, "rlig,liga", &gi[0], count, &lig, 4);
     while (ligs > 1 && ligs <= count) 
     {
       gi[0] = lig;
       // ligs fell out.
       for(i=1; i<count-ligs; i++) gi[i] = gi[i+ligs];
       count = count-ligs+1;
       ligs = getOTFLigature (0, "rlig,liga", &gi[0], count, &lig, 4);
     }

     for (i=0; i<count; i++) out->append (gi[i]); 
     SString chc ((char*)gi, count * sizeof (SS_GlyphIndex));
     char2Glyphs.put (key, chc);
     delete chars;
     delete gi;
     return true;
     
  }
  else if (scriptcode == SD_COMBINING_LIGATURE)
  {
    /* never comes here */
    SString chc;
    char2Glyphs.put (key, chc);
    return false;
  }
  else if (scriptcode == SD_AS_SHAPES && fontencoding.size() == 0
     && hasOTFLigatures())
  {
    /* No encoder support for OTF single substitution */
    if (hardWire == SS_MSLVT)
    {
      SString chc;
      char2Glyphs.put (key, chc);
      return false;
    }
    bool success = false;

    unsigned int fcode = (lig & 0xf000) >> 12;
    SS_UCS4 gcode = lig & 0x0fff;

    SS_GlyphIndex gi[2];
    unsigned int len = 1;
    bool shouldBe1 = true;
    switch (gcode)
    {
    case 1: /* A000X001 */
      gi[0] = findGlyph (0x072A); 
      gi[1] = findGlyph (0x0308); 
      len = 2;
      shouldBe1 = false;
      break;
    case 2: /* A000X002 */
      gi[0] = findGlyph (0x06A9); 
      gi[1] = findGlyph (0x0627); 
      len = 2;
      break;
    case 3: /* A000X003 */
      gi[0] = findGlyph (0x06A9); 
      gi[1] = findGlyph (0x0644); 
      len = 2;
      break;
    default:
      gi[0] = findGlyph (gcode); 
      break;
    }
    /* Check if we got all glyphs */
    for (unsigned int i=0; i<len;  i++)
    {
      if ( gi[i] == 0)
      {
        SString chc;
        char2Glyphs.put (key, chc);
        return false;
      }
    }
    /* isolated=1 initial=2 medial=3 final=4 */ 
    // Miikka:
    //  For some strange reason, Syriac alaph-fj is known as
    //  "fina" in OTF, and alaph-r as "med2", so we'll swap
    //  these two
    if (gcode == 0x0710) {
       if (fcode == 4) fcode=5;
       else if (fcode == 5) fcode=4;
    }

    if (len==2)
    {
      SS_GlyphIndex out = 0;
      if (getOTFLigature ("syrc", "ccmp", gi, 2, &out, 4))
      {
        gi[0] = out; len = 1;
        success = true;
      }
      /* is it urdu or just urd<space>? */
      else if (getOTFLigature ("urd ", "ccmp", gi, 2, &out, 4))
      {
        gi[0] = out; len = 1;
        success = true;
      }
      else if (getOTFLigature ("urdu", "ccmp", gi, 2, &out, 4))
      {
        gi[0] = out; len = 1;
        success = true;
      }
    }
    const char* fname = getShapeCode (fcode-1);
    SS_GlyphIndex go = substituteOTFGlyph (fname, gi[0]);

    /* use it if found - fallback otherwise */
    if (go)
    {
      gi[0] = go;
      success = true;
    }
    else if (len!=2)/* fallback where placement is important  */
    {
      SString chc;
      char2Glyphs.put (key, chc);
      return false;
    }

    /* Try to get a ligature substitution */
    if (len==2)
    {
      SS_GlyphIndex out = 0;
      /* FIXME: How about URDU? 
       * should we do this before shaping?
       */
      if (getOTFLigature ("syrc", "rlig", gi, 2, &out, 4))
      {
        gi[0] = out; len = 1;
        success = true;
      }
    }
    if (shouldBe1 && len != 1) success = false;
    if (success)
    {
      out->append (gi[0]);
      if (len==2) out->append (gi[1]);

      SString chc ((char*)out->array(), 
        out->size() * sizeof (SS_GlyphIndex));
      char2Glyphs.put (key, chc);
      return true;
    }
    else
    {
      SString chc;
      char2Glyphs.put (key, chc);
      return false;
    }
  }

  /* INDIC */
  else if (isLigature (lig) && hasOTFLigatures() 
      && scriptcode != SD_AS_SHAPES && scriptcode != SD_AS_LITERAL
       && (liglen=getLigatureUnicode(lig, 0)) > 0)
  {
     if ((hardWire == SS_MSLVT || hardWire == SS_NOJAMO)
          && scriptcode!=SD_HANGUL_PREC && scriptcode!=SD_HANGUL_JAMO)
     {
       SString chc;
       char2Glyphs.put (key, chc);
       return false;
     }

     bool fixedcluster = true;

     SS_UCS4* chars =  new SS_UCS4[liglen];
     CHECK_NEW (chars);
     getLigatureUnicode (lig, chars);

     /*
      * Complex script rendering, with uniscribe-like algorithm.
      * This can be enabled with command line:
      *       -us 
      * option.
      * SS_MSLVT and SS_NOJAMO hardwired fonts will not be processed.
      */
     SScriptProcessor engine (this);

     // Should be able to start with ZWJ 
     SS_UCS4 sample = ((chars[0] == 0x200D || chars[0] == 0x25cc) && liglen > 1) ? chars[1] : chars[0];
     // Precompiled Hangul should not go through this.
     if (scriptcode!=SD_HANGUL_PREC 
         && hardWire!=SS_MSLVT 
         && hardWire!=SS_NOJAMO 
         && engine.isSupported(sample))
     {
       bool isbegin = (scriptcode == SD_BENGALI_BEGIN);
       unsigned int plen = engine.put (chars, liglen, isbegin);
       /*
        * We already have a full cluster, so we can fail
        * only if the engine can not find some glyphs.
        */
       if (plen != liglen)
       {
         SString chc;
         char2Glyphs.put (key, chc);
         delete chars;
         return false;
       }
       engine.apply ();
       *out =engine.getGlyphs ();
       if (out->size()==0)
       {
         SString chc;
         char2Glyphs.put (key, chc);
         delete chars;
         return false;
       } 

       /* Maintain our glyph-cache. */
       SString chc ((char*)out->array(), out->size() * sizeof (SS_GlyphIndex));
       char2Glyphs.put (key, chc);

       /* Maintain our position-cache. */
       SV_INT positions = engine.getPositions();
       positions.append (engine.getWidth());
       mark2BaseList.put (key, positions);
       delete chars;
       return true;
     }

     /*
      * Hangul, Thai and Lao is processed right here in the switch
      */
     switch (scriptcode)
     {
     case SD_THAI:
     case SD_LAO:
       {
         bool ret = false;
         /* don't support non-unicode encoded fonts for now */
         const char * script = getLigatureScript (lig);
         if (fontencoding.size()!=0 || !isOK()  || script==0)
         {
           ret = false;
         }
         else
         {
           ret = findSouthIndicGlyphs (key, scriptcode, 
               script, chars, liglen, out);
         }
         if (ret)
         {
           SString chc ((char*)out->array(), 
              out->size() * sizeof (SS_GlyphIndex));
           char2Glyphs.put (key, chc);
         }
         else
         {
           SString chc;
           char2Glyphs.put (key, chc);
         }
         delete chars;
         return ret;
       }
     case SD_HANGUL_PREC:
     case SD_HANGUL_JAMO:
       {
         bool ret = findJamoGlyphs (chars, liglen, out);
         /* cache */
         if (ret)
         {
           SString chc ((char*)out->array(), 
              out->size() * sizeof (SS_GlyphIndex));
           char2Glyphs.put (key, chc);
         }
         else
         {
           SString chc;
           char2Glyphs.put (key, chc);
         }
         delete chars;
         return ret;
       }
     case SD_TAMIL:
       fixedcluster = true;
       break;
     default: 
       fixedcluster = false;
       break;
     }

     SUniMap umap = charEncoder;
     if (!umap.isOK())
     {
        delete chars;
        SString chc;
        char2Glyphs.put (key, chc);
        return false;
     }

     /* we allocate one more to allow for LEFT_RIGHT vowel expansion */
     SS_GlyphIndex* gchars = new SS_GlyphIndex[liglen+1];
     CHECK_NEW (gchars);
     const char * script = getLigatureScript (lig);
     if (script == 0) script = "default";

     /* get the encoder for this table. */
     bool decoded = true; 
     /* we need this hocus-pocus because getLigature works on
        glyph indeces */
     /* for indic modifiers */
     unsigned int mstart = 0;
     unsigned int mend = 0;
     for (unsigned int i=0; i<liglen; i++)
     {
       SS_UCS2 ucs2 = umap.encode (chars[i]);
       if (ucs2==0)
       {
         if (chars[i] > 0xffff)
         {
            decoded = false;
            break;
         }
         /* BE AWARE HACK! Try straight unicode  */
         ucs2 = chars[i];
       }
       gchars[i] =  findGlyph(ucs2);
       if (gchars[i]==0)
       {
         decoded = false;
         break;
       }
       int endtype = getCharType (chars[i]);
       if (i>0 && endtype == SD_INDIC_MODIFIER && mstart == 0)
       {
          mstart = i; mend = liglen;
       }
     }
     /* adjust liglen to where modifiers start */
     if (mstart != 0)
     {
        liglen = mstart;
     }

     /* this is unicode encoded... */
     SS_GlyphIndex halant = findGlyph (getHalant (scriptcode));
     SS_GlyphIndex reorder = 0;
     SS_GlyphIndex addVirama = 0;
     unsigned int inlen = liglen;
     bool *gbase = NULL;

     // post-consonant Malayalam ra has to be reordered to syllable start
     if (scriptcode == SD_MALAYALAM)
        reorder = findGlyph (0x0d30);

     // special rules for clusters ending in virama
     if (decoded && liglen == 2 && chars[1] == getHalant(scriptcode))
     {
       decoded = false;
       unsigned int olen = getOTFLigatures (gchars, inlen, 
            script, "haln", halant, reorder, gbase);
       if (olen != inlen)
       {
          debugChars ("GCHARS haln=", gchars, olen);
          decoded = true;
          inlen--;
       }
     }
     else if (decoded && chars[liglen-1] == getHalant(scriptcode))
     {
        // todo - RA+H RA+H
        addVirama = gchars[liglen-1];
        inlen--;
     }
     /*
      * Scripts like Tamil do not need complex processing. 
      * The combinations are finite, a fixed cluster suffices.
      */
     if (fixedcluster && decoded)
     {
       SS_GlyphIndex gi;
       unsigned int nind =  getOTFLigature (script, 0, gchars, liglen, &gi);
       if (nind == liglen)
       {
         out->append (gi);
       }
       else
       {
         decoded = false;
       }
     }
     /*
      * Complex script rendering, with our own algorithm.
      */
     else if (decoded)
     {
       /* ----> DEBUG Information */
       debugChars ("GCHARS=", gchars, liglen);
#ifdef DEBUG_LIGATURE
       fprintf (stderr, "Halant=%04X reorder=%04X gbase=%04X\n", 
         halant, reorder, (gbase==0)?0: *gbase);
#endif
       /* ----< DEBUG Information */
       unsigned int olen = getOTFLigatures (gchars, inlen, 
            script, "akhn", halant, reorder, gbase);
       if (olen != inlen)
       {
          debugChars ("GCHARS akhn=", gchars, olen);
          inlen = olen;
       }
       /* can be at beginning only */
       SS_GlyphIndex rphfGlyph = 0;
       SS_GlyphIndex rphfNone = 0;
       if (inlen>2 && gchars[2] != findGlyph(SD_CD_ZWJ))
       {
         debugChars ("BEFORE RPH =", gchars, inlen);
         SS_GlyphIndex g[2]; g[0] = gchars[0]; g[1] = gchars[1];
         olen = getOTFLigatures (g, 2 , script, "rphf",
              halant, reorder, gbase);
         if (olen == 2)
           olen = getOTFLigatures (g, 2, script, "abvs",
                halant, reorder, gbase);
         if (olen == 1 && liglen > 2)
         {
           int ct = getCharType (chars[2]);
           // if chars[2] == SD_CD_ZWJ will be handled automagically here 
           if (ct == SD_INDIC_CONSONANT_BASE 
             || ct == SD_INDIC_CONSONANT_POST_BASE 
             || ct == SD_INDIC_CONSONANT_BELOW_BASE)
           {
             debugChars ("GCHARS rphf=", g, olen);
             rphfGlyph = g[0];
           }
           else
           {
             //fprintf (stderr, "GCHARS rphfNone\n");
             rphfNone = gchars[0];
           }
           /* remove */
           for (unsigned int i=2; i<inlen; i++) gchars[i-2] = gchars[i];
           inlen -= 2;
         }
         debugChars ("AFTER RPH =", gchars, inlen);
       }

       // Vowel placement in Malayalam is somewhat peculiar, as compared
       // to other Indic scripts; also Telugu and Kannada need special treatment
       if ((scriptcode == SD_MALAYALAM &&
           (getCharType (chars[liglen-1]) == SD_INDIC_LEFT_VOWEL ||
            getCharType (chars[liglen-1]) == SD_INDIC_LEFT_RIGHT_VOWEL)) ||
           ((scriptcode == SD_TELUGU || scriptcode == SD_KANNADA) &&
           liglen > 2))
       {
          gbase = new bool [inlen-1];
          for (unsigned int i=0; i<inlen-1; i++)
          {
             if (gchars[i] == halant)
                gbase[i] = false;
             else gbase[i] = true;
          }
       }

       olen = getOTFLigatures (gchars, inlen,
            script, "blwf", halant, reorder, gbase);
       if (olen != inlen)
       {
          debugChars ("GCHARS blwf=", gchars, olen);
          inlen = olen;
       }
       olen = getOTFLigatures (gchars, inlen,
            script, "vatu", halant, reorder, gbase);
       if (olen != inlen)
       {
          debugChars ("GCHARS vatu=", gchars, olen);
          inlen = olen;
       }
       olen = getOTFLigatures (gchars, inlen,
            script, "pstf", halant, reorder, gbase);
       if (olen != inlen)
       {
          debugChars ("GCHARS pstf=", gchars, olen);
          inlen = olen;
       }
       olen = getOTFLigatures (gchars, inlen, script, "blws",
            halant, reorder, gbase);
       if (olen != inlen)
       {
          debugChars ("GCHARS blws=", gchars, olen);
          inlen = olen;
       }
       olen = getOTFLigatures (gchars, inlen, script, "psts",
            halant, reorder, gbase);
       if (olen != inlen)
       {
          debugChars ("GCHARS psts=", gchars, olen);
          inlen = olen;
       }
       /* if we still have U+0931 at this point, let's try U+0930 "half" */
       if (gchars[0] == findGlyph(0x0931) && inlen > 2)
       {
          gchars[0] = findGlyph(0x0930);
          olen = getOTFLigatures (gchars, inlen, script, "half",
               halant, reorder, gbase);
          if (olen != inlen)
          {
             debugChars ("GCHARS eyelash=", gchars, olen);
             inlen = olen;
          }
          else // otherwise we change it back to U+0931
          {
             gchars[0] = findGlyph(0x0931);
          }
       }
       /* Half-forms */
       olen = getOTFLigatures (gchars, inlen, script, "half",
            halant, reorder, gbase);
       if (olen != inlen)
       {
          debugChars ("GCHARS half=", gchars, olen);
          inlen = olen;
       }

       olen = getOTFLigatures (gchars, inlen, script,
           "!pstf,blwf,vatu,blws,rphf,psts,haln", halant, reorder, gbase);
       while (olen != inlen)
       {
          debugChars ("GCHARS any=", gchars, olen);
          inlen = olen;
          olen = getOTFLigatures (gchars, inlen, script,
            "!pstf,blwf,vatu,blws,rphf,psts,haln", halant, reorder, gbase);
       }

       /* in fact, this alone should do all the junk job (above) */
       /*
        * From: http://www.microsoft.com/typography/otspec/indicot/reg.htm
        *
        * In scripts like Malayalam, the halant form of certain consonants
        * is represented by 'chillaksharams'. These can appear at any
        *  non-initial or final consonant location in a syllable. 
        *
        * - unfortunatelly it is very vague: 'scripts like Malayalam'
        *    gaspar
        */
       if (inlen > 1)
       {
         /* does it start with consonant + halant + ZWJ ? */
         bool firstHalanOK = scriptcode!=SD_MALAYALAM  /* bit vague */
           || (inlen > 2 && gchars[1] == halant && gchars[2] == SD_G_INDIC_ZWJ);

         if (firstHalanOK) /* a bit vague */
         {
            olen = getOTFLigatures (gchars, inlen, script,
              "haln", halant, reorder, gbase);
         }
         else
         {
            olen = getOTFLigatures (&gchars[1], inlen-1, script,
              "haln", halant, reorder, gbase?&gbase[1]:0);
            olen++;
         }
         inlen = olen;
       }

       /* insert back virama and search for feature "haln" */
       if (addVirama)
       {
         gchars[olen] = addVirama;
         inlen++;
         olen = getOTFLigatures (gchars, inlen, script, "haln",
              halant, reorder, gbase);
       }
       /* This is "haln" not applied in while loop because of a specific 
          check condition for SD_G_INDIC_ZWNJ in getOTFLigatures */
       else if (inlen > 1 && gchars[inlen-1] == SD_G_INDIC_ZWNJ)
       {
         if (scriptcode != SD_MALAYALAM) /* a bit vague */
         {
           olen = getOTFLigatures (gchars, inlen-1, script, "haln",
                halant, reorder, gbase);
           if (olen != inlen-1)
           {
              gchars[olen] = gchars[inlen-1];
              olen++;
           }
         }
       }
       /* insert back non repha after getOTFLigatures */
       if (rphfNone)
       {
         for (unsigned int i=olen-1; i>1; i--)
         {
            gchars[i] = gchars[i-2];
         }
         gchars[0] = rphfNone;
         gchars[1] = halant;
         olen += 2;
       }
          
       int endtype = getCharType (chars[liglen-1]);
       switch (endtype)
       {
       case SD_INDIC_LEFT_VOWEL:
         if (olen > 1)
         {
            SS_GlyphIndex g = gchars[olen-1];
            if (gbase)
            {
               unsigned int i;
               for (i=olen-2; i && !gbase[i]; i--);
               for (unsigned int j=olen-1; j > i; j--)
                  gchars[j]=gchars[j-1];
               gchars[i] = g;
            }
            else
            {
               for (unsigned int i=olen-1; i; i--)
                  gchars[i]=gchars[i-1];
               gchars[0] = g;
            }
         }
         break;
       case SD_INDIC_RIGHT_VOWEL:
       case SD_INDIC_TOP_VOWEL:
       case SD_INDIC_BOTTOM_VOWEL:
         if (olen > 0)
         {
            SS_GlyphIndex g = gchars[olen-1];
            if (gbase)
            {
               unsigned int i;
               for (i=olen-2; i && !gbase[i]; i--);
               for (unsigned int j=olen-1; j > i+1; j--)
                  gchars[j]=gchars[j-1];
               gchars[i+1] = g;
            }
         }
         break;
       case SD_INDIC_LEFT_RIGHT_VOWEL:
         if (olen > 0)
         {
           SS_GlyphIndex g1 = findGlyph (getLRVowelLeft(chars[liglen-1]));
           SS_GlyphIndex g2 = findGlyph (getLRVowelRight(chars[liglen-1]));
           if (g1 && g2)
           {
             if (gbase)
             {
               unsigned int i;
               for (i=olen-2; i && !gbase[i]; i--);
               for (unsigned int j=olen-1; j > i; j--)
                  gchars[j]=gchars[j-1];
               gchars[i] = g1;
             }
             else
             {
               for (unsigned int i=olen; i; i--)
                  gchars[i]=gchars[i-1];
               gchars[0] = g1;
             }
             gchars[olen] = g2;
             olen++;
	     liglen++; // We increase this, so that the program could notice
	       // that the original character sequence has changed
           }
         }
       }
       if (rphfGlyph)
       {
          gchars[olen] = rphfGlyph;
          olen++;
       }
       /* add modifiers back */
       for (unsigned int i=mstart; i<mend; i++)
       {
          gchars[olen] = gchars[i];
          olen++;
          liglen++;
       }
       inlen = olen;
       olen = getOTFLigatures (gchars, inlen, script, "blws",
            halant, reorder, gbase);
       if (olen != inlen)
       {
          debugChars ("GCHARS blws=", gchars, olen);
          inlen = olen;
       }
       olen = getOTFLigatures (gchars, inlen, script, "abvs",
            halant, reorder, gbase);
       if (olen != inlen)
       {
          debugChars ("GCHARS abvs=", gchars, olen);
          inlen = olen;
       }
       olen = getOTFLigatures (gchars, inlen, script, "psts",
            halant, reorder, gbase);
       if (olen != inlen)
       {
          debugChars ("GCHARS psts=", gchars, olen);
          inlen = olen;
       }

       /* Finally, do a chaining context substitution */
       bool chained = doContextSubstitutions (gchars, inlen, &olen, script, 0);
       if (chained)
       {
          debugChars ("GCHARS ChainContext=", gchars, olen);
          inlen = olen;
       }
       /* Just consider this decoded, even if no substitution is made. */
       if (olen > 0)
       {
          for (unsigned int i=0; i<olen; i++)
          {
            out->append (gchars[i]);
          }
          decoded = true;
       }
       else
       {
          decoded = false;
       }
     }

     /*
      * At this point both fixed and variable cluster 
      * glyph substitutions have been finished for
      * all scripts.
      */
     if (decoded)
     {
       SString chc ((char*)out->array(), out->size() * sizeof (SS_GlyphIndex));
       char2Glyphs.put (key, chc);
       /* some scripts, like TIBETAN require more fine grained positioning */
       if (!storeMarkPositions (key, out->array(), out->size()))
       {

#ifdef DEBUG_LIGATURE
         fprintf (stderr, "Can not find mark to base for %X\n", in);
#endif
       }
       else
       {
#ifdef DEBUG_LIGATURE
         fprintf (stderr, "Found mark to base for %X\n", in);
#endif
       }

#ifdef DEBUG_LIGATURE
       fprintf (stderr, "SFontTTF.cpp: Found OTF ligature:%s[%04X] %u -> %u: ", 
          script, (lig & 0xffff), liglen, out->size());
       debugChars ("GCHARS glyphs=",out->array(), out->size());
       for (unsigned int i=0; i<liglen; i++)
       {
         fprintf (stderr, " %X", chars[i]);
       }
       fprintf (stderr, "\n");
#endif
       delete chars;
       delete gchars;
       if (gbase) delete gbase;
       return true;
     }
     /* try to fall-back to font encoder, or hardwire if any */
     out->clear();
     delete chars;
     delete gchars;
     if (gbase) delete gbase;
  } /* End of Indic/Hangul/OTF */

  /* Let precomposed Hangul through. */
  
  bool okToProcess = true;

  /* Set okToProcess accoriding to artifical encodings */
  switch (hardWire)
  {
  case SS_MSLVT:
    /* precomposed or jamo */
    okToProcess = ((in>=0xac00 && in<0xd7a4) || getJamoClass (in) != SD_JAMO_X);
    break;
  case SS_NOJAMO:
    /* non jamo */
    okToProcess = (getJamoClass (in) == SD_JAMO_X);
    break;
  case SS_NONE:
  default:
    okToProcess = true;
    break;
  }

  if (!okToProcess)
  {
     SString chc;
     char2Glyphs.put (key, chc);
     return false;
  }

  /**
   * When using external maps we are using the same map for all
   * tables.
   */
  if (fontencoding.size()!=0 &&  charEncoder.isOK() && !charEncoder.isUMap())
  {
     /* max 3 */
     SV_UCS4 ucs4; ucs4.append (in); SV_UCS4 decd;
     SUniMap umap = charEncoder;
     unsigned int lifted = umap.lift (ucs4, 0, false, &decd);
     if (lifted == 0)
     {
        /* try straight - font has to have ascii mapping */
        SS_GlyphIndex gi = (in>=0x80) ? 0 :  findGlyph (in);
        if (gi)
        {
          out->append (gi);
          SString chc ((char*) &gi,  sizeof (SS_GlyphIndex));
          char2Glyphs.put (key, chc);
          return true;
        }
        SString chc; char2Glyphs.put (key, chc);
        return false;
     }
     for (unsigned int i=0; i<decd.size(); i++)
     {
        SS_GlyphIndex gi = findGlyph (decd[i]);
        if (gi == 0)
        {
          out->clear ();
          SString chc; char2Glyphs.put (key, chc);
          return false;
        }
        out->append (gi);
     }
     SString chc ((char*) out->array(), out->size() * sizeof (SS_GlyphIndex));
     char2Glyphs.put (key, chc);
     return true;
  }

  /* as I see there is no way to define multiple tables
   * for now so we just hardcode first one in reality we should
   * go through  0..num_tables
   */
  SUniMap umap = charEncoder;
  if (!umap.isOK())
  {
    SString chc; char2Glyphs.put (key, chc);
    return false;
  }
  /* get the encoder for this table. */
  // FIXME:
  // if in is non-BMP we will just use the value - hack - I know 
  SS_UCS4 ucs4 = (in>0xffff) ? in : (SS_UCS4) umap.encode (in);
  if (ucs4==0)
  {
    SString chc; char2Glyphs.put (key, chc);
    return false;
  }
  SS_GlyphIndex o = findGlyph (ucs4);
  if (o==0)
  {
    /* Try the decomposed one instead */
    if (hardWire==SS_MSLVT && 
       /* chekc for Precomposed Korean or JAMO */
      ((in>=0xac00 && in<0xd7a4) || getJamoClass (in) != SD_JAMO_X))
    {
      SS_UCS4 chars[3]; /* lvt */
      unsigned int liglen = 1;
      /* decompose if precomposed */
      if (in>=0xac00 && in<0xd7a4)
      {
        SS_UCS4 hangul = ucs4 - 0xac00;
        chars[0] = hangul / (21*28) + 0x1100;
        chars[1] = (hangul % (21*28))/28 + 0x1161;
        chars[2] = (hangul % 28) + 0x11a7;
        liglen = (chars[2] == 0x11a7) ? 2 : 3;
      }
      else
      {
        liglen = 1;
        chars[0] = in;
      }
      bool ret = findJamoGlyphs (chars, liglen, out);
      /* cache */
      if (ret)
      {
        if (liglen==1 && getJamoClass (in) != SD_JAMO_L)
        {
          /* standalone jamos fill emptyness */
          SS_GlyphIndex placeHolder = findGlyph (0x4e00);
          if (placeHolder) out->insert (0, placeHolder);
        }
        SString chc ((char*)out->array(), 
           out->size() * sizeof (SS_GlyphIndex));
        char2Glyphs.put (key, chc);
        return ret;
      }
      /* not found */
    }
    /* cache the nothing. */
    SString chc; char2Glyphs.put (key, chc);
    return false;
  }
  out->append (o);
  SString chc ((char*)&o, sizeof (SS_GlyphIndex));
  char2Glyphs.put (key, chc);
  return true;
}

/**
 * Chaining Context Substitution may not change the length of the
 * input.
 * @param ino is the input/output array.
 * @param inlen is the length of the ino array.
 * @param olen is the new length of the ino array.
 * @param script is the OTF script code - or null.
 * @param feature is the OTF feature code - or null.
 * @return true if at least one substitution has been made.
 */
bool
SFontTTF::doContextSubstitutions (SS_GlyphIndex* ino, unsigned int inlen, 
  unsigned int * olen, const char* script, const char* feature)
{
  unsigned int i;
  unsigned int len = inlen;
  *olen = len;
  for (i=0; i<len; i++)
  {
    if (ino[i] == 0) return false;
  }
  unsigned int curin=0;

  // We have our limitattions: it can not increase the glyphs.
  SS_GlyphIndex* lig = new SS_GlyphIndex[len];
  bool isok = false;
  while (curin < len)
  {
    if (len-curin < 2) break;

    // Substitute from curin till the end of the input array. 
    unsigned int nind = 
      getOTFLigature (script, feature, &ino[curin], len-curin, lig, 6);
    if (nind == 0)
    {
      curin++;
      continue;
    }
    /* copy output */
    isok = true;
    for (i=0; i<nind; i++)
    {
      ino[i+curin] = lig[i];
    }
    len = nind + curin;
    curin++;
  }
  delete [] lig;
  *olen = len;
  return isok;
}

/**
 * Get OTF ligatures.
 * @param ino is the input-output buffer
 * @param len is the input length
 * @param gbase contains true at base ligature.
 * @return output length
 */
unsigned int
SFontTTF::getOTFLigatures (SS_GlyphIndex* ino, unsigned int len,
  const char* script, const char* feature, SS_GlyphIndex halant, 
  SS_GlyphIndex reord, bool* base) 
{
  unsigned int i;
  for (i=0; i<len; i++)
  {
    if (ino[i] == 0) return len;
  }
  if (len == 1) return 1;
  /* collect all ligatures in one loop, starting from big ones. */
  unsigned int curin=0;
  unsigned int curout=0;
  /* moved this outside of the loop */
  
  bool needreorder = feature!=0 
    && (strcmp (feature, "vatu") ==0 
        || strcmp (feature, "blwf")==0 || strcmp (feature, "pstf")==0
        || strcmp (feature, "blws")==0 || strcmp (feature, "psts")==0);

  while (curin < len)
  {
    SS_GlyphIndex lig;
    unsigned int nind = 0;
    bool fullglyph = true;
    bool reorder = false;
    /* with these features we always need to reorder stuff */
    if (needreorder) { 
      /* we have at least 3 characters to reorder */
      if (len-curin >= 3 && ino[curin+1] == halant)
      {
        SS_GlyphIndex tmp[3];
        tmp[0] = ino[curin];
        tmp[1] = ino[curin+2];
        tmp[2] = ino[curin+1];
        nind = getOTFLigature (script, feature, tmp, 3, &lig);
      }
      /* we have at least 2 characters to reorder */
      else if (len-curin >= 2 && ino[curin] == halant)
      {
        SS_GlyphIndex tmp[2];
        tmp[0] = ino[curin+1];
        tmp[1] = ino[curin];
        nind = getOTFLigature (script, feature, tmp, 2, &lig);
        fullglyph = false;
        /* set reorder if resulting/original glyphs is a reorder glyph */
        if (reord && tmp[0] == reord) reorder = true;
        /* reorder shows that this should go to zero position */
      }
      else
      {
        /* don't reorder - just apply feature. nothing will happen. */
        nind = getOTFLigature (script, feature, &ino[curin], len-curin, &lig);
      }
    }
    else /* ! needreorder */
    {
      /* just apply feature.  */
      nind = getOTFLigature (script, feature, &ino[curin], len-curin, &lig);
    }
    /* nind is the index of the ligature found */
    bool isok = (nind != 0);

    /* i point to next character index. ino is not rewritten yet and lig may 
       contain a ligature that was found. */
    i=nind+curin;
    if (isok)
    {
      /* don't worry i can not be zero nind!=0 checks it*/
      /* SD_G_INDIC_ZWNJ prevents half form when halant comes. */
      /* halant + ZWNJ */
      if (i+1==len && ino[i-1] == halant && ino[i] == SD_G_INDIC_ZWNJ)
      {
        isok = false;
      }
    }
    /* substitution can go ahead */
    if (isok)
    {
      ino[curout] = lig;
      curout++;
      curin = i;
      /* we need to update base output parameter */
      if (base) {
         unsigned int j, k;
         for (j=curin, k=curout; j<len; j++, k++) base[k]=base[j];
         if (!fullglyph)
         {
            base[curout-1] = false;
         }
      }
      /* we need to update base output parameter */
      if (reorder)
      {
         /* shift stuff up, and insert to zero pos, instead of curout */
         for (unsigned int j=curout; j; j--) ino[j]=ino[j-1];
         ino[0] = lig;
      }
    }
    else  /* not ok, continue loop to find ligatures from next position */
    {
      ino[curout] = ino[curin];
      curout++;
      curin++;
    }
  }
  return curout;
}

/**
 * Find one single glyph. 
 * @return the index or 0.
 * TODO: SFontTTF currently handles TTF_CMAP_FMT4 only. 
 *  This can encode 16 bit unicode only. Make it handle
 *   format 8  Mixed 16 bit and 32 bit coverage using Surrogates(U+D800-U+DFFF)
 *   format 10 Trimmed Array using Surrogates (U+D800-U+DFFF)
 *   format 12 Segmented Coverage using Surrogates (U+D800-U+DFFF)
 * I am not aware of any other format that does not used 
 * this MS surrogate hack.
 * (Guess why are these ugly surrogates in Unicode ? :)
 * /param ownjoiners is true if we use our 
 *        own SD_G_INDIC_ZWJ and SD_G_INDIC_ZWNJ
 */
SS_GlyphIndex
SFontTTF::findGlyph (SS_UCS4 in, bool ownjoiners)
{
  /* Support only BMP for now */
  if (in == SD_CD_ZWJ && ownjoiners) return SD_G_INDIC_ZWJ;
  if (in == SD_CD_ZWNJ && ownjoiners) return SD_G_INDIC_ZWNJ;
  /* Experimental filters for whole ranges */
  switch (hardWire)
  { 
  case SS_INDIC:
    if (in<0x0900 || in>0x0FFF) return 0;
    break;
  case SS_DEVANAGARI:
    if (in<0x0900 || in>0x097F) return 0;
    break;
  case SS_BENGALI:
    if (in<0x0980 || in>0x09FF) return 0;
    break;
  case SS_GURMUKHI:
    if (in<0x0A00 || in>0x0A7F) return 0;
    break;
  case SS_GUJARATI:
    if (in<0x0A80 || in>0x0AFF) return 0;
    break;
  case SS_ORIYA:
    if (in<0x0B00 || in>0x0B7F) return 0;
    break;
  case SS_TAMIL:
    if (in<0x0B80 || in>0x0BFF) return 0;
    break;
  case SS_TELUGU:
    if (in<0x0C00 || in>0x0C7F) return 0;
    break;
  case SS_KANNADA:
    if (in<0x0C80 || in>0x0CFF) return 0;
    break;
  case SS_MALAYALAM:
    if (in<0x0D00 || in>0x0D7F) return 0;
    break;
  case SS_SINHALA:
    if (in<0x0D80 || in>0x0DFF) return 0;
    break;
  case SS_THAI:
    if (in<0x0E00 || in>0x0E7F) return 0;
    break;
  case SS_LAO:
    if (in<0x0E80 || in>0x0EFF) return 0;
    break;
  case SS_TIBETAN:
    if (in<0x0F00 || in>0x0FFF) return 0;
    break;
  case SS_JAMO:
    if (in<0x1100 || in>0x11FF) return 0;
    break;
  default: break;
  }

  TTF_CMAP* cmap_table = (TTF_CMAP*) tables[SS_TB_CMAP];
  int num_tables = ntohs(cmap_table->numberOfEncodingTables);
  /* Go for it directly */
  bool uniconly = false;
  if (charEncoderTable != (unsigned int) num_tables)
  {
    TTF_CMAP_ENTRY* table_entry = 
             &(cmap_table->encodingTable[charEncoderTable]);

    int offset = ntohl(table_entry->offset);
    int format = ntohs(*((SD_USHORT*)((SD_BYTE*)cmap_table+offset)));
    SS_GlyphIndex gi=0;
    switch (format)
    {
    case 0:
      gi =  findGlyph0 ((TTF_CMAP_FMT0 *)((SD_BYTE *)cmap_table + offset), in);
      break;
    case 2:
      /* this has to have an encoder */
      if (fontencoding.size())
      {
        gi =  findGlyph2 ((TTF_CMAP_FMT2 *)((SD_BYTE *)cmap_table + offset), in);
      }
      break;
    case 4:
      gi =  findGlyph4 ((TTF_CMAP_FMT4 *)((SD_BYTE *)cmap_table + offset), in);
      break;
    case 8: /* TODO */
      break;
    case 10:
      break;
    case 12:
      gi =  findGlyph12 ((TTF_CMAP_FMT12 *)((SD_BYTE *)cmap_table + offset), in);
      break;
    default:
      break;
    }
    if (gi) return gi;
    /* look for unicode encoding only */
    uniconly = true;
  }
  //fprintf (stderr, "Second round\n");

  /* go through all tables */
  int platform = 0;
  int encoding_id = 0;
  for (int i=0; i < num_tables; i++)
  {
    TTF_CMAP_ENTRY* table_entry = &(cmap_table->encodingTable[i]);
    int offset = ntohl(table_entry->offset);
    int format = ntohs(*((SD_USHORT*)((SD_BYTE*)cmap_table+offset)));
    bool isok = true;
    if (uniconly)
    {
      platform = ntohs(table_entry->platformID);
      encoding_id = ntohs(table_entry->encodingID);
      isok = false;
      switch (platform)
      {
      case TT_PLAT_ID_MICROSOFT:
        switch (encoding_id)
        { 
        case TT_ENC_ID_MS_UNICODE:
        case TT_ENC_ID_MS_SURROGATES:
          isok = true; break;
        case TT_ENC_ID_MS_SYMBOL:
        case TT_ENC_ID_MS_SHIFT_JIS:
        case TT_ENC_ID_MS_BIG5:
        case TT_ENC_ID_MS_RPC:
        case TT_ENC_ID_MS_WANSUNG:
        case TT_ENC_ID_MS_JOHAB:
        default:
         break;
        }
        break;
      case TT_PLAT_ID_ISO:
        switch (encoding_id)
        { 
        case TT_ENC_ID_ISO_ASCII:
        case TT_ENC_ID_ISO_10646:
        case TT_ENC_ID_ISO_8859_1:
         isok = true; break;
        case TT_ENC_ID_ANY:
        default:
         break;
        }
        break;
      case TT_PLAT_ID_APPLE:
        switch (encoding_id)
        { 
        case TT_ENC_ID_APPLE_UNICODE_1_1:
        case TT_ENC_ID_APPLE_ISO_10646:
        case TT_ENC_ID_APPLE_UNICODE_2_0:
          isok = true; break;
        case TT_ENC_ID_APPLE_DEFAULT:
        default:
          break;
        }
        break;
      case TT_PLAT_ID_MACINTOSH:
        switch (encoding_id)
        { 
        case TT_ENC_ID_MAC_ROMAN:
          isok = true; break;
          /* a lot of other encodings missing */
        default:
          break;
        }
        break;
      default:
        break;
      }
    }
    if (!isok) continue;
    SS_GlyphIndex gi=0;
    switch (format)
    {
    case 0:/* TODO - SGC 8 bit*/
      gi =  findGlyph0 ((TTF_CMAP_FMT0 *)((SD_BYTE *)cmap_table + offset), in);
      break; 
    case 2:
      /* this has to have an encoder */
      if (fontencoding.size())
      {
        gi =  findGlyph2 ((TTF_CMAP_FMT2 *)((SD_BYTE *)cmap_table + offset), in);
      }
      break;
    case 4:
      gi =  findGlyph4 ((TTF_CMAP_FMT4 *)((SD_BYTE *)cmap_table + offset), in);
      break;
    case 8: /* TODO */
      break;
    case 10:
      break;
    case 12:
      gi =  findGlyph12 ((TTF_CMAP_FMT12 *)((SD_BYTE *)cmap_table + offset), in);
      break;
    default:
      break;
    }
/*
    if (uniconly && in > 0x1000 && format == 12 && gi)
    {
     fprintf (stderr, "platform = %d id=%d\n", platform, encoding_id);
    }
*/
    if (gi) return gi;
  }
  return 0;
}

/**
 * Try to find Glyph in an encoding format 4 table 
 * @param encoding4 is the encoding4 tables 
 * @param ucs2 is the character to find.
 * @return the glyph index.
 */
static SS_GlyphIndex
findGlyph0 (TTF_CMAP_FMT0* encoding0, SS_UCS4 ucs4)
{
  if (ucs4==0||ucs4>255) return 0;
  /* 1 byte does not need byteorder */
  return (SS_GlyphIndex) encoding0->glyphIdArray[ucs4];
}

/**
 * Try to find Glyph in an encoding format 2 table 
 * @param encoding4 is the encoding4 tables 
 * @param ucs2 is the character to find.
 * @return the glyph index.
 */
static SS_GlyphIndex
findGlyph2 (TTF_CMAP_FMT2* encoding2, SS_UCS4 ucs4)
{
  if (ucs4 > 0xffff) return 0;
  unsigned int first = (ucs4>>8) &0xff;
  unsigned int second = ucs4 & 0xff;
  SD_USHORT n = 0;
  if (first == 0)
  {
    SD_USHORT k = ntohs (encoding2->subHeaderKeys[second]) / 8;
    if (k!=0) return 0;
    TTF_CMAP_FMT2_SUBHEADER * sh1 = &encoding2->subHeaders[0];
    SD_USHORT firstCode = ntohs (sh1->firstCode);
    SD_USHORT entryCount = ntohs (sh1->entryCount);
    SD_USHORT ro = ntohs (sh1->idRangeOffset);
    if (firstCode!=0 || entryCount != 256 || ro == 0) return 0;
    unsigned int ind = (ro/2) + (second - firstCode);
    n = (SD_USHORT) ntohs(*(&sh1->idRangeOffset + ind));
    if (n==0) return n;
    SD_SHORT delta = (SD_SHORT)ntohs (sh1->idDelta);
    /* negative possible */
    n += delta;
    n = n % 0xffff;
    return n;
  }
  SD_USHORT k = ntohs (encoding2->subHeaderKeys[first]) / 8;
  /* 1 byte - we can not deal with this */
  if (k==0) return 0;
  TTF_CMAP_FMT2_SUBHEADER * sh = &encoding2->subHeaders[k];
  SD_USHORT firstCode = ntohs (sh->firstCode);
  SD_USHORT entryCount = ntohs (sh->entryCount);

  if (second < (unsigned int) firstCode  || second >= 
         ((unsigned int)firstCode + (unsigned int)entryCount)) return 0;

  SD_USHORT ro = ntohs (sh->idRangeOffset);
  /* If the idRangeOffset value for the segment is not 0, 
   * the mapping of the character codes relies on
   * the glyphIndexArray.
   */
  if (ro==0) return 0;
  /* 
   * The value of the idRangeOffset is the number of bytes past 
   * the actual location of the idRangeOffset word where the 
   * glyphIndexArray element corresponding to firstCode
   * appears 
   */
  unsigned int ind = (ro/2) + (second - firstCode);
  n = (SD_USHORT) ntohs(*(&sh->idRangeOffset + ind));
  if (n==0) return 0;
  /* If the idRangeOffset is 0, the idDelta value is added 
   * directly to the character code to get the corresponding 
   * glyph index
   */
  SD_SHORT delta = (SD_SHORT)ntohs (sh->idDelta);
  /* negative possible */
  n += delta;
  n = n % 0xffff;
  return (SS_GlyphIndex) n;
}

/**
 * Try to find Glyph in an encoding format 4 table 
 * @param encoding4 is the encoding4 tables 
 * @param ucs2 is the character to find.
 * @return the glyph index.
 */
static SS_GlyphIndex
findGlyph4 (TTF_CMAP_FMT4* encoding4, SS_UCS4 ucs4)
{
  if (ucs4 > 0xffff) return 0;
  /* Finally we found it. Maybe */
  int seg_c2 = ntohs(encoding4->segCountX2);
  SD_SHORT cmap_n_segs = seg_c2 >> 1;
  SD_BYTE* ptr = (SD_BYTE *)encoding4 + 14;
  SD_USHORT* cmap_seg_end = (SD_USHORT *) ptr;
  /* here comes a pad, then: */
  SD_USHORT* cmap_seg_start = (SD_USHORT *) (ptr + seg_c2 + 2);
  SD_SHORT* cmap_idDelta = (SD_SHORT *) (ptr + (seg_c2 * 2 )+ 2);
  SD_SHORT* cmap_idRangeOffset = (SD_SHORT *) (ptr + (seg_c2 * 3) + 2);
  //SD_USHORT* glyphIndexArray = (SD_USHORT *) (ptr + (seg_c2 * 4) + 2);

  /* No choice. Go through the segments */
  for (int j=0; j < cmap_n_segs; j++)
  {
    SD_USHORT start = ntohs(cmap_seg_start[j]);
    SD_USHORT end   = ntohs(cmap_seg_end[j]);
    SD_USHORT ro    = ntohs(cmap_idRangeOffset[j]);
    if (start == 0xffff) return 0;

    if (ucs4> end || ucs4 < start) continue;
    /* If the idRangeOffset value for the segment is not 0, 
     * the mapping of the character codes relies on
     * the glyphIndexArray.
     */
    SD_USHORT n = 0;
    SD_SHORT delta = ntohs(cmap_idDelta[j]);
    /* 
     * Should be ro only - but it dumps on code2000.ttf 
     * with U+5C81 if I don't check for delta too 
     */
    if (ro!=0 && delta==0)
    {
      //n =  ntohs (glyphIndexArray[ro/2 + (ucs4 - start) + ro]);
      unsigned int ind = (ro/2) + (ucs4 - start);
      n = (SD_USHORT) ntohs(*(&cmap_idRangeOffset [j] + ind));

    }
    /* If the idRangeOffset is 0, the idDelta value is added 
     * directly to the character code to get the corresponding 
     * glyph index
     */
    else if (delta!=0) /* should not really check for != 0 - I am paranoid */
    {
      /* negative possible */
      n = ucs4 + delta;
      n = n % 0xffff;
    }
    return (SS_GlyphIndex) n;
  }
  return 0;
}
/**
 * Try to find Glyph in an encoding format 4 table 
 * @param encoding4 is the encoding4 tables 
 * @param ucs2 is the character to find.
 * @return the glyph index.
 */
static SS_GlyphIndex
findGlyph12 (TTF_CMAP_FMT12* encoding12, SS_UCS4 ucs4)
{
  unsigned int count = ntohl (encoding12->nGroups);
  for (unsigned int i=0; i<count; i++)
  {
    unsigned int start = ntohl (encoding12->entry[i].startCharCode);
    unsigned int end = ntohl (encoding12->entry[i].endCharCode);
    if (ucs4 >= start && ucs4 <= end)
    {
       unsigned int gl = ntohl (encoding12->entry[i].startGlyphCode);
       gl += (ucs4 - start);
       if (gl > 0xffff)
       {
         return 0;
       }
       return (SS_GlyphIndex) gl;
    }
  }
  return 0;
}

/**
 * Draw a single glyph.
 * @pama canvas is the canvas to draw to
 * @param pen is the pen to draw with
 * @param m is the transformation matrix.
 * @param glyphindex is the local glyph index
 * @return nothing 
 */
void
SFontTTF::drawGlyph (SCanvas* canvas, const SS_Matrix2D& matrix, 
  SS_GlyphIndex glyphno)
{
  if (!isOK()) return ;
  if (glyphno == SD_G_INDIC_ZWJ) return;
  if (glyphno == SD_G_INDIC_ZWNJ) return;

  SD_BYTE* gstart = (SD_BYTE *) tables[SS_TB_GLYF];

  TTF_GLYF* gtable;
  int len =0;
  if (longOffsets)
  {
     SD_ULONG* lloca = (SD_ULONG *) tables[SS_TB_LOCA];
     unsigned int offs1 = ntohl (lloca[glyphno]);
     unsigned int offs2 = ntohl (lloca[glyphno+1]);
     gtable = (TTF_GLYF *) ((char*)gstart + offs1);
     len = offs2-offs1;
  }
  else
  {
     SD_USHORT* sloca = (SD_USHORT *) tables[SS_TB_LOCA];
     gtable = (TTF_GLYF *) (gstart + (ntohs (sloca[glyphno]) << 1));
     len = (ntohs (sloca[glyphno+1]) - ntohs (sloca[glyphno])) << 1;
  }
  if (len <= 0)
  {
    if (!broken && len < 0)
    {
      fprintf (stderr, "SFontTTF-2: non-existent glyph %u in %*.*s %d\n",
          (unsigned int) glyphno,
          SSARGS (name), (int) len) ;
      broken = true;
    }
    return;
  }
  TTF_GLYF* kludge = 0;
  if ((((unsigned long) gtable) & 1) != 0)
  {
    if (!broken)
    {
      fprintf  (stderr, "SFontTTF: fixing unaligned %*.*s.\n", SSARGS(name));
      broken = true;
    }
    kludge = new TTF_GLYF[len]; 
    CHECK_NEW (kludge);
    memcpy (kludge, gtable, len * sizeof (TTF_GLYF));
    gtable = kludge;
  }

  int ncontours = (int) ((short)ntohs (gtable->numberOfContours));
  if (ncontours <= 0)
  {
    SD_BYTE *ptr = ((SD_BYTE *) gtable + sizeof(TTF_GLYF));
    SD_SHORT *sptr = (SD_SHORT *) ptr;
    SD_USHORT flagbyte;
    do
    {
      SS_Matrix2D m;
      flagbyte = ntohs(*sptr); sptr ++;
      SS_GlyphIndex glyphindex = ntohs(*sptr); sptr ++;

      if (flagbyte & ARG_1_AND_2_ARE_WORDS)
      {
         /* we need to make it short as it can be negative */
         m.t0 = (double) ((SD_SHORT)ntohs(*sptr)); sptr++;
         m.t1 = (double) ((SD_SHORT)ntohs(*sptr)); sptr++;
      }
      else
      {
         char* bptr = (char *)sptr;
         m.t0 = (signed char)bptr[0];
         m.t1 = (signed char)bptr[1];
         sptr ++;
      }
      if (flagbyte & WE_HAVE_A_SCALE)
      {
         m.x0 = m.y1 = f2dot14(*sptr);
         sptr ++;
      }
      else if (flagbyte & WE_HAVE_AN_X_AND_Y_SCALE)
      {
        m.x0 = f2dot14(*sptr); sptr ++;
        m.y1 = f2dot14(*sptr); sptr ++;
      }
      else if (flagbyte & WE_HAVE_A_TWO_BY_TWO)
      {
        m.x0 = f2dot14(*sptr); sptr ++;
        m.y0 = f2dot14(*sptr); sptr ++;
        m.x1 = f2dot14(*sptr); sptr ++;
        m.y1 = f2dot14(*sptr); sptr ++;
      }
      SS_Matrix2D mm = matrix * m;
      /* recursively call itself */
      drawGlyph (canvas, mm, glyphindex);
    } while (flagbyte & MORE_COMPONENTS);
    if (kludge) delete kludge;
    return;
  }

  SD_USHORT* contour_end_pt = (SD_USHORT *) ((char *)gtable + sizeof(TTF_GLYF));

  int last_point = (int) ntohs (contour_end_pt[ncontours-1]);

  int n_inst = (int) ntohs (contour_end_pt[ncontours]);

  SD_BYTE* ptr = ((SD_BYTE *)contour_end_pt) + (ncontours << 1) + n_inst + 2;

  int j = 0; int k = 0;

  SBinVector<SD_BYTE> flags;
  while (k <= last_point)
  {
    flags.append (ptr[j]);
    if (ptr[j] & REPEAT)
    {
       for (int k1=0; k1 < ptr[j+1]; k1++)
       {
            k++;
            flags.append (ptr[j]);
       }
       j++;
    }
    j++; k++;
  }
  
  SBinVector<SD_SHORT> xrel;
  SBinVector<SD_SHORT> xcoord;

  for (k=0; k <= last_point; k++)
  {
    /* Process xrel */
    if (flags[k] & XSD_SHORT)
    {
      if (flags[k] & XSAME)
      {
        xrel.append (ptr[j]);
      }
      else
      {
        xrel.append (-ptr[j]);
      }
      j++;
    }
    else if (flags[k] & XSAME)
    {
      xrel.append (0);
    }
    else
    {
      xrel.append (ptr[j] * 256 + ptr[j+1]);
      j += 2;
    }
    /* Process x coordinate */
    if (k==0)
    {
      xcoord.append (xrel[k]);
    }
    else
    {
      xcoord.append (xrel[k] + xcoord[k-1]);
    }
  }

  SBinVector<SD_SHORT> yrel;
  SBinVector<SD_SHORT> ycoord;

  /* one more run fore yrel and ycoord */
  for (k=0; k <= last_point; k++)
  {
    if (flags[k] & YSD_SHORT)
    {
      if (flags[k] & YSAME)
      {
        yrel.append (ptr[j]);
      }
      else
      {
         yrel.append (- ptr[j]);
      }
      j++;
    }
    else if (flags[k] & YSAME)
    {
      yrel.append (0);
    }
    else
    {
      yrel.append (ptr[j] * 256 + ptr[j+1]);
      j += 2;
    }
    if (k==0)
    {
       ycoord.append (yrel[k]);
    }
    else
    {
       ycoord.append (yrel[k] + ycoord[k-1]);
    }
  }

  bool first = true;

  j = 0;
  int i = 0;
  int contour_start = 0;
  while (i <= ntohs(contour_end_pt[ncontours-1]))
  {
    int contour_end = ntohs(contour_end_pt[j]);

    if (first)
    {
      moveto (canvas, matrix, xcoord[i], ycoord[i]);
      contour_start = i;
      first = false;
    }
    else if (flags[i] & ONOROFF)
    {
      lineto (canvas, matrix, xcoord[i], ycoord[i]);
    }
    else /* curvy */
    {
      int cs = i-1;
      int ce = 0;
      bool finished = false;
      int nguide = 0;
      while (!finished)
      {
        if (i == contour_end+1)
        {
          ce = contour_start;
          finished = true;
        }
        else if (flags[i] & ONOROFF)
        {
          ce = i;
          finished = true;
        }
        else
        {
           i++;
           nguide++;
        }
      }

      switch (nguide)
      {
      case 0:
        lineto (canvas, matrix, xcoord[ce], ycoord[ce]);
        break;

      case 1:
        cureveto (canvas, matrix, 
           (xcoord[cs]+2*xcoord[cs+1])/3, (ycoord[cs]+2*ycoord[cs+1])/3,
           (2*xcoord[cs+1]+xcoord[ce])/3, (2*ycoord[cs+1]+ycoord[ce])/3,
            xcoord[ce], ycoord[ce]);
        break;

      case 2: 
        cureveto (canvas, matrix, 
            (-xcoord[cs]+4*xcoord[cs+1])/3, (-ycoord[cs]+4*ycoord[cs+1])/3,
            (4*xcoord[cs+2]-xcoord[ce])/3, (4*ycoord[cs+2]-ycoord[ce])/3,
            xcoord[ce], ycoord[ce]);
        break;

      case 3: 
        cureveto (canvas, matrix, 
            (xcoord[cs]+2*xcoord[cs+1])/3, (ycoord[cs]+2*ycoord[cs+1])/3,
            (5*xcoord[cs+1]+xcoord[cs+2])/6, (5*ycoord[cs+1]+ycoord[cs+2])/6,
            (xcoord[cs+1]+xcoord[cs+2])/2, (ycoord[cs+1]+ycoord[cs+2])/2);
        cureveto (canvas, matrix,
            (xcoord[cs+1]+5*xcoord[cs+2])/6, (ycoord[cs+1]+5*ycoord[cs+2])/6,
            (5*xcoord[cs+2]+xcoord[cs+3])/6, (5*ycoord[cs+2]+ycoord[cs+3])/6,
            (xcoord[cs+3]+xcoord[cs+2])/2, (ycoord[cs+3]+ycoord[cs+2])/2);
        cureveto (canvas, matrix,
            (xcoord[cs+2]+5*xcoord[cs+3])/6, (ycoord[cs+2]+5*ycoord[cs+3])/6,
            (2*xcoord[cs+3]+xcoord[ce])/3, (2*ycoord[cs+3]+ycoord[ce])/3,
            xcoord[ce], ycoord[ce]);
        break;

      default:
        int k1 = cs + nguide;
        cureveto (canvas, matrix, 
            (xcoord[cs]+2*xcoord[cs+1])/3, (ycoord[cs]+2*ycoord[cs+1])/3,
            (5*xcoord[cs+1]+xcoord[cs+2])/6, (5*ycoord[cs+1]+ycoord[cs+2])/6,
            (xcoord[cs+1]+xcoord[cs+2])/2, (ycoord[cs+1]+ycoord[cs+2])/2);
        for (k = cs+2; k <= k1-1; k++)
        {
          cureveto (canvas, matrix, 
            (xcoord[k-1]+5*xcoord[k])/6, (ycoord[k-1]+5*ycoord[k])/6,
            (5*xcoord[k]+xcoord[k+1])/6, (5*ycoord[k]+ycoord[k+1])/6,
            (xcoord[k]+xcoord[k+1])/2, (ycoord[k]+ycoord[k+1])/2);
        }
        cureveto (canvas, matrix, 
            (xcoord[k1-1]+5*xcoord[k1])/6, (ycoord[k1-1]+5*ycoord[k1])/6,
            (2*xcoord[k1]+xcoord[ce])/3, (2*ycoord[k1]+ycoord[ce])/3,
            xcoord[ce], ycoord[ce]);
        break;
      } /* end switch (nguide) */
    }
    if (i >= contour_end)
    {
      first = true;
      i = contour_end + 1;
      canvas->closepath ();
      j++;
    }
    else
    {
      i++;
    }
  }
  if (kludge!=0) delete kludge;
  return;
}

/**
 * The moveto
 */
static void
moveto (SCanvas* canvas, const SS_Matrix2D &m,
  SD_SHORT _x, SD_SHORT _y)
{
  double x = m.x0 * double (_x) + m.y0 * double (_y) + m.t0;
  double y = m.x1 * double (_x) + m.y1 * double (_y) + m.t1;
  canvas->moveto (x, y);
}
/**
 * The lineto
 */
static void
lineto (SCanvas* canvas, const SS_Matrix2D &m,
  SD_SHORT _x, SD_SHORT _y)
{
  double x = m.x0 * double (_x) + m.y0 * double (_y) + m.t0;
  double y = m.x1 * double (_x) + m.y1 * double (_y) + m.t1;
  canvas->lineto (x, y);
}

/**
 * The curveto
 */
static void
cureveto (SCanvas* canvas, const SS_Matrix2D &m,
  SD_SHORT _x0, SD_SHORT _y0,
  SD_SHORT _x1, SD_SHORT _y1,
  SD_SHORT _x2, SD_SHORT _y2)
{
  double x0 = m.x0 * double (_x0) + m.y0 * double (_y0) + m.t0;
  double y0 = m.x1 * double (_x0) + m.y1 * double (_y0) + m.t1;

  double x1 = m.x0 * double (_x1) + m.y0 * double (_y1) + m.t0;
  double y1 = m.x1 * double (_x1) + m.y1 * double (_y1) + m.t1;

  double x2 = m.x0 * double (_x2) + m.y0 * double (_y2) + m.t0;
  double y2 = m.x1 * double (_x2) + m.y1 * double (_y2) + m.t1;
  canvas->curveto (x0, y0, x1, y1, x2, y2);
}

/**
 * Create double numbers between -2 .. 1.99994 
 * from a packed short.
 */
static double
f2dot14 (short x)
{
  short y = ntohs(x);
  return (y >> 14) + ((y & 0x3fff) / 16384.0);
}

static void
debugChars (const char* msg, const SS_GlyphIndex* gchars, unsigned int len)
{
#ifdef DEBUG_LIGATURE
  fprintf (stderr, "SFontTTF.cpp: %s", msg);
  for (unsigned int i=0; i<len; i++)
  {
    fprintf (stderr, " %04X", gchars[i]);
  }
  fprintf (stderr, "\n");
#endif
}
