/** 
 *  Yudit Unicode Editor Source File
 *
 *  GNU Copyright (C) 1997-2023  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.
 */
 
#include "swindow/sx11/SX11Font.h"
#include "stoolkit/SBinHashtable.h"
#include "stoolkit/SExcept.h"
#include "stoolkit/SUtil.h"
#include "stoolkit/SCluster.h"
#include <X11/Xatom.h>

#include <stdio.h>

#define SD_ZW_FOTNTHACK 0.0001

static SStringVector mapNameVector;
/**
 * Maps SCluster.h SD_DEVANAGARI - like codes to pango codes.
 */
static SStringVector scriptCodeMap("*-iso10646-yudit,*-iso10646-dev,*-iso10646-bng,*-iso10646-gur,*-iso10646-guj,*-iso10646-ori,*-iso10646-tam,*-iso10646-tel,*-iso10646-kan,*-iso10646-mal,*-iso10646-sin");


/**
 * forcefully sets coverage even if glyph is zero size
 */

static bool inited= false;
static const char* defaultNames[] =
{
"*-iso10646-1", "ucs2",
"*-iso10646-dev", "ucs2",
"*-iso10646-tam", "ucs2",
"*-iso10646-bgn", "ucs2",
"*-iso10646-bng", "ucs2",
"*-iso10646-bur", "ucs2",
"*-iso10646-gur", "ucs2",
"*-unicode", "ucs2",
"*-iso8859-1", "iso-8859-1",
"*-iso8859-2", "iso-8859-2",
"*-iso8859-3", "iso-8859-3",
"*-iso8859-4", "iso-8859-4",
"*-iso8859-5", "iso-8859-5",
"*-iso8859-6", "iso-8859-6",
"*-iso8859-7", "iso-8859-7",
"*-iso8859-8", "iso-8859-8",
"*-iso8859-9", "iso-8859-9",
"*-iso8859-11", "iso-8859-11",
"*-iso8859-12", "iso-8859-12",
"*-iso8859-13", "iso-8859-13",
"*-iso8859-14", "iso-8859-14",
"*-iso8859-15", "iso-8859-15",
"*-iso8859-16", "iso-8859-16",
"*-koi8-r", "koi8-r",
"*-koi8-c", "koi8-c",
"*-koi8-u", "koi8-u",
"*-koi8-1", "koi8-r",

"*-ksc5601.1987-0", "ksc-5601-l",
"*-ksc5601*", "ksc-5601-l",

"*-ksx1001*", "ksx-1001",

"*-jisx0208.1983-0", "jis-0208",
"*-jisx0208*", "jis-0208",

"*-jisx0208.1990-0", "jis-0208",
"*-jisx0208*", "jis-0208",

"*-jisx0201.1976-0", "jis-0201",
"*-jisx0201*", "jis-0201",

"*-jisx0212.1990-0", "jis-0212",
"*-jisx0212*-0", "jis-0212",

"*-jisx0213*-1", "jis-0213-1",
"*-jisx0213*-2", "jis-0213-2",

"*-big5.hku-0", "big-5",
"*-big5-0", "big-5",            
"*-big5*", "big-5",            

"*-gb2312.1980-0", "gb-2312-l", /* There are some font encoded with gb-2312-r"*/
"*-gb2312*", "gb-2312-l", /* There are some font encoded with gb-2312-r"*/

"*-iso646*", "iso-646", /* 646 is not 10646 */
};

/**
 * @author: Gaspar Sinai <gaspar@yudit.org>
 * @version: 2000-06-03
 * This is a specific native font, with a certain size
 */
class SX11FontCache
{
public:
  SX11FontCache (SX11Impl *impl,const SString& xlfd);
  ~SX11FontCache ();
  SString xlfd;
  SX11Impl* impl;
  SUniMap map;
  double fontAscent;
  double fontDescent;
  double fontWidth;
  XFontStruct* font;
  bool loaded;
  bool load ();
  unsigned int getRawPangoLigatures (unsigned int script, SS_UCS4* ino, 
       unsigned int len);
  unsigned int getPangoLigatures (unsigned int script, SS_UCS4* ino, 
       unsigned int len);

