*** empty log message ***
[m17n/m17n-test.git] / flt.c
diff --git a/flt.c b/flt.c
new file mode 100644 (file)
index 0000000..d7cf04a
--- /dev/null
+++ b/flt.c
@@ -0,0 +1,1030 @@
+#include <stdio.h>
+#include <string.h>
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_TRUETYPE_TABLES_H
+#include <fontconfig/fontconfig.h>
+#include <fontconfig/fcfreetype.h>
+
+#if defined (FLT_GUI)
+
+#include <m17n-gui.h>
+#define PROGNAME "flt-gui"
+
+#elif defined (FLT_OTF)
+
+#include <m17n-flt.h>
+#include <otf.h>
+#define PROGNAME "flt-otf"
+
+#else  /* defined (FLT_HB) */
+
+#include <m17n-flt.h>
+#include <harfbuzz.h>
+#define PROGNAME "flt-hb"
+
+#endif
+
+static FT_Library ft_library;
+
+FT_Face
+new_face (char *fontname, char **err)
+{
+  FcPattern *pat;
+  FcChar8 *fam, *file;
+  double pixelsize;
+  FcFontSet *fs;
+  FcObjectSet *os;
+  FT_Face face;
+
+  pat = FcNameParse ((FcChar8 *) fontname);
+  if (! pat)
+    {
+      *err = "invalid name format";
+      return NULL;
+    }
+  if (FcPatternGetString (pat, FC_FAMILY, 0, &fam) != FcResultMatch)
+    {
+      *err = "no family name";
+      return NULL;
+    }
+  if (FcPatternGetDouble (pat, FC_PIXEL_SIZE, 0, &pixelsize) != FcResultMatch)
+    pixelsize = 20;
+  FcPatternAddBool (pat, FC_SCALABLE, FcTrue);
+  FcPatternDel (pat, FC_PIXEL_SIZE);
+  os = FcObjectSetBuild (FC_FILE, NULL);
+  fs = FcFontList (NULL, pat, os);
+  if (fs->nfont == 0)
+    {
+      *err = "no matching font";
+      return NULL;
+    }
+  FcPatternGetString (fs->fonts[0], FC_FILE, 0, &file);
+
+  if (FT_New_Face (ft_library, (char *) file, 0, &face))
+    {
+      FcFontSetDestroy (fs);
+      FcObjectSetDestroy (os);
+      FcPatternDestroy (pat);
+      *err = "font open fail";
+      return NULL;
+    }
+  FcFontSetDestroy (fs);
+  FcObjectSetDestroy (os);
+  FcPatternDestroy (pat);
+  if (FT_Set_Pixel_Sizes (face, pixelsize, pixelsize))
+    {
+      *err = "set pixelsize fail";
+      FT_Done_Face (face);
+      return NULL;
+    }
+  return face;
+}
+
+#ifdef FLT_GUI
+
+typedef struct
+{
+  MFont *font;
+  FT_Face face;
+} MFLTFont;
+
+MFrame *frame;
+
+MFLTFont *
+open_font (char *fontname, char **err)
+{
+  FT_Face ft_face = new_face (fontname, err);
+  MFLTFont *font;
+  MFace *face;
+  MPlist *plist;
+  MFontset *fontset;
+
+  if (! ft_face)
+    return NULL;
+  face = mface ();
+  plist = mplist ();
+  fontset = mfontset ("generic");
+  mface_put_prop (face, Mfontset, fontset);
+  mplist_add (plist, Mface, face);
+  mplist_add (plist, Mdevice, Mnil);
+  frame = mframe (plist);
+  m17n_object_unref (plist);
+  m17n_object_unref (face);
+  m17n_object_unref (fontset);
+
+  font = calloc (1, sizeof (MFLTFont));
+  font->font = mfont_encapsulate (frame, Mfreetype, ft_face);
+  font->face = ft_face;
+  return font;
+}
+
+void
+close_font (MFLTFont *font)
+{
+  FT_Done_Face (font->face);
+  free (font);
+  m17n_object_unref (frame);
+}
+
+int
+flt (MText *mt, int from, int to, MFLTFont *font, char *flt_name)
+{
+  mtext_put_prop (mt, from, to, Mfont, font->font);
+  mdraw_text_extents (frame, mt, from, to, NULL, NULL, NULL, NULL);
+  return 0;
+}
+
+#else  /* FLT_OTF or FLT_HB */
+
+typedef struct {
+  MFLTFont font;
+  FT_Face face;
+} FontInfo;
+
+int
+get_glyph_id (MFLTFont *font, MFLTGlyph *g)
+{
+  FT_Face face = ((FontInfo *) font)->face;
+
+  g->code = FT_Get_Char_Index (face, g->code);
+
+  return (g->code ? 0 : -1);
+}
+
+int 
+get_metric (MFLTFont *font, MFLTGlyphString *gstring, int from, int to)
+{
+  FT_Face face = ((FontInfo *) font)->face;
+
+  for (; from < to; from++)
+    {
+      MFLTGlyph *g = gstring->glyphs + from;
+      FT_Glyph_Metrics *metrics;
+
+      if (FT_Load_Glyph (face, g->code, FT_LOAD_DEFAULT))
+       return -1;
+      metrics = &face->glyph->metrics;
+      g->lbearing = metrics->horiBearingX;
+      g->rbearing = metrics->horiBearingX + metrics->width;
+      g->xadv = metrics->horiAdvance;
+      g->yadv = metrics->vertAdvance;
+      g->ascent = metrics->horiBearingY;
+      g->descent = metrics->height - metrics->horiBearingY;
+    }
+  return 0;
+}
+
+int
+flt (MText *mt, int from, int to, MFLTFont *font, char *flt_name)
+{
+  MFLTGlyphString gstring;
+  int len = to - from;
+  int i;
+
+  memset (&gstring, 0, sizeof (MFLTGlyphString));
+  gstring.glyph_size = sizeof (MFLTGlyph);
+  gstring.allocated = len * 2;
+  gstring.glyphs = alloca (sizeof (MFLTGlyph) * gstring.allocated);
+  for (i = 0; i < len; i++)
+    gstring.glyphs[i].c = mtext_ref_char (mt, from + i);
+  gstring.used = len;
+  return mflt_run (&gstring, 0, len, font, msymbol (flt_name));
+}
+
+#ifdef FLT_OTF
+
+typedef struct {
+  MFLTFont font;
+  FT_Face face;
+  OTF *otf;
+} FontInfoOTF;
+
+#define DEVICE_DELTA(table, size)                              \
+  (((size) >= (table).StartSize && (size) <= (table).EndSize)  \
+   ? ((table).DeltaValue[(size) - (table).StartSize] << 6)     \
+   : 0)
+
+static void
+adjust_anchor (OTF_Anchor *anchor, FT_Face face,
+              unsigned code, unsigned x_ppem, unsigned y_ppem, int *x, int *y)
+{
+  if (anchor->AnchorFormat == 2)
+    {
+      FT_Outline *outline;
+      int ap = anchor->f.f1.AnchorPoint;
+
+      FT_Load_Glyph (face, (FT_UInt) code, FT_LOAD_MONOCHROME);
+      outline = &face->glyph->outline;
+      if (ap < outline->n_points)
+       {
+         *x = outline->points[ap].x;
+         *y = outline->points[ap].y;
+       }
+    }
+  else if (anchor->AnchorFormat == 3)
+    {
+      if (anchor->f.f2.XDeviceTable.offset)
+       *x += DEVICE_DELTA (anchor->f.f2.XDeviceTable, x_ppem);
+      if (anchor->f.f2.YDeviceTable.offset)
+       *y += DEVICE_DELTA (anchor->f.f2.YDeviceTable, y_ppem);
+    }
+}
+
+char *
+tag_name (char *str, unsigned tag)
+{
+  *str++ = tag >> 24;
+  *str++ = (tag >> 16) & 0xFF;
+  *str++ = (tag >> 8) & 0xFF;
+  *str++ = tag & 0xFF;
+  return str;
+}
+
+void
+encode_features (char *str, int count, unsigned *features)
+{
+  int all = 0;
+  int i;
+
+  for (i = 0; i < count; i++)
+    {
+      unsigned tag = features[i];
+
+      if (tag == 0)
+       all = 1;
+      else
+       {
+         if (i > 0)
+           *str++ = ',';
+         if (all)
+           *str++ = '~';
+         str = tag_name (str, tag);
+       }
+    }
+  if (all)
+    {
+      if (i > 1)
+       *str++ = ',';
+      *str++ = '*';
+    }
+  *str = '\0';
+}
+
+int
+drive_otf (MFLTFont *font, MFLT_OTF_Spec *spec,
+          MFLTGlyphString *in, int from, int to,
+          MFLTGlyphString *out, MFLTGlyphAdjustment *adjustment)
+{
+  int len = to - from;
+  int i, gidx;
+  OTF *otf = ((FontInfoOTF *) font)->otf;
+  OTF_GlyphString otf_gstring;
+  OTF_Glyph *otfg;
+  char script[4], langsys[4];
+  char *gsub_features = NULL, *gpos_features = NULL;
+
+  if (len == 0)
+    return from;
+
+  if (! otf)
+    goto simple_copy;
+  otf_gstring.glyphs = NULL;
+  if (OTF_get_table (otf, "head") < 0)
+    {
+      OTF_close (otf);
+      ((FontInfoOTF *) font)->otf = NULL;
+      goto simple_copy;
+    }
+
+  tag_name (script, spec->script);
+  tag_name (langsys, spec->langsys);
+
+  if (spec->gsub_count > 0)
+    {
+      gsub_features = alloca (6 * spec->gsub_count);
+      if (gsub_features)
+       {
+         if (OTF_check_table (otf, "GSUB") < 0)
+           gsub_features = NULL;
+         else
+           encode_features (gsub_features, spec->gsub_count, spec->gsub);
+       }
+    }
+  if (spec->gpos_count)
+    {
+      gpos_features = alloca (6 * spec->gpos_count);
+      if (gpos_features)
+       {
+         if (OTF_check_table (otf, "GPOS") < 0)
+           gpos_features = NULL;
+         else
+           encode_features (gpos_features, spec->gpos_count, spec->gpos);
+       }
+    }
+
+  otf_gstring.size = otf_gstring.used = len;
+  otf_gstring.glyphs = (OTF_Glyph *) malloc (sizeof (OTF_Glyph) * len);
+  memset (otf_gstring.glyphs, 0, sizeof (OTF_Glyph) * len);
+  for (i = 0; i < len; i++)
+    {
+      otf_gstring.glyphs[i].c = in->glyphs[from + i].c;
+      otf_gstring.glyphs[i].glyph_id = in->glyphs[from + i].code;
+    }
+
+  OTF_drive_gdef (otf, &otf_gstring);
+  gidx = out->used;
+
+  if (gsub_features)
+    {
+      if (OTF_drive_gsub (otf, &otf_gstring, script, langsys, gsub_features)
+         < 0)
+       goto simple_copy;
+      if (out->allocated < out->used + otf_gstring.used)
+       return -2;
+      for (i = 0, otfg = otf_gstring.glyphs; i < otf_gstring.used; i++, otfg++)
+       {
+         MFLTGlyph *g = out->glyphs + out->used;
+
+         *g = in->glyphs[from + otfg->f.index.from];
+         g->c = otfg->c;
+         g->code = otfg->glyph_id;
+         out->used++;
+       }
+    }
+  else
+    {
+      if (out->allocated < out->used + len)
+       return -2;
+      for (i = 0; i < len; i++)
+       out->glyphs[out->used++] = in->glyphs[from + i];
+    }
+
+  font->get_metric (font, out, gidx, out->used);
+
+  if (gpos_features)
+    {
+      FT_Face face;
+      MFLTGlyph *base = NULL, *mark = NULL, *g;
+      int x_ppem, y_ppem, x_scale, y_scale;
+
+      if (OTF_check_features (otf, 1,
+                             spec->script, spec->langsys,
+                             spec->gpos, spec->gpos_count) != 1
+         || (OTF_drive_gpos (otf, &otf_gstring, script, langsys,
+                             gpos_features) < 0))
+       return to;
+
+      face = ((FontInfo *) font)->face;
+      x_ppem = face->size->metrics.x_ppem;
+      y_ppem = face->size->metrics.y_ppem;
+      x_scale = face->size->metrics.x_scale;
+      y_scale = face->size->metrics.y_scale;
+
+      for (i = 0, otfg = otf_gstring.glyphs, g = out->glyphs + gidx;
+          i < otf_gstring.used; i++, otfg++, g++)
+       {
+         MFLTGlyph *prev;
+
+         if (! otfg->glyph_id)
+           continue;
+         switch (otfg->positioning_type)
+           {
+           case 0:
+             break;
+           case 1: case 2:
+             {
+               int format = otfg->f.f1.format;
+
+               if (format & OTF_XPlacement)
+                 adjustment[i].xoff
+                   = otfg->f.f1.value->XPlacement * x_scale / 0x10000;
+               if (format & OTF_XPlaDevice)
+                 adjustment[i].xoff
+                   += DEVICE_DELTA (otfg->f.f1.value->XPlaDevice, x_ppem);
+               if (format & OTF_YPlacement)
+                 adjustment[i].yoff
+                   = - (otfg->f.f1.value->YPlacement * y_scale / 0x10000);
+               if (format & OTF_YPlaDevice)
+                 adjustment[i].yoff
+                   -= DEVICE_DELTA (otfg->f.f1.value->YPlaDevice, y_ppem);
+               if (format & OTF_XAdvance)
+                 adjustment[i].xadv
+                   += otfg->f.f1.value->XAdvance * x_scale / 0x10000;
+               if (format & OTF_XAdvDevice)
+                 adjustment[i].xadv
+                   += DEVICE_DELTA (otfg->f.f1.value->XAdvDevice, x_ppem);
+               if (format & OTF_YAdvance)
+                 adjustment[i].yadv
+                   += otfg->f.f1.value->YAdvance * y_scale / 0x10000;
+               if (format & OTF_YAdvDevice)
+                 adjustment[i].xadv
+                   += DEVICE_DELTA (otfg->f.f1.value->YAdvDevice, y_ppem);
+               adjustment[i].set = 1;
+             }
+             break;
+           case 3:
+             /* Not yet supported.  */
+             break;
+           case 4: case 5:
+             if (! base)
+               break;
+             prev = base;
+             goto label_adjust_anchor;
+           default:            /* i.e. case 6 */
+             if (! mark)
+               break;
+             prev = mark;
+
+           label_adjust_anchor:
+             {
+               int base_x, base_y, mark_x, mark_y;
+
+               base_x = otfg->f.f4.base_anchor->XCoordinate * x_scale / 0x10000;
+               base_y = otfg->f.f4.base_anchor->YCoordinate * y_scale / 0x10000;
+               mark_x = otfg->f.f4.mark_anchor->XCoordinate * x_scale / 0x10000;
+               mark_y = otfg->f.f4.mark_anchor->YCoordinate * y_scale / 0x10000;;
+
+               if (otfg->f.f4.base_anchor->AnchorFormat != 1)
+                 adjust_anchor (otfg->f.f4.base_anchor, face, prev->code,
+                                x_ppem, y_ppem, &base_x, &base_y);
+               if (otfg->f.f4.mark_anchor->AnchorFormat != 1)
+                 adjust_anchor (otfg->f.f4.mark_anchor, face, g->code,
+                                x_ppem, y_ppem, &mark_x, &mark_y);
+               adjustment[i].xoff = (base_x - mark_x);
+               adjustment[i].yoff = - (base_y - mark_y);
+               adjustment[i].back = (g - prev);
+               adjustment[i].set = 1;
+             }
+           }
+         if (otfg->GlyphClass == OTF_GlyphClass0)
+           base = mark = g;
+         else if (otfg->GlyphClass == OTF_GlyphClassMark)
+           mark = g;
+         else
+           base = g;
+       }
+    }
+  free (otf_gstring.glyphs);
+  return to;
+
+ simple_copy:
+  font->get_metric (font, in, from, to);
+  for (i = 0; i < len; i++)
+    {
+      MFLTGlyph *g = in->glyphs + (from + i);
+
+      out->glyphs[out->used++] = *g;
+    }
+  if (otf_gstring.glyphs)
+    free (otf_gstring.glyphs);
+  return to;
+}
+
+MFLTFont *
+open_font (char *fontname, char **err)
+{
+  FT_Face face = new_face (fontname, err);
+  FontInfoOTF *font_info;
+
+  if (! face)
+    return NULL;
+  font_info = malloc (sizeof (FontInfoOTF));
+  font_info->font.get_glyph_id = get_glyph_id;
+  font_info->font.get_metric = get_metric;
+  font_info->font.drive_otf = drive_otf;
+  font_info->face = face;
+  font_info->otf = OTF_open_ft_face (face);
+  return ((MFLTFont *) font_info);
+}
+
+void
+close_font (MFLTFont *font)
+{
+  FontInfoOTF *font_info = (FontInfoOTF *) font;
+
+  OTF_close (font_info->otf);
+  FT_Done_Face (font_info->face);
+  free (font_info);
+}
+
+#else  /* FLT_HB */
+
+typedef struct {
+  HB_UShort count;
+  HB_UShort *indices;
+} FeatureInfo;
+
+typedef struct {
+  FeatureInfo gsub;
+  FeatureInfo gpos;
+} GsubGposInfo;
+
+typedef struct {
+  MFLTFont font;
+  FT_Face face;
+  HB_FontRec hb_font;
+  HB_StreamRec gdef_stream;
+  HB_GDEF gdef;
+  HB_GSUB gsub;
+  HB_GPOS gpos;
+  MPlist *otf_spec_cache;
+} FontInfoHB;
+
+HB_Bool
+convertStringToGlyphIndices (HB_Font font, const HB_UChar16 *string,
+                            hb_uint32 length, HB_Glyph *glyphs,
+                            hb_uint32 *numGlyphs, HB_Bool rightToLeft)
+{
+  return TRUE;
+}
+
+void
+getGlyphAdvances (HB_Font font, const HB_Glyph *glyphs, hb_uint32 numGlyphs,
+                 HB_Fixed *advances, int flags /*HB_ShaperFlag*/)
+{
+  hb_uint32 i;
+
+  for (i = 0; i < numGlyphs; i++)
+    advances[i] =0;
+}
+
+HB_Bool
+canRender (HB_Font font, const HB_UChar16 *string, hb_uint32 length)
+{
+  return TRUE;
+}
+
+HB_Error
+getPointInOutline (HB_Font font, HB_Glyph glyph, int flags /*HB_ShaperFlag*/,
+                  hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos,
+                  hb_uint32 *nPoints)
+{
+  FT_Face face = font->faceData;
+  HB_Error error;
+
+  if ((error = (HB_Error) FT_Load_Glyph (face, glyph, flags)))
+    return error;
+  if (face->glyph->format != ft_glyph_format_outline)
+    return (HB_Error) HB_Err_Invalid_GPOS_SubTable;
+  *nPoints = face->glyph->outline.n_points;
+  if (! *nPoints)
+    return HB_Err_Ok;
+  if (point > *nPoints)
+    return (HB_Error) HB_Err_Invalid_GPOS_SubTable;
+
+  *xpos = face->glyph->outline.points[point].x;
+  *ypos = face->glyph->outline.points[point].y;
+
+  return HB_Err_Ok;
+}
+
+void
+getGlyphMetrics (HB_Font font, HB_Glyph glyph, HB_GlyphMetrics *metrics)
+{
+}
+
+HB_Fixed
+getFontMetric (HB_Font font, HB_FontMetric metric)
+{
+  return 0;
+}
+
+#define MAKE_TAG(name) ((((FT_ULong) (name)[0]) << 24)         \
+                       | (((FT_ULong) (name)[1]) << 16)        \
+                       | (((FT_ULong) (name)[2]) << 8)         \
+                       | (name)[3])
+
+HB_Error
+new_stream (FT_Face face, char *tagname, HB_Stream stream)
+{
+  FT_ULong tag = MAKE_TAG (tagname);
+  FT_ULong len;
+  FT_Byte *buf;
+
+  if (! FT_IS_SFNT (face))
+    return HB_Err_Invalid_Argument;
+  len = 0;
+  if (FT_Load_Sfnt_Table (face, tag, 0, NULL, &len))
+    return HB_Err_Table_Missing;
+  buf = malloc (len);
+  if (! buf)
+    return HB_Err_Out_Of_Memory;
+  if (FT_Load_Sfnt_Table (face, tag, 0, buf, &len))
+    {
+      free (buf);
+      return HB_Err_Table_Missing;
+    }
+  stream->base = (HB_Byte *) buf;
+  stream->size = len;
+  stream->pos = 0;
+  stream->cursor = NULL;
+
+  return HB_Err_Ok;
+}
+
+HB_Error
+load_gdef (FontInfoHB *font_info)
+{
+  HB_Error err;
+
+  if (! font_info->gdef_stream.base)
+    {
+      err = new_stream ((FT_Face) font_info->hb_font.faceData, "GDEF",
+                       &font_info->gdef_stream);
+      if (err != HB_Err_Ok)
+       return err;
+    }
+  return HB_Load_GDEF_Table (&font_info->gdef_stream, &font_info->gdef);
+}
+
+HB_Error
+load_gsub (FontInfoHB *font_info)
+{
+  HB_Error err;
+  HB_StreamRec stream;
+  HB_LookupList *lookup_list;
+  int i;
+
+  if (! font_info->gdef)
+    {
+      if ((err = load_gdef (font_info)) != HB_Err_Ok)
+       return err;
+    }
+  err = new_stream ((FT_Face) font_info->hb_font.faceData, "GSUB", &stream);
+  if (err != HB_Err_Ok)
+    return err;
+  err = HB_Load_GSUB_Table (&stream, &font_info->gsub,
+                           font_info->gdef, &font_info->gdef_stream);
+  free (stream.base);
+  lookup_list = &font_info->gsub->LookupList;
+  for (i = 0; i < lookup_list->LookupCount; i++)
+    lookup_list->Properties[i] = HB_GLYPH_PROPERTIES_UNKNOWN;
+  return err;
+}
+
+HB_Error
+load_gpos (FontInfoHB *font_info)
+{
+  HB_Error err;
+  HB_StreamRec stream;
+  HB_LookupList *lookup_list;
+  int i;
+
+  if (! font_info->gdef)
+    {
+      if ((err = load_gdef (font_info)) != HB_Err_Ok)
+       return err;
+    }
+  err = new_stream ((FT_Face) font_info->hb_font.faceData, "GPOS", &stream);
+  if (err != HB_Err_Ok)
+    return err;
+  err = HB_Load_GPOS_Table (&stream, &font_info->gpos,
+                           font_info->gdef, &font_info->gdef_stream);
+  free (stream.base);
+  lookup_list = &font_info->gpos->LookupList;
+  for (i = 0; i < lookup_list->LookupCount; i++)
+    lookup_list->Properties[i] = HB_GLYPH_PROPERTIES_UNKNOWN;
+  return err;
+}
+
+const HB_FontClass hb_fontClass = {
+    convertStringToGlyphIndices, getGlyphAdvances, canRender,
+    getPointInOutline, getGlyphMetrics, getFontMetric
+};
+
+HB_Error
+setup_features (int gsubp, FontInfoHB *font_info, MFLT_OTF_Spec *spec,
+               FeatureInfo *info)
+{
+  int count, preordered;
+  unsigned int *features;
+  FT_UShort script, langsys;
+  FT_UShort req_feature, index;
+  FT_UInt *feature_list;
+  int i, j, k;
+  HB_Error err;
+
+  if (gsubp)
+    {
+      if (! font_info->gsub)
+       {
+         if ((err = load_gsub (font_info)) != HB_Err_Ok)
+           return err;
+       }
+      if ((err = HB_GSUB_Select_Script (font_info->gsub, spec->script, &script))
+         != HB_Err_Ok)
+       return err;
+      if (spec->langsys)
+       {
+         if ((err = HB_GSUB_Select_Language (font_info->gsub, spec->langsys,
+                                             script, &langsys, &req_feature))
+             != HB_Err_Ok)
+           return err;
+       }
+      else
+       langsys = req_feature = 0xFFFF;
+      count = spec->gsub_count;
+      features = spec->gsub;
+    }
+  else
+    {
+      if (! font_info->gpos)
+       {
+         if ((err = load_gpos (font_info)) != HB_Err_Ok)
+           return err;
+       }
+      if ((err = HB_GPOS_Select_Script (font_info->gpos, spec->script, &script))
+         != HB_Err_Ok)
+       return err;
+      if (spec->langsys)
+       {
+         if ((err = HB_GPOS_Select_Language (font_info->gpos, spec->langsys,
+                                             script, &langsys, &req_feature))
+             != HB_Err_Ok)
+           return err;
+       }
+      else
+       langsys = req_feature = 0xFFFF;
+      count = spec->gpos_count;
+      features = spec->gpos;
+    }
+  feature_list = NULL;
+  for (preordered = 0; preordered < count; preordered++)
+    if (features[preordered] == 0)
+      {
+       if ((err = (gsubp
+                   ? HB_GSUB_Query_Features (font_info->gsub, script, langsys,
+                                             &feature_list)
+                   : HB_GPOS_Query_Features (font_info->gpos, script, langsys,
+                                             &feature_list)))
+           != HB_Err_Ok)
+         return err;
+       break;
+      }
+  if (feature_list)
+    for (i = 0; feature_list[i]; i++);
+  else
+    i = preordered;
+  info->indices = malloc (sizeof (FT_UShort) * ((req_feature != 0xFFFF) + i));
+  i = 0;
+  if (req_feature != 0xFFFF)
+    info->indices[i++] = req_feature;
+  for (j = 0; j < preordered; j++)
+    {
+      if ((err = (gsubp
+                 ? HB_GSUB_Select_Feature (font_info->gsub, features[j],
+                                           script, langsys, &index)
+                 : HB_GPOS_Select_Feature (font_info->gpos, features[j],
+                                           script, langsys, &index)))
+         != HB_Err_Ok)
+       return err;
+      info->indices[i++] = index;
+    }
+  if (feature_list)
+    for (j = 0; feature_list[j]; j++)
+      {
+       for (k = preordered + 1; k < count; k++)
+         if (feature_list[j] == features[k])
+           break;
+       if (k == count)
+         {
+           if ((gsubp
+                ? HB_GSUB_Select_Feature (font_info->gsub, feature_list[j],
+                                          script, langsys, &index)
+                : HB_GPOS_Select_Feature (font_info->gpos, feature_list[j],
+                                          script, langsys, &index))
+               != FT_Err_Ok)
+             return -1;
+           info->indices[i++] = index;
+         }
+      }
+  info->count = i;
+  return HB_Err_Ok;
+}
+
+GsubGposInfo *
+setup_otf_spec (MFLTFont *font, MFLT_OTF_Spec *spec)
+{
+  FontInfoHB *font_info = (FontInfoHB *) font;
+  GsubGposInfo *ginfo;
+
+  if (spec->gsub_count + spec->gpos_count == 0)
+    return NULL;
+  ginfo = mplist_get (font_info->otf_spec_cache, spec->sym);
+  if (ginfo)
+    return (ginfo->gsub.count + ginfo->gpos.count > 0 ? ginfo : NULL);
+  ginfo = calloc (1, sizeof (GsubGposInfo));
+  mplist_push (font_info->otf_spec_cache, spec->sym, ginfo);
+  if (setup_features (1, font_info, spec, &(ginfo->gsub)) != HB_Err_Ok)
+    return NULL;
+  if (setup_features (0, font_info, spec, &(ginfo->gpos)) != HB_Err_Ok)
+    return NULL;
+  return ginfo;
+}
+
+int
+drive_otf (MFLTFont *font, MFLT_OTF_Spec *spec,
+          MFLTGlyphString *in, int from, int to,
+          MFLTGlyphString *out, MFLTGlyphAdjustment *adjustment)
+{
+  FontInfoHB *font_info = (FontInfoHB *) font;
+  int len = to - from;
+  int i, gidx;
+  HB_Buffer buf;
+  GsubGposInfo *ginfo;
+  HB_UShort *apply_order_save;
+  HB_UShort apply_count_save;
+  int apply_saved = 0;
+  int gpos_applied = 0;
+  HB_Error err;
+
+  if (len == 0)
+    return from;
+
+  buf = NULL;
+  if (hb_buffer_new (&buf) != FT_Err_Ok)
+    goto simple_copy;
+  for (i = from; i < to; i++)
+    {
+      if (hb_buffer_add_glyph (buf, in->glyphs[i].code, 0, i) != FT_Err_Ok)
+       goto simple_copy;
+    }
+  ginfo = setup_otf_spec (font, spec);
+  if (! ginfo)
+    goto simple_copy;
+  if (ginfo->gsub.count > 0)
+    {
+      if (! font_info->gdef
+         && load_gdef (font_info) != HB_Err_Ok)
+       goto simple_copy;
+      apply_order_save = font_info->gsub->FeatureList.ApplyOrder;
+      apply_count_save = font_info->gsub->FeatureList.ApplyCount;
+      apply_saved = 1;
+      font_info->gsub->FeatureList.ApplyOrder = ginfo->gsub.indices;
+      font_info->gsub->FeatureList.ApplyCount = ginfo->gsub.count;
+      err = HB_GSUB_Apply_String (font_info->gsub, buf);
+      if (err != HB_Err_Ok && err != HB_Err_Not_Covered)
+       goto simple_copy;
+      if (out->used + buf->in_length > out->allocated)
+       return -2;
+    }
+
+  if (ginfo->gpos.count > 0
+      && (font_info->gpos || load_gpos (font_info) == HB_Err_Ok))
+    {
+      if (! apply_saved)
+       {
+         apply_order_save = font_info->gsub->FeatureList.ApplyOrder;
+         apply_count_save = font_info->gsub->FeatureList.ApplyCount;
+         apply_saved = 1;
+       }
+      font_info->gpos->FeatureList.ApplyOrder = ginfo->gpos.indices;
+      font_info->gpos->FeatureList.ApplyCount = ginfo->gpos.count;
+      err = HB_GPOS_Apply_String (&font_info->hb_font, font_info->gpos,
+                                 FT_LOAD_DEFAULT, buf, FALSE, FALSE);
+      if (err == HB_Err_Ok)
+       gpos_applied = 1;
+    }
+
+  if (apply_saved)
+    {
+      font_info->gsub->FeatureList.ApplyOrder = apply_order_save;
+      font_info->gsub->FeatureList.ApplyCount = apply_count_save;
+    }
+
+  gidx = out->used;
+
+  for (i = 0; i < buf->in_length; i++)
+    {
+      HB_GlyphItem hg = buf->in_string + i;
+      HB_Position pos = buf->positions + i;
+      MFLTGlyph *g;
+
+      out->glyphs[out->used] = in->glyphs[hg->cluster];
+      g = out->glyphs + out->used++;
+      if (g->code != hg->gindex)
+       g->c = 0, g->code = hg->gindex;
+      adjustment[i].set = gpos_applied;
+      if (gpos_applied)
+       {
+         adjustment[i].xadv = pos->x_advance;
+         adjustment[i].yadv = pos->y_advance;
+         adjustment[i].xoff = pos->x_pos;
+         adjustment[i].yoff = - pos->y_pos;
+         adjustment[i].back = pos->back;
+         adjustment[i].advance_is_absolute = pos->new_advance;
+       }
+    }
+  return to;
+
+ simple_copy:
+  if (buf)
+    hb_buffer_free (buf);
+  for (i = 0; i < len; i++)
+    out->glyphs[out->used++] = in->glyphs[from + i];
+  return to;
+}
+
+MFLTFont *
+open_font (char *fontname, char **err)
+{
+  FT_Face face = new_face (fontname, err);
+  FontInfoHB *font_info;
+
+  if (! face)
+    return NULL;
+  font_info = calloc (1, sizeof (FontInfoHB));
+  font_info->font.get_glyph_id = get_glyph_id;
+  font_info->font.get_metric = get_metric;
+  font_info->font.drive_otf = drive_otf;
+  font_info->face = face;
+  font_info->hb_font.klass = &hb_fontClass;
+  font_info->hb_font.faceData = face;
+  font_info->hb_font.userData = NULL;
+  font_info->hb_font.x_ppem = face->size->metrics.x_ppem;
+  font_info->hb_font.x_scale = face->size->metrics.x_scale;
+  font_info->hb_font.y_scale = face->size->metrics.y_scale;
+  font_info->hb_font.y_ppem = face->size->metrics.y_ppem;
+  font_info->otf_spec_cache = mplist ();
+  return ((MFLTFont *) font_info);
+}
+
+void
+close_font (MFLTFont *font)
+{
+  FontInfoHB *font_info = (FontInfoHB *) font;
+  MPlist *p;
+
+  if (font_info->gdef)
+    {
+      HB_Done_GDEF_Table (font_info->gdef);
+      free (font_info->gdef_stream.base);
+    }
+  if (font_info->gsub)
+    HB_Done_GSUB_Table (font_info->gsub);
+  if (font_info->gpos)
+    HB_Done_GPOS_Table (font_info->gpos);
+  FT_Done_Face ((FT_Face) (font_info->hb_font.faceData));
+  for (p = font_info->otf_spec_cache; mplist_key (p) != Mnil;
+       p = mplist_next (p))
+    {
+      GsubGposInfo *ginfo = mplist_value (p);
+
+      if (ginfo->gsub.count > 0)
+       free (ginfo->gsub.indices);
+      if (ginfo->gpos.count > 0)
+       free (ginfo->gpos.indices);
+    }
+  m17n_object_unref (font_info->otf_spec_cache);
+  free (font_info);
+}
+
+#endif
+#endif
+
+int
+main (int argc, char **argv)
+{
+  char *font_name, *flt_name;
+  char *err;
+  MText *mt;
+  MFLTFont *font;
+  int len, i, j;
+
+  if (argc < 3)
+    {
+      fprintf (stderr, "Usage: flt FONT-NAME FLT-NAME < TEXT-FILE\n");
+      exit (1);
+    }
+
+  font_name = argv[1];
+  flt_name = argv[2];
+
+  FT_Init_FreeType (&ft_library);
+  M17N_INIT ();
+  font = open_font (font_name, &err);
+  if (! font)
+    {
+      fprintf (stderr, "Font error: %s: %s\n", argv[1], err);
+      exit (1);
+    }
+
+  mt = mconv_decode_stream (msymbol ("utf-8"), stdin);
+  len = mtext_len (mt);
+  for (i = 0, j = mtext_character (mt, i, len, '\n'); i < len;
+       i = j + 1, j = mtext_character (mt, i, len, '\n'))
+    {
+      if (j < 0)
+       j = len;
+      if (flt (mt, i, j, font, flt_name) < 0)
+       {
+         fprintf (stderr, "Error in FLT processing.\n");
+         exit (1);
+       }
+    }
+  m17n_object_unref (mt);
+  close_font (font);
+  M17N_FINI ();
+  return 0;
+}