Since Xft may only be available statically without shlib deps, check for

Tue Dec 19 22:47:16 2000  Owen Taylor  <otaylor@redhat.com>

	* configure.in pango-config.in pangoxft.pc.in
	modules/basic/Makefile.am: Since Xft may only be available
	statically without shlib deps, check for FreeType libs explicitly
	and include them when linking, otherwise things won't work. Also,
	define FREETYPE_CFLAGS from freetype-config --cflags.

	* modules/basic/basic-xft.c pango/pangoxft-font{,map}.c: Fool
	Xft into not converting glyph indices by loading the
	face unencoded then calling FT_Set_Charmap ourselves.

	* pango/Makefile.am pango/pango-ot.h pango/opentype/* :Add start
	of opentype handling - most of the actually meat of the code here
	is the OpenType layout code from FreeType 1 ported to freetype2
	and adapted slighlty for our purposes.  Also, includes a
	incomplete OpenType-table-dumping code useful for figuring
	out what is going on.

	* pango/pangoxft.h pango/pangoxft-font.h: Add calls for
	getting FT_Face and PangoOTInfo from PangoXftFont.

	* modules/arabic/{Makefile.am,arabic-ot.[ch],arabic-xft.c}:
	Initial support for rendering Arabic with OpenType fonts.
diff --git a/src/.cvsignore b/src/.cvsignore
new file mode 100644
index 0000000..5195f99
--- /dev/null
+++ b/src/.cvsignore
@@ -0,0 +1,8 @@
+Makefile.in
+Makefile
+makefile.mingw
+ottest
+*.lo
+*.la
+.deps
+.libs
diff --git a/src/FT-license.txt b/src/FT-license.txt
new file mode 100644
index 0000000..8dae609
--- /dev/null
+++ b/src/FT-license.txt
@@ -0,0 +1,158 @@
+                    The FreeType Project LICENSE
+                    ----------------------------
+
+                       Copyright 1996-1999 by
+          David Turner, Robert Wilhelm, and Werner Lemberg
+
+
+
+Introduction
+============
+
+  The FreeType  Project is distributed in  several archive packages;
+  some of them may contain, in addition to the FreeType font engine,
+  various tools and  contributions which rely on, or  relate to, the
+  FreeType Project.
+
+  This  license applies  to all  files found  in such  packages, and
+  which do not  fall under their own explicit  license.  The license
+  affects  thus  the  FreeType   font  engine,  the  test  programs,
+  documentation and makefiles, at the very least.
+
+  This  license   was  inspired  by  the  BSD,   Artistic,  and  IJG
+  (Independent JPEG  Group) licenses, which  all encourage inclusion
+  and  use of  free  software in  commercial  and freeware  products
+  alike.  As a consequence, its main points are that:
+
+    o We don't promise that this software works.  However, we are be
+      interested in any kind of bug reports. (`as is' distribution)
+
+    o You can  use this software for whatever you  want, in parts or
+      full form, without having to pay us. (`royalty-free' usage)
+
+    o You may not pretend that  you wrote this software.  If you use
+      it, or  only parts of it,  in a program,  you must acknowledge
+      somewhere in your documentation  that you've used the FreeType
+      code. (`credits')
+
+  We  specifically  permit  and  encourage  the  inclusion  of  this
+  software,  with  or without modifications, in commercial products,
+  provided that all warranty or  liability claims are assumed by the
+  product vendor.
+
+
+Legal Terms
+===========
+
+0. Definitions
+--------------
+
+  Throughout this license,  the terms `package', `FreeType Project',
+  and  `FreeType  archive' refer  to  the  set  of files  originally
+  distributed  by the  authors  (David Turner,  Robert Wilhelm,  and
+  Werner Lemberg) as the `FreeType project', be they named as alpha,
+  beta or final release.
+
+  `You' refers to  the licensee, or person using  the project, where
+  `using' is a generic term including compiling the project's source
+  code as  well as linking it  to form a  `program' or `executable'.
+  This  program is  referred to  as  `a program  using the  FreeType
+  engine'.
+
+  This  license applies  to all  files distributed  in  the original
+  FreeType  archive,   including  all  source   code,  binaries  and
+  documentation,  unless  otherwise  stated   in  the  file  in  its
+  original, unmodified form as  distributed in the original archive.
+  If you are  unsure whether or not a particular  file is covered by
+  this license, you must contact us to verify this.
+
+  The FreeType  project is copyright (C) 1996-1999  by David Turner,
+  Robert Wilhelm, and Werner Lemberg.  All rights reserved except as
+  specified below.
+
+1. No Warranty
+--------------
+
+  THE FREETYPE ARCHIVE  IS PROVIDED `AS IS' WITHOUT  WARRANTY OF ANY
+  KIND, EITHER EXPRESSED OR  IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+  WARRANTIES  OF  MERCHANTABILITY   AND  FITNESS  FOR  A  PARTICULAR
+  PURPOSE.  IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS
+  BE LIABLE  FOR ANY DAMAGES CAUSED  BY THE USE OR  THE INABILITY TO
+  USE, OF THE FREETYPE PROJECT.
+
+  As  you have  not signed  this license,  you are  not  required to
+  accept  it.   However,  as  the FreeType  project  is  copyrighted
+  material, only  this license, or  another one contracted  with the
+  authors, grants you  the right to use, distribute,  and modify it.
+  Therefore,  by  using,  distributing,  or modifying  the  FreeType
+  project, you indicate that you understand and accept all the terms
+  of this license.
+
+2. Redistribution
+-----------------
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions
+  are met:
+
+    o Redistribution  of source code  must retain this  license file
+      (`licence.txt') unaltered; any additions, deletions or changes
+      to   the  original   files  must   be  clearly   indicated  in
+      accompanying  documentation.   The  copyright notices  of  the
+      unaltered, original  files must be preserved in  all copies of
+      source files.
+
+    o Redistribution in binary form must provide a  disclaimer  that
+      states  that  the software is based in part of the work of the
+      FreeType Team,  in  the  distribution  documentation.  We also
+      encourage you to put an URL to the FreeType web page  in  your
+      documentation, though this isn't mandatory.
+
+  These conditions  apply to any  software derived from or  based on
+  the FreeType code, not just  the unmodified files.  If you use our
+  work, you  must acknowledge us.  However,  no fee need  be paid to
+  us.
+
+3. Advertising
+--------------
+
+  The names of  FreeType's authors and contributors may  not be used
+  to endorse or promote  products derived from this software without
+  specific prior written permission.
+  
+  We suggest,  but do not require, that  you use one or  more of the
+  following phrases to refer  to this software in your documentation
+  or advertising  materials: `FreeType Project',  `FreeType Engine',
+  `FreeType library', or `FreeType Distribution'.
+
+4. Contacts
+-----------
+
+  There are two mailing lists related to FreeType:
+
+    o freetype@freetype.org
+
+      Discusses general use and applications of FreeType, as well as
+      future and  wanted additions to the  library and distribution.
+      If  you are looking  for support,  start in  this list  if you
+      haven't found anything to help you in the documentation.
+
+    o devel@freetype.org
+
+      Discusses bugs,  as well  as engine internals,  design issues,
+      specific licenses, porting, etc.
+
+    o http://www.freetype.org
+
+      Holds the current  FreeType web page, which will  allow you to
+      download  our  latest  development  version  and  read  online
+      documentation.
+
+  You can also contact us individually at:
+
+    David Turner      <david.turner@freetype.org>
+    Robert Wilhelm    <robert.wilhelm@freetype.org>
+    Werner Lemberg    <werner.lemberg@freetype.org>
+
+
+--- end of license.txt ---
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..38cbfd2
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,39 @@
+## Process this file with automake to produce Makefile.in
+
+INCLUDES = 					\
+	-DSYSCONFDIR=\"$(sysconfdir)\" 		\
+	-DLIBDIR=\"$(libdir)\" 			\
+	$(FREETYPE_CFLAGS)			\
+	$(X_CFLAGS)				\
+	-I$(top_srcdir)
+
+LDADDS = @STRIP_BEGIN@ 	\
+	@x_ldflags@	\
+	@x_libs@	\
+	@GLIB_LIBS@	\
+	-lm		\
+@STRIP_END@
+
+noinst_LTLIBRARIES = libpango-ot.la
+
+libpango_ot_la_SOURCES =  	\
+	ftxopen.c	 	\
+	ftxgdef.c		\
+	ftxgpos.c 		\
+	pango-ot-info.c		\
+	pango-ot-ruleset.c	\
+	ftxgsub.c
+
+noinst_PROGRAMS = ottest
+
+ottest_SOURCES = 	\
+	ottest.c	\
+	disasm.c	\
+	disasm.h
+
+ottest_LDADD = 		\
+	libpango-ot.la	\
+	$(FREETYPE_LIBS)
+
+EXTRA_DIST = 		\
+	README
diff --git a/src/README b/src/README
new file mode 100644
index 0000000..6deb7f5
--- /dev/null
+++ b/src/README
@@ -0,0 +1,36 @@
+This directory includes code for using OpenType Layout tables from
+OpenType fonts with FreeType and 
+
+The table reading code in:
+
+ ftxopen.[ch]
+ ftxopenf.h
+ ftxgdef.[ch]
+ ftxgpos.[ch]
+ ftxgdef.[ch]
+
+Is derived from the OpenType code in FreeType-1.x, ported to FreeType2. 
+(This code has been abandoned for FreeType2, but until something better
+comes along, should serve our purposes.)
+
+This code should be left following the FreeType indentation style and
+coding conventions.
+
+In addition to porting to FreeType-2, it has been modified to
+add support for PangoGlyphString's log_clusters, and in various
+other ways. Bug reports on these files should be sent to 
+gtk-i18n-list@gtk.org, NOT to the freetype maintainers.
+
+The license for these files is in the file freetype-license.txt. 
+
+
+Most of the additional files in this directory implement a high-level
+interface to this that follows Pango conventions and integrates with
+Pango.
+
+disasm.[ch] is a partial dumper for OpenType layout tables useful
+in figuring out what is going on. Please extend to cover additional
+parts of the tables as you encounter fonts using them.
+
+Owen Taylor
+17 December 2000
\ No newline at end of file
diff --git a/src/disasm.c b/src/disasm.c
new file mode 100644
index 0000000..2e064ec
--- /dev/null
+++ b/src/disasm.c
@@ -0,0 +1,317 @@
+/* Pango
+ * disasm.c: Dump OpenType layout tables
+ *
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <stdarg.h>
+
+#include "disasm.h"
+
+#define DUMP(args...) dump (stream, indent, args)
+#define DUMP_FINT(strct,fld) dump (stream, indent, "<" #fld ">%d</" #fld ">\n", (strct)->fld)
+#define DUMP_FUINT(strct,fld) dump (stream, indent, "<" #fld ">%u</" #fld ">\n", (strct)->fld)
+#define DUMP_FGLYPH(strct,fld) dump (stream, indent, "<" #fld ">%#4x</" #fld ">\n", (strct)->fld)
+
+#define DEF_DUMP(type) static void Dump_ ## type (TTO_ ## type *type, FILE *stream, int indent, FT_Bool is_gsub)
+#define RECURSE(name, type, val) do {  DUMP ("<" #name ">\n"); Dump_ ## type (val, stream, indent + 1, is_gsub); DUMP ("</" #name ">\n"); } while (0)
+
+static void
+do_indent (FILE *stream, int indent)
+{
+  int i;
+
+  for (i = 0; i < indent * 3; i++)
+    fputc (' ', stream);
+}
+
+static void 
+dump (FILE *stream, int indent, const char *format, ...) 
+{
+  va_list list;
+  
+  do_indent (stream, indent);
+  
+  va_start (list, format);
+  vfprintf (stream, format, list);
+  va_end (list);
+}
+
+static void 
+Print_Tag (FT_ULong tag, FILE *stream)
+{
+  fprintf (stream, "%c%c%c%c", 
+	   (unsigned char)(tag >> 24),
+	   (unsigned char)((tag & 0xff0000) >> 16),
+	   (unsigned char)((tag & 0xff00) >> 8),
+	   (unsigned char)(tag & 0xff));
+}
+
+DEF_DUMP (LangSys)
+{
+  int i;
+
+  DUMP_FUINT (LangSys, LookupOrderOffset);
+  DUMP_FUINT (LangSys, ReqFeatureIndex);
+  DUMP_FUINT (LangSys, FeatureCount);
+
+  for (i=0; i < LangSys->FeatureCount; i++)
+    DUMP("<FeatureIndex>%d</FeatureIndex>\n", LangSys->FeatureIndex[i]);
+}
+
+DEF_DUMP (Script)
+{
+  int i;
+
+  RECURSE (DefaultLangSys, LangSys, &Script->DefaultLangSys);
+
+  DUMP_FUINT (Script, LangSysCount);
+
+  for (i=0; i < Script->LangSysCount; i++)
+    {
+      do_indent (stream, indent);
+      fprintf (stream, "<LangSysTag>");
+      Print_Tag (Script->LangSysRecord[i].LangSysTag, stream);
+      fprintf (stream, "</LangSysTag>\n");
+      RECURSE (LangSys, LangSys, &Script->LangSysRecord[i].LangSys);
+    }
+}
+
+DEF_DUMP (ScriptList)
+{
+  int i;
+  
+  DUMP_FUINT (ScriptList, ScriptCount);
+
+  for (i=0; i < ScriptList->ScriptCount; i++)
+    { 
+      do_indent (stream, indent);
+      fprintf (stream, "<ScriptTag>");
+      Print_Tag (ScriptList->ScriptRecord[i].ScriptTag, stream);
+      fprintf (stream, "</ScriptTag>\n");
+      RECURSE (Script, Script, &ScriptList->ScriptRecord[i].Script);
+    }
+}
+
+DEF_DUMP (Feature)
+{
+  int i;
+  
+  DUMP_FUINT (Feature, FeatureParams);
+  DUMP_FUINT (Feature, LookupListCount);
+
+  for (i=0; i < Feature->LookupListCount; i++)
+    DUMP("<LookupIndex>%d</LookupIndex>\n", Feature->LookupListIndex[i]);
+}
+
+DEF_DUMP (FeatureList)
+{
+  int i;
+  
+  DUMP_FUINT (FeatureList, FeatureCount);
+
+  for (i=0; i < FeatureList->FeatureCount; i++)
+    { 
+      do_indent (stream, indent);
+      fprintf (stream, "<FeatureTag>");
+      Print_Tag (FeatureList->FeatureRecord[i].FeatureTag, stream);
+      fprintf (stream, "</FeatureTag>\n");
+      RECURSE (Feature, Feature, &FeatureList->FeatureRecord[i].Feature);
+    }
+}
+
+DEF_DUMP (Coverage)
+{
+  DUMP_FUINT (Coverage, CoverageFormat);
+
+  if (Coverage->CoverageFormat == 1)
+    {
+      int i;
+      DUMP_FUINT (&Coverage->cf.cf1, GlyphCount);
+
+      for (i = 0; i < Coverage->cf.cf1.GlyphCount; i++)
+	DUMP("<Glyph>%#4x</Glyph> <!-- %d -->\n", Coverage->cf.cf1.GlyphArray[i], i);
+    }
+  else
+    {
+    }
+}
+
+static void
+Dump_GSUB_Lookup_Single (TTO_SubTable *subtable, FILE *stream, int indent, FT_Bool is_gsub)
+{
+  TTO_SingleSubst *SingleSubst = &subtable->st.gsub.single;
+
+  DUMP_FUINT (SingleSubst, SubstFormat);
+  RECURSE (Coverage, Coverage, &SingleSubst->Coverage);
+
+  if (SingleSubst->SubstFormat == 1)
+    {
+      DUMP_FINT (&SingleSubst->ssf.ssf1, DeltaGlyphID);
+    }
+  else
+    {
+      int i;
+      
+      DUMP_FINT (&SingleSubst->ssf.ssf2, GlyphCount);
+      for (i=0; i < SingleSubst->ssf.ssf2.GlyphCount; i++)
+	DUMP("<Substitute>%#4x</Substitute> <!-- %d -->\n", SingleSubst->ssf.ssf2.Substitute[i], i);
+    }
+}
+
+DEF_DUMP (Ligature)
+{
+  int i;
+  
+  DUMP_FGLYPH (Ligature, LigGlyph);
+  DUMP_FUINT (Ligature, ComponentCount);
+
+  for (i=0; i < Ligature->ComponentCount - 1; i++)
+    DUMP("<Component>%#4x</Component>\n", Ligature->Component[i]);
+}
+
+DEF_DUMP (LigatureSet)
+{
+  int i;
+  
+  DUMP_FUINT (LigatureSet, LigatureCount);
+
+  for (i=0; i < LigatureSet->LigatureCount; i++)
+    RECURSE (Ligature, Ligature, &LigatureSet->Ligature[i]);
+}
+
+static void
+Dump_GSUB_Lookup_Ligature (TTO_SubTable *subtable, FILE *stream, int indent, FT_Bool is_gsub)
+{
+  int i;
+  TTO_LigatureSubst *LigatureSubst = &subtable->st.gsub.ligature;
+
+  DUMP_FUINT (LigatureSubst, SubstFormat);
+  RECURSE (Coverage, Coverage, &LigatureSubst->Coverage);
+
+  DUMP_FUINT (LigatureSubst, LigatureSetCount);
+
+  for (i=0; i < LigatureSubst->LigatureSetCount; i++)
+    RECURSE (LigatureSet, LigatureSet, &LigatureSubst->LigatureSet[i]);
+}
+
+DEF_DUMP (Lookup)
+{
+  int i;
+  const char *lookup_name = NULL;
+  void (*lookup_func) (TTO_SubTable *subtable, FILE *stream, int indent, FT_Bool is_gsub) = NULL;
+
+  if (is_gsub)
+    {
+      switch (Lookup->LookupType)
+	{
+	case  GSUB_LOOKUP_SINGLE:
+	  lookup_name = "SINGLE";
+	  lookup_func = Dump_GSUB_Lookup_Single;
+	  break;
+	case  GSUB_LOOKUP_MULTIPLE:
+	  lookup_name = "MULTIPLE";
+	  break;
+	case  GSUB_LOOKUP_ALTERNATE:
+	  lookup_name = "ALTERNATE";
+	  break;
+	case  GSUB_LOOKUP_LIGATURE:
+	  lookup_name = "LIGATURE";
+	  lookup_func = Dump_GSUB_Lookup_Ligature;
+	  break;
+	case  GSUB_LOOKUP_CONTEXT:
+	  lookup_name = "CONTEXT";
+	  break;
+	case  GSUB_LOOKUP_CHAIN:
+	  lookup_name = "CHAIN";
+	  break;
+	}
+    }
+  else
+    {
+      switch (Lookup->LookupType)
+	{
+	case GPOS_LOOKUP_SINGLE:
+	  lookup_name = "SINGLE";
+	  break;
+	case GPOS_LOOKUP_PAIR:
+	  lookup_name = "PAIR";
+	  break;
+	case GPOS_LOOKUP_CURSIVE:
+	  lookup_name = "CURSIVE";
+	  break;
+	case GPOS_LOOKUP_MARKBASE:
+	  lookup_name = "MARKBASE";
+	  break;
+	case GPOS_LOOKUP_MARKLIG:
+	  lookup_name = "MARKLIG";
+	  break;
+	case GPOS_LOOKUP_MARKMARK:
+	  lookup_name = "MARKMARK";
+	  break;
+	case GPOS_LOOKUP_CONTEXT:
+	  lookup_name = "CONTEXT";
+	  break;
+	case GPOS_LOOKUP_CHAIN:
+	  lookup_name = "CHAIN";
+	  break;
+	}
+    }
+
+  DUMP("<LookupType>%s</LookupType>\n", lookup_name);
+
+  for (i=0; i < Lookup->SubTableCount; i++)
+    {
+      DUMP ("<Subtable>\n");
+      if (lookup_func)
+	(*lookup_func) (&Lookup->SubTable[i], stream, indent + 1, is_gsub);
+      DUMP ("</Subtable>\n");
+    }
+}
+
+DEF_DUMP (LookupList)
+{
+  int i;
+
+  DUMP_FUINT (LookupList, LookupCount);
+
+  for (i=0; i < LookupList->LookupCount; i++)
+    RECURSE (Lookup, Lookup, &LookupList->Lookup[i]);
+}
+
+void
+TT_Dump_GSUB_Table (TTO_GSUB gsub, FILE *stream)
+{
+  int indent = 0;
+  FT_Bool is_gsub = 1;
+
+  RECURSE (ScriptList, ScriptList, &gsub->ScriptList);
+  RECURSE (FeatureList, FeatureList, &gsub->FeatureList);
+  RECURSE (LookupList, LookupList, &gsub->LookupList);
+}
+
+void
+TT_Dump_GPOS_Table (TTO_GPOS gpos, FILE *stream)
+{
+  int indent = 0;
+  FT_Bool is_gsub = 0;
+
+  RECURSE (ScriptList, ScriptList, &gpos->ScriptList);
+  RECURSE (FeatureList, FeatureList, &gpos->FeatureList);
+  RECURSE (LookupList, LookupList, &gpos->LookupList);
+}
diff --git a/src/disasm.h b/src/disasm.h
new file mode 100644
index 0000000..e7556d4
--- /dev/null
+++ b/src/disasm.h
@@ -0,0 +1,26 @@
+/* Pango
+ * disasm.h: Dump OpenType layout tables
+ *
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <stdio.h>
+#include "ftxopen.h"
+
+void TT_Dump_GSUB_Table (TTO_GSUB gsub, FILE *stream);
+void TT_Dump_GPOS_Table (TTO_GPOS gpos, FILE *stream);
diff --git a/src/ftxgdef.c b/src/ftxgdef.c
new file mode 100644
index 0000000..dd1754c
--- /dev/null
+++ b/src/ftxgdef.c
@@ -0,0 +1,1155 @@
+/*******************************************************************
+ *
+ *  ftxgdef.c
+ *
+ *    TrueType Open GDEF table support.
+ *
+ *  Copyright 1996-2000 by
+ *  David Turner, Robert Wilhelm, and Werner Lemberg.
+ *
+ *  This file is part of the FreeType project, and may only be used
+ *  modified and distributed under the terms of the FreeType project
+ *  license, LICENSE.TXT.  By continuing to use, modify, or distribute
+ *  this file you indicate that you have read the license and
+ *  understand and accept it fully.
+ *
+ ******************************************************************/
+
+#include <freetype/tttags.h>
+
+#include <freetype/internal/ftstream.h>
+#include <freetype/internal/ftmemory.h>
+#include <freetype/internal/tterrors.h>
+#include <freetype/internal/tttypes.h>
+
+#include "ftxopen.h"
+#include "ftxopenf.h"
+
+#define TTAG_GDEF  FT_MAKE_TAG( 'G', 'D', 'E', 'F' )
+
+  static FT_Error  Load_AttachList( TTO_AttachList*  al,
+                                    FT_Stream        stream );
+  static FT_Error  Load_LigCaretList( TTO_LigCaretList*  lcl,
+                                      FT_Stream          stream );
+
+  static void  Free_AttachList( TTO_AttachList*  al,
+				FT_Memory        memory );
+  static void  Free_LigCaretList( TTO_LigCaretList*  lcl,
+				  FT_Memory          memory );
+
+  static void  Free_NewGlyphClasses( TTO_GDEFHeader*  gdef,
+				     FT_Memory        memory );
+
+
+
+  /**********************
+   * Extension Functions
+   **********************/
+
+#if 0
+#define GDEF_ID  Build_Extension_ID( 'G', 'D', 'E', 'F' )
+
+
+  static FT_Error  GDEF_Create( void*  ext,
+                                PFace  face )
+  {
+    DEFINE_LOAD_LOCALS( face->stream );
+
+    TTO_GDEFHeader*  gdef = (TTO_GDEFHeader*)ext;
+    Long             table;
+
+
+    /* by convention */
+
+    if ( !gdef )
+      return TT_Err_Ok;
+
+    /* a null offset indicates that there is no GDEF table */
+
+    gdef->offset = 0;
+
+    /* we store the start offset and the size of the subtable */
+
+    table = TT_LookUp_Table( face, TTAG_GDEF );
+    if ( table < 0 )
+      return TT_Err_Ok;             /* The table is optional */
+
+    if ( FILE_Seek( face->dirTables[table].Offset ) ||
+         ACCESS_Frame( 4L ) )
+      return error;
+
+    gdef->offset  = FILE_Pos() - 4L;    /* undo ACCESS_Frame() */
+    gdef->Version = GET_ULong();
+
+    FORGET_Frame();
+
+    gdef->loaded = FALSE;
+
+    return TT_Err_Ok;
+  }
+
+
+  static FT_Error  GDEF_Destroy( void*  ext,
+                                 PFace  face )
+  {
+    TTO_GDEFHeader*  gdef = (TTO_GDEFHeader*)ext;
+
+
+    /* by convention */
+
+    if ( !gdef )
+      return TT_Err_Ok;
+
+    if ( gdef->loaded )
+    {
+      Free_LigCaretList( &gdef->LigCaretList, memory );
+      Free_AttachList( &gdef->AttachList, memory );
+      Free_ClassDefinition( &gdef->GlyphClassDef, memory );
+      Free_ClassDefinition( &gdef->MarkAttachClassDef, memory );
+
+      Free_NewGlyphClasses( gdef, memory );
+    }
+
+    return TT_Err_Ok;
+  }
+
+
+  EXPORT_FUNC
+  FT_Error  TT_Init_GDEF_Extension( TT_Engine  engine )
+  {
+    PEngine_Instance  _engine = HANDLE_Engine( engine );
+
+
+    if ( !_engine )
+      return TT_Err_Invalid_Engine;
+
+    return  TT_Register_Extension( _engine,
+                                   GDEF_ID,
+                                   sizeof ( TTO_GDEFHeader ),
+                                   GDEF_Create,
+                                   GDEF_Destroy );
+  }
+#endif
+
+  EXPORT_FUNC
+  FT_Error  TT_Load_GDEF_Table( FT_Face          face,
+                                TTO_GDEFHeader** retptr )
+  {
+    FT_Error         error;
+    FT_Memory        memory = face->memory;
+    FT_Stream        stream = face->stream;
+    TT_Face          tt_face = (TT_Face)face;
+    FT_ULong         cur_offset, new_offset, base_offset;
+
+    TTO_GDEFHeader*  gdef;
+
+
+    if ( !retptr )
+      return TT_Err_Invalid_Argument;
+
+    if (( error = tt_face->goto_table( tt_face, TTAG_GDEF, stream, 0 ) ))
+      return error;
+
+    if ( ALLOC( gdef, sizeof( *gdef ) ) )
+      return error;
+
+    gdef->memory = face->memory;
+
+    base_offset = FILE_Pos();
+
+    /* skip version */
+
+    if ( FILE_Seek( base_offset + 4L ) ||
+         ACCESS_Frame( 2L ) )
+      goto Fail0;
+
+    new_offset = GET_UShort();
+
+    FORGET_Frame();
+
+    /* all GDEF subtables are optional */
+
+    if ( new_offset )
+    {
+      new_offset += base_offset;
+
+      /* only classes 1-4 are allowed here */
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_ClassDefinition( &gdef->GlyphClassDef, 5,
+                                           stream ) ) != TT_Err_Ok )
+        goto Fail0;
+      (void)FILE_Seek( cur_offset );
+    }
+    else
+      gdef->GlyphClassDef.loaded = FALSE;
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail0;
+
+    new_offset = GET_UShort();
+
+    FORGET_Frame();
+
+    if ( new_offset )
+    {
+      new_offset += base_offset;
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_AttachList( &gdef->AttachList,
+                                      stream ) ) != TT_Err_Ok )
+        goto Fail1;
+      (void)FILE_Seek( cur_offset );
+    }
+    else
+      gdef->AttachList.loaded = FALSE;
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    new_offset = GET_UShort();
+
+    FORGET_Frame();
+
+    if ( new_offset )
+    {
+      new_offset += base_offset;
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_LigCaretList( &gdef->LigCaretList,
+                                        stream ) ) != TT_Err_Ok )
+        goto Fail2;
+      (void)FILE_Seek( cur_offset );
+    }
+    else
+      gdef->LigCaretList.loaded = FALSE;
+
+    /* OpenType 1.2 has introduced the `MarkAttachClassDef' field.  We
+       first have to scan the LookupFlag values to find out whether we
+       must load it or not.  Here we only store the current file offset. */
+
+    gdef->MarkAttachClassDef_offset = FILE_Pos();
+    gdef->MarkAttachClassDef.loaded = FALSE;
+
+    gdef->LastGlyph       = 0;
+    gdef->NewGlyphClasses = NULL;
+
+    *retptr = gdef;
+    DONE_Stream( stream );
+
+    return TT_Err_Ok;
+
+  Fail2:
+    Free_AttachList( &gdef->AttachList, memory );
+
+  Fail1:
+    Free_ClassDefinition( &gdef->GlyphClassDef, memory );
+
+  Fail0:
+    FREE( gdef );
+
+    return error;
+  }
+
+  EXPORT_FUNC
+  FT_Error  TT_Done_GDEF_Table ( TTO_GDEFHeader* gdef ) 
+  {
+    FT_Memory memory = gdef->memory;
+    
+    Free_LigCaretList( &gdef->LigCaretList, memory );
+    Free_AttachList( &gdef->AttachList, memory );
+    Free_ClassDefinition( &gdef->GlyphClassDef, memory );
+    Free_ClassDefinition( &gdef->MarkAttachClassDef, memory );
+    
+    Free_NewGlyphClasses( gdef, memory );
+
+    return TT_Err_Ok;
+  }
+
+
+
+
+  /*******************************
+   * AttachList related functions
+   *******************************/
+
+
+  /* AttachPoint */
+
+  static FT_Error  Load_AttachPoint( TTO_AttachPoint*  ap,
+                                     FT_Stream         stream )
+  {
+    FT_Memory memory = stream->memory;
+    FT_Error  error;
+
+    FT_UShort   n, count;
+    FT_UShort*  pi;
+
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = ap->PointCount = GET_UShort();
+
+    FORGET_Frame();
+
+    ap->PointIndex = NULL;
+
+    if ( count )
+    {
+      if ( ALLOC_ARRAY( ap->PointIndex, count, FT_UShort ) )
+        return error;
+
+      pi = ap->PointIndex;
+
+      if ( ACCESS_Frame( count * 2L ) )
+      {
+        FREE( pi );
+        return error;
+      }
+
+      for ( n = 0; n < count; n++ )
+        pi[n] = GET_UShort();
+
+      FORGET_Frame();
+    }
+
+    return TT_Err_Ok;
+  }
+
+
+  static void  Free_AttachPoint( TTO_AttachPoint*  ap,
+				 FT_Memory        memory )
+  {
+    FREE( ap->PointIndex );
+  }
+
+
+  /* AttachList */
+
+  static FT_Error  Load_AttachList( TTO_AttachList*  al,
+                                    FT_Stream        stream )
+  {
+    FT_Memory memory = stream->memory;
+    FT_Error  error;
+
+    FT_UShort         n, count;
+    FT_ULong          cur_offset, new_offset, base_offset;
+
+    TTO_AttachPoint*  ap;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &al->Coverage, stream ) ) != TT_Err_Ok )
+      return error;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail2;
+
+    count = al->GlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    al->AttachPoint = NULL;
+
+    if ( ALLOC_ARRAY( al->AttachPoint, count, TTO_AttachPoint ) )
+      goto Fail2;
+
+    ap = al->AttachPoint;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail1;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_AttachPoint( &ap[n], stream ) ) != TT_Err_Ok )
+        goto Fail1;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    al->loaded = TRUE;
+
+    return TT_Err_Ok;
+
+  Fail1:
+    for ( n = 0; n < count; n++ )
+      Free_AttachPoint( &ap[n], memory );
+
+    FREE( ap );
+
+  Fail2:
+    Free_Coverage( &al->Coverage, memory );
+    return error;
+  }
+
+
+  static void  Free_AttachList( TTO_AttachList*  al,
+				FT_Memory        memory )
+  {
+    FT_UShort         n, count;
+
+    TTO_AttachPoint*  ap;
+
+
+    if ( !al->loaded )
+      return;
+
+    if ( al->AttachPoint )
+    {
+      count = al->GlyphCount;
+      ap    = al->AttachPoint;
+
+      for ( n = 0; n < count; n++ )
+        Free_AttachPoint( &ap[n], memory );
+
+      FREE( ap );
+    }
+
+    Free_Coverage( &al->Coverage, memory );
+  }
+
+
+
+  /*********************************
+   * LigCaretList related functions
+   *********************************/
+
+
+  /* CaretValueFormat1 */
+  /* CaretValueFormat2 */
+  /* CaretValueFormat3 */
+  /* CaretValueFormat4 */
+
+  static FT_Error  Load_CaretValue( TTO_CaretValue*  cv,
+                                    FT_Stream        stream )
+  {
+    FT_Error  error;
+
+    FT_ULong cur_offset, new_offset, base_offset;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    cv->CaretValueFormat = GET_UShort();
+
+    FORGET_Frame();
+
+    switch ( cv->CaretValueFormat )
+    {
+    case 1:
+      if ( ACCESS_Frame( 2L ) )
+        return error;
+
+      cv->cvf.cvf1.Coordinate = GET_Short();
+
+      FORGET_Frame();
+
+      break;
+
+    case 2:
+      if ( ACCESS_Frame( 2L ) )
+        return error;
+
+      cv->cvf.cvf2.CaretValuePoint = GET_UShort();
+
+      FORGET_Frame();
+
+      break;
+
+    case 3:
+      if ( ACCESS_Frame( 4L ) )
+        return error;
+
+      cv->cvf.cvf3.Coordinate = GET_Short();
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_Device( &cv->cvf.cvf3.Device,
+                                  stream ) ) != TT_Err_Ok )
+        return error;
+      (void)FILE_Seek( cur_offset );
+
+      break;
+
+    case 4:
+      if ( ACCESS_Frame( 2L ) )
+        return error;
+
+      cv->cvf.cvf4.IdCaretValue = GET_UShort();
+
+      FORGET_Frame();
+      break;
+
+    default:
+      return TTO_Err_Invalid_GDEF_SubTable_Format;
+    }
+
+    return TT_Err_Ok;
+  }
+
+
+  static void  Free_CaretValue( TTO_CaretValue*  cv,
+				FT_Memory        memory )
+  {
+    if ( cv->CaretValueFormat == 3 )
+      Free_Device( &cv->cvf.cvf3.Device, memory );
+  }
+
+
+  /* LigGlyph */
+
+  static FT_Error  Load_LigGlyph( TTO_LigGlyph*  lg,
+                                  FT_Stream      stream )
+  {
+    FT_Memory memory = stream->memory;
+    FT_Error  error;
+
+    FT_UShort        n, count;
+    FT_ULong         cur_offset, new_offset, base_offset;
+
+    TTO_CaretValue*  cv;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = lg->CaretCount = GET_UShort();
+
+    FORGET_Frame();
+
+    lg->CaretValue = NULL;
+
+    if ( ALLOC_ARRAY( lg->CaretValue, count, TTO_CaretValue ) )
+      return error;
+
+    cv = lg->CaretValue;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_CaretValue( &cv[n], stream ) ) != TT_Err_Ok )
+        goto Fail;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    for ( n = 0; n < count; n++ )
+      Free_CaretValue( &cv[n], memory );
+
+    FREE( cv );
+    return error;
+  }
+
+
+  static void  Free_LigGlyph( TTO_LigGlyph*  lg,
+			      FT_Memory      memory )
+  {
+    FT_UShort        n, count;
+
+    TTO_CaretValue*  cv;
+
+
+    if ( lg->CaretValue )
+    {
+      count = lg->CaretCount;
+      cv    = lg->CaretValue;
+
+      for ( n = 0; n < count; n++ )
+        Free_CaretValue( &cv[n], memory );
+
+      FREE( cv );
+    }
+  }
+
+
+  /* LigCaretList */
+
+  static FT_Error  Load_LigCaretList( TTO_LigCaretList*  lcl,
+                                      FT_Stream          stream )
+  {
+    FT_Memory memory = stream->memory;
+    FT_Error  error;
+
+    FT_UShort      n, count;
+    FT_ULong       cur_offset, new_offset, base_offset;
+
+    TTO_LigGlyph*  lg;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &lcl->Coverage, stream ) ) != TT_Err_Ok )
+      return error;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail2;
+
+    count = lcl->LigGlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    lcl->LigGlyph = NULL;
+
+    if ( ALLOC_ARRAY( lcl->LigGlyph, count, TTO_LigGlyph ) )
+      goto Fail2;
+
+    lg = lcl->LigGlyph;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail1;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_LigGlyph( &lg[n], stream ) ) != TT_Err_Ok )
+        goto Fail1;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    lcl->loaded = TRUE;
+
+    return TT_Err_Ok;
+
+  Fail1:
+    for ( n = 0; n < count; n++ )
+      Free_LigGlyph( &lg[n], memory );
+
+    FREE( lg );
+
+  Fail2:
+    Free_Coverage( &lcl->Coverage, memory );
+    return error;
+  }
+
+
+  static void  Free_LigCaretList( TTO_LigCaretList*  lcl,
+				  FT_Memory           memory )
+  {
+    FT_UShort      n, count;
+
+    TTO_LigGlyph*  lg;
+
+
+    if ( !lcl->loaded )
+      return;
+
+    if ( lcl->LigGlyph )
+    {
+      count = lcl->LigGlyphCount;
+      lg    = lcl->LigGlyph;
+
+      for ( n = 0; n < count; n++ )
+        Free_LigGlyph( &lg[n], memory );
+
+      FREE( lg );
+    }
+
+    Free_Coverage( &lcl->Coverage, memory );
+  }
+
+
+
+  /***********
+   * GDEF API
+   ***********/
+
+
+  static FT_UShort  Get_New_Class( TTO_GDEFHeader*  gdef,
+				   FT_UShort        glyphID,
+				   FT_UShort        index )
+  {
+    FT_UShort              glyph_index, array_index;
+    FT_UShort              byte, bits;
+    
+    TTO_ClassRangeRecord*  gcrr;
+    FT_UShort**            ngc;
+
+
+    if ( glyphID >= gdef->LastGlyph )
+      return 0;
+
+    gcrr = gdef->GlyphClassDef.cd.cd2.ClassRangeRecord;
+    ngc  = gdef->NewGlyphClasses;
+
+    if ( glyphID < gcrr[index].Start )
+    {
+      array_index = 0;
+      if ( index == 0 )
+        glyph_index = glyphID;
+      else
+        glyph_index = glyphID - gcrr[index - 1].End - 1;
+    }
+    else
+    {
+      array_index = index + 1;
+      glyph_index = glyphID - gcrr[index].End - 1;
+    }
+
+    byte = ngc[array_index][glyph_index / 4 + 1];
+    bits = byte >> ( 16 - ( glyph_index % 4 + 1 ) * 4 );
+
+    return bits & 0x000F;
+  }
+
+
+  EXPORT_FUNC
+  FT_Error  TT_GDEF_Get_Glyph_Property( TTO_GDEFHeader*  gdef,
+                                        FT_UShort        glyphID,
+                                        FT_UShort*       property )
+  {
+    FT_UShort class, index;
+
+    FT_Error  error;
+
+
+    if ( !gdef || !property )
+      return TT_Err_Invalid_Argument;
+
+    /* first, we check for mark attach classes */
+
+    if ( gdef->MarkAttachClassDef.loaded )
+    {
+      error = Get_Class( &gdef->MarkAttachClassDef, glyphID, &class, &index );
+      if ( error && error != TTO_Err_Not_Covered )
+        return error;
+      if ( !error )
+      {
+        *property = class << 8;
+        return TT_Err_Ok;
+      }
+    }
+
+    error = Get_Class( &gdef->GlyphClassDef, glyphID, &class, &index );
+    if ( error && error != TTO_Err_Not_Covered )
+      return error;
+
+    /* if we have a constructed class table, check whether additional
+       values have been assigned                                      */
+
+    if ( error == TTO_Err_Not_Covered && gdef->NewGlyphClasses )
+      class = Get_New_Class( gdef, glyphID, index );
+
+    switch ( class )
+    {
+    case UNCLASSIFIED_GLYPH:
+      *property = 0;
+      break;
+
+    case SIMPLE_GLYPH:
+      *property = TTO_BASE_GLYPH;
+      break;
+
+    case LIGATURE_GLYPH:
+      *property = TTO_LIGATURE;
+      break;
+
+    case MARK_GLYPH:
+      *property = TTO_MARK;
+      break;
+
+    case COMPONENT_GLYPH:
+      *property = TTO_COMPONENT;
+      break;
+    }
+
+    return TT_Err_Ok;
+  }
+
+
+  static FT_Error  Make_ClassRange( TTO_ClassDefinition*  cd,
+                                    FT_UShort             start,
+                                    FT_UShort             end,
+                                    FT_UShort             class,
+				    FT_Memory             memory )
+  {
+    FT_Error               error;
+    FT_UShort              index;
+
+    TTO_ClassDefFormat2*   cdf2;
+    TTO_ClassRangeRecord*  crr;
+
+
+    cdf2 = &cd->cd.cd2;
+
+    if ( REALLOC_ARRAY( cdf2->ClassRangeRecord,
+			cdf2->ClassRangeCount,
+			cdf2->ClassRangeCount + 1 ,
+                        TTO_ClassRangeRecord ) )
+      return error;
+
+    cdf2->ClassRangeCount++;
+
+    crr   = cdf2->ClassRangeRecord;
+    index = cdf2->ClassRangeCount - 1;
+
+    crr[index].Start = start;
+    crr[index].End   = end;
+    crr[index].Class = class;
+
+    cd->Defined[class] = TRUE;
+
+    return TT_Err_Ok;
+  }
+
+
+  EXPORT_FUNC
+  FT_Error  TT_GDEF_Build_ClassDefinition( TTO_GDEFHeader*  gdef,
+                                           FT_UShort        num_glyphs,
+                                           FT_UShort        glyph_count,
+                                           FT_UShort*       glyph_array,
+                                           FT_UShort*       class_array )
+  {
+    FT_UShort              start, curr_glyph, curr_class;
+    FT_UShort              n, count;
+    FT_Error               error;
+    FT_Memory              memory = gdef->memory;
+
+    TTO_ClassDefinition*   gcd;
+    TTO_ClassRangeRecord*  gcrr;
+    FT_UShort**            ngc;
+
+
+    if ( !gdef || !glyph_array || !class_array )
+      return TT_Err_Invalid_Argument;
+
+    gcd = &gdef->GlyphClassDef;
+
+    /* We build a format 2 table */
+
+    gcd->ClassFormat = 2;
+
+    /* A GlyphClassDef table contains at most 5 different class values */
+
+    if ( ALLOC_ARRAY( gcd->Defined, 5, FT_Bool ) )
+      return error;
+
+    gcd->cd.cd2.ClassRangeCount  = 0;
+    gcd->cd.cd2.ClassRangeRecord = NULL;
+
+    start      = glyph_array[0];
+    curr_class = class_array[0];
+    curr_glyph = start;
+
+    if ( curr_class >= 5 )
+    {
+      error = TT_Err_Invalid_Argument;
+      goto Fail4;
+    }
+
+    glyph_count--;
+
+    for ( n = 0; n <= glyph_count; n++ )
+    {
+      if ( curr_glyph == glyph_array[n] && curr_class == class_array[n] )
+      {
+        if ( n == glyph_count )
+        {
+          if ( ( error = Make_ClassRange( gcd, start,
+                                          curr_glyph,
+                                          curr_class,
+					  memory ) ) != TT_Err_Ok )
+            goto Fail3;
+        }
+        else
+        {
+          if ( curr_glyph == 0xFFFF )
+          {
+            error = TT_Err_Invalid_Argument;
+            goto Fail3;
+          }
+          else
+            curr_glyph++;
+        }
+      }
+      else
+      {
+        if ( ( error = Make_ClassRange( gcd, start,
+                                        curr_glyph - 1,
+                                        curr_class,
+					memory ) ) != TT_Err_Ok )
+          goto Fail3;
+
+        if ( curr_glyph > glyph_array[n] )
+        {
+          error = TT_Err_Invalid_Argument;
+          goto Fail3;
+        }
+
+        start      = glyph_array[n];
+        curr_class = class_array[n];
+        curr_glyph = start;
+
+        if ( curr_class >= 5 )
+        {
+          error = TT_Err_Invalid_Argument;
+          goto Fail3;
+        }
+
+        if ( n == glyph_count )
+        {
+          if ( ( error = Make_ClassRange( gcd, start,
+                                          curr_glyph,
+                                          curr_class,
+					  memory ) ) != TT_Err_Ok )
+            goto Fail3;
+        }
+        else
+        {
+          if ( curr_glyph == 0xFFFF )
+          {
+            error = TT_Err_Invalid_Argument;
+            goto Fail3;
+          }
+          else
+            curr_glyph++;
+        }
+      }
+    }
+
+    /* now prepare the arrays for class values assigned during the lookup
+       process                                                            */
+
+    if ( ALLOC_ARRAY( gdef->NewGlyphClasses,
+                      gcd->cd.cd2.ClassRangeCount + 1, FT_UShort* ) )
+      goto Fail2;
+
+    count = gcd->cd.cd2.ClassRangeCount;
+    gcrr  = gcd->cd.cd2.ClassRangeRecord;
+    ngc   = gdef->NewGlyphClasses;
+
+    /* We allocate arrays for all glyphs not covered by the class range
+       records.  Each element holds four class values.                  */
+
+    if ( gcrr[0].Start )
+    {
+      if ( ALLOC_ARRAY( ngc[0], gcrr[0].Start / 4 + 1, FT_UShort ) )
+        goto Fail1;
+    }
+
+    for ( n = 1; n < count; n++ )
+    {
+      if ( gcrr[n].Start - gcrr[n - 1].End > 1 )
+        if ( ALLOC_ARRAY( ngc[n],
+                          ( gcrr[n].Start - gcrr[n - 1].End - 1 ) / 4 + 1,
+                          FT_UShort ) )
+          goto Fail1;
+    }
+
+    if ( gcrr[count - 1].End != num_glyphs - 1 )
+    {
+      if ( ALLOC_ARRAY( ngc[count],
+                        ( num_glyphs - gcrr[count - 1].End - 1 ) / 4 + 1,
+                        FT_UShort ) )
+        goto Fail1;
+    }
+
+    gdef->LastGlyph = num_glyphs - 1;
+
+    gdef->MarkAttachClassDef_offset = 0L;
+    gdef->MarkAttachClassDef.loaded = FALSE;
+
+    return TT_Err_Ok;
+
+  Fail1:
+    for ( n = 0; n < count; n++ )
+      FREE( ngc[n] );
+
+  Fail2:
+    FREE( gdef->NewGlyphClasses );
+
+  Fail3:
+    FREE( gcd->cd.cd2.ClassRangeRecord );
+
+  Fail4:
+    FREE( gcd->Defined );
+    return error;
+  }
+
+
+  static void  Free_NewGlyphClasses( TTO_GDEFHeader*  gdef,
+				     FT_Memory        memory )
+  {
+    FT_UShort**  ngc;
+    FT_UShort    n, count;
+
+
+    if ( gdef->NewGlyphClasses )
+    {
+      count = gdef->GlyphClassDef.cd.cd2.ClassRangeCount + 1;
+      ngc   = gdef->NewGlyphClasses;
+
+      for ( n = 0; n < count; n++ )
+        FREE( ngc[n] );
+
+      FREE( ngc );
+    }
+  }
+
+
+  FT_Error  Add_Glyph_Property( TTO_GDEFHeader*  gdef,
+                                FT_UShort        glyphID,
+                                FT_UShort        property )
+  {
+    FT_Error               error;
+    FT_UShort              class, new_class, index;
+    FT_UShort              byte, bits, mask;
+    FT_UShort              array_index, glyph_index;
+
+    TTO_ClassRangeRecord*  gcrr;
+    FT_UShort**            ngc;
+
+
+    error = Get_Class( &gdef->GlyphClassDef, glyphID, &class, &index );
+    if ( error && error != TTO_Err_Not_Covered )
+      return error;
+
+    /* we don't accept glyphs covered in `GlyphClassDef' */
+
+    if ( !error )
+      return TTO_Err_Not_Covered;
+
+    switch ( property )
+    {
+    case 0:
+      new_class = UNCLASSIFIED_GLYPH;
+      break;
+
+    case TTO_BASE_GLYPH:
+      new_class = SIMPLE_GLYPH;
+      break;
+
+    case TTO_LIGATURE:
+      new_class = LIGATURE_GLYPH;
+      break;
+
+    case TTO_MARK:
+      new_class = MARK_GLYPH;
+      break;
+
+    case TTO_COMPONENT:
+      new_class = COMPONENT_GLYPH;
+      break;
+
+    default:
+      return TT_Err_Invalid_Argument;
+    }
+
+    gcrr = gdef->GlyphClassDef.cd.cd2.ClassRangeRecord;
+    ngc  = gdef->NewGlyphClasses;
+
+    if ( glyphID < gcrr[index].Start )
+    {
+      array_index = 0;
+      if ( index == 0 )
+        glyph_index = glyphID;
+      else
+        glyph_index = glyphID - gcrr[index - 1].End - 1;
+    }
+    else
+    {
+      array_index = index + 1;
+      glyph_index = glyphID - gcrr[index].End - 1;
+    }
+
+    byte  = ngc[array_index][glyph_index / 4 + 1];
+    bits  = byte >> ( 16 - ( glyph_index % 4 + 1 ) * 4 );
+    class = bits & 0x000F;
+
+    /* we don't overwrite existing entries */
+
+    if ( !class )
+    {
+      bits = new_class << ( 16 - ( glyph_index % 4 + 1 ) * 4 );
+      mask = ~( 0x000F << ( 16 - ( glyph_index % 4 + 1 ) * 4 ) );
+
+      ngc[array_index][glyph_index / 4 + 1] &= mask;
+      ngc[array_index][glyph_index / 4 + 1] |= bits;
+    }
+
+    return TT_Err_Ok;
+  }
+
+
+  FT_Error  Check_Property( TTO_GDEFHeader*  gdef,
+                            FT_UShort        index,
+                            FT_UShort        flags,
+                            FT_UShort*       property )
+  {
+    FT_Error  error;
+
+
+    if ( gdef )
+    {
+      error = TT_GDEF_Get_Glyph_Property( gdef, index, property );
+      if ( error )
+        return error;
+
+      /* This is OpenType 1.2 */
+
+      if ( flags & IGNORE_SPECIAL_MARKS )
+        if ( (flags & 0xFF00) != *property )
+          return TTO_Err_Not_Covered;
+
+      if ( flags & *property )
+        return TTO_Err_Not_Covered;
+    }
+
+    return TT_Err_Ok;
+  }
+
+
+/* END */
diff --git a/src/ftxgdef.h b/src/ftxgdef.h
new file mode 100644
index 0000000..6395bf3
--- /dev/null
+++ b/src/ftxgdef.h
@@ -0,0 +1,220 @@
+/*******************************************************************
+ *
+ *  ftxgdef.h
+ *
+ *    TrueType Open GDEF table support
+ *
+ *  Copyright 1996-2000 by
+ *  David Turner, Robert Wilhelm, and Werner Lemberg.
+ *
+ *  This file is part of the FreeType project, and may only be used
+ *  modified and distributed under the terms of the FreeType project
+ *  license, LICENSE.TXT.  By continuing to use, modify, or distribute
+ *  this file you indicate that you have read the license and
+ *  understand and accept it fully.
+ *
+ ******************************************************************/
+
+#ifndef FTXOPEN_H
+#error "Don't include this file! Use ftxopen.h instead."
+#endif
+
+#ifndef FTXGDEF_H
+#define FTXGDEF_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define TTO_Err_Invalid_GDEF_SubTable_Format  0x1030
+#define TTO_Err_Invalid_GDEF_SubTable         0x1031
+
+
+/* GDEF glyph classes */
+
+#define UNCLASSIFIED_GLYPH  0
+#define SIMPLE_GLYPH        1
+#define LIGATURE_GLYPH      2
+#define MARK_GLYPH          3
+#define COMPONENT_GLYPH     4
+
+/* GDEF glyph properties, corresponding to class values 1-4.  Note that
+   TTO_COMPONENT has no corresponding flag in the LookupFlag field.     */
+
+#define TTO_BASE_GLYPH  0x0002
+#define TTO_LIGATURE    0x0004
+#define TTO_MARK        0x0008
+#define TTO_COMPONENT   0x0010
+
+
+  /* Attachment related structures */
+
+  struct  TTO_AttachPoint_
+  {
+    FT_UShort   PointCount;             /* size of the PointIndex array */
+    FT_UShort*  PointIndex;             /* array of contour points      */
+  };
+
+  typedef struct TTO_AttachPoint_  TTO_AttachPoint;
+
+
+  struct  TTO_AttachList_
+  {
+    FT_Bool           loaded;
+
+    TTO_Coverage      Coverage;         /* Coverage table              */
+    FT_UShort         GlyphCount;       /* number of glyphs with
+                                           attachments                 */
+    TTO_AttachPoint*  AttachPoint;      /* array of AttachPoint tables */
+  };
+
+  typedef struct TTO_AttachList_  TTO_AttachList;
+
+
+  /* Ligature Caret related structures */
+
+  struct  TTO_CaretValueFormat1_
+  {
+    FT_Short  Coordinate;               /* x or y value (in design units) */
+  };
+
+  typedef struct TTO_CaretValueFormat1_  TTO_CaretValueFormat1;
+
+
+  struct  TTO_CaretValueFormat2_
+  {
+    FT_UShort  CaretValuePoint;         /* contour point index on glyph */
+  };
+
+  typedef struct TTO_CaretValueFormat2_  TTO_CaretValueFormat2;
+
+
+  struct  TTO_CaretValueFormat3_
+  {
+    FT_Short    Coordinate;             /* x or y value (in design units) */
+    TTO_Device  Device;                 /* Device table for x or y value  */
+  };
+
+  typedef struct TTO_CaretValueFormat3_  TTO_CaretValueFormat3;
+
+
+  struct  TTO_CaretValueFormat4_
+  {
+    FT_UShort  IdCaretValue;            /* metric ID */
+  };
+
+  typedef struct TTO_CaretValueFormat4_  TTO_CaretValueFormat4;
+
+
+  struct  TTO_CaretValue_
+  {
+    FT_UShort  CaretValueFormat;        /* 1, 2, 3, or 4 */
+
+    union
+    {
+      TTO_CaretValueFormat1  cvf1;
+      TTO_CaretValueFormat2  cvf2;
+      TTO_CaretValueFormat3  cvf3;
+      TTO_CaretValueFormat4  cvf4;
+    } cvf;
+  };
+
+  typedef struct TTO_CaretValue_  TTO_CaretValue;
+
+
+  struct  TTO_LigGlyph_
+  {
+    FT_Bool          loaded;
+
+    FT_UShort        CaretCount;        /* number of caret values */
+    TTO_CaretValue*  CaretValue;        /* array of caret values  */
+  };
+
+  typedef struct TTO_LigGlyph_  TTO_LigGlyph;
+
+
+  struct  TTO_LigCaretList_
+  {
+    FT_Bool        loaded;
+
+    TTO_Coverage   Coverage;            /* Coverage table            */
+    FT_UShort      LigGlyphCount;       /* number of ligature glyphs */
+    TTO_LigGlyph*  LigGlyph;            /* array of LigGlyph tables  */
+  };
+
+  typedef struct TTO_LigCaretList_  TTO_LigCaretList;
+
+
+  /* The `NewGlyphClasses' field is not defined in the TTO specification.
+     We use it for fonts with a constructed `GlyphClassDef' structure
+     (i.e., which don't have a GDEF table) to collect glyph classes
+     assigned during the lookup process.  The number of arrays in this
+     pointer array is GlyphClassDef->cd.cd2.ClassRangeCount+1; the nth
+     array then contains the glyph class values of the glyphs not covered
+     by the ClassRangeRecords structures with index n-1 and n.  We store
+     glyph class values for four glyphs in a single array element.
+
+     `LastGlyph' is identical to the number of glyphs minus one in the
+     font; we need it only if `NewGlyphClasses' is not NULL (to have an
+     upper bound for the last array).
+
+     Note that we first store the file offset to the `MarkAttachClassDef'
+     field (which has been introduced in OpenType 1.2) -- since the
+     `Version' field value hasn't been increased to indicate that we have
+     one more field for some obscure reason, we must parse the GSUB table
+     to find out whether class values refer to this table.  Only then we
+     can finally load the MarkAttachClassDef structure if necessary.      */
+
+  struct  TTO_GDEFHeader_
+  {
+    FT_Memory            memory;
+    FT_ULong             offset;
+
+    FT_Fixed             Version;
+
+    TTO_ClassDefinition  GlyphClassDef;
+    TTO_AttachList       AttachList;
+    TTO_LigCaretList     LigCaretList;
+    FT_ULong             MarkAttachClassDef_offset;
+    TTO_ClassDefinition  MarkAttachClassDef;        /* new in OT 1.2 */
+
+    FT_UShort            LastGlyph;
+    FT_UShort**          NewGlyphClasses;
+  };
+
+  typedef struct TTO_GDEFHeader_   TTO_GDEFHeader;
+  typedef struct TTO_GDEFHeader_*  TTO_GDEF;
+
+
+  /* finally, the GDEF API */
+
+  /*  EXPORT_DEF
+      FT_Error  TT_Init_GDEF_Extension( TT_Engine  engine ); */
+
+  EXPORT_DEF
+  FT_Error  TT_Load_GDEF_Table( FT_Face          face,
+                                TTO_GDEFHeader** gdef );
+
+  EXPORT_DEF
+  FT_Error  TT_Done_GDEF_Table ( TTO_GDEFHeader* gdef );
+
+  EXPORT_DEF
+  FT_Error  TT_GDEF_Get_Glyph_Property( TTO_GDEFHeader*  gdef,
+                                        FT_UShort        glyphID,
+                                        FT_UShort*       property );
+  EXPORT_DEF
+  FT_Error  TT_GDEF_Build_ClassDefinition( TTO_GDEFHeader*  gdef,
+                                           FT_UShort        num_glyphs,
+                                           FT_UShort        glyph_count,
+                                           FT_UShort*       glyph_array,
+                                           FT_UShort*       class_array );
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FTXGDEF_H */
+
+
+/* END */
diff --git a/src/ftxgpos.c b/src/ftxgpos.c
new file mode 100644
index 0000000..48d5009
--- /dev/null
+++ b/src/ftxgpos.c
@@ -0,0 +1,6222 @@
+/*******************************************************************
+ *
+ *  ftxgpos.c
+ *
+ *    TrueType Open GPOS table support.
+ *
+ *  Copyright 1996-2000 by
+ *  David Turner, Robert Wilhelm, and Werner Lemberg.
+ *
+ *  This file is part of the FreeType project, and may only be used
+ *  modified and distributed under the terms of the FreeType project
+ *  license, LICENSE.TXT.  By continuing to use, modify, or distribute
+ *  this file you indicate that you have read the license and
+ *  understand and accept it fully.
+ *
+ ******************************************************************/
+
+/* XXX There is *a lot* of duplicated code (cf. formats 7 and 8), but
+       I don't care currently.  I believe that it would be possible to
+       save about 50% of TTO code by carefully designing the structures,
+       sharing as much as possible with extensive use of macros.  This
+       is something for a volunteer :-)                                  */
+
+#define TTAG_GPOS  FT_MAKE_TAG( 'G', 'P', 'O', 'S' )
+
+#include <freetype/tttags.h>
+
+#include <freetype/internal/ftstream.h>
+#include <freetype/internal/ftmemory.h>
+#include <freetype/internal/tterrors.h>
+#include <freetype/internal/tttypes.h>
+
+#include "ftxopen.h"
+#include "ftxopenf.h"
+
+
+  struct  GPOS_Instance_
+  {
+    TTO_GPOSHeader*  gpos;
+    FT_Face          face;
+    FT_Bool          dvi;
+    FT_UShort        load_flags;  /* how the glyph should be loaded */
+    FT_Bool          r2l;
+
+    FT_UShort        last;        /* the last valid glyph -- used
+                                     with cursive positioning     */
+    FT_Pos           anchor_x;    /* the coordinates of the anchor point */
+    FT_Pos           anchor_y;    /* of the last valid glyph             */
+  };
+
+  typedef struct GPOS_Instance_  GPOS_Instance;
+
+
+  static FT_Error  Do_Glyph_Lookup( GPOS_Instance*    gpi,
+                                    FT_UShort         lookup_index,
+                                    TTO_GSUB_String*  in,
+                                    TTO_GPOS_Data*    out,
+                                    FT_UShort         context_length,
+                                    int               nesting_level );
+
+
+  /* the client application must replace this with something more
+     meaningful if multiple master fonts are to be supported.     */
+
+  static FT_Error  default_mmfunc( FT_Face      face,
+                                   FT_UShort    metric_id,
+                                   FT_Pos*      metric_value,
+                                   void*        data )
+  {
+    return TTO_Err_No_MM_Interpreter;
+  }
+
+
+#if 0
+#define GPOS_ID  Build_Extension_ID( 'G', 'P', 'O', 'S' )
+
+  /**********************
+   * Extension Functions
+   **********************/
+
+  static FT_Error  GPOS_Create( void*      ext,
+                                FT_Stream stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    TTO_GPOSHeader*  gpos = (TTO_GPOSHeader*)ext;
+    FT_Long          table;
+
+
+    /* by convention */
+
+    if ( !gpos )
+      return TT_Err_Ok;
+
+    /* a null offset indicates that there is no GPOS table */
+
+    gpos->offset = 0;
+
+    /* we store the start offset and the size of the subtable */
+
+    table = face->lookup_table ( face, TTAG_GPOS );
+    if ( table < 0 )
+      return TT_Err_Ok;             /* The table is optional */
+
+    if ( FILE_Seek( face->dirTables[table].Offset ) ||
+         ACCESS_Frame( 4L ) )
+      return error;
+
+    gpos->offset  = FILE_Pos() - 4L;    /* undo ACCESS_Frame() */
+    gpos->Version = GET_ULong();
+
+    FORGET_Frame();
+
+    /* a default mmfunc() handler which just returns an error */
+
+    gpos->mmfunc = default_mmfunc;
+
+    /* the default glyph function is TT_Load_Glyph() */
+
+    gpos->gfunc = FT_Load_Glyph;
+
+    gpos->loaded = FALSE;
+
+    return TT_Err_Ok;
+  }
+
+
+  static FT_Error  GPOS_Destroy( void*  ext,
+                                 PFace  face )
+  {
+    TTO_GPOSHeader*  gpos = (TTO_GPOSHeader*)ext;
+
+
+    /* by convention */
+
+    if ( !gpos )
+      return TT_Err_Ok;
+
+    if ( gpos->loaded )
+    {
+      Free_LookupList( &gpos->LookupList, GPOS );
+      Free_FeatureList( &gpos->FeatureList );
+      Free_ScriptList( &gpos->ScriptList );
+    }
+
+    return TT_Err_Ok;
+  }
+
+
+  EXPORT_FUNC
+  FT_Error  TT_Init_GPOS_Extension( TT_Engine  engine )
+  {
+    PEngine_Instance  _engine = HANDLE_Engine( engine );
+
+
+    if ( !_engine )
+      return TT_Err_Invalid_Engine;
+
+    return  TT_Register_Extension( _engine,
+                                   GPOS_ID,
+                                   sizeof ( TTO_GPOSHeader ),
+                                   GPOS_Create,
+                                   GPOS_Destroy );
+  }
+#endif
+
+  EXPORT_FUNC
+  FT_Error  TT_Load_GPOS_Table( FT_Face          face,
+                                TTO_GPOSHeader** retptr,
+                                TTO_GDEFHeader*  gdef )
+  {
+    FT_ULong         cur_offset, new_offset, base_offset;
+
+    FT_UShort        i, num_lookups;
+    TTO_GPOSHeader*  gpos;
+    TTO_Lookup*      lo;
+    TT_Face          tt_face = (TT_Face)face;
+
+    FT_Stream  stream = face->stream;
+    FT_Error   error;
+    FT_Memory  memory = face->memory;
+
+
+    if ( !retptr )
+      return TT_Err_Invalid_Argument;
+
+    if ( !stream )
+      return TT_Err_Invalid_Face_Handle;
+
+    if (( error = tt_face->goto_table( tt_face, TTAG_GPOS, stream, 0 ) ))
+      return error;
+
+    base_offset = FILE_Pos();
+
+    if ( ALLOC ( gpos, sizeof( *gpos ) ) )
+      return error;
+
+    gpos->memory = memory;
+    gpos->gfunc = FT_Load_Glyph;
+    gpos->mmfunc = default_mmfunc;
+
+    /* skip version */
+
+    if ( FILE_Seek( base_offset + 4L ) ||
+         ACCESS_Frame( 2L ) )
+      goto Fail4;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_ScriptList( &gpos->ScriptList,
+                                    stream ) ) != TT_Err_Ok )
+      goto Fail4;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail3;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_FeatureList( &gpos->FeatureList,
+                                     stream ) ) != TT_Err_Ok )
+      goto Fail3;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail2;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_LookupList( &gpos->LookupList,
+                                    stream, GPOS ) ) != TT_Err_Ok )
+      goto Fail2;
+
+    gpos->gdef = gdef;      /* can be NULL */
+
+    /* We now check the LookupFlags for values larger than 0xFF to find
+       out whether we need to load the `MarkAttachClassDef' field of the
+       GDEF table -- this hack is necessary for OpenType 1.2 tables since
+       the version field of the GDEF table hasn't been incremented.
+
+       For constructed GDEF tables, we only load it if
+       `MarkAttachClassDef_offset' is not zero (nevertheless, a build of
+       a constructed mark attach table is not supported currently).       */
+
+    if ( gdef &&
+         gdef->MarkAttachClassDef_offset && !gdef->MarkAttachClassDef.loaded )
+    {
+      lo          = gpos->LookupList.Lookup;
+      num_lookups = gpos->LookupList.LookupCount;
+
+      for ( i = 0; i < num_lookups; i++ )
+      {
+        if ( lo[i].LookupFlag & IGNORE_SPECIAL_MARKS )
+        {
+          if ( FILE_Seek( gdef->MarkAttachClassDef_offset ) ||
+               ACCESS_Frame( 2L ) )
+            goto Fail1;
+
+          new_offset = GET_UShort();
+
+          FORGET_Frame();
+
+          if ( !new_offset )
+            return TTO_Err_Invalid_GDEF_SubTable;
+
+          new_offset += base_offset;
+
+          if ( FILE_Seek( new_offset ) ||
+               ( error = Load_ClassDefinition( &gdef->MarkAttachClassDef,
+                                               256, stream ) ) != TT_Err_Ok )
+            goto Fail1;
+
+          break;
+        }
+      }
+    }
+
+    *retptr = gpos;
+
+    return TT_Err_Ok;
+
+  Fail1:
+    Free_LookupList( &gpos->LookupList, GPOS, memory );
+
+  Fail2:
+    Free_FeatureList( &gpos->FeatureList, memory );
+
+  Fail3:
+    Free_ScriptList( &gpos->ScriptList, memory );
+
+  Fail4:
+    FREE( gpos );
+
+    return error;
+  }
+
+  EXPORT_FUNC
+  FT_Error  TT_Done_GPOS_Table( TTO_GPOSHeader* gpos )
+  {
+    FT_Memory memory = gpos->memory;
+    
+    Free_LookupList( &gpos->LookupList, GPOS, memory );
+    Free_FeatureList( &gpos->FeatureList, memory );
+    Free_ScriptList( &gpos->ScriptList, memory );
+
+    return FT_Err_Ok;
+  }
+
+
+  /*****************************
+   * SubTable related functions
+   *****************************/
+
+  /* shared tables */
+
+  /* ValueRecord */
+
+  /* There is a subtle difference in the specs between a `table' and a
+     `record' -- offsets for device tables in ValueRecords are taken from
+     the parent table and not the parent record.                          */
+
+  static FT_Error  Load_ValueRecord( TTO_ValueRecord*  vr,
+                                     FT_UShort         format,
+                                     FT_ULong          base_offset,
+                                     FT_Stream         stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+    
+    FT_ULong cur_offset, new_offset;
+
+
+    if ( format & HAVE_X_PLACEMENT )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        return error;
+
+      vr->XPlacement = GET_Short();
+
+      FORGET_Frame();
+    }
+    else
+      vr->XPlacement = 0;
+
+    if ( format & HAVE_Y_PLACEMENT )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        return error;
+
+      vr->YPlacement = GET_Short();
+
+      FORGET_Frame();
+    }
+    else
+      vr->YPlacement = 0;
+
+    if ( format & HAVE_X_ADVANCE )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        return error;
+
+      vr->XAdvance = GET_Short();
+
+      FORGET_Frame();
+    }
+    else
+      vr->XAdvance = 0;
+
+    if ( format & HAVE_Y_ADVANCE )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        return error;
+
+      vr->YAdvance = GET_Short();
+
+      FORGET_Frame();
+    }
+    else
+      vr->YAdvance = 0;
+
+    if ( format & HAVE_X_PLACEMENT_DEVICE )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        return error;
+
+      new_offset = GET_UShort();
+
+      FORGET_Frame();
+
+      if ( new_offset )
+      {
+        new_offset += base_offset;
+
+        cur_offset = FILE_Pos();
+        if ( FILE_Seek( new_offset ) ||
+             ( error = Load_Device( &vr->XPlacementDevice,
+                                    stream ) ) != TT_Err_Ok )
+          return error;
+        (void)FILE_Seek( cur_offset );
+      }
+      else
+        goto empty1;
+    }
+    else
+    {
+    empty1:
+      vr->XPlacementDevice.StartSize  = 0;
+      vr->XPlacementDevice.EndSize    = 0;
+      vr->XPlacementDevice.DeltaValue = NULL;
+    }
+
+    if ( format & HAVE_Y_PLACEMENT_DEVICE )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail3;
+
+      new_offset = GET_UShort();
+
+      FORGET_Frame();
+
+      if ( new_offset )
+      {
+        new_offset += base_offset;
+
+        cur_offset = FILE_Pos();
+        if ( FILE_Seek( new_offset ) ||
+             ( error = Load_Device( &vr->YPlacementDevice,
+                                    stream ) ) != TT_Err_Ok )
+          goto Fail3;
+        (void)FILE_Seek( cur_offset );
+      }
+      else
+        goto empty2;
+    }
+    else
+    {
+    empty2:
+      vr->YPlacementDevice.StartSize  = 0;
+      vr->YPlacementDevice.EndSize    = 0;
+      vr->YPlacementDevice.DeltaValue = NULL;
+    }
+
+    if ( format & HAVE_X_ADVANCE_DEVICE )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail2;
+
+      new_offset = GET_UShort();
+
+      FORGET_Frame();
+
+      if ( new_offset )
+      {
+        new_offset += base_offset;
+
+        cur_offset = FILE_Pos();
+        if ( FILE_Seek( new_offset ) ||
+             ( error = Load_Device( &vr->XAdvanceDevice,
+                                    stream ) ) != TT_Err_Ok )
+          goto Fail2;
+        (void)FILE_Seek( cur_offset );
+      }
+      else
+        goto empty3;
+    }
+    else
+    {
+    empty3:
+      vr->XAdvanceDevice.StartSize  = 0;
+      vr->XAdvanceDevice.EndSize    = 0;
+      vr->XAdvanceDevice.DeltaValue = NULL;
+    }
+
+    if ( format & HAVE_Y_ADVANCE_DEVICE )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail1;
+
+      new_offset = GET_UShort();
+
+      FORGET_Frame();
+
+      if ( new_offset )
+      {
+        new_offset += base_offset;
+
+        cur_offset = FILE_Pos();
+        if ( FILE_Seek( new_offset ) ||
+             ( error = Load_Device( &vr->YAdvanceDevice,
+                                    stream ) ) != TT_Err_Ok )
+          goto Fail1;
+        (void)FILE_Seek( cur_offset );
+      }
+      else
+        goto empty4;
+    }
+    else
+    {
+    empty4:
+      vr->YAdvanceDevice.StartSize  = 0;
+      vr->YAdvanceDevice.EndSize    = 0;
+      vr->YAdvanceDevice.DeltaValue = NULL;
+    }
+
+    if ( format & HAVE_X_ID_PLACEMENT )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail1;
+
+      vr->XIdPlacement = GET_UShort();
+
+      FORGET_Frame();
+    }
+    else
+      vr->XIdPlacement = 0;
+
+    if ( format & HAVE_Y_ID_PLACEMENT )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail1;
+
+      vr->YIdPlacement = GET_UShort();
+
+      FORGET_Frame();
+    }
+    else
+      vr->YIdPlacement = 0;
+
+    if ( format & HAVE_X_ID_ADVANCE )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail1;
+
+      vr->XIdAdvance = GET_UShort();
+
+      FORGET_Frame();
+    }
+    else
+      vr->XIdAdvance = 0;
+
+    if ( format & HAVE_Y_ID_ADVANCE )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail1;
+
+      vr->YIdAdvance = GET_UShort();
+
+      FORGET_Frame();
+    }
+    else
+      vr->YIdAdvance = 0;
+
+    return TT_Err_Ok;
+
+  Fail1:
+    Free_Device( &vr->YAdvanceDevice, memory );
+
+  Fail2:
+    Free_Device( &vr->XAdvanceDevice, memory );
+
+  Fail3:
+    Free_Device( &vr->YPlacementDevice, memory );
+    return error;
+  }
+
+
+  static void  Free_ValueRecord( TTO_ValueRecord*  vr,
+                                 FT_UShort         format,
+				 FT_Memory         memory )
+  {
+    if ( format & HAVE_Y_ADVANCE_DEVICE )
+      Free_Device( &vr->YAdvanceDevice, memory );
+    if ( format & HAVE_X_ADVANCE_DEVICE )
+      Free_Device( &vr->XAdvanceDevice, memory );
+    if ( format & HAVE_Y_PLACEMENT_DEVICE )
+      Free_Device( &vr->YPlacementDevice, memory );
+    if ( format & HAVE_X_PLACEMENT_DEVICE )
+      Free_Device( &vr->XPlacementDevice, memory );
+  }
+
+
+  static FT_Error  Get_ValueRecord( GPOS_Instance*    gpi,
+                                    TTO_ValueRecord*  vr,
+                                    FT_UShort         format,
+                                    TTO_GPOS_Data*    gd )
+  {
+    FT_Pos           value;
+    FT_Short         pixel_value;
+    FT_Error         error = TT_Err_Ok;
+    TTO_GPOSHeader*  gpos = gpi->gpos;
+
+    FT_UShort  x_ppem, y_ppem;
+    FT_Fixed   x_scale, y_scale;
+
+
+    if ( !format )
+      return TT_Err_Ok;
+
+    x_ppem  = gpi->face->size->metrics.x_ppem;
+    y_ppem  = gpi->face->size->metrics.y_ppem;
+    x_scale = gpi->face->size->metrics.x_scale;
+    y_scale = gpi->face->size->metrics.y_scale;
+
+    /* design units -> fractional pixel */
+
+    if ( format & HAVE_X_PLACEMENT )
+      gd->x_pos += x_scale * vr->XPlacement / 0x10000;
+    if ( format & HAVE_Y_PLACEMENT )
+      gd->y_pos += y_scale * vr->YPlacement / 0x10000;
+    if ( format & HAVE_X_ADVANCE )
+      gd->x_advance += x_scale * vr->XAdvance / 0x10000;
+    if ( format & HAVE_Y_ADVANCE )
+      gd->y_advance += y_scale * vr->YAdvance / 0x10000;
+
+    if ( !gpi->dvi )
+    {
+      /* pixel -> fractional pixel */
+
+      if ( format & HAVE_X_PLACEMENT_DEVICE )
+      {
+        Get_Device( &vr->XPlacementDevice, x_ppem, &pixel_value );
+        gd->x_pos += pixel_value << 6;
+      }
+      if ( format & HAVE_Y_PLACEMENT_DEVICE )
+      {
+        Get_Device( &vr->YPlacementDevice, y_ppem, &pixel_value );
+        gd->y_pos += pixel_value << 6;
+      }
+      if ( format & HAVE_X_ADVANCE_DEVICE )
+      {
+        Get_Device( &vr->XAdvanceDevice, x_ppem, &pixel_value );
+        gd->x_advance += pixel_value << 6;
+      }
+      if ( format & HAVE_Y_ADVANCE_DEVICE )
+      {
+        Get_Device( &vr->YAdvanceDevice, y_ppem, &pixel_value );
+        gd->y_advance += pixel_value << 6;
+      }
+    }
+
+    /* values returned from mmfunc() are already in fractional pixels */
+
+    if ( format & HAVE_X_ID_PLACEMENT )
+    {
+      error = (gpos->mmfunc)( gpi->face, vr->XIdPlacement,
+                              &value, gpos->data );
+      if ( error )
+        return error;
+      gd->x_pos += value;
+    }
+    if ( format & HAVE_Y_ID_PLACEMENT )
+    {
+      error = (gpos->mmfunc)( gpi->face, vr->YIdPlacement,
+                              &value, gpos->data );
+      if ( error )
+        return error;
+      gd->y_pos += value;
+    }
+    if ( format & HAVE_X_ID_ADVANCE )
+    {
+      error = (gpos->mmfunc)( gpi->face, vr->XIdAdvance,
+                              &value, gpos->data );
+      if ( error )
+        return error;
+      gd->x_advance += value;
+    }
+    if ( format & HAVE_Y_ID_ADVANCE )
+    {
+      error = (gpos->mmfunc)( gpi->face, vr->YIdAdvance,
+                              &value, gpos->data );
+      if ( error )
+        return error;
+      gd->y_advance += value;
+    }
+
+    return error;
+  }
+
+
+  /* AnchorFormat1 */
+  /* AnchorFormat2 */
+  /* AnchorFormat3 */
+  /* AnchorFormat4 */
+
+  static FT_Error  Load_Anchor( TTO_Anchor*  an,
+                                FT_Stream    stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_ULong cur_offset, new_offset, base_offset;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    an->PosFormat = GET_UShort();
+
+    FORGET_Frame();
+
+    switch ( an->PosFormat )
+    {
+    case 1:
+      if ( ACCESS_Frame( 4L ) )
+        return error;
+
+      an->af.af1.XCoordinate = GET_Short();
+      an->af.af1.YCoordinate = GET_Short();
+
+      FORGET_Frame();
+      break;
+
+    case 2:
+      if ( ACCESS_Frame( 6L ) )
+        return error;
+
+      an->af.af2.XCoordinate = GET_Short();
+      an->af.af2.YCoordinate = GET_Short();
+      an->af.af2.AnchorPoint = GET_UShort();
+
+      FORGET_Frame();
+      break;
+
+    case 3:
+      if ( ACCESS_Frame( 6L ) )
+        return error;
+
+      an->af.af3.XCoordinate = GET_Short();
+      an->af.af3.YCoordinate = GET_Short();
+
+      new_offset = GET_UShort();
+
+      FORGET_Frame();
+
+      if ( new_offset )
+      {
+        new_offset += base_offset;
+
+        cur_offset = FILE_Pos();
+        if ( FILE_Seek( new_offset ) ||
+             ( error = Load_Device( &an->af.af3.XDeviceTable,
+                                    stream ) ) != TT_Err_Ok )
+          return error;
+        (void)FILE_Seek( cur_offset );
+      }
+      else
+      {
+        an->af.af3.XDeviceTable.StartSize  = 0;
+        an->af.af3.XDeviceTable.EndSize    = 0;
+        an->af.af3.XDeviceTable.DeltaValue = 0;
+      }
+
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail;
+
+      new_offset = GET_UShort();
+
+      FORGET_Frame();
+
+      if ( new_offset )
+      {
+        new_offset += base_offset;
+
+        cur_offset = FILE_Pos();
+        if ( FILE_Seek( new_offset ) ||
+             ( error = Load_Device( &an->af.af3.YDeviceTable,
+                                    stream ) ) != TT_Err_Ok )
+          goto Fail;
+        (void)FILE_Seek( cur_offset );
+      }
+      else
+      {
+        an->af.af3.YDeviceTable.StartSize  = 0;
+        an->af.af3.YDeviceTable.EndSize    = 0;
+        an->af.af3.YDeviceTable.DeltaValue = 0;
+      }
+      break;
+
+    case 4:
+      if ( ACCESS_Frame( 4L ) )
+        return error;
+
+      an->af.af4.XIdAnchor = GET_UShort();
+      an->af.af4.YIdAnchor = GET_UShort();
+
+      FORGET_Frame();
+      break;
+
+    default:
+      return TTO_Err_Invalid_GPOS_SubTable_Format;
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    Free_Device( &an->af.af3.XDeviceTable, memory );
+    return error;
+  }
+
+
+  static void  Free_Anchor( TTO_Anchor*  an,
+			    FT_Memory    memory)
+  {
+    if ( an->PosFormat == 3 )
+    {
+      Free_Device( &an->af.af3.YDeviceTable, memory );
+      Free_Device( &an->af.af3.XDeviceTable, memory );
+    }
+  }
+
+
+  static FT_Error  Get_Anchor( GPOS_Instance*   gpi,
+                               TTO_Anchor*      an,
+                               FT_UShort        glyph_index,
+                               FT_Pos*          x_value,
+                               FT_Pos*          y_value )
+  {
+    FT_Error  error = TT_Err_Ok;
+
+    FT_Outline       outline;
+    TTO_GPOSHeader*  gpos = gpi->gpos;
+    FT_UShort        ap;
+
+    FT_Short         pixel_value;
+    FT_UShort        load_flags;
+
+    FT_UShort        x_ppem, y_ppem;
+    FT_Fixed         x_scale, y_scale;
+
+
+    x_ppem  = gpi->face->size->metrics.x_ppem;
+    y_ppem  = gpi->face->size->metrics.y_ppem;
+    x_scale = gpi->face->size->metrics.x_scale;
+    y_scale = gpi->face->size->metrics.y_scale;
+
+    switch ( an->PosFormat )
+    {
+    case 0:
+      /* The special case of an empty AnchorTable */
+
+      return TTO_Err_Not_Covered;
+
+    case 1:
+      *x_value = x_scale * an->af.af1.XCoordinate / 0x10000;
+      *y_value = y_scale * an->af.af1.YCoordinate / 0x10000;
+      break;
+
+    case 2:
+      /* glyphs must be scaled */
+
+      load_flags = gpi->load_flags & ~FT_LOAD_NO_SCALE;
+
+      if ( !gpi->dvi )
+      {
+        error = (gpos->gfunc)( gpi->face, glyph_index, load_flags );
+        if ( error )
+          return error;
+
+	if ( gpi->face->glyph->format != ft_glyph_format_outline )
+          return TTO_Err_Invalid_GPOS_SubTable;	  
+	
+	outline = gpi->face->glyph->outline;
+
+        /* if outline.n_points is set to zero by gfunc(), we use the
+           design coordinate value pair.  This can happen e.g. for
+           sbit glyphs                                               */
+
+        if ( !outline.n_points )
+          goto no_contour_point;
+
+        if ( ap >= outline.n_points )
+          return TTO_Err_Invalid_GPOS_SubTable;
+
+        *x_value = outline.points[ap].x;
+        *y_value = outline.points[ap].y;
+      }
+      else
+      {
+      no_contour_point:
+        *x_value = x_scale * an->af.af3.XCoordinate / 0x10000;
+        *y_value = y_scale * an->af.af3.YCoordinate / 0x10000;
+      }
+      break;
+
+    case 3:
+      if ( !gpi->dvi )
+      {
+        Get_Device( &an->af.af3.XDeviceTable, x_ppem, &pixel_value );
+        *x_value = pixel_value << 6;
+        Get_Device( &an->af.af3.YDeviceTable, y_ppem, &pixel_value );
+        *y_value = pixel_value << 6;
+      }
+      else
+        *x_value = *y_value = 0;
+
+      *x_value += x_scale * an->af.af3.XCoordinate / 0x10000;
+      *y_value += y_scale * an->af.af3.YCoordinate / 0x10000;
+      break;
+
+    case 4:
+      error = (gpos->mmfunc)( gpi->face, an->af.af4.XIdAnchor,
+                              x_value, gpos->data );
+      if ( error )
+        return error;
+
+      error = (gpos->mmfunc)( gpi->face, an->af.af4.YIdAnchor,
+                              y_value, gpos->data );
+      if ( error )
+        return error;
+      break;
+    }
+
+    return error;
+  }
+
+
+  /* MarkArray */
+
+  static FT_Error  Load_MarkArray ( TTO_MarkArray*  ma,
+                                    FT_Stream       stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort        n, count;
+    FT_ULong         cur_offset, new_offset, base_offset;
+
+    TTO_MarkRecord*  mr;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = ma->MarkCount = GET_UShort();
+    
+    FORGET_Frame();
+
+    ma->MarkRecord = NULL;
+
+    if ( ALLOC_ARRAY( ma->MarkRecord, count, TTO_MarkRecord ) )
+      return error;
+
+    mr = ma->MarkRecord;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 4L ) )
+        goto Fail;
+
+      mr[n].Class = GET_UShort();
+      new_offset  = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_Anchor( &mr[n].MarkAnchor, stream ) ) != TT_Err_Ok )
+        goto Fail;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    for ( n = 0; n < count; n++ )
+      Free_Anchor( &mr[n].MarkAnchor, memory );
+
+    FREE( mr );
+    return error;
+  }
+
+
+  static void  Free_MarkArray( TTO_MarkArray*  ma,
+			       FT_Memory       memory )
+  {
+    FT_UShort        n, count;
+
+    TTO_MarkRecord*  mr;
+
+
+    if ( ma->MarkRecord )
+    {
+      count = ma->MarkCount;
+      mr    = ma->MarkRecord;
+
+      for ( n = 0; n < count; n++ )
+        Free_Anchor( &mr[n].MarkAnchor, memory );
+
+      FREE( mr );
+    }
+  }
+
+
+  /* LookupType 1 */
+
+  /* SinglePosFormat1 */
+  /* SinglePosFormat2 */
+
+  FT_Error  Load_SinglePos( TTO_SinglePos*  sp,
+                            FT_Stream       stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort         n, count, format;
+    FT_ULong          cur_offset, new_offset, base_offset;
+
+    TTO_ValueRecord*  vr;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 6L ) )
+      return error;
+
+    sp->PosFormat = GET_UShort();
+    new_offset    = GET_UShort() + base_offset;
+
+    format = sp->ValueFormat = GET_UShort();
+
+    FORGET_Frame();
+
+    if ( !format )
+      return TTO_Err_Invalid_GPOS_SubTable;
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &sp->Coverage, stream ) ) != TT_Err_Ok )
+      return error;
+    (void)FILE_Seek( cur_offset );
+
+    switch ( sp->PosFormat )
+    {
+    case 1:
+      error = Load_ValueRecord( &sp->spf.spf1.Value, format,
+                                base_offset, stream );
+      if ( error )
+        goto Fail2;
+      break;
+
+    case 2:
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail2;
+
+      count = sp->spf.spf2.ValueCount = GET_UShort();
+
+      FORGET_Frame();
+
+      sp->spf.spf2.Value = NULL;
+
+      if ( ALLOC_ARRAY( sp->spf.spf2.Value, count, TTO_ValueRecord ) )
+        goto Fail2;
+
+      vr = sp->spf.spf2.Value;
+
+      for ( n = 0; n < count; n++ )
+      {
+        error = Load_ValueRecord( &vr[n], format, base_offset, stream );
+        if ( error )
+          goto Fail1;
+      }
+      break;
+
+    default:
+      return TTO_Err_Invalid_GPOS_SubTable_Format;
+    }
+
+    return TT_Err_Ok;
+
+  Fail1:
+    for ( n = 0; n < count; n++ )
+      Free_ValueRecord( &vr[n], format, memory );
+
+    FREE( vr );
+
+  Fail2:
+    Free_Coverage( &sp->Coverage, memory );
+    return error;
+  }
+
+
+  void  Free_SinglePos( TTO_SinglePos*  sp,
+			FT_Memory       memory )
+  {
+    FT_UShort         n, count, format;
+
+    TTO_ValueRecord*  v;
+
+
+    format = sp->ValueFormat;
+
+    switch ( sp->PosFormat )
+    {
+    case 1:
+      Free_ValueRecord( &sp->spf.spf1.Value, format, memory );
+      break;
+
+    case 2:
+      if ( sp->spf.spf2.Value )
+      {
+        count = sp->spf.spf2.ValueCount;
+        v     = sp->spf.spf2.Value;
+
+        for ( n = 0; n < count; n++ )
+          Free_ValueRecord( &v[n], format, memory );
+
+        FREE( v );
+      }
+      break;
+    }
+
+    Free_Coverage( &sp->Coverage, memory );
+  }
+
+
+  static FT_Error  Lookup_SinglePos( GPOS_Instance*    gpi,
+                                     TTO_SinglePos*    sp,
+                                     TTO_GSUB_String*  in,
+                                     TTO_GPOS_Data*    out,
+                                     FT_UShort         flags,
+                                     FT_UShort         context_length )
+  {
+    FT_UShort        index, property;
+    FT_Error         error;
+    TTO_GPOSHeader*  gpos = gpi->gpos;
+
+
+    if ( context_length != 0xFFFF && context_length < 1 )
+      return TTO_Err_Not_Covered;
+
+    if ( CHECK_Property( gpos->gdef, in->string[in->pos], flags, &property ) )
+      return error;
+
+    error = Coverage_Index( &sp->Coverage, in->string[in->pos], &index );
+    if ( error )
+      return error;
+
+    switch ( sp->PosFormat )
+    {
+    case 1:
+      error = Get_ValueRecord( gpi, &sp->spf.spf1.Value,
+                               sp->ValueFormat, &out[in->pos] );
+      if ( error )
+        return error;
+      break;
+
+    case 2:
+      if ( index >= sp->spf.spf2.ValueCount )
+        return TTO_Err_Invalid_GPOS_SubTable;
+      error = Get_ValueRecord( gpi, &sp->spf.spf2.Value[index],
+                               sp->ValueFormat, &out[in->pos] );
+      if ( error )
+        return error;
+      break;
+
+    default:
+      return TTO_Err_Invalid_GPOS_SubTable;
+    }
+
+    (in->pos)++;
+
+    return TT_Err_Ok;
+  }
+
+
+  /* LookupType 2 */
+
+  /* PairSet */
+
+  static FT_Error  Load_PairSet ( TTO_PairSet*  ps,
+                                  FT_UShort     format1,
+                                  FT_UShort     format2,
+                                  FT_Stream     stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort             n, count;
+    FT_ULong              base_offset;
+
+    TTO_PairValueRecord*  pvr;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = ps->PairValueCount = GET_UShort();
+    
+    FORGET_Frame();
+
+    ps->PairValueRecord = NULL;
+
+    if ( ALLOC_ARRAY( ps->PairValueRecord, count, TTO_PairValueRecord ) )
+      return error;
+
+    pvr = ps->PairValueRecord;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail;
+
+      pvr[n].SecondGlyph = GET_UShort();
+
+      FORGET_Frame();
+
+      if ( format1 )
+      {
+        error = Load_ValueRecord( &pvr[n].Value1, format1,
+                                  base_offset, stream );
+        if ( error )
+          goto Fail;
+      }
+      if ( format2 )
+      {
+        error = Load_ValueRecord( &pvr[n].Value2, format2,
+                                  base_offset, stream );
+        if ( error )
+          goto Fail;
+      }
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    for ( n = 0; n < count; n++ )
+    {
+      if ( format1 )
+        Free_ValueRecord( &pvr[n].Value1, format1, memory );
+      if ( format2 )
+        Free_ValueRecord( &pvr[n].Value2, format2, memory );
+    }
+
+    FREE( pvr );
+    return error;
+  }
+
+
+  static void  Free_PairSet( TTO_PairSet*  ps,
+                             FT_UShort     format1,
+                             FT_UShort     format2,
+			     FT_Memory     memory )
+  {
+    FT_UShort             n, count;
+
+    TTO_PairValueRecord*  pvr;
+
+
+    if ( ps->PairValueRecord )
+    {
+      count = ps->PairValueCount;
+      pvr   = ps->PairValueRecord;
+
+      for ( n = 0; n < count; n++ )
+      {
+        if ( format1 )
+          Free_ValueRecord( &pvr[n].Value1, format1, memory );
+        if ( format2 )
+          Free_ValueRecord( &pvr[n].Value2, format2, memory );
+      }
+
+      FREE( pvr );
+    }
+  }
+
+
+  /* PairPosFormat1 */
+
+  static FT_Error  Load_PairPos1( TTO_PairPosFormat1*  ppf1,
+                                  FT_UShort            format1,
+                                  FT_UShort            format2,
+                                  FT_Stream            stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort     n, count;
+    FT_ULong      cur_offset, new_offset, base_offset;
+
+    TTO_PairSet*  ps;
+
+
+    base_offset = FILE_Pos() - 8L;
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = ppf1->PairSetCount = GET_UShort();
+
+    FORGET_Frame();
+
+    ppf1->PairSet = NULL;
+
+    if ( ALLOC_ARRAY( ppf1->PairSet, count, TTO_PairSet ) )
+      goto Fail;
+
+    ps = ppf1->PairSet;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_PairSet( &ps[n], format1,
+                                   format2, stream ) ) != TT_Err_Ok )
+        goto Fail;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    for ( n = 0; n < count; n++ )
+      Free_PairSet( &ps[n], format1, format2, memory );
+
+    FREE( ps );
+    return error;
+  }
+
+
+  static void  Free_PairPos1( TTO_PairPosFormat1*  ppf1,
+                              FT_UShort            format1,
+                              FT_UShort            format2,
+			      FT_Memory            memory )
+  {
+    FT_UShort     n, count;
+
+    TTO_PairSet*  ps;
+
+
+    if ( ppf1->PairSet )
+    {
+      count = ppf1->PairSetCount;
+      ps    = ppf1->PairSet;
+
+      for ( n = 0; n < count; n++ )
+        Free_PairSet( &ps[n], format1, format2, memory );
+
+      FREE( ps );
+    }
+  }
+
+
+  /* PairPosFormat2 */
+
+  static FT_Error  Load_PairPos2( TTO_PairPosFormat2*  ppf2,
+                                  FT_UShort            format1,
+                                  FT_UShort            format2,
+                                  FT_Stream            stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort          m, n, count1, count2;
+    FT_ULong           cur_offset, new_offset1, new_offset2, base_offset;
+
+    TTO_Class1Record*  c1r;
+    TTO_Class2Record*  c2r;
+
+
+    base_offset = FILE_Pos() - 8L;
+
+    if ( ACCESS_Frame( 8L ) )
+      return error;
+
+    new_offset1 = GET_UShort() + base_offset;
+    new_offset2 = GET_UShort() + base_offset;
+
+    /* `Class1Count' and `Class2Count' are the upper limits for class
+       values, thus we read it now to make additional safety checks.  */
+
+    count1 = ppf2->Class1Count = GET_UShort();
+    count2 = ppf2->Class2Count = GET_UShort();
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset1 ) ||
+         ( error = Load_ClassDefinition( &ppf2->ClassDef1, count1,
+                                         stream ) ) != TT_Err_Ok )
+      return error;
+    if ( FILE_Seek( new_offset2 ) ||
+         ( error = Load_ClassDefinition( &ppf2->ClassDef2, count2,
+                                         stream ) ) != TT_Err_Ok )
+      goto Fail2;
+    (void)FILE_Seek( cur_offset );
+
+    ppf2->Class1Record = NULL;
+
+    if ( ALLOC_ARRAY( ppf2->Class1Record, count1, TTO_Class1Record ) )
+      goto Fail1;
+
+    c1r = ppf2->Class1Record;
+
+    for ( m = 0; m < count1; m++ )
+    {
+      c1r[m].Class2Record = NULL;
+
+      if ( ALLOC_ARRAY( c1r[m].Class2Record, count2, TTO_Class2Record ) )
+        goto Fail1;
+
+      c2r = c1r[m].Class2Record;
+
+      for ( n = 0; n < count2; n++ )
+      {
+        if ( format1 )
+        {
+          error = Load_ValueRecord( &c2r[n].Value1, format1,
+                                    base_offset, stream );
+          if ( error )
+            goto Fail1;
+        }
+        if ( format2 )
+        {
+          error = Load_ValueRecord( &c2r[n].Value2, format2,
+                                    base_offset, stream );
+          if ( error )
+            goto Fail1;
+        }
+      }
+    }
+
+    return TT_Err_Ok;
+
+  Fail1:
+    for ( m = 0; m < count1; m++ )
+    {
+      c2r = c1r[m].Class2Record;
+
+      for ( n = 0; n < count2; n++ )
+      {
+        if ( format1 )
+          Free_ValueRecord( &c2r[n].Value1, format1, memory );
+        if ( format2 )
+          Free_ValueRecord( &c2r[n].Value2, format2, memory );
+      }
+
+      FREE( c2r );
+    }
+
+    FREE( c1r );
+
+    Free_ClassDefinition( &ppf2->ClassDef2, memory );
+
+  Fail2:
+    Free_ClassDefinition( &ppf2->ClassDef1, memory );
+    return error;
+  }
+
+
+  static void  Free_PairPos2( TTO_PairPosFormat2*  ppf2,
+                              FT_UShort            format1,
+                              FT_UShort            format2,
+			      FT_Memory            memory )
+  {
+    FT_UShort          m, n, count1, count2;
+
+    TTO_Class1Record*  c1r;
+    TTO_Class2Record*  c2r;
+
+
+    if ( ppf2->Class1Record )
+    {
+      c1r    = ppf2->Class1Record;
+      count1 = ppf2->Class1Count;
+      count2 = ppf2->Class2Count;
+
+      for ( m = 0; m < count1; m++ )
+      {
+        c2r = c1r[m].Class2Record;
+
+        for ( n = 0; n < count2; n++ )
+        {
+          if ( format1 )
+            Free_ValueRecord( &c2r[n].Value1, format1, memory );
+          if ( format2 )
+            Free_ValueRecord( &c2r[n].Value2, format2, memory );
+        }
+
+        FREE( c2r );
+      }
+
+      FREE( c1r );
+
+      Free_ClassDefinition( &ppf2->ClassDef2, memory );
+      Free_ClassDefinition( &ppf2->ClassDef1, memory );
+    }
+  }
+
+
+  FT_Error  Load_PairPos( TTO_PairPos*  pp,
+                          FT_Stream     stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort         format1, format2;
+    FT_ULong          cur_offset, new_offset, base_offset;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 8L ) )
+      return error;
+
+    pp->PosFormat = GET_UShort();
+    new_offset    = GET_UShort() + base_offset;
+
+    format1 = pp->ValueFormat1 = GET_UShort();
+    format2 = pp->ValueFormat2 = GET_UShort();
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &pp->Coverage, stream ) ) != TT_Err_Ok )
+      return error;
+    (void)FILE_Seek( cur_offset );
+
+    switch ( pp->PosFormat )
+    {
+    case 1:
+      error = Load_PairPos1( &pp->ppf.ppf1, format1, format2, stream );
+      if ( error )
+        goto Fail;
+      break;
+
+    case 2:
+      error = Load_PairPos2( &pp->ppf.ppf2, format1, format2, stream );
+      if ( error )
+        goto Fail;
+      break;
+
+    default:
+      return TTO_Err_Invalid_GPOS_SubTable_Format;
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    Free_Coverage( &pp->Coverage, memory );
+    return error;
+  }
+
+
+  void  Free_PairPos( TTO_PairPos*  pp,
+		      FT_Memory     memory )
+  {
+    FT_UShort  format1, format2;
+
+
+    format1 = pp->ValueFormat1;
+    format2 = pp->ValueFormat2;
+
+    switch ( pp->PosFormat )
+    {
+    case 1:
+      Free_PairPos1( &pp->ppf.ppf1, format1, format2, memory );
+      break;
+
+    case 2:
+      Free_PairPos2( &pp->ppf.ppf2, format1, format2, memory );
+      break;
+    }
+
+    Free_Coverage( &pp->Coverage, memory );
+  }
+
+
+  static FT_Error  Lookup_PairPos1( GPOS_Instance*       gpi,
+                                    TTO_PairPosFormat1*  ppf1,
+                                    TTO_GSUB_String*     in,
+                                    TTO_GPOS_Data*       out,
+                                    FT_UShort            first_pos,
+                                    FT_UShort            index,
+                                    FT_UShort            format1,
+                                    FT_UShort            format2 )
+  {
+    FT_Error              error;
+    FT_UShort             numpvr, glyph2;
+
+    TTO_PairValueRecord*  pvr;
+
+
+    if ( index >= ppf1->PairSetCount )
+       return TTO_Err_Invalid_GPOS_SubTable;
+
+    pvr = ppf1->PairSet[index].PairValueRecord;
+    if ( !pvr )
+      return TTO_Err_Invalid_GPOS_SubTable;
+
+    glyph2 = in->string[in->pos];
+
+    for ( numpvr = ppf1->PairSet[index].PairValueCount;
+          numpvr;
+          numpvr--, pvr++ )
+    {
+      if ( glyph2 == pvr->SecondGlyph )
+      {
+        error = Get_ValueRecord( gpi, &pvr->Value1, format1,
+                                 &out[first_pos] );
+        if ( error )
+          return error;
+        return Get_ValueRecord( gpi, &pvr->Value2, format2,
+                                &out[in->pos] );
+      }
+    }
+
+    return TTO_Err_Not_Covered;
+  }
+
+
+  static FT_Error  Lookup_PairPos2( GPOS_Instance*       gpi,
+                                    TTO_PairPosFormat2*  ppf2,
+                                    TTO_GSUB_String*     in,
+                                    TTO_GPOS_Data*       out,
+                                    FT_UShort            first_pos,
+                                    FT_UShort            format1,
+                                    FT_UShort            format2 )
+  {
+    FT_Error           error;
+    FT_UShort          cl1, cl2;
+
+    TTO_Class1Record*  c1r;
+    TTO_Class2Record*  c2r;
+
+
+    error = Get_Class( &ppf2->ClassDef1, in->string[first_pos],
+                       &cl1, NULL );
+    if ( error )
+      return error;
+    error = Get_Class( &ppf2->ClassDef2, in->string[in->pos],
+                       &cl2, NULL );
+    if ( error )
+      return error;
+
+    c1r = &ppf2->Class1Record[cl1];
+    if ( !c1r )
+      return TTO_Err_Invalid_GPOS_SubTable;
+    c2r = &c1r->Class2Record[cl2];
+
+    error = Get_ValueRecord( gpi, &c2r->Value1, format1, &out[first_pos] );
+    if ( error )
+      return error;
+    return Get_ValueRecord( gpi, &c2r->Value2, format2, &out[in->pos] );
+  }
+
+
+  static FT_Error  Lookup_PairPos( GPOS_Instance*    gpi,
+                                   TTO_PairPos*      pp,
+                                   TTO_GSUB_String*  in,
+                                   TTO_GPOS_Data*    out,
+                                   FT_UShort         flags,
+                                   FT_UShort         context_length )
+  {
+    FT_Error         error;
+    FT_UShort        index, property, first_pos;
+    TTO_GPOSHeader*  gpos = gpi->gpos;
+
+
+    if ( in->pos >= in->length )
+      return TTO_Err_Not_Covered;           /* Not enough glyphs in stream */
+
+    if ( context_length != 0xFFFF && context_length < 2 )
+      return TTO_Err_Not_Covered;
+
+    if ( CHECK_Property( gpos->gdef, in->string[in->pos], flags, &property ) )
+      return error;
+
+    error = Coverage_Index( &pp->Coverage, in->string[in->pos], &index );
+    if ( error )
+      return error;
+
+    /* second glyph */
+
+    first_pos = in->pos;
+    (in->pos)++;
+
+    while ( CHECK_Property( gpos->gdef, in->string[in->pos],
+                            flags, &property ) )
+    {
+      if ( error && error != TTO_Err_Not_Covered )
+        return error;
+
+      if ( in->pos < in->length )
+        (in->pos)++;
+      else
+        break;
+    }
+
+    switch ( pp->PosFormat )
+    {
+    case 1:
+      error = Lookup_PairPos1( gpi, &pp->ppf.ppf1, in, out,
+                               first_pos, index,
+                               pp->ValueFormat1, pp->ValueFormat2 );
+      break;
+
+    case 2:
+      error = Lookup_PairPos2( gpi, &pp->ppf.ppf2, in, out, first_pos,
+                               pp->ValueFormat1, pp->ValueFormat2 );
+      break;
+
+    default:
+      return TTO_Err_Invalid_GPOS_SubTable_Format;
+    }
+
+    /* adjusting the `next' glyph */
+
+    if ( pp->ValueFormat2 )
+      (in->pos)++;
+
+    return error;
+  }
+
+
+  /* LookupType 3 */
+
+  /* CursivePosFormat1 */
+
+  FT_Error  Load_CursivePos( TTO_CursivePos*  cp,
+                             FT_Stream        stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort             n, count;
+    FT_ULong              cur_offset, new_offset, base_offset;
+
+    TTO_EntryExitRecord*  eer;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 4L ) )
+      return error;
+
+    cp->PosFormat = GET_UShort();
+    new_offset    = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &cp->Coverage, stream ) ) != TT_Err_Ok )
+      return error;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail2;
+
+    count = cp->EntryExitCount = GET_UShort();
+
+    FORGET_Frame();
+
+    cp->EntryExitRecord = NULL;
+
+    if ( ALLOC_ARRAY( cp->EntryExitRecord, count, TTO_EntryExitRecord ) )
+      goto Fail2;
+
+    eer = cp->EntryExitRecord;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        return error;
+
+      new_offset = GET_UShort();
+
+      FORGET_Frame();
+
+      if ( new_offset )
+      {
+        new_offset += base_offset;
+
+        cur_offset = FILE_Pos();
+        if ( FILE_Seek( new_offset ) ||
+             ( error = Load_Anchor( &eer[n].EntryAnchor,
+                                    stream ) ) != TT_Err_Ok )
+          goto Fail1;
+        (void)FILE_Seek( cur_offset );
+      }
+      else
+        eer[n].EntryAnchor.PosFormat   = 0;
+
+      if ( ACCESS_Frame( 2L ) )
+        return error;
+
+      new_offset = GET_UShort();
+
+      FORGET_Frame();
+
+      if ( new_offset )
+      {
+        new_offset += base_offset;
+
+        cur_offset = FILE_Pos();
+        if ( FILE_Seek( new_offset ) ||
+             ( error = Load_Anchor( &eer[n].ExitAnchor,
+                                    stream ) ) != TT_Err_Ok )
+          goto Fail1;
+        (void)FILE_Seek( cur_offset );
+      }
+      else
+        eer[n].ExitAnchor.PosFormat   = 0;
+    }
+
+    return TT_Err_Ok;
+
+  Fail1:
+    for ( n = 0; n < count; n++ )
+    {
+      Free_Anchor( &eer[n].EntryAnchor, memory );
+      Free_Anchor( &eer[n].ExitAnchor, memory );
+    }
+
+    FREE( eer );
+
+  Fail2:
+    Free_Coverage( &cp->Coverage, memory );
+    return error;
+  }
+
+
+  void  Free_CursivePos( TTO_CursivePos*  cp,
+			 FT_Memory        memory )
+  {
+    FT_UShort             n, count;
+
+    TTO_EntryExitRecord*  eer;
+
+
+    if ( cp->EntryExitRecord )
+    {
+      count = cp->EntryExitCount;
+      eer   = cp->EntryExitRecord;
+
+      for ( n = 0; n < count; n++ )
+      {
+        Free_Anchor( &eer[n].EntryAnchor, memory );
+        Free_Anchor( &eer[n].ExitAnchor, memory );
+      }
+
+      FREE( eer );
+    }
+
+    Free_Coverage( &cp->Coverage, memory );
+  }
+
+
+  static FT_Error  Lookup_CursivePos( GPOS_Instance*    gpi,
+                                      TTO_CursivePos*   cp,
+                                      TTO_GSUB_String*  in,
+                                      TTO_GPOS_Data*    out,
+                                      FT_UShort         flags,
+                                      FT_UShort         context_length )
+  {
+    FT_UShort        index, property;
+    FT_Error         error;
+    TTO_GPOSHeader*  gpos = gpi->gpos;
+
+    TTO_EntryExitRecord*  eer;
+    FT_Pos                entry_x, entry_y;
+    FT_Pos                exit_x, exit_y;
+
+
+    if ( context_length != 0xFFFF && context_length < 1 )
+    {
+      gpi->last = 0xFFFF;
+      return TTO_Err_Not_Covered;
+    }
+
+    /* Glyphs not having the right GDEF properties will be ignored, i.e.,
+       gpi->last won't be reset (contrary to user defined properties). */
+
+    if ( CHECK_Property( gpos->gdef, in->string[in->pos], flags, &property ) )
+      return error;
+
+    /* We don't handle mark glyphs here.  According to Andrei, this isn't
+       possible, but who knows...                                         */
+
+    if ( property == MARK_GLYPH )
+    {
+      gpi->last = 0xFFFF;
+      return TTO_Err_Not_Covered;
+    }
+
+    error = Coverage_Index( &cp->Coverage, in->string[in->pos], &index );
+    if ( error )
+    {
+      gpi->last = 0xFFFF;
+      return error;
+    }
+
+    if ( index >= cp->EntryExitCount )
+      return TTO_Err_Invalid_GPOS_SubTable;
+
+    eer = &cp->EntryExitRecord[index];
+
+    /* Now comes the messiest part of the whole OpenType
+       specification.  At first glance, cursive connections seem easy
+       to understand, but there are pitfalls!  The reason is, that
+       the specs don't mention how to compute the advance values
+       resp. glyph offsets.  I was told it would be an omission, to
+       be fixed in the next OpenType version...  Again many thanks to
+       Andrei Burago <andreib@microsoft.com> for clarifications.
+
+       Consider the following example:
+
+                        |  xadv1    |
+                         +---------+
+                         |         |
+                   +-----+--+ 1    |
+                   |     | .|      |
+                   |    0+--+------+
+                   |   2    |
+                   |        |
+                  0+--------+
+                  |  xadv2   |
+
+         glyph1: advance width = 12
+                 anchor point = (3,1)
+
+         glyph2: advance width = 11
+                 anchor point = (9,4)
+
+         LSB is 1 for both glyphs (so the boxes drawn above are glyph
+         bboxes).  Writing direction is R2L; `0' denotes the glyph's
+         coordinate origin.
+
+       Now the surprising part: The advance width of the *left* glyph
+       (resp. of the *bottom* glyph) will be modified, no matter
+       whether the writing direction is L2R or R2L (resp. T2B or
+       B2T)!  This assymetry is caused by the fact that the glyph's
+       coordinate origin is always the lower left corner for all
+       writing directions.
+
+       Continuing the above example, we can compute the new
+       (horizontal) advance width of glyph2 as
+
+         9 - 3 = 6  ,
+
+       and the new vertical offset of glyph2 as
+
+         1 - 4 = -3  .
+
+
+       Vertical writing direction is far more complicated:
+
+       a) Assuming that we recompute the advance height of the lower glyph:
+
+                                    --
+                         +---------+
+                --       |         |
+                   +-----+--+ 1    | yadv1
+                   |     | .|      |
+             yadv2 |    0+--+------+        -- BSB1  --
+                   |   2    |       --      --        y_offset
+                   |        |
+     BSB2 --      0+--------+                        --
+          --    --
+
+         glyph1: advance height = 6
+                 anchor point = (3,1)
+
+         glyph2: advance height = 7
+                 anchor point = (9,4)
+
+         TSB is 1 for both glyphs; writing direction is T2B.
+
+
+           BSB1     = yadv1 - (TSB1 + ymax1)
+           BSB2     = yadv2 - (TSB2 + ymax2)
+           y_offset = y2 - y1
+
+         vertical advance width of glyph2
+           = y_offset + BSB2 - BSB1
+           = (y2 - y1) + (yadv2 - (TSB2 + ymax2)) - (yadv1 - (TSB1 + ymax1))
+           = y2 - y1 + yadv2 - TSB2 - ymax2 - (yadv1 - TSB1 - ymax1)
+           = y2 - y1 + yadv2 - TSB2 - ymax2 - yadv1 + TSB1 + ymax1
+
+
+       b) Assuming that we recompute the advance height of the upper glyph:
+
+                                    --      --
+                         +---------+        -- TSB1
+          --    --       |         |
+     TSB2 --       +-----+--+ 1    | yadv1   ymax1
+                   |     | .|      |
+             yadv2 |    0+--+------+        --       --
+      ymax2        |   2    |       --                y_offset
+                   |        |
+          --      0+--------+                        --
+                --
+
+         glyph1: advance height = 6
+                 anchor point = (3,1)
+
+         glyph2: advance height = 7
+                 anchor point = (9,4)
+
+         TSB is 1 for both glyphs; writing direction is T2B.
+
+         y_offset = y2 - y1
+
+         vertical advance width of glyph2
+           = TSB1 + ymax1 + y_offset - (TSB2 + ymax2)
+           = TSB1 + ymax1 + y2 - y1 - TSB2 - ymax2
+
+
+       Comparing a) with b) shows that b) is easier to compute.  I'll wait
+       for a reply from Andrei to see what should really be implemented...
+
+       Since horizontal advance widths or vertical advance heights
+       can be used alone but not together, no ambiguity occurs.        */
+
+    if ( gpi->last == 0xFFFF )
+      goto end;
+
+    /* Get_Anchor() returns TTO_Err_Not_Covered if there is no anchor
+       table.                                                         */
+
+    error = Get_Anchor( gpi, &eer->EntryAnchor, in->string[in->pos],
+                        &entry_x, &entry_y );
+    if ( error == TTO_Err_Not_Covered )
+      goto end;
+    if ( error )
+      return error;
+
+    if ( gpi->r2l )
+    {
+      out[in->pos].x_advance   = entry_x - gpi->anchor_x;
+      out[in->pos].new_advance = TRUE;
+    }
+    else
+    {
+      out[gpi->last].x_advance   = gpi->anchor_x - entry_x;
+      out[gpi->last].new_advance = TRUE;
+    }
+
+    out[in->pos].y_pos = gpi->anchor_y - entry_y + out[gpi->last].y_pos;
+
+  end:
+    error = Get_Anchor( gpi, &eer->ExitAnchor, in->string[in->pos],
+                        &exit_x, &exit_y );
+    if ( error == TTO_Err_Not_Covered )
+      gpi->last = 0xFFFF;
+    else
+    {
+      gpi->last     = in->pos;
+      gpi->anchor_x = exit_x;
+      gpi->anchor_y = exit_y;
+    }
+    if ( error )
+      return error;
+
+    (in->pos)++;
+
+    return TT_Err_Ok;
+  }
+
+
+  /* LookupType 4 */
+
+  /* BaseArray */
+
+  static FT_Error  Load_BaseArray( TTO_BaseArray*  ba,
+                                   FT_UShort       num_classes,
+                                   FT_Stream       stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort        m, n, count;
+    FT_ULong         cur_offset, new_offset, base_offset;
+
+    TTO_BaseRecord*  br;
+    TTO_Anchor*      ban;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = ba->BaseCount = GET_UShort();
+    
+    FORGET_Frame();
+
+    ba->BaseRecord = NULL;
+
+    if ( ALLOC_ARRAY( ba->BaseRecord, count, TTO_BaseRecord ) )
+      return error;
+
+    br = ba->BaseRecord;
+
+    for ( m = 0; m < count; m++ )
+    {
+      br[m].BaseAnchor = NULL;
+
+      if ( ALLOC_ARRAY( br[m].BaseAnchor, num_classes, TTO_Anchor ) )
+        goto Fail;
+
+      ban = br[m].BaseAnchor;
+
+      for ( n = 0; n < num_classes; n++ )
+      {
+        if ( ACCESS_Frame( 2L ) )
+          goto Fail;
+
+        new_offset = GET_UShort() + base_offset;
+
+        FORGET_Frame();
+
+        cur_offset = FILE_Pos();
+        if ( FILE_Seek( new_offset ) ||
+             ( error = Load_Anchor( &ban[n], stream ) ) != TT_Err_Ok )
+          goto Fail;
+        (void)FILE_Seek( cur_offset );
+      }
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    for ( m = 0; m < count; m++ )
+    {
+      ban = br[m].BaseAnchor;
+      
+      for ( n = 0; n < num_classes; n++ )
+        Free_Anchor( &ban[n], memory );
+
+      FREE( ban );
+    }
+
+    FREE( br );
+    return error;
+  }
+
+
+  static void  Free_BaseArray( TTO_BaseArray*  ba,
+                               FT_UShort       num_classes,
+			       FT_Memory       memory )
+  {
+    FT_UShort        m, n, count;
+
+    TTO_BaseRecord*  br;
+    TTO_Anchor*      ban;
+
+
+    if ( ba->BaseRecord )
+    {
+      count = ba->BaseCount;
+      br    = ba->BaseRecord;
+
+      for ( m = 0; m < count; m++ )
+      {
+        ban = br[m].BaseAnchor;
+
+        for ( n = 0; n < num_classes; n++ )
+          Free_Anchor( &ban[n], memory );
+
+        FREE( ban );
+      }
+
+      FREE( br );
+    }
+  }
+
+
+  /* MarkBasePosFormat1 */
+
+  FT_Error  Load_MarkBasePos( TTO_MarkBasePos*  mbp,
+                              FT_Stream         stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_ULong  cur_offset, new_offset, base_offset;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 4L ) )
+      return error;
+
+    mbp->PosFormat = GET_UShort();
+    new_offset     = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &mbp->MarkCoverage, stream ) ) != TT_Err_Ok )
+      return error;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail3;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &mbp->BaseCoverage, stream ) ) != TT_Err_Ok )
+      goto Fail3;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 4L ) )
+      goto Fail2;
+
+    mbp->ClassCount = GET_UShort();
+    new_offset      = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_MarkArray( &mbp->MarkArray, stream ) ) != TT_Err_Ok )
+      goto Fail2;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail1;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_BaseArray( &mbp->BaseArray, mbp->ClassCount,
+                                   stream ) ) != TT_Err_Ok )
+      goto Fail1;
+
+    return TT_Err_Ok;
+
+  Fail1:
+    Free_MarkArray( &mbp->MarkArray, memory );
+
+  Fail2:
+    Free_Coverage( &mbp->BaseCoverage, memory );
+
+  Fail3:
+    Free_Coverage( &mbp->MarkCoverage, memory );
+    return error;
+  }
+
+
+  void  Free_MarkBasePos( TTO_MarkBasePos*  mbp,
+			  FT_Memory         memory )
+  {
+    Free_BaseArray( &mbp->BaseArray, mbp->ClassCount, memory );
+    Free_MarkArray( &mbp->MarkArray, memory );
+    Free_Coverage( &mbp->BaseCoverage, memory );
+    Free_Coverage( &mbp->MarkCoverage, memory );
+  }
+
+
+  static FT_Error  Lookup_MarkBasePos( GPOS_Instance*    gpi,
+                                       TTO_MarkBasePos*  mbp,
+                                       TTO_GSUB_String*  in,
+                                       TTO_GPOS_Data*    out,
+                                       FT_UShort         flags,
+                                       FT_UShort         context_length )
+  {
+    FT_UShort        i, j, mark_index, base_index, property, class;
+    FT_Pos           x_mark_value, y_mark_value, x_base_value, y_base_value;
+    FT_Error         error;
+    TTO_GPOSHeader*  gpos = gpi->gpos;
+
+    TTO_MarkArray*   ma;
+    TTO_BaseArray*   ba;
+    TTO_BaseRecord*  br;
+    TTO_Anchor*      mark_anchor;
+    TTO_Anchor*      base_anchor;
+
+    TTO_GPOS_Data*   o;
+
+
+    if ( context_length != 0xFFFF && context_length < 1 )
+      return TTO_Err_Not_Covered;
+
+    if ( flags & IGNORE_BASE_GLYPHS )
+      return TTO_Err_Not_Covered;
+
+    if ( CHECK_Property( gpos->gdef, in->string[in->pos],
+                         flags, &property ) )
+      return error;
+
+    error = Coverage_Index( &mbp->MarkCoverage, in->string[in->pos],
+                            &mark_index );
+    if ( error )
+      return error;
+
+    /* now we search backwards for a base glyph */
+
+    i = 1;
+    j = in->pos - 1;
+
+    while ( i <= in->pos )
+    {
+      error = TT_GDEF_Get_Glyph_Property( gpos->gdef, in->string[j],
+                                          &property );
+      if ( error )
+        return error;
+
+      if ( property != TTO_MARK )
+        break;
+
+      i++;
+      j--;
+    }
+
+    if ( property != TTO_BASE_GLYPH )
+      return TTO_Err_Not_Covered;
+
+    if ( i > in->pos )
+      return TTO_Err_Not_Covered;
+
+    error = Coverage_Index( &mbp->BaseCoverage, in->string[j],
+                            &base_index );
+    if ( error )
+      return error;
+
+    ma = &mbp->MarkArray;
+
+    if ( mark_index >= ma->MarkCount )
+      return TTO_Err_Invalid_GPOS_SubTable;
+
+    class       = ma->MarkRecord[mark_index].Class;
+    mark_anchor = &ma->MarkRecord[mark_index].MarkAnchor;
+
+    if ( class >= mbp->ClassCount )
+      return TTO_Err_Invalid_GPOS_SubTable;
+
+    ba = &mbp->BaseArray;
+
+    if ( base_index >= ba->BaseCount )
+      return TTO_Err_Invalid_GPOS_SubTable;
+
+    br          = &ba->BaseRecord[base_index];
+    base_anchor = &br->BaseAnchor[class];
+
+    error = Get_Anchor( gpi, mark_anchor, in->string[in->pos],
+                        &x_mark_value, &y_mark_value );
+    if ( error )
+      return error;
+    error = Get_Anchor( gpi, base_anchor, in->string[j],
+                        &x_base_value, &y_base_value );
+    if ( error )
+      return error;
+
+    /* anchor points are not cumulative */
+
+    o = &out[in->pos];
+
+    o->x_pos     = x_base_value - x_mark_value;
+    o->y_pos     = y_base_value - y_mark_value;
+    o->x_advance = 0;
+    o->y_advance = 0;
+    o->back      = i;
+
+    (in->pos)++;
+
+    return TT_Err_Ok;
+  }
+
+
+  /* LookupType 5 */
+
+  /* LigatureAttach */
+
+  static FT_Error  Load_LigatureAttach( TTO_LigatureAttach*  lat,
+                                        FT_UShort            num_classes,
+                                        FT_Stream            stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort             m, n, count;
+    FT_ULong              cur_offset, new_offset, base_offset;
+
+    TTO_ComponentRecord*  cr;
+    TTO_Anchor*           lan;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = lat->ComponentCount = GET_UShort();
+    
+    FORGET_Frame();
+
+    lat->ComponentRecord = NULL;
+
+    if ( ALLOC_ARRAY( lat->ComponentRecord, count, TTO_ComponentRecord ) )
+      return error;
+
+    cr = lat->ComponentRecord;
+
+    for ( m = 0; m < count; m++ )
+    {
+      cr[m].LigatureAnchor = NULL;
+
+      if ( ALLOC_ARRAY( cr[m].LigatureAnchor, num_classes, TTO_Anchor ) )
+        goto Fail;
+
+      lan = cr[m].LigatureAnchor;
+
+      for ( n = 0; n < num_classes; n++ )
+      {
+        if ( ACCESS_Frame( 2L ) )
+          goto Fail;
+
+        new_offset = GET_UShort();
+
+        FORGET_Frame();
+
+        if ( new_offset )
+        {
+          new_offset += base_offset;
+
+          cur_offset = FILE_Pos();
+          if ( FILE_Seek( new_offset ) ||
+               ( error = Load_Anchor( &lan[n], stream ) ) != TT_Err_Ok )
+            goto Fail;
+          (void)FILE_Seek( cur_offset );
+        }
+        else
+          lan[n].PosFormat = 0;
+      }
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    for ( m = 0; m < count; m++ )
+    {
+      lan = cr[m].LigatureAnchor;
+      
+      for ( n = 0; n < num_classes; n++ )
+        Free_Anchor( &lan[n], memory );
+
+      FREE( lan );
+    }
+
+    FREE( cr );
+    return error;
+  }
+
+
+  static void  Free_LigatureAttach( TTO_LigatureAttach*  lat,
+                                    FT_UShort            num_classes,
+				    FT_Memory            memory )
+  {
+    FT_UShort        m, n, count;
+
+    TTO_ComponentRecord*  cr;
+    TTO_Anchor*           lan;
+
+
+    if ( lat->ComponentRecord )
+    {
+      count = lat->ComponentCount;
+      cr    = lat->ComponentRecord;
+
+      for ( m = 0; m < count; m++ )
+      {
+        lan = cr[m].LigatureAnchor;
+
+        for ( n = 0; n < num_classes; n++ )
+          Free_Anchor( &lan[n], memory );
+
+        FREE( lan );
+      }
+
+      FREE( cr );
+    }
+  }
+
+
+  /* LigatureArray */
+
+  static FT_Error  Load_LigatureArray( TTO_LigatureArray*  la,
+                                       FT_UShort           num_classes,
+                                       FT_Stream           stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort            n, count;
+    FT_ULong             cur_offset, new_offset, base_offset;
+
+    TTO_LigatureAttach*  lat;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = la->LigatureCount = GET_UShort();
+
+    FORGET_Frame();
+
+    la->LigatureAttach = NULL;
+
+    if ( ALLOC_ARRAY( la->LigatureAttach, count, TTO_LigatureAttach ) )
+      return error;
+
+    lat = la->LigatureAttach;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_LigatureAttach( &lat[n], num_classes,
+                                          stream ) ) != TT_Err_Ok )
+        goto Fail;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    for ( n = 0; n < count; n++ )
+      Free_LigatureAttach( &lat[n], num_classes, memory );
+
+    FREE( lat );
+    return error;
+  }
+
+
+  static void  Free_LigatureArray( TTO_LigatureArray*  la,
+                                   FT_UShort           num_classes,
+				   FT_Memory           memory )
+  {
+    FT_UShort            n, count;
+
+    TTO_LigatureAttach*  lat;
+
+
+    if ( la->LigatureAttach )
+    {
+      count = la->LigatureCount;
+      lat   = la->LigatureAttach;
+
+      for ( n = 0; n < count; n++ )
+        Free_LigatureAttach( &lat[n], num_classes, memory );
+
+      FREE( lat );
+    }
+  }
+
+
+  /* MarkLigPosFormat1 */
+
+  FT_Error  Load_MarkLigPos( TTO_MarkLigPos*  mlp,
+                             FT_Stream        stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_ULong  cur_offset, new_offset, base_offset;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 4L ) )
+      return error;
+
+    mlp->PosFormat = GET_UShort();
+    new_offset     = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &mlp->MarkCoverage, stream ) ) != TT_Err_Ok )
+      return error;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail3;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &mlp->LigatureCoverage,
+                                  stream ) ) != TT_Err_Ok )
+      goto Fail3;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 4L ) )
+      goto Fail2;
+
+    mlp->ClassCount = GET_UShort();
+    new_offset      = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_MarkArray( &mlp->MarkArray, stream ) ) != TT_Err_Ok )
+      goto Fail2;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail1;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_LigatureArray( &mlp->LigatureArray, mlp->ClassCount,
+                                       stream ) ) != TT_Err_Ok )
+      goto Fail1;
+
+    return TT_Err_Ok;
+
+  Fail1:
+    Free_MarkArray( &mlp->MarkArray, memory );
+
+  Fail2:
+    Free_Coverage( &mlp->LigatureCoverage, memory );
+
+  Fail3:
+    Free_Coverage( &mlp->MarkCoverage, memory );
+    return error;
+  }
+
+
+  void  Free_MarkLigPos( TTO_MarkLigPos*  mlp,
+			 FT_Memory        memory)
+  {
+    Free_LigatureArray( &mlp->LigatureArray, mlp->ClassCount, memory );
+    Free_MarkArray( &mlp->MarkArray, memory );
+    Free_Coverage( &mlp->LigatureCoverage, memory );
+    Free_Coverage( &mlp->MarkCoverage, memory );
+  }
+
+
+  static FT_Error  Lookup_MarkLigPos( GPOS_Instance*    gpi,
+                                      TTO_MarkLigPos*   mlp,
+                                      TTO_GSUB_String*  in,
+                                      TTO_GPOS_Data*    out,
+                                      FT_UShort         flags,
+                                      FT_UShort         context_length )
+  {
+    FT_UShort        i, j, mark_index, lig_index, property, class;
+    FT_UShort        mark_glyph;
+    FT_Pos           x_mark_value, y_mark_value, x_lig_value, y_lig_value;
+    FT_Error         error;
+    TTO_GPOSHeader*  gpos = gpi->gpos;
+
+    TTO_MarkArray*        ma;
+    TTO_LigatureArray*    la;
+    TTO_LigatureAttach*   lat;
+    TTO_ComponentRecord*  cr;
+    FT_UShort             comp_index;
+    TTO_Anchor*           mark_anchor;
+    TTO_Anchor*           lig_anchor;
+
+    TTO_GPOS_Data*  o;
+
+
+    if ( context_length != 0xFFFF && context_length < 1 )
+      return TTO_Err_Not_Covered;
+
+    if ( flags & IGNORE_LIGATURES )
+      return TTO_Err_Not_Covered;
+
+    mark_glyph = in->string[in->pos];
+
+    if ( CHECK_Property( gpos->gdef, mark_glyph, flags, &property ) )
+      return error;
+
+    error = Coverage_Index( &mlp->MarkCoverage, mark_glyph, &mark_index );
+    if ( error )
+      return error;
+
+    /* now we search backwards for a ligature */
+
+    i = 1;
+    j = in->pos - 1;
+
+    while ( i <= in->pos )
+    {
+      error = TT_GDEF_Get_Glyph_Property( gpos->gdef, in->string[j],
+                                          &property );
+      if ( error )
+        return error;
+
+      if ( property != TTO_MARK )
+        break;
+
+      i++;
+      j--;
+    }
+
+    if ( property != TTO_LIGATURE )
+      return TTO_Err_Not_Covered;
+
+    if ( i > in->pos )
+      return TTO_Err_Not_Covered;
+
+    error = Coverage_Index( &mlp->LigatureCoverage, in->string[j],
+                            &lig_index );
+    if ( error )
+      return error;
+
+    ma = &mlp->MarkArray;
+
+    if ( mark_index >= ma->MarkCount )
+      return TTO_Err_Invalid_GPOS_SubTable;
+
+    class       = ma->MarkRecord[mark_index].Class;
+    mark_anchor = &ma->MarkRecord[mark_index].MarkAnchor;
+
+    if ( class >= mlp->ClassCount )
+      return TTO_Err_Invalid_GPOS_SubTable;
+
+    la = &mlp->LigatureArray;
+
+    if ( lig_index >= la->LigatureCount )
+      return TTO_Err_Invalid_GPOS_SubTable;
+
+    lat = &la->LigatureAttach[lig_index];
+
+    /* We must now check whether the ligature ID of the current mark glyph
+       is identical to the ligature ID of the found ligature.  If yes, we
+       can directly use the component index.  If not, we attach the mark
+       glyph to the last component of the ligature.                        */
+
+    if ( in->ligIDs && in->components &&
+         in->ligIDs[j] == in->ligIDs[in->pos] )
+    {
+      comp_index = in->components[in->pos];
+      if ( comp_index >= lat->ComponentCount )
+        return TTO_Err_Not_Covered;
+    }
+    else
+      comp_index = lat->ComponentCount - 1;
+
+    cr         = &lat->ComponentRecord[comp_index];
+    lig_anchor = &cr->LigatureAnchor[class];
+
+    error = Get_Anchor( gpi, mark_anchor, in->string[in->pos],
+                        &x_mark_value, &y_mark_value );
+    if ( error )
+      return error;
+    error = Get_Anchor( gpi, lig_anchor, in->string[j],
+                        &x_lig_value, &y_lig_value );
+    if ( error )
+      return error;
+
+    /* anchor points are not cumulative */
+
+    o = &out[in->pos];
+
+    o->x_pos     = x_lig_value - x_mark_value;
+    o->y_pos     = y_lig_value - y_mark_value;
+    o->x_advance = 0;
+    o->y_advance = 0;
+    o->back      = i;
+
+    (in->pos)++;
+
+    return TT_Err_Ok;
+  }
+
+
+  /* LookupType 6 */
+
+  /* Mark2Array */
+
+  static FT_Error  Load_Mark2Array( TTO_Mark2Array*  m2a,
+                                    FT_UShort        num_classes,
+                                    FT_Stream        stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort         m, n, count;
+    FT_ULong          cur_offset, new_offset, base_offset;
+
+    TTO_Mark2Record*  m2r;
+    TTO_Anchor*       m2an;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = m2a->Mark2Count = GET_UShort();
+    
+    FORGET_Frame();
+
+    m2a->Mark2Record = NULL;
+
+    if ( ALLOC_ARRAY( m2a->Mark2Record, count, TTO_Mark2Record ) )
+      return error;
+
+    m2r = m2a->Mark2Record;
+
+    for ( m = 0; m < count; m++ )
+    {
+      m2r[m].Mark2Anchor = NULL;
+
+      if ( ALLOC_ARRAY( m2r[m].Mark2Anchor, num_classes, TTO_Anchor ) )
+        goto Fail;
+
+      m2an = m2r[m].Mark2Anchor;
+
+      for ( n = 0; n < num_classes; n++ )
+      {
+        if ( ACCESS_Frame( 2L ) )
+          goto Fail;
+
+        new_offset = GET_UShort() + base_offset;
+
+        FORGET_Frame();
+
+        cur_offset = FILE_Pos();
+        if ( FILE_Seek( new_offset ) ||
+             ( error = Load_Anchor( &m2an[n], stream ) ) != TT_Err_Ok )
+          goto Fail;
+        (void)FILE_Seek( cur_offset );
+      }
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    for ( m = 0; m < count; m++ )
+    {
+      m2an = m2r[m].Mark2Anchor;
+      
+      for ( n = 0; n < num_classes; n++ )
+        Free_Anchor( &m2an[n], memory );
+
+      FREE( m2an );
+    }
+
+    FREE( m2r );
+    return error;
+  }
+
+
+  static void  Free_Mark2Array( TTO_Mark2Array*  m2a,
+                                FT_UShort        num_classes,
+				FT_Memory        memory )
+  {
+    FT_UShort         m, n, count;
+
+    TTO_Mark2Record*  m2r;
+    TTO_Anchor*       m2an;
+
+
+    if ( m2a->Mark2Record )
+    {
+      count = m2a->Mark2Count;
+      m2r   = m2a->Mark2Record;
+
+      for ( m = 0; m < count; m++ )
+      {
+        m2an = m2r[m].Mark2Anchor;
+
+        for ( n = 0; n < num_classes; n++ )
+          Free_Anchor( &m2an[n], memory );
+
+        FREE( m2an );
+      }
+
+      FREE( m2r );
+    }
+  }
+
+
+  /* MarkMarkPosFormat1 */
+
+  FT_Error  Load_MarkMarkPos( TTO_MarkMarkPos*  mmp,
+                              FT_Stream         stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_ULong  cur_offset, new_offset, base_offset;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 4L ) )
+      return error;
+
+    mmp->PosFormat = GET_UShort();
+    new_offset     = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &mmp->Mark1Coverage,
+                                  stream ) ) != TT_Err_Ok )
+      return error;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail3;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &mmp->Mark2Coverage,
+                                  stream ) ) != TT_Err_Ok )
+      goto Fail3;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 4L ) )
+      goto Fail2;
+
+    mmp->ClassCount = GET_UShort();
+    new_offset      = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_MarkArray( &mmp->Mark1Array, stream ) ) != TT_Err_Ok )
+      goto Fail2;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail1;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Mark2Array( &mmp->Mark2Array, mmp->ClassCount,
+                                    stream ) ) != TT_Err_Ok )
+      goto Fail1;
+
+    return TT_Err_Ok;
+
+  Fail1:
+    Free_MarkArray( &mmp->Mark1Array, memory );
+
+  Fail2:
+    Free_Coverage( &mmp->Mark2Coverage, memory );
+
+  Fail3:
+    Free_Coverage( &mmp->Mark1Coverage, memory );
+    return error;
+  }
+
+
+  void  Free_MarkMarkPos( TTO_MarkMarkPos*  mmp,
+			  FT_Memory         memory)
+  {
+    Free_Mark2Array( &mmp->Mark2Array, mmp->ClassCount, memory );
+    Free_MarkArray( &mmp->Mark1Array, memory );
+    Free_Coverage( &mmp->Mark2Coverage, memory );
+    Free_Coverage( &mmp->Mark1Coverage, memory );
+  }
+
+
+  static FT_Error  Lookup_MarkMarkPos( GPOS_Instance*    gpi,
+                                       TTO_MarkMarkPos*  mmp,
+                                       TTO_GSUB_String*  in,
+                                       TTO_GPOS_Data*    out,
+                                       FT_UShort         flags,
+                                       FT_UShort         context_length )
+  {
+    FT_UShort        j, mark1_index, mark2_index, property, class;
+    FT_Pos           x_mark1_value, y_mark1_value,
+                     x_mark2_value, y_mark2_value;
+    FT_Error         error;
+    TTO_GPOSHeader*  gpos = gpi->gpos;
+
+    TTO_MarkArray*    ma1;
+    TTO_Mark2Array*   ma2;
+    TTO_Mark2Record*  m2r;
+    TTO_Anchor*       mark1_anchor;
+    TTO_Anchor*       mark2_anchor;
+
+    TTO_GPOS_Data*  o;
+
+
+    if ( context_length != 0xFFFF && context_length < 1 )
+      return TTO_Err_Not_Covered;
+
+    if ( flags & IGNORE_MARKS )
+      return TTO_Err_Not_Covered;
+
+    if ( CHECK_Property( gpos->gdef, in->string[in->pos],
+                         flags, &property ) )
+      return error;
+
+    error = Coverage_Index( &mmp->Mark1Coverage, in->string[in->pos],
+                            &mark1_index );
+    if ( error )
+      return error;
+
+    /* now we check the preceding glyph whether it is a suitable
+       mark glyph                                                */
+
+    if ( in->pos == 0 )
+      return TTO_Err_Not_Covered;
+
+    j = in->pos - 1;
+    error = TT_GDEF_Get_Glyph_Property( gpos->gdef, in->string[j],
+                                        &property );
+    if ( error )
+      return error;
+
+    if ( flags & IGNORE_SPECIAL_MARKS )
+    {
+      if ( property != (flags & 0xFF00) )
+        return TTO_Err_Not_Covered;
+    }
+    else
+    {
+      if ( property != TTO_MARK )
+        return TTO_Err_Not_Covered;
+    }
+
+    error = Coverage_Index( &mmp->Mark2Coverage, in->string[j],
+                            &mark2_index );
+    if ( error )
+      return error;
+
+    ma1 = &mmp->Mark1Array;
+
+    if ( mark1_index >= ma1->MarkCount )
+      return TTO_Err_Invalid_GPOS_SubTable;
+
+    class        = ma1->MarkRecord[mark1_index].Class;
+    mark1_anchor = &ma1->MarkRecord[mark1_index].MarkAnchor;
+
+    if ( class >= mmp->ClassCount )
+      return TTO_Err_Invalid_GPOS_SubTable;
+
+    ma2 = &mmp->Mark2Array;
+
+    if ( mark2_index >= ma2->Mark2Count )
+      return TTO_Err_Invalid_GPOS_SubTable;
+
+    m2r          = &ma2->Mark2Record[mark2_index];
+    mark2_anchor = &m2r->Mark2Anchor[class];
+
+    error = Get_Anchor( gpi, mark1_anchor, in->string[in->pos],
+                        &x_mark1_value, &y_mark1_value );
+    if ( error )
+      return error;
+    error = Get_Anchor( gpi, mark2_anchor, in->string[j],
+                        &x_mark2_value, &y_mark2_value );
+    if ( error )
+      return error;
+
+    /* anchor points are not cumulative */
+
+    o = &out[in->pos];
+
+    o->x_pos     = x_mark2_value - x_mark1_value;
+    o->y_pos     = y_mark2_value - y_mark1_value;
+    o->x_advance = 0;
+    o->y_advance = 0;
+    o->back      = 1;
+
+    (in->pos)++;
+
+    return TT_Err_Ok;
+  }
+
+
+  /* Do the actual positioning for a context positioning (either format
+     7 or 8).  This is only called after we've determined that the stream
+     matches the subrule.                                                 */
+
+  static FT_Error  Do_ContextPos( GPOS_Instance*        gpi,
+                                  FT_UShort             GlyphCount,
+                                  FT_UShort             PosCount,
+                                  TTO_PosLookupRecord*  pos,
+                                  TTO_GSUB_String*      in,
+                                  TTO_GPOS_Data*        out,
+                                  int                   nesting_level )
+  {
+    FT_Error  error;
+    FT_UShort i, old_pos;
+
+
+    i = 0;
+
+    while ( i < GlyphCount )
+    {
+      if ( PosCount && i == pos->SequenceIndex )
+      {
+        old_pos = in->pos;
+
+        /* Do a positioning */
+
+        error = Do_Glyph_Lookup( gpi, pos->LookupListIndex, in, out,
+                                 GlyphCount, nesting_level );
+
+        if ( error )
+          return error;
+
+        pos++;
+        PosCount--;
+        i += in->pos - old_pos;
+      }
+      else
+      {
+        i++;
+        (in->pos)++;
+      }
+    }
+
+    return TT_Err_Ok;
+  }
+
+
+  /* LookupType 7 */
+
+  /* PosRule */
+
+  static FT_Error  Load_PosRule( TTO_PosRule*  pr,
+                                 FT_Stream     stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort             n, count;
+    FT_UShort*            i;
+
+    TTO_PosLookupRecord*  plr;
+
+
+    if ( ACCESS_Frame( 4L ) )
+      return error;
+
+    pr->GlyphCount = GET_UShort();
+    pr->PosCount   = GET_UShort();
+
+    FORGET_Frame();
+
+    pr->Input = NULL;
+
+    count = pr->GlyphCount - 1;         /* only GlyphCount - 1 elements */
+
+    if ( ALLOC_ARRAY( pr->Input, count, FT_UShort ) )
+      return error;
+
+    i = pr->Input;
+
+    if ( ACCESS_Frame( count * 2L ) )
+      goto Fail2;
+
+    for ( n = 0; n < count; n++ )
+      i[n] = GET_UShort();
+
+    FORGET_Frame();
+
+    pr->PosLookupRecord = NULL;
+
+    count = pr->PosCount;
+
+    if ( ALLOC_ARRAY( pr->PosLookupRecord, count, TTO_PosLookupRecord ) )
+      goto Fail2;
+
+    plr = pr->PosLookupRecord;
+
+    if ( ACCESS_Frame( count * 4L ) )
+      goto Fail1;
+
+    for ( n = 0; n < count; n++ )
+    {
+      plr[n].SequenceIndex   = GET_UShort();
+      plr[n].LookupListIndex = GET_UShort();
+    }
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+
+  Fail1:
+    FREE( plr );
+
+  Fail2:
+    FREE( i );
+    return error;
+  }
+
+
+  static void  Free_PosRule( TTO_PosRule*  pr,
+			     FT_Memory     memory )
+  {
+    FREE( pr->PosLookupRecord );
+    FREE( pr->Input );
+  }
+
+
+  /* PosRuleSet */
+
+  static FT_Error  Load_PosRuleSet( TTO_PosRuleSet*  prs,
+                                    FT_Stream        stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort     n, count;
+    FT_ULong      cur_offset, new_offset, base_offset;
+
+    TTO_PosRule*  pr;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = prs->PosRuleCount = GET_UShort();
+
+    FORGET_Frame();
+
+    prs->PosRule = NULL;
+
+    if ( ALLOC_ARRAY( prs->PosRule, count, TTO_PosRule ) )
+      return error;
+
+    pr = prs->PosRule;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_PosRule( &pr[n], stream ) ) != TT_Err_Ok )
+        goto Fail;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    for ( n = 0; n < count; n++ )
+      Free_PosRule( &pr[n], memory );
+
+    FREE( pr );
+    return error;
+  }
+
+
+  static void  Free_PosRuleSet( TTO_PosRuleSet*  prs,
+				FT_Memory        memory )
+  {
+    FT_UShort     n, count;
+
+    TTO_PosRule*  pr;
+
+
+    if ( prs->PosRule )
+    {
+      count = prs->PosRuleCount;
+      pr    = prs->PosRule;
+
+      for ( n = 0; n < count; n++ )
+        Free_PosRule( &pr[n], memory );
+
+      FREE( pr );
+    }
+  }
+
+
+  /* ContextPosFormat1 */
+
+  static FT_Error  Load_ContextPos1( TTO_ContextPosFormat1*  cpf1,
+                                     FT_Stream               stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort        n, count;
+    FT_ULong         cur_offset, new_offset, base_offset;
+
+    TTO_PosRuleSet*  prs;
+
+
+    base_offset = FILE_Pos() - 2L;
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &cpf1->Coverage, stream ) ) != TT_Err_Ok )
+      return error;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail2;
+
+    count = cpf1->PosRuleSetCount = GET_UShort();
+
+    FORGET_Frame();
+
+    cpf1->PosRuleSet = NULL;
+
+    if ( ALLOC_ARRAY( cpf1->PosRuleSet, count, TTO_PosRuleSet ) )
+      goto Fail2;
+
+    prs = cpf1->PosRuleSet;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail1;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_PosRuleSet( &prs[n], stream ) ) != TT_Err_Ok )
+        goto Fail1;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail1:
+    for ( n = 0; n < count; n++ )
+      Free_PosRuleSet( &prs[n], memory );
+
+    FREE( prs );
+
+  Fail2:
+    Free_Coverage( &cpf1->Coverage, memory );
+    return error;
+  }
+
+
+  static void  Free_Context1( TTO_ContextPosFormat1*  cpf1,
+			      FT_Memory               memory )
+  {
+    FT_UShort        n, count;
+
+    TTO_PosRuleSet*  prs;
+
+
+    if ( cpf1->PosRuleSet )
+    {
+      count = cpf1->PosRuleSetCount;
+      prs   = cpf1->PosRuleSet;
+
+      for ( n = 0; n < count; n++ )
+        Free_PosRuleSet( &prs[n], memory );
+
+      FREE( prs );
+    }
+
+    Free_Coverage( &cpf1->Coverage, memory );
+  }
+
+
+  /* PosClassRule */
+
+  static FT_Error  Load_PosClassRule( TTO_ContextPosFormat2*  cpf2,
+                                      TTO_PosClassRule*       pcr,
+                                      FT_Stream               stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort             n, count;
+
+    FT_UShort*            c;
+    TTO_PosLookupRecord*  plr;
+    FT_Bool*              d;
+
+
+    if ( ACCESS_Frame( 4L ) )
+      return error;
+
+    pcr->GlyphCount = GET_UShort();
+    pcr->PosCount   = GET_UShort();
+
+    FORGET_Frame();
+
+    if ( pcr->GlyphCount > cpf2->MaxContextLength )
+      cpf2->MaxContextLength = pcr->GlyphCount;
+
+    pcr->Class = NULL;
+
+    count = pcr->GlyphCount - 1;        /* only GlyphCount - 1 elements */
+
+    if ( ALLOC_ARRAY( pcr->Class, count, FT_UShort ) )
+      return error;
+
+    c = pcr->Class;
+    d = cpf2->ClassDef.Defined;
+
+    if ( ACCESS_Frame( count * 2L ) )
+      goto Fail2;
+
+    for ( n = 0; n < count; n++ )
+    {
+      c[n] = GET_UShort();
+
+      /* We check whether the specific class is used at all.  If not,
+         class 0 is used instead.                                     */
+
+      if ( !d[c[n]] )
+        c[n] = 0;
+    }
+
+    FORGET_Frame();
+
+    pcr->PosLookupRecord = NULL;
+
+    count = pcr->PosCount;
+
+    if ( ALLOC_ARRAY( pcr->PosLookupRecord, count, TTO_PosLookupRecord ) )
+      goto Fail2;
+
+    plr = pcr->PosLookupRecord;
+
+    if ( ACCESS_Frame( count * 4L ) )
+      goto Fail1;
+
+    for ( n = 0; n < count; n++ )
+    {
+      plr[n].SequenceIndex   = GET_UShort();
+      plr[n].LookupListIndex = GET_UShort();
+    }
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+
+  Fail1:
+    FREE( plr );
+
+  Fail2:
+    FREE( c );
+    return error;
+  }
+
+
+  static void  Free_PosClassRule( TTO_PosClassRule*  pcr,
+				  FT_Memory          memory )
+  {
+    FREE( pcr->PosLookupRecord );
+    FREE( pcr->Class );
+  }
+
+
+  /* PosClassSet */
+
+  static FT_Error  Load_PosClassSet( TTO_ContextPosFormat2*  cpf2,
+                                     TTO_PosClassSet*        pcs,
+                                     FT_Stream               stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort          n, count;
+    FT_ULong           cur_offset, new_offset, base_offset;
+
+    TTO_PosClassRule*  pcr;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = pcs->PosClassRuleCount = GET_UShort();
+
+    FORGET_Frame();
+
+    pcs->PosClassRule = NULL;
+
+    if ( ALLOC_ARRAY( pcs->PosClassRule, count, TTO_PosClassRule ) )
+      return error;
+
+    pcr = pcs->PosClassRule;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_PosClassRule( cpf2, &pcr[n],
+                                        stream ) ) != TT_Err_Ok )
+        goto Fail;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    for ( n = 0; n < count; n++ )
+      Free_PosClassRule( &pcr[n], memory );
+
+    FREE( pcr );
+    return error;
+  }
+
+
+  static void  Free_PosClassSet( TTO_PosClassSet*  pcs,
+				 FT_Memory         memory )
+  {
+    FT_UShort          n, count;
+
+    TTO_PosClassRule*  pcr;
+
+
+    if ( pcs->PosClassRule )
+    {
+      count = pcs->PosClassRuleCount;
+      pcr   = pcs->PosClassRule;
+
+      for ( n = 0; n < count; n++ )
+        Free_PosClassRule( &pcr[n], memory );
+
+      FREE( pcr );
+    }
+  }
+
+
+  /* ContextPosFormat2 */
+
+  static FT_Error  Load_ContextPos2( TTO_ContextPosFormat2*  cpf2,
+                                     FT_Stream               stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort         n, count;
+    FT_ULong          cur_offset, new_offset, base_offset;
+
+    TTO_PosClassSet*  pcs;
+
+
+    base_offset = FILE_Pos() - 2;
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &cpf2->Coverage, stream ) ) != TT_Err_Ok )
+      return error;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 4L ) )
+      goto Fail3;
+
+    new_offset = GET_UShort() + base_offset;
+
+    /* `PosClassSetCount' is the upper limit for class values, thus we
+       read it now to make an additional safety check.                 */
+
+    count = cpf2->PosClassSetCount = GET_UShort();
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_ClassDefinition( &cpf2->ClassDef, count,
+                                         stream ) ) != TT_Err_Ok )
+      goto Fail3;
+    (void)FILE_Seek( cur_offset );
+
+    cpf2->PosClassSet      = NULL;
+    cpf2->MaxContextLength = 0;
+
+    if ( ALLOC_ARRAY( cpf2->PosClassSet, count, TTO_PosClassSet ) )
+      goto Fail2;
+
+    pcs = cpf2->PosClassSet;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail1;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      if ( new_offset != base_offset )      /* not a NULL offset */
+      {
+        cur_offset = FILE_Pos();
+        if ( FILE_Seek( new_offset ) ||
+             ( error = Load_PosClassSet( cpf2, &pcs[n],
+                                         stream ) ) != TT_Err_Ok )
+          goto Fail1;
+        (void)FILE_Seek( cur_offset );
+      }
+      else
+      {
+        /* we create a PosClassSet table with no entries */
+
+        cpf2->PosClassSet[n].PosClassRuleCount = 0;
+        cpf2->PosClassSet[n].PosClassRule      = NULL;
+      }
+    }
+
+    return TT_Err_Ok;
+
+  Fail1:
+    for ( n = 0; n < count; n++ )
+      Free_PosClassSet( &pcs[n], memory );
+
+    FREE( pcs );
+
+  Fail2:
+    Free_ClassDefinition( &cpf2->ClassDef, memory );
+
+  Fail3:
+    Free_Coverage( &cpf2->Coverage, memory );
+    return error;
+  }
+
+
+  static void  Free_Context2( TTO_ContextPosFormat2*  cpf2,
+			      FT_Memory               memory )
+  {
+    FT_UShort         n, count;
+
+    TTO_PosClassSet*  pcs;
+
+
+    if ( cpf2->PosClassSet )
+    {
+      count = cpf2->PosClassSetCount;
+      pcs   = cpf2->PosClassSet;
+
+      for ( n = 0; n < count; n++ )
+        Free_PosClassSet( &pcs[n], memory );
+
+      FREE( pcs );
+    }
+
+    Free_ClassDefinition( &cpf2->ClassDef, memory );
+    Free_Coverage( &cpf2->Coverage, memory );
+  }
+
+
+  /* ContextPosFormat3 */
+
+  static FT_Error  Load_ContextPos3( TTO_ContextPosFormat3*  cpf3,
+                                     FT_Stream               stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort             n, count;
+    FT_ULong              cur_offset, new_offset, base_offset;
+
+    TTO_Coverage*         c;
+    TTO_PosLookupRecord*  plr;
+
+
+    base_offset = FILE_Pos() - 2L;
+
+    if ( ACCESS_Frame( 4L ) )
+      return error;
+
+    cpf3->GlyphCount = GET_UShort();
+    cpf3->PosCount   = GET_UShort();
+
+    FORGET_Frame();
+
+    cpf3->Coverage = NULL;
+
+    count = cpf3->GlyphCount;
+
+    if ( ALLOC_ARRAY( cpf3->Coverage, count, TTO_Coverage ) )
+      return error;
+
+    c = cpf3->Coverage;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail2;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_Coverage( &c[n], stream ) ) != TT_Err_Ok )
+        goto Fail2;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    cpf3->PosLookupRecord = NULL;
+
+    count = cpf3->PosCount;
+
+    if ( ALLOC_ARRAY( cpf3->PosLookupRecord, count, TTO_PosLookupRecord ) )
+      goto Fail2;
+
+    plr = cpf3->PosLookupRecord;
+
+    if ( ACCESS_Frame( count * 4L ) )
+      goto Fail1;
+
+    for ( n = 0; n < count; n++ )
+    {
+      plr[n].SequenceIndex   = GET_UShort();
+      plr[n].LookupListIndex = GET_UShort();
+    }
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+
+  Fail1:
+    FREE( plr );
+
+  Fail2:
+    for ( n = 0; n < count; n++ )
+      Free_Coverage( &c[n], memory );
+
+    FREE( c );
+    return error;
+  }
+
+
+  static void  Free_Context3( TTO_ContextPosFormat3*  cpf3,
+			      FT_Memory               memory )
+  {
+    FT_UShort      n, count;
+
+    TTO_Coverage*  c;
+
+
+    FREE( cpf3->PosLookupRecord );
+
+    if ( cpf3->Coverage )
+    {
+      count = cpf3->GlyphCount;
+      c     = cpf3->Coverage;
+
+      for ( n = 0; n < count; n++ )
+        Free_Coverage( &c[n], memory );
+
+      FREE( c );
+    }
+  }
+
+
+  /* ContextPos */
+
+  FT_Error  Load_ContextPos( TTO_ContextPos*  cp,
+                             FT_Stream        stream )
+  {
+    FT_Error  error;
+
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    cp->PosFormat = GET_UShort();
+
+    FORGET_Frame();
+
+    switch ( cp->PosFormat )
+    {
+    case 1:
+      return Load_ContextPos1( &cp->cpf.cpf1, stream );
+
+    case 2:
+      return Load_ContextPos2( &cp->cpf.cpf2, stream );
+
+    case 3:
+      return Load_ContextPos3( &cp->cpf.cpf3, stream );
+
+    default:
+      return TTO_Err_Invalid_GPOS_SubTable_Format;
+    }
+
+    return TT_Err_Ok;               /* never reached */
+  }
+
+
+  void  Free_ContextPos( TTO_ContextPos*  cp,
+			 FT_Memory        memory )
+  {
+    switch ( cp->PosFormat )
+    {
+    case 1:
+      Free_Context1( &cp->cpf.cpf1, memory );
+      break;
+
+    case 2:
+      Free_Context2( &cp->cpf.cpf2, memory );
+      break;
+
+    case 3:
+      Free_Context3( &cp->cpf.cpf3, memory );
+      break;
+    }
+  }
+
+
+  static FT_Error  Lookup_ContextPos1( GPOS_Instance*          gpi,
+                                       TTO_ContextPosFormat1*  cpf1,
+                                       TTO_GSUB_String*        in,
+                                       TTO_GPOS_Data*          out,
+                                       FT_UShort               flags,
+                                       FT_UShort               context_length,
+                                       int                     nesting_level )
+  {
+    FT_UShort        index, property;
+    FT_UShort        i, j, k, numpr;
+    FT_Error         error;
+    FT_UShort*       s_in;
+    TTO_GPOSHeader*  gpos = gpi->gpos;
+
+    TTO_PosRule*     pr;
+    TTO_GDEFHeader*  gdef;
+
+
+    gdef = gpos->gdef;
+
+    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
+      return error;
+
+    error = Coverage_Index( &cpf1->Coverage, in->string[in->pos], &index );
+    if ( error )
+      return error;
+
+    pr    = cpf1->PosRuleSet[index].PosRule;
+    numpr = cpf1->PosRuleSet[index].PosRuleCount;
+
+    for ( k = 0; k < numpr; k++ )
+    {
+      if ( context_length != 0xFFFF && context_length < pr[k].GlyphCount )
+        continue;
+
+      if ( in->pos + pr[k].GlyphCount > in->length )
+        continue;                           /* context is too long */
+
+      s_in = &in->string[in->pos];
+
+      for ( i = 1, j = 1; i < pr[k].GlyphCount; i++, j++ )
+      {
+        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+        {
+          if ( error && error != TTO_Err_Not_Covered )
+            return error;
+
+          if ( in->pos + j < in->length )
+            j++;
+          else
+            break;
+        }
+
+        if ( s_in[j] != pr[k].Input[i - 1] )
+          break;
+      }
+
+      if ( i == pr[k].GlyphCount )
+        return Do_ContextPos( gpi, pr[k].GlyphCount,
+                              pr[k].PosCount, pr[k].PosLookupRecord,
+                              in, out,
+                              nesting_level );
+    }
+
+    return TTO_Err_Not_Covered;
+  }
+
+
+  static FT_Error  Lookup_ContextPos2( GPOS_Instance*          gpi,
+                                       TTO_ContextPosFormat2*  cpf2,
+                                       TTO_GSUB_String*        in,
+                                       TTO_GPOS_Data*          out,
+                                       FT_UShort               flags,
+                                       FT_UShort               context_length,
+                                       int                     nesting_level )
+  {
+    FT_UShort          index, property;
+    FT_Error           error;
+    FT_Memory          memory = gpi->face->memory;
+    FT_UShort          i, j, k, known_classes;
+
+    FT_UShort*         classes;
+    FT_UShort*         s_in;
+    FT_UShort*         cl;
+    TTO_GPOSHeader*    gpos = gpi->gpos;
+
+    TTO_PosClassSet*   pcs;
+    TTO_PosClassRule*  pr;
+    TTO_GDEFHeader*    gdef;
+
+
+    gdef = gpos->gdef;
+
+    if ( ALLOC_ARRAY( classes, cpf2->MaxContextLength, FT_UShort ) )
+      return error;
+
+    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
+      return error;
+
+    /* Note: The coverage table in format 2 doesn't give an index into
+             anything.  It just lets us know whether or not we need to
+             do any lookup at all.                                     */
+
+    error = Coverage_Index( &cpf2->Coverage, in->string[in->pos], &index );
+    if ( error )
+      goto End;
+
+    error = Get_Class( &cpf2->ClassDef, in->string[in->pos],
+                       &classes[0], NULL );
+    if ( error )
+      goto End;
+    known_classes = 0;
+
+    pcs = &cpf2->PosClassSet[classes[0]];
+    if ( !pcs )
+    {
+      error = TTO_Err_Invalid_GPOS_SubTable;
+      goto End;
+    }
+
+    for ( k = 0; k < pcs->PosClassRuleCount; k++ )
+    {
+      pr = &pcs->PosClassRule[k];
+
+      if ( context_length != 0xFFFF && context_length < pr->GlyphCount )
+        continue;
+
+      if ( in->pos + pr->GlyphCount > in->length )
+        continue;                           /* context is too long */
+
+      s_in = &in->string[in->pos];
+      cl   = pr->Class;
+
+      /* Start at 1 because [0] is implied */
+
+      for ( i = 1, j = 1; i < pr->GlyphCount; i++, j++ )
+      {
+        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+        {
+          if ( error && error != TTO_Err_Not_Covered )
+            return error;
+
+          if ( in->pos + j < in->length )
+            j++;
+          else
+            break;
+        }
+
+        if ( i > known_classes )
+        {
+          /* Keeps us from having to do this for each rule */
+
+          error = Get_Class( &cpf2->ClassDef, s_in[j], &classes[i], NULL );
+          if ( error && error != TTO_Err_Not_Covered )
+            return error;
+          known_classes = i;
+        }
+
+        if ( cl[i - 1] != classes[i] )
+          break;
+      }
+
+      if ( i == pr->GlyphCount )
+      {
+        error = Do_ContextPos( gpi, pr->GlyphCount,
+                               pr->PosCount, pr->PosLookupRecord,
+                               in, out,
+                               nesting_level );
+        goto End;
+      }
+    }
+
+    error = TTO_Err_Not_Covered;
+
+  End:
+    FREE( classes );
+    return error;
+  }
+
+
+  static FT_Error  Lookup_ContextPos3( GPOS_Instance*          gpi,
+                                       TTO_ContextPosFormat3*  cpf3,
+                                       TTO_GSUB_String*        in,
+                                       TTO_GPOS_Data*          out,
+                                       FT_UShort               flags,
+                                       FT_UShort               context_length,
+                                       int                     nesting_level )
+  {
+    FT_Error         error;
+    FT_UShort        index, i, j, property;
+    FT_UShort*       s_in;
+    TTO_GPOSHeader*  gpos = gpi->gpos;
+
+    TTO_Coverage*    c;
+    TTO_GDEFHeader*  gdef;
+
+
+    gdef = gpos->gdef;
+
+    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
+      return error;
+
+    if ( context_length != 0xFFFF && context_length < cpf3->GlyphCount )
+      return TTO_Err_Not_Covered;
+
+    if ( in->pos + cpf3->GlyphCount > in->length )
+      return TTO_Err_Not_Covered;         /* context is too long */
+
+    s_in = &in->string[in->pos];
+    c    = cpf3->Coverage;
+
+    for ( i = 1, j = 1; i < cpf3->GlyphCount; i++, j++ )
+    {
+      while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+      {
+        if ( error && error != TTO_Err_Not_Covered )
+          return error;
+
+        if ( in->pos + j < in->length )
+          j++;
+        else
+          return TTO_Err_Not_Covered;
+      }
+
+      error = Coverage_Index( &c[i], s_in[j], &index );
+      if ( error )
+        return error;
+    }
+
+    return Do_ContextPos( gpi, cpf3->GlyphCount,
+                          cpf3->PosCount, cpf3->PosLookupRecord,
+                          in, out,
+                          nesting_level );
+  }
+
+
+  static FT_Error  Lookup_ContextPos( GPOS_Instance*    gpi,
+                                      TTO_ContextPos*   cp,
+                                      TTO_GSUB_String*  in,
+                                      TTO_GPOS_Data*    out,
+                                      FT_UShort         flags,
+                                      FT_UShort         context_length,
+                                      int               nesting_level )
+  {
+    switch ( cp->PosFormat )
+    {
+    case 1:
+      return Lookup_ContextPos1( gpi, &cp->cpf.cpf1, in, out,
+                                 flags, context_length, nesting_level );
+
+    case 2:
+      return Lookup_ContextPos2( gpi, &cp->cpf.cpf2, in, out,
+                                 flags, context_length, nesting_level );
+
+    case 3:
+      return Lookup_ContextPos3( gpi, &cp->cpf.cpf3, in, out,
+                                 flags, context_length, nesting_level );
+
+    default:
+      return TTO_Err_Invalid_GPOS_SubTable_Format;
+    }
+
+    return TT_Err_Ok;               /* never reached */
+  }
+
+
+  /* LookupType 8 */
+
+  /* ChainPosRule */
+
+  static FT_Error  Load_ChainPosRule( TTO_ChainPosRule*  cpr,
+                                      FT_Stream          stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort             n, count;
+    FT_UShort*            b;
+    FT_UShort*            i;
+    FT_UShort*            l;
+
+    TTO_PosLookupRecord*  plr;
+
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    cpr->BacktrackGlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    cpr->Backtrack = NULL;
+
+    count = cpr->BacktrackGlyphCount;
+
+    if ( ALLOC_ARRAY( cpr->Backtrack, count, FT_UShort ) )
+      return error;
+
+    b = cpr->Backtrack;
+
+    if ( ACCESS_Frame( count * 2L ) )
+      goto Fail4;
+
+    for ( n = 0; n < count; n++ )
+      b[n] = GET_UShort();
+
+    FORGET_Frame();
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail4;
+
+    cpr->InputGlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    cpr->Input = NULL;
+
+    count = cpr->InputGlyphCount - 1;  /* only InputGlyphCount - 1 elements */
+
+    if ( ALLOC_ARRAY( cpr->Input, count, FT_UShort ) )
+      goto Fail4;
+
+    i = cpr->Input;
+
+    if ( ACCESS_Frame( count * 2L ) )
+      goto Fail3;
+
+    for ( n = 0; n < count; n++ )
+      i[n] = GET_UShort();
+
+    FORGET_Frame();
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail3;
+
+    cpr->LookaheadGlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    cpr->Lookahead = NULL;
+
+    count = cpr->LookaheadGlyphCount;
+
+    if ( ALLOC_ARRAY( cpr->Lookahead, count, FT_UShort ) )
+      goto Fail3;
+
+    l = cpr->Lookahead;
+
+    if ( ACCESS_Frame( count * 2L ) )
+      goto Fail2;
+
+    for ( n = 0; n < count; n++ )
+      l[n] = GET_UShort();
+
+    FORGET_Frame();
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail2;
+
+    cpr->PosCount = GET_UShort();
+
+    FORGET_Frame();
+
+    cpr->PosLookupRecord = NULL;
+
+    count = cpr->PosCount;
+
+    if ( ALLOC_ARRAY( cpr->PosLookupRecord, count, TTO_PosLookupRecord ) )
+      goto Fail2;
+
+    plr = cpr->PosLookupRecord;
+
+    if ( ACCESS_Frame( count * 4L ) )
+      goto Fail1;
+
+    for ( n = 0; n < count; n++ )
+    {
+      plr[n].SequenceIndex   = GET_UShort();
+      plr[n].LookupListIndex = GET_UShort();
+    }
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+
+  Fail1:
+    FREE( plr );
+
+  Fail2:
+    FREE( l );
+
+  Fail3:
+    FREE( i );
+
+  Fail4:
+    FREE( b );
+    return error;
+  }
+
+
+  static void  Free_ChainPosRule( TTO_ChainPosRule*  cpr,
+				  FT_Memory          memory )
+  {
+    FREE( cpr->PosLookupRecord );
+    FREE( cpr->Lookahead );
+    FREE( cpr->Input );
+    FREE( cpr->Backtrack );
+  }
+
+
+  /* ChainPosRuleSet */
+
+  static FT_Error  Load_ChainPosRuleSet( TTO_ChainPosRuleSet*  cprs,
+                                         FT_Stream             stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort          n, count;
+    FT_ULong           cur_offset, new_offset, base_offset;
+
+    TTO_ChainPosRule*  cpr;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = cprs->ChainPosRuleCount = GET_UShort();
+
+    FORGET_Frame();
+
+    cprs->ChainPosRule = NULL;
+
+    if ( ALLOC_ARRAY( cprs->ChainPosRule, count, TTO_ChainPosRule ) )
+      return error;
+
+    cpr = cprs->ChainPosRule;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_ChainPosRule( &cpr[n], stream ) ) != TT_Err_Ok )
+        goto Fail;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    for ( n = 0; n < count; n++ )
+      Free_ChainPosRule( &cpr[n], memory );
+
+    FREE( cpr );
+    return error;
+  }
+
+
+  static void  Free_ChainPosRuleSet( TTO_ChainPosRuleSet*  cprs,
+				     FT_Memory             memory )
+  {
+    FT_UShort          n, count;
+
+    TTO_ChainPosRule*  cpr;
+
+
+    if ( cprs->ChainPosRule )
+    {
+      count = cprs->ChainPosRuleCount;
+      cpr   = cprs->ChainPosRule;
+
+      for ( n = 0; n < count; n++ )
+        Free_ChainPosRule( &cpr[n], memory );
+
+      FREE( cpr );
+    }
+  }
+
+
+  /* ChainContextPosFormat1 */
+
+  static FT_Error  Load_ChainContextPos1( TTO_ChainContextPosFormat1*  ccpf1,
+                                          FT_Stream                    stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort             n, count;
+    FT_ULong              cur_offset, new_offset, base_offset;
+
+    TTO_ChainPosRuleSet*  cprs;
+
+
+    base_offset = FILE_Pos() - 2L;
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &ccpf1->Coverage, stream ) ) != TT_Err_Ok )
+      return error;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail2;
+
+    count = ccpf1->ChainPosRuleSetCount = GET_UShort();
+
+    FORGET_Frame();
+
+    ccpf1->ChainPosRuleSet = NULL;
+
+    if ( ALLOC_ARRAY( ccpf1->ChainPosRuleSet, count, TTO_ChainPosRuleSet ) )
+      goto Fail2;
+
+    cprs = ccpf1->ChainPosRuleSet;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail1;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_ChainPosRuleSet( &cprs[n], stream ) ) != TT_Err_Ok )
+        goto Fail1;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail1:
+    for ( n = 0; n < count; n++ )
+      Free_ChainPosRuleSet( &cprs[n], memory );
+
+    FREE( cprs );
+
+  Fail2:
+    Free_Coverage( &ccpf1->Coverage, memory );
+    return error;
+  }
+
+
+  static void  Free_ChainContext1( TTO_ChainContextPosFormat1*  ccpf1,
+				   FT_Memory                    memory )
+  {
+    FT_UShort             n, count;
+
+    TTO_ChainPosRuleSet*  cprs;
+
+
+    if ( ccpf1->ChainPosRuleSet )
+    {
+      count = ccpf1->ChainPosRuleSetCount;
+      cprs  = ccpf1->ChainPosRuleSet;
+
+      for ( n = 0; n < count; n++ )
+        Free_ChainPosRuleSet( &cprs[n], memory );
+
+      FREE( cprs );
+    }
+
+    Free_Coverage( &ccpf1->Coverage, memory );
+  }
+
+
+  /* ChainPosClassRule */
+
+  static FT_Error  Load_ChainPosClassRule(
+                     TTO_ChainContextPosFormat2*  ccpf2,
+                     TTO_ChainPosClassRule*       cpcr,
+                     FT_Stream                    stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort             n, count;
+
+    FT_UShort*            b;
+    FT_UShort*            i;
+    FT_UShort*            l;
+    TTO_PosLookupRecord*  plr;
+    FT_Bool*              d;
+
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    cpcr->BacktrackGlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    if ( cpcr->BacktrackGlyphCount > ccpf2->MaxBacktrackLength )
+      ccpf2->MaxBacktrackLength = cpcr->BacktrackGlyphCount;
+
+    cpcr->Backtrack = NULL;
+
+    count = cpcr->BacktrackGlyphCount;
+
+    if ( ALLOC_ARRAY( cpcr->Backtrack, count, FT_UShort ) )
+      return error;
+
+    b = cpcr->Backtrack;
+    d = ccpf2->BacktrackClassDef.Defined;
+
+    if ( ACCESS_Frame( count * 2L ) )
+      goto Fail4;
+
+    for ( n = 0; n < count; n++ )
+    {
+      b[n] = GET_UShort();
+
+      /* We check whether the specific class is used at all.  If not,
+         class 0 is used instead.                                     */
+
+      if ( !d[b[n]] )
+        b[n] = 0;
+    }
+
+    FORGET_Frame();
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail4;
+
+    cpcr->InputGlyphCount = GET_UShort();
+
+    if ( cpcr->InputGlyphCount > ccpf2->MaxInputLength )
+      ccpf2->MaxInputLength = cpcr->InputGlyphCount;
+
+    FORGET_Frame();
+
+    cpcr->Input = NULL;
+
+    count = cpcr->InputGlyphCount - 1; /* only InputGlyphCount - 1 elements */
+
+    if ( ALLOC_ARRAY( cpcr->Input, count, FT_UShort ) )
+      goto Fail4;
+
+    i = cpcr->Input;
+    d = ccpf2->InputClassDef.Defined;
+
+    if ( ACCESS_Frame( count * 2L ) )
+      goto Fail3;
+
+    for ( n = 0; n < count; n++ )
+    {
+      i[n] = GET_UShort();
+
+      if ( !d[i[n]] )
+        i[n] = 0;
+    }
+
+    FORGET_Frame();
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail3;
+
+    cpcr->LookaheadGlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    if ( cpcr->LookaheadGlyphCount > ccpf2->MaxLookaheadLength )
+      ccpf2->MaxLookaheadLength = cpcr->LookaheadGlyphCount;
+
+    cpcr->Lookahead = NULL;
+
+    count = cpcr->LookaheadGlyphCount;
+
+    if ( ALLOC_ARRAY( cpcr->Lookahead, count, FT_UShort ) )
+      goto Fail3;
+
+    l = cpcr->Lookahead;
+    d = ccpf2->LookaheadClassDef.Defined;
+
+    if ( ACCESS_Frame( count * 2L ) )
+      goto Fail2;
+
+    for ( n = 0; n < count; n++ )
+    {
+      l[n] = GET_UShort();
+
+      if ( !d[l[n]] )
+        l[n] = 0;
+    }
+
+    FORGET_Frame();
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail2;
+
+    cpcr->PosCount = GET_UShort();
+
+    FORGET_Frame();
+
+    cpcr->PosLookupRecord = NULL;
+
+    count = cpcr->PosCount;
+
+    if ( ALLOC_ARRAY( cpcr->PosLookupRecord, count, TTO_PosLookupRecord ) )
+      goto Fail2;
+
+    plr = cpcr->PosLookupRecord;
+
+    if ( ACCESS_Frame( count * 4L ) )
+      goto Fail1;
+
+    for ( n = 0; n < count; n++ )
+    {
+      plr[n].SequenceIndex   = GET_UShort();
+      plr[n].LookupListIndex = GET_UShort();
+    }
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+
+  Fail1:
+    FREE( plr );
+
+  Fail2:
+    FREE( l );
+
+  Fail3:
+    FREE( i );
+
+  Fail4:
+    FREE( b );
+    return error;
+  }
+
+
+  static void  Free_ChainPosClassRule( TTO_ChainPosClassRule*  cpcr,
+				       FT_Memory               memory )
+  {
+    FREE( cpcr->PosLookupRecord );
+    FREE( cpcr->Lookahead );
+    FREE( cpcr->Input );
+    FREE( cpcr->Backtrack );
+  }
+
+
+  /* PosClassSet */
+
+  static FT_Error  Load_ChainPosClassSet(
+                     TTO_ChainContextPosFormat2*  ccpf2,
+                     TTO_ChainPosClassSet*        cpcs,
+                     FT_Stream                    stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort               n, count;
+    FT_ULong                cur_offset, new_offset, base_offset;
+
+    TTO_ChainPosClassRule*  cpcr;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = cpcs->ChainPosClassRuleCount = GET_UShort();
+
+    FORGET_Frame();
+
+    cpcs->ChainPosClassRule = NULL;
+
+    if ( ALLOC_ARRAY( cpcs->ChainPosClassRule, count,
+                      TTO_ChainPosClassRule ) )
+      return error;
+
+    cpcr = cpcs->ChainPosClassRule;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_ChainPosClassRule( ccpf2, &cpcr[n],
+                                             stream ) ) != TT_Err_Ok )
+        goto Fail;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    for ( n = 0; n < count; n++ )
+      Free_ChainPosClassRule( &cpcr[n], memory );
+
+    FREE( cpcr );
+    return error;
+  }
+
+
+  static void  Free_ChainPosClassSet( TTO_ChainPosClassSet*  cpcs,
+				      FT_Memory              memory )
+  {
+    FT_UShort               n, count;
+
+    TTO_ChainPosClassRule*  cpcr;
+
+
+    if ( cpcs->ChainPosClassRule )
+    {
+      count = cpcs->ChainPosClassRuleCount;
+      cpcr  = cpcs->ChainPosClassRule;
+
+      for ( n = 0; n < count; n++ )
+        Free_ChainPosClassRule( &cpcr[n], memory );
+
+      FREE( cpcr );
+    }
+  }
+
+
+  /* ChainContextPosFormat2 */
+
+  static FT_Error  Load_ChainContextPos2( TTO_ChainContextPosFormat2*  ccpf2,
+                                          FT_Stream                    stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort              n, count;
+    FT_ULong               cur_offset, new_offset, base_offset;
+    FT_ULong               backtrack_offset, input_offset, lookahead_offset;
+
+    TTO_ChainPosClassSet*  cpcs;
+
+
+    base_offset = FILE_Pos() - 2;
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &ccpf2->Coverage, stream ) ) != TT_Err_Ok )
+      return error;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 8L ) )
+      goto Fail5;
+
+    backtrack_offset = GET_UShort() + base_offset;
+    input_offset     = GET_UShort() + base_offset;
+    lookahead_offset = GET_UShort() + base_offset;
+
+    /* `ChainPosClassSetCount' is the upper limit for input class values,
+       thus we read it now to make an additional safety check.            */
+
+    count = ccpf2->ChainPosClassSetCount = GET_UShort();
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( backtrack_offset ) ||
+         ( error = Load_ClassDefinition( &ccpf2->BacktrackClassDef, count,
+                                         stream ) ) != TT_Err_Ok )
+      goto Fail5;
+    if ( FILE_Seek( input_offset ) ||
+         ( error = Load_ClassDefinition( &ccpf2->InputClassDef, count,
+                                         stream ) ) != TT_Err_Ok )
+      goto Fail4;
+    if ( FILE_Seek( lookahead_offset ) ||
+         ( error = Load_ClassDefinition( &ccpf2->LookaheadClassDef, count,
+                                         stream ) ) != TT_Err_Ok )
+      goto Fail3;
+    (void)FILE_Seek( cur_offset );
+
+    ccpf2->ChainPosClassSet   = NULL;
+    ccpf2->MaxBacktrackLength = 0;
+    ccpf2->MaxInputLength     = 0;
+    ccpf2->MaxLookaheadLength = 0;
+
+    if ( ALLOC_ARRAY( ccpf2->ChainPosClassSet, count, TTO_ChainPosClassSet ) )
+      goto Fail2;
+
+    cpcs = ccpf2->ChainPosClassSet;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail1;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      if ( new_offset != base_offset )      /* not a NULL offset */
+      {
+        cur_offset = FILE_Pos();
+        if ( FILE_Seek( new_offset ) ||
+             ( error = Load_ChainPosClassSet( ccpf2, &cpcs[n],
+                                              stream ) ) != TT_Err_Ok )
+          goto Fail1;
+        (void)FILE_Seek( cur_offset );
+      }
+      else
+      {
+        /* we create a ChainPosClassSet table with no entries */
+
+        ccpf2->ChainPosClassSet[n].ChainPosClassRuleCount = 0;
+        ccpf2->ChainPosClassSet[n].ChainPosClassRule      = NULL;
+      }
+    }
+
+    return TT_Err_Ok;
+
+  Fail1:
+    for ( n = 0; n < count; n++ )
+      Free_ChainPosClassSet( &cpcs[n], memory );
+
+    FREE( cpcs );
+
+  Fail2:
+    Free_ClassDefinition( &ccpf2->LookaheadClassDef, memory );
+
+  Fail3:
+    Free_ClassDefinition( &ccpf2->InputClassDef, memory );
+
+  Fail4:
+    Free_ClassDefinition( &ccpf2->BacktrackClassDef, memory );
+
+  Fail5:
+    Free_Coverage( &ccpf2->Coverage, memory );
+    return error;
+  }
+
+
+  static void  Free_ChainContext2( TTO_ChainContextPosFormat2*  ccpf2,
+				   FT_Memory                    memory )
+  {
+    FT_UShort              n, count;
+
+    TTO_ChainPosClassSet*  cpcs;
+
+
+    if ( ccpf2->ChainPosClassSet )
+    {
+      count = ccpf2->ChainPosClassSetCount;
+      cpcs  = ccpf2->ChainPosClassSet;
+
+      for ( n = 0; n < count; n++ )
+        Free_ChainPosClassSet( &cpcs[n], memory );
+
+      FREE( cpcs );
+    }
+
+    Free_ClassDefinition( &ccpf2->LookaheadClassDef, memory );
+    Free_ClassDefinition( &ccpf2->InputClassDef, memory );
+    Free_ClassDefinition( &ccpf2->BacktrackClassDef, memory );
+
+    Free_Coverage( &ccpf2->Coverage, memory );
+  }
+
+
+  /* ChainContextPosFormat3 */
+
+  static FT_Error  Load_ChainContextPos3( TTO_ChainContextPosFormat3*  ccpf3,
+                                          FT_Stream                    stream )
+  {
+    FT_Error  error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort             n, count;
+    FT_UShort             backtrack_count, input_count, lookahead_count;
+    FT_ULong              cur_offset, new_offset, base_offset;
+
+    TTO_Coverage*         b;
+    TTO_Coverage*         i;
+    TTO_Coverage*         l;
+    TTO_PosLookupRecord*  plr;
+
+
+    base_offset = FILE_Pos() - 2L;
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    ccpf3->BacktrackGlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    ccpf3->BacktrackCoverage = NULL;
+
+    backtrack_count = ccpf3->BacktrackGlyphCount;
+
+    if ( ALLOC_ARRAY( ccpf3->BacktrackCoverage, backtrack_count,
+                      TTO_Coverage ) )
+      return error;
+
+    b = ccpf3->BacktrackCoverage;
+
+    for ( n = 0; n < backtrack_count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail4;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_Coverage( &b[n], stream ) ) != TT_Err_Ok )
+        goto Fail4;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail4;
+
+    ccpf3->InputGlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    ccpf3->InputCoverage = NULL;
+
+    input_count = ccpf3->InputGlyphCount;
+
+    if ( ALLOC_ARRAY( ccpf3->InputCoverage, input_count, TTO_Coverage ) )
+      goto Fail4;
+
+    i = ccpf3->InputCoverage;
+
+    for ( n = 0; n < input_count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail3;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_Coverage( &i[n], stream ) ) != TT_Err_Ok )
+        goto Fail3;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail3;
+
+    ccpf3->LookaheadGlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    ccpf3->LookaheadCoverage = NULL;
+
+    lookahead_count = ccpf3->LookaheadGlyphCount;
+
+    if ( ALLOC_ARRAY( ccpf3->LookaheadCoverage, lookahead_count,
+                      TTO_Coverage ) )
+      goto Fail3;
+
+    l = ccpf3->LookaheadCoverage;
+
+    for ( n = 0; n < lookahead_count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail2;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_Coverage( &l[n], stream ) ) != TT_Err_Ok )
+        goto Fail2;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail2;
+
+    ccpf3->PosCount = GET_UShort();
+
+    FORGET_Frame();
+
+    ccpf3->PosLookupRecord = NULL;
+
+    count = ccpf3->PosCount;
+
+    if ( ALLOC_ARRAY( ccpf3->PosLookupRecord, count, TTO_PosLookupRecord ) )
+      goto Fail2;
+
+    plr = ccpf3->PosLookupRecord;
+
+    if ( ACCESS_Frame( count * 4L ) )
+      goto Fail1;
+
+    for ( n = 0; n < count; n++ )
+    {
+      plr[n].SequenceIndex   = GET_UShort();
+      plr[n].LookupListIndex = GET_UShort();
+    }
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+
+  Fail1:
+    FREE( plr );
+
+  Fail2:
+    for ( n = 0; n < lookahead_count; n++ )
+      Free_Coverage( &l[n], memory );
+
+    FREE( l );
+
+  Fail3:
+    for ( n = 0; n < input_count; n++ )
+      Free_Coverage( &i[n], memory );
+
+    FREE( i );
+
+  Fail4:
+    for ( n = 0; n < backtrack_count; n++ )
+      Free_Coverage( &b[n], memory );
+
+    FREE( b );
+    return error;
+  }
+
+
+  static void  Free_ChainContext3( TTO_ChainContextPosFormat3*  ccpf3,
+				   FT_Memory                    memory )
+  {
+    FT_UShort      n, count;
+
+    TTO_Coverage*  c;
+
+
+    FREE( ccpf3->PosLookupRecord );
+
+    if ( ccpf3->LookaheadCoverage )
+    {
+      count = ccpf3->LookaheadGlyphCount;
+      c     = ccpf3->LookaheadCoverage;
+
+      for ( n = 0; n < count; n++ )
+        Free_Coverage( &c[n], memory );
+
+      FREE( c );
+    }
+
+    if ( ccpf3->InputCoverage )
+    {
+      count = ccpf3->InputGlyphCount;
+      c     = ccpf3->InputCoverage;
+
+      for ( n = 0; n < count; n++ )
+        Free_Coverage( &c[n], memory );
+
+      FREE( c );
+    }
+
+    if ( ccpf3->BacktrackCoverage )
+    {
+      count = ccpf3->BacktrackGlyphCount;
+      c     = ccpf3->BacktrackCoverage;
+
+      for ( n = 0; n < count; n++ )
+        Free_Coverage( &c[n], memory );
+
+      FREE( c );
+    }
+  }
+
+
+  /* ChainContextPos */
+
+  FT_Error  Load_ChainContextPos( TTO_ChainContextPos*  ccp,
+                                  FT_Stream             stream )
+  {
+    FT_Error  error;
+
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    ccp->PosFormat = GET_UShort();
+
+    FORGET_Frame();
+
+    switch ( ccp->PosFormat )
+    {
+    case 1:
+      return Load_ChainContextPos1( &ccp->ccpf.ccpf1, stream );
+
+    case 2:
+      return Load_ChainContextPos2( &ccp->ccpf.ccpf2, stream );
+
+    case 3:
+      return Load_ChainContextPos3( &ccp->ccpf.ccpf3, stream );
+
+    default:
+      return TTO_Err_Invalid_GPOS_SubTable_Format;
+    }
+
+    return TT_Err_Ok;               /* never reached */
+  }
+
+
+  void  Free_ChainContextPos( TTO_ChainContextPos*  ccp,
+			      FT_Memory             memory )
+  {
+    switch ( ccp->PosFormat )
+    {
+    case 1:
+      Free_ChainContext1( &ccp->ccpf.ccpf1, memory );
+      break;
+
+    case 2:
+      Free_ChainContext2( &ccp->ccpf.ccpf2, memory );
+      break;
+
+    case 3:
+      Free_ChainContext3( &ccp->ccpf.ccpf3, memory );
+      break;
+    }
+  }
+
+
+  static FT_Error  Lookup_ChainContextPos1(
+                     GPOS_Instance*               gpi,
+                     TTO_ChainContextPosFormat1*  ccpf1,
+                     TTO_GSUB_String*             in,
+                     TTO_GPOS_Data*               out,
+                     FT_UShort                    flags,
+                     FT_UShort                    context_length,
+                     int                          nesting_level )
+  {
+    FT_UShort          index, property;
+    FT_UShort          i, j, k, num_cpr, curr_pos;
+    FT_UShort          bgc, igc, lgc;
+    FT_Error           error;
+    FT_UShort*         s_in;
+    TTO_GPOSHeader*    gpos = gpi->gpos;
+
+    TTO_ChainPosRule*  cpr;
+    TTO_ChainPosRule   curr_cpr;
+    TTO_GDEFHeader*    gdef;
+
+
+    gdef = gpos->gdef;
+
+    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
+      return error;
+
+    error = Coverage_Index( &ccpf1->Coverage, in->string[in->pos], &index );
+    if ( error )
+      return error;
+
+    cpr     = ccpf1->ChainPosRuleSet[index].ChainPosRule;
+    num_cpr = ccpf1->ChainPosRuleSet[index].ChainPosRuleCount;
+
+    for ( k = 0; k < num_cpr; k++ )
+    {
+      curr_cpr = cpr[k];
+      bgc      = curr_cpr.BacktrackGlyphCount;
+      igc      = curr_cpr.InputGlyphCount;
+      lgc      = curr_cpr.LookaheadGlyphCount;
+
+      if ( context_length != 0xFFFF && context_length < igc )
+        continue;
+
+      /* check whether context is too long; it is a first guess only */
+
+      if ( bgc > in->pos || in->pos + igc + lgc > in->length )
+        continue;
+
+      if ( bgc )
+      {
+        /* Since we don't know in advance the number of glyphs to inspect,
+           we search backwards for matches in the backtrack glyph array    */
+
+        curr_pos = 0;
+        s_in     = &in->string[curr_pos];
+
+        for ( i = bgc, j = in->pos - 1; i > 0; i--, j-- )
+        {
+          while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+          {
+            if ( error && error != TTO_Err_Not_Covered )
+              return error;
+
+            if ( j > curr_pos )
+              j--;
+            else
+              break;
+          }
+
+          if ( s_in[j] != curr_cpr.Backtrack[i - 1] )
+            break;
+        }
+
+        if ( i != 0 )
+          continue;
+      }
+
+      curr_pos = in->pos;
+      s_in     = &in->string[curr_pos];
+
+      /* Start at 1 because [0] is implied */
+
+      for ( i = 1, j = 1; i < igc; i++, j++ )
+      {
+        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+        {
+          if ( error && error != TTO_Err_Not_Covered )
+            return error;
+
+          if ( curr_pos + j < in->length )
+            j++;
+          else
+            break;
+        }
+
+        if ( s_in[j] != curr_cpr.Input[i - 1] )
+          break;
+      }
+
+      if ( i != igc )
+        continue;
+
+      /* we are starting to check for lookahead glyphs right after the
+         last context glyph                                            */
+
+      curr_pos = j;
+      s_in     = &in->string[curr_pos];
+
+      for ( i = 0, j = 0; i < lgc; i++, j++ )
+      {
+        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+        {
+          if ( error && error != TTO_Err_Not_Covered )
+            return error;
+
+          if ( curr_pos + j < in->length )
+            j++;
+          else
+            break;
+        }
+
+        if ( s_in[j] != curr_cpr.Lookahead[i] )
+          break;
+      }
+
+      if ( i == lgc )
+        return Do_ContextPos( gpi, igc,
+                              curr_cpr.PosCount,
+                              curr_cpr.PosLookupRecord,
+                              in, out,
+                              nesting_level );
+    }
+
+    return TTO_Err_Not_Covered;
+  }
+
+
+  static FT_Error  Lookup_ChainContextPos2(
+                     GPOS_Instance*               gpi,
+                     TTO_ChainContextPosFormat2*  ccpf2,
+                     TTO_GSUB_String*             in,
+                     TTO_GPOS_Data*               out,
+                     FT_UShort                    flags,
+                     FT_UShort                    context_length,
+                     int                          nesting_level )
+  {
+    FT_UShort              index, property;
+    FT_Memory              memory = gpi->face->memory;
+    FT_Error               error;
+    FT_UShort              i, j, k, curr_pos;
+    FT_UShort              bgc, igc, lgc;
+    FT_UShort              known_backtrack_classes,
+                           known_input_classes,
+                           known_lookahead_classes;
+
+    FT_UShort*             backtrack_classes;
+    FT_UShort*             input_classes;
+    FT_UShort*             lookahead_classes;
+
+    FT_UShort*             s_in;
+
+    FT_UShort*             bc;
+    FT_UShort*             ic;
+    FT_UShort*             lc;
+    TTO_GPOSHeader*        gpos = gpi->gpos;
+
+    TTO_ChainPosClassSet*  cpcs;
+    TTO_ChainPosClassRule  cpcr;
+    TTO_GDEFHeader*        gdef;
+
+
+    gdef = gpos->gdef;
+
+    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
+      return error;
+
+    /* Note: The coverage table in format 2 doesn't give an index into
+             anything.  It just lets us know whether or not we need to
+             do any lookup at all.                                     */
+
+    error = Coverage_Index( &ccpf2->Coverage, in->string[in->pos], &index );
+    if ( error )
+      return error;
+
+    if ( ALLOC_ARRAY( backtrack_classes, ccpf2->MaxBacktrackLength, FT_UShort ) )
+      return error;
+    known_backtrack_classes = 0;
+
+    if ( ALLOC_ARRAY( input_classes, ccpf2->MaxInputLength, FT_UShort ) )
+      goto End3;
+    known_input_classes = 1;
+
+    if ( ALLOC_ARRAY( lookahead_classes, ccpf2->MaxLookaheadLength, FT_UShort ) )
+      goto End2;
+    known_lookahead_classes = 0;
+
+    error = Get_Class( &ccpf2->InputClassDef, in->string[in->pos],
+                       &input_classes[0], NULL );
+    if ( error )
+      goto End1;
+
+    cpcs = &ccpf2->ChainPosClassSet[input_classes[0]];
+    if ( !cpcs )
+    {
+      error = TTO_Err_Invalid_GPOS_SubTable;
+      goto End1;
+    }
+
+    for ( k = 0; k < cpcs->ChainPosClassRuleCount; k++ )
+    {
+      cpcr = cpcs->ChainPosClassRule[k];
+      bgc  = cpcr.BacktrackGlyphCount;
+      igc  = cpcr.InputGlyphCount;
+      lgc  = cpcr.LookaheadGlyphCount;
+
+      if ( context_length != 0xFFFF && context_length < igc )
+        continue;
+
+      /* check whether context is too long; it is a first guess only */
+
+      if ( bgc > in->pos || in->pos + igc + lgc > in->length )
+        continue;
+
+      if ( bgc )
+      {
+        /* Since we don't know in advance the number of glyphs to inspect,
+           we search backwards for matches in the backtrack glyph array.
+           Note that `known_backtrack_classes' starts at index 0.         */
+
+        curr_pos = 0;
+        s_in     = &in->string[curr_pos];
+        bc       = cpcr.Backtrack;
+
+        for ( i = 0, j = in->pos - 1; i < bgc; i++, j-- )
+        {
+          while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+          {
+            if ( error && error != TTO_Err_Not_Covered )
+              return error;
+
+            if ( j > curr_pos )
+              j--;
+            else
+              break;
+          }
+
+          if ( i >= known_backtrack_classes )
+          {
+            /* Keeps us from having to do this for each rule */
+
+            error = Get_Class( &ccpf2->BacktrackClassDef, s_in[j],
+                               &backtrack_classes[i], NULL );
+            if ( error && error != TTO_Err_Not_Covered )
+              goto End1;
+            known_backtrack_classes = i;
+          }
+
+          if ( bc[bgc - 1 - i] != backtrack_classes[i] )
+            break;
+        }
+
+        if ( i != bgc )
+          continue;
+      }
+
+      curr_pos = in->pos;
+      s_in     = &in->string[curr_pos];
+      ic       = cpcr.Input;
+
+      /* Start at 1 because [0] is implied */
+
+      for ( i = 1, j = 1; i < igc; i++, j++ )
+      {
+        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+        {
+          if ( error && error != TTO_Err_Not_Covered )
+            goto End1;
+
+          if ( curr_pos + j < in->length )
+            j++;
+          else
+            break;
+        }
+
+        if ( i >= known_input_classes )
+        {
+          error = Get_Class( &ccpf2->InputClassDef, s_in[j],
+                             &input_classes[i], NULL );
+          if ( error && error != TTO_Err_Not_Covered )
+            goto End1;
+          known_input_classes = i;
+        }
+
+        if ( ic[i - 1] != input_classes[i] )
+          break;
+      }
+
+      if ( i != igc )
+        continue;
+
+      /* we are starting to check for lookahead glyphs right after the
+         last context glyph                                            */
+
+      curr_pos = j;
+      s_in     = &in->string[curr_pos];
+      lc       = cpcr.Lookahead;
+
+      for ( i = 0, j = 0; i < lgc; i++, j++ )
+      {
+        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+        {
+          if ( error && error != TTO_Err_Not_Covered )
+            return error;
+
+          if ( curr_pos + j < in->length )
+            j++;
+          else
+            break;
+        }
+
+        if ( i >= known_lookahead_classes )
+        {
+          error = Get_Class( &ccpf2->LookaheadClassDef, s_in[j],
+                             &lookahead_classes[i], NULL );
+          if ( error && error != TTO_Err_Not_Covered )
+            goto End1;
+          known_lookahead_classes = i;
+        }
+
+        if ( lc[i] != lookahead_classes[i] )
+          break;
+      }
+
+      if ( i == lgc )
+      {
+        error = Do_ContextPos( gpi, igc,
+                               cpcr.PosCount,
+                               cpcr.PosLookupRecord,
+                               in, out,
+                               nesting_level );
+        goto End1;
+      }
+    }
+
+    error = TTO_Err_Not_Covered;
+
+  End1:
+    FREE( lookahead_classes );
+
+  End2:
+    FREE( input_classes );
+
+  End3:
+    FREE( backtrack_classes );
+    return error;
+  }
+
+
+  static FT_Error  Lookup_ChainContextPos3(
+                     GPOS_Instance*               gpi,
+                     TTO_ChainContextPosFormat3*  ccpf3,
+                     TTO_GSUB_String*             in,
+                     TTO_GPOS_Data*               out,
+                     FT_UShort                    flags,
+                     FT_UShort                    context_length,
+                     int                          nesting_level )
+  {
+    FT_UShort        index, i, j, curr_pos, property;
+    FT_UShort        bgc, igc, lgc;
+    FT_Error         error;
+    FT_UShort*       s_in;
+    TTO_GPOSHeader*  gpos = gpi->gpos;
+
+    TTO_Coverage*    bc;
+    TTO_Coverage*    ic;
+    TTO_Coverage*    lc;
+    TTO_GDEFHeader*  gdef;
+
+
+    gdef = gpos->gdef;
+
+    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
+      return error;
+
+    bgc = ccpf3->BacktrackGlyphCount;
+    igc = ccpf3->InputGlyphCount;
+    lgc = ccpf3->LookaheadGlyphCount;
+
+    if ( context_length != 0xFFFF && context_length < igc )
+      return TTO_Err_Not_Covered;
+
+    /* check whether context is too long; it is a first guess only */
+
+    if ( bgc > in->pos || in->pos + igc + lgc > in->length )
+      return TTO_Err_Not_Covered;
+
+    if ( bgc )
+    {
+      /* Since we don't know in advance the number of glyphs to inspect,
+         we search backwards for matches in the backtrack glyph array    */
+
+      curr_pos = 0;
+      s_in     = &in->string[curr_pos];
+      bc       = ccpf3->BacktrackCoverage;
+
+      for ( i = bgc, j = in->pos - 1; i > 0; i--, j-- )
+      {
+        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+        {
+          if ( error && error != TTO_Err_Not_Covered )
+            return error;
+
+          if ( j > curr_pos )
+            j--;
+          else
+            return TTO_Err_Not_Covered;
+        }
+
+        error = Coverage_Index( &bc[i - 1], s_in[j], &index );
+        if ( error )
+          return error;
+      }
+    }
+
+    curr_pos = in->pos;
+    s_in     = &in->string[curr_pos];
+    ic       = ccpf3->InputCoverage;
+
+    /* Start at 1 because [0] is implied */
+
+    for ( i = 1, j = 1; i < igc; i++, j++ )
+    {
+      while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+      {
+        if ( error && error != TTO_Err_Not_Covered )
+          return error;
+
+        if ( curr_pos + j < in->length )
+          j++;
+        else
+          return TTO_Err_Not_Covered;
+      }
+
+      error = Coverage_Index( &ic[i], s_in[j], &index );
+      if ( error )
+        return error;
+    }
+
+    /* we are starting for lookahead glyphs right after the last context
+       glyph                                                             */
+
+    curr_pos = j;
+    s_in     = &in->string[curr_pos];
+    lc       = ccpf3->LookaheadCoverage;
+
+    for ( i = 0, j = 0; i < lgc; i++, j++ )
+    {
+      while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+      {
+        if ( error && error != TTO_Err_Not_Covered )
+          return error;
+
+        if ( curr_pos + j < in->length )
+          j++;
+        else
+          return TTO_Err_Not_Covered;
+      }
+
+      error = Coverage_Index( &lc[i], s_in[j], &index );
+      if ( error )
+        return error;
+    }
+
+    return Do_ContextPos( gpi, igc,
+                          ccpf3->PosCount,
+                          ccpf3->PosLookupRecord,
+                          in, out,
+                          nesting_level );
+  }
+
+
+  static FT_Error  Lookup_ChainContextPos(
+                     GPOS_Instance*        gpi,
+                     TTO_ChainContextPos*  ccp,
+                     TTO_GSUB_String*      in,
+                     TTO_GPOS_Data*        out,
+                     FT_UShort             flags,
+                     FT_UShort             context_length,
+                     int                   nesting_level )
+  {
+    switch ( ccp->PosFormat )
+    {
+    case 1:
+      return Lookup_ChainContextPos1( gpi, &ccp->ccpf.ccpf1, in, out,
+                                      flags, context_length,
+                                      nesting_level );
+
+    case 2:
+      return Lookup_ChainContextPos2( gpi, &ccp->ccpf.ccpf2, in, out,
+                                      flags, context_length,
+                                      nesting_level );
+
+    case 3:
+      return Lookup_ChainContextPos3( gpi, &ccp->ccpf.ccpf3, in, out,
+                                      flags, context_length,
+                                      nesting_level );
+
+    default:
+      return TTO_Err_Invalid_GPOS_SubTable_Format;
+    }
+
+    return TT_Err_Ok;               /* never reached */
+  }
+
+
+
+  /***********
+   * GPOS API
+   ***********/
+
+
+  EXPORT_FUNC
+  FT_Error  TT_GPOS_Select_Script( TTO_GPOSHeader*  gpos,
+                                   FT_ULong         script_tag,
+                                   FT_UShort*       script_index )
+  {
+    FT_UShort          n;
+
+    TTO_ScriptList*    sl;
+    TTO_ScriptRecord*  sr;
+
+
+    if ( !gpos || !script_index )
+      return TT_Err_Invalid_Argument;
+
+    sl = &gpos->ScriptList;
+    sr = sl->ScriptRecord;
+
+    for ( n = 0; n < sl->ScriptCount; n++ )
+      if ( script_tag == sr[n].ScriptTag )
+      {
+        *script_index = n;
+
+        return TT_Err_Ok;
+      }
+
+    return TTO_Err_Not_Covered;
+  }
+
+
+  EXPORT_FUNC
+  FT_Error  TT_GPOS_Select_Language( TTO_GPOSHeader*  gpos,
+                                     FT_ULong         language_tag,
+                                     FT_UShort        script_index,
+                                     FT_UShort*       language_index,
+                                     FT_UShort*       req_feature_index )
+  {
+    FT_UShort           n;
+
+    TTO_ScriptList*     sl;
+    TTO_ScriptRecord*   sr;
+    TTO_Script*         s;
+    TTO_LangSysRecord*  lsr;
+
+
+    if ( !gpos || !language_index || !req_feature_index )
+      return TT_Err_Invalid_Argument;
+
+    sl = &gpos->ScriptList;
+    sr = sl->ScriptRecord;
+
+    if ( script_index >= sl->ScriptCount )
+      return TT_Err_Invalid_Argument;
+
+    s   = &sr[script_index].Script;
+    lsr = s->LangSysRecord;
+
+    for ( n = 0; n < s->LangSysCount; n++ )
+      if ( language_tag == lsr[n].LangSysTag )
+      {
+        *language_index = n;
+        *req_feature_index = lsr[n].LangSys.ReqFeatureIndex;
+
+        return TT_Err_Ok;
+      }
+
+    return TTO_Err_Not_Covered;
+  }
+
+
+  /* selecting 0xFFFF for language_index asks for the values of the
+     default language (DefaultLangSys)                              */
+
+  EXPORT_FUNC
+  FT_Error  TT_GPOS_Select_Feature( TTO_GPOSHeader*  gpos,
+                                    FT_ULong         feature_tag,
+                                    FT_UShort        script_index,
+                                    FT_UShort        language_index,
+                                    FT_UShort*       feature_index )
+  {
+    FT_UShort           n;
+
+    TTO_ScriptList*     sl;
+    TTO_ScriptRecord*   sr;
+    TTO_Script*         s;
+    TTO_LangSysRecord*  lsr;
+    TTO_LangSys*        ls;
+    FT_UShort*          fi;
+
+    TTO_FeatureList*    fl;
+    TTO_FeatureRecord*  fr;
+
+
+    if ( !gpos || !feature_index )
+      return TT_Err_Invalid_Argument;
+
+    sl = &gpos->ScriptList;
+    sr = sl->ScriptRecord;
+
+    fl = &gpos->FeatureList;
+    fr = fl->FeatureRecord;
+
+    if ( script_index >= sl->ScriptCount )
+      return TT_Err_Invalid_Argument;
+
+    s   = &sr[script_index].Script;
+    lsr = s->LangSysRecord;
+
+    if ( language_index == 0xFFFF )
+      ls = &s->DefaultLangSys;
+    else
+    {
+      if ( language_index >= s->LangSysCount )
+        return TT_Err_Invalid_Argument;
+
+      ls = &lsr[language_index].LangSys;
+    }
+
+    fi = ls->FeatureIndex;
+
+    for ( n = 0; n < ls->FeatureCount; n++ )
+    {
+      if ( fi[n] >= fl->FeatureCount )
+        return TTO_Err_Invalid_GPOS_SubTable_Format;
+
+      if ( feature_tag == fr[fi[n]].FeatureTag )
+      {
+        *feature_index = fi[n];
+
+        return TT_Err_Ok;
+      }
+    }
+
+    return TTO_Err_Not_Covered;
+  }
+
+
+  /* The next three functions return a null-terminated list */
+
+  EXPORT_FUNC
+  FT_Error  TT_GPOS_Query_Scripts( TTO_GPOSHeader*  gpos,
+                                   FT_ULong**       script_tag_list )
+  {
+    FT_Error           error;
+    FT_Memory          memory = gpos->memory;
+    FT_UShort          n;
+    FT_ULong*          stl;
+
+    TTO_ScriptList*    sl;
+    TTO_ScriptRecord*  sr;
+
+
+    if ( !gpos || !script_tag_list )
+      return TT_Err_Invalid_Argument;
+
+    sl = &gpos->ScriptList;
+    sr = sl->ScriptRecord;
+
+    if ( ALLOC_ARRAY( stl, sl->ScriptCount + 1, FT_ULong ) )
+      return error;
+
+    for ( n = 0; n < sl->ScriptCount; n++ )
+      stl[n] = sr[n].ScriptTag;
+    stl[n] = 0;
+
+    *script_tag_list = stl;
+
+    return TT_Err_Ok;
+  }
+
+
+  EXPORT_FUNC
+  FT_Error  TT_GPOS_Query_Languages( TTO_GPOSHeader*  gpos,
+                                     FT_UShort        script_index,
+                                     FT_ULong**       language_tag_list )
+  {
+    FT_Error            error;
+    FT_Memory           memory = gpos->memory;
+    FT_UShort           n;
+    FT_ULong*           ltl;
+
+    TTO_ScriptList*     sl;
+    TTO_ScriptRecord*   sr;
+    TTO_Script*         s;
+    TTO_LangSysRecord*  lsr;
+
+
+    if ( !gpos || !language_tag_list )
+      return TT_Err_Invalid_Argument;
+
+    sl = &gpos->ScriptList;
+    sr = sl->ScriptRecord;
+
+    if ( script_index >= sl->ScriptCount )
+      return TT_Err_Invalid_Argument;
+
+    s   = &sr[script_index].Script;
+    lsr = s->LangSysRecord;
+
+    if ( ALLOC_ARRAY( ltl, s->LangSysCount + 1, FT_ULong ) )
+      return error;
+
+    for ( n = 0; n < s->LangSysCount; n++ )
+      ltl[n] = lsr[n].LangSysTag;
+    ltl[n] = 0;
+
+    *language_tag_list = ltl;
+
+    return TT_Err_Ok;
+  }
+
+
+  /* selecting 0xFFFF for language_index asks for the values of the
+     default language (DefaultLangSys)                              */
+
+  EXPORT_FUNC
+  FT_Error  TT_GPOS_Query_Features( TTO_GPOSHeader*  gpos,
+                                    FT_UShort        script_index,
+                                    FT_UShort        language_index,
+                                    FT_ULong**       feature_tag_list )
+  {
+    FT_UShort           n;
+    FT_Error            error;
+    FT_Memory           memory = gpos->memory;
+    FT_ULong*           ftl;
+
+    TTO_ScriptList*     sl;
+    TTO_ScriptRecord*   sr;
+    TTO_Script*         s;
+    TTO_LangSysRecord*  lsr;
+    TTO_LangSys*        ls;
+    FT_UShort*          fi;
+
+    TTO_FeatureList*    fl;
+    TTO_FeatureRecord*  fr;
+
+
+    if ( !gpos || !feature_tag_list )
+      return TT_Err_Invalid_Argument;
+
+    sl = &gpos->ScriptList;
+    sr = sl->ScriptRecord;
+
+    fl = &gpos->FeatureList;
+    fr = fl->FeatureRecord;
+
+    if ( script_index >= sl->ScriptCount )
+      return TT_Err_Invalid_Argument;
+
+    s   = &sr[script_index].Script;
+    lsr = s->LangSysRecord;
+
+    if ( language_index == 0xFFFF )
+      ls = &s->DefaultLangSys;
+    else
+    {
+      if ( language_index >= s->LangSysCount )
+        return TT_Err_Invalid_Argument;
+
+      ls = &lsr[language_index].LangSys;
+    }
+
+    fi = ls->FeatureIndex;
+
+    if ( ALLOC_ARRAY( ftl, ls->FeatureCount + 1, FT_ULong ) )
+      return error;
+
+    for ( n = 0; n < ls->FeatureCount; n++ )
+    {
+      if ( fi[n] >= fl->FeatureCount )
+      {
+        FREE( ftl );
+        return TTO_Err_Invalid_GPOS_SubTable_Format;
+      }
+      ftl[n] = fr[fi[n]].FeatureTag;
+    }
+    ftl[n] = 0;
+
+    *feature_tag_list = ftl;
+
+    return TT_Err_Ok;
+  }
+
+
+  /* Do an individual subtable lookup.  Returns TT_Err_Ok if positioning
+     has been done, or TTO_Err_Not_Covered if not.                        */
+
+  static FT_Error  Do_Glyph_Lookup( GPOS_Instance*    gpi,
+                                    FT_UShort         lookup_index,
+                                    TTO_GSUB_String*  in,
+                                    TTO_GPOS_Data*    out,
+                                    FT_UShort         context_length,
+                                    int               nesting_level )
+  {
+    FT_Error         error = TT_Err_Ok;
+    FT_UShort        i, flags;
+    TTO_GPOSHeader*  gpos = gpi->gpos;
+    TTO_Lookup*      lo;
+
+
+    nesting_level++;
+
+    if ( nesting_level > TTO_MAX_NESTING_LEVEL )
+      return TTO_Err_Too_Many_Nested_Contexts;
+
+    lo    = &gpos->LookupList.Lookup[lookup_index];
+    flags = lo->LookupFlag;
+
+    for ( i = 0; i < lo->SubTableCount; i++ )
+    {
+      switch ( lo->LookupType )
+      {
+      case GPOS_LOOKUP_SINGLE:
+        error = Lookup_SinglePos( gpi,
+                                  &lo->SubTable[i].st.gpos.single,
+                                  in, out,
+                                  flags, context_length );
+        break;
+
+      case GPOS_LOOKUP_PAIR:
+        error = Lookup_PairPos( gpi,
+                                &lo->SubTable[i].st.gpos.pair,
+                                in, out,
+                                flags, context_length );
+        break;
+
+      case GPOS_LOOKUP_CURSIVE:
+        error = Lookup_CursivePos( gpi,
+                                   &lo->SubTable[i].st.gpos.cursive,
+                                   in, out,
+                                   flags, context_length );
+        break;
+
+      case GPOS_LOOKUP_MARKBASE:
+        error = Lookup_MarkBasePos( gpi,
+                                    &lo->SubTable[i].st.gpos.markbase,
+                                    in, out,
+                                    flags, context_length );
+        break;
+
+      case GPOS_LOOKUP_MARKLIG:
+        error = Lookup_MarkLigPos( gpi,
+                                   &lo->SubTable[i].st.gpos.marklig,
+                                   in, out,
+                                   flags, context_length );
+        break;
+
+      case GPOS_LOOKUP_MARKMARK:
+        error = Lookup_MarkMarkPos( gpi,
+                                    &lo->SubTable[i].st.gpos.markmark,
+                                    in, out,
+                                    flags, context_length );
+        break;
+
+      case GPOS_LOOKUP_CONTEXT:
+        error = Lookup_ContextPos( gpi,
+                                   &lo->SubTable[i].st.gpos.context,
+                                   in, out,
+                                   flags, context_length,
+                                   nesting_level );
+        break;
+
+      case GPOS_LOOKUP_CHAIN:
+        error = Lookup_ChainContextPos( gpi,
+                                        &lo->SubTable[i].st.gpos.chain,
+                                        in, out,
+                                        flags, context_length,
+                                        nesting_level );
+        break;
+      }
+
+      /* Check whether we have a successful positioning or an error other
+         than TTO_Err_Not_Covered                                         */
+
+      if ( error != TTO_Err_Not_Covered )
+        return error;
+    }
+
+    return TTO_Err_Not_Covered;
+  }
+
+
+  /* apply one lookup to the input string object */
+
+  static FT_Error  Do_String_Lookup( GPOS_Instance*    gpi,
+                                     FT_UShort         lookup_index,
+                                     TTO_GSUB_String*  in,
+                                     TTO_GPOS_Data*    out )
+  {
+    FT_Error         error = TTO_Err_Not_Covered;
+    TTO_GPOSHeader*  gpos = gpi->gpos;
+
+    FT_UShort*  properties = gpos->LookupList.Properties;
+    FT_UShort*  p_in       = in->properties;
+
+    int      nesting_level = 0;
+
+
+    gpi->last = 0xFFFF;      /* no last valid glyph for cursive pos. */
+
+    in->pos = 0;
+
+    while ( in->pos < in->length )
+    {
+      if ( ~p_in[in->pos] & properties[lookup_index] )
+      {
+        /* 0xFFFF indicates that we don't have a context length yet. */
+
+        /* Note that the connection between mark and base glyphs hold
+           exactly one (string) lookup.  For example, it would be possible
+           that in the first lookup, mark glyph X is attached to base
+           glyph A, and in the next lookup it is attached to base glyph B.
+           It is up to the font designer to provide meaningful lookups and
+           lookup order.                                                   */
+
+        error = Do_Glyph_Lookup( gpi, lookup_index, in, out,
+                                 0xFFFF, nesting_level );
+        if ( error && error != TTO_Err_Not_Covered )
+          return error;
+      }
+      else
+      {
+        /* Contrary to properties defined in GDEF, user-defined properties
+           will always stop a possible cursive positioning.                */
+        gpi->last = 0xFFFF;
+
+        error = TTO_Err_Not_Covered;
+      }
+
+      if ( error == TTO_Err_Not_Covered )
+        (in->pos)++;
+    }
+
+    return error;
+  }
+
+
+  EXPORT_FUNC
+  FT_Error  TT_GPOS_Add_Feature( TTO_GPOSHeader*  gpos,
+                                 FT_UShort        feature_index,
+                                 FT_UShort        property )
+  {
+    FT_UShort    i;
+
+    TTO_Feature  feature;
+    FT_UShort*   properties;
+    FT_UShort*   index;
+
+
+    if ( !gpos ||
+         feature_index >= gpos->FeatureList.FeatureCount )
+      return TT_Err_Invalid_Argument;
+
+    properties = gpos->LookupList.Properties;
+
+    feature = gpos->FeatureList.FeatureRecord[feature_index].Feature;
+    index   = feature.LookupListIndex;
+
+    for ( i = 0; i < feature.LookupListCount; i++ )
+      properties[index[i]] |= property;
+
+    return TT_Err_Ok;
+  }
+
+
+  EXPORT_FUNC
+  FT_Error  TT_GPOS_Clear_Features( TTO_GPOSHeader*  gpos )
+  {
+    FT_UShort i;
+
+    FT_UShort*  properties;
+
+
+    if ( !gpos )
+      return TT_Err_Invalid_Argument;
+
+    properties = gpos->LookupList.Properties;
+
+    for ( i = 0; i < gpos->LookupList.LookupCount; i++ )
+      properties[i] = 0;
+
+    return TT_Err_Ok;
+  }
+
+
+  EXPORT_FUNC
+  FT_Error  TT_GPOS_Register_Glyph_Function( TTO_GPOSHeader*    gpos,
+                                             TTO_GlyphFunction  gfunc )
+  {
+    if ( !gpos )
+      return TT_Err_Invalid_Argument;
+
+    gpos->gfunc = gfunc;
+
+    return TT_Err_Ok;
+  }
+
+
+  EXPORT_FUNC
+  FT_Error  TT_GPOS_Register_MM_Function( TTO_GPOSHeader*  gpos,
+                                          TTO_MMFunction   mmfunc,
+                                          void*            data )
+  {
+    if ( !gpos )
+      return TT_Err_Invalid_Argument;
+
+    gpos->mmfunc = mmfunc;
+    gpos->data   = data;
+
+    return TT_Err_Ok;
+  }
+
+
+  /* If `dvi' is TRUE, glyph contour points for anchor points and device
+     tables are ignored -- you will get device independent values.         */
+
+  EXPORT_FUNC
+  FT_Error  TT_GPOS_Apply_String( FT_Face            face,
+                                  TTO_GPOSHeader*    gpos,
+                                  FT_UShort          load_flags,
+                                  TTO_GSUB_String*   in,
+                                  TTO_GPOS_Data**    out,
+                                  FT_Bool            dvi,
+                                  FT_Bool            r2l )
+  {
+    FT_Memory      memory = gpos->memory;
+    FT_Error       error = TTO_Err_Not_Covered;
+    GPOS_Instance  gpi;
+
+    FT_UShort j;
+
+    FT_UShort* properties;
+
+
+    if ( !face || !gpos ||
+         !in || in->length == 0 || in->pos >= in->length )
+      return TT_Err_Invalid_Argument;
+
+    properties = gpos->LookupList.Properties;
+
+    gpi.face       = face;
+    gpi.gpos       = gpos;
+    gpi.load_flags = load_flags;
+    gpi.r2l        = r2l;
+    gpi.dvi        = dvi;
+
+    if ( *out )
+      FREE( *out );
+    if ( ALLOC_ARRAY( *out, in->length, TTO_GPOS_Data ) )
+      return error;
+
+    for ( j = 0; j < gpos->LookupList.LookupCount; j++ )
+      if ( !properties || properties[j] )
+      {
+        error = Do_String_Lookup( &gpi, j, in, *out );
+        if ( error && error != TTO_Err_Not_Covered )
+          return error;
+      }
+
+    return error;
+  }
+
+/* END */
diff --git a/src/ftxgpos.h b/src/ftxgpos.h
new file mode 100644
index 0000000..56ffb78
--- /dev/null
+++ b/src/ftxgpos.h
@@ -0,0 +1,858 @@
+/*******************************************************************
+ *
+ *  ftxgpos.h
+ *
+ *    TrueType Open GPOS table support
+ *
+ *  Copyright 1996-2000 by
+ *  David Turner, Robert Wilhelm, and Werner Lemberg.
+ *
+ *  This file is part of the FreeType project, and may only be used
+ *  modified and distributed under the terms of the FreeType project
+ *  license, LICENSE.TXT.  By continuing to use, modify, or distribute
+ *  this file you indicate that you have read the license and
+ *  understand and accept it fully.
+ *
+ ******************************************************************/
+
+#ifndef FTXOPEN_H
+#error "Don't include this file! Use ftxopen.h instead."
+#endif
+
+#ifndef FTXGPOS_H
+#define FTXGPOS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define TTO_Err_Invalid_GPOS_SubTable_Format  0x1020
+#define TTO_Err_Invalid_GPOS_SubTable         0x1021
+
+
+/* Lookup types for glyph positioning */
+
+#define GPOS_LOOKUP_SINGLE    1
+#define GPOS_LOOKUP_PAIR      2
+#define GPOS_LOOKUP_CURSIVE   3
+#define GPOS_LOOKUP_MARKBASE  4
+#define GPOS_LOOKUP_MARKLIG   5
+#define GPOS_LOOKUP_MARKMARK  6
+#define GPOS_LOOKUP_CONTEXT   7
+#define GPOS_LOOKUP_CHAIN     8
+
+
+  /* A pointer to a function which loads a glyph.  Its parameters are
+     the same as in a call to TT_Load_Glyph() -- if no glyph loading
+     function will be registered with TTO_GPOS_Register_Glyph_Function(),
+     TT_Load_Glyph() will be called indeed.  The purpose of this function
+     pointer is to provide a hook for caching glyph outlines and sbits
+     (using the instance's generic pointer to hold the data).
+
+     If for some reason no outline data is available (e.g. for an
+     embedded bitmap glyph), _glyph->outline.n_points should be set to
+     zero.  _glyph can be computed with
+
+        _glyph = HANDLE_Glyph( glyph )                                    */
+
+  typedef FT_Error  (*TTO_GlyphFunction)(FT_Face      face,
+					 FT_UInt      glyphIndex,
+					 FT_Int       loadFlags );
+
+
+  /* A pointer to a function which accesses the PostScript interpreter.
+     Multiple Master fonts need this interface to convert a metric ID
+     (as stored in an OpenType font version 1.2 or higher) `metric_id'
+     into a metric value (returned in `metric_value').
+
+     `data' points to the user-defined structure specified during a
+     call to TT_GPOS_Register_MM_Function().
+
+     `metric_value' must be returned as a scaled value (but shouldn't
+     be rounded).                                                       */
+
+  typedef FT_Error  (*TTO_MMFunction)(FT_Face      face,
+				      FT_UShort    metric_id,
+                                      FT_Pos*      metric_value,
+                                      void*        data );
+                                          
+
+  struct  TTO_GPOSHeader_
+  {
+    FT_Memory          memory;
+    
+    FT_Fixed           Version;
+
+    TTO_ScriptList     ScriptList;
+    TTO_FeatureList    FeatureList;
+    TTO_LookupList     LookupList;
+
+    TTO_GDEFHeader*    gdef;
+
+    /* the next field is used for a callback function to get the
+       glyph outline.                                            */
+
+    TTO_GlyphFunction  gfunc;
+
+    /* this is OpenType 1.2 -- Multiple Master fonts need this
+       callback function to get various metric values from the
+       PostScript interpreter.                                 */
+
+    TTO_MMFunction     mmfunc;
+    void*              data;
+  };
+
+  typedef struct TTO_GPOSHeader_  TTO_GPOSHeader;
+  typedef struct TTO_GPOSHeader_* TTO_GPOS;
+
+
+  /* shared tables */
+
+  struct  TTO_ValueRecord_
+  {
+    FT_Short    XPlacement;             /* horizontal adjustment for
+                                           placement                      */
+    FT_Short    YPlacement;             /* vertical adjustment for
+                                           placement                      */
+    FT_Short    XAdvance;               /* horizontal adjustment for
+                                           advance                        */
+    FT_Short    YAdvance;               /* vertical adjustment for
+                                           advance                        */
+    TTO_Device  XPlacementDevice;       /* device table for horizontal
+                                           placement                      */
+    TTO_Device  YPlacementDevice;       /* device table for vertical
+                                           placement                      */
+    TTO_Device  XAdvanceDevice;         /* device table for horizontal
+                                           advance                        */
+    TTO_Device  YAdvanceDevice;         /* device table for vertical
+                                           advance                        */
+    FT_UShort   XIdPlacement;           /* horizontal placement metric ID */
+    FT_UShort   YIdPlacement;           /* vertical placement metric ID   */
+    FT_UShort   XIdAdvance;             /* horizontal advance metric ID   */
+    FT_UShort   YIdAdvance;             /* vertical advance metric ID     */
+  };
+
+  typedef struct TTO_ValueRecord_  TTO_ValueRecord;
+
+
+/* Mask values to scan the value format of the ValueRecord structure.
+   We always expand compressed ValueRecords of the font.              */
+
+#define HAVE_X_PLACEMENT         0x0001
+#define HAVE_Y_PLACEMENT         0x0002
+#define HAVE_X_ADVANCE           0x0004
+#define HAVE_Y_ADVANCE           0x0008
+#define HAVE_X_PLACEMENT_DEVICE  0x0010
+#define HAVE_Y_PLACEMENT_DEVICE  0x0020
+#define HAVE_X_ADVANCE_DEVICE    0x0040
+#define HAVE_Y_ADVANCE_DEVICE    0x0080
+#define HAVE_X_ID_PLACEMENT      0x0100
+#define HAVE_Y_ID_PLACEMENT      0x0200
+#define HAVE_X_ID_ADVANCE        0x0400
+#define HAVE_Y_ID_ADVANCE        0x0800
+
+
+  struct  TTO_AnchorFormat1_
+  {
+    FT_Short   XCoordinate;             /* horizontal value */
+    FT_Short   YCoordinate;             /* vertical value   */
+  };
+
+  typedef struct TTO_AnchorFormat1_  TTO_AnchorFormat1;
+
+
+  struct  TTO_AnchorFormat2_
+  {
+    FT_Short   XCoordinate;             /* horizontal value             */
+    FT_Short   YCoordinate;             /* vertical value               */
+    FT_UShort  AnchorPoint;             /* index to glyph contour point */
+  };
+
+  typedef struct TTO_AnchorFormat2_  TTO_AnchorFormat2;
+
+
+  struct  TTO_AnchorFormat3_
+  {
+    FT_Short    XCoordinate;            /* horizontal value              */
+    FT_Short    YCoordinate;            /* vertical value                */
+    TTO_Device  XDeviceTable;           /* device table for X coordinate */
+    TTO_Device  YDeviceTable;           /* device table for Y coordinate */
+  };
+
+  typedef struct TTO_AnchorFormat3_  TTO_AnchorFormat3;
+
+
+  struct  TTO_AnchorFormat4_
+  {
+    FT_UShort  XIdAnchor;               /* horizontal metric ID */
+    FT_UShort  YIdAnchor;               /* vertical metric ID   */
+  };
+
+  typedef struct TTO_AnchorFormat4_  TTO_AnchorFormat4;
+
+
+  struct  TTO_Anchor_
+  {
+    FT_UShort  PosFormat;               /* 1, 2, 3, or 4 -- 0 indicates
+                                           that there is no Anchor table */
+
+    union
+    {
+      TTO_AnchorFormat1  af1;
+      TTO_AnchorFormat2  af2;
+      TTO_AnchorFormat3  af3;
+      TTO_AnchorFormat4  af4;
+    } af;
+  };
+
+  typedef struct TTO_Anchor_  TTO_Anchor;
+
+
+  struct  TTO_MarkRecord_
+  {
+    FT_UShort   Class;                  /* mark class   */
+    TTO_Anchor  MarkAnchor;             /* anchor table */
+  };
+
+  typedef struct TTO_MarkRecord_  TTO_MarkRecord;
+
+
+  struct  TTO_MarkArray_
+  {
+    FT_UShort        MarkCount;         /* number of MarkRecord tables */
+    TTO_MarkRecord*  MarkRecord;        /* array of MarkRecord tables  */
+  };
+
+  typedef struct TTO_MarkArray_  TTO_MarkArray;
+
+
+  /* LookupType 1 */
+
+  struct  TTO_SinglePosFormat1_
+  {
+    TTO_ValueRecord  Value;             /* ValueRecord for all covered
+                                           glyphs                      */
+  };
+
+  typedef struct TTO_SinglePosFormat1_  TTO_SinglePosFormat1;
+
+
+  struct  TTO_SinglePosFormat2_
+  {
+    FT_UShort         ValueCount;       /* number of ValueRecord tables */
+    TTO_ValueRecord*  Value;            /* array of ValueRecord tables  */
+  };
+
+  typedef struct TTO_SinglePosFormat2_  TTO_SinglePosFormat2;
+
+
+  struct  TTO_SinglePos_
+  {
+    FT_UShort     PosFormat;            /* 1 or 2         */
+    TTO_Coverage  Coverage;             /* Coverage table */
+
+    FT_UShort     ValueFormat;          /* format of ValueRecord table */
+
+    union
+    {
+      TTO_SinglePosFormat1  spf1;
+      TTO_SinglePosFormat2  spf2;
+    } spf;
+  };
+
+  typedef struct TTO_SinglePos_  TTO_SinglePos;
+
+
+  /* LookupType 2 */
+
+  struct  TTO_PairValueRecord_
+  {
+    FT_UShort        SecondGlyph;       /* glyph ID for second glyph  */
+    TTO_ValueRecord  Value1;            /* pos. data for first glyph  */
+    TTO_ValueRecord  Value2;            /* pos. data for second glyph */
+  };
+
+  typedef struct TTO_PairValueRecord_  TTO_PairValueRecord;
+
+
+  struct  TTO_PairSet_
+  {
+    FT_UShort             PairValueCount;
+                                        /* number of PairValueRecord tables */
+    TTO_PairValueRecord*  PairValueRecord;
+                                        /* array of PairValueRecord tables  */
+  };
+
+  typedef struct TTO_PairSet_  TTO_PairSet;
+
+
+  struct  TTO_PairPosFormat1_
+  {
+    FT_UShort     PairSetCount;         /* number of PairSet tables    */
+    TTO_PairSet*  PairSet;              /* array of PairSet tables     */
+  };
+
+  typedef struct TTO_PairPosFormat1_  TTO_PairPosFormat1;
+
+
+  struct  TTO_Class2Record_
+  {
+    TTO_ValueRecord  Value1;            /* pos. data for first glyph  */
+    TTO_ValueRecord  Value2;            /* pos. data for second glyph */
+  };
+
+  typedef struct TTO_Class2Record_  TTO_Class2Record;
+
+
+  struct  TTO_Class1Record_
+  {
+    TTO_Class2Record*  Class2Record;    /* array of Class2Record tables */
+  };
+
+  typedef struct TTO_Class1Record_  TTO_Class1Record;
+
+
+  struct  TTO_PairPosFormat2_
+  {
+    TTO_ClassDefinition  ClassDef1;     /* class def. for first glyph     */
+    TTO_ClassDefinition  ClassDef2;     /* class def. for second glyph    */
+    FT_UShort            Class1Count;   /* number of classes in ClassDef1
+                                           table                          */
+    FT_UShort            Class2Count;   /* number of classes in ClassDef2
+                                           table                          */
+    TTO_Class1Record*    Class1Record;  /* array of Class1Record tables   */
+  };
+
+  typedef struct TTO_PairPosFormat2_  TTO_PairPosFormat2;
+
+
+  struct  TTO_PairPos_
+  {
+    FT_UShort     PosFormat;            /* 1 or 2         */
+    TTO_Coverage  Coverage;             /* Coverage table */
+    FT_UShort     ValueFormat1;         /* format of ValueRecord table
+                                           for first glyph             */
+    FT_UShort     ValueFormat2;         /* format of ValueRecord table
+                                           for second glyph            */
+
+    union
+    {
+      TTO_PairPosFormat1  ppf1;
+      TTO_PairPosFormat2  ppf2;
+    } ppf;
+  };
+
+  typedef struct TTO_PairPos_  TTO_PairPos;
+
+
+  /* LookupType 3 */
+
+  struct  TTO_EntryExitRecord_
+  {
+    TTO_Anchor  EntryAnchor;            /* entry Anchor table */
+    TTO_Anchor  ExitAnchor;             /* exit Anchor table  */
+  };
+
+
+  typedef struct TTO_EntryExitRecord_  TTO_EntryExitRecord;
+
+  struct  TTO_CursivePos_
+  {
+    FT_UShort             PosFormat;    /* always 1                         */
+    TTO_Coverage          Coverage;     /* Coverage table                   */
+    FT_UShort             EntryExitCount;
+                                        /* number of EntryExitRecord tables */
+    TTO_EntryExitRecord*  EntryExitRecord;
+                                        /* array of EntryExitRecord tables  */
+  };
+
+  typedef struct TTO_CursivePos_  TTO_CursivePos;
+
+
+  /* LookupType 4 */
+
+  struct  TTO_BaseRecord_
+  {
+    TTO_Anchor*  BaseAnchor;            /* array of base glyph anchor
+                                           tables                     */
+  };
+
+  typedef struct TTO_BaseRecord_  TTO_BaseRecord;
+
+
+  struct  TTO_BaseArray_
+  {
+    FT_UShort        BaseCount;         /* number of BaseRecord tables */
+    TTO_BaseRecord*  BaseRecord;        /* array of BaseRecord tables  */
+  };
+
+  typedef struct TTO_BaseArray_  TTO_BaseArray;
+
+
+  struct  TTO_MarkBasePos_
+  {
+    FT_UShort      PosFormat;           /* always 1                  */
+    TTO_Coverage   MarkCoverage;        /* mark glyph coverage table */
+    TTO_Coverage   BaseCoverage;        /* base glyph coverage table */
+    FT_UShort      ClassCount;          /* number of mark classes    */
+    TTO_MarkArray  MarkArray;           /* mark array table          */
+    TTO_BaseArray  BaseArray;           /* base array table          */
+  };
+
+  typedef struct TTO_MarkBasePos_  TTO_MarkBasePos;
+
+
+  /* LookupType 5 */
+
+  struct  TTO_ComponentRecord_
+  {
+    TTO_Anchor*  LigatureAnchor;        /* array of ligature glyph anchor
+                                           tables                         */
+  };
+
+  typedef struct TTO_ComponentRecord_  TTO_ComponentRecord;
+
+
+  struct  TTO_LigatureAttach_
+  {
+    FT_UShort             ComponentCount;
+                                        /* number of ComponentRecord tables */
+    TTO_ComponentRecord*  ComponentRecord;
+                                        /* array of ComponentRecord tables  */
+  };
+
+  typedef struct TTO_LigatureAttach_  TTO_LigatureAttach;
+
+
+  struct  TTO_LigatureArray_
+  {
+    FT_UShort            LigatureCount; /* number of LigatureAttach tables */
+    TTO_LigatureAttach*  LigatureAttach;
+                                        /* array of LigatureAttach tables  */
+  };
+
+  typedef struct TTO_LigatureArray_  TTO_LigatureArray;
+
+
+  struct  TTO_MarkLigPos_
+  {
+    FT_UShort          PosFormat;       /* always 1                      */
+    TTO_Coverage       MarkCoverage;    /* mark glyph coverage table     */
+    TTO_Coverage       LigatureCoverage;
+                                        /* ligature glyph coverage table */
+    FT_UShort          ClassCount;      /* number of mark classes        */
+    TTO_MarkArray      MarkArray;       /* mark array table              */
+    TTO_LigatureArray  LigatureArray;   /* ligature array table          */
+  };
+
+  typedef struct TTO_MarkLigPos_  TTO_MarkLigPos;
+
+
+  /* LookupType 6 */
+
+  struct  TTO_Mark2Record_
+  {
+    TTO_Anchor*  Mark2Anchor;           /* array of mark glyph anchor
+                                           tables                     */
+  };
+
+  typedef struct TTO_Mark2Record_  TTO_Mark2Record;
+
+
+  struct  TTO_Mark2Array_
+  {
+    FT_UShort         Mark2Count;       /* number of Mark2Record tables */
+    TTO_Mark2Record*  Mark2Record;      /* array of Mark2Record tables  */
+  };
+
+  typedef struct TTO_Mark2Array_  TTO_Mark2Array;
+
+
+  struct  TTO_MarkMarkPos_
+  {
+    FT_UShort       PosFormat;          /* always 1                         */
+    TTO_Coverage    Mark1Coverage;      /* first mark glyph coverage table  */
+    TTO_Coverage    Mark2Coverage;      /* second mark glyph coverave table */
+    FT_UShort       ClassCount;         /* number of combining mark classes */
+    TTO_MarkArray   Mark1Array;         /* MarkArray table for first mark   */
+    TTO_Mark2Array  Mark2Array;         /* MarkArray table for second mark  */
+  };
+
+  typedef struct TTO_MarkMarkPos_  TTO_MarkMarkPos;
+
+
+  /* needed by both lookup type 7 and 8 */
+
+  struct  TTO_PosLookupRecord_
+  {
+    FT_UShort  SequenceIndex;           /* index into current
+                                           glyph sequence               */
+    FT_UShort  LookupListIndex;         /* Lookup to apply to that pos. */
+  };
+
+  typedef struct TTO_PosLookupRecord_  TTO_PosLookupRecord;
+
+
+  /* LookupType 7 */
+
+  struct  TTO_PosRule_
+  {
+    FT_UShort             GlyphCount;   /* total number of input glyphs     */
+    FT_UShort             PosCount;     /* number of PosLookupRecord tables */
+    FT_UShort*            Input;        /* array of input glyph IDs         */
+    TTO_PosLookupRecord*  PosLookupRecord;
+                                        /* array of PosLookupRecord tables  */
+  };
+
+  typedef struct TTO_PosRule_  TTO_PosRule;
+
+
+  struct  TTO_PosRuleSet_
+  {
+    FT_UShort     PosRuleCount;         /* number of PosRule tables */
+    TTO_PosRule*  PosRule;              /* array of PosRule tables  */
+  };
+
+  typedef struct TTO_PosRuleSet_  TTO_PosRuleSet;
+
+
+  struct  TTO_ContextPosFormat1_
+  {
+    TTO_Coverage     Coverage;          /* Coverage table              */
+    FT_UShort        PosRuleSetCount;   /* number of PosRuleSet tables */
+    TTO_PosRuleSet*  PosRuleSet;        /* array of PosRuleSet tables  */
+  };
+
+  typedef struct TTO_ContextPosFormat1_  TTO_ContextPosFormat1;
+
+
+  struct  TTO_PosClassRule_
+  {
+    FT_UShort             GlyphCount;   /* total number of context classes  */
+    FT_UShort             PosCount;     /* number of PosLookupRecord tables */
+    FT_UShort*            Class;        /* array of classes                 */
+    TTO_PosLookupRecord*  PosLookupRecord;
+                                        /* array of PosLookupRecord tables  */
+  };
+
+  typedef struct TTO_PosClassRule_  TTO_PosClassRule;
+
+
+  struct  TTO_PosClassSet_
+  {
+    FT_UShort          PosClassRuleCount;
+                                        /* number of PosClassRule tables */
+    TTO_PosClassRule*  PosClassRule;    /* array of PosClassRule tables  */
+  };
+
+  typedef struct TTO_PosClassSet_  TTO_PosClassSet;
+
+
+  /* The `MaxContextLength' field is not defined in the TTO specification
+     but simplifies the implementation of this format.  It holds the
+     maximal context length used in the context rules.                    */
+
+  struct  TTO_ContextPosFormat2_
+  {
+    FT_UShort            MaxContextLength;
+                                        /* maximal context length       */
+    TTO_Coverage         Coverage;      /* Coverage table               */
+    TTO_ClassDefinition  ClassDef;      /* ClassDef table               */
+    FT_UShort            PosClassSetCount;
+                                        /* number of PosClassSet tables */
+    TTO_PosClassSet*     PosClassSet;   /* array of PosClassSet tables  */
+  };
+
+  typedef struct TTO_ContextPosFormat2_  TTO_ContextPosFormat2;
+
+
+  struct  TTO_ContextPosFormat3_
+  {
+    FT_UShort             GlyphCount;   /* number of input glyphs           */
+    FT_UShort             PosCount;     /* number of PosLookupRecord tables */
+    TTO_Coverage*         Coverage;     /* array of Coverage tables         */
+    TTO_PosLookupRecord*  PosLookupRecord;
+                                        /* array of PosLookupRecord tables  */
+  };
+
+  typedef struct TTO_ContextPosFormat3_  TTO_ContextPosFormat3;
+
+
+  struct  TTO_ContextPos_
+  {
+    FT_UShort  PosFormat;               /* 1, 2, or 3     */
+
+    union
+    {
+      TTO_ContextPosFormat1  cpf1;
+      TTO_ContextPosFormat2  cpf2;
+      TTO_ContextPosFormat3  cpf3;
+    } cpf;
+  };
+
+  typedef struct TTO_ContextPos_  TTO_ContextPos;
+
+
+  /* LookupType 8 */
+
+  struct  TTO_ChainPosRule_
+  {
+    FT_UShort             BacktrackGlyphCount;
+                                        /* total number of backtrack glyphs */
+    FT_UShort*            Backtrack;    /* array of backtrack glyph IDs     */
+    FT_UShort             InputGlyphCount;
+                                        /* total number of input glyphs     */
+    FT_UShort*            Input;        /* array of input glyph IDs         */
+    FT_UShort             LookaheadGlyphCount;
+                                        /* total number of lookahead glyphs */
+    FT_UShort*            Lookahead;    /* array of lookahead glyph IDs     */
+    FT_UShort             PosCount;     /* number of PosLookupRecords       */
+    TTO_PosLookupRecord*  PosLookupRecord;
+                                        /* array of PosLookupRecords       */
+  };
+
+  typedef struct TTO_ChainPosRule_  TTO_ChainPosRule;
+
+
+  struct  TTO_ChainPosRuleSet_
+  {
+    FT_UShort          ChainPosRuleCount;
+                                        /* number of ChainPosRule tables */
+    TTO_ChainPosRule*  ChainPosRule;    /* array of ChainPosRule tables  */
+  };
+
+  typedef struct TTO_ChainPosRuleSet_  TTO_ChainPosRuleSet;
+
+
+  struct  TTO_ChainContextPosFormat1_
+  {
+    TTO_Coverage          Coverage;     /* Coverage table                   */
+    FT_UShort             ChainPosRuleSetCount;
+                                        /* number of ChainPosRuleSet tables */
+    TTO_ChainPosRuleSet*  ChainPosRuleSet;
+                                        /* array of ChainPosRuleSet tables  */
+  };
+
+  typedef struct TTO_ChainContextPosFormat1_  TTO_ChainContextPosFormat1;
+
+
+  struct  TTO_ChainPosClassRule_
+  {
+    FT_UShort             BacktrackGlyphCount;
+                                        /* total number of backtrack
+                                           classes                         */
+    FT_UShort*            Backtrack;    /* array of backtrack classes      */
+    FT_UShort             InputGlyphCount;
+                                        /* total number of context classes */
+    FT_UShort*            Input;        /* array of context classes        */
+    FT_UShort             LookaheadGlyphCount;
+                                        /* total number of lookahead
+                                           classes                         */
+    FT_UShort*            Lookahead;    /* array of lookahead classes      */
+    FT_UShort             PosCount;     /* number of PosLookupRecords      */
+    TTO_PosLookupRecord*  PosLookupRecord;
+                                        /* array of substitution lookups   */
+  };
+
+  typedef struct TTO_ChainPosClassRule_  TTO_ChainPosClassRule;
+
+
+  struct  TTO_ChainPosClassSet_
+  {
+    FT_UShort               ChainPosClassRuleCount;
+                                        /* number of ChainPosClassRule
+                                           tables                      */
+    TTO_ChainPosClassRule*  ChainPosClassRule;
+                                        /* array of ChainPosClassRule
+                                           tables                      */
+  };
+
+  typedef struct TTO_ChainPosClassSet_  TTO_ChainPosClassSet;
+
+
+  /* The `MaxXXXLength' fields are not defined in the TTO specification
+     but simplifies the implementation of this format.  It holds the
+     maximal context length used in the specific context rules.         */
+
+  struct  TTO_ChainContextPosFormat2_
+  {
+    TTO_Coverage           Coverage;    /* Coverage table             */
+
+    FT_UShort              MaxBacktrackLength;
+                                        /* maximal backtrack length   */
+    TTO_ClassDefinition    BacktrackClassDef;
+                                        /* BacktrackClassDef table    */
+    FT_UShort              MaxInputLength;
+                                        /* maximal input length       */
+    TTO_ClassDefinition    InputClassDef;
+                                        /* InputClassDef table        */
+    FT_UShort              MaxLookaheadLength;
+                                        /* maximal lookahead length   */
+    TTO_ClassDefinition    LookaheadClassDef;
+                                        /* LookaheadClassDef table    */
+
+    FT_UShort              ChainPosClassSetCount;
+                                        /* number of ChainPosClassSet
+                                           tables                     */
+    TTO_ChainPosClassSet*  ChainPosClassSet;
+                                        /* array of ChainPosClassSet
+                                           tables                     */
+  };
+
+  typedef struct TTO_ChainContextPosFormat2_  TTO_ChainContextPosFormat2;
+
+
+  struct  TTO_ChainContextPosFormat3_
+  {
+    FT_UShort             BacktrackGlyphCount;
+                                        /* number of backtrack glyphs    */
+    TTO_Coverage*         BacktrackCoverage;
+                                        /* array of backtrack Coverage
+                                           tables                        */
+    FT_UShort             InputGlyphCount;
+                                        /* number of input glyphs        */
+    TTO_Coverage*         InputCoverage;
+                                        /* array of input coverage
+                                           tables                        */
+    FT_UShort             LookaheadGlyphCount;
+                                        /* number of lookahead glyphs    */
+    TTO_Coverage*         LookaheadCoverage;
+                                        /* array of lookahead coverage
+                                           tables                        */
+    FT_UShort             PosCount;     /* number of PosLookupRecords    */
+    TTO_PosLookupRecord*  PosLookupRecord;
+                                        /* array of substitution lookups */
+  };
+
+  typedef struct TTO_ChainContextPosFormat3_  TTO_ChainContextPosFormat3;
+
+
+  struct  TTO_ChainContextPos_
+  {
+    FT_UShort  PosFormat;             /* 1, 2, or 3 */
+
+    union
+    {
+      TTO_ChainContextPosFormat1  ccpf1;
+      TTO_ChainContextPosFormat2  ccpf2;
+      TTO_ChainContextPosFormat3  ccpf3;
+    } ccpf;
+  };
+
+  typedef struct TTO_ChainContextPos_  TTO_ChainContextPos;
+
+
+  union  TTO_GPOS_SubTable_
+  {
+    TTO_SinglePos        single;
+    TTO_PairPos          pair;
+    TTO_CursivePos       cursive;
+    TTO_MarkBasePos      markbase;
+    TTO_MarkLigPos       marklig;
+    TTO_MarkMarkPos      markmark;
+    TTO_ContextPos       context;
+    TTO_ChainContextPos  chain;
+  };
+
+  typedef union TTO_GPOS_SubTable_  TTO_GPOS_SubTable;
+
+
+  /* This `string object' is much simpler compared to TTO_GSUB_String.
+     A call to TTO_GPOS_Apply_String() will allocate it.               */
+
+  struct TTO_GPOS_Data_
+  {
+    FT_Pos     x_pos;
+    FT_Pos     y_pos;
+    FT_Pos     x_advance;
+    FT_Pos     y_advance;
+    FT_UShort  back;            /* number of glyphs to go back
+                                   for drawing current glyph   */
+    FT_Bool    new_advance;     /* if set, the advance width values are
+                                   absolute, i.e., they won't be
+                                   added to the original glyph's value
+                                   but rather replace them.            */
+  };
+
+  typedef struct TTO_GPOS_Data_  TTO_GPOS_Data;
+
+
+  /* finally, the GPOS API */
+
+  /*  EXPORT_DEF
+      FT_Export ( FT_Error )  TT_Init_GPOS_Extension( TT_Engine  engine ); */
+
+  EXPORT_DEF
+  FT_Error  TT_Load_GPOS_Table( FT_Face          face,
+                                TTO_GPOSHeader** gpos,
+                                TTO_GDEFHeader*  gdef );
+
+  EXPORT_DEF
+  FT_Error  TT_Done_GPOS_Table( TTO_GPOSHeader* gpos );
+
+  EXPORT_DEF
+  FT_Error  TT_GPOS_Select_Script( TTO_GPOSHeader*  gpos,
+                                   FT_ULong         script_tag,
+                                   FT_UShort*       script_index );
+  EXPORT_DEF
+  FT_Error  TT_GPOS_Select_Language( TTO_GPOSHeader*  gpos,
+                                     FT_ULong         language_tag,
+                                     FT_UShort        script_index,
+                                     FT_UShort*       language_index,
+                                     FT_UShort*       req_feature_index );
+  EXPORT_DEF
+  FT_Error  TT_GPOS_Select_Feature( TTO_GPOSHeader*  gpos,
+                                    FT_ULong         feature_tag,
+                                    FT_UShort        script_index,
+                                    FT_UShort        language_index,
+                                    FT_UShort*       feature_index );
+
+  EXPORT_DEF
+  FT_Error  TT_GPOS_Query_Scripts( TTO_GPOSHeader*  gpos,
+                                   FT_ULong**       script_tag_list );
+  EXPORT_DEF
+  FT_Error  TT_GPOS_Query_Languages( TTO_GPOSHeader*  gpos,
+                                     FT_UShort        script_index,
+                                     FT_ULong**       language_tag_list );
+  EXPORT_DEF
+  FT_Error  TT_GPOS_Query_Features( TTO_GPOSHeader*  gpos,
+                                    FT_UShort        script_index,
+                                    FT_UShort        language_index,
+                                    FT_ULong**       feature_tag_list );
+
+  EXPORT_DEF
+  FT_Error  TT_GPOS_Add_Feature( TTO_GPOSHeader*  gpos,
+                                 FT_UShort        feature_index,
+                                 FT_UShort        property );
+  EXPORT_DEF
+  FT_Error  TT_GPOS_Clear_Features( TTO_GPOSHeader*  gpos );
+
+  EXPORT_DEF
+  FT_Error  TT_GPOS_Register_Glyph_Function( TTO_GPOSHeader*    gpos,
+                                             TTO_GlyphFunction  gfunc );
+
+  EXPORT_DEF
+  FT_Error  TT_GPOS_Register_MM_Function( TTO_GPOSHeader*  gpos,
+                                          TTO_MMFunction   mmfunc,
+                                          void*            data );
+
+  /* If `dvi' is TRUE, glyph contour points for anchor points and device
+     tables are ignored -- you will get device independent values.         */
+
+  EXPORT_DEF
+  FT_Error  TT_GPOS_Apply_String( FT_Face           face,
+                                  TTO_GPOSHeader*   gpos,
+                                  FT_UShort         load_flags,
+                                  TTO_GSUB_String*  in,
+                                  TTO_GPOS_Data**   out,
+                                  FT_Bool           dvi,
+                                  FT_Bool           r2l );
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FTXGPOS_H */
+
+
+/* END */
diff --git a/src/ftxgsub.c b/src/ftxgsub.c
new file mode 100644
index 0000000..43adb2c
--- /dev/null
+++ b/src/ftxgsub.c
@@ -0,0 +1,4531 @@
+/*******************************************************************
+ *
+ *  ftxgsub.c
+ *
+ *    TrueType Open GSUB table support.
+ *
+ *  Copyright 1996-2000 by
+ *  David Turner, Robert Wilhelm, and Werner Lemberg.
+ *
+ *  This file is part of the FreeType project, and may only be used
+ *  modified and distributed under the terms of the FreeType project
+ *  license, LICENSE.TXT.  By continuing to use, modify, or distribute
+ *  this file you indicate that you have read the license and
+ *  understand and accept it fully.
+ *
+ ******************************************************************/
+
+/* XXX There is *a lot* of duplicated code (cf. formats 5 and 6), but
+       I don't care currently.  I believe that it would be possible to
+       save about 50% of TTO code by carefully designing the structures,
+       sharing as much as possible with extensive use of macros.  This
+       is something for a volunteer :-)                                  */
+
+#define EXPORT_FUNC
+
+#include <freetype/tttags.h>
+
+#include <freetype/internal/ftstream.h>
+#include <freetype/internal/ftmemory.h>
+#include <freetype/internal/tterrors.h>
+#include <freetype/internal/tttypes.h>
+
+#include "ftxopen.h"
+#include "ftxopenf.h"
+
+
+
+#define GSUB_ID  Build_Extension_ID( 'G', 'S', 'U', 'B' )
+
+
+#define ADD_String( in, num_in, out, num_out, glyph_data, component, ligID ) \
+          ( ( error = TT_GSUB_Add_String( (in), (num_in),                    \
+                                          (out), (num_out),                  \
+                                          (glyph_data), (component), (ligID) \
+                                        ) ) != TT_Err_Ok )
+
+
+  static FT_Error  Do_Glyph_Lookup( TTO_GSUBHeader*   gsub,
+                                    FT_UShort         lookup_index,
+                                    TTO_GSUB_String*  in,
+                                    TTO_GSUB_String*  out,
+                                    FT_UShort         context_length,
+                                    int               nesting_level );
+
+
+
+  /**********************
+   * Auxiliary functions
+   **********************/
+
+
+  /* The following function copies `num_out' elements from `glyph_data'
+     to `out', advancing the array pointer in the `in' structure by
+     `num_in' elements, and in `out' by `num_out' elements.  If the
+     string (resp. the properties) array in `out' is empty or too
+     small, it allocates resp. reallocates the string (and properties)
+     array.  Finally, it sets the `length' field of `out' equal to
+     `pos' of the `out' structure.
+
+     If `component' is 0xFFFF, the value `in->component[in->pos]'
+     will be copied `num_out' times, otherwise `component' itself will
+     be used to fill `out->component'.
+
+     If `ligID' is 0xFFFF, the value `in->lig_IDs[in->pos]' will be
+     copied `num_out' times, otherwise `ligID' itself will be used to
+     fill `out->ligIDs'.
+
+     The properties (if defined) for all replaced glyphs are taken
+     from the glyph at position `in->pos'.
+
+     The logClusters[] value for the glyph at position in->pos is used
+     for all replacement glyphs */
+
+  EXPORT_FUNC
+  FT_Error  TT_GSUB_Add_String( TTO_GSUB_String*  in,
+                                FT_UShort         num_in,
+                                TTO_GSUB_String*  out,
+                                FT_UShort         num_out,
+                                FT_UShort*        glyph_data,
+                                FT_UShort         component,
+                                FT_UShort         ligID )
+  {
+    FT_Memory memory = in->memory;
+    FT_Error  error;
+    FT_UShort i;
+    FT_UShort p_in;
+    FT_UShort*p_out;
+
+
+    /* sanity check */
+
+    if ( !in || !out ||
+         in->length == 0 || in->pos >= in->length ||
+         in->length < in->pos + num_in )
+      return TT_Err_Invalid_Argument;
+
+    if ( out->pos + num_out >= out->allocated )
+    {
+      FT_ULong  size = out->pos + num_out + 256L;
+
+
+      /* The following works because all fields in `out' must be
+         initialized to zero (including the `string' field) for the
+         first use.                                                 */
+
+      if ( REALLOC_ARRAY( out->string, out->allocated, size, FT_UShort ) )
+        return error;
+      if ( REALLOC_ARRAY( out->components, out->allocated, size, FT_UShort ) )
+        return error;
+      if ( REALLOC_ARRAY( out->ligIDs, out->allocated, size, FT_UShort ) )
+        return error;
+      if ( in->properties )
+        if ( REALLOC_ARRAY( out->properties, out->allocated, size, FT_UShort ) )
+          return error;
+      if ( REALLOC_ARRAY( out->logClusters, out->allocated, size, FT_Int ) )
+	return error;
+
+      out->allocated = size;
+    }
+
+    if ( num_out )
+    {
+      MEM_Copy( &out->string[out->pos], glyph_data,
+                num_out * sizeof ( FT_UShort ) );
+
+      if ( component == 0xFFFF )
+        component = in->components[in->pos];
+
+      p_out = out->components;
+
+      for ( i = out->pos; i < out->pos + num_out; i++ )
+	p_out[i] = component;
+
+      p_out = out->ligIDs;
+
+      if ( ligID == 0xFFFF )
+        ligID = in->ligIDs[in->pos];
+
+      for ( i = out->pos; i < out->pos + num_out; i++ )
+        p_out[i] = ligID;
+
+      if ( in->properties )
+      {
+        p_in  = in->properties[in->pos];
+        p_out = out->properties;
+
+        for ( i = out->pos; i < out->pos + num_out; i++ )
+          p_out[i] = p_in;
+      }
+
+      for ( i = out->pos; i < out->pos + num_out; i++ )
+	out->logClusters[i] = in->logClusters[in->pos];
+    }
+
+    in->pos  += num_in;
+    out->pos += num_out;
+
+    out->length = out->pos;
+
+    return TT_Err_Ok;
+  }
+
+
+#if 0
+
+  /**********************
+   * Extension Functions
+   **********************/
+
+
+  static FT_Error  GSUB_Create( void*  ext,
+                                PFace  face )
+  {
+    DEFINE_LOAD_LOCALS( face->stream );
+
+    TTO_GSUBHeader*  gsub = (TTO_GSUBHeader*)ext;
+    Long             table;
+
+
+    /* by convention */
+
+    if ( !gsub )
+      return TT_Err_Ok;
+
+    /* a null offset indicates that there is no GSUB table */
+
+    gsub->offset = 0;
+
+    /* we store the start offset and the size of the subtable */
+
+    table = TT_LookUp_Table( face, TTAG_GSUB );
+    if ( table < 0 )
+      return TT_Err_Ok;             /* The table is optional */
+
+    if ( FILE_Seek( face->dirTables[table].Offset ) ||
+         ACCESS_Frame( 4L ) )
+      return error;
+
+    gsub->offset  = FILE_Pos() - 4L;    /* undo ACCESS_Frame() */
+    gsub->Version = GET_ULong();
+
+    FORGET_Frame();
+
+    gsub->loaded = FALSE;
+
+    return TT_Err_Ok;
+  }
+
+
+  static FT_Error  GSUB_Destroy( void*  ext,
+                                 PFace  face )
+  {
+    TTO_GSUBHeader*  gsub = (TTO_GSUBHeader*)ext;
+
+
+    /* by convention */
+
+    if ( !gsub )
+      return TT_Err_Ok;
+
+    if ( gsub->loaded )
+    {
+      Free_LookupList( &gsub->LookupList, GSUB, memory );
+      Free_FeatureList( &gsub->FeatureList, memory );
+      Free_ScriptList( &gsub->ScriptList, memory );
+    }
+
+    return TT_Err_Ok;
+  }
+
+
+  EXPORT_FUNC
+  FT_Error  TT_Init_GSUB_Extension( TT_Engine  engine )
+  {
+    PEngine_Instance  _engine = HANDLE_Engine( engine );
+
+
+    if ( !_engine )
+      return TT_Err_Invalid_Engine;
+
+    return  TT_Register_Extension( _engine,
+                                   GSUB_ID,
+                                   sizeof ( TTO_GSUBHeader ),
+                                   GSUB_Create,
+                                   GSUB_Destroy );
+  }
+#endif
+
+  EXPORT_FUNC
+  FT_Error  TT_Load_GSUB_Table( FT_Face          face,
+                                TTO_GSUBHeader** retptr,
+                                TTO_GDEFHeader*  gdef )
+  {
+    FT_Stream        stream = face->stream;
+    FT_Memory        memory = face->memory;
+    FT_Error         error;
+    FT_ULong         cur_offset, new_offset, base_offset;
+    TT_Face          tt_face = (TT_Face)face;
+
+    FT_UShort        i, num_lookups;
+    TTO_GSUBHeader*  gsub;
+    TTO_Lookup*      lo;
+
+    if ( !retptr )
+      return TT_Err_Invalid_Argument;
+
+    if (( error = tt_face->goto_table( tt_face, TTAG_GSUB, stream, 0 ) ))
+      return error;
+
+    base_offset = FILE_Pos();
+
+    if ( ALLOC ( gsub, sizeof( *gsub ) ) )
+      return error;
+
+    gsub->memory = memory;
+
+    /* skip version */
+
+    if ( FILE_Seek( base_offset + 4L ) ||
+         ACCESS_Frame( 2L ) )
+      goto Fail4;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_ScriptList( &gsub->ScriptList,
+                                    stream ) ) != TT_Err_Ok )
+      goto Fail4;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail3;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_FeatureList( &gsub->FeatureList,
+                                     stream ) ) != TT_Err_Ok )
+      goto Fail3;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail2;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_LookupList( &gsub->LookupList,
+                                    stream, GSUB ) ) != TT_Err_Ok )
+      goto Fail2;
+
+    gsub->gdef = gdef;      /* can be NULL */
+
+    /* We now check the LookupFlags for values larger than 0xFF to find
+       out whether we need to load the `MarkAttachClassDef' field of the
+       GDEF table -- this hack is necessary for OpenType 1.2 tables since
+       the version field of the GDEF table hasn't been incremented.
+
+       For constructed GDEF tables, we only load it if
+       `MarkAttachClassDef_offset' is not zero (nevertheless, a build of
+       a constructed mark attach table is not supported currently).       */
+
+    if ( gdef &&
+         gdef->MarkAttachClassDef_offset && !gdef->MarkAttachClassDef.loaded )
+    {
+      lo          = gsub->LookupList.Lookup;
+      num_lookups = gsub->LookupList.LookupCount;
+
+      for ( i = 0; i < num_lookups; i++ )
+      {
+
+        if ( lo[i].LookupFlag & IGNORE_SPECIAL_MARKS )
+        {
+          if ( FILE_Seek( gdef->MarkAttachClassDef_offset ) ||
+               ACCESS_Frame( 2L ) )
+            goto Fail1;
+
+          new_offset = GET_UShort();
+
+          FORGET_Frame();
+
+          if ( !new_offset )
+            return TTO_Err_Invalid_GDEF_SubTable;
+
+          new_offset += base_offset;
+
+          if ( FILE_Seek( new_offset ) ||
+               ( error = Load_ClassDefinition( &gdef->MarkAttachClassDef,
+                                               256, stream ) ) != TT_Err_Ok )
+            goto Fail1;
+
+          break;
+        }
+      }
+    }
+
+    *retptr = gsub;
+
+    return TT_Err_Ok;
+
+  Fail1:
+    Free_LookupList( &gsub->LookupList, GSUB, memory );
+
+  Fail2:
+    Free_FeatureList( &gsub->FeatureList, memory );
+
+  Fail3:
+    Free_ScriptList( &gsub->ScriptList, memory );
+
+  Fail4:
+    FREE ( gsub );
+
+
+    return error;
+  }
+
+  EXPORT_FUNC
+  FT_Error   TT_Done_GSUB_Table( TTO_GSUBHeader* gsub )
+  {
+    FT_Memory memory = gsub->memory;
+    
+    Free_LookupList( &gsub->LookupList, GSUB, memory );
+    Free_FeatureList( &gsub->FeatureList, memory );
+    Free_ScriptList( &gsub->ScriptList, memory );
+
+    FREE( gsub );
+
+    return TT_Err_Ok;
+  }
+
+  /*****************************
+   * SubTable related functions
+   *****************************/
+
+
+  /* LookupType 1 */
+
+  /* SingleSubstFormat1 */
+  /* SingleSubstFormat2 */
+
+  FT_Error  Load_SingleSubst( TTO_SingleSubst*  ss,
+                              FT_Stream         stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort n, count;
+    FT_ULong cur_offset, new_offset, base_offset;
+
+    FT_UShort*  s;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 4L ) )
+      return error;
+
+    ss->SubstFormat = GET_UShort();
+    new_offset      = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &ss->Coverage, stream ) ) != TT_Err_Ok )
+      return error;
+    (void)FILE_Seek( cur_offset );
+
+    switch ( ss->SubstFormat )
+    {
+    case 1:
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail2;
+
+      ss->ssf.ssf1.DeltaGlyphID = GET_UShort();
+
+      FORGET_Frame();
+
+      break;
+
+    case 2:
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail2;
+
+      count = ss->ssf.ssf2.GlyphCount = GET_UShort();
+
+      FORGET_Frame();
+
+      ss->ssf.ssf2.Substitute = NULL;
+
+      if ( ALLOC_ARRAY( ss->ssf.ssf2.Substitute, count, FT_UShort ) )
+        goto Fail2;
+
+      s = ss->ssf.ssf2.Substitute;
+
+      if ( ACCESS_Frame( count * 2L ) )
+        goto Fail1;
+
+      for ( n = 0; n < count; n++ )
+        s[n] = GET_UShort();
+
+      FORGET_Frame();
+
+      break;
+
+    default:
+      return TTO_Err_Invalid_GSUB_SubTable_Format;
+    }
+
+    return TT_Err_Ok;
+
+  Fail1:
+    FREE( s );
+
+  Fail2:
+    Free_Coverage( &ss->Coverage, memory );
+    return error;
+  }
+
+
+  void  Free_SingleSubst( TTO_SingleSubst*  ss,
+			  FT_Memory         memory )
+  {
+    switch ( ss->SubstFormat )
+    {
+    case 1:
+      break;
+
+    case 2:
+      FREE( ss->ssf.ssf2.Substitute );
+      break;
+    }
+
+    Free_Coverage( &ss->Coverage, memory );
+  }
+
+
+  static FT_Error  Lookup_SingleSubst( TTO_SingleSubst*  ss,
+                                       TTO_GSUB_String*  in,
+                                       TTO_GSUB_String*  out,
+                                       FT_UShort         flags,
+                                       FT_UShort         context_length,
+                                       TTO_GDEFHeader*   gdef )
+  {
+    FT_UShort index, value[1], property;
+    FT_Error  error;
+
+
+    if ( context_length != 0xFFFF && context_length < 1 )
+      return TTO_Err_Not_Covered;
+
+    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
+      return error;
+
+    error = Coverage_Index( &ss->Coverage, in->string[in->pos], &index );
+    if ( error )
+      return error;
+
+    switch ( ss->SubstFormat )
+    {
+    case 1:
+      value[0] = ( in->string[in->pos] + ss->ssf.ssf1.DeltaGlyphID ) & 0xFFFF;
+      if ( ADD_String( in, 1, out, 1, value, 0xFFFF, 0xFFFF ) )
+        return error;
+      break;
+
+    case 2:
+      if ( index >= ss->ssf.ssf2.GlyphCount )
+        return TTO_Err_Invalid_GSUB_SubTable;
+      value[0] = ss->ssf.ssf2.Substitute[index];
+      if ( ADD_String( in, 1, out, 1, value, 0xFFFF, 0xFFFF ) )
+        return error;
+      break;
+
+    default:
+      return TTO_Err_Invalid_GSUB_SubTable;
+    }
+
+    if ( gdef && gdef->NewGlyphClasses )
+    {
+      /* we inherit the old glyph class to the substituted glyph */
+
+      error = Add_Glyph_Property( gdef, value[0], property );
+      if ( error && error != TTO_Err_Not_Covered )
+        return error;
+    }
+
+    return TT_Err_Ok;
+  }
+
+
+  /* LookupType 2 */
+
+  /* Sequence */
+
+  static FT_Error  Load_Sequence( TTO_Sequence*  s,
+                                  FT_Stream      stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort n, count;
+    FT_UShort*  sub;
+
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = s->GlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    s->Substitute = NULL;
+
+    if ( count )
+    {
+      if ( ALLOC_ARRAY( s->Substitute, count, FT_UShort ) )
+        return error;
+
+      sub = s->Substitute;
+
+      if ( ACCESS_Frame( count * 2L ) )
+      {
+        FREE( sub );
+        return error;
+      }
+
+      for ( n = 0; n < count; n++ )
+        sub[n] = GET_UShort();
+
+      FORGET_Frame();
+    }
+
+    return TT_Err_Ok;
+  }
+
+
+  static void  Free_Sequence( TTO_Sequence*  s,
+			      FT_Memory      memory )
+  {
+    FREE( s->Substitute );
+  }
+
+
+  /* MultipleSubstFormat1 */
+
+  FT_Error  Load_MultipleSubst( TTO_MultipleSubst*  ms,
+                                FT_Stream           stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort      n, count;
+    FT_ULong       cur_offset, new_offset, base_offset;
+
+    TTO_Sequence*  s;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 4L ) )
+      return error;
+
+    ms->SubstFormat = GET_UShort();             /* should be 1 */
+    new_offset      = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &ms->Coverage, stream ) ) != TT_Err_Ok )
+      return error;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail2;
+
+    count = ms->SequenceCount = GET_UShort();
+
+    FORGET_Frame();
+
+    ms->Sequence = NULL;
+
+    if ( ALLOC_ARRAY( ms->Sequence, count, TTO_Sequence ) )
+      goto Fail2;
+
+    s = ms->Sequence;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail1;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_Sequence( &s[n], stream ) ) != TT_Err_Ok )
+        goto Fail1;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail1:
+    for ( n = 0; n < count; n++ )
+      Free_Sequence( &s[n], memory );
+
+    FREE( s );
+
+  Fail2:
+    Free_Coverage( &ms->Coverage, memory );
+    return error;
+  }
+
+
+  void  Free_MultipleSubst( TTO_MultipleSubst*  ms,
+			    FT_Memory           memory )
+  {
+    FT_UShort      n, count;
+
+    TTO_Sequence*  s;
+
+
+    if ( ms->Sequence )
+    {
+      count = ms->SequenceCount;
+      s     = ms->Sequence;
+
+      for ( n = 0; n < count; n++ )
+        Free_Sequence( &s[n], memory );
+
+      FREE( s );
+    }
+
+    Free_Coverage( &ms->Coverage, memory );
+  }
+
+
+  static FT_Error  Lookup_MultipleSubst( TTO_MultipleSubst*  ms,
+                                         TTO_GSUB_String*    in,
+                                         TTO_GSUB_String*    out,
+                                         FT_UShort           flags,
+                                         FT_UShort           context_length,
+                                         TTO_GDEFHeader*     gdef )
+  {
+    FT_Error  error;
+    FT_UShort index, property, n, count;
+    FT_UShort*s;
+
+
+    if ( context_length != 0xFFFF && context_length < 1 )
+      return TTO_Err_Not_Covered;
+
+    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
+      return error;
+
+    error = Coverage_Index( &ms->Coverage, in->string[in->pos], &index );
+    if ( error )
+      return error;
+
+    if ( index >= ms->SequenceCount )
+      return TTO_Err_Invalid_GSUB_SubTable;
+
+    count = ms->Sequence[index].GlyphCount;
+    s     = ms->Sequence[index].Substitute;
+
+    if ( ADD_String( in, 1, out, count, s, 0xFFFF, 0xFFFF ) )
+      return error;
+
+    if ( gdef && gdef->NewGlyphClasses )
+    {
+      /* this is a guess only ... */
+
+      if ( property == TTO_LIGATURE )
+        property = TTO_BASE_GLYPH;
+
+      for ( n = 0; n < count; n++ )
+      {
+        error = Add_Glyph_Property( gdef, s[n], property );
+        if ( error && error != TTO_Err_Not_Covered )
+          return error;
+      }
+    }
+
+    return TT_Err_Ok;
+  }
+
+
+  /* LookupType 3 */
+
+  /* AlternateSet */
+
+  static FT_Error  Load_AlternateSet( TTO_AlternateSet*  as,
+                                      FT_Stream          stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort n, count;
+    FT_UShort*  a;
+
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = as->GlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    as->Alternate = NULL;
+
+    if ( ALLOC_ARRAY( as->Alternate, count, FT_UShort ) )
+      return error;
+
+    a = as->Alternate;
+
+    if ( ACCESS_Frame( count * 2L ) )
+    {
+      FREE( a );
+      return error;
+    }
+
+    for ( n = 0; n < count; n++ )
+      a[n] = GET_UShort();
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+  }
+
+
+  static void  Free_AlternateSet( TTO_AlternateSet*  as,
+				  FT_Memory          memory )
+  {
+    FREE( as->Alternate );
+  }
+
+
+  /* AlternateSubstFormat1 */
+
+  FT_Error  Load_AlternateSubst( TTO_AlternateSubst*  as,
+                                 FT_Stream            stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort          n, count;
+    FT_ULong           cur_offset, new_offset, base_offset;
+
+    TTO_AlternateSet*  aset;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 4L ) )
+      return error;
+
+    as->SubstFormat = GET_UShort();             /* should be 1 */
+    new_offset      = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &as->Coverage, stream ) ) != TT_Err_Ok )
+      return error;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail2;
+
+    count = as->AlternateSetCount = GET_UShort();
+
+    FORGET_Frame();
+
+    as->AlternateSet = NULL;
+
+    if ( ALLOC_ARRAY( as->AlternateSet, count, TTO_AlternateSet ) )
+      goto Fail2;
+
+    aset = as->AlternateSet;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail1;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_AlternateSet( &aset[n], stream ) ) != TT_Err_Ok )
+        goto Fail1;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail1:
+    for ( n = 0; n < count; n++ )
+      Free_AlternateSet( &aset[n], memory );
+
+    FREE( aset );
+
+  Fail2:
+    Free_Coverage( &as->Coverage, memory );
+    return error;
+  }
+
+
+  void  Free_AlternateSubst( TTO_AlternateSubst*  as,
+			     FT_Memory            memory )
+  {
+    FT_UShort          n, count;
+
+    TTO_AlternateSet*  aset;
+
+
+    if ( as->AlternateSet )
+    {
+      count = as->AlternateSetCount;
+      aset  = as->AlternateSet;
+
+      for ( n = 0; n < count; n++ )
+        Free_AlternateSet( &aset[n], memory );
+
+      FREE( aset );
+    }
+
+    Free_Coverage( &as->Coverage, memory );
+  }
+
+
+  static FT_Error  Lookup_AlternateSubst( TTO_GSUBHeader*      gsub,
+                                          TTO_AlternateSubst*  as,
+                                          TTO_GSUB_String*     in,
+                                          TTO_GSUB_String*     out,
+                                          FT_UShort            flags,
+                                          FT_UShort            context_length,
+                                          TTO_GDEFHeader*      gdef )
+  {
+    FT_Error          error;
+    FT_UShort         index, alt_index, property;
+
+    TTO_AlternateSet  aset;
+
+
+    if ( context_length != 0xFFFF && context_length < 1 )
+      return TTO_Err_Not_Covered;
+
+    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
+      return error;
+
+    error = Coverage_Index( &as->Coverage, in->string[in->pos], &index );
+    if ( error )
+      return error;
+
+    aset = as->AlternateSet[index];
+
+    /* we use a user-defined callback function to get the alternate index */
+
+    if ( gsub->altfunc )
+      alt_index = (gsub->altfunc)( out->pos, in->string[in->pos],
+                                   aset.GlyphCount, aset.Alternate,
+                                   gsub->data );
+    else
+      alt_index = 0;
+
+    if ( ADD_String( in, 1, out, 1, &aset.Alternate[alt_index],
+                     0xFFFF, 0xFFFF ) )
+      return error;
+
+    if ( gdef && gdef->NewGlyphClasses )
+    {
+      /* we inherit the old glyph class to the substituted glyph */
+
+      error = Add_Glyph_Property( gdef, aset.Alternate[alt_index],
+                                  property );
+      if ( error && error != TTO_Err_Not_Covered )
+        return error;
+    }
+
+    return TT_Err_Ok;
+  }
+
+
+  /* LookupType 4 */
+
+  /* Ligature */
+
+  static FT_Error  Load_Ligature( TTO_Ligature*  l,
+                                  FT_Stream      stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort n, count;
+    FT_UShort*  c;
+
+
+    if ( ACCESS_Frame( 4L ) )
+      return error;
+
+    l->LigGlyph       = GET_UShort();
+    l->ComponentCount = GET_UShort();
+
+    FORGET_Frame();
+
+    l->Component = NULL;
+
+    count = l->ComponentCount - 1;      /* only ComponentCount - 1 elements */
+
+    if ( ALLOC_ARRAY( l->Component, count, FT_UShort ) )
+      return error;
+
+    c = l->Component;
+
+    if ( ACCESS_Frame( count * 2L ) )
+    {
+      FREE( c );
+      return error;
+    }
+
+    for ( n = 0; n < count; n++ )
+      c[n] = GET_UShort();
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+  }
+
+
+  static void  Free_Ligature( TTO_Ligature*  l,
+			      FT_Memory      memory )
+  {
+    FREE( l->Component );
+  }
+
+
+  /* LigatureSet */
+
+  static FT_Error  Load_LigatureSet( TTO_LigatureSet*  ls,
+                                     FT_Stream         stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort      n, count;
+    FT_ULong       cur_offset, new_offset, base_offset;
+
+    TTO_Ligature*  l;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = ls->LigatureCount = GET_UShort();
+
+    FORGET_Frame();
+
+    ls->Ligature = NULL;
+
+    if ( ALLOC_ARRAY( ls->Ligature, count, TTO_Ligature ) )
+      return error;
+
+    l = ls->Ligature;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_Ligature( &l[n], stream ) ) != TT_Err_Ok )
+        goto Fail;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    for ( n = 0; n < count; n++ )
+      Free_Ligature( &l[n], memory );
+
+    FREE( l );
+    return error;
+  }
+
+
+  static void  Free_LigatureSet( TTO_LigatureSet*  ls,
+				 FT_Memory         memory )
+  {
+    FT_UShort      n, count;
+
+    TTO_Ligature*  l;
+
+
+    if ( ls->Ligature )
+    {
+      count = ls->LigatureCount;
+      l     = ls->Ligature;
+
+      for ( n = 0; n < count; n++ )
+        Free_Ligature( &l[n], memory );
+
+      FREE( l );
+    }
+  }
+
+
+  /* LigatureSubstFormat1 */
+
+  FT_Error  Load_LigatureSubst( TTO_LigatureSubst*  ls,
+                                FT_Stream           stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort         n, count;
+    FT_ULong          cur_offset, new_offset, base_offset;
+
+    TTO_LigatureSet*  lset;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 4L ) )
+      return error;
+
+    ls->SubstFormat = GET_UShort();             /* should be 1 */
+    new_offset      = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &ls->Coverage, stream ) ) != TT_Err_Ok )
+      return error;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail2;
+
+    count = ls->LigatureSetCount = GET_UShort();
+
+    FORGET_Frame();
+
+    ls->LigatureSet = NULL;
+
+    if ( ALLOC_ARRAY( ls->LigatureSet, count, TTO_LigatureSet ) )
+      goto Fail2;
+
+    lset = ls->LigatureSet;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail1;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_LigatureSet( &lset[n], stream ) ) != TT_Err_Ok )
+        goto Fail1;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail1:
+    for ( n = 0; n < count; n++ )
+      Free_LigatureSet( &lset[n], memory );
+
+    FREE( lset );
+
+  Fail2:
+    Free_Coverage( &ls->Coverage, memory );
+    return error;
+  }
+
+
+  void  Free_LigatureSubst( TTO_LigatureSubst*  ls,
+			    FT_Memory           memory )
+  {
+    FT_UShort         n, count;
+
+    TTO_LigatureSet*  lset;
+
+
+    if ( ls->LigatureSet )
+    {
+      count = ls->LigatureSetCount;
+      lset  = ls->LigatureSet;
+
+      for ( n = 0; n < count; n++ )
+        Free_LigatureSet( &lset[n], memory );
+
+      FREE( lset );
+    }
+
+    Free_Coverage( &ls->Coverage, memory );
+  }
+
+
+  static FT_Error  Lookup_LigatureSubst( TTO_LigatureSubst*  ls,
+                                         TTO_GSUB_String*    in,
+                                         TTO_GSUB_String*    out,
+                                         FT_UShort           flags,
+                                         FT_UShort           context_length,
+                                         TTO_GDEFHeader*     gdef )
+  {
+    FT_UShort      index, property;
+    FT_Error       error;
+    FT_UShort      numlig, i, j, is_mark, first_is_mark = FALSE;
+    FT_UShort*     s_in;
+    FT_UShort*     c;
+
+    TTO_Ligature*  lig;
+
+
+    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
+      return error;
+
+    if ( property == TTO_MARK )
+      first_is_mark = TRUE;
+
+    error = Coverage_Index( &ls->Coverage, in->string[in->pos], &index );
+    if ( error )
+      return error;
+
+    if ( index >= ls->LigatureSetCount )
+       return TTO_Err_Invalid_GSUB_SubTable;
+
+    lig = ls->LigatureSet[index].Ligature;
+
+    for ( numlig = ls->LigatureSet[index].LigatureCount;
+          numlig;
+          numlig--, lig++ )
+    {
+      if ( in->pos + lig->ComponentCount > in->length )
+        continue;                         /* Not enough glyphs in input */
+
+      s_in = &in->string[in->pos];
+      c    = lig->Component;
+
+      is_mark = first_is_mark;
+
+      if ( context_length != 0xFFFF && context_length < lig->ComponentCount )
+        break;
+
+      for ( i = 1, j = 1; i < lig->ComponentCount; i++, j++ )
+      {
+        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+        {
+          if ( error && error != TTO_Err_Not_Covered )
+            return error;
+
+          if ( in->pos + j < in->length )
+            j++;
+          else
+            break;
+        }
+
+        if ( property != TTO_MARK )
+          is_mark = FALSE;
+
+        if ( s_in[j] != c[i - 1] )
+          break;
+      }
+
+      if ( i == lig->ComponentCount )
+      {
+        if ( gdef && gdef->NewGlyphClasses )
+        {
+          /* this is just a guess ... */
+
+          error = Add_Glyph_Property( gdef, lig->LigGlyph,
+                                      is_mark ? TTO_MARK : TTO_LIGATURE );
+          if ( error && error != TTO_Err_Not_Covered )
+            return error;
+        }
+
+        if ( i == j )
+        {
+          /* We don't use a new ligature ID if there are no skipped
+             glyphs and the ligature already has an ID.             */
+
+          if ( in->ligIDs[in->pos] )
+          {
+            if ( ADD_String( in, i, out, 1, &lig->LigGlyph,
+                             0xFFFF, 0xFFFF ) )
+              return error;
+          }
+          else
+          {
+            if ( ADD_String( in, i, out, 1, &lig->LigGlyph,
+                             0xFFFF, in->max_ligID ) )
+              return error;
+
+            (in->max_ligID)++;
+          }
+        }
+        else
+        {
+          if ( ADD_String( in, 1, out, 1, &lig->LigGlyph,
+                           0xFFFF, in->max_ligID ) )
+            return error;
+
+          /* Now we must do a second loop to copy the skipped glyphs to
+             `out' and assign component values to it.  We start with the
+             glyph after the first component.  Glyphs between component
+             i and i+1 belong to component i.  Together with the ligID
+             value it is later possible to check whether a specific
+             component value really belongs to a given ligature.         */
+
+          for ( i = 0; i < lig->ComponentCount - 1; i++ )
+          {
+            while ( CHECK_Property( gdef, in->string[in->pos],
+                                    flags, &property ) )
+              if ( ADD_String( in, 1, out, 1, &in->string[in->pos],
+                               i, in->max_ligID ) )
+                return error;
+
+            (in->pos)++;
+          }
+
+          (in->max_ligID)++;
+        }
+
+        return TT_Err_Ok;
+      }
+    }
+
+    return TTO_Err_Not_Covered;
+  }
+
+
+  /* Do the actual substitution for a context substitution (either format
+     5 or 6).  This is only called after we've determined that the input
+     matches the subrule.                                                 */
+
+  static FT_Error  Do_ContextSubst( TTO_GSUBHeader*         gsub,
+                                    FT_UShort               GlyphCount,
+                                    FT_UShort               SubstCount,
+                                    TTO_SubstLookupRecord*  subst,
+                                    TTO_GSUB_String*        in,
+                                    TTO_GSUB_String*        out,
+                                    int                     nesting_level )
+  {
+    FT_Error  error;
+    FT_UShort i, old_pos;
+
+
+    i = 0;
+
+    while ( i < GlyphCount )
+    {
+      if ( SubstCount && i == subst->SequenceIndex )
+      {
+        old_pos = in->pos;
+
+        /* Do a substitution */
+
+        error = Do_Glyph_Lookup( gsub, subst->LookupListIndex, in, out,
+                                 GlyphCount, nesting_level );
+
+        subst++;
+        SubstCount--;
+        i += in->pos - old_pos;
+
+        if ( error == TTO_Err_Not_Covered )
+        {
+          /* XXX "can't happen" -- but don't count on it */
+
+          if ( ADD_String( in, 1, out, 1, &in->string[in->pos],
+                           0xFFFF, 0xFFFF ) )
+            return error;
+          i++;
+        }
+        else if ( error )
+          return error;
+      }
+      else
+      {
+        /* No substitution for this index */
+
+        if ( ADD_String( in, 1, out, 1, &in->string[in->pos],
+                         0xFFFF, 0xFFFF ) )
+          return error;
+        i++;
+      }
+    }
+
+    return TT_Err_Ok;
+  }
+
+
+  /* LookupType 5 */
+
+  /* SubRule */
+
+  static FT_Error  Load_SubRule( TTO_SubRule*  sr,
+                                 FT_Stream     stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort               n, count;
+    FT_UShort*              i;
+
+    TTO_SubstLookupRecord*  slr;
+
+
+    if ( ACCESS_Frame( 4L ) )
+      return error;
+
+    sr->GlyphCount = GET_UShort();
+    sr->SubstCount = GET_UShort();
+
+    FORGET_Frame();
+
+    sr->Input = NULL;
+
+    count = sr->GlyphCount - 1;         /* only GlyphCount - 1 elements */
+
+    if ( ALLOC_ARRAY( sr->Input, count, FT_UShort ) )
+      return error;
+
+    i = sr->Input;
+
+    if ( ACCESS_Frame( count * 2L ) )
+      goto Fail2;
+
+    for ( n = 0; n < count; n++ )
+      i[n] = GET_UShort();
+
+    FORGET_Frame();
+
+    sr->SubstLookupRecord = NULL;
+
+    count = sr->SubstCount;
+
+    if ( ALLOC_ARRAY( sr->SubstLookupRecord, count, TTO_SubstLookupRecord ) )
+      goto Fail2;
+
+    slr = sr->SubstLookupRecord;
+
+    if ( ACCESS_Frame( count * 4L ) )
+      goto Fail1;
+
+    for ( n = 0; n < count; n++ )
+    {
+      slr[n].SequenceIndex   = GET_UShort();
+      slr[n].LookupListIndex = GET_UShort();
+    }
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+
+  Fail1:
+    FREE( slr );
+
+  Fail2:
+    FREE( i );
+    return error;
+  }
+
+
+  static void  Free_SubRule( TTO_SubRule*  sr,
+			     FT_Memory     memory )
+  {
+    FREE( sr->SubstLookupRecord );
+    FREE( sr->Input );
+  }
+
+
+  /* SubRuleSet */
+
+  static FT_Error  Load_SubRuleSet( TTO_SubRuleSet*  srs,
+                                    FT_Stream        stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort     n, count;
+    FT_ULong      cur_offset, new_offset, base_offset;
+
+    TTO_SubRule*  sr;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = srs->SubRuleCount = GET_UShort();
+
+    FORGET_Frame();
+
+    srs->SubRule = NULL;
+
+    if ( ALLOC_ARRAY( srs->SubRule, count, TTO_SubRule ) )
+      return error;
+
+    sr = srs->SubRule;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_SubRule( &sr[n], stream ) ) != TT_Err_Ok )
+        goto Fail;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    for ( n = 0; n < count; n++ )
+      Free_SubRule( &sr[n], memory );
+
+    FREE( sr );
+    return error;
+  }
+
+
+  static void  Free_SubRuleSet( TTO_SubRuleSet*  srs,
+				FT_Memory        memory )
+  {
+    FT_UShort     n, count;
+
+    TTO_SubRule*  sr;
+
+
+    if ( srs->SubRule )
+    {
+      count = srs->SubRuleCount;
+      sr    = srs->SubRule;
+
+      for ( n = 0; n < count; n++ )
+        Free_SubRule( &sr[n], memory );
+
+      FREE( sr );
+    }
+  }
+
+
+  /* ContextSubstFormat1 */
+
+  static FT_Error  Load_ContextSubst1( TTO_ContextSubstFormat1*  csf1,
+                                       FT_Stream                 stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort        n, count;
+    FT_ULong         cur_offset, new_offset, base_offset;
+
+    TTO_SubRuleSet*  srs;
+
+
+    base_offset = FILE_Pos() - 2L;
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &csf1->Coverage, stream ) ) != TT_Err_Ok )
+      return error;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail2;
+
+    count = csf1->SubRuleSetCount = GET_UShort();
+
+    FORGET_Frame();
+
+    csf1->SubRuleSet = NULL;
+
+    if ( ALLOC_ARRAY( csf1->SubRuleSet, count, TTO_SubRuleSet ) )
+      goto Fail2;
+
+    srs = csf1->SubRuleSet;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail1;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_SubRuleSet( &srs[n], stream ) ) != TT_Err_Ok )
+        goto Fail1;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail1:
+    for ( n = 0; n < count; n++ )
+      Free_SubRuleSet( &srs[n], memory );
+
+    FREE( srs );
+
+  Fail2:
+    Free_Coverage( &csf1->Coverage, memory );
+    return error;
+  }
+
+
+  static void  Free_Context1( TTO_ContextSubstFormat1* csf1,
+			      FT_Memory                memory )
+  {
+    FT_UShort        n, count;
+
+    TTO_SubRuleSet*  srs;
+
+
+    if ( csf1->SubRuleSet )
+    {
+      count = csf1->SubRuleSetCount;
+      srs   = csf1->SubRuleSet;
+
+      for ( n = 0; n < count; n++ )
+        Free_SubRuleSet( &srs[n], memory );
+
+      FREE( srs );
+    }
+
+    Free_Coverage( &csf1->Coverage, memory );
+  }
+
+
+  /* SubClassRule */
+
+  static FT_Error  Load_SubClassRule( TTO_ContextSubstFormat2*  csf2,
+                                      TTO_SubClassRule*         scr,
+                                      FT_Stream                 stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort               n, count;
+
+    FT_UShort*              c;
+    TTO_SubstLookupRecord*  slr;
+    FT_Bool*                d;
+
+
+    if ( ACCESS_Frame( 4L ) )
+      return error;
+
+    scr->GlyphCount = GET_UShort();
+    scr->SubstCount = GET_UShort();
+
+    if ( scr->GlyphCount > csf2->MaxContextLength )
+      csf2->MaxContextLength = scr->GlyphCount;
+
+    FORGET_Frame();
+
+    scr->Class = NULL;
+
+    count = scr->GlyphCount - 1;        /* only GlyphCount - 1 elements */
+
+    if ( ALLOC_ARRAY( scr->Class, count, FT_UShort ) )
+      return error;
+
+    c = scr->Class;
+    d = csf2->ClassDef.Defined;
+
+    if ( ACCESS_Frame( count * 2L ) )
+      goto Fail2;
+
+    for ( n = 0; n < count; n++ )
+    {
+      c[n] = GET_UShort();
+
+      /* We check whether the specific class is used at all.  If not,
+         class 0 is used instead.                                     */
+      if ( !d[c[n]] )
+        c[n] = 0;
+    }
+
+    FORGET_Frame();
+
+    scr->SubstLookupRecord = NULL;
+
+    count = scr->SubstCount;
+
+    if ( ALLOC_ARRAY( scr->SubstLookupRecord, count, TTO_SubstLookupRecord ) )
+      goto Fail2;
+
+    slr = scr->SubstLookupRecord;
+
+    if ( ACCESS_Frame( count * 4L ) )
+      goto Fail1;
+
+    for ( n = 0; n < count; n++ )
+    {
+      slr[n].SequenceIndex   = GET_UShort();
+      slr[n].LookupListIndex = GET_UShort();
+    }
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+
+  Fail1:
+    FREE( slr );
+
+  Fail2:
+    FREE( c );
+    return error;
+  }
+
+
+  static void  Free_SubClassRule( TTO_SubClassRule*  scr,
+				  FT_Memory          memory )
+  {
+    FREE( scr->SubstLookupRecord );
+    FREE( scr->Class );
+  }
+
+
+  /* SubClassSet */
+
+  static FT_Error  Load_SubClassSet( TTO_ContextSubstFormat2*  csf2,
+                                     TTO_SubClassSet*          scs,
+                                     FT_Stream                 stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort          n, count;
+    FT_ULong           cur_offset, new_offset, base_offset;
+
+    TTO_SubClassRule*  scr;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = scs->SubClassRuleCount = GET_UShort();
+
+    FORGET_Frame();
+
+    scs->SubClassRule = NULL;
+
+    if ( ALLOC_ARRAY( scs->SubClassRule, count, TTO_SubClassRule ) )
+      return error;
+
+    scr = scs->SubClassRule;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_SubClassRule( csf2, &scr[n],
+                                        stream ) ) != TT_Err_Ok )
+        goto Fail;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    for ( n = 0; n < count; n++ )
+      Free_SubClassRule( &scr[n], memory );
+
+    FREE( scr );
+    return error;
+  }
+
+
+  static void  Free_SubClassSet( TTO_SubClassSet*  scs,
+				 FT_Memory         memory )
+  {
+    FT_UShort          n, count;
+
+    TTO_SubClassRule*  scr;
+
+
+    if ( scs->SubClassRule )
+    {
+      count = scs->SubClassRuleCount;
+      scr   = scs->SubClassRule;
+
+      for ( n = 0; n < count; n++ )
+        Free_SubClassRule( &scr[n], memory );
+
+      FREE( scr );
+    }
+  }
+
+
+  /* ContextSubstFormat2 */
+
+  static FT_Error  Load_ContextSubst2( TTO_ContextSubstFormat2*  csf2,
+                                       FT_Stream                 stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort         n, count;
+    FT_ULong          cur_offset, new_offset, base_offset;
+
+    TTO_SubClassSet*  scs;
+
+
+    base_offset = FILE_Pos() - 2;
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &csf2->Coverage, stream ) ) != TT_Err_Ok )
+      return error;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 4L ) )
+      goto Fail3;
+
+    new_offset = GET_UShort() + base_offset;
+
+    /* `SubClassSetCount' is the upper limit for class values, thus we
+       read it now to make an additional safety check.                 */
+
+    count = csf2->SubClassSetCount = GET_UShort();
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_ClassDefinition( &csf2->ClassDef, count,
+                                         stream ) ) != TT_Err_Ok )
+      goto Fail3;
+    (void)FILE_Seek( cur_offset );
+
+    csf2->SubClassSet      = NULL;
+    csf2->MaxContextLength = 0;
+
+    if ( ALLOC_ARRAY( csf2->SubClassSet, count, TTO_SubClassSet ) )
+      goto Fail2;
+
+    scs = csf2->SubClassSet;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail1;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      if ( new_offset != base_offset )      /* not a NULL offset */
+      {
+        cur_offset = FILE_Pos();
+        if ( FILE_Seek( new_offset ) ||
+             ( error = Load_SubClassSet( csf2, &scs[n],
+                                         stream ) ) != TT_Err_Ok )
+          goto Fail1;
+        (void)FILE_Seek( cur_offset );
+      }
+      else
+      {
+        /* we create a SubClassSet table with no entries */
+
+        csf2->SubClassSet[n].SubClassRuleCount = 0;
+        csf2->SubClassSet[n].SubClassRule      = NULL;
+      }
+    }
+
+    return TT_Err_Ok;
+
+  Fail1:
+    for ( n = 0; n < count; n++ )
+      Free_SubClassSet( &scs[n], memory );
+
+    FREE( scs );
+
+  Fail2:
+    Free_ClassDefinition( &csf2->ClassDef, memory );
+
+  Fail3:
+    Free_Coverage( &csf2->Coverage, memory );
+    return error;
+  }
+
+
+  static void  Free_Context2( TTO_ContextSubstFormat2*  csf2,
+			      FT_Memory                 memory )
+  {
+    FT_UShort         n, count;
+
+    TTO_SubClassSet*  scs;
+
+
+    if ( csf2->SubClassSet )
+    {
+      count = csf2->SubClassSetCount;
+      scs   = csf2->SubClassSet;
+
+      for ( n = 0; n < count; n++ )
+        Free_SubClassSet( &scs[n], memory );
+
+      FREE( scs );
+    }
+
+    Free_ClassDefinition( &csf2->ClassDef, memory );
+    Free_Coverage( &csf2->Coverage, memory );
+  }
+
+
+  /* ContextSubstFormat3 */
+
+  static FT_Error  Load_ContextSubst3( TTO_ContextSubstFormat3*  csf3,
+                                       FT_Stream                 stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort               n, count;
+    FT_ULong                cur_offset, new_offset, base_offset;
+
+    TTO_Coverage*           c;
+    TTO_SubstLookupRecord*  slr;
+
+
+    base_offset = FILE_Pos() - 2L;
+
+    if ( ACCESS_Frame( 4L ) )
+      return error;
+
+    csf3->GlyphCount = GET_UShort();
+    csf3->SubstCount = GET_UShort();
+
+    FORGET_Frame();
+
+    csf3->Coverage = NULL;
+
+    count = csf3->GlyphCount;
+
+    if ( ALLOC_ARRAY( csf3->Coverage, count, TTO_Coverage ) )
+      return error;
+
+    c = csf3->Coverage;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail2;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_Coverage( &c[n], stream ) ) != TT_Err_Ok )
+        goto Fail2;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    csf3->SubstLookupRecord = NULL;
+
+    count = csf3->SubstCount;
+
+    if ( ALLOC_ARRAY( csf3->SubstLookupRecord, count,
+                      TTO_SubstLookupRecord ) )
+      goto Fail2;
+
+    slr = csf3->SubstLookupRecord;
+
+    if ( ACCESS_Frame( count * 4L ) )
+      goto Fail1;
+
+    for ( n = 0; n < count; n++ )
+    {
+      slr[n].SequenceIndex   = GET_UShort();
+      slr[n].LookupListIndex = GET_UShort();
+    }
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+
+  Fail1:
+    FREE( slr );
+
+  Fail2:
+    for ( n = 0; n < count; n++ )
+      Free_Coverage( &c[n], memory );
+
+    FREE( c );
+    return error;
+  }
+
+
+  static void  Free_Context3( TTO_ContextSubstFormat3*  csf3,
+			      FT_Memory                 memory )
+  {
+    FT_UShort      n, count;
+
+    TTO_Coverage*  c;
+
+
+    FREE( csf3->SubstLookupRecord );
+
+    if ( csf3->Coverage )
+    {
+      count = csf3->GlyphCount;
+      c     = csf3->Coverage;
+
+      for ( n = 0; n < count; n++ )
+        Free_Coverage( &c[n], memory );
+
+      FREE( c );
+    }
+  }
+
+
+  /* ContextSubst */
+
+  FT_Error  Load_ContextSubst( TTO_ContextSubst*  cs,
+                               FT_Stream          stream )
+  {
+    FT_Error error;
+
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    cs->SubstFormat = GET_UShort();
+
+    FORGET_Frame();
+
+    switch ( cs->SubstFormat )
+    {
+    case 1:
+      return Load_ContextSubst1( &cs->csf.csf1, stream );
+
+    case 2:
+      return Load_ContextSubst2( &cs->csf.csf2, stream );
+
+    case 3:
+      return Load_ContextSubst3( &cs->csf.csf3, stream );
+
+    default:
+      return TTO_Err_Invalid_GSUB_SubTable_Format;
+    }
+
+    return TT_Err_Ok;               /* never reached */
+  }
+
+
+  void  Free_ContextSubst( TTO_ContextSubst*  cs,
+			   FT_Memory          memory )
+  {
+    switch ( cs->SubstFormat )
+    {
+    case 1:
+      Free_Context1( &cs->csf.csf1, memory );
+      break;
+
+    case 2:
+      Free_Context2( &cs->csf.csf2, memory );
+      break;
+
+    case 3:
+      Free_Context3( &cs->csf.csf3, memory );
+      break;
+    }
+  }
+
+
+  static FT_Error  Lookup_ContextSubst1(
+                     TTO_GSUBHeader*           gsub,
+                     TTO_ContextSubstFormat1*  csf1,
+                     TTO_GSUB_String*          in,
+                     TTO_GSUB_String*          out,
+                     FT_UShort                 flags,
+                     FT_UShort                 context_length,
+                     int                       nesting_level )
+  {
+    FT_UShort        index, property;
+    FT_UShort        i, j, k, numsr;
+    FT_Error         error;
+    FT_UShort*       s_in;
+
+    TTO_SubRule*     sr;
+    TTO_GDEFHeader*  gdef;
+
+
+    gdef = gsub->gdef;
+
+    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
+      return error;
+
+    error = Coverage_Index( &csf1->Coverage, in->string[in->pos], &index );
+    if ( error )
+      return error;
+
+    sr    = csf1->SubRuleSet[index].SubRule;
+    numsr = csf1->SubRuleSet[index].SubRuleCount;
+
+    for ( k = 0; k < numsr; k++ )
+    {
+      if ( context_length != 0xFFFF && context_length < sr[k].GlyphCount )
+        continue;
+
+      if ( in->pos + sr[k].GlyphCount > in->length )
+        continue;                           /* context is too long */
+
+      s_in = &in->string[in->pos];
+
+      for ( i = 1, j = 1; i < sr[k].GlyphCount; i++, j++ )
+      {
+        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+        {
+          if ( error && error != TTO_Err_Not_Covered )
+            return error;
+
+          if ( in->pos + j < in->length )
+            j++;
+          else
+            break;
+        }
+
+        if ( s_in[j] != sr[k].Input[i - 1] )
+          break;
+      }
+
+      if ( i == sr[k].GlyphCount )
+        return Do_ContextSubst( gsub, sr[k].GlyphCount,
+                                sr[k].SubstCount, sr[k].SubstLookupRecord,
+                                in, out,
+                                nesting_level );
+    }
+
+    return TTO_Err_Not_Covered;
+  }
+
+
+  static FT_Error  Lookup_ContextSubst2(
+                     TTO_GSUBHeader*           gsub,
+                     TTO_ContextSubstFormat2*  csf2,
+                     TTO_GSUB_String*          in,
+                     TTO_GSUB_String*          out,
+                     FT_UShort                 flags,
+                     FT_UShort                 context_length,
+                     int                       nesting_level )
+  {
+    FT_UShort          index, property;
+    FT_Error           error;
+    FT_Memory          memory = gsub->memory;
+    FT_UShort          i, j, k, known_classes;
+
+    FT_UShort*         classes;
+    FT_UShort*         s_in;
+    FT_UShort*         cl;
+
+    TTO_SubClassSet*   scs;
+    TTO_SubClassRule*  sr;
+    TTO_GDEFHeader*    gdef;
+
+
+    gdef = gsub->gdef;
+
+    if ( ALLOC_ARRAY( classes, csf2->MaxContextLength, FT_UShort ) )
+      return error;
+
+    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
+      return error;
+
+    /* Note: The coverage table in format 2 doesn't give an index into
+             anything.  It just lets us know whether or not we need to
+             do any lookup at all.                                     */
+
+    error = Coverage_Index( &csf2->Coverage, in->string[in->pos], &index );
+    if ( error )
+      goto End;
+
+    error = Get_Class( &csf2->ClassDef, in->string[in->pos],
+                       &classes[0], NULL );
+    if ( error )
+      goto End;
+    known_classes = 0;
+
+    scs = &csf2->SubClassSet[classes[0]];
+    if ( !scs )
+    {
+      error = TTO_Err_Invalid_GSUB_SubTable;
+      goto End;
+    }
+
+    for ( k = 0; k < scs->SubClassRuleCount; k++ )
+    {
+      sr  = &scs->SubClassRule[k];
+
+      if ( context_length != 0xFFFF && context_length < sr->GlyphCount )
+        continue;
+
+      if ( in->pos + sr->GlyphCount > in->length )
+        continue;                           /* context is too long */
+
+      s_in = &in->string[in->pos];
+      cl   = sr->Class;
+
+      /* Start at 1 because [0] is implied */
+
+      for ( i = 1, j = 1; i < sr->GlyphCount; i++, j++ )
+      {
+        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+        {
+          if ( error && error != TTO_Err_Not_Covered )
+            return error;
+
+          if ( in->pos + j < in->length )
+            j++;
+          else
+            break;
+        }
+
+        if ( i > known_classes )
+        {
+          /* Keeps us from having to do this for each rule */
+
+          error = Get_Class( &csf2->ClassDef, s_in[j], &classes[i], NULL );
+          if ( error && error != TTO_Err_Not_Covered )
+            return error;
+          known_classes = i;
+        }
+
+        if ( cl[i - 1] != classes[i] )
+          break;
+      }
+
+      if ( i == sr->GlyphCount )
+      {
+        error = Do_ContextSubst( gsub, sr->GlyphCount,
+                                 sr->SubstCount, sr->SubstLookupRecord,
+                                 in, out,
+                                 nesting_level );
+        goto End;
+      }
+    }
+
+    error = TTO_Err_Not_Covered;
+
+  End:
+    FREE( classes );
+    return error;
+  }
+
+
+  static FT_Error  Lookup_ContextSubst3(
+                     TTO_GSUBHeader*           gsub,
+                     TTO_ContextSubstFormat3*  csf3,
+                     TTO_GSUB_String*          in,
+                     TTO_GSUB_String*          out,
+                     FT_UShort                 flags,
+                     FT_UShort                 context_length,
+                     int                       nesting_level )
+  {
+    FT_Error         error;
+    FT_UShort        index, i, j, property;
+    FT_UShort*       s_in;
+
+    TTO_Coverage*    c;
+    TTO_GDEFHeader*  gdef;
+
+
+    gdef = gsub->gdef;
+
+    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
+      return error;
+
+    if ( context_length != 0xFFFF && context_length < csf3->GlyphCount )
+      return TTO_Err_Not_Covered;
+
+    if ( in->pos + csf3->GlyphCount > in->length )
+      return TTO_Err_Not_Covered;         /* context is too long */
+
+    s_in = &in->string[in->pos];
+    c    = csf3->Coverage;
+
+    for ( i = 1, j = 1; i < csf3->GlyphCount; i++, j++ )
+    {
+      while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+      {
+        if ( error && error != TTO_Err_Not_Covered )
+          return error;
+
+        if ( in->pos + j < in->length )
+          j++;
+        else
+          return TTO_Err_Not_Covered;
+      }
+
+      error = Coverage_Index( &c[i], s_in[j], &index );
+      if ( error )
+        return error;
+    }
+
+    return Do_ContextSubst( gsub, csf3->GlyphCount,
+                            csf3->SubstCount, csf3->SubstLookupRecord,
+                            in, out,
+                            nesting_level );
+  }
+
+
+  static FT_Error  Lookup_ContextSubst( TTO_GSUBHeader*    gsub,
+                                        TTO_ContextSubst*  cs,
+                                        TTO_GSUB_String*   in,
+                                        TTO_GSUB_String*   out,
+                                        FT_UShort          flags,
+                                        FT_UShort          context_length,
+                                        int                nesting_level )
+  {
+    switch ( cs->SubstFormat )
+    {
+    case 1:
+      return Lookup_ContextSubst1( gsub, &cs->csf.csf1, in, out,
+                                   flags, context_length, nesting_level );
+
+    case 2:
+      return Lookup_ContextSubst2( gsub, &cs->csf.csf2, in, out,
+                                   flags, context_length, nesting_level );
+
+    case 3:
+      return Lookup_ContextSubst3( gsub, &cs->csf.csf3, in, out,
+                                   flags, context_length, nesting_level );
+
+    default:
+      return TTO_Err_Invalid_GSUB_SubTable_Format;
+    }
+
+    return TT_Err_Ok;               /* never reached */
+  }
+
+
+  /* LookupType 6 */
+
+  /* ChainSubRule */
+
+  static FT_Error  Load_ChainSubRule( TTO_ChainSubRule*  csr,
+                                      FT_Stream          stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort               n, count;
+    FT_UShort*              b;
+    FT_UShort*              i;
+    FT_UShort*              l;
+
+    TTO_SubstLookupRecord*  slr;
+
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    csr->BacktrackGlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    csr->Backtrack = NULL;
+
+    count = csr->BacktrackGlyphCount;
+
+    if ( ALLOC_ARRAY( csr->Backtrack, count, FT_UShort ) )
+      return error;
+
+    b = csr->Backtrack;
+
+    if ( ACCESS_Frame( count * 2L ) )
+      goto Fail4;
+
+    for ( n = 0; n < count; n++ )
+      b[n] = GET_UShort();
+
+    FORGET_Frame();
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail4;
+
+    csr->InputGlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    csr->Input = NULL;
+
+    count = csr->InputGlyphCount - 1;  /* only InputGlyphCount - 1 elements */
+
+    if ( ALLOC_ARRAY( csr->Input, count, FT_UShort ) )
+      goto Fail4;
+
+    i = csr->Input;
+
+    if ( ACCESS_Frame( count * 2L ) )
+      goto Fail3;
+
+    for ( n = 0; n < count; n++ )
+      i[n] = GET_UShort();
+
+    FORGET_Frame();
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail3;
+
+    csr->LookaheadGlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    csr->Lookahead = NULL;
+
+    count = csr->LookaheadGlyphCount;
+
+    if ( ALLOC_ARRAY( csr->Lookahead, count, FT_UShort ) )
+      goto Fail3;
+
+    l = csr->Lookahead;
+
+    if ( ACCESS_Frame( count * 2L ) )
+      goto Fail2;
+
+    for ( n = 0; n < count; n++ )
+      l[n] = GET_UShort();
+
+    FORGET_Frame();
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail2;
+
+    csr->SubstCount = GET_UShort();
+
+    FORGET_Frame();
+
+    csr->SubstLookupRecord = NULL;
+
+    count = csr->SubstCount;
+
+    if ( ALLOC_ARRAY( csr->SubstLookupRecord, count, TTO_SubstLookupRecord ) )
+      goto Fail2;
+
+    slr = csr->SubstLookupRecord;
+
+    if ( ACCESS_Frame( count * 4L ) )
+      goto Fail1;
+
+    for ( n = 0; n < count; n++ )
+    {
+      slr[n].SequenceIndex   = GET_UShort();
+      slr[n].LookupListIndex = GET_UShort();
+    }
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+
+  Fail1:
+    FREE( slr );
+
+  Fail2:
+    FREE( l );
+
+  Fail3:
+    FREE( i );
+
+  Fail4:
+    FREE( b );
+    return error;
+  }
+
+
+  static void  Free_ChainSubRule( TTO_ChainSubRule*  csr,
+				  FT_Memory          memory )
+  {
+    FREE( csr->SubstLookupRecord );
+    FREE( csr->Lookahead );
+    FREE( csr->Input );
+    FREE( csr->Backtrack );
+  }
+
+
+  /* ChainSubRuleSet */
+
+  static FT_Error  Load_ChainSubRuleSet( TTO_ChainSubRuleSet*  csrs,
+                                         FT_Stream             stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort          n, count;
+    FT_ULong           cur_offset, new_offset, base_offset;
+
+    TTO_ChainSubRule*  csr;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = csrs->ChainSubRuleCount = GET_UShort();
+
+    FORGET_Frame();
+
+    csrs->ChainSubRule = NULL;
+
+    if ( ALLOC_ARRAY( csrs->ChainSubRule, count, TTO_ChainSubRule ) )
+      return error;
+
+    csr = csrs->ChainSubRule;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_ChainSubRule( &csr[n], stream ) ) != TT_Err_Ok )
+        goto Fail;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    for ( n = 0; n < count; n++ )
+      Free_ChainSubRule( &csr[n], memory );
+
+    FREE( csr );
+    return error;
+  }
+
+
+  static void  Free_ChainSubRuleSet( TTO_ChainSubRuleSet*  csrs,
+				     FT_Memory             memory )
+  {
+    FT_UShort          n, count;
+
+    TTO_ChainSubRule*  csr;
+
+
+    if ( csrs->ChainSubRule )
+    {
+      count = csrs->ChainSubRuleCount;
+      csr   = csrs->ChainSubRule;
+
+      for ( n = 0; n < count; n++ )
+        Free_ChainSubRule( &csr[n], memory );
+
+      FREE( csr );
+    }
+  }
+
+
+  /* ChainContextSubstFormat1 */
+
+  static FT_Error  Load_ChainContextSubst1(
+                     TTO_ChainContextSubstFormat1*  ccsf1,
+                     FT_Stream                      stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort             n, count;
+    FT_ULong              cur_offset, new_offset, base_offset;
+
+    TTO_ChainSubRuleSet*  csrs;
+
+
+    base_offset = FILE_Pos() - 2L;
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &ccsf1->Coverage, stream ) ) != TT_Err_Ok )
+      return error;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail2;
+
+    count = ccsf1->ChainSubRuleSetCount = GET_UShort();
+
+    FORGET_Frame();
+
+    ccsf1->ChainSubRuleSet = NULL;
+
+    if ( ALLOC_ARRAY( ccsf1->ChainSubRuleSet, count, TTO_ChainSubRuleSet ) )
+      goto Fail2;
+
+    csrs = ccsf1->ChainSubRuleSet;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail1;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_ChainSubRuleSet( &csrs[n], stream ) ) != TT_Err_Ok )
+        goto Fail1;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail1:
+    for ( n = 0; n < count; n++ )
+      Free_ChainSubRuleSet( &csrs[n], memory );
+
+    FREE( csrs );
+
+  Fail2:
+    Free_Coverage( &ccsf1->Coverage, memory );
+    return error;
+  }
+
+
+  static void  Free_ChainContext1( TTO_ChainContextSubstFormat1*  ccsf1,
+				   FT_Memory                      memory )
+  {
+    FT_UShort             n, count;
+
+    TTO_ChainSubRuleSet*  csrs;
+
+
+    if ( ccsf1->ChainSubRuleSet )
+    {
+      count = ccsf1->ChainSubRuleSetCount;
+      csrs  = ccsf1->ChainSubRuleSet;
+
+      for ( n = 0; n < count; n++ )
+        Free_ChainSubRuleSet( &csrs[n], memory );
+
+      FREE( csrs );
+    }
+
+    Free_Coverage( &ccsf1->Coverage, memory );
+  }
+
+
+  /* ChainSubClassRule */
+
+  static FT_Error  Load_ChainSubClassRule(
+                     TTO_ChainContextSubstFormat2*  ccsf2,
+                     TTO_ChainSubClassRule*         cscr,
+                     FT_Stream                      stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort               n, count;
+
+    FT_UShort*              b;
+    FT_UShort*              i;
+    FT_UShort*              l;
+    TTO_SubstLookupRecord*  slr;
+    FT_Bool*                d;
+
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    cscr->BacktrackGlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    if ( cscr->BacktrackGlyphCount > ccsf2->MaxBacktrackLength )
+      ccsf2->MaxBacktrackLength = cscr->BacktrackGlyphCount;
+
+    cscr->Backtrack = NULL;
+
+    count = cscr->BacktrackGlyphCount;
+
+    if ( ALLOC_ARRAY( cscr->Backtrack, count, FT_UShort ) )
+      return error;
+
+    b = cscr->Backtrack;
+    d = ccsf2->BacktrackClassDef.Defined;
+
+    if ( ACCESS_Frame( count * 2L ) )
+      goto Fail4;
+
+    for ( n = 0; n < count; n++ )
+    {
+      b[n] = GET_UShort();
+
+      /* We check whether the specific class is used at all.  If not,
+         class 0 is used instead.                                     */
+
+      if ( !d[b[n]] )
+        b[n] = 0;
+    }
+
+    FORGET_Frame();
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail4;
+
+    cscr->InputGlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    if ( cscr->InputGlyphCount > ccsf2->MaxInputLength )
+      ccsf2->MaxInputLength = cscr->InputGlyphCount;
+
+    cscr->Input = NULL;
+
+    count = cscr->InputGlyphCount - 1; /* only InputGlyphCount - 1 elements */
+
+    if ( ALLOC_ARRAY( cscr->Input, count, FT_UShort ) )
+      goto Fail4;
+
+    i = cscr->Input;
+    d = ccsf2->InputClassDef.Defined;
+
+    if ( ACCESS_Frame( count * 2L ) )
+      goto Fail3;
+
+    for ( n = 0; n < count; n++ )
+    {
+      i[n] = GET_UShort();
+
+      if ( !d[i[n]] )
+        i[n] = 0;
+    }
+
+    FORGET_Frame();
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail3;
+
+    cscr->LookaheadGlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    if ( cscr->LookaheadGlyphCount > ccsf2->MaxLookaheadLength )
+      ccsf2->MaxLookaheadLength = cscr->LookaheadGlyphCount;
+
+    cscr->Lookahead = NULL;
+
+    count = cscr->LookaheadGlyphCount;
+
+    if ( ALLOC_ARRAY( cscr->Lookahead, count, FT_UShort ) )
+      goto Fail3;
+
+    l = cscr->Lookahead;
+    d = ccsf2->LookaheadClassDef.Defined;
+
+    if ( ACCESS_Frame( count * 2L ) )
+      goto Fail2;
+
+    for ( n = 0; n < count; n++ )
+    {
+      l[n] = GET_UShort();
+
+      if ( !d[l[n]] )
+        l[n] = 0;
+    }
+
+    FORGET_Frame();
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail2;
+
+    cscr->SubstCount = GET_UShort();
+
+    FORGET_Frame();
+
+    cscr->SubstLookupRecord = NULL;
+
+    count = cscr->SubstCount;
+
+    if ( ALLOC_ARRAY( cscr->SubstLookupRecord, count,
+                      TTO_SubstLookupRecord ) )
+      goto Fail2;
+
+    slr = cscr->SubstLookupRecord;
+
+    if ( ACCESS_Frame( count * 4L ) )
+      goto Fail1;
+
+    for ( n = 0; n < count; n++ )
+    {
+      slr[n].SequenceIndex   = GET_UShort();
+      slr[n].LookupListIndex = GET_UShort();
+    }
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+
+  Fail1:
+    FREE( slr );
+
+  Fail2:
+    FREE( l );
+
+  Fail3:
+    FREE( i );
+
+  Fail4:
+    FREE( b );
+    return error;
+  }
+
+
+  static void  Free_ChainSubClassRule( TTO_ChainSubClassRule*  cscr,
+				       FT_Memory               memory )
+  {
+    FREE( cscr->SubstLookupRecord );
+    FREE( cscr->Lookahead );
+    FREE( cscr->Input );
+    FREE( cscr->Backtrack );
+  }
+
+
+  /* SubClassSet */
+
+  static FT_Error  Load_ChainSubClassSet(
+                     TTO_ChainContextSubstFormat2*  ccsf2,
+                     TTO_ChainSubClassSet*          cscs,
+                     FT_Stream                      stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort               n, count;
+    FT_ULong                cur_offset, new_offset, base_offset;
+
+    TTO_ChainSubClassRule*  cscr;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = cscs->ChainSubClassRuleCount = GET_UShort();
+
+    FORGET_Frame();
+
+    cscs->ChainSubClassRule = NULL;
+
+    if ( ALLOC_ARRAY( cscs->ChainSubClassRule, count,
+                      TTO_ChainSubClassRule ) )
+      return error;
+
+    cscr = cscs->ChainSubClassRule;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_ChainSubClassRule( ccsf2, &cscr[n],
+                                             stream ) ) != TT_Err_Ok )
+        goto Fail;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    for ( n = 0; n < count; n++ )
+      Free_ChainSubClassRule( &cscr[n], memory );
+
+    FREE( cscr );
+    return error;
+  }
+
+
+  static void  Free_ChainSubClassSet( TTO_ChainSubClassSet*  cscs,
+				      FT_Memory              memory )
+  {
+    FT_UShort               n, count;
+
+    TTO_ChainSubClassRule*  cscr;
+
+
+    if ( cscs->ChainSubClassRule )
+    {
+      count = cscs->ChainSubClassRuleCount;
+      cscr  = cscs->ChainSubClassRule;
+
+      for ( n = 0; n < count; n++ )
+        Free_ChainSubClassRule( &cscr[n], memory );
+
+      FREE( cscr );
+    }
+  }
+
+
+  /* ChainContextSubstFormat2 */
+
+  static FT_Error  Load_ChainContextSubst2(
+                     TTO_ChainContextSubstFormat2*  ccsf2,
+                     FT_Stream                      stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort              n, count;
+    FT_ULong               cur_offset, new_offset, base_offset;
+    FT_ULong               backtrack_offset, input_offset, lookahead_offset;
+
+    TTO_ChainSubClassSet*  cscs;
+
+
+    base_offset = FILE_Pos() - 2;
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( new_offset ) ||
+         ( error = Load_Coverage( &ccsf2->Coverage, stream ) ) != TT_Err_Ok )
+      return error;
+    (void)FILE_Seek( cur_offset );
+
+    if ( ACCESS_Frame( 8L ) )
+      goto Fail5;
+
+    backtrack_offset = GET_UShort() + base_offset;
+    input_offset     = GET_UShort() + base_offset;
+    lookahead_offset = GET_UShort() + base_offset;
+
+    /* `ChainSubClassSetCount' is the upper limit for input class values,
+       thus we read it now to make an additional safety check.            */
+
+    count = ccsf2->ChainSubClassSetCount = GET_UShort();
+
+    FORGET_Frame();
+
+    cur_offset = FILE_Pos();
+    if ( FILE_Seek( backtrack_offset ) ||
+         ( error = Load_ClassDefinition( &ccsf2->BacktrackClassDef, count,
+                                         stream ) ) != TT_Err_Ok )
+      goto Fail5;
+    if ( FILE_Seek( input_offset ) ||
+         ( error = Load_ClassDefinition( &ccsf2->InputClassDef, count,
+                                         stream ) ) != TT_Err_Ok )
+      goto Fail4;
+    if ( FILE_Seek( lookahead_offset ) ||
+         ( error = Load_ClassDefinition( &ccsf2->LookaheadClassDef, count,
+                                         stream ) ) != TT_Err_Ok )
+      goto Fail3;
+    (void)FILE_Seek( cur_offset );
+
+    ccsf2->ChainSubClassSet   = NULL;
+    ccsf2->MaxBacktrackLength = 0;
+    ccsf2->MaxInputLength     = 0;
+    ccsf2->MaxLookaheadLength = 0;
+
+    if ( ALLOC_ARRAY( ccsf2->ChainSubClassSet, count, TTO_ChainSubClassSet ) )
+      goto Fail2;
+
+    cscs = ccsf2->ChainSubClassSet;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail1;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      if ( new_offset != base_offset )      /* not a NULL offset */
+      {
+        cur_offset = FILE_Pos();
+        if ( FILE_Seek( new_offset ) ||
+             ( error = Load_ChainSubClassSet( ccsf2, &cscs[n],
+                                              stream ) ) != TT_Err_Ok )
+          goto Fail1;
+        (void)FILE_Seek( cur_offset );
+      }
+      else
+      {
+        /* we create a ChainSubClassSet table with no entries */
+
+        ccsf2->ChainSubClassSet[n].ChainSubClassRuleCount = 0;
+        ccsf2->ChainSubClassSet[n].ChainSubClassRule      = NULL;
+      }
+    }
+
+    return TT_Err_Ok;
+
+  Fail1:
+    for ( n = 0; n < count; n++ )
+      Free_ChainSubClassSet( &cscs[n], memory );
+
+    FREE( cscs );
+
+  Fail2:
+    Free_ClassDefinition( &ccsf2->LookaheadClassDef, memory );
+
+  Fail3:
+    Free_ClassDefinition( &ccsf2->InputClassDef, memory );
+
+  Fail4:
+    Free_ClassDefinition( &ccsf2->BacktrackClassDef, memory );
+
+  Fail5:
+    Free_Coverage( &ccsf2->Coverage, memory );
+    return error;
+  }
+
+
+  static void  Free_ChainContext2( TTO_ChainContextSubstFormat2*  ccsf2,
+				   FT_Memory                      memory )
+  {
+    FT_UShort              n, count;
+
+    TTO_ChainSubClassSet*  cscs;
+
+
+    if ( ccsf2->ChainSubClassSet )
+    {
+      count = ccsf2->ChainSubClassSetCount;
+      cscs  = ccsf2->ChainSubClassSet;
+
+      for ( n = 0; n < count; n++ )
+        Free_ChainSubClassSet( &cscs[n], memory );
+
+      FREE( cscs );
+    }
+
+    Free_ClassDefinition( &ccsf2->LookaheadClassDef, memory );
+    Free_ClassDefinition( &ccsf2->InputClassDef, memory );
+    Free_ClassDefinition( &ccsf2->BacktrackClassDef, memory );
+
+    Free_Coverage( &ccsf2->Coverage, memory );
+  }
+
+
+  /* ChainContextSubstFormat3 */
+
+  static FT_Error  Load_ChainContextSubst3(
+                     TTO_ChainContextSubstFormat3*  ccsf3,
+                     FT_Stream                      stream )
+  {
+    FT_Error error;
+    FT_Memory memory = stream->memory;
+
+    FT_UShort               n, count;
+    FT_UShort               backtrack_count, input_count, lookahead_count;
+    FT_ULong                cur_offset, new_offset, base_offset;
+
+    TTO_Coverage*           b;
+    TTO_Coverage*           i;
+    TTO_Coverage*           l;
+    TTO_SubstLookupRecord*  slr;
+
+
+    base_offset = FILE_Pos() - 2L;
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    ccsf3->BacktrackGlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    ccsf3->BacktrackCoverage = NULL;
+
+    backtrack_count = ccsf3->BacktrackGlyphCount;
+
+    if ( ALLOC_ARRAY( ccsf3->BacktrackCoverage, backtrack_count,
+                      TTO_Coverage ) )
+      return error;
+
+    b = ccsf3->BacktrackCoverage;
+
+    for ( n = 0; n < backtrack_count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail4;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_Coverage( &b[n], stream ) ) != TT_Err_Ok )
+        goto Fail4;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail4;
+
+    ccsf3->InputGlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    ccsf3->InputCoverage = NULL;
+
+    input_count = ccsf3->InputGlyphCount;
+
+    if ( ALLOC_ARRAY( ccsf3->InputCoverage, input_count, TTO_Coverage ) )
+      goto Fail4;
+
+    i = ccsf3->InputCoverage;
+
+    for ( n = 0; n < input_count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail3;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_Coverage( &i[n], stream ) ) != TT_Err_Ok )
+        goto Fail3;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail3;
+
+    ccsf3->LookaheadGlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    ccsf3->LookaheadCoverage = NULL;
+
+    lookahead_count = ccsf3->LookaheadGlyphCount;
+
+    if ( ALLOC_ARRAY( ccsf3->LookaheadCoverage, lookahead_count,
+                      TTO_Coverage ) )
+      goto Fail3;
+
+    l = ccsf3->LookaheadCoverage;
+
+    for ( n = 0; n < lookahead_count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail2;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_Coverage( &l[n], stream ) ) != TT_Err_Ok )
+        goto Fail2;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail2;
+
+    ccsf3->SubstCount = GET_UShort();
+
+    FORGET_Frame();
+
+    ccsf3->SubstLookupRecord = NULL;
+
+    count = ccsf3->SubstCount;
+
+    if ( ALLOC_ARRAY( ccsf3->SubstLookupRecord, count,
+                      TTO_SubstLookupRecord ) )
+      goto Fail2;
+
+    slr = ccsf3->SubstLookupRecord;
+
+    if ( ACCESS_Frame( count * 4L ) )
+      goto Fail1;
+
+    for ( n = 0; n < count; n++ )
+    {
+      slr[n].SequenceIndex   = GET_UShort();
+      slr[n].LookupListIndex = GET_UShort();
+    }
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+
+  Fail1:
+    FREE( slr );
+
+  Fail2:
+    for ( n = 0; n < lookahead_count; n++ )
+      Free_Coverage( &l[n], memory );
+
+    FREE( l );
+
+  Fail3:
+    for ( n = 0; n < input_count; n++ )
+      Free_Coverage( &i[n], memory );
+
+    FREE( i );
+
+  Fail4:
+    for ( n = 0; n < backtrack_count; n++ )
+      Free_Coverage( &b[n], memory );
+
+    FREE( b );
+    return error;
+  }
+
+
+  static void  Free_ChainContext3( TTO_ChainContextSubstFormat3*  ccsf3,
+				   FT_Memory                      memory )
+  {
+    FT_UShort      n, count;
+
+    TTO_Coverage*  c;
+
+
+    FREE( ccsf3->SubstLookupRecord );
+
+    if ( ccsf3->LookaheadCoverage )
+    {
+      count = ccsf3->LookaheadGlyphCount;
+      c     = ccsf3->LookaheadCoverage;
+
+      for ( n = 0; n < count; n++ )
+        Free_Coverage( &c[n], memory );
+
+      FREE( c );
+    }
+
+    if ( ccsf3->InputCoverage )
+    {
+      count = ccsf3->InputGlyphCount;
+      c     = ccsf3->InputCoverage;
+
+      for ( n = 0; n < count; n++ )
+        Free_Coverage( &c[n], memory );
+
+      FREE( c );
+    }
+
+    if ( ccsf3->BacktrackCoverage )
+    {
+      count = ccsf3->BacktrackGlyphCount;
+      c     = ccsf3->BacktrackCoverage;
+
+      for ( n = 0; n < count; n++ )
+        Free_Coverage( &c[n], memory );
+
+      FREE( c );
+    }
+  }
+
+
+  /* ChainContextSubst */
+
+  FT_Error  Load_ChainContextSubst( TTO_ChainContextSubst*  ccs,
+                                    FT_Stream               stream )
+  {
+    FT_Error error;
+
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    ccs->SubstFormat = GET_UShort();
+
+    FORGET_Frame();
+
+    switch ( ccs->SubstFormat )
+    {
+    case 1:
+      return Load_ChainContextSubst1( &ccs->ccsf.ccsf1, stream );
+
+    case 2:
+      return Load_ChainContextSubst2( &ccs->ccsf.ccsf2, stream );
+
+    case 3:
+      return Load_ChainContextSubst3( &ccs->ccsf.ccsf3, stream );
+
+    default:
+      return TTO_Err_Invalid_GSUB_SubTable_Format;
+    }
+
+    return TT_Err_Ok;               /* never reached */
+  }
+
+
+  void  Free_ChainContextSubst( TTO_ChainContextSubst*  ccs,
+				FT_Memory               memory )
+  {
+    switch ( ccs->SubstFormat )
+    {
+    case 1:
+      Free_ChainContext1( &ccs->ccsf.ccsf1, memory );
+      break;
+
+    case 2:
+      Free_ChainContext2( &ccs->ccsf.ccsf2, memory );
+      break;
+
+    case 3:
+      Free_ChainContext3( &ccs->ccsf.ccsf3, memory );
+      break;
+    }
+  }
+
+
+  static FT_Error  Lookup_ChainContextSubst1(
+                     TTO_GSUBHeader*                gsub,
+                     TTO_ChainContextSubstFormat1*  ccsf1,
+                     TTO_GSUB_String*               in,
+                     TTO_GSUB_String*               out,
+                     FT_UShort                      flags,
+                     FT_UShort                      context_length,
+                     int                            nesting_level )
+  {
+    FT_UShort          index, property;
+    FT_UShort          i, j, k, num_csr, curr_pos;
+    FT_UShort          bgc, igc, lgc;
+    FT_Error           error;
+    FT_UShort*         s_in;
+
+    TTO_ChainSubRule*  csr;
+    TTO_ChainSubRule   curr_csr;
+    TTO_GDEFHeader*    gdef;
+
+
+    gdef = gsub->gdef;
+
+    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
+      return error;
+
+    error = Coverage_Index( &ccsf1->Coverage, in->string[in->pos], &index );
+    if ( error )
+      return error;
+
+    csr     = ccsf1->ChainSubRuleSet[index].ChainSubRule;
+    num_csr = ccsf1->ChainSubRuleSet[index].ChainSubRuleCount;
+
+    for ( k = 0; k < num_csr; k++ )
+    {
+      curr_csr = csr[k];
+      bgc      = curr_csr.BacktrackGlyphCount;
+      igc      = curr_csr.InputGlyphCount;
+      lgc      = curr_csr.LookaheadGlyphCount;
+
+      if ( context_length != 0xFFFF && context_length < igc )
+        continue;
+
+      /* check whether context is too long; it is a first guess only */
+
+      if ( bgc > in->pos || in->pos + igc + lgc > in->length )
+        continue;
+
+      if ( bgc )
+      {
+        /* Since we don't know in advance the number of glyphs to inspect,
+           we search backwards for matches in the backtrack glyph array    */
+
+        curr_pos = 0;
+        s_in     = &in->string[curr_pos];
+
+        for ( i = bgc, j = in->pos - 1; i > 0; i--, j-- )
+        {
+          while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+          {
+            if ( error && error != TTO_Err_Not_Covered )
+              return error;
+
+            if ( j > curr_pos )
+              j--;
+            else
+              break;
+          }
+
+          if ( s_in[j] != curr_csr.Backtrack[i - 1] )
+            break;
+        }
+
+        if ( i != 0 )
+          continue;
+      }
+
+      curr_pos = in->pos;
+      s_in     = &in->string[curr_pos];
+
+      /* Start at 1 because [0] is implied */
+
+      for ( i = 1, j = 1; i < igc; i++, j++ )
+      {
+        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+        {
+          if ( error && error != TTO_Err_Not_Covered )
+            return error;
+
+          if ( curr_pos + j < in->length )
+            j++;
+          else
+            break;
+        }
+
+        if ( s_in[j] != curr_csr.Input[i - 1] )
+          break;
+      }
+
+      if ( i != igc )
+        continue;
+
+      /* we are starting to check for lookahead glyphs right after the
+         last context glyph                                            */
+
+      curr_pos = j;
+      s_in     = &in->string[curr_pos];
+
+      for ( i = 0, j = 0; i < lgc; i++, j++ )
+      {
+        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+        {
+          if ( error && error != TTO_Err_Not_Covered )
+            return error;
+
+          if ( curr_pos + j < in->length )
+            j++;
+          else
+            break;
+        }
+
+        if ( s_in[j] != curr_csr.Lookahead[i] )
+          break;
+      }
+
+      if ( i == lgc )
+        return Do_ContextSubst( gsub, igc,
+                                curr_csr.SubstCount,
+                                curr_csr.SubstLookupRecord,
+                                in, out,
+                                nesting_level );
+    }
+
+    return TTO_Err_Not_Covered;
+  }
+
+
+  static FT_Error  Lookup_ChainContextSubst2(
+                     TTO_GSUBHeader*                gsub,
+                     TTO_ChainContextSubstFormat2*  ccsf2,
+                     TTO_GSUB_String*               in,
+                     TTO_GSUB_String*               out,
+                     FT_UShort                      flags,
+                     FT_UShort                      context_length,
+                     int                            nesting_level )
+  {
+    FT_UShort              index, property;
+    FT_Memory              memory;
+    FT_Error               error;
+    FT_UShort              i, j, k, curr_pos;
+    FT_UShort              bgc, igc, lgc;
+    FT_UShort              known_backtrack_classes,
+                           known_input_classes,
+                           known_lookahead_classes;
+
+    FT_UShort*             backtrack_classes;
+    FT_UShort*             input_classes;
+    FT_UShort*             lookahead_classes;
+
+    FT_UShort*             s_in;
+
+    FT_UShort*             bc;
+    FT_UShort*             ic;
+    FT_UShort*             lc;
+
+    TTO_ChainSubClassSet*  cscs;
+    TTO_ChainSubClassRule  ccsr;
+    TTO_GDEFHeader*        gdef;
+
+
+    gdef = gsub->gdef;
+    memory = gsub->memory;
+
+    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
+      return error;
+
+    /* Note: The coverage table in format 2 doesn't give an index into
+             anything.  It just lets us know whether or not we need to
+             do any lookup at all.                                     */
+
+    error = Coverage_Index( &ccsf2->Coverage, in->string[in->pos], &index );
+    if ( error )
+      return error;
+
+    if ( ALLOC_ARRAY( backtrack_classes, ccsf2->MaxBacktrackLength, FT_UShort ) )
+      return error;
+    known_backtrack_classes = 0;
+
+    if ( ALLOC_ARRAY( input_classes, ccsf2->MaxInputLength, FT_UShort ) )
+      goto End3;
+    known_input_classes = 1;
+
+    if ( ALLOC_ARRAY( lookahead_classes, ccsf2->MaxLookaheadLength, FT_UShort ) )
+      goto End2;
+    known_lookahead_classes = 0;
+
+    error = Get_Class( &ccsf2->InputClassDef, in->string[in->pos],
+                       &input_classes[0], NULL );
+    if ( error )
+      goto End1;
+
+    cscs = &ccsf2->ChainSubClassSet[input_classes[0]];
+    if ( !cscs )
+    {
+      error = TTO_Err_Invalid_GSUB_SubTable;
+      goto End1;
+    }
+
+    for ( k = 0; k < cscs->ChainSubClassRuleCount; k++ )
+    {
+      ccsr = cscs->ChainSubClassRule[k];
+      bgc  = ccsr.BacktrackGlyphCount;
+      igc  = ccsr.InputGlyphCount;
+      lgc  = ccsr.LookaheadGlyphCount;
+
+      if ( context_length != 0xFFFF && context_length < igc )
+        continue;
+
+      /* check whether context is too long; it is a first guess only */
+
+      if ( bgc > in->pos || in->pos + igc + lgc > in->length )
+        continue;
+
+      if ( bgc )
+      {
+        /* Since we don't know in advance the number of glyphs to inspect,
+           we search backwards for matches in the backtrack glyph array.
+           Note that `known_backtrack_classes' starts at index 0.         */
+
+        curr_pos = 0;
+        s_in     = &in->string[curr_pos];
+        bc       = ccsr.Backtrack;
+
+        for ( i = 0, j = in->pos - 1; i < bgc; i++, j-- )
+        {
+          while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+          {
+            if ( error && error != TTO_Err_Not_Covered )
+              return error;
+
+            if ( j > curr_pos )
+              j--;
+            else
+              break;
+          }
+
+          if ( i >= known_backtrack_classes )
+          {
+            /* Keeps us from having to do this for each rule */
+
+            error = Get_Class( &ccsf2->BacktrackClassDef, s_in[j],
+                               &backtrack_classes[i], NULL );
+            if ( error && error != TTO_Err_Not_Covered )
+              goto End1;
+            known_backtrack_classes = i;
+          }
+
+          if ( bc[bgc - 1 - i] != backtrack_classes[i] )
+            break;
+        }
+
+        if ( i != bgc )
+          continue;
+      }
+
+      curr_pos = in->pos;
+      s_in     = &in->string[curr_pos];
+      ic       = ccsr.Input;
+
+      /* Start at 1 because [0] is implied */
+
+      for ( i = 1, j = 1; i < igc; i++, j++ )
+      {
+        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+        {
+          if ( error && error != TTO_Err_Not_Covered )
+            goto End1;
+
+          if ( curr_pos + j < in->length )
+            j++;
+          else
+            break;
+        }
+
+        if ( i >= known_input_classes )
+        {
+          error = Get_Class( &ccsf2->InputClassDef, s_in[j],
+                             &input_classes[i], NULL );
+          if ( error && error != TTO_Err_Not_Covered )
+            goto End1;
+          known_input_classes = i;
+        }
+
+        if ( ic[i - 1] != input_classes[i] )
+          break;
+      }
+
+      if ( i != igc )
+        continue;
+
+      /* we are starting to check for lookahead glyphs right after the
+         last context glyph                                            */
+
+      curr_pos = j;
+      s_in     = &in->string[curr_pos];
+      lc       = ccsr.Lookahead;
+
+      for ( i = 0, j = 0; i < lgc; i++, j++ )
+      {
+        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+        {
+          if ( error && error != TTO_Err_Not_Covered )
+            return error;
+
+          if ( curr_pos + j < in->length )
+            j++;
+          else
+            break;
+        }
+
+        if ( i >= known_lookahead_classes )
+        {
+          error = Get_Class( &ccsf2->LookaheadClassDef, s_in[j],
+                             &lookahead_classes[i], NULL );
+          if ( error && error != TTO_Err_Not_Covered )
+            goto End1;
+          known_lookahead_classes = i;
+        }
+
+        if ( lc[i] != lookahead_classes[i] )
+          break;
+      }
+
+      if ( i == lgc )
+      {
+        error = Do_ContextSubst( gsub, igc,
+                                 ccsr.SubstCount,
+                                 ccsr.SubstLookupRecord,
+                                 in, out,
+                                 nesting_level );
+        goto End1;
+      }
+    }
+
+    error = TTO_Err_Not_Covered;
+
+  End1:
+    FREE( lookahead_classes );
+
+  End2:
+    FREE( input_classes );
+
+  End3:
+    FREE( backtrack_classes );
+    return error;
+  }
+
+
+  static FT_Error  Lookup_ChainContextSubst3(
+                     TTO_GSUBHeader*                gsub,
+                     TTO_ChainContextSubstFormat3*  ccsf3,
+                     TTO_GSUB_String*               in,
+                     TTO_GSUB_String*               out,
+                     FT_UShort                      flags,
+                     FT_UShort                      context_length,
+                     int                            nesting_level )
+  {
+    FT_UShort        index, i, j, curr_pos, property;
+    FT_UShort        bgc, igc, lgc;
+    FT_Error         error;
+    FT_UShort*       s_in;
+
+    TTO_Coverage*    bc;
+    TTO_Coverage*    ic;
+    TTO_Coverage*    lc;
+    TTO_GDEFHeader*  gdef;
+
+
+    gdef = gsub->gdef;
+
+    if ( CHECK_Property( gdef, in->string[in->pos], flags, &property ) )
+      return error;
+
+    bgc = ccsf3->BacktrackGlyphCount;
+    igc = ccsf3->InputGlyphCount;
+    lgc = ccsf3->LookaheadGlyphCount;
+
+    if ( context_length != 0xFFFF && context_length < igc )
+      return TTO_Err_Not_Covered;
+
+    /* check whether context is too long; it is a first guess only */
+
+    if ( bgc > in->pos || in->pos + igc + lgc > in->length )
+      return TTO_Err_Not_Covered;
+
+    if ( bgc )
+    {
+      /* Since we don't know in advance the number of glyphs to inspect,
+         we search backwards for matches in the backtrack glyph array    */
+
+      curr_pos = 0;
+      s_in     = &in->string[curr_pos];
+      bc       = ccsf3->BacktrackCoverage;
+
+      for ( i = bgc, j = in->pos - 1; i > 0; i--, j-- )
+      {
+        while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+        {
+          if ( error && error != TTO_Err_Not_Covered )
+            return error;
+
+          if ( j > curr_pos )
+            j--;
+          else
+            return TTO_Err_Not_Covered;
+        }
+
+        error = Coverage_Index( &bc[i - 1], s_in[j], &index );
+        if ( error )
+          return error;
+      }
+    }
+
+    curr_pos = in->pos;
+    s_in     = &in->string[curr_pos];
+    ic       = ccsf3->InputCoverage;
+
+    /* Start at 1 because [0] is implied */
+
+    for ( i = 1, j = 1; i < igc; i++, j++ )
+    {
+      while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+      {
+        if ( error && error != TTO_Err_Not_Covered )
+          return error;
+
+        if ( curr_pos + j < in->length )
+          j++;
+        else
+          return TTO_Err_Not_Covered;
+      }
+
+      error = Coverage_Index( &ic[i], s_in[j], &index );
+      if ( error )
+        return error;
+    }
+
+    /* we are starting for lookahead glyphs right after the last context
+       glyph                                                             */
+
+    curr_pos = j;
+    s_in     = &in->string[curr_pos];
+    lc       = ccsf3->LookaheadCoverage;
+
+    for ( i = 0, j = 0; i < lgc; i++, j++ )
+    {
+      while ( CHECK_Property( gdef, s_in[j], flags, &property ) )
+      {
+        if ( error && error != TTO_Err_Not_Covered )
+          return error;
+
+        if ( curr_pos + j < in->length )
+          j++;
+        else
+          return TTO_Err_Not_Covered;
+      }
+
+      error = Coverage_Index( &lc[i], s_in[j], &index );
+      if ( error )
+        return error;
+    }
+
+    return Do_ContextSubst( gsub, igc,
+                            ccsf3->SubstCount,
+                            ccsf3->SubstLookupRecord,
+                            in, out,
+                            nesting_level );
+  }
+
+
+  static FT_Error  Lookup_ChainContextSubst(
+                     TTO_GSUBHeader*         gsub,
+                     TTO_ChainContextSubst*  ccs,
+                     TTO_GSUB_String*        in,
+                     TTO_GSUB_String*        out,
+                     FT_UShort               flags,
+                     FT_UShort               context_length,
+                     int                     nesting_level )
+  {
+    switch ( ccs->SubstFormat )
+    {
+    case 1:
+      return Lookup_ChainContextSubst1( gsub, &ccs->ccsf.ccsf1, in, out,
+                                        flags, context_length,
+                                        nesting_level );
+
+    case 2:
+      return Lookup_ChainContextSubst2( gsub, &ccs->ccsf.ccsf2, in, out,
+                                        flags, context_length,
+                                        nesting_level );
+
+    case 3:
+      return Lookup_ChainContextSubst3( gsub, &ccs->ccsf.ccsf3, in, out,
+                                        flags, context_length,
+                                        nesting_level );
+
+    default:
+      return TTO_Err_Invalid_GSUB_SubTable_Format;
+    }
+
+    return TT_Err_Ok;               /* never reached */
+  }
+
+
+
+  /***********
+   * GSUB API
+   ***********/
+
+
+  EXPORT_FUNC
+  FT_Error  TT_GSUB_Select_Script( TTO_GSUBHeader*  gsub,
+                                   FT_ULong         script_tag,
+                                   FT_UShort*       script_index )
+  {
+    FT_UShort          n;
+
+    TTO_ScriptList*    sl;
+    TTO_ScriptRecord*  sr;
+
+
+    if ( !gsub || !script_index )
+      return TT_Err_Invalid_Argument;
+
+    sl = &gsub->ScriptList;
+    sr = sl->ScriptRecord;
+
+    for ( n = 0; n < sl->ScriptCount; n++ )
+      if ( script_tag == sr[n].ScriptTag )
+      {
+        *script_index = n;
+
+        return TT_Err_Ok;
+      }
+
+    return TTO_Err_Not_Covered;
+  }
+
+
+  EXPORT_FUNC
+  FT_Error  TT_GSUB_Select_Language( TTO_GSUBHeader*  gsub,
+                                     FT_ULong         language_tag,
+                                     FT_UShort        script_index,
+                                     FT_UShort*       language_index,
+                                     FT_UShort*       req_feature_index )
+  {
+    FT_UShort           n;
+
+    TTO_ScriptList*     sl;
+    TTO_ScriptRecord*   sr;
+    TTO_Script*         s;
+    TTO_LangSysRecord*  lsr;
+
+
+    if ( !gsub || !language_index || !req_feature_index )
+      return TT_Err_Invalid_Argument;
+
+    sl = &gsub->ScriptList;
+    sr = sl->ScriptRecord;
+
+    if ( script_index >= sl->ScriptCount )
+      return TT_Err_Invalid_Argument;
+
+    s   = &sr[script_index].Script;
+    lsr = s->LangSysRecord;
+
+    for ( n = 0; n < s->LangSysCount; n++ )
+      if ( language_tag == lsr[n].LangSysTag )
+      {
+        *language_index = n;
+        *req_feature_index = lsr[n].LangSys.ReqFeatureIndex;
+
+        return TT_Err_Ok;
+      }
+
+    return TTO_Err_Not_Covered;
+  }
+
+
+  /* selecting 0xFFFF for language_index asks for the values of the
+     default language (DefaultLangSys)                              */
+
+  EXPORT_FUNC
+  FT_Error  TT_GSUB_Select_Feature( TTO_GSUBHeader*  gsub,
+                                    FT_ULong         feature_tag,
+                                    FT_UShort        script_index,
+                                    FT_UShort        language_index,
+                                    FT_UShort*       feature_index )
+  {
+    FT_UShort           n;
+
+    TTO_ScriptList*     sl;
+    TTO_ScriptRecord*   sr;
+    TTO_Script*         s;
+    TTO_LangSysRecord*  lsr;
+    TTO_LangSys*        ls;
+    FT_UShort*          fi;
+
+    TTO_FeatureList*    fl;
+    TTO_FeatureRecord*  fr;
+
+
+    if ( !gsub || !feature_index )
+      return TT_Err_Invalid_Argument;
+
+    sl = &gsub->ScriptList;
+    sr = sl->ScriptRecord;
+
+    fl = &gsub->FeatureList;
+    fr = fl->FeatureRecord;
+
+    if ( script_index >= sl->ScriptCount )
+      return TT_Err_Invalid_Argument;
+
+    s   = &sr[script_index].Script;
+    lsr = s->LangSysRecord;
+
+    if ( language_index == 0xFFFF )
+      ls = &s->DefaultLangSys;
+    else
+    {
+      if ( language_index >= s->LangSysCount )
+        return TT_Err_Invalid_Argument;
+
+      ls = &lsr[language_index].LangSys;
+    }
+
+    fi = ls->FeatureIndex;
+
+    for ( n = 0; n < ls->FeatureCount; n++ )
+    {
+      if ( fi[n] >= fl->FeatureCount )
+        return TTO_Err_Invalid_GSUB_SubTable_Format;
+
+      if ( feature_tag == fr[fi[n]].FeatureTag )
+      {
+        *feature_index = fi[n];
+
+        return TT_Err_Ok;
+      }
+    }
+
+    return TTO_Err_Not_Covered;
+  }
+
+
+  /* The next three functions return a null-terminated list */
+
+  EXPORT_FUNC
+  FT_Error  TT_GSUB_Query_Scripts( TTO_GSUBHeader*  gsub,
+                                   FT_ULong**       script_tag_list )
+  {
+    FT_UShort          n;
+    FT_Error           error;
+    FT_Memory          memory;
+    FT_ULong*          stl;
+
+    TTO_ScriptList*    sl;
+    TTO_ScriptRecord*  sr;
+
+
+    if ( !gsub || !script_tag_list )
+      return TT_Err_Invalid_Argument;
+
+    memory = gsub->memory;
+
+    sl = &gsub->ScriptList;
+    sr = sl->ScriptRecord;
+
+    if ( ALLOC_ARRAY( stl, sl->ScriptCount + 1, FT_ULong ) )
+      return error;
+
+    for ( n = 0; n < sl->ScriptCount; n++ )
+      stl[n] = sr[n].ScriptTag;
+    stl[n] = 0;
+
+    *script_tag_list = stl;
+
+    return TT_Err_Ok;
+  }
+
+
+  EXPORT_FUNC
+  FT_Error  TT_GSUB_Query_Languages( TTO_GSUBHeader*  gsub,
+                                     FT_UShort        script_index,
+                                     FT_ULong**       language_tag_list )
+  {
+    FT_UShort           n;
+    FT_Error            error;
+    FT_Memory           memory;
+    FT_ULong*           ltl;
+
+    TTO_ScriptList*     sl;
+    TTO_ScriptRecord*   sr;
+    TTO_Script*         s;
+    TTO_LangSysRecord*  lsr;
+
+
+    if ( !gsub || !language_tag_list )
+      return TT_Err_Invalid_Argument;
+
+    memory = gsub->memory;
+    
+    sl = &gsub->ScriptList;
+    sr = sl->ScriptRecord;
+
+    if ( script_index >= sl->ScriptCount )
+      return TT_Err_Invalid_Argument;
+
+    s   = &sr[script_index].Script;
+    lsr = s->LangSysRecord;
+
+    if ( ALLOC_ARRAY( ltl, s->LangSysCount + 1, FT_ULong ) )
+      return error;
+
+    for ( n = 0; n < s->LangSysCount; n++ )
+      ltl[n] = lsr[n].LangSysTag;
+    ltl[n] = 0;
+
+    *language_tag_list = ltl;
+
+    return TT_Err_Ok;
+  }
+
+
+  /* selecting 0xFFFF for language_index asks for the values of the
+     default language (DefaultLangSys)                              */
+
+  EXPORT_FUNC
+  FT_Error  TT_GSUB_Query_Features( TTO_GSUBHeader*  gsub,
+                                    FT_UShort        script_index,
+                                    FT_UShort        language_index,
+                                    FT_ULong**       feature_tag_list )
+  {
+    FT_UShort           n;
+    FT_Error            error;
+    FT_Memory           memory;
+    FT_ULong*           ftl;
+
+    TTO_ScriptList*     sl;
+    TTO_ScriptRecord*   sr;
+    TTO_Script*         s;
+    TTO_LangSysRecord*  lsr;
+    TTO_LangSys*        ls;
+    FT_UShort*          fi;
+
+    TTO_FeatureList*    fl;
+    TTO_FeatureRecord*  fr;
+
+
+    if ( !gsub || !feature_tag_list )
+      return TT_Err_Invalid_Argument;
+
+    memory = gsub->memory;
+    
+    sl = &gsub->ScriptList;
+    sr = sl->ScriptRecord;
+
+    fl = &gsub->FeatureList;
+    fr = fl->FeatureRecord;
+
+    if ( script_index >= sl->ScriptCount )
+      return TT_Err_Invalid_Argument;
+
+    s   = &sr[script_index].Script;
+    lsr = s->LangSysRecord;
+
+    if ( language_index == 0xFFFF )
+      ls = &s->DefaultLangSys;
+    else
+    {
+      if ( language_index >= s->LangSysCount )
+        return TT_Err_Invalid_Argument;
+
+      ls = &lsr[language_index].LangSys;
+    }
+
+    fi = ls->FeatureIndex;
+
+    if ( ALLOC_ARRAY( ftl, ls->FeatureCount + 1, FT_ULong ) )
+      return error;
+
+    for ( n = 0; n < ls->FeatureCount; n++ )
+    {
+      if ( fi[n] >= fl->FeatureCount )
+      {
+        FREE( ftl );
+        return TTO_Err_Invalid_GSUB_SubTable_Format;
+      }
+      ftl[n] = fr[fi[n]].FeatureTag;
+    }
+    ftl[n] = 0;
+
+    *feature_tag_list = ftl;
+
+    return TT_Err_Ok;
+  }
+
+
+  /* Do an individual subtable lookup.  Returns TT_Err_Ok if substitution
+     has been done, or TTO_Err_Not_Covered if not.                        */
+
+  static FT_Error  Do_Glyph_Lookup( TTO_GSUBHeader*   gsub,
+                                    FT_UShort         lookup_index,
+                                    TTO_GSUB_String*  in,
+                                    TTO_GSUB_String*  out,
+                                    FT_UShort         context_length,
+                                    int               nesting_level )
+  {
+    FT_Error     error = TT_Err_Ok;
+    FT_UShort    i, flags;
+    TTO_Lookup*  lo;
+
+
+    nesting_level++;
+
+    if ( nesting_level > TTO_MAX_NESTING_LEVEL )
+      return TTO_Err_Too_Many_Nested_Contexts;
+
+    lo    = &gsub->LookupList.Lookup[lookup_index];
+    flags = lo->LookupFlag;
+
+    for ( i = 0; i < lo->SubTableCount; i++ )
+    {
+      switch ( lo->LookupType )
+      {
+      case GSUB_LOOKUP_SINGLE:
+        error = Lookup_SingleSubst( &lo->SubTable[i].st.gsub.single,
+                                    in, out,
+                                    flags, context_length, gsub->gdef );
+        break;
+
+      case GSUB_LOOKUP_MULTIPLE:
+        error = Lookup_MultipleSubst( &lo->SubTable[i].st.gsub.multiple,
+                                      in, out,
+                                      flags, context_length, gsub->gdef );
+        break;
+
+      case GSUB_LOOKUP_ALTERNATE:
+        error = Lookup_AlternateSubst( gsub,
+                                       &lo->SubTable[i].st.gsub.alternate,
+                                       in, out,
+                                       flags, context_length, gsub->gdef );
+        break;
+
+      case GSUB_LOOKUP_LIGATURE:
+        error = Lookup_LigatureSubst( &lo->SubTable[i].st.gsub.ligature,
+                                      in, out,
+                                      flags, context_length, gsub->gdef );
+        break;
+
+      case GSUB_LOOKUP_CONTEXT:
+        error = Lookup_ContextSubst( gsub, &lo->SubTable[i].st.gsub.context,
+                                     in, out,
+                                     flags, context_length, nesting_level );
+        break;
+
+      case GSUB_LOOKUP_CHAIN:
+        error = Lookup_ChainContextSubst( gsub,
+                                          &lo->SubTable[i].st.gsub.chain,
+                                          in, out,
+                                          flags, context_length,
+                                          nesting_level );
+        break;
+      }
+
+      /* Check whether we have a successful substitution or an error other
+         than TTO_Err_Not_Covered                                          */
+
+      if ( error != TTO_Err_Not_Covered )
+        return error;
+    }
+
+    return TTO_Err_Not_Covered;
+  }
+
+
+  /* apply one lookup to the input string object */
+
+  static FT_Error  Do_String_Lookup( TTO_GSUBHeader*   gsub,
+                                     FT_UShort         lookup_index,
+                                     TTO_GSUB_String*  in,
+                                     TTO_GSUB_String*  out )
+  {
+    FT_Error  error = TTO_Err_Not_Covered;
+
+    FT_UShort*  properties = gsub->LookupList.Properties;
+    FT_UShort*  p_in       = in->properties;
+    FT_UShort*  s_in       = in->string;
+
+    int      nesting_level = 0;
+
+
+    while ( in->pos < in->length )
+    {
+      if ( ~p_in[in->pos] & properties[lookup_index] )
+      {
+        /* 0xFFFF indicates that we don't have a context length yet */
+        error = Do_Glyph_Lookup( gsub, lookup_index, in, out,
+                                 0xFFFF, nesting_level );
+        if ( error && error != TTO_Err_Not_Covered )
+          return error;
+      }
+      else
+        error = TTO_Err_Not_Covered;
+
+      if ( error == TTO_Err_Not_Covered ) 
+        if ( ADD_String( in, 1, out, 1, &s_in[in->pos], 0xFFFF, 0xFFFF ) )
+          return error;
+    }
+
+    return error;
+  }
+
+
+  EXPORT_FUNC
+  FT_Error  TT_GSUB_Add_Feature( TTO_GSUBHeader*  gsub,
+                                 FT_UShort        feature_index,
+                                 FT_UShort        property )
+  {
+    FT_UShort    i;
+
+    TTO_Feature  feature;
+    FT_UShort*   properties;
+    FT_UShort*   index;
+
+
+    if ( !gsub ||
+         feature_index >= gsub->FeatureList.FeatureCount )
+      return TT_Err_Invalid_Argument;
+
+    properties = gsub->LookupList.Properties;
+
+    feature = gsub->FeatureList.FeatureRecord[feature_index].Feature;
+    index   = feature.LookupListIndex;
+
+    for ( i = 0; i < feature.LookupListCount; i++ )
+      properties[index[i]] |= property;
+
+    return TT_Err_Ok;
+  }
+
+
+  EXPORT_FUNC
+  FT_Error  TT_GSUB_Clear_Features( TTO_GSUBHeader*  gsub )
+  {
+    FT_UShort i;
+
+    FT_UShort*  properties;
+
+
+    if ( !gsub )
+      return TT_Err_Invalid_Argument;
+
+    properties = gsub->LookupList.Properties;
+
+    for ( i = 0; i < gsub->LookupList.LookupCount; i++ )
+      properties[i] = 0;
+
+    return TT_Err_Ok;
+  }
+
+
+  EXPORT_FUNC
+  FT_Error  TT_GSUB_Register_Alternate_Function( TTO_GSUBHeader*  gsub,
+                                                 TTO_AltFunction  altfunc,
+                                                 void*            data )
+  {
+    if ( !gsub )
+      return TT_Err_Invalid_Argument;
+
+    gsub->altfunc = altfunc;
+    gsub->data    = data;
+
+    return TT_Err_Ok;
+  }
+
+
+  EXPORT_FUNC
+  FT_Error  TT_GSUB_String_New( FT_Memory           memory,
+				TTO_GSUB_String   **result )
+  {
+    FT_Error error;
+    
+    TTO_GSUB_String *str;
+
+    if ( ALLOC( str, sizeof( *str ) ) )
+      return error;
+
+    str->memory = memory;
+
+    str->length = 0;
+    str->allocated = 0;
+    str->pos = 0;
+    str->string = NULL;
+    str->properties = NULL;
+    str->components = NULL;
+    str->max_ligID = 0;
+    str->ligIDs = 0;
+    str->logClusters = 0;
+
+    *result = str;
+
+    return TT_Err_Ok;
+  }
+
+  EXPORT_DEF
+  FT_Error  TT_GSUB_String_Set_Length( TTO_GSUB_String *str,
+				       FT_ULong         new_length)
+  {
+    FT_Memory memory = str->memory;
+    FT_Error error;
+    
+    if ( new_length > str->allocated )
+    {
+      if ( REALLOC_ARRAY( str->string, str->allocated, new_length, FT_UShort ) )
+        return error;
+      if ( REALLOC_ARRAY( str->properties, str->allocated, new_length, FT_UShort ) )
+        return error;
+      if ( REALLOC_ARRAY( str->components, str->allocated, new_length, FT_UShort ) )
+        return error;
+      if ( REALLOC_ARRAY( str->ligIDs, str->allocated, new_length, FT_UShort ) )
+        return error;
+      if ( REALLOC_ARRAY( str->logClusters, str->allocated, new_length, FT_Int ) )
+        return error;
+
+      str->allocated = new_length;
+      str->length = new_length;
+    }
+
+    return TT_Err_Ok;
+  }
+
+  EXPORT_FUNC
+  FT_Error  TT_GSUB_String_Done( TTO_GSUB_String   *str )
+  {
+    FT_Memory memory = str->memory;
+
+    FREE( str->string );
+    FREE( str->properties );
+    FREE( str->components );
+    FREE( str->ligIDs );
+    FREE( str->logClusters );
+
+    FREE( str );
+
+    return TT_Err_Ok;
+  }
+
+  EXPORT_FUNC
+  FT_Error  TT_GSUB_Apply_String( TTO_GSUBHeader*   gsub,
+                                  TTO_GSUB_String*  in,
+                                  TTO_GSUB_String*  out )
+  {
+    FT_Error          error = TTO_Err_Not_Covered;
+    FT_Memory         memory = in->memory;
+    FT_UShort         j;
+
+    TTO_GSUB_String   tmp1;
+    TTO_GSUB_String*  ptmp1;
+    TTO_GSUB_String   tmp2;
+    TTO_GSUB_String*  ptmp2;
+    TTO_GSUB_String*  t;
+
+    FT_UShort*        properties;
+
+
+    if ( !gsub ||
+         !in || !out || in->length == 0 || in->pos >= in->length )
+      return TT_Err_Invalid_Argument;
+
+    properties = gsub->LookupList.Properties;
+    
+    tmp1.memory    = memory;
+    tmp1.length    = in->length;
+    tmp1.allocated = in->length;
+    tmp1.pos       = in->pos;
+    tmp1.max_ligID = 1;
+
+    if ( ALLOC_ARRAY( tmp1.string, tmp1.length, FT_UShort ) )
+      return error;
+    MEM_Copy( tmp1.string, in->string, in->length * sizeof ( FT_UShort ) );
+
+    /* make sure that we always have a `properties', `components', and
+       `ligIDs' array in the string object                             */
+
+    if ( ALLOC_ARRAY( tmp1.components, tmp1.length, FT_UShort ) )
+      return error;
+    if ( ALLOC_ARRAY( tmp1.ligIDs, tmp1.length, FT_UShort ) )
+      return error;
+    if ( ALLOC_ARRAY( tmp1.properties, tmp1.length, FT_UShort ) )
+      return error;
+    if ( in->properties )
+      MEM_Copy( tmp1.properties, in->properties,
+                in->length * sizeof( FT_UShort ) );
+    if ( ALLOC_ARRAY( tmp1.logClusters, tmp1.length, FT_Int ) )
+      return error;
+    MEM_Copy( tmp1.logClusters, in->logClusters,
+	      in->length * sizeof( FT_Int ) );
+
+    tmp2.memory      = memory;
+    tmp2.allocated   = 0;
+    tmp2.pos         = 0;
+    tmp2.string      = NULL;
+    tmp2.properties  = NULL;
+    tmp2.components  = NULL;
+    tmp2.ligIDs      = NULL;
+    tmp2.logClusters = NULL;
+
+    ptmp1 = &tmp1;
+    ptmp2 = &tmp2;
+
+    for ( j = 0; j < gsub->LookupList.LookupCount; j++ )
+      if ( properties[j] )
+      {
+        error = Do_String_Lookup( gsub, j, ptmp1, ptmp2 );
+        if ( error && error != TTO_Err_Not_Covered )
+          return error;
+
+        /* flipping `in' and `out', preparing the next loop */
+
+        ptmp1->pos       = in->pos;
+        ptmp2->length    = ptmp2->pos;
+        ptmp2->pos       = in->pos;
+        ptmp2->max_ligID = ptmp1->max_ligID;
+
+        t     = ptmp2;
+        ptmp2 = ptmp1;
+        ptmp1 = t;
+      }
+
+    out->length      = ptmp1->length;
+    out->pos         = 0;
+    out->allocated   = ptmp1->allocated;
+    out->string      = ptmp1->string;
+    out->components  = ptmp1->components;
+    out->ligIDs      = ptmp1->ligIDs;
+    out->logClusters = ptmp1->logClusters;
+
+    if ( in->properties )
+      out->properties = ptmp1->properties;
+    else
+    {
+      FREE( ptmp1->properties );
+      out->properties = NULL;
+    }
+
+    FREE( ptmp2->string );
+    FREE( ptmp2->properties );
+    FREE( ptmp2->components );
+    FREE( ptmp2->ligIDs );
+    FREE( ptmp2->logClusters );
+
+    return error;
+  }
+
+
+/* END */
diff --git a/src/ftxgsub.h b/src/ftxgsub.h
new file mode 100644
index 0000000..3f4f8ad
--- /dev/null
+++ b/src/ftxgsub.h
@@ -0,0 +1,612 @@
+/*******************************************************************
+ *
+ *  ftxgsub.h
+ *
+ *    TrueType Open GSUB table support
+ *
+ *  Copyright 1996-2000 by
+ *  David Turner, Robert Wilhelm, and Werner Lemberg.
+ *
+ *  This file is part of the FreeType project, and may only be used
+ *  modified and distributed under the terms of the FreeType project
+ *  license, LICENSE.TXT.  By continuing to use, modify, or distribute
+ *  this file you indicate that you have read the license and
+ *  understand and accept it fully.
+ *
+ ******************************************************************/
+
+#ifndef FTXOPEN_H
+#error "Don't include this file! Use ftxopen.h instead."
+#endif
+
+#ifndef FTXGSUB_H
+#define FTXGSUB_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define TTO_Err_Invalid_GSUB_SubTable_Format  0x1010
+#define TTO_Err_Invalid_GSUB_SubTable         0x1011
+
+
+/* Lookup types for glyph substitution */
+
+#define GSUB_LOOKUP_SINGLE     1
+#define GSUB_LOOKUP_MULTIPLE   2
+#define GSUB_LOOKUP_ALTERNATE  3
+#define GSUB_LOOKUP_LIGATURE   4
+#define GSUB_LOOKUP_CONTEXT    5
+#define GSUB_LOOKUP_CHAIN      6
+
+
+/* Use this if a feature applies to all glyphs */
+
+#define ALL_GLYPHS  0xFFFF
+
+
+  /* A pointer to a function which selects the alternate glyph.  `pos' is
+     the position of the glyph with index `glyphID', `num_alternates'
+     gives the number of alternates in the `alternates' array.  `data'
+     points to the user-defined structure specified during a call to
+     TT_GSUB_Register_Alternate_Function().  The function must return an
+     index into the `alternates' array.                                   */
+
+  typedef FT_UShort  (*TTO_AltFunction)(FT_ULong    pos,
+                                        FT_UShort   glyphID,
+                                        FT_UShort   num_alternates,
+                                        FT_UShort*  alternates,
+                                        void*       data );
+
+
+  struct  TTO_GSUBHeader_
+  {
+    FT_Memory        memory;
+    
+    FT_ULong         offset;
+
+    FT_Fixed         Version;
+
+    TTO_ScriptList   ScriptList;
+    TTO_FeatureList  FeatureList;
+    TTO_LookupList   LookupList;
+
+    TTO_GDEFHeader*  gdef;
+
+    /* the next two fields are used for an alternate substitution callback
+       function to select the proper alternate glyph.                      */
+
+    TTO_AltFunction  altfunc;
+    void*            data;
+  };
+
+  typedef struct TTO_GSUBHeader_   TTO_GSUBHeader;
+  typedef struct TTO_GSUBHeader_*  TTO_GSUB;
+
+
+  /* LookupType 1 */
+
+  struct  TTO_SingleSubstFormat1_
+  {
+    FT_Short  DeltaGlyphID;             /* constant added to get
+                                           substitution glyph index */
+  };
+
+  typedef struct TTO_SingleSubstFormat1_  TTO_SingleSubstFormat1;
+
+
+  struct  TTO_SingleSubstFormat2_
+  {
+    FT_UShort   GlyphCount;             /* number of glyph IDs in
+                                           Substitute array              */
+    FT_UShort*  Substitute;             /* array of substitute glyph IDs */
+  };
+
+  typedef struct TTO_SingleSubstFormat2_  TTO_SingleSubstFormat2;
+
+
+  struct  TTO_SingleSubst_
+  {
+    FT_UShort     SubstFormat;          /* 1 or 2         */
+    TTO_Coverage  Coverage;             /* Coverage table */
+
+    union
+    {
+      TTO_SingleSubstFormat1  ssf1;
+      TTO_SingleSubstFormat2  ssf2;
+    } ssf;
+  };
+
+  typedef struct TTO_SingleSubst_  TTO_SingleSubst;
+
+
+  /* LookupType 2 */
+
+  struct  TTO_Sequence_
+  {
+    FT_UShort   GlyphCount;             /* number of glyph IDs in the
+                                           Substitute array           */
+    FT_UShort*  Substitute;             /* string of glyph IDs to
+                                           substitute                 */
+  };
+
+  typedef struct TTO_Sequence_  TTO_Sequence;
+
+
+  struct  TTO_MultipleSubst_
+  {
+    FT_UShort      SubstFormat;         /* always 1                  */
+    TTO_Coverage   Coverage;            /* Coverage table            */
+    FT_UShort      SequenceCount;       /* number of Sequence tables */
+    TTO_Sequence*  Sequence;            /* array of Sequence tables  */
+  };
+
+  typedef struct TTO_MultipleSubst_  TTO_MultipleSubst;
+
+
+  /* LookupType 3 */
+
+  struct  TTO_AlternateSet_
+  {
+    FT_UShort   GlyphCount;             /* number of glyph IDs in the
+                                           Alternate array              */
+    FT_UShort*  Alternate;              /* array of alternate glyph IDs */
+  };
+
+  typedef struct TTO_AlternateSet_  TTO_AlternateSet;
+
+
+  struct  TTO_AlternateSubst_
+  {
+    FT_UShort          SubstFormat;     /* always 1                      */
+    TTO_Coverage       Coverage;        /* Coverage table                */
+    FT_UShort          AlternateSetCount;
+                                        /* number of AlternateSet tables */
+    TTO_AlternateSet*  AlternateSet;    /* array of AlternateSet tables  */
+  };
+
+  typedef struct TTO_AlternateSubst_  TTO_AlternateSubst;
+
+
+  /* LookupType 4 */
+
+  struct  TTO_Ligature_
+  {
+    FT_UShort   LigGlyph;               /* glyphID of ligature
+                                           to substitute                    */
+    FT_UShort   ComponentCount;         /* number of components in ligature */
+    FT_UShort*  Component;              /* array of component glyph IDs     */
+  };
+
+  typedef struct TTO_Ligature_  TTO_Ligature;
+
+
+  struct  TTO_LigatureSet_
+  {
+    FT_UShort      LigatureCount;       /* number of Ligature tables */
+    TTO_Ligature*  Ligature;            /* array of Ligature tables  */
+  };
+
+  typedef struct TTO_LigatureSet_  TTO_LigatureSet;
+
+
+  struct  TTO_LigatureSubst_
+  {
+    FT_UShort         SubstFormat;      /* always 1                     */
+    TTO_Coverage      Coverage;         /* Coverage table               */
+    FT_UShort         LigatureSetCount; /* number of LigatureSet tables */
+    TTO_LigatureSet*  LigatureSet;      /* array of LigatureSet tables  */
+  };
+
+  typedef struct TTO_LigatureSubst_  TTO_LigatureSubst;
+
+
+  /* needed by both lookup type 5 and 6 */
+
+  struct  TTO_SubstLookupRecord_
+  {
+    FT_UShort  SequenceIndex;           /* index into current
+                                           glyph sequence               */
+    FT_UShort  LookupListIndex;         /* Lookup to apply to that pos. */
+  };
+
+  typedef struct TTO_SubstLookupRecord_  TTO_SubstLookupRecord;
+
+
+  /* LookupType 5 */
+
+  struct  TTO_SubRule_
+  {
+    FT_UShort               GlyphCount; /* total number of input glyphs */
+    FT_UShort               SubstCount; /* number of SubstLookupRecord
+                                           tables                       */
+    FT_UShort*              Input;      /* array of input glyph IDs     */
+    TTO_SubstLookupRecord*  SubstLookupRecord;
+                                        /* array of SubstLookupRecord
+                                           tables                       */
+  };
+
+  typedef struct TTO_SubRule_  TTO_SubRule;
+
+
+  struct  TTO_SubRuleSet_
+  {
+    FT_UShort     SubRuleCount;         /* number of SubRule tables */
+    TTO_SubRule*  SubRule;              /* array of SubRule tables  */
+  };
+
+  typedef struct TTO_SubRuleSet_  TTO_SubRuleSet;
+
+
+  struct  TTO_ContextSubstFormat1_
+  {
+    TTO_Coverage     Coverage;          /* Coverage table              */
+    FT_UShort        SubRuleSetCount;   /* number of SubRuleSet tables */
+    TTO_SubRuleSet*  SubRuleSet;        /* array of SubRuleSet tables  */
+  };
+
+  typedef struct TTO_ContextSubstFormat1_  TTO_ContextSubstFormat1;
+
+
+  struct  TTO_SubClassRule_
+  {
+    FT_UShort               GlyphCount; /* total number of context classes */
+    FT_UShort               SubstCount; /* number of SubstLookupRecord
+                                           tables                          */
+    FT_UShort*              Class;      /* array of classes                */
+    TTO_SubstLookupRecord*  SubstLookupRecord;
+                                        /* array of SubstLookupRecord
+                                           tables                          */
+  };
+
+  typedef struct TTO_SubClassRule_  TTO_SubClassRule;
+
+
+  struct  TTO_SubClassSet_
+  {
+    FT_UShort          SubClassRuleCount;
+                                        /* number of SubClassRule tables */
+    TTO_SubClassRule*  SubClassRule;    /* array of SubClassRule tables  */
+  };
+
+  typedef struct TTO_SubClassSet_  TTO_SubClassSet;
+
+
+  /* The `MaxContextLength' field is not defined in the TTO specification
+     but simplifies the implementation of this format.  It holds the
+     maximal context length used in the context rules.                    */
+
+  struct  TTO_ContextSubstFormat2_
+  {
+    FT_UShort            MaxContextLength;
+                                        /* maximal context length       */
+    TTO_Coverage         Coverage;      /* Coverage table               */
+    TTO_ClassDefinition  ClassDef;      /* ClassDef table               */
+    FT_UShort            SubClassSetCount;
+                                        /* number of SubClassSet tables */
+    TTO_SubClassSet*     SubClassSet;   /* array of SubClassSet tables  */
+  };
+
+  typedef struct TTO_ContextSubstFormat2_  TTO_ContextSubstFormat2;
+
+
+  struct  TTO_ContextSubstFormat3_
+  {
+    FT_UShort               GlyphCount; /* number of input glyphs        */
+    FT_UShort               SubstCount; /* number of SubstLookupRecords  */
+    TTO_Coverage*           Coverage;   /* array of Coverage tables      */
+    TTO_SubstLookupRecord*  SubstLookupRecord;
+                                        /* array of substitution lookups */
+  };
+
+  typedef struct TTO_ContextSubstFormat3_  TTO_ContextSubstFormat3;
+
+
+  struct  TTO_ContextSubst_
+  {
+    FT_UShort  SubstFormat;             /* 1, 2, or 3 */
+
+    union
+    {
+      TTO_ContextSubstFormat1  csf1;
+      TTO_ContextSubstFormat2  csf2;
+      TTO_ContextSubstFormat3  csf3;
+    } csf;
+  };
+
+  typedef struct TTO_ContextSubst_  TTO_ContextSubst;
+
+
+  /* LookupType 6 */
+
+  struct  TTO_ChainSubRule_
+  {
+    FT_UShort               BacktrackGlyphCount;
+                                        /* total number of backtrack glyphs */
+    FT_UShort*              Backtrack;  /* array of backtrack glyph IDs     */
+    FT_UShort               InputGlyphCount;
+                                        /* total number of input glyphs     */
+    FT_UShort*              Input;      /* array of input glyph IDs         */
+    FT_UShort               LookaheadGlyphCount;
+                                        /* total number of lookahead glyphs */
+    FT_UShort*              Lookahead;  /* array of lookahead glyph IDs     */
+    FT_UShort               SubstCount; /* number of SubstLookupRecords     */
+    TTO_SubstLookupRecord*  SubstLookupRecord;
+                                        /* array of SubstLookupRecords      */
+  };
+
+  typedef struct TTO_ChainSubRule_  TTO_ChainSubRule;
+
+
+  struct  TTO_ChainSubRuleSet_
+  {
+    FT_UShort          ChainSubRuleCount;
+                                        /* number of ChainSubRule tables */
+    TTO_ChainSubRule*  ChainSubRule;    /* array of ChainSubRule tables  */
+  };
+
+  typedef struct TTO_ChainSubRuleSet_  TTO_ChainSubRuleSet;
+
+
+  struct  TTO_ChainContextSubstFormat1_
+  {
+    TTO_Coverage          Coverage;     /* Coverage table                   */
+    FT_UShort             ChainSubRuleSetCount;
+                                        /* number of ChainSubRuleSet tables */
+    TTO_ChainSubRuleSet*  ChainSubRuleSet;
+                                        /* array of ChainSubRuleSet tables  */
+  };
+
+  typedef struct TTO_ChainContextSubstFormat1_  TTO_ChainContextSubstFormat1;
+
+
+  struct  TTO_ChainSubClassRule_
+  {
+    FT_UShort               BacktrackGlyphCount;
+                                        /* total number of backtrack
+                                           classes                         */
+    FT_UShort*              Backtrack;  /* array of backtrack classes      */
+    FT_UShort               InputGlyphCount;
+                                        /* total number of context classes */
+    FT_UShort*              Input;      /* array of context classes        */
+    FT_UShort               LookaheadGlyphCount;
+                                        /* total number of lookahead
+                                           classes                         */
+    FT_UShort*              Lookahead;  /* array of lookahead classes      */
+    FT_UShort               SubstCount; /* number of SubstLookupRecords    */
+    TTO_SubstLookupRecord*  SubstLookupRecord;
+                                        /* array of substitution lookups   */
+  };
+
+  typedef struct TTO_ChainSubClassRule_  TTO_ChainSubClassRule;
+
+
+  struct  TTO_ChainSubClassSet_
+  {
+    FT_UShort               ChainSubClassRuleCount;
+                                        /* number of ChainSubClassRule
+                                           tables                      */
+    TTO_ChainSubClassRule*  ChainSubClassRule;
+                                        /* array of ChainSubClassRule
+                                           tables                      */
+  };
+
+  typedef struct TTO_ChainSubClassSet_  TTO_ChainSubClassSet;
+
+
+  /* The `MaxXXXLength' fields are not defined in the TTO specification
+     but simplifies the implementation of this format.  It holds the
+     maximal context length used in the specific context rules.         */
+
+  struct  TTO_ChainContextSubstFormat2_
+  {
+    TTO_Coverage           Coverage;    /* Coverage table             */
+
+    FT_UShort              MaxBacktrackLength;
+                                        /* maximal backtrack length   */
+    TTO_ClassDefinition    BacktrackClassDef;
+                                        /* BacktrackClassDef table    */
+    FT_UShort              MaxInputLength;
+                                        /* maximal input length       */
+    TTO_ClassDefinition    InputClassDef;
+                                        /* InputClassDef table        */
+    FT_UShort              MaxLookaheadLength;
+                                        /* maximal lookahead length   */
+    TTO_ClassDefinition    LookaheadClassDef;
+                                        /* LookaheadClassDef table    */
+
+    FT_UShort              ChainSubClassSetCount;
+                                        /* number of ChainSubClassSet
+                                           tables                     */
+    TTO_ChainSubClassSet*  ChainSubClassSet;
+                                        /* array of ChainSubClassSet
+                                           tables                     */
+  };
+
+  typedef struct TTO_ChainContextSubstFormat2_  TTO_ChainContextSubstFormat2;
+
+
+  struct  TTO_ChainContextSubstFormat3_
+  {
+    FT_UShort               BacktrackGlyphCount;
+                                        /* number of backtrack glyphs    */
+    TTO_Coverage*           BacktrackCoverage;
+                                        /* array of backtrack Coverage
+                                           tables                        */
+    FT_UShort               InputGlyphCount;
+                                        /* number of input glyphs        */
+    TTO_Coverage*           InputCoverage;
+                                        /* array of input coverage
+                                           tables                        */
+    FT_UShort               LookaheadGlyphCount;
+                                        /* number of lookahead glyphs    */
+    TTO_Coverage*           LookaheadCoverage;
+                                        /* array of lookahead coverage
+                                           tables                        */
+    FT_UShort               SubstCount; /* number of SubstLookupRecords  */
+    TTO_SubstLookupRecord*  SubstLookupRecord;
+                                        /* array of substitution lookups */
+  };
+
+  typedef struct TTO_ChainContextSubstFormat3_  TTO_ChainContextSubstFormat3;
+
+
+  struct  TTO_ChainContextSubst_
+  {
+    FT_UShort  SubstFormat;             /* 1, 2, or 3 */
+
+    union
+    {
+      TTO_ChainContextSubstFormat1  ccsf1;
+      TTO_ChainContextSubstFormat2  ccsf2;
+      TTO_ChainContextSubstFormat3  ccsf3;
+    } ccsf;
+  };
+
+  typedef struct TTO_ChainContextSubst_  TTO_ChainContextSubst;
+
+
+  union  TTO_GSUB_SubTable_
+  {
+    TTO_SingleSubst        single;
+    TTO_MultipleSubst      multiple;
+    TTO_AlternateSubst     alternate;
+    TTO_LigatureSubst      ligature;
+    TTO_ContextSubst       context;
+    TTO_ChainContextSubst  chain;
+  };
+
+  typedef union TTO_GSUB_SubTable_  TTO_GSUB_SubTable;
+
+
+  /* A simple string object.  It can both `send' and `receive' data.
+     In case of sending, `length' and `pos' will be used.  In case of
+     receiving, `pos' points to the first free slot, and `allocated'
+     specifies the amount of allocated memory (and the `length' field
+     will be ignored).  The routine TT_Add_String() will increase the
+     amount of memory if necessary.  After end of receive, `length'
+     should be set to the value of `pos', and `pos' will be set to zero.
+
+     `properties' (which is treated as a bit field) gives the glyph's
+     properties: If a certain bit is set for a glyph, the feature which
+     has the same bit set in its property value is applied.
+
+     `components' is an internal array which tracks components of
+     ligatures.  We need this for MarkToLigature Attachment Positioning
+     Subtables (in GPOS) together with `ligIDs' (which is used to mark
+     ligatures and the skipped glyphs during a ligature lookup).
+     `max_ligID' is increased after a successful ligature lookup.
+
+     NEVER modify any elements of the structure!  You should rather copy
+     its contents if necessary.
+
+     TT_Add_String() will also handle allocation; you should use
+     free() in case you want to destroy the arrays in the object. */
+
+  struct  TTO_GSUB_String_
+  {
+    FT_Memory   memory;
+    
+    FT_ULong    length;
+    FT_ULong    pos;
+    FT_ULong    allocated;
+    FT_UShort*  string;
+    FT_UShort*  properties;
+    FT_UShort*  components;
+    FT_UShort   max_ligID;
+    FT_UShort*  ligIDs;
+    FT_Int*     logClusters;
+  };
+
+  typedef struct TTO_GSUB_String_  TTO_GSUB_String;
+
+
+  /* finally, the GSUB API */
+
+  /*  EXPORT_DEF
+      TT_Error  TT_Init_GSUB_Extension( TT_Engine  engine ); */
+
+  EXPORT_DEF
+  FT_Error  TT_Load_GSUB_Table( FT_Face          face,
+                                TTO_GSUBHeader** gsub,
+                                TTO_GDEFHeader*  gdef );
+
+  EXPORT_DEF
+  FT_Error  TT_Done_GSUB_Table( TTO_GSUBHeader*  gsub );
+
+  EXPORT_DEF
+  FT_Error  TT_GSUB_Select_Script( TTO_GSUBHeader*  gsub,
+                                   FT_ULong         script_tag,
+                                   FT_UShort*       script_index );
+  EXPORT_DEF
+  FT_Error  TT_GSUB_Select_Language( TTO_GSUBHeader*  gsub,
+                                     FT_ULong         language_tag,
+                                     FT_UShort        script_index,
+                                     FT_UShort*       language_index,
+                                     FT_UShort*       req_feature_index );
+  EXPORT_DEF
+  FT_Error  TT_GSUB_Select_Feature( TTO_GSUBHeader*  gsub,
+                                    FT_ULong         feature_tag,
+                                    FT_UShort        script_index,
+                                    FT_UShort        language_index,
+                                    FT_UShort*       feature_index );
+
+  EXPORT_DEF
+  FT_Error  TT_GSUB_Query_Scripts( TTO_GSUBHeader*  gsub,
+                                   FT_ULong**       script_tag_list );
+  EXPORT_DEF
+  FT_Error  TT_GSUB_Query_Languages( TTO_GSUBHeader*  gsub,
+                                     FT_UShort        script_index,
+                                     FT_ULong**       language_tag_list );
+  EXPORT_DEF
+  FT_Error  TT_GSUB_Query_Features( TTO_GSUBHeader*  gsub,
+                                    FT_UShort        script_index,
+                                    FT_UShort        language_index,
+                                    FT_ULong**       feature_tag_list );
+
+  EXPORT_DEF
+  FT_Error  TT_GSUB_Add_Feature( TTO_GSUBHeader*  gsub,
+                                 FT_UShort        feature_index,
+                                 FT_UShort        property );
+  EXPORT_DEF
+  FT_Error  TT_GSUB_Clear_Features( TTO_GSUBHeader*  gsub );
+
+  EXPORT_DEF
+  FT_Error  TT_GSUB_Register_Alternate_Function( TTO_GSUBHeader*  gsub,
+                                                 TTO_AltFunction  altfunc,
+                                                 void*            data );
+  
+  EXPORT_DEF
+  FT_Error  TT_GSUB_String_New( FT_Memory           memory,
+				TTO_GSUB_String   **result );
+
+  EXPORT_DEF
+  FT_Error  TT_GSUB_String_Set_Length( TTO_GSUB_String *str,
+				       FT_ULong         new_length);
+
+  EXPORT_DEF
+  FT_Error  TT_GSUB_String_Done( TTO_GSUB_String   *str );
+
+
+  EXPORT_DEF
+  FT_Error  TT_GSUB_Apply_String( TTO_GSUBHeader*   gsub,
+                                  TTO_GSUB_String*  in,
+                                  TTO_GSUB_String*  out );
+
+  EXPORT_DEF
+  FT_Error  TT_GSUB_Add_String( TTO_GSUB_String*  in,
+                                FT_UShort         num_in,
+                                TTO_GSUB_String*  out,
+                                FT_UShort         num_out,
+                                FT_UShort*        glyph_data,
+                                FT_UShort         component,
+                                FT_UShort         ligID );
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FTXGSUB_H */
+
+
+/* END */
diff --git a/src/ftxopen.c b/src/ftxopen.c
new file mode 100644
index 0000000..5ffe030
--- /dev/null
+++ b/src/ftxopen.c
@@ -0,0 +1,1467 @@
+/*******************************************************************
+ *
+ *  ftxopen.c
+ *
+ *    TrueType Open common table support.
+ *
+ *  Copyright 1996-2000 by
+ *  David Turner, Robert Wilhelm, and Werner Lemberg.
+ *
+ *  This file is part of the FreeType project, and may only be used
+ *  modified and distributed under the terms of the FreeType project
+ *  license, LICENSE.TXT.  By continuing to use, modify, or distribute
+ *  this file you indicate that you have read the license and
+ *  understand and accept it fully.
+ *
+ ******************************************************************/
+
+#include <freetype/internal/ftstream.h>
+#include <freetype/internal/ftmemory.h>
+#include <freetype/internal/tterrors.h>
+#include <freetype/internal/tttypes.h>
+
+#include "ftxopen.h"
+#include "ftxopenf.h"
+
+
+  /***************************
+   * Script related functions
+   ***************************/
+
+
+  /* LangSys */
+
+  static FT_Error  Load_LangSys( TTO_LangSys*  ls,
+				 FT_Stream     stream )
+  {
+    FT_Error   error;
+    FT_Memory  memory = stream->memory;
+    FT_UShort  n, count;
+    FT_UShort* fi;
+
+
+    if ( ACCESS_Frame( 6L ) )
+      return error;
+
+    ls->LookupOrderOffset    = GET_UShort();    /* should be 0 */
+    ls->ReqFeatureIndex      = GET_UShort();
+    count = ls->FeatureCount = GET_UShort();
+
+    FORGET_Frame();
+
+    ls->FeatureIndex = NULL;
+
+    if ( ALLOC_ARRAY( ls->FeatureIndex, count, FT_UShort ) )
+      return error;
+
+    if ( ACCESS_Frame( count * 2L ) )
+    {
+      FREE( ls->FeatureIndex );
+      return error;
+    }
+
+    fi = ls->FeatureIndex;
+
+    for ( n = 0; n < count; n++ )
+      fi[n] = GET_UShort();
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+  }
+
+
+  static void  Free_LangSys( TTO_LangSys*  ls,
+   		             FT_Memory     memory )
+  {
+    FREE( ls->FeatureIndex );
+  }
+
+
+  /* Script */
+
+  static FT_Error  Load_Script( TTO_Script*  s,
+				FT_Stream    stream )
+  {
+    FT_Error   error;
+    FT_Memory  memory = stream->memory;
+    FT_UShort  n, count;
+    FT_ULong   cur_offset, new_offset, base_offset;
+
+    TTO_LangSysRecord*  lsr;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    new_offset = GET_UShort() + base_offset;
+
+    FORGET_Frame();
+
+    if ( new_offset != base_offset )        /* not a NULL offset */
+    {
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_LangSys( &s->DefaultLangSys,
+                                   stream ) ) != TT_Err_Ok )
+        return error;
+      (void)FILE_Seek( cur_offset );
+    }
+    else
+    {
+      /* we create a DefaultLangSys table with no entries */
+
+      s->DefaultLangSys.LookupOrderOffset = 0;
+      s->DefaultLangSys.ReqFeatureIndex   = 0xFFFF;
+      s->DefaultLangSys.FeatureCount      = 0;
+      s->DefaultLangSys.FeatureIndex      = NULL;
+    }
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail2;
+
+    count = s->LangSysCount = GET_UShort();
+
+    /* safety check; otherwise the official handling of TrueType Open
+       fonts won't work */
+
+    if ( s->LangSysCount == 0 && s->DefaultLangSys.FeatureCount == 0 )
+    {
+      error = TTO_Err_Invalid_SubTable;
+      goto Fail2;
+    }
+
+    FORGET_Frame();
+
+    s->LangSysRecord = NULL;
+
+    if ( ALLOC_ARRAY( s->LangSysRecord, count, TTO_LangSysRecord ) )
+      goto Fail2;
+
+    lsr = s->LangSysRecord;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 6L ) )
+        goto Fail1;
+
+      lsr[n].LangSysTag = GET_ULong();
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_LangSys( &lsr[n].LangSys, stream ) ) != TT_Err_Ok )
+        goto Fail1;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail1:
+    for ( n = 0; n < count; n++ )
+      Free_LangSys( &lsr[n].LangSys, memory );
+
+    FREE( s->LangSysRecord );
+
+  Fail2:
+    Free_LangSys( &s->DefaultLangSys, memory );
+    return error;
+  }
+
+
+  static void  Free_Script( TTO_Script*  s,
+			    FT_Memory    memory )
+  {
+    FT_UShort           n, count;
+
+    TTO_LangSysRecord*  lsr;
+
+
+    Free_LangSys( &s->DefaultLangSys, memory );
+
+    if ( s->LangSysRecord )
+    {
+      count = s->LangSysCount;
+      lsr   = s->LangSysRecord;
+
+      for ( n = 0; n < count; n++ )
+        Free_LangSys( &lsr[n].LangSys, memory );
+
+      FREE( lsr );
+    }
+  }
+
+
+  /* ScriptList */
+
+  FT_Error  Load_ScriptList( TTO_ScriptList*  sl,
+   		   	     FT_Stream        stream )
+  {
+    FT_Error   error;
+    FT_Memory  memory = stream->memory;
+
+    FT_UShort          n, count;
+    FT_ULong           cur_offset, new_offset, base_offset;
+
+    TTO_ScriptRecord*  sr;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = sl->ScriptCount = GET_UShort();
+
+    FORGET_Frame();
+
+    sl->ScriptRecord = NULL;
+
+    if ( ALLOC_ARRAY( sl->ScriptRecord, count, TTO_ScriptRecord ) )
+      return error;
+
+    sr = sl->ScriptRecord;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 6L ) )
+        goto Fail;
+
+      sr[n].ScriptTag = GET_ULong();
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_Script( &sr[n].Script, stream ) ) != TT_Err_Ok )
+        goto Fail;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    for ( n = 0; n < count; n++ )
+      Free_Script( &sr[n].Script, memory );
+
+    FREE( sl->ScriptRecord );
+    return error;
+  }
+
+
+  void  Free_ScriptList( TTO_ScriptList*  sl,
+                         FT_Memory        memory )
+  {
+    FT_UShort          n, count;
+
+    TTO_ScriptRecord*  sr;
+
+
+    if ( sl->ScriptRecord )
+    {
+      count = sl->ScriptCount;
+      sr    = sl->ScriptRecord;
+
+      for ( n = 0; n < count; n++ )
+        Free_Script( &sr[n].Script, memory );
+
+      FREE( sr );
+    }
+  }
+
+
+
+  /*********************************
+   * Feature List related functions
+   *********************************/
+
+
+  /* Feature */
+
+  static FT_Error  Load_Feature( TTO_Feature*  f,
+				 FT_Stream     stream )
+  {
+    FT_Error   error;
+    FT_Memory  memory = stream->memory;
+
+    FT_UShort   n, count;
+
+    FT_UShort*  lli;
+
+
+    if ( ACCESS_Frame( 4L ) )
+      return error;
+
+    f->FeatureParams           = GET_UShort();    /* should be 0 */
+    count = f->LookupListCount = GET_UShort();
+
+    FORGET_Frame();
+
+    f->LookupListIndex = NULL;
+
+    if ( ALLOC_ARRAY( f->LookupListIndex, count, FT_UShort ) )
+      return error;
+
+    lli = f->LookupListIndex;
+
+    if ( ACCESS_Frame( count * 2L ) )
+    {
+      FREE( f->LookupListIndex );
+      return error;
+    }
+
+    for ( n = 0; n < count; n++ )
+      lli[n] = GET_UShort();
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+  }
+
+
+  static void  Free_Feature( TTO_Feature*  f,
+			     FT_Memory     memory )
+  {
+    FREE( f->LookupListIndex );
+  }
+
+
+  /* FeatureList */
+
+  FT_Error  Load_FeatureList( TTO_FeatureList*  fl,
+			      FT_Stream         stream )
+  {
+    FT_Error   error;
+    FT_Memory  memory = stream->memory;
+
+    FT_UShort           n, count;
+    FT_ULong            cur_offset, new_offset, base_offset;
+
+    TTO_FeatureRecord*  fr;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = fl->FeatureCount = GET_UShort();
+
+    FORGET_Frame();
+
+    fl->FeatureRecord = NULL;
+
+    if ( ALLOC_ARRAY( fl->FeatureRecord, count, TTO_FeatureRecord ) )
+      return error;
+
+    fr = fl->FeatureRecord;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 6L ) )
+        goto Fail;
+
+      fr[n].FeatureTag = GET_ULong();
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_Feature( &fr[n].Feature, stream ) ) != TT_Err_Ok )
+        goto Fail;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    for ( n = 0; n < count; n++ )
+      Free_Feature( &fr[n].Feature, memory );
+
+    FREE( fl->FeatureRecord );
+    return error;
+  }
+
+
+  void  Free_FeatureList( TTO_FeatureList*  fl,
+			  FT_Memory         memory)
+  {
+    FT_UShort           n, count;
+
+    TTO_FeatureRecord*  fr;
+
+
+    if ( fl->FeatureRecord )
+    {
+      count = fl->FeatureCount;
+      fr    = fl->FeatureRecord;
+
+      for ( n = 0; n < count; n++ )
+        Free_Feature( &fr[n].Feature, memory );
+
+      FREE( fr );
+    }
+  }
+
+
+
+  /********************************
+   * Lookup List related functions
+   ********************************/
+
+  /* the subroutines of the following two functions are defined in
+     ftxgsub.c and ftxgpos.c respectively                          */
+
+
+  /* SubTable */
+
+  static FT_Error  Load_SubTable( TTO_SubTable*  st,
+				  FT_Stream      stream,
+                                  TTO_Type       table_type,
+                                  FT_UShort      lookup_type )
+  {
+    if ( table_type == GSUB )
+      switch ( lookup_type )
+      {
+      case GSUB_LOOKUP_SINGLE:
+        return Load_SingleSubst( &st->st.gsub.single, stream );
+
+      case GSUB_LOOKUP_MULTIPLE:
+        return Load_MultipleSubst( &st->st.gsub.multiple, stream );
+
+      case GSUB_LOOKUP_ALTERNATE:
+        return Load_AlternateSubst( &st->st.gsub.alternate, stream );
+
+      case GSUB_LOOKUP_LIGATURE:
+        return Load_LigatureSubst( &st->st.gsub.ligature, stream );
+
+      case GSUB_LOOKUP_CONTEXT:
+        return Load_ContextSubst( &st->st.gsub.context, stream );
+
+      case GSUB_LOOKUP_CHAIN:
+        return Load_ChainContextSubst( &st->st.gsub.chain, stream );
+
+      default:
+        return TTO_Err_Invalid_GSUB_SubTable_Format;
+      }
+    else
+      switch ( lookup_type )
+      {
+#if 0
+        case GPOS_LOOKUP_SINGLE:
+          return Load_SinglePos( &st->st.gpos.single, stream );
+
+        case GPOS_LOOKUP_PAIR:
+          return Load_PairPos( &st->st.gpos.pair, stream );
+
+        case GPOS_LOOKUP_CURSIVE:
+          return Load_CursivePos( &st->st.gpos.cursive, stream );
+
+        case GPOS_LOOKUP_MARKBASE:
+          return Load_MarkBasePos( &st->st.gpos.markbase, stream );
+
+        case GPOS_LOOKUP_MARKLIG:
+          return Load_MarkLigPos( &st->st.gpos.marklig, stream );
+
+        case GPOS_LOOKUP_MARKMARK:
+          return Load_MarkMarkPos( &st->st.gpos.markmark, stream );
+
+        case GPOS_LOOKUP_CONTEXT:
+          return Load_ContextPos( &st->st.gpos.context, stream );
+
+        case GPOS_LOOKUP_CHAIN:
+          return Load_ChainContextPos( &st->st.gpos.chain, stream );
+
+#endif
+        default:
+          return TTO_Err_Invalid_GPOS_SubTable_Format;
+      }
+
+    return TT_Err_Ok;           /* never reached */
+  }
+
+
+  static void  Free_SubTable( TTO_SubTable*  st,
+                              TTO_Type       table_type,
+                              FT_UShort      lookup_type,
+			      FT_Memory      memory )
+  {
+    if ( table_type == GSUB )
+      switch ( lookup_type )
+      {
+      case GSUB_LOOKUP_SINGLE:
+        Free_SingleSubst( &st->st.gsub.single, memory );
+        break;
+
+      case GSUB_LOOKUP_MULTIPLE:
+        Free_MultipleSubst( &st->st.gsub.multiple, memory );
+        break;
+
+      case GSUB_LOOKUP_ALTERNATE:
+        Free_AlternateSubst( &st->st.gsub.alternate, memory );
+        break;
+
+      case GSUB_LOOKUP_LIGATURE:
+        Free_LigatureSubst( &st->st.gsub.ligature, memory );
+        break;
+
+      case GSUB_LOOKUP_CONTEXT:
+        Free_ContextSubst( &st->st.gsub.context, memory );
+        break;
+
+      case GSUB_LOOKUP_CHAIN:
+        Free_ChainContextSubst( &st->st.gsub.chain, memory );
+        break;
+      }
+    else
+      switch ( lookup_type )
+      {
+#if 0
+      case GPOS_LOOKUP_SINGLE:
+        Free_SinglePos( &st->st.gpos.single );
+        break;
+
+      case GPOS_LOOKUP_PAIR:
+        Free_PairPos( &st->st.gpos.pair );
+        break;
+
+      case GPOS_LOOKUP_CURSIVE:
+        Free_CursivePos( &st->st.gpos.cursive );
+        break;
+
+      case GPOS_LOOKUP_MARKBASE:
+        Free_MarkBasePos( &st->st.gpos.markbase );
+        break;
+
+      case GPOS_LOOKUP_MARKLIG:
+        Free_MarkLigPos( &st->st.gpos.marklig );
+        break;
+
+      case GPOS_LOOKUP_MARKMARK:
+        Free_MarkMarkPos( &st->st.gpos.markmark );
+        break;
+
+      case GPOS_LOOKUP_CONTEXT:
+        Free_ContextPos( &st->st.gpos.context );
+        break;
+
+      case GPOS_LOOKUP_CHAIN:
+        Free_ChainContextPos ( &st->st.gpos.chain );
+        break;
+#endif
+      }
+  }
+
+
+  /* Lookup */
+
+  static FT_Error  Load_Lookup( TTO_Lookup*   l,
+				FT_Stream     stream,
+                                TTO_Type      type )
+  {
+    FT_Error   error;
+    FT_Memory  memory = stream->memory;
+
+    FT_UShort      n, count;
+    FT_ULong       cur_offset, new_offset, base_offset;
+
+    TTO_SubTable*  st;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 6L ) )
+      return error;
+
+    l->LookupType            = GET_UShort();
+    l->LookupFlag            = GET_UShort();
+    count = l->SubTableCount = GET_UShort();
+
+    FORGET_Frame();
+
+    l->SubTable = NULL;
+
+    if ( ALLOC_ARRAY( l->SubTable, count, TTO_SubTable ) )
+      return error;
+
+    st = l->SubTable;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_SubTable( &st[n], stream,
+                                    type, l->LookupType ) ) != TT_Err_Ok )
+        goto Fail;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail:
+    for ( n = 0; n < count; n++ )
+      Free_SubTable( &st[n], type, l->LookupType, memory );
+
+    FREE( l->SubTable );
+    return error;
+  }
+
+
+  static void  Free_Lookup( TTO_Lookup*   l,
+                            TTO_Type      type,
+			    FT_Memory     memory)
+  {
+    FT_UShort      n, count;
+
+    TTO_SubTable*  st;
+
+
+    if ( l->SubTable )
+    {
+      count = l->SubTableCount;
+      st    = l->SubTable;
+
+      for ( n = 0; n < count; n++ )
+        Free_SubTable( &st[n], type, l->LookupType, memory );
+
+      FREE( st );
+    }
+  }
+
+
+  /* LookupList */
+
+  FT_Error  Load_LookupList( TTO_LookupList*  ll,
+			     FT_Stream        stream,
+                             TTO_Type         type )
+  {
+    FT_Error   error;
+    FT_Memory  memory = stream->memory;
+
+    FT_UShort    n, count;
+    FT_ULong     cur_offset, new_offset, base_offset;
+
+    TTO_Lookup*  l;
+
+
+    base_offset = FILE_Pos();
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = ll->LookupCount = GET_UShort();
+
+    FORGET_Frame();
+
+    ll->Lookup = NULL;
+
+    if ( ALLOC_ARRAY( ll->Lookup, count, TTO_Lookup ) )
+      return error;
+    if ( ALLOC_ARRAY( ll->Properties, count, FT_UShort ) )
+      goto Fail2;
+
+    l = ll->Lookup;
+
+    for ( n = 0; n < count; n++ )
+    {
+      if ( ACCESS_Frame( 2L ) )
+        goto Fail1;
+
+      new_offset = GET_UShort() + base_offset;
+
+      FORGET_Frame();
+
+      cur_offset = FILE_Pos();
+      if ( FILE_Seek( new_offset ) ||
+           ( error = Load_Lookup( &l[n], stream, type ) ) != TT_Err_Ok )
+        goto Fail1;
+      (void)FILE_Seek( cur_offset );
+    }
+
+    return TT_Err_Ok;
+
+  Fail1:
+    FREE( ll->Properties );
+
+    for ( n = 0; n < count; n++ )
+      Free_Lookup( &l[n], type, memory );
+
+  Fail2:
+    FREE( ll->Lookup );
+    return error;
+  }
+
+
+  void  Free_LookupList( TTO_LookupList*  ll,
+                         TTO_Type         type,
+			 FT_Memory        memory )
+  {
+    FT_UShort    n, count;
+
+    TTO_Lookup*  l;
+
+
+    FREE( ll->Properties );
+
+    if ( ll->Lookup )
+    {
+      count = ll->LookupCount;
+      l     = ll->Lookup;
+
+      for ( n = 0; n < count; n++ )
+        Free_Lookup( &l[n], type, memory );
+
+      FREE( l );
+    }
+  }
+
+
+
+  /*****************************
+   * Coverage related functions
+   *****************************/
+
+
+  /* CoverageFormat1 */
+
+  static FT_Error  Load_Coverage1( TTO_CoverageFormat1*  cf1,
+				   FT_Stream             stream )
+  {
+    FT_Error   error;
+    FT_Memory  memory = stream->memory;
+
+    FT_UShort  n, count;
+
+    FT_UShort* ga;
+
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = cf1->GlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    cf1->GlyphArray = NULL;
+
+    if ( ALLOC_ARRAY( cf1->GlyphArray, count, FT_UShort ) )
+      return error;
+
+    ga = cf1->GlyphArray;
+
+    if ( ACCESS_Frame( count * 2L ) )
+    {
+      FREE( cf1->GlyphArray );
+      return error;
+    }
+
+    for ( n = 0; n < count; n++ )
+      ga[n] = GET_UShort();
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+  }
+
+
+  static void  Free_Coverage1( TTO_CoverageFormat1*  cf1,
+			       FT_Memory             memory)
+  {
+    FREE( cf1->GlyphArray );
+  }
+
+
+  /* CoverageFormat2 */
+
+  static FT_Error  Load_Coverage2( TTO_CoverageFormat2*  cf2,
+				   FT_Stream             stream )
+  {
+    FT_Error   error;
+    FT_Memory  memory = stream->memory;
+
+    FT_UShort         n, count;
+
+    TTO_RangeRecord*  rr;
+
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = cf2->RangeCount = GET_UShort();
+
+    FORGET_Frame();
+
+    cf2->RangeRecord = NULL;
+
+    if ( ALLOC_ARRAY( cf2->RangeRecord, count, TTO_RangeRecord ) )
+      return error;
+
+    rr = cf2->RangeRecord;
+
+    if ( ACCESS_Frame( count * 6L ) )
+      goto Fail;
+
+    for ( n = 0; n < count; n++ )
+    {
+      rr[n].Start              = GET_UShort();
+      rr[n].End                = GET_UShort();
+      rr[n].StartCoverageIndex = GET_UShort();
+
+      /* sanity check; we are limited to 16bit integers */
+      if ( rr[n].Start > rr[n].End ||
+           ( rr[n].End - rr[n].Start + (long)rr[n].StartCoverageIndex ) >=
+             0x10000L )
+      {
+        error = TTO_Err_Invalid_SubTable;
+        goto Fail;
+      }
+    }
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+
+  Fail:
+    FREE( cf2->RangeRecord );
+    return error;
+  }
+
+
+  static void  Free_Coverage2( TTO_CoverageFormat2*  cf2,
+			       FT_Memory             memory )
+  {
+    FREE( cf2->RangeRecord );
+  }
+
+
+  FT_Error  Load_Coverage( TTO_Coverage*  c,
+			   FT_Stream      stream )
+  {
+    FT_Error   error;
+    FT_Memory  memory = stream->memory;
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    c->CoverageFormat = GET_UShort();
+
+    FORGET_Frame();
+
+    switch ( c->CoverageFormat )
+    {
+    case 1:
+      return Load_Coverage1( &c->cf.cf1, stream );
+
+    case 2:
+      return Load_Coverage2( &c->cf.cf2, stream );
+
+    default:
+      return TTO_Err_Invalid_SubTable_Format;
+    }
+
+    return TT_Err_Ok;               /* never reached */
+  }
+
+
+  void  Free_Coverage( TTO_Coverage*  c,
+		       FT_Memory      memory )
+  {
+    switch ( c->CoverageFormat )
+    {
+    case 1:
+      Free_Coverage1( &c->cf.cf1, memory );
+      break;
+
+    case 2:
+      Free_Coverage2( &c->cf.cf2, memory );
+      break;
+    }
+  }
+
+
+  static FT_Error  Coverage_Index1( TTO_CoverageFormat1*  cf1,
+                                    FT_UShort             glyphID,
+                                    FT_UShort*            index )
+  {
+    FT_UShort min, max, new_min, new_max, middle;
+
+    FT_UShort*  array = cf1->GlyphArray;
+
+
+    /* binary search */
+
+    new_min = 0;
+    new_max = cf1->GlyphCount - 1;
+
+    do
+    {
+      min = new_min;
+      max = new_max;
+
+      /* we use (min + max) / 2 = max - (max - min) / 2  to avoid
+         overflow and rounding errors                             */
+
+      middle = max - ( ( max - min ) >> 1 );
+
+      if ( glyphID == array[middle] )
+      {
+        *index = middle;
+        return TT_Err_Ok;
+      }
+      else if ( glyphID < array[middle] )
+      {
+        if ( middle == min )
+          break;
+        new_max = middle - 1;
+      }
+      else
+      {
+        if ( middle == max )
+          break;
+        new_min = middle + 1;
+      }
+    } while ( min < max );
+
+    return TTO_Err_Not_Covered;
+  }
+
+
+  static FT_Error  Coverage_Index2( TTO_CoverageFormat2*  cf2,
+                                    FT_UShort             glyphID,
+                                    FT_UShort*            index )
+  {
+    FT_UShort         min, max, new_min, new_max, middle;
+
+    TTO_RangeRecord*  rr = cf2->RangeRecord;
+
+
+    /* binary search */
+
+    new_min = 0;
+    new_max = cf2->RangeCount - 1;
+
+    do
+    {
+      min = new_min;
+      max = new_max;
+
+      /* we use (min + max) / 2 = max - (max - min) / 2  to avoid
+         overflow and rounding errors                             */
+
+      middle = max - ( ( max - min ) >> 1 );
+
+      if ( glyphID >= rr[middle].Start && glyphID <= rr[middle].End )
+      {
+        *index = rr[middle].StartCoverageIndex + glyphID - rr[middle].Start;
+        return TT_Err_Ok;
+      }
+      else if ( glyphID < rr[middle].Start )
+      {
+        if ( middle == min )
+          break;
+        new_max = middle - 1;
+      }
+      else
+      {
+        if ( middle == max )
+          break;
+        new_min = middle + 1;
+      }
+    } while ( min < max );
+
+    return TTO_Err_Not_Covered;
+  }
+
+
+  FT_Error  Coverage_Index( TTO_Coverage*  c,
+                            FT_UShort      glyphID,
+                            FT_UShort*     index )
+  {
+    switch ( c->CoverageFormat )
+    {
+    case 1:
+      return Coverage_Index1( &c->cf.cf1, glyphID, index );
+
+    case 2:
+      return Coverage_Index2( &c->cf.cf2, glyphID, index );
+
+    default:
+      return TTO_Err_Invalid_SubTable_Format;
+    }
+
+    return TT_Err_Ok;               /* never reached */
+  }
+
+
+
+  /*************************************
+   * Class Definition related functions
+   *************************************/
+
+
+  /* ClassDefFormat1 */
+
+  static FT_Error  Load_ClassDef1( TTO_ClassDefinition*  cd,
+                                   FT_UShort             limit,
+				   FT_Stream             stream )
+  {
+    FT_Error   error;
+    FT_Memory  memory = stream->memory;
+
+    FT_UShort             n, count;
+
+    FT_UShort*            cva;
+    FT_Bool*              d;
+
+    TTO_ClassDefFormat1*  cdf1;
+
+
+    cdf1 = &cd->cd.cd1;
+
+    if ( ACCESS_Frame( 4L ) )
+      return error;
+
+    cdf1->StartGlyph         = GET_UShort();
+    count = cdf1->GlyphCount = GET_UShort();
+
+    FORGET_Frame();
+
+    /* sanity check; we are limited to 16bit integers */
+
+    if ( cdf1->StartGlyph + (long)count >= 0x10000L )
+      return TTO_Err_Invalid_SubTable;
+
+    cdf1->ClassValueArray = NULL;
+
+    if ( ALLOC_ARRAY( cdf1->ClassValueArray, count, FT_UShort ) )
+      return error;
+
+    d   = cd->Defined;
+    cva = cdf1->ClassValueArray;
+
+    if ( ACCESS_Frame( count * 2L ) )
+      goto Fail;
+
+    for ( n = 0; n < count; n++ )
+    {
+      cva[n] = GET_UShort();
+      if ( cva[n] >= limit )
+      {
+        error = TTO_Err_Invalid_SubTable;
+        goto Fail;
+      }
+      d[cva[n]] = TRUE;
+    }
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+
+  Fail:
+    FREE( cva );
+
+    return error;
+  }
+
+
+  static void  Free_ClassDef1( TTO_ClassDefFormat1*  cdf1,
+			       FT_Memory             memory )
+  {
+    FREE( cdf1->ClassValueArray );
+  }
+
+
+  /* ClassDefFormat2 */
+
+  static FT_Error  Load_ClassDef2( TTO_ClassDefinition*  cd,
+                                   FT_UShort             limit,
+				   FT_Stream             stream )
+  {
+    FT_Error   error;
+    FT_Memory  memory = stream->memory;
+
+    FT_UShort              n, count;
+
+    TTO_ClassRangeRecord*  crr;
+    FT_Bool*               d;
+
+    TTO_ClassDefFormat2*   cdf2;
+
+
+    cdf2 = &cd->cd.cd2;
+
+    if ( ACCESS_Frame( 2L ) )
+      return error;
+
+    count = cdf2->ClassRangeCount = GET_UShort();
+
+    FORGET_Frame();
+
+    cdf2->ClassRangeRecord = NULL;
+
+    if ( ALLOC_ARRAY( cdf2->ClassRangeRecord, count, TTO_ClassRangeRecord ) )
+      return error;
+
+    d   = cd->Defined;
+    crr = cdf2->ClassRangeRecord;
+
+    if ( ACCESS_Frame( count * 6L ) )
+      goto Fail;
+
+    for ( n = 0; n < count; n++ )
+    {
+      crr[n].Start = GET_UShort();
+      crr[n].End   = GET_UShort();
+      crr[n].Class = GET_UShort();
+
+      /* sanity check */
+
+      if ( crr[n].Start > crr[n].End ||
+           crr[n].Class >= limit )
+      {
+        error = TTO_Err_Invalid_SubTable;
+        goto Fail;
+      }
+      d[crr[n].Class] = TRUE;
+    }
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+
+  Fail:
+    FREE( crr );
+
+    return error;
+  }
+
+
+  static void  Free_ClassDef2( TTO_ClassDefFormat2*  cdf2,
+			       FT_Memory             memory )
+  {
+    FREE( cdf2->ClassRangeRecord );
+  }
+
+
+  /* ClassDefinition */
+
+  FT_Error  Load_ClassDefinition( TTO_ClassDefinition*  cd,
+                                  FT_UShort             limit,
+				  FT_Stream             stream )
+  {
+    FT_Error   error;
+    FT_Memory  memory = stream->memory;
+
+
+    if ( ALLOC_ARRAY( cd->Defined, limit, FT_Bool ) )
+      return error;
+
+    if ( ACCESS_Frame( 2L ) )
+      goto Fail;
+
+    cd->ClassFormat = GET_UShort();
+
+    FORGET_Frame();
+
+    switch ( cd->ClassFormat )
+    {
+    case 1:
+      error = Load_ClassDef1( cd, limit, stream );
+      break;
+
+    case 2:
+      error = Load_ClassDef2( cd, limit, stream );
+      break;
+
+    default:
+      error = TTO_Err_Invalid_SubTable_Format;
+      break;
+    }
+
+    if ( error )
+      goto Fail;
+
+    cd->loaded = TRUE;
+
+    return TT_Err_Ok;
+
+  Fail:
+    FREE( cd->Defined );
+    return error;
+  }
+
+
+  void  Free_ClassDefinition( TTO_ClassDefinition*  cd,
+			      FT_Memory             memory )
+  {
+    if ( !cd->loaded )
+      return;
+
+    FREE( cd->Defined );
+
+    switch ( cd->ClassFormat )
+    {
+    case 1:
+      Free_ClassDef1( &cd->cd.cd1, memory );
+      break;
+
+    case 2:
+      Free_ClassDef2( &cd->cd.cd2, memory );
+      break;
+    }
+  }
+
+
+  static FT_Error  Get_Class1( TTO_ClassDefFormat1*  cdf1,
+                               FT_UShort             glyphID,
+                               FT_UShort*            class,
+                               FT_UShort*            index )
+  {
+    FT_UShort*  cva = cdf1->ClassValueArray;
+
+
+    *index = 0;
+
+    if ( glyphID >= cdf1->StartGlyph &&
+         glyphID <= cdf1->StartGlyph + cdf1->GlyphCount )
+    {
+      *class = cva[glyphID - cdf1->StartGlyph];
+      return TT_Err_Ok;
+    }
+    else
+    {
+      *class = 0;
+      return TTO_Err_Not_Covered;
+    }
+  }
+
+
+  /* we need the index value of the last searched class range record
+     in case of failure for constructed GDEF tables                  */
+
+  static FT_Error  Get_Class2( TTO_ClassDefFormat2*  cdf2,
+                               FT_UShort             glyphID,
+                               FT_UShort*            class,
+                               FT_UShort*            index )
+  {
+    FT_Error               error = TT_Err_Ok;
+    FT_UShort              min, max, new_min, new_max, middle;
+
+    TTO_ClassRangeRecord*  crr = cdf2->ClassRangeRecord;
+
+
+    /* binary search */
+
+    new_min = 0;
+    new_max = cdf2->ClassRangeCount - 1;
+
+    do
+    {
+      min = new_min;
+      max = new_max;
+
+      /* we use (min + max) / 2 = max - (max - min) / 2  to avoid
+         overflow and rounding errors                             */
+
+      middle = max - ( ( max - min ) >> 1 );
+
+      if ( glyphID >= crr[middle].Start && glyphID <= crr[middle].End )
+      {
+        *class = crr[middle].Class;
+        error  = TT_Err_Ok;
+        break;
+      }
+      else if ( glyphID < crr[middle].Start )
+      {
+        if ( middle == min )
+        {
+          *class = 0;
+          error  = TTO_Err_Not_Covered;
+          break;
+        }
+        new_max = middle - 1;
+      }
+      else
+      {
+        if ( middle == max )
+        {
+          *class = 0;
+          error  = TTO_Err_Not_Covered;
+          break;
+        }
+        new_min = middle + 1;
+      }
+    } while ( min < max );
+
+    if ( index )
+      *index = middle;
+
+    return error;
+  }
+
+
+  FT_Error  Get_Class( TTO_ClassDefinition*  cd,
+                       FT_UShort             glyphID,
+                       FT_UShort*            class,
+                       FT_UShort*            index )
+  {
+    switch ( cd->ClassFormat )
+    {
+    case 1:
+      return Get_Class1( &cd->cd.cd1, glyphID, class, index );
+
+    case 2:
+      return Get_Class2( &cd->cd.cd2, glyphID, class, index );
+
+    default:
+      return TTO_Err_Invalid_SubTable_Format;
+    }
+
+    return TT_Err_Ok;               /* never reached */
+  }
+
+
+
+  /***************************
+   * Device related functions
+   ***************************/
+
+
+  FT_Error  Load_Device( TTO_Device*  d,
+			 FT_Stream    stream )
+  {
+    FT_Error   error;
+    FT_Memory  memory = stream->memory;
+
+    FT_UShort   n, count;
+
+    FT_UShort*  dv;
+
+
+    if ( ACCESS_Frame( 6L ) )
+      return error;
+
+    d->StartSize   = GET_UShort();
+    d->EndSize     = GET_UShort();
+    d->DeltaFormat = GET_UShort();
+
+    FORGET_Frame();
+
+    if ( d->StartSize > d->EndSize ||
+         d->DeltaFormat == 0 || d->DeltaFormat > 3 )
+      return TTO_Err_Invalid_SubTable;
+
+    d->DeltaValue = NULL;
+
+    count = ( ( d->EndSize - d->StartSize + 1 ) >>
+                ( 4 - d->DeltaFormat ) ) + 1;
+
+    if ( ALLOC_ARRAY( d->DeltaValue, count, FT_UShort ) )
+      return error;
+
+    if ( ACCESS_Frame( count * 2L ) )
+    {
+      FREE( d->DeltaValue );
+      return error;
+    }
+
+    dv = d->DeltaValue;
+
+    for ( n = 0; n < count; n++ )
+      dv[n] = GET_UShort();
+
+    FORGET_Frame();
+
+    return TT_Err_Ok;
+  }
+
+
+  void  Free_Device( TTO_Device*  d,
+		     FT_Memory    memory )
+  {
+    FREE( d->DeltaValue );
+  }
+
+
+  /* Since we have the delta values stored in compressed form, we must
+     uncompress it now.  To simplify the interface, the function always
+     returns a meaningful value in `value'; the error is just for
+     information.
+                                 |                |
+     format = 1: 0011223344556677|8899101112131415|...
+                                 |                |
+                      byte 1           byte 2
+
+       00: (byte >> 14) & mask
+       11: (byte >> 12) & mask
+       ...
+
+       mask = 0x0003
+                                 |                |
+     format = 2: 0000111122223333|4444555566667777|...
+                                 |                |
+                      byte 1           byte 2
+
+       0000: (byte >> 12) & mask
+       1111: (byte >>  8) & mask
+       ...
+
+       mask = 0x000F
+                                 |                |
+     format = 3: 0000000011111111|2222222233333333|...
+                                 |                |
+                      byte 1           byte 2
+
+       00000000: (byte >> 8) & mask
+       11111111: (byte >> 0) & mask
+       ....
+
+       mask = 0x00FF                                    */
+
+  FT_Error  Get_Device( TTO_Device*  d,
+                        FT_UShort    size,
+                        FT_Short*    value )
+  {
+    FT_UShort  byte, bits, mask, f, s;
+
+
+    f = d->DeltaFormat;
+
+    if ( size >= d->StartSize && size <= d->EndSize )
+    {
+      s    = size - d->StartSize;
+      byte = d->DeltaValue[s >> ( 4 - f )];
+      bits = byte >> ( 16 - ( ( s % ( 1 << ( 4 - f ) ) + 1 ) << f ) );
+      mask = 0xFFFF >> ( 16 - ( 1 << f ) );
+
+      *value = (FT_Short)( bits & mask );
+
+      /* conversion to a signed value */
+
+      if ( *value >= ( ( mask + 1 ) >> 1 ) )
+        *value -= mask + 1;
+
+      return TT_Err_Ok;
+    }
+    else
+    {
+      *value = 0;
+      return TTO_Err_Not_Covered;
+    }
+  }
+
+
+/* END */
diff --git a/src/ftxopen.h b/src/ftxopen.h
new file mode 100644
index 0000000..28dff90
--- /dev/null
+++ b/src/ftxopen.h
@@ -0,0 +1,308 @@
+/*******************************************************************
+ *
+ *  ftxopen.h
+ *
+ *    TrueType Open support.
+ *
+ *  Copyright 1996-2000 by
+ *  David Turner, Robert Wilhelm, and Werner Lemberg.
+ *
+ *  This file is part of the FreeType project, and may only be used
+ *  modified and distributed under the terms of the FreeType project
+ *  license, LICENSE.TXT.  By continuing to use, modify, or distribute
+ *  this file you indicate that you have read the license and
+ *  understand and accept it fully.
+ *
+ *  This file should be included by the application.  Nevertheless,
+ *  the table specific APIs (and structures) are located in files like
+ *  ftxgsub.h or ftxgpos.h; these header files are read by ftxopen.h .
+ *
+ ******************************************************************/
+
+#ifndef FTXOPEN_H
+#define FTXOPEN_H
+
+#include <freetype/freetype.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define EXPORT_DEF 
+#define EXPORT_FUNC
+
+#define TTO_MAX_NESTING_LEVEL  100
+
+#define TTO_Err_Invalid_SubTable_Format   0x1000
+#define TTO_Err_Invalid_SubTable          0x1001
+#define TTO_Err_Not_Covered               0x1002
+#define TTO_Err_Too_Many_Nested_Contexts  0x1003
+#define TTO_Err_No_MM_Interpreter         0x1004
+
+
+  /* Script list related structures */
+
+  struct  TTO_LangSys_
+  {
+    FT_UShort   LookupOrderOffset;      /* always 0 for TT Open 1.0  */
+    FT_UShort   ReqFeatureIndex;        /* required FeatureIndex     */
+    FT_UShort   FeatureCount;           /* number of Feature indices */
+    FT_UShort*  FeatureIndex;           /* array of Feature indices  */
+  };
+
+  typedef struct TTO_LangSys_  TTO_LangSys;
+
+
+  struct  TTO_LangSysRecord_
+  {
+    FT_ULong     LangSysTag;            /* LangSysTag identifier */
+    TTO_LangSys  LangSys;               /* LangSys table         */
+  };
+
+  typedef struct TTO_LangSysRecord_  TTO_LangSysRecord;
+
+
+  struct  TTO_Script_
+  {
+    TTO_LangSys         DefaultLangSys; /* DefaultLangSys table     */
+    FT_UShort           LangSysCount;   /* number of LangSysRecords */
+    TTO_LangSysRecord*  LangSysRecord;  /* array of LangSysRecords  */
+  };
+
+  typedef struct TTO_Script_  TTO_Script;
+
+
+  struct  TTO_ScriptRecord_
+  {
+    FT_ULong    ScriptTag;              /* ScriptTag identifier */
+    TTO_Script  Script;                 /* Script table         */
+  };
+
+  typedef struct TTO_ScriptRecord_  TTO_ScriptRecord;
+
+
+  struct  TTO_ScriptList_
+  {
+    FT_UShort          ScriptCount;     /* number of ScriptRecords */
+    TTO_ScriptRecord*  ScriptRecord;    /* array of ScriptRecords  */
+  };
+
+  typedef struct TTO_ScriptList_  TTO_ScriptList;
+
+
+  /* Feature list related structures */
+
+  struct TTO_Feature_
+  {
+    FT_UShort   FeatureParams;          /* always 0 for TT Open 1.0     */
+    FT_UShort   LookupListCount;        /* number of LookupList indices */
+    FT_UShort*  LookupListIndex;        /* array of LookupList indices  */
+  };
+
+  typedef struct TTO_Feature_  TTO_Feature;
+
+
+  struct  TTO_FeatureRecord_
+  {
+    FT_ULong     FeatureTag;            /* FeatureTag identifier */
+    TTO_Feature  Feature;               /* Feature table         */
+  };
+
+  typedef struct TTO_FeatureRecord_  TTO_FeatureRecord;
+
+
+  struct  TTO_FeatureList_
+  {
+    FT_UShort           FeatureCount;   /* number of FeatureRecords */
+    TTO_FeatureRecord*  FeatureRecord;  /* array of FeatureRecords  */
+  };
+
+  typedef struct TTO_FeatureList_  TTO_FeatureList;
+
+
+  /* Lookup list related structures */
+
+  struct  TTO_SubTable_;                /* defined below after inclusion
+                                           of ftxgsub.h and ftxgpos.h    */
+  typedef struct TTO_SubTable_  TTO_SubTable;
+
+
+  struct  TTO_Lookup_
+  {
+    FT_UShort      LookupType;          /* Lookup type         */
+    FT_UShort      LookupFlag;          /* Lookup qualifiers   */
+    FT_UShort      SubTableCount;       /* number of SubTables */
+    TTO_SubTable*  SubTable;            /* array of SubTables  */
+  };
+
+  typedef struct TTO_Lookup_  TTO_Lookup;
+
+
+  /* The `Properties' field is not defined in the TTO specification but
+     is needed for processing lookups.  If properties[n] is > 0, the
+     functions TT_GSUB_Apply_String() resp. TT_GPOS_Apply_String() will
+     process Lookup[n] for glyphs which have the specific bit not set in
+     the `properties' field of the input string object.                  */
+
+  struct  TTO_LookupList_
+  {
+    FT_UShort    LookupCount;           /* number of Lookups       */
+    TTO_Lookup*  Lookup;                /* array of Lookup records */
+    FT_UShort*   Properties;            /* array of flags          */
+  };
+
+  typedef struct TTO_LookupList_  TTO_LookupList;
+
+
+/* Possible LookupFlag bit masks.  `IGNORE_SPECIAL_MARKS' comes from the
+   OpenType 1.2 specification.                                           */
+
+#define IGNORE_BASE_GLYPHS    0x0002
+#define IGNORE_LIGATURES      0x0004
+#define IGNORE_MARKS          0x0008
+#define IGNORE_SPECIAL_MARKS  0xFF00
+
+
+  struct  TTO_CoverageFormat1_
+  {
+    FT_UShort   GlyphCount;             /* number of glyphs in GlyphArray */
+    FT_UShort*  GlyphArray;             /* array of glyph IDs             */
+  };
+
+  typedef struct TTO_CoverageFormat1_  TTO_CoverageFormat1;
+
+
+  struct TTO_RangeRecord_
+  {
+    FT_UShort  Start;                   /* first glyph ID in the range */
+    FT_UShort  End;                     /* last glyph ID in the range  */
+    FT_UShort  StartCoverageIndex;      /* coverage index of first
+                                           glyph ID in the range       */
+  };
+
+  typedef struct TTO_RangeRecord_  TTO_RangeRecord;
+
+
+  struct  TTO_CoverageFormat2_
+  {
+    FT_UShort         RangeCount;       /* number of RangeRecords */
+    TTO_RangeRecord*  RangeRecord;      /* array of RangeRecords  */
+  };
+
+  typedef struct TTO_CoverageFormat2_  TTO_CoverageFormat2;
+
+
+  struct  TTO_Coverage_
+  {
+    FT_UShort  CoverageFormat;          /* 1 or 2 */
+
+    union
+    {
+      TTO_CoverageFormat1  cf1;
+      TTO_CoverageFormat2  cf2;
+    } cf;
+  };
+
+  typedef struct TTO_Coverage_  TTO_Coverage;
+
+
+  struct  TTO_ClassDefFormat1_
+  {
+    FT_UShort   StartGlyph;             /* first glyph ID of the
+                                           ClassValueArray             */
+    FT_UShort   GlyphCount;             /* size of the ClassValueArray */
+    FT_UShort*  ClassValueArray;        /* array of class values       */
+  };
+
+  typedef struct TTO_ClassDefFormat1_  TTO_ClassDefFormat1;
+
+
+  struct  TTO_ClassRangeRecord_
+  {
+    FT_UShort  Start;                   /* first glyph ID in the range    */
+    FT_UShort  End;                     /* last glyph ID in the range     */
+    FT_UShort  Class;                   /* applied to all glyphs in range */
+  };
+
+  typedef struct TTO_ClassRangeRecord_  TTO_ClassRangeRecord;
+
+
+  struct  TTO_ClassDefFormat2_
+  {
+    FT_UShort              ClassRangeCount;
+                                        /* number of ClassRangeRecords */
+    TTO_ClassRangeRecord*  ClassRangeRecord;
+                                        /* array of ClassRangeRecords  */
+  };
+
+  typedef struct TTO_ClassDefFormat2_  TTO_ClassDefFormat2;
+
+
+  /* The `Defined' field is not defined in the TTO specification but
+     apparently needed for processing fonts like trado.ttf: This font
+     refers to a class which contains not a single element.  We map such
+     classes to class 0.                                                 */
+
+  struct  TTO_ClassDefinition_
+  {
+    FT_Bool    loaded;
+
+    FT_Bool*   Defined;                 /* array of Booleans.
+                                           If Defined[n] is FALSE,
+                                           class n contains no glyphs. */
+    FT_UShort  ClassFormat;             /* 1 or 2                      */
+
+    union
+    {
+      TTO_ClassDefFormat1  cd1;
+      TTO_ClassDefFormat2  cd2;
+    } cd;
+  };
+
+  typedef struct TTO_ClassDefinition_  TTO_ClassDefinition;
+
+
+  struct TTO_Device_
+  {
+    FT_UShort   StartSize;              /* smallest size to correct      */
+    FT_UShort   EndSize;                /* largest size to correct       */
+    FT_UShort   DeltaFormat;            /* DeltaValue array data format:
+                                           1, 2, or 3                    */
+    FT_UShort*  DeltaValue;             /* array of compressed data      */
+  };
+
+  typedef struct TTO_Device_  TTO_Device;
+
+
+#include "ftxgdef.h"
+#include "ftxgsub.h"
+#include "ftxgpos.h"
+
+
+  struct  TTO_SubTable_
+  {
+    union
+    {
+      TTO_GSUB_SubTable  gsub;
+      TTO_GPOS_SubTable  gpos;
+    } st;
+  };
+
+
+  enum  TTO_Type_
+  {
+    GSUB,
+    GPOS
+  };
+
+  typedef enum TTO_Type_  TTO_Type;
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FTXOPEN_H */
+
+
+/* END */
diff --git a/src/ftxopenf.h b/src/ftxopenf.h
new file mode 100644
index 0000000..bbb2c85
--- /dev/null
+++ b/src/ftxopenf.h
@@ -0,0 +1,161 @@
+/*******************************************************************
+ *
+ *  ftxopenf.h
+ *
+ *    internal TrueType Open functions
+ *
+ *  Copyright 1996-2000 by
+ *  David Turner, Robert Wilhelm, and Werner Lemberg.
+ *
+ *  This file is part of the FreeType project, and may only be used
+ *  modified and distributed under the terms of the FreeType project
+ *  license, LICENSE.TXT.  By continuing to use, modify, or distribute
+ *  this file you indicate that you have read the license and
+ *  understand and accept it fully.
+ *
+ ******************************************************************/
+
+#ifndef FTXOPENF_H
+#define FTXOPENF_H
+
+#include "ftxopen.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+  /* functions from ftxopen.c */
+
+  FT_Error  Load_ScriptList( TTO_ScriptList*  sl,
+			     FT_Stream     stream );
+  FT_Error  Load_FeatureList( TTO_FeatureList*  fl,
+                              FT_Stream         input );
+  FT_Error  Load_LookupList( TTO_LookupList*  ll,
+                             FT_Stream        input,
+                             TTO_Type         type );
+
+  FT_Error  Load_Coverage( TTO_Coverage*  c,
+                           FT_Stream      input );
+  FT_Error  Load_ClassDefinition( TTO_ClassDefinition*  cd,
+                                  FT_UShort             limit,
+                                  FT_Stream             input );
+  FT_Error  Load_Device( TTO_Device*  d,
+                         FT_Stream    input );
+
+  void  Free_ScriptList( TTO_ScriptList*  sl, 
+                         FT_Memory        memory );
+  void  Free_FeatureList( TTO_FeatureList*  fl,
+			  FT_Memory         memory );
+  void  Free_LookupList( TTO_LookupList*  ll,
+                         TTO_Type         type,
+			 FT_Memory        memory );
+
+  void  Free_Coverage( TTO_Coverage*  c,
+		       FT_Memory      memory );
+  void  Free_ClassDefinition( TTO_ClassDefinition*  cd,
+			      FT_Memory             memory );
+  void  Free_Device( TTO_Device*  d,
+		     FT_Memory    memory );
+
+
+  /* functions from ftxgsub.c */
+
+  FT_Error  Load_SingleSubst( TTO_SingleSubst*  ss,
+                              FT_Stream         input );
+  FT_Error  Load_MultipleSubst( TTO_MultipleSubst*  ms,
+                                FT_Stream           input );
+  FT_Error  Load_AlternateSubst( TTO_AlternateSubst*  as,
+                                 FT_Stream            input );
+  FT_Error  Load_LigatureSubst( TTO_LigatureSubst*  ls,
+                                FT_Stream           input );
+  FT_Error  Load_ContextSubst( TTO_ContextSubst*  cs,
+                               FT_Stream          input );
+  FT_Error  Load_ChainContextSubst( TTO_ChainContextSubst*  ccs,
+                                    FT_Stream               input );
+
+  void  Free_SingleSubst( TTO_SingleSubst*  ss,
+			  FT_Memory         memory );
+  void  Free_MultipleSubst( TTO_MultipleSubst*  ms,
+			    FT_Memory         memory );
+  void  Free_AlternateSubst( TTO_AlternateSubst*  as,
+			     FT_Memory            memory );
+  void  Free_LigatureSubst( TTO_LigatureSubst*  ls,
+			    FT_Memory           memory );
+  void  Free_ContextSubst( TTO_ContextSubst*  cs,
+			   FT_Memory         memory );
+  void  Free_ChainContextSubst( TTO_ChainContextSubst*  ccs,
+				FT_Memory               memory );
+
+
+  /* functions from ftxgpos.c */
+
+  FT_Error  Load_SinglePos( TTO_SinglePos*  sp,
+                            FT_Stream       input );
+  FT_Error  Load_PairPos( TTO_PairPos*  pp,
+                          FT_Stream     input );
+  FT_Error  Load_CursivePos( TTO_CursivePos*  cp,
+                             FT_Stream        input );
+  FT_Error  Load_MarkBasePos( TTO_MarkBasePos*  mbp,
+                              FT_Stream         input );
+  FT_Error  Load_MarkLigPos( TTO_MarkLigPos*  mlp,
+                             FT_Stream        input );
+  FT_Error  Load_MarkMarkPos( TTO_MarkMarkPos*  mmp,
+                              FT_Stream         input );
+  FT_Error  Load_ContextPos( TTO_ContextPos*  cp,
+                             FT_Stream        input );
+  FT_Error  Load_ChainContextPos( TTO_ChainContextPos*  ccp,
+                                  FT_Stream             input );
+
+  void  Free_SinglePos( TTO_SinglePos*  sp,
+			FT_Memory       memory );
+  void  Free_PairPos( TTO_PairPos*  pp,
+		      FT_Memory     memory );
+  void  Free_CursivePos( TTO_CursivePos*  cp,
+   		         FT_Memory     memory );
+  void  Free_MarkBasePos( TTO_MarkBasePos*  mbp,
+			  FT_Memory         memory );
+  void  Free_MarkLigPos( TTO_MarkLigPos*  mlp,
+			 FT_Memory        memory );
+  void  Free_MarkMarkPos( TTO_MarkMarkPos*  mmp,
+			  FT_Memory         memory );
+  void  Free_ContextPos( TTO_ContextPos*  cp,
+			 FT_Memory         memory );
+  void  Free_ChainContextPos( TTO_ChainContextPos*  ccp,
+			      FT_Memory             memory );
+  /* query functions */
+
+  FT_Error  Coverage_Index( TTO_Coverage*  c,
+                            FT_UShort      glyphID,
+                            FT_UShort*     index );
+  FT_Error  Get_Class( TTO_ClassDefinition*  cd,
+                       FT_UShort             glyphID,
+                       FT_UShort*            class,
+                       FT_UShort*            index );
+  FT_Error  Get_Device( TTO_Device*  d,
+                        FT_UShort    size,
+                        FT_Short*    value );
+
+
+  /* functions from ftxgdef.c */
+
+  FT_Error  Add_Glyph_Property( TTO_GDEFHeader*  gdef,
+                                FT_UShort        glyphID,
+                                FT_UShort        property );
+
+  FT_Error  Check_Property( TTO_GDEFHeader*  gdef,
+                            FT_UShort        index,
+                            FT_UShort        flags,
+                            FT_UShort*       property );
+
+#define CHECK_Property( gdef, index, flags, property )              \
+          ( ( error = Check_Property( (gdef), (index), (flags),     \
+                                      (property) ) ) != TT_Err_Ok )
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FTXOPENF_H */
+
+
+/* END */
diff --git a/src/ottest.c b/src/ottest.c
new file mode 100644
index 0000000..c6338e9
--- /dev/null
+++ b/src/ottest.c
@@ -0,0 +1,265 @@
+/* Pango
+ * otttest.c: Test program for OpenType
+ *
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "ftxopen.h"
+#include <freetype/internal/ftmemory.h>
+
+#include "disasm.h"
+
+#define N_ELEMENTS(arr) (sizeof(arr)/ sizeof((arr)[0]))
+
+int
+croak (const char *situation, FT_Error error)
+{
+  fprintf (stderr, "%s: Error %d\n", situation, error);
+
+  exit (1);
+}
+
+enum {
+  I = 1 << 0,
+  M = 1 << 1,
+  F = 1 << 2,
+  L = 1 << 3
+};
+
+void
+print_tag (FT_ULong tag)
+{
+  fprintf (stderr, "%c%c%c%c", 
+	  (unsigned char)(tag >> 24),
+	  (unsigned char)((tag & 0xff0000) >> 16),
+	  (unsigned char)((tag & 0xff00) >> 8),
+	  (unsigned char)(tag & 0xff));
+}
+
+void
+maybe_add_feature (TTO_GSUB  gsub,
+		   FT_UShort script_index,
+		   FT_ULong  tag,
+		   FT_UShort property)
+{
+  FT_Error error;
+  FT_UShort feature_index;
+  
+  /* 0xffff == default language system */
+  error = TT_GSUB_Select_Feature (gsub, tag, script_index, 0xffff, &feature_index);
+  
+  if (error)
+    {
+      if (error == TTO_Err_Not_Covered)
+	{
+	  print_tag (tag);
+	  fprintf (stderr, " not covered, ignored\n");
+	  return;
+	}
+
+      croak ("TT_GSUB_Select_Feature", error);
+    }
+
+  if ((error = TT_GSUB_Add_Feature (gsub, feature_index, property)))
+    croak ("TT_GSUB_Add_Feature", error);
+}
+
+void
+select_cmap (FT_Face face)
+{
+  FT_UShort  i;
+  FT_CharMap cmap = NULL;
+  
+  for (i = 0; i < face->num_charmaps; i++)
+    {
+      if (face->charmaps[i]->platform_id == 3 && face->charmaps[i]->encoding_id == 1)
+	{
+	  cmap = face->charmaps[i];
+	  break;
+	}
+    }
+  
+  /* we try only pid/eid (0,0) if no (3,1) map is found -- many Windows
+     fonts have only rudimentary (0,0) support.                         */
+
+  if (!cmap)
+    for (i = 0; i < face->num_charmaps; i++)
+      {
+	if (face->charmaps[i]->platform_id == 3 && face->charmaps[i]->encoding_id == 1)
+	  {
+	    cmap = face->charmaps[i];
+	    break;
+	  }
+      }
+
+  if (cmap)
+    FT_Set_Charmap (face, cmap);
+  else
+    {
+      fprintf (stderr, "Sorry, but this font doesn't contain"
+	       " any Unicode mapping table.\n");
+      exit (1);
+    }
+}
+
+void
+add_features (TTO_GSUB gsub)
+{
+  FT_Error error;
+  FT_ULong tag = FT_MAKE_TAG ('a', 'r', 'a', 'b');
+  FT_UShort script_index;
+
+  error = TT_GSUB_Select_Script (gsub, tag, &script_index);
+
+  if (error)
+    {
+      if (error == TTO_Err_Not_Covered)
+	{
+	  fprintf (stderr, "Arabic not covered, no features used\n");
+	  return;
+	}
+
+      croak ("TT_GSUB_Select_Script", error);
+    }
+
+  maybe_add_feature (gsub, script_index, FT_MAKE_TAG ('i', 'n', 'i', 't'), I);
+  maybe_add_feature (gsub, script_index, FT_MAKE_TAG ('m', 'e', 'd', 'i'), M);
+  maybe_add_feature (gsub, script_index, FT_MAKE_TAG ('f', 'i', 'n', 'a'), F);
+  maybe_add_feature (gsub, script_index, FT_MAKE_TAG ('l', 'i', 'g', 'a'), L);
+}
+
+void 
+dump_string (TTO_GSUB_String *str)
+{
+  int i;
+
+  fprintf (stderr, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+  for (i = 0; i < str->length; i++)
+    {
+      fprintf (stderr, "%2d: %#06x %#06x %4d %4d\n",
+	       i,
+	       str->string[i],
+	       str->properties[i],
+	       str->components[i],
+	       str->ligIDs[i]);
+    }
+  fprintf (stderr, "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+}
+
+FT_UShort arabic_str[]   = { 0x645, 0x643, 0x64a, 0x644, 0x639, 0x20, 0x645, 0x627, 0x644, 0x633, 0x644, 0x627 };
+FT_UShort arabic_props[] = { I|L,   M|L,   M|L,   M|L,   M|L,   F|L,   I|L,  M|L,   M|L,   M|L,   M|L,   F|L };
+
+void
+try_string (FT_Library library,
+	    FT_Face    face,
+	    TTO_GSUB   gsub)
+{
+  FT_Error error;
+  TTO_GSUB_String *in_str;
+  TTO_GSUB_String *out_str;
+  int i;
+
+  if ((error = TT_GSUB_String_New (face->memory, &in_str)))
+    croak ("TT_GSUB_String_New", error);
+  if ((error = TT_GSUB_String_New (face->memory, &out_str)))
+    croak ("TT_GSUB_String_New", error);
+
+  if ((error = TT_GSUB_String_Set_Length (in_str, N_ELEMENTS (arabic_str))))
+    croak ("TT_GSUB_String_Set_Length", error);
+
+  for (i=0; i < N_ELEMENTS (arabic_str); i++)
+    {
+      in_str->string[i] = FT_Get_Char_Index (face, arabic_str[i]);
+      in_str->properties[i] = arabic_props[i];
+      in_str->components[i] = i;
+      in_str->ligIDs[i] = i;
+    }
+
+  if ((error = TT_GSUB_Apply_String (gsub, in_str, out_str)))
+    croak ("TT_GSUB_Apply_String", error);
+
+  dump_string (in_str);
+  dump_string (out_str);
+
+  if ((error = TT_GSUB_String_Done (in_str)))
+    croak ("TT_GSUB_String_New", error);
+  if ((error = TT_GSUB_String_Done (out_str)))
+    croak ("TT_GSUB_String_New", error);
+}
+
+int 
+main (int argc, char **argv)
+{
+  FT_Error error;
+  FT_Library library;
+  FT_Face face;
+  TTO_GSUB gsub;
+  TTO_GPOS gpos;
+
+  if (argc != 2)
+    {
+      fprintf (stderr, "Usage: ottest MYFONT.TTF\n");
+      exit(1);
+    }
+
+  if ((error = FT_Init_FreeType (&library)))
+    croak ("FT_Init_FreeType", error);
+
+  if ((error = FT_New_Face (library, argv[1], 0, &face)))
+    croak ("FT_New_Face", error);
+
+  if (!(error = TT_Load_GSUB_Table (face, &gsub, NULL)))
+    {
+      TT_Dump_GSUB_Table (gsub, stdout);
+      
+      if ((error = TT_Done_GSUB_Table (gsub)))
+	croak ("FT_Done_GSUB_Table", error);
+    }
+  else
+    fprintf (stderr, "TT_Load_GSUB_Table %d\n", error);
+
+  if (!(error = TT_Load_GPOS_Table (face, &gpos, NULL)))
+    {
+      TT_Dump_GPOS_Table (gpos, stdout);
+      
+      if ((error = TT_Done_GPOS_Table (gpos)))
+	croak ("FT_Done_GPOS_Table", error);
+    }
+  else
+    fprintf (stderr, "TT_Load_GPOS_Table %d\n", error);
+
+#if 0  
+  select_cmap (face);
+
+  add_features (gsub);
+  try_string (library, face, gsub);
+#endif
+
+
+  if ((error = FT_Done_Face (face)))
+    croak ("FT_Done_Face", error);
+
+  if ((error = FT_Done_FreeType (library)))
+    croak ("FT_Done_FreeType", error);
+  
+  return 0;
+}
+
diff --git a/src/pango-ot-info.c b/src/pango-ot-info.c
new file mode 100644
index 0000000..b426e32
--- /dev/null
+++ b/src/pango-ot-info.c
@@ -0,0 +1,438 @@
+/* Pango
+ * pango-ot-info.c: Store tables for OpenType
+ *
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "pango-ot-private.h"
+#include <freetype/internal/tterrors.h>
+#include <freetype/internal/ftobjs.h>
+#include <freetype/ftmodule.h>
+
+static void pango_ot_info_class_init (GObjectClass *object_class);
+static void pango_ot_info_finalize   (GObject *object);
+
+static GObjectClass *parent_class;
+
+enum
+{
+  INFO_LOADED_GDEF = 1 << 0,
+  INFO_LOADED_GSUB = 1 << 1,
+  INFO_LOADED_GPOS = 1 << 2
+};
+
+GType
+pango_ot_info_get_type (void)
+{
+  static GType object_type = 0;
+
+  if (!object_type)
+    {
+      static const GTypeInfo object_info =
+      {
+        sizeof (PangoOTInfoClass),
+        (GBaseInitFunc) NULL,
+        (GBaseFinalizeFunc) NULL,
+        (GClassInitFunc)pango_ot_info_class_init,
+        NULL,           /* class_finalize */
+        NULL,           /* class_data */
+        sizeof (PangoOTInfo),
+        0,              /* n_preallocs */
+	NULL            /* init */
+      };
+      
+      object_type = g_type_register_static (G_TYPE_OBJECT,
+                                            "PangoOTInfo",
+                                            &object_info, 0);
+    }
+  
+  return object_type;
+}
+
+static void 
+pango_ot_info_class_init (GObjectClass *object_class)
+{
+  parent_class = g_type_class_peek_parent (object_class);
+  
+  object_class->finalize = pango_ot_info_finalize;
+}
+
+static void 
+pango_ot_info_finalize (GObject *object)
+{
+  PangoOTInfo *info = PANGO_OT_INFO (object);
+  
+  if (info->gdef)
+    {
+      TT_Done_GDEF_Table (info->gdef);
+      info->gdef = NULL;
+    }
+  if (info->gsub)
+    {
+      TT_Done_GSUB_Table (info->gsub);
+      info->gsub = NULL;
+    }
+  if (info->gpos)
+    {
+      TT_Done_GPOS_Table (info->gpos);
+      info->gpos = NULL;
+    }
+}
+
+PangoOTInfo *
+pango_ot_info_new (FT_Face face)
+{
+  PangoOTInfo *info;
+
+  info = g_object_new (PANGO_TYPE_OT_INFO, NULL);
+
+  info->face = face;
+
+  return info;
+}
+
+/* There must be be a better way to do this
+ */
+static gboolean
+is_truetype (FT_Face face)
+{
+  return strcmp (FT_MODULE_CLASS (face->driver)->module_name, "truetype") == 0;
+}
+
+TTO_GDEF 
+pango_ot_info_get_gdef (PangoOTInfo *info)
+{
+  g_return_val_if_fail (PANGO_IS_OT_INFO (info), NULL);
+
+  if (!(info->loaded & INFO_LOADED_GDEF))
+    {
+      FT_Error error;
+      
+      info->loaded |= INFO_LOADED_GDEF;
+
+      if (is_truetype (info->face))
+	{
+	  error = TT_Load_GDEF_Table (info->face, &info->gdef);
+	  
+	  if (error && error != TT_Err_Table_Missing)
+	    g_warning ("Error loading GDEF table %d", error);
+	}
+    }
+
+  return info->gdef;
+}
+
+TTO_GSUB
+pango_ot_info_get_gsub (PangoOTInfo *info)
+{
+  g_return_val_if_fail (PANGO_IS_OT_INFO (info), NULL);
+  
+  if (!(info->loaded & INFO_LOADED_GSUB))
+    {
+      FT_Error error;
+      TTO_GDEF gdef = pango_ot_info_get_gdef (info);
+      
+      info->loaded |= INFO_LOADED_GSUB;
+
+      if (is_truetype (info->face))
+	{
+	  error = TT_Load_GSUB_Table (info->face, &info->gsub, gdef);
+
+	  if (error && error != TT_Err_Table_Missing)
+	    g_warning ("Error loading GSUB table %d", error);
+	}
+    }
+  
+  return info->gsub;
+}
+
+TTO_GPOS
+pango_ot_info_get_gpos (PangoOTInfo *info)
+{
+  g_return_val_if_fail (PANGO_IS_OT_INFO (info), NULL);
+  
+  if (!(info->loaded & INFO_LOADED_GPOS))
+    {
+      FT_Error error;
+      TTO_GDEF gdef = pango_ot_info_get_gdef (info);
+
+      info->loaded |= INFO_LOADED_GPOS;
+
+      if (is_truetype (info->face))
+	{
+	  error = TT_Load_GPOS_Table (info->face, &info->gpos, gdef);
+
+	  if (error && error != TT_Err_Table_Missing)
+	    g_warning ("Error loading GPOS table %d", error);
+	}
+    }
+
+  return info->gpos;
+}
+
+static gboolean
+get_tables (PangoOTInfo      *info,
+	    PangoOTTableType  table_type,
+	    TTO_ScriptList  **script_list,
+	    TTO_FeatureList **feature_list)
+{
+  if (table_type == PANGO_OT_TABLE_GSUB)
+    {
+      TTO_GSUB gsub = pango_ot_info_get_gsub (info);
+
+      if (!gsub)
+	return FALSE;
+      else
+	{
+	  if (script_list)
+	    *script_list = &gsub->ScriptList;
+	  if (feature_list)
+	    *feature_list = &gsub->FeatureList;
+	  return TRUE;
+	}
+    }
+  else
+    {
+      TTO_GPOS gpos = pango_ot_info_get_gpos (info);
+
+      if (!gpos)
+	return FALSE;
+      else
+	{
+	  if (script_list)
+	    *script_list = &gpos->ScriptList;
+	  if (feature_list)
+	    *feature_list = &gpos->FeatureList;
+	  return TRUE;
+	}
+    }
+}
+
+gboolean 
+pango_ot_info_find_script (PangoOTInfo      *info,
+			   PangoOTTableType  table_type,
+			   PangoOTTag        script_tag,
+			   guint            *script_index)
+{
+  TTO_ScriptList *script_list;
+  int i;
+
+  g_return_val_if_fail (PANGO_IS_OT_INFO (info), FALSE);
+
+  if (!get_tables (info, table_type, &script_list, NULL))
+    return FALSE;
+
+  for (i=0; i < script_list->ScriptCount; i++)
+    {
+      if (script_list->ScriptRecord[i].ScriptTag == script_tag)
+	{
+	  if (script_index)
+	    *script_index = i;
+
+	  return TRUE;
+	}
+    }
+
+  return FALSE;
+}
+
+gboolean
+pango_ot_info_find_language (PangoOTInfo      *info,
+			     PangoOTTableType  table_type,
+			     guint             script_index,
+			     PangoOTTag        language_tag,
+			     guint            *language_index,
+			     guint            *required_feature_index)
+{
+  TTO_ScriptList *script_list;
+  TTO_Script *script;
+  int i;
+
+  g_return_val_if_fail (PANGO_IS_OT_INFO (info), FALSE);
+
+  if (!get_tables (info, table_type, &script_list, NULL))
+    return FALSE;
+
+  g_return_val_if_fail (script_index < script_list->ScriptCount, FALSE);
+
+  script = &script_list->ScriptRecord[script_index].Script;
+
+  for (i = 0; i < script->LangSysCount; i++)
+    {
+      if (script->LangSysRecord[i].LangSysTag == language_tag)
+	{
+	  if (language_index)
+	    *language_index = i;
+	  if (required_feature_index)
+	    *required_feature_index = script->LangSysRecord[i].LangSys.ReqFeatureIndex;
+	  return TRUE;
+	}
+    }
+
+  return FALSE;
+}
+
+gboolean
+pango_ot_info_find_feature  (PangoOTInfo      *info,
+			     PangoOTTableType  table_type,
+			     PangoOTTag        feature_tag,
+			     guint             script_index,
+			     guint             language_index,
+			     guint            *feature_index)
+{
+  TTO_ScriptList *script_list;
+  TTO_FeatureList *feature_list;
+  TTO_Script *script;
+  TTO_LangSys *lang_sys;
+
+  int i;
+
+  g_return_val_if_fail (PANGO_IS_OT_INFO (info), FALSE);
+
+  if (!get_tables (info, table_type, &script_list, &feature_list))
+    return FALSE;
+
+  g_return_val_if_fail (script_index < script_list->ScriptCount, FALSE);
+
+  script = &script_list->ScriptRecord[script_index].Script;
+
+  if (language_index == 0xffff)
+    lang_sys = &script->DefaultLangSys;
+  else
+    {
+      g_return_val_if_fail (language_index < script->LangSysCount, FALSE);
+      lang_sys = &script->LangSysRecord[language_index].LangSys;
+    }
+
+  for (i = 0; i < lang_sys->FeatureCount; i++)
+    {
+      FT_UShort index = lang_sys->FeatureIndex[i];
+
+      if (feature_list->FeatureRecord[index].FeatureTag == feature_tag)
+	{
+	  if (feature_index)
+	    *feature_index = index;
+
+	  return TRUE;
+	}
+    }
+
+  return FALSE;
+}
+
+PangoOTTag *
+pango_ot_info_list_scripts (PangoOTInfo      *info,
+			    PangoOTTableType  table_type)
+{
+  PangoOTTag *result;
+  TTO_ScriptList *script_list;
+  int i;
+
+  g_return_val_if_fail (PANGO_IS_OT_INFO (info), NULL);
+
+  if (!get_tables (info, table_type, &script_list, NULL))
+    return NULL;
+
+  result = g_new (PangoOTTag, script_list->ScriptCount + 1);
+  
+  for (i=0; i < script_list->ScriptCount; i++)
+    result[i] = script_list->ScriptRecord[i].ScriptTag;
+
+  result[i] = 0;
+
+  return result;
+}
+
+PangoOTTag *
+pango_ot_info_list_languages (PangoOTInfo      *info,
+			      PangoOTTableType  table_type,
+			      guint             script_index,
+			      PangoOTTag        language_tag)
+{
+  PangoOTTag *result;
+  TTO_ScriptList *script_list;
+  TTO_Script *script;
+  int i;
+
+  g_return_val_if_fail (PANGO_IS_OT_INFO (info), NULL);
+
+  if (!get_tables (info, table_type, &script_list, NULL))
+    return NULL;
+
+  g_return_val_if_fail (script_index < script_list->ScriptCount, NULL);
+
+  script = &script_list->ScriptRecord[script_index].Script;
+  
+  result = g_new (PangoOTTag, script->LangSysCount + 1);
+  
+  for (i = 0; i < script->LangSysCount; i++)
+    result[i] = script->LangSysRecord[i].LangSysTag;
+
+  result[i] = 0;
+
+  return result;
+}
+
+PangoOTTag *
+pango_ot_info_list_features  (PangoOTInfo      *info,
+			      PangoOTTableType  table_type,
+			      PangoOTTag        tag,
+			      guint             script_index,
+			      guint             language_index)
+{
+  PangoOTTag *result;
+
+  TTO_ScriptList *script_list;
+  TTO_FeatureList *feature_list;
+  TTO_Script *script;
+  TTO_LangSys *lang_sys;
+
+  int i;
+
+  g_return_val_if_fail (PANGO_IS_OT_INFO (info), NULL);
+
+  if (!get_tables (info, table_type, &script_list, &feature_list))
+    return NULL;
+
+  g_return_val_if_fail (script_index < script_list->ScriptCount, NULL);
+
+  script = &script_list->ScriptRecord[script_index].Script;
+
+  if (language_index == 0xffff)
+    lang_sys = &script->DefaultLangSys;
+  else
+    {
+      g_return_val_if_fail (language_index < script->LangSysCount, NULL);
+      lang_sys = &script->LangSysRecord[language_index].LangSys;
+    }
+
+  result = g_new (PangoOTTag, lang_sys->FeatureCount + 1);
+  
+  for (i = 0; i < lang_sys->FeatureCount; i++)
+    {
+      FT_UShort index = lang_sys->FeatureIndex[i];
+
+      result[i] = feature_list->FeatureRecord[index].FeatureTag;
+    }
+
+  result[i] = 0;
+
+  return result;
+}
+
+
diff --git a/src/pango-ot-private.h b/src/pango-ot-private.h
new file mode 100644
index 0000000..782a7dd
--- /dev/null
+++ b/src/pango-ot-private.h
@@ -0,0 +1,98 @@
+/* Pango
+ * pango-ot-private.h: Implementation details for Pango OpenType code
+ *
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PANGO_OT_PRIVATE_H__
+#define __PANGO_OT_PRIVATE_H__
+
+#include <freetype/freetype.h>
+
+#include <glib-object.h>
+
+#include <pango/pango-ot.h>
+#include "ftxopen.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define PANGO_TYPE_OT_INFO              (pango_ot_info_get_type ())
+#define PANGO_OT_INFO(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), PANGO_TYPE_OT_INFO, PangoOTInfo))
+#define PANGO_OT_INFO_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), PANGO_TYPE_OT_INFO, PangoOTInfoClass))
+#define PANGO_IS_OT_INFO(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), PANGO_TYPE_OT_INFO))
+#define PANGO_IS_OT_INFO_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), PANGO_TYPE_OT_INFO))
+#define PANGO_OT_INFO_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), PANGO_TYPE_OT_INFO, PangoOTInfoClass))
+
+typedef struct _PangoOTInfoClass PangoOTInfoClass;
+
+struct _PangoOTInfo 
+{
+  GObject parent_instance;
+
+  guint loaded;
+
+  FT_Face face;
+
+  TTO_GSUB gsub;
+  TTO_GDEF gdef;
+  TTO_GPOS gpos;
+};
+
+struct _PangoOTInfoClass
+{
+  GObjectClass parent_class;
+};
+
+#define PANGO_TYPE_OT_RULESET              (pango_ot_ruleset_get_type ())
+#define PANGO_OT_RULESET(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), PANGO_TYPE_OT_RULESET, PangoOTRuleset))
+#define PANGO_OT_RULESET_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), PANGO_TYPE_OT_RULESET, PangoOTRulesetClass))f
+#define PANGO_OT_IS_RULESET(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), PANGO_TYPE_OT_RULESET))
+#define PANGO_OT_IS_RULESET_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), PANGO_TYPE_OT_RULESET))
+#define PANGO_OT_RULESET_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), PANGO_TYPE_OT_RULESET, PangoOTRulesetClass))
+
+typedef struct _PangoOTRulesetClass PangoOTRulesetClass;
+
+struct _PangoOTRuleset
+{
+  GObject parent_instance;
+
+  GArray *rules;
+  PangoOTInfo *info;
+};
+
+struct _PangoOTRulesetClass
+{
+  GObjectClass parent_class;
+};
+
+GType pango_ot_info_get_type (void);
+
+TTO_GDEF pango_ot_info_get_gdef (PangoOTInfo *info);
+TTO_GSUB pango_ot_info_get_gsub (PangoOTInfo *info);
+TTO_GPOS pango_ot_info_get_gpos (PangoOTInfo *info);
+
+GType pango_ot_ruleset_get_type (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* __PANGO_OT_PRIVATE_H__ */
diff --git a/src/pango-ot-ruleset.c b/src/pango-ot-ruleset.c
new file mode 100644
index 0000000..79245b5
--- /dev/null
+++ b/src/pango-ot-ruleset.c
@@ -0,0 +1,232 @@
+/* Pango
+ * pango-ot-ruleset.c: Shaping using OpenType features
+ *
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <freetype/internal/ftmemory.h>
+
+#include <pango/pango-ot.h>
+#include "pango-ot-private.h"
+
+typedef struct _PangoOTRule PangoOTRule;
+
+struct _PangoOTRule 
+{
+  gulong     property_bit;
+  FT_UShort  feature_index;
+  guint      table_type : 1;
+};
+
+static void pango_ot_ruleset_class_init (GObjectClass   *object_class);
+static void pango_ot_ruleset_init       (PangoOTRuleset *ruleset);
+static void pango_ot_ruleset_finalize   (GObject        *object);
+
+static GObjectClass *parent_class;
+
+GType
+pango_ot_ruleset_get_type (void)
+{
+  static GType object_type = 0;
+
+  if (!object_type)
+    {
+      static const GTypeInfo object_info =
+      {
+        sizeof (PangoOTRulesetClass),
+        (GBaseInitFunc) NULL,
+        (GBaseFinalizeFunc) NULL,
+        (GClassInitFunc)pango_ot_ruleset_class_init,
+        NULL,           /* class_finalize */
+        NULL,           /* class_data */
+        sizeof (PangoOTRuleset),
+        0,              /* n_preallocs */
+        (GInstanceInitFunc)pango_ot_ruleset_init,
+      };
+      
+      object_type = g_type_register_static (G_TYPE_OBJECT,
+                                            "PangoOTRuleset",
+                                            &object_info, 0);
+    }
+  
+  return object_type;
+}
+
+static void 
+pango_ot_ruleset_class_init (GObjectClass *object_class)
+{
+  parent_class = g_type_class_peek_parent (object_class);
+  
+  object_class->finalize = pango_ot_ruleset_finalize;
+}
+
+static void 
+pango_ot_ruleset_init (PangoOTRuleset *ruleset)
+{
+  ruleset->rules = g_array_new (FALSE, FALSE, sizeof (PangoOTRule));
+}
+
+static void 
+pango_ot_ruleset_finalize (GObject *object)
+{
+  PangoOTRuleset *ruleset = PANGO_OT_RULESET (object);
+
+  g_array_free (ruleset->rules, TRUE);
+  g_object_unref (G_OBJECT (ruleset->info));
+}
+
+PangoOTRuleset *
+pango_ot_ruleset_new (PangoOTInfo *info)
+{
+  PangoOTRuleset *ruleset;
+
+  ruleset = g_object_new (PANGO_TYPE_OT_RULESET, NULL);
+
+  ruleset->info = g_object_ref (G_OBJECT (info));
+
+  return ruleset;
+}
+
+void
+pango_ot_ruleset_add_feature (PangoOTRuleset   *ruleset,
+			      PangoOTTableType  table_type,
+			      guint             feature_index,
+			      gulong            property_bit)
+{
+  PangoOTRule tmp_rule;
+
+  g_return_if_fail (PANGO_OT_IS_RULESET (ruleset));
+
+  tmp_rule.table_type = table_type;
+  tmp_rule.feature_index = feature_index;
+  tmp_rule.property_bit = property_bit;
+
+  g_array_append_val (ruleset->rules, tmp_rule);
+}
+
+void
+pango_ot_ruleset_shape (PangoOTRuleset   *ruleset,
+			PangoGlyphString *glyphs,
+			gulong           *properties)
+{
+  int i;
+  int last_cluster;
+  
+  TTO_GSUB gsub = NULL;
+  TTO_GPOS gpos = NULL;
+  
+  TTO_GSUB_String *in_string = NULL;
+  TTO_GSUB_String *out_string = NULL;
+  TTO_GSUB_String *result_string = NULL;
+  TTO_GPOS_Data *pos_data;
+
+  gboolean need_gsub = FALSE;
+  gboolean need_gpos = FALSE;
+
+  g_return_if_fail (PANGO_OT_IS_RULESET (ruleset));
+
+  for (i = 0; i < ruleset->rules->len; i++)
+    {
+      PangoOTRule *rule = &g_array_index (ruleset->rules, PangoOTRule, i);
+
+      if (rule->table_type == PANGO_OT_TABLE_GSUB)
+	need_gsub = TRUE;
+      else 
+	need_gpos = TRUE;
+    }
+
+  if (need_gsub)
+    {
+      gsub = pango_ot_info_get_gsub (ruleset->info);
+
+      if (gsub)
+	TT_GSUB_Clear_Features (gsub);
+    }
+
+  if (need_gpos)
+    {
+      gpos = pango_ot_info_get_gpos (ruleset->info);
+
+      if (gpos)
+	TT_GPOS_Clear_Features (gpos);
+    }
+
+  for (i = 0; i < ruleset->rules->len; i++)
+    {
+      PangoOTRule *rule = &g_array_index (ruleset->rules, PangoOTRule, i);
+
+      if (rule->table_type == PANGO_OT_TABLE_GSUB)
+	{
+	  if (gsub)
+	    TT_GSUB_Add_Feature (gsub, rule->feature_index, rule->property_bit);
+	}
+      else
+	{
+	  if (gpos)
+	    TT_GPOS_Add_Feature (gpos, rule->feature_index, rule->property_bit);
+	}
+    }
+
+  if (!gsub && !gpos)
+    return;
+
+  g_assert (TT_GSUB_String_New (ruleset->info->face->memory,
+				&in_string) == FT_Err_Ok);
+  g_assert (TT_GSUB_String_Set_Length (in_string, glyphs->num_glyphs) == FT_Err_Ok);
+
+  for (i = 0; i < glyphs->num_glyphs; i++)
+    {
+      in_string->string[i] = glyphs->glyphs[i].glyph;
+      in_string->properties[i] = properties[i];
+      in_string->logClusters[i] = glyphs->log_clusters[i];
+    }
+  in_string->max_ligID = i;
+  
+  if (gsub)
+    {
+      g_assert (TT_GSUB_String_New (ruleset->info->face->memory,
+				    &out_string) == FT_Err_Ok);
+      result_string = out_string;
+
+      TT_GSUB_Apply_String (gsub, in_string, out_string);
+    }
+  else
+    result_string = in_string;
+  
+  pango_glyph_string_set_size (glyphs, result_string->length);
+
+  last_cluster = -1;
+  for (i = 0; i < result_string->length; i++)
+    {
+      glyphs->glyphs[i].glyph = result_string->string[i];
+      glyphs->glyphs[i].glyph = result_string->string[i];
+
+      glyphs->log_clusters[i] = result_string->logClusters[i];
+      if (glyphs->log_clusters[i] != last_cluster)
+	glyphs->glyphs[i].attr.is_cluster_start = 1;
+      else
+	glyphs->glyphs[i].attr.is_cluster_start = 0;
+
+      last_cluster = glyphs->log_clusters[i];
+    }
+
+  if (in_string)
+    TT_GSUB_String_Done (in_string);
+  if (out_string)
+    TT_GSUB_String_Done (out_string);
+}