  double draw (SCanvas* canvas, const SPen& pen, double x, double y, 
       SS_UCS2 glyphno);
  double width (SS_UCS2 glyphno);

  SProperties ligatures;
  unsigned int ligatureCount;
  int scriptCode;
  /* Still no agreement on this - I am using : */ 
  /* http://mail.nl.linux.org/linux-utf8/2000-12/msg00042.html */
  unsigned int plane;
};


SX11FontCache::SX11FontCache(SX11Impl *_impl,  const SString& _xlfd) 
     : xlfd (_xlfd)
{
  impl = _impl;
  fontAscent = 0.0;
  fontDescent = 0.0;
  fontWidth = 0.0;
  loaded = false;
  plane = 0;
}

/**
 * Create an XLFD and try to load this as a font.
 * TODO: it should modify xlfd to show the real xlfd
 */
bool
SX11FontCache::load ()
{
  if (loaded) return (font!=0);
  scriptCode = -1;
  plane = 0;
  loaded = true;
  char* fontName = xlfd.cString();
  font = XLoadQueryFont (impl->display, fontName);
  delete fontName;
  if (font == 0)  return false;
  /* Fix broken fonts */
  Atom fontatom = XInternAtom (impl->display, "FONT", False);
  Atom pangohack = XInternAtom (impl->display, "PANGO_LIGATURE_HACK", False);
  char* vle = 0;
  char* pangohackname = 0;
  XFontProp *fp = font->properties;
  for (int i = 0; i < font->n_properties; i++, fp++)
  {
    if (fp->name == fontatom)
    {
      vle = XGetAtomName (impl->display, fp->card32);
    }
    else if (fp->name == pangohack)
    {
      pangohackname = XGetAtomName (impl->display, fp->card32);
    }
  }
  if (vle)
  {
    SString fn(vle);
    XFree (vle);
    fn.lower();
    xlfd = fn;
  }

  int tryAscent[2];
  tryAscent[0] = font->ascent;
  tryAscent[1] = font->max_bounds.ascent;
  if (tryAscent[1] < tryAscent[0]
     || (tryAscent[0] +2) * 2 < tryAscent[1])
  {
    tryAscent[1] = tryAscent[0];
  }
  int tryDescent[2];
  tryDescent[0] = font->descent;
  tryDescent[1] = font->max_bounds.descent;
  if (tryDescent[1] < tryDescent[0]
     || (tryDescent[0] +2) * 2 < tryDescent[1])
  {
    tryDescent[1] = tryDescent[0];
  }
  fontAscent = (double) tryAscent[1];
  fontDescent = (double)tryDescent[1];
  fontWidth = (double) font->max_bounds.width;
  if (!inited)
  {
    for (unsigned int i=0; i<sizeof (defaultNames) / sizeof (char*)/2; i++)
    {
      mapNameVector.append (defaultNames[2*i]);
      mapNameVector.append (defaultNames[2*i+1]);
    }
    inited = true;
  }
  /* Find out what map is used for this font */
  SString m;
  unsigned int i;
  for (i=0; i<scriptCodeMap.size(); i++)
  {
     if (xlfd.match (scriptCodeMap[i]))
     {
        scriptCode = (int)i;
     }
  }
  for (i=0; i+1<mapNameVector.size(); i=i+2)
  {
    if (xlfd.match (mapNameVector[i]))
    {
      m = mapNameVector[i+1];
      break;
    }
  }
  for (i=1; i<17; i++)
  {
    /* http://mail.nl.linux.org/linux-utf8/2000-12/msg00024.html */
    SString s("*-iso10646p2-");
    s.print (i);
    if (xlfd.match (s))
    {
      plane = i;
      if ((m.size() ==0)) m=SString("ucs2");
      break;
    }
/* Hmm.
    SString s2("*-iso10646.2-");
    s2.print (i);
    if (xlfd.match (s2))
    {
      plane = i;
      if ((m.size() ==0)) m=SString("ucs2");
      break;
    }
*/
  }
  if (m.size() ==0)
  {
    fprintf (stderr, "swindow/sx11/SX11Font need fontmap for %*.*s\n",
        SSARGS(xlfd));
  }
  if (m.size() !=0 && m != SString("ucs2"))
  {
    SUniMap um(m);
    if (!um.isOK())
    {
     fprintf (stderr, "SX11Font .my map '%*.*s' needed for for %*.*s\n",
        SSARGS(m), SSARGS(xlfd));
    }
    else
    {
      map = um;
    }
  }
  ligatureCount = 0;
  /* We just parse the simplest version of PANGO_LIGATURE_HACK */
  if (pangohackname)
  {
    SStringVector hacks(pangohackname, " ");
    SBinVector<Atom> hackatoms;
    unsigned int i;
    for (i=0; i<hacks.size(); i++)
    {
       SString hi = hacks[i];
       SStringVector vi (hi, ":");
       if (vi.size()==0) continue; /* never happens */
       /* language grossly ignored */
       SString hname = vi[vi.size()-1];
       hname.append ((char)0);
       Atom atom = XInternAtom (impl->display, hname.array(), False);
       hackatoms.append (atom);
    }
    XFontProp *fp = font->properties;
    for (int j = 0; j < font->n_properties; j++, fp++)
    {
      for (i=0; i<hackatoms.size(); i++)
      {
        if (fp->name != hackatoms[i]) continue;
        char* hvle = XGetAtomName (impl->display, fp->card32);
        SStringVector hvect (hvle, " ");
        for (unsigned int k=0; k<hvect.size(); k++)
        {
          SString ligkvle=hvect[k];
          SStringVector vvle (ligkvle, "+=");
          if (vvle.size()==1) continue; /* never happens */
          SV_UCS4 v;
          bool bad = false;
          for (unsigned int m=0; m<vvle.size(); m++)
          {
            unsigned int l;
            SString s = vvle[m];
            s.append ((char)0);
            char dummy[2];
            if (sscanf (s.array(), "%x%1s", &l, dummy)!=1)
            {
              //fprintf (stderr, "BAD X11 PANGO_HACK='%*.*s' at '%s'\n", 
              //  SSARGS(ligkvle), s);
              bad = true;
              break;
            }
            v.append (l);
          }
          if (!bad)
          {
            SS_UCS4 glyphno = v.array()[v.size()-1];
            /* scriptCode should be initialized at this point */
            double w = width ((SS_UCS2) glyphno);
            if (glyphno > 0xffff || w == 0.0)
            {
              static bool warned = false;
              if (!warned) fprintf (stderr, 
                "SX11Font: Deleting non-existent ligatures from %*.*s\n", 
                SSARGS(xlfd));
              warned = true;
            }
            else
            {
              SString key ((char*)v.array(), sizeof (SS_UCS4) * (v.size()-1));
              SString vle ((char*)&v.array()[v.size()-1], sizeof (SS_UCS4));
              ligatureCount++;
              ligatures.put (key, vle);
            }
          }
        }
      }
    }
  }
  if (ligatureCount != 0)
  {
#if 0
    fprintf (stderr, "loaded %u ligatures from %*.*s.\n", 
        ligatureCount, SSARGS(xlfd));
#endif
  }
  return true;
}

