blob: 1cf107aea958a097284c8c42f628dfeae54d23b1 [file] [log] [blame]
/*
* ot-info.c: Store tables for OpenType
*
* Copyright (C) 2003 Red Hat K.K.
* 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 "ot-info.h"
#include "ot-ruleset.h"
#include "ot-unicode.h"
#include "fterrcompat.h"
#include FT_INTERNAL_OBJECTS_H
#include FT_MODULE_H
#define noINFO_DEBUG_MEMORY
enum
{
INFO_LOADED_GDEF = 1 << 0,
INFO_LOADED_GSUB = 1 << 1,
INFO_LOADED_GPOS = 1 << 2
};
static FT_Error ot_table_check( FT_Face face );
OTInfo *
ot_info_new ( FT_Face face )
{
FT_Error error;
OTInfo *info;
FT_Memory memory;
if ( ot_table_check( face ) )
return NULL;
memory = FT_FACE_MEMORY( face );
if ( FT_NEW ( info ) )
return NULL;
ot_info_setup (info);
return info;
}
static FT_Error
ot_table_check( FT_Face face )
{
FT_Stream stream = face->stream;
TT_Face tt_face = (TT_Face)face;
FT_Error error_gdef, error_gpos, error_gsub;
error_gdef = tt_face->goto_table( tt_face, TTAG_GDEF, stream, 0 );
error_gsub = tt_face->goto_table( tt_face, TTAG_GSUB, stream, 0 );
error_gpos = tt_face->goto_table( tt_face, TTAG_GPOS, stream, 0 );
if ( (!error_gdef) || (!error_gsub) || (!error_gpos) )
return FT_Err_Ok;
else
return OT_Err_Unknown_File_Format;
}
OTInfo *
ot_info_delete (OTInfo *info)
{
FT_Memory memory = FT_FACE_MEMORY( info->face );
if (info) {
ot_info_release (info);
FT_FREE( info );
}
return NULL;
}
OTInfo *
ot_info_ref (OTInfo *info)
{
FT_ASSERT (info != NULL );
FT_ASSERT (info->refcount > 0);
info->refcount += 1;
return info;
}
OTInfo *
ot_info_unref (OTInfo *info)
{
FT_ASSERT (info != NULL);
FT_ASSERT (info->refcount > 0);
info->refcount -= 1;
if (info->refcount == 0) {
ot_info_delete (info);
return NULL;
}
return info;
}
#ifdef INFO_DEBUG_MEMORY
static int ot_info_count = 0;
#endif
OTInfo *
ot_info_setup (OTInfo *info)
{
#ifdef INFO_DEBUG_MEMORY
ot_info_count++;
FT_Message ("ot_info_setup: %d\n", ot_info_count);
#endif
info->refcount = 1;
info->gsub = NULL;
info->gdef = NULL;
info->gpos = NULL;
info->ruleset = NULL;
return info;
}
OTInfo *
ot_info_release (OTInfo *info)
{
FT_ASSERT (info != NULL);
#ifdef INFO_DEBUG_MEMORY
ot_info_count--;
FT_Message ("ot_info_release: %d\n", ot_info_count);
#endif
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;
}
if (info->ruleset)
{
ot_ruleset_delete (info->ruleset);
info->ruleset = NULL;
}
return info;
}
/* static int info_count = 0; */
void
ot_info_finalizer (void *object)
{
FT_Face face = object;
OTInfo *info = face->generic.data;
ot_info_unref (info);
info->face = NULL;
}
/**
* ot_info_get_ruleset
*
* @returns: the #OTRulset for @info. This object will
* have the same lifetime as OTInfo.
*
* Returns the #OTRuleset structure for the info.
*/
OTRuleset *
ot_info_get_ruleset (OTInfo *info)
{
if (! info->ruleset) {
info->ruleset = ot_ruleset_new (info);
}
return info->ruleset;
}
/**
* ot_info_get:
* @face: a #FT_Face.
* @returns: the #OTInfo for @face. This object will
* have the same lifetime as FT_Face.
*
* Returns the #OTInfo structure for the given FreeType font.
*
* Since: 1.2
**/
OTInfo *
ot_info_get (FT_Face face)
{
OTInfo *info;
if (face->generic.data)
return face->generic.data;
else
{
info = ot_info_new ( face );
if ( info ) {
face->generic.data = info;
face->generic.finalizer = ot_info_finalizer;
info->face = face;
}
}
return info;
}
/* There must be be a better way to do this
*/
static FT_Bool
is_truetype (FT_Face face)
{
return (
(strcmp (FT_MODULE_CLASS (face->driver)->module_name, "truetype") == 0)
|| (strcmp (FT_MODULE_CLASS (face->driver)->module_name, "ot") == 0)
);
}
typedef struct _GlyphInfo GlyphInfo;
struct _GlyphInfo {
FT_UShort glyph;
FT_UShort class;
};
static int
compare_glyph_info (const void * a,
const void * b)
{
const GlyphInfo *info_a = a;
const GlyphInfo *info_b = b;
return (info_a->glyph < info_b->glyph) ? -1 :
(info_a->glyph == info_b->glyph) ? 0 : 1;
}
/* Make a guess at the appropriate class for a glyph given
* a character code that maps to the glyph
*/
static FT_Bool
set_unicode_charmap (FT_Face face)
{
int charmap;
for (charmap = 0; charmap < face->num_charmaps; charmap++)
if (face->charmaps[charmap]->encoding == ft_encoding_unicode)
{
FT_Error error = FT_Set_Charmap(face, face->charmaps[charmap]);
return error == FT_Err_Ok;
}
return FALSE;
}
/* Synthesize a GDEF table using the font's charmap and the
* unicode property database. We'll fill in class definitions
* for glyphs not in the charmap as we walk through the tables.
*/
static void
synthesize_class_def (OTInfo *info)
{
OTArray *glyph_infos;
FT_UShort *glyph_indices;
FT_UShort *classes;
FT_ULong charcode;
FT_UInt glyph;
int i, j;
FT_CharMap old_charmap;
FT_Error error;
FT_Memory memory = FT_FACE_MEMORY( info->face );
old_charmap = info->face->charmap;
if (!old_charmap || !old_charmap->encoding != ft_encoding_unicode)
if (!set_unicode_charmap (info->face))
return;
glyph_infos = OT_Array_New ( sizeof (GlyphInfo), memory );
/* Collect all the glyphs in the charmap, and guess
* the appropriate classes for them
*/
charcode = FT_Get_First_Char (info->face, &glyph);
while (glyph != 0)
{
GlyphInfo glyph_info;
if (glyph <= 65535)
{
glyph_info.glyph = glyph;
glyph_info.class = ot_get_glyph_class (charcode);
OT_Array_Append_Val ( glyph_infos, &glyph_info );
}
charcode = FT_Get_Next_Char (info->face, charcode, &glyph);
}
/* Sort and remove duplicates
*/
OT_Array_Sort ( glyph_infos, compare_glyph_info );
FT_ALLOC_ARRAY( glyph_indices, glyph_infos->length, FT_UShort );
FT_ALLOC_ARRAY( classes, glyph_infos->length, FT_UShort );
for (i = 0, j = 0; i < glyph_infos->length; i++)
{
GlyphInfo *info = &OT_Array_Index (glyph_infos, GlyphInfo, i);
if (j == 0 || info->glyph != glyph_indices[j - 1])
{
glyph_indices[j] = info->glyph;
classes[j] = info->class;
j++;
}
}
OT_Array_Free ( glyph_infos );
TT_GDEF_Build_ClassDefinition (info->gdef, info->face->num_glyphs, j,
glyph_indices, classes);
FT_FREE( glyph_indices );
FT_FREE( classes );
if (old_charmap && info->face->charmap != old_charmap)
FT_Set_Charmap (info->face, old_charmap);
}
TTO_GDEF
ot_info_get_gdef (OTInfo *info)
{
FT_ASSERT (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)
FT_ERROR (("Error loading GDEF table %d", error));
if (!info->gdef)
error = TT_New_GDEF_Table (info->face, &info->gdef);
if (info->gdef && !info->gdef->GlyphClassDef.loaded)
synthesize_class_def (info);
}
}
return info->gdef;
}
TTO_GSUB
ot_info_get_gsub (OTInfo *info)
{
FT_ASSERT (info != NULL);
if (!(info->loaded & INFO_LOADED_GSUB))
{
FT_Error error;
TTO_GDEF gdef = 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)
FT_ERROR (("Error loading GSUB table %d", error));
}
}
return info->gsub;
}
TTO_GPOS
ot_info_get_gpos (OTInfo *info)
{
FT_ASSERT (info != NULL);
if (!(info->loaded & INFO_LOADED_GPOS))
{
FT_Error error;
TTO_GDEF gdef = 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)
FT_ERROR (("Error loading GPOS table %d", error));
}
}
return info->gpos;
}
static FT_Bool
get_tables (OTInfo *info,
OTTableType table_type,
TTO_ScriptList **script_list,
TTO_FeatureList **feature_list)
{
if (table_type == OT_TABLE_GSUB)
{
TTO_GSUB gsub = 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 = 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;
}
}
}
/**
* ot_info_find_script:
* @info: a #OTInfo.
* @table_type: the table type to obtain information about.
* @script_tag: the tag of the script to find.
* @script_index: location to store the index of the script, or %NULL.
* @returns: %TRUE if the script was found.
*
* Finds the index of a script.
**/
FT_Bool
ot_info_find_script (OTInfo *info,
OTTableType table_type,
OTTag script_tag,
FT_UInt *script_index)
{
TTO_ScriptList *script_list;
int i;
FT_ASSERT (info != NULL);
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;
}
/**
* ot_info_find_language:
* @info: a #OTInfo.
* @table_type: the table type to obtain information about.
* @script_index: the index of the script whose languages are searched.
* @language_tag: the tag of the language to find.
* @language_index: location to store the index of the language, or %NULL.
* @required_feature_index: location to store the required feature index of
* the language, or %NULL.
* @returns: %TRUE if the language was found.
*
* Finds the index of a language and its required feature index.
**/
FT_Bool
ot_info_find_language (OTInfo *info,
OTTableType table_type,
FT_UInt script_index,
OTTag language_tag,
FT_UInt *language_index,
FT_UInt *required_feature_index)
{
TTO_ScriptList *script_list;
TTO_Script *script;
int i;
FT_ASSERT (info != NULL);
if (!get_tables (info, table_type, &script_list, NULL))
return FALSE;
FT_ASSERT (script_index < script_list->ScriptCount);
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;
}
/**
* ot_info_find_feature:
* @info: a #OTInfo.
* @table_type: the table type to obtain information about.
* @feature_tag: the tag of the feature to find.
* @script_index: the index of the script.
* @language_index: the index of the language whose features are searched,
* or 0xffff to use the default language of the script.
* @feature_index: location to store the index of the feature, or %NULL.
* @returns: %TRUE if the feature was found.
*
* Finds the index of a feature.
**/
FT_Bool
ot_info_find_feature (OTInfo *info,
OTTableType table_type,
OTTag feature_tag,
FT_UInt script_index,
FT_UInt language_index,
FT_UInt *feature_index)
{
TTO_ScriptList *script_list;
TTO_FeatureList *feature_list;
TTO_Script *script;
TTO_LangSys *lang_sys;
int i;
FT_ASSERT (info != NULL);
if (!get_tables (info, table_type, &script_list, &feature_list))
return FALSE;
FT_ASSERT (script_index < script_list->ScriptCount);
script = &script_list->ScriptRecord[script_index].Script;
if (language_index == 0xffff)
lang_sys = &script->DefaultLangSys;
else
{
FT_ASSERT (language_index < script->LangSysCount);
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;
}
/**
* ot_info_list_scripts:
* @info: a #OTInfo.
* @table_type: the table type to obtain information about.
* @returns: a newly-allocated array containing the tags of the
* available scripts.
*
* Obtains the list of available scripts.
**/
OTTag *
ot_info_list_scripts (OTInfo *info,
OTTableType table_type)
{
FT_Error error;
FT_Memory memory = FT_FACE_MEMORY( info->face );
OTTag *result;
TTO_ScriptList *script_list;
int i;
FT_ASSERT (info != NULL);
if (!get_tables (info, table_type, &script_list, NULL))
return NULL;
if ( FT_ALLOC_ARRAY ( result,
script_list->ScriptCount + 1,
OTTag ) )
return NULL;
for (i=0; i < script_list->ScriptCount; i++)
result[i] = script_list->ScriptRecord[i].ScriptTag;
result[i] = 0;
return result;
}
/**
* ot_info_list_languages:
* @info: a #OTInfo.
* @table_type: the table type to obtain information about.
* @script_index: the index of the script to list languages for.
* @language_tag: unused parameter.
* @returns: a newly-allocated array containing the tags of the
* available languages.
*
* Obtains the list of available languages for a given script.
**/
OTTag *
ot_info_list_languages (OTInfo *info,
OTTableType table_type,
FT_UInt script_index,
OTTag language_tag)
{
FT_Error error;
FT_Memory memory = FT_FACE_MEMORY( info->face );
OTTag *result;
TTO_ScriptList *script_list;
TTO_Script *script;
int i;
FT_ASSERT (info != NULL);
if (!get_tables (info, table_type, &script_list, NULL))
return NULL;
FT_ASSERT (script_index < script_list->ScriptCount);
script = &script_list->ScriptRecord[script_index].Script;
if ( FT_ALLOC_ARRAY ( result,
script->LangSysCount + 1,
OTTag ) )
return NULL;
for (i = 0; i < script->LangSysCount; i++)
result[i] = script->LangSysRecord[i].LangSysTag;
result[i] = 0;
return result;
}
/**
* ot_info_list_features:
* @info: a #OTInfo.
* @table_type: the table type to obtain information about.
* @tag: unused parameter.
* @script_index: the index of the script to obtain information about.
* @language_index: the indes of the language to list features for, or
* 0xffff, to list features for the default language of the script.
* @returns: a newly-allocated array containing the tags of the available
* features.
*
* Obtains the list of features for the given language of the given script.
**/
OTTag *
ot_info_list_features (OTInfo *info,
OTTableType table_type,
OTTag tag,
FT_UInt script_index,
FT_UInt language_index)
{
FT_Error error;
FT_Memory memory = FT_FACE_MEMORY( info->face );
OTTag *result;
TTO_ScriptList *script_list;
TTO_FeatureList *feature_list;
TTO_Script *script;
TTO_LangSys *lang_sys;
int i;
FT_ASSERT (info != NULL);
if (!get_tables (info, table_type, &script_list, &feature_list))
return NULL;
FT_ASSERT (script_index < script_list->ScriptCount);
script = &script_list->ScriptRecord[script_index].Script;
if (language_index == 0xffff)
lang_sys = &script->DefaultLangSys;
else
{
FT_ASSERT (language_index < script->LangSysCount);
lang_sys = &script->LangSysRecord[language_index].LangSys;
}
if ( FT_ALLOC_ARRAY ( result,
lang_sys->FeatureCount + 1,
OTTag ) )
return NULL;
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;
}