/**
 * Draw a single glyph.
 * @return the width
 */
double
SX11FontCache::draw (SCanvas* canvas, const SPen& pen, double x, double y, 
       SS_UCS2 glyphno)
{
  /* ZWJ and ZWNJ */
  /* ZWJ and ZWNJ */
  if (plane == 0 && scriptCode != -1 
        && (glyphno == 0x200d 
         || glyphno == 0x200c))
  {
      return SD_ZW_FOTNTHACK;
  }
  bool covered = isCoveredScipt ((SS_UCS4) glyphno, scriptCode);
  /* pango subscript and superscirpt ra - zero width */
  if (plane == 0 && !covered && scriptCode != -1 && (glyphno == 0xc97e || glyphno == 0xc97f))
  {
    covered = true;
  }

  XChar2b     char2B;
  char2B.byte1 = glyphno >> 8;
  char2B.byte2 = glyphno & 0xff;
  double fwidth;

  if (font->per_char==0)
  {
    int ddirection;
    int dascent;
    int ddescent;
    XCharStruct overall;
    if (!XQueryTextExtents16 (impl->display,
       font->fid, &char2B, 1, &ddirection, &dascent, &ddescent, &overall))
    {
      return 0.0;
    }
    if (overall.width==0 && !covered) return 0.0;
    fwidth = (double) overall.width;
    if (fwidth==0.0) fwidth = SD_ZW_FOTNTHACK;
  }
  else
  {
    if (char2B.byte1 < font->min_byte1 
      || char2B.byte2 < font->min_char_or_byte2
      || char2B.byte1 > font->max_byte1 
      || char2B.byte2 > font->max_char_or_byte2)
    {
       return 0.0;
    }
    int cellIndex = (char2B.byte1-font->min_byte1)
     * (font->max_char_or_byte2 -font->min_char_or_byte2+1)
     + char2B.byte2-font->min_char_or_byte2;
    fwidth = (double) font->per_char[cellIndex].width;
    if (fwidth == 0.0 && !covered) return 0.0;
    if (fwidth==0.0) fwidth = SD_ZW_FOTNTHACK;
  }
  canvas->bitfont (pen, x, y, (void*) font->fid, (char*) &char2B, 2);
  return fwidth; 
}

/**
 * get width of a single glyph.
 * @return the width
 */
double
SX11FontCache::width (SS_UCS2 glyphno)
{
  /* ZWJ and ZWNJ */
  if (plane == 0 && scriptCode != -1 && (glyphno == 0x200d || glyphno == 0x200c))
  {
      return SD_ZW_FOTNTHACK;
  }
  /* pango subscript and superscirpt ra - zero width */
  bool covered = isCoveredScipt ((SS_UCS4) glyphno, scriptCode);
  if (plane == 0 && !covered && scriptCode != -1 && (glyphno == 0xc97e || glyphno == 0xc97f))
  {
    covered = true;
  }
  XChar2b     char2B;
  char2B.byte1 = glyphno >> 8;
  char2B.byte2 = glyphno & 0xff;
  if (font->per_char==0)
  {
    int ddirection;
    int ddescent;
    int dascent;
    XCharStruct overall;
    if (!XQueryTextExtents16 (impl->display,
       font->fid, &char2B, 1, &ddirection, &dascent, &ddescent, &overall))
    {
      return 0.0;
    }
    if (!covered) return (double)overall.width;
    if (overall.width == 0) return SD_ZW_FOTNTHACK;
    return (double) overall.width;
  }
  if (char2B.byte1 < font->min_byte1 || char2B.byte2 < font->min_char_or_byte2
      || char2B.byte1 > font->max_byte1 || char2B.byte2 > font->max_char_or_byte2)
  {
     return 0.0;
  }
  int cellIndex = (char2B.byte1-font->min_byte1)
     * (font->max_char_or_byte2 -font->min_char_or_byte2+1)
     + char2B.byte2-font->min_char_or_byte2;
  double ret =  (double) font->per_char[cellIndex].width;
  if (!covered) return ret;
  if (ret == 0.0) return SD_ZW_FOTNTHACK;
  return ret;
}

SX11FontCache::~SX11FontCache ()
{
  if (font) XFreeFont (impl->display, (XFontStruct*) font);
} 

/**
 * TODO: this is never freed up. It simply grows...
 */
static SBinHashtable<SX11FontCache*> fontCache;

static SX11FontCache* loadFont (SX11Impl* impl, const SString& xlfd);

/**
 * Create a new empty font 
 * if _encoding exists use it as the decoder for the font.
 */
SX11Font::SX11Font (SX11Impl* _impl, const SString& _encoding) 
  : encoder (_encoding), impl(_impl)
{
  isencoded = (_encoding.size()!=0);
  if (isencoded && !encoder.isOK())
  {
    if (_encoding != "unicode"
     && _encoding != "mslvt"
     && _encoding != "nojamo"
     && _encoding != "indic"
     && _encoding != "deva"
     && _encoding != "beng"
     && _encoding != "guru"
     && _encoding != "gujr"
     && _encoding != "orya"
     && _encoding != "taml"
     && _encoding != "telu"
     && _encoding != "knda"
     && _encoding != "mlym"
     && _encoding != "sinh"
     && _encoding != "thai"
     && _encoding != "lao"
     && _encoding != "tibt"
     && _encoding != "emoji"
    )
    {
      fprintf (stderr, "SX11Font: unknown encoding : '%*.*s'\n",
         SSARGS (_encoding));
    }
    isencoded = false;
  }
}

SX11Font::~SX11Font ()
{
} 

/**
 * Draw one glyph. 
 */
bool
SX11Font::draw (const SString& xlfd, SCanvas* canvas, const SPen& pen, 
    const SS_Matrix2D& matrix, SS_UCS4 g)
{
  SX11FontCache* fc = loadFont(impl, xlfd);
  if (!fc->load()) return false;
  /**
   * create a glyphno array 
   */
  SS_UCS2 glyphno = 0;
  unsigned int liglen =0;
  if (isLigature (g) && fc->ligatureCount != 0 
       && (liglen=getLigatureUnicode (g, 0)) > 0)
  {
     /* we allocate one more to allow for LEFT_RIGHT vowel expansion */
     SS_UCS4* chars = new SS_UCS4[liglen + 1];
     CHECK_NEW (chars);
     getLigatureUnicode (g, chars);
     unsigned int script = (g & 0x7fff0000) >> 16;
     unsigned int collapsed = fc->getPangoLigatures (script, chars, liglen);
     if (collapsed==0 || collapsed == liglen)
     {
       delete chars;
       return false;
     }
     if (collapsed == 1)
     {
       glyphno = chars[0];
     }
     else
     {
       double x = matrix.t0;
       double y = matrix.t1;
       /* draw the whole ligature */
       for (unsigned int i=0; i<collapsed; i++)
       {
         double dw = 0.0;
         if (chars[i] > 0xffff 
             || ((dw = fc->draw (canvas, pen, 
                        x, y, (SS_UCS2) chars[i])) == 0.0 && i==0))
        {
          delete chars;
          return false;
        }
        x += dw;
       }
       delete chars;
       return true;
     }
     delete chars;
  }
  else if (isencoded)
  {
    /* to make things faster */
    if (encoder.isUMap())
    {
      glyphno = encoder.encode (g);
    }
    else
    {
      SV_UCS4 ucs4; ucs4.append (g); SV_UCS4 decd;
      unsigned int lifted = encoder.lift (ucs4, 0, false, &decd);
      /* like TTF we fallback to original font encoding  */
      if (lifted == 0)
      {
         glyphno = fc->map.encode (g);
      } 
      else
      {
        double x = matrix.t0;
        double y = matrix.t1;
        /* draw the whole ligature */
        for (unsigned int i=0; i<decd.size(); i++)
        {
          if (decd[i] > 0xffff) return 0;
          double dw = fc->draw (canvas, pen, x, y, (SS_UCS2) decd[i]);
          if (dw == 0.0 && i==0)
          {
            return false;
          }
          x += dw;
        }
        return true;
      }
    }
  }
  else
  {
    if (fc->plane == 0)
    {
      /* faster */
      if (fc->map.isUMap()) {
        glyphno = fc->map.encode (g);
      } else {
        SV_UCS4 ucs4; ucs4.append (g); SV_UCS4 decd;
        unsigned int lifted = fc->map.lift (ucs4, 0, false, &decd);
        if (lifted==1 && decd.size()==1) glyphno = decd[0];
      }
    }
    else
    {
       SS_UCS4  start = fc->plane*0x10000;;
       glyphno =  (g < start) ? 0 : fc->map.encode (g-start);
    }
  }
  if (glyphno==0) return false;
  double dw = fc->draw (canvas, pen, matrix.t0, matrix.t1, glyphno);
  return dw;
}

/**
 * Get pango ligatures.
 * @param ino is the input-output buffer
 * @param len is the input length
 * @return output length
 */
unsigned int
SX11FontCache::getRawPangoLigatures (unsigned int script, 
   SS_UCS4* ino, unsigned int len)
{
  for (unsigned int i=0; i<len; i++)
  {
    if (ino[i] > 0xffff) return len;
  }
  if (len == 1) return 1;
  SString l;
  /* collect all ligatures in one loop, starting from big ones. */
  unsigned int curin=0;
  unsigned int curout=0;
  unsigned int i;
  while (curin < len)
  {
    bool gotone = false;
    for (i=len; i>curin; i--)
    {
      SString key ((char*)&ino[curin], (i-curin)*sizeof (SS_UCS4));
      const SString* lig = ligatures.get (key);
      /* get hard-coded pango scripts */
      if (lig == 0 && i== curin+2 && scriptCode==(int)script)
      {
        int type0 = getCharType (ino[curin]);
        int type1 = getCharType (ino[curin+1]);
        if ((type0 == SD_INDIC_CONSONANT_BASE
            || type0 == SD_INDIC_CONSONANT_POST_BASE
            || type0 == SD_INDIC_CONSONANT_BELOW_BASE
            ) 
           && type1 == SD_INDIC_HALANT)
        {
          /* PANGO intermediate half form */
          SS_UCS4 gl = 0;
          switch (script)
          {
          case SD_DEVANAGARI:
           gl = 0xf000 + ino[curin];
           l = SString ((char*)&gl, sizeof (SS_UCS4));
           lig = &l;
           break;
          default:
           break;
          }
        }
        else
        {
          /* TODO: other stuff */
          switch (script)
          {
          case SD_DEVANAGARI:
           break;
          default:
           break;
          }
        }
      }
      if (lig && lig->size() == sizeof (SS_UCS4))
      {
        memcpy (&ino[curout], lig->array(), sizeof (SS_UCS4));
        curout++;
        curin = i;
        gotone = true;
        break;
      }
    }
    if (!gotone)
    {
      ino[curout] = ino[curin];
      curout++;
      curin++;
    }
  }
  if (curout == len) return curout;
  /* try collapsing further */
  return getRawPangoLigatures (script, ino, curout);
}

/**
 * Get pango ligatures, and fix them.
 * @param ino is the input-output buffer
 * @param len is the input length
 * @return output length
 */
unsigned int
SX11FontCache::getPangoLigatures (unsigned int script, 
   SS_UCS4* ino, unsigned int _len)
{
  /* modifiers*/
  unsigned int mstart=0;
  unsigned int mend=0;
  unsigned int i;
  unsigned int len = _len;
  /* chop off modifier */
  for (i=0; i<_len; i++)
  {
    int type0 = getCharType (ino[i]);
    if (i>0 && type0 == SD_INDIC_MODIFIER && mstart==0)
    {
      mstart = i; mend = len;
      len = mstart;
    }
  }
  /* pre-processing */
  switch (script)
  {
  case SD_DEVANAGARI:
    if (len > 2 && ino[0] == 0x930 && ino[1] == 0x94d && ino[2] == 0x200d)
    {
      ino[0] = 0x0931; /* making sure eyelash is eyelash */
    }
    break;
  }
  
  unsigned int ret = getRawPangoLigatures (script, ino, len);
  if (scriptCode != (int)script || ret < 2)
  {
     /* put back modifiers */
     for (i=mstart; i<mend; i++, ret++) ino[ret] = ino[i];
     return ret;
  }
  SS_UCS4 prefix=0;
  SS_UCS4 postfix=0;
  int endtype = getCharType  (ino[ret-1]);
  /* Put stuff in order */
  switch (script)
  {
  case SD_BENGALI:
    if (endtype == SD_INDIC_LEFT_VOWEL)
    {
      prefix = ino[ret-1];
    }
    else if (endtype == SD_INDIC_LEFT_RIGHT_VOWEL)
    {
      prefix = getLRVowelLeft (ino[ret-1]);
      postfix = getLRVowelRight (ino[ret-1]);
    }
    /* FIXME: Put pango bengali fuzzy logic here. 
               Halant is 0x09CD. Also, there are  left+right vowels  */
    /* make room for pre - we have one byte more for this...*/
    if (prefix)
    {
      /* note that we have one more */
      for (i=ret-1; i>0; i--)
      {
        ino[i] = ino[i-1];
      }
      ino[0] = prefix;
      if (postfix)
      {
        ino[ret] = postfix;
        ret++;
      }
    }
    break;
  case SD_DEVANAGARI:
    if (ino[ret-1] == 0x93f)
    {
      prefix = 0x93f;
    }
    /* convert something + RA to subscript ra */
    for (i=1; i<ret; i++)
    {
      if (ino[i-1] >= 0xf900 && ino[i-1] <0xf980 && ino[i] == 0x0930) // RA
      {
        ino[i-1] = ino[i-1] - 0xf000; /* back to original form */
        ino[i] = 0xc97e; /* subscript */
      }
      if (ino[i-1] >= 0xf900 && ino[i-1] <0xf980 && ino[i] == 0xf000 + 0x0930) // Half RA
      {
        ino[i-1] = ino[i-1] - 0xf000; /* back to original form */
        for (unsigned int j=ret; j>i+1; j--)
        {
          ino[j] = ino[j-1];
        }
        ino[i] = 0xc97e; /* subscript */
        ino[i+1] = 0x094D; /* halant */
        ret++;
      }
    }
    if (ino[0] == 0xf000 + 0x0930)// Starts with half-ra.
    {
      for (i=1; i<ret; i++)
      {
        ino[i-1] = ino[i];
      }
      if (prefix)
      {
        ino[ret-2] = 0xc97f;
      }
      else
      {
        /* we just copied it to ret-2 */
        int type0 = getCharType (ino[ret-1]);
        switch (type0)
        {
        case SD_INDIC_RIGHT_VOWEL:
        case SD_INDIC_TOP_VOWEL:
        case SD_INDIC_BOTTOM_VOWEL:
          ino[ret-2] = 0xc97f;
          break;
        default:
          ino[ret-1] = 0xc97f;
          break;
        }
      }
    }
    /* make room for pre */
    if (prefix)
    {
      for (i=ret-1; i>0; i--)
      {
        ino[i] = ino[i-1];
      }
      ino[0] = prefix;
    }
    /* convert stuff to half forms - everything excpept if it ends with half */
    for (i=prefix?1:0; i+1<ret; i++)
    {
      if (ino[i] >= 0xf000 && ino[i] < 0xf9ff)
      {
        ino[i] = (ino[i] & 0x0fff) | 0xe000;
      }
    }
    break;
  default:
    break;
  }
#if 0
  fprintf (stderr, "Glyphs ");
  for (i=0; i<ret; i++)
  {
    fprintf (stderr, "%04X ", ino[i]);
  }
  fprintf (stderr, "\n");
#endif
  /* put back modifiers */
  for (i=mstart; i<mend; i++, ret++) ino[ret] = ino[i];
  return ret;
}

/**
 * @return the width of the character at o position.
 */
bool
SX11Font::width (const SString& xlfd, SS_UCS4 g, double *width_)
{
  if (width_) *width_=0;
  SX11FontCache* fc = loadFont(impl, xlfd);
  if (!fc->load()) return false;

  SS_UCS2 glyphno = 0;
  unsigned int liglen =0;
  if (isLigature (g) && fc->ligatureCount != 0 
       && (liglen=getLigatureUnicode (g, 0)) > 0)
  {
     /* we allocate one more to allow for LEFT_RIGHT vowel expansion */
     SS_UCS4* chars = new SS_UCS4[liglen + 1];
     CHECK_NEW (chars);
     getLigatureUnicode (g, chars);
     unsigned int script = (g & 0x7fff0000) >> 16;
     unsigned int collapsed = fc->getPangoLigatures (script, chars, liglen);
     if (collapsed==0 || collapsed == liglen)
     {
       delete chars;
       return false;
     }
     if (collapsed == 1)
     {
       glyphno = chars[0];
     }
     else
     {
       double x = 0;
       /* the whole ligature */
       for (unsigned int i=0; i<collapsed; i++)
       {
         double dw = 0.0;
         if (chars[i] > 0xffff 
          || ((dw=fc->width ((SS_UCS2) chars[i]))==0.0 && i==0))
         {
           delete chars;
           return false;
         }
         x += dw;
       }
       if (width_) *width_ = x;
       /* loop */
       delete chars;
       return true;
     }
     delete chars;
  }
  /**
   * create a glyphno array 
   */
  else if (isencoded)
  {
    /* to make things faster */
    if (encoder.isUMap())
    {
      glyphno = encoder.encode (g);
    }
    else
    {
      SV_UCS4 ucs4; ucs4.append (g); SV_UCS4 decd;
      unsigned int lifted = encoder.lift (ucs4, 0, false, &decd);
      /* Like TTF we fallback to original font encoding  */
      if (lifted == 0)
      {
        glyphno = fc->map.encode (g);
      } 
      else
      {
        double x = 0;
        /* draw the whole ligature */
        for (unsigned int i=0; i<decd.size(); i++)
        {
          double dw;
          if (decd[i] > 0xffff) return false;
          if ((dw=fc->width ((SS_UCS2) decd[i]))==0.0 && i==0)
          {
            return false;
          }
          x += dw;
        }
        if (width_) *width_ = x;
        return true;
      }
    }
  }
  else
  {
    if (fc->plane == 0)
    {
      if (fc->map.isUMap()) {
        glyphno = fc->map.encode (g);
      } else {
        SV_UCS4 ucs4; ucs4.append (g); SV_UCS4 decd;
        unsigned int lifted = fc->map.lift (ucs4, 0, false, &decd);
        if (lifted==1 && decd.size()==1) glyphno = decd[0];
      }
    }
    else
    {
       SS_UCS4  start = fc->plane*0x10000;;
       glyphno =  (g < start) ? 0 : fc->map.encode (g-start);
    }
  }
  if (glyphno==0) return false;
  double dw;
  if ((dw = fc->width (glyphno))!=0.0)
  {
    if (width_) *width_ = dw;
    return true;
  }
  return false;
}

double
SX11Font::width (const SString& xlfd)
{
  SX11FontCache* fc = loadFont(impl, xlfd);
  if (!fc->load()) return 0.0;
  return fc->fontWidth;
}

double
SX11Font::ascent (const SString& xlfd)
{
  SX11FontCache* fc = loadFont(impl, xlfd);
  if (!fc->load()) return 0.0;
  return fc->fontAscent;
}

double
SX11Font::descent (const SString& xlfd)
{
  SX11FontCache* fc = loadFont(impl, xlfd);
  if (!fc->load()) return 0.0;
  return fc->fontDescent;
}

double
SX11Font::gap (const SString& xlfd)
{
  return 0.0;
}

static SX11FontCache*
loadFont (SX11Impl* impl, const SString& xlfd)
{
  SX11FontCache* f = fontCache.get (xlfd);
  if (f!=0) return f;
  f = new SX11FontCache (impl, xlfd);
  CHECK_NEW (f);
  fontCache.put (xlfd, f);
  return f;
}


