8004986: Better handling of glyph table

8004987: Improve font layout
8004994: Improve checking of glyph table

Reviewed-by: srl, jgodinez
This commit is contained in:
Phil Race 2013-02-26 10:07:26 -08:00
parent ae5b3c9310
commit ae2454b3e5
12 changed files with 205 additions and 36 deletions

View File

@ -26,7 +26,7 @@
/*
*
* (C) Copyright IBM Corp. 1998-2005 - All Rights Reserved
* (C) Copyright IBM Corp. 1998-2013 - All Rights Reserved
*
*/
@ -152,17 +152,16 @@ void ArabicOpenTypeLayoutEngine::adjustGlyphPositions(const LEUnicode chars[], l
}
UnicodeArabicOpenTypeLayoutEngine::UnicodeArabicOpenTypeLayoutEngine(const LEFontInstance *fontInstance, le_int32 scriptCode, le_int32 languageCode, le_int32 typoFlags, LEErrorCode &success)
: ArabicOpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, typoFlags, success)
: ArabicOpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, typoFlags | LE_CHAR_FILTER_FEATURE_FLAG, success)
{
fGSUBTable = (const GlyphSubstitutionTableHeader *) CanonShaping::glyphSubstitutionTable;
fGDEFTable = (const GlyphDefinitionTableHeader *) CanonShaping::glyphDefinitionTable;
fSubstitutionFilter = new CharSubstitutionFilter(fontInstance);
/* OpenTypeLayoutEngine will allocate a substitution filter */
}
UnicodeArabicOpenTypeLayoutEngine::~UnicodeArabicOpenTypeLayoutEngine()
{
delete fSubstitutionFilter;
/* OpenTypeLayoutEngine will cleanup the substitution filter */
}
// "glyphs", "indices" -> glyphs, indices

View File

@ -38,7 +38,6 @@
#include "ContextualGlyphSubstProc2.h"
#include "LEGlyphStorage.h"
#include "LESwaps.h"
#include <stdio.h>
U_NAMESPACE_BEGIN
@ -123,7 +122,7 @@ TTGlyphID ContextualGlyphSubstitutionProcessor2::lookup(le_uint32 offset, LEGlyp
break;
}
case ltfSegmentArray: {
printf("Context Lookup Table Format4: specific interpretation needed!\n");
//printf("Context Lookup Table Format4: specific interpretation needed!\n");
break;
}
case ltfSingleTable:

View File

@ -339,6 +339,35 @@ typedef struct LEPoint LEPoint;
#ifndef U_HIDE_INTERNAL_API
#ifndef LE_ASSERT_BAD_FONT
#define LE_ASSERT_BAD_FONT 0
#endif
#if LE_ASSERT_BAD_FONT
#include <stdio.h>
#define LE_DEBUG_BAD_FONT(x) fprintf(stderr,"%s:%d: BAD FONT: %s\n", __FILE__, __LINE__, (x));
#else
#define LE_DEBUG_BAD_FONT(x)
#endif
/**
* Max value representable by a uintptr
*/
#ifndef UINTPTR_MAX
#ifndef UINT32_MAX
#define LE_UINTPTR_MAX 0xFFFFFFFFU
#else
#define LE_UINTPTR_MAX UINT32_MAX
#endif
#else
#define LE_UINTPTR_MAX UINTPTR_MAX
#endif
/**
* Range check for overflow
*/
#define LE_RANGE_CHECK(type, count, ptrfn) (( (LE_UINTPTR_MAX / sizeof(type)) < count ) ? NULL : (ptrfn))
/**
* A convenience macro to get the length of an array.
*
@ -360,7 +389,7 @@ typedef struct LEPoint LEPoint;
*
* @internal
*/
#define LE_NEW_ARRAY(type, count) (type *) uprv_malloc((count) * sizeof(type))
#define LE_NEW_ARRAY(type, count) (type *) LE_RANGE_CHECK(type,count,uprv_malloc((count) * sizeof(type)))
/**
* Re-allocate an array of basic types. This is used to isolate the rest of
@ -403,7 +432,7 @@ typedef struct LEPoint LEPoint;
*
* @internal
*/
#define LE_NEW_ARRAY(type, count) (type *) malloc((count) * sizeof(type))
#define LE_NEW_ARRAY(type, count) LE_RANGE_CHECK(type,count,(type *) malloc((count) * sizeof(type)))
/**
* Re-allocate an array of basic types. This is used to isolate the rest of
@ -696,6 +725,8 @@ enum LEFeatureENUMs {
#define LE_CHAR_FILTER_FEATURE_FLAG (1 << LE_CHAR_FILTER_FEATURE_ENUM)
#define LE_DEFAULT_FEATURE_FLAG (LE_Kerning_FEATURE_FLAG | LE_Ligatures_FEATURE_FLAG) /**< default features */
/**
* Error codes returned by the LayoutEngine.
*

View File

@ -428,7 +428,7 @@ void LayoutEngine::adjustGlyphPositions(const LEUnicode chars[], le_int32 offset
adjustMarkGlyphs(&chars[offset], count, reverse, glyphStorage, &filter, success);
if (fTypoFlags & 0x1) { /* kerning enabled */
if (fTypoFlags & LE_Kerning_FEATURE_FLAG) { /* kerning enabled */
static const le_uint32 kernTableTag = LE_KERN_TABLE_TAG;
KernTable kt(fFontInstance, getFontTable(kernTableTag));
@ -571,8 +571,8 @@ void LayoutEngine::reset()
LayoutEngine *LayoutEngine::layoutEngineFactory(const LEFontInstance *fontInstance, le_int32 scriptCode, le_int32 languageCode, LEErrorCode &success)
{
// 3 -> kerning and ligatures
return LayoutEngine::layoutEngineFactory(fontInstance, scriptCode, languageCode, 3, success);
//kerning and ligatures - by default
return LayoutEngine::layoutEngineFactory(fontInstance, scriptCode, languageCode, LE_DEFAULT_FEATURE_FLAG, success);
}
LayoutEngine *LayoutEngine::layoutEngineFactory(const LEFontInstance *fontInstance, le_int32 scriptCode, le_int32 languageCode, le_int32 typoFlags, LEErrorCode &success)
@ -660,11 +660,11 @@ LayoutEngine *LayoutEngine::layoutEngineFactory(const LEFontInstance *fontInstan
}
} else {
MorphTableHeader2 *morxTable = (MorphTableHeader2 *)fontInstance->getFontTable(morxTableTag);
if (morxTable != NULL) {
if (morxTable != NULL && SWAPL(morxTable->version)==0x00020000) {
result = new GXLayoutEngine2(fontInstance, scriptCode, languageCode, morxTable, typoFlags, success);
} else {
const MorphTableHeader *mortTable = (MorphTableHeader *) fontInstance->getFontTable(mortTableTag);
if (mortTable != NULL) { // mort
if (mortTable != NULL && SWAPL(mortTable->version)==0x00010000) { // mort
result = new GXLayoutEngine(fontInstance, scriptCode, languageCode, mortTable, success);
} else {
switch (scriptCode) {

View File

@ -25,7 +25,7 @@
/*
*
* (C) Copyright IBM Corp. 1998-2004 - All Rights Reserved
* (C) Copyright IBM Corp. 1998-2013 - All Rights Reserved
*
*/
@ -79,6 +79,10 @@ ByteOffset LigatureSubstitutionProcessor::processStateEntry(LEGlyphStorage &glyp
}
componentStack[m] = currGlyph;
} else if ( m == -1) {
// bad font- skip this glyph.
currGlyph++;
return newState;
}
ByteOffset actionOffset = flags & lsfActionOffsetMask;
@ -102,7 +106,21 @@ ByteOffset LigatureSubstitutionProcessor::processStateEntry(LEGlyphStorage &glyp
offset = action & lafComponentOffsetMask;
if (offset != 0) {
const le_int16 *offsetTable = (const le_int16 *)((char *) &ligatureSubstitutionHeader->stHeader + 2 * SignExtend(offset, lafComponentOffsetMask));
const le_int16 *tableEnd = (const le_int16 *)((char *) &ligatureSubstitutionHeader + 1 * SWAPW(ligatureSubstitutionHeader->length));
// Check if the font is internally consistent
if(tableEnd < (const le_int16*)&ligatureSubstitutionHeader // stated end wrapped around?
|| offsetTable > tableEnd) { // offset past end of stated length?
currGlyph++;
LE_DEBUG_BAD_FONT("off end of ligature substitution header");
return newState; // get out! bad font
}
if(componentGlyph > glyphStorage.getGlyphCount()) {
LE_DEBUG_BAD_FONT("preposterous componentGlyph");
currGlyph++;
return newState; // get out! bad font
}
i += SWAPW(offsetTable[LE_GET_GLYPH(glyphStorage[componentGlyph])]);
if (action & (lafLast | lafStore)) {
@ -110,13 +128,22 @@ ByteOffset LigatureSubstitutionProcessor::processStateEntry(LEGlyphStorage &glyp
TTGlyphID ligatureGlyph = SWAPW(*ligatureOffset);
glyphStorage[componentGlyph] = LE_SET_GLYPH(glyphStorage[componentGlyph], ligatureGlyph);
if(mm==nComponents) {
LE_DEBUG_BAD_FONT("exceeded nComponents");
mm--; // don't overrun the stack.
}
stack[++mm] = componentGlyph;
i = 0;
} else {
glyphStorage[componentGlyph] = LE_SET_GLYPH(glyphStorage[componentGlyph], 0xFFFF);
}
}
} while (!(action & lafLast));
#if LE_ASSERT_BAD_FONT
if(m<0) {
LE_DEBUG_BAD_FONT("m<0")
}
#endif
} while (!(action & lafLast) && (m>=0) ); // stop if last bit is set, or if run out of items
while (mm >= 0) {
if (++m >= nComponents) {

View File

@ -79,6 +79,11 @@ le_uint16 LigatureSubstitutionProcessor2::processStateEntry(LEGlyphStorage &glyp
m = 0;
}
componentStack[m] = currGlyph;
} else if ( m == -1) {
// bad font- skip this glyph.
LE_DEBUG_BAD_FONT("m==-1")
currGlyph+= dir;
return nextStateIndex;
}
ByteOffset actionOffset = flags & lsfPerformAction;
@ -93,6 +98,16 @@ le_uint16 LigatureSubstitutionProcessor2::processStateEntry(LEGlyphStorage &glyp
const le_uint16 *componentTable = (const le_uint16 *)((char *) &ligatureSubstitutionHeader->stHeader + componentOffset);
const le_uint16 *tableEnd = (const le_uint16 *)((char *) &ligatureSubstitutionHeader + SWAPL(ligatureSubstitutionHeader->length));
// Check if the font is internally consistent
if(tableEnd < (const le_uint16*)&ligatureSubstitutionHeader // stated end wrapped around?
|| componentTable > tableEnd) { // offset past end of stated length?
currGlyph+= dir;
LE_DEBUG_BAD_FONT("ligatureSubstHeader off end of table")
return nextStateIndex; // get out! bad font
}
do {
le_uint32 componentGlyph = componentStack[m--]; // pop off
@ -104,19 +119,32 @@ le_uint16 LigatureSubstitutionProcessor2::processStateEntry(LEGlyphStorage &glyp
offset = action & lafComponentOffsetMask;
if (offset != 0) {
if(componentGlyph > glyphStorage.getGlyphCount()) {
LE_DEBUG_BAD_FONT("preposterous componentGlyph");
currGlyph+= dir;
return nextStateIndex; // get out! bad font
}
i += SWAPW(componentTable[LE_GET_GLYPH(glyphStorage[componentGlyph]) + (SignExtend(offset, lafComponentOffsetMask))]);
if (action & (lafLast | lafStore)) {
TTGlyphID ligatureGlyph = SWAPW(ligatureTable[i]);
glyphStorage[componentGlyph] = LE_SET_GLYPH(glyphStorage[componentGlyph], ligatureGlyph);
if(mm==nComponents) {
LE_DEBUG_BAD_FONT("exceeded nComponents");
mm--; // don't overrun the stack.
}
stack[++mm] = componentGlyph;
i = 0;
} else {
glyphStorage[componentGlyph] = LE_SET_GLYPH(glyphStorage[componentGlyph], 0xFFFF);
}
}
} while (!(action & lafLast));
#if LE_ASSERT_BAD_FONT
if(m<0) {
LE_DEBUG_BAD_FONT("m<0")
}
#endif
} while (!(action & lafLast) && (m>=0) ); // stop if last bit is set, or if run out of items
while (mm >= 0) {
if (++m >= nComponents) {

View File

@ -208,7 +208,7 @@ LookupProcessor::LookupProcessor(const char *baseAddress,
lookupSelectCount = lookupListCount;
le_int32 count, order = 0;
le_int32 featureReferences = 0;
le_uint32 featureReferences = 0;
const FeatureTable *featureTable = NULL;
LETag featureTag;
@ -219,7 +219,7 @@ LookupProcessor::LookupProcessor(const char *baseAddress,
// be the maximum number of entries in the lookupOrderArray. We can't use
// lookupListCount because some lookups might be referenced by more than
// one feature.
for (le_int32 feature = 0; feature < featureCount; feature += 1) {
for (le_uint32 feature = 0; feature < featureCount; feature += 1) {
le_uint16 featureIndex = SWAPW(langSysTable->featureIndexArray[feature]);
featureTable = featureListTable->getFeatureTable(featureIndex, &featureTag);

View File

@ -123,7 +123,7 @@ static const FeatureMap featureMap[] =
{ccmpFeatureTag, ccmpFeatureMask},
{ligaFeatureTag, ligaFeatureMask},
{cligFeatureTag, cligFeatureMask},
{kernFeatureTag, kernFeatureMask},
{kernFeatureTag, kernFeatureMask},
{paltFeatureTag, paltFeatureMask},
{markFeatureTag, markFeatureMask},
{mkmkFeatureTag, mkmkFeatureMask},
@ -160,6 +160,23 @@ OpenTypeLayoutEngine::OpenTypeLayoutEngine(const LEFontInstance *fontInstance, l
static const le_uint32 gposTableTag = LE_GPOS_TABLE_TAG;
const GlyphPositioningTableHeader *gposTable = (const GlyphPositioningTableHeader *) getFontTable(gposTableTag);
applyTypoFlags();
setScriptAndLanguageTags();
fGDEFTable = (const GlyphDefinitionTableHeader *) getFontTable(gdefTableTag);
// JK patch, 2008-05-30 - see Sinhala bug report and LKLUG font
// if (gposTable != NULL && gposTable->coversScriptAndLanguage(fScriptTag, fLangSysTag)) {
if (gposTable != NULL && gposTable->coversScript(fScriptTag)) {
fGPOSTable = gposTable;
}
}
void OpenTypeLayoutEngine::applyTypoFlags() {
const le_int32& typoFlags = fTypoFlags;
const LEFontInstance *fontInstance = fFontInstance;
switch (typoFlags & (LE_SS01_FEATURE_FLAG
| LE_SS02_FEATURE_FLAG
| LE_SS03_FEATURE_FLAG
@ -221,15 +238,6 @@ OpenTypeLayoutEngine::OpenTypeLayoutEngine(const LEFontInstance *fontInstance, l
fSubstitutionFilter = new CharSubstitutionFilter(fontInstance);
}
setScriptAndLanguageTags();
fGDEFTable = (const GlyphDefinitionTableHeader *) getFontTable(gdefTableTag);
// JK patch, 2008-05-30 - see Sinhala bug report and LKLUG font
// if (gposTable != NULL && gposTable->coversScriptAndLanguage(fScriptTag, fLangSysTag)) {
if (gposTable != NULL && gposTable->coversScript(fScriptTag)) {
fGPOSTable = gposTable;
}
}
void OpenTypeLayoutEngine::reset()
@ -246,13 +254,15 @@ OpenTypeLayoutEngine::OpenTypeLayoutEngine(const LEFontInstance *fontInstance, l
: LayoutEngine(fontInstance, scriptCode, languageCode, typoFlags, success), fFeatureOrder(FALSE),
fGSUBTable(NULL), fGDEFTable(NULL), fGPOSTable(NULL), fSubstitutionFilter(NULL)
{
setScriptAndLanguageTags();
applyTypoFlags();
setScriptAndLanguageTags();
}
OpenTypeLayoutEngine::~OpenTypeLayoutEngine()
{
if (fTypoFlags & 0x80000000L) {
if (fTypoFlags & LE_CHAR_FILTER_FEATURE_FLAG) {
delete fSubstitutionFilter;
fSubstitutionFilter = NULL;
}
reset();

View File

@ -24,7 +24,7 @@
*/
/*
* (C) Copyright IBM Corp. 1998-2010 - All Rights Reserved
* (C) Copyright IBM Corp. 1998-2013 - All Rights Reserved
*
*/
@ -184,6 +184,11 @@ private:
*/
static const LETag scriptTags[];
/**
* apply the typoflags. Only called by the c'tors.
*/
void applyTypoFlags();
protected:
/**
* A set of "default" features. The default characterProcessing method

View File

@ -65,6 +65,9 @@ StateTableProcessor::~StateTableProcessor()
void StateTableProcessor::process(LEGlyphStorage &glyphStorage)
{
LE_STATE_PATIENCE_INIT();
// Start at state 0
// XXX: How do we know when to start at state 1?
ByteOffset currentState = stateArrayOffset;
@ -76,6 +79,7 @@ void StateTableProcessor::process(LEGlyphStorage &glyphStorage)
beginStateTable();
while (currGlyph <= glyphCount) {
if(LE_STATE_PATIENCE_DECR()) break; // patience exceeded.
ClassCode classCode = classCodeOOB;
if (currGlyph == glyphCount) {
// XXX: How do we handle EOT vs. EOL?
@ -92,8 +96,9 @@ void StateTableProcessor::process(LEGlyphStorage &glyphStorage)
const EntryTableIndex *stateArray = (const EntryTableIndex *) ((char *) &stateTableHeader->stHeader + currentState);
EntryTableIndex entryTableIndex = stateArray[(le_uint8)classCode];
LE_STATE_PATIENCE_CURR(le_int32, currGlyph);
currentState = processStateEntry(glyphStorage, currGlyph, entryTableIndex);
LE_STATE_PATIENCE_INCR(currGlyph);
}
endStateTable();

View File

@ -38,7 +38,6 @@
#include "LEGlyphStorage.h"
#include "LESwaps.h"
#include "LookupTables.h"
#include <stdio.h>
U_NAMESPACE_BEGIN
@ -72,6 +71,8 @@ void StateTableProcessor2::process(LEGlyphStorage &glyphStorage)
le_uint16 currentState = 0;
le_int32 glyphCount = glyphStorage.getGlyphCount();
LE_STATE_PATIENCE_INIT();
le_int32 currGlyph = 0;
if ((coverage & scfReverse2) != 0) { // process glyphs in descending order
currGlyph = glyphCount - 1;
@ -86,6 +87,10 @@ void StateTableProcessor2::process(LEGlyphStorage &glyphStorage)
#ifdef TEST_FORMAT
SimpleArrayLookupTable *lookupTable0 = (SimpleArrayLookupTable *) classTable;
while ((dir == 1 && currGlyph <= glyphCount) || (dir == -1 && currGlyph >= -1)) {
if(LE_STATE_PATIENCE_DECR()) {
LE_DEBUG_BAD_FONT("patience exceeded - state table not moving")
break; // patience exceeded.
}
LookupValue classCode = classCodeOOB;
if (currGlyph == glyphCount || currGlyph == -1) {
// XXX: How do we handle EOT vs. EOL?
@ -101,7 +106,9 @@ void StateTableProcessor2::process(LEGlyphStorage &glyphStorage)
}
}
EntryTableIndex2 entryTableIndex = SWAPW(stateArray[classCode + currentState * nClasses]);
LE_STATE_PATIENCE_CURR(le_int32, currGlyph);
currentState = processStateEntry(glyphStorage, currGlyph, entryTableIndex); // return a zero-based index instead of a byte offset
LE_STATE_PATIENCE_INCR(currGlyph);
}
#endif
break;
@ -109,6 +116,10 @@ void StateTableProcessor2::process(LEGlyphStorage &glyphStorage)
case ltfSegmentSingle: {
SegmentSingleLookupTable *lookupTable2 = (SegmentSingleLookupTable *) classTable;
while ((dir == 1 && currGlyph <= glyphCount) || (dir == -1 && currGlyph >= -1)) {
if(LE_STATE_PATIENCE_DECR()) {
LE_DEBUG_BAD_FONT("patience exceeded - state table not moving")
break; // patience exceeded.
}
LookupValue classCode = classCodeOOB;
if (currGlyph == glyphCount || currGlyph == -1) {
// XXX: How do we handle EOT vs. EOL?
@ -127,21 +138,31 @@ void StateTableProcessor2::process(LEGlyphStorage &glyphStorage)
}
}
EntryTableIndex2 entryTableIndex = SWAPW(stateArray[classCode + currentState * nClasses]);
LE_STATE_PATIENCE_CURR(le_int32, currGlyph);
currentState = processStateEntry(glyphStorage, currGlyph, entryTableIndex);
LE_STATE_PATIENCE_INCR(currGlyph);
}
break;
}
case ltfSegmentArray: {
printf("Lookup Table Format4: specific interpretation needed!\n");
//printf("Lookup Table Format4: specific interpretation needed!\n");
break;
}
case ltfSingleTable: {
SingleTableLookupTable *lookupTable6 = (SingleTableLookupTable *) classTable;
while ((dir == 1 && currGlyph <= glyphCount) || (dir == -1 && currGlyph >= -1)) {
if(LE_STATE_PATIENCE_DECR()) {
LE_DEBUG_BAD_FONT("patience exceeded - state table not moving")
break; // patience exceeded.
}
LookupValue classCode = classCodeOOB;
if (currGlyph == glyphCount || currGlyph == -1) {
// XXX: How do we handle EOT vs. EOL?
classCode = classCodeEOT;
} else if(currGlyph > glyphCount) {
// note if > glyphCount, we've run off the end (bad font)
currGlyph = glyphCount;
classCode = classCodeEOT;
} else {
LEGlyphID gid = glyphStorage[currGlyph];
TTGlyphID glyphCode = (TTGlyphID) LE_GET_GLYPH(gid);
@ -156,7 +177,9 @@ void StateTableProcessor2::process(LEGlyphStorage &glyphStorage)
}
}
EntryTableIndex2 entryTableIndex = SWAPW(stateArray[classCode + currentState * nClasses]);
LE_STATE_PATIENCE_CURR(le_int32, currGlyph);
currentState = processStateEntry(glyphStorage, currGlyph, entryTableIndex);
LE_STATE_PATIENCE_INCR(currGlyph);
}
break;
}
@ -166,6 +189,11 @@ void StateTableProcessor2::process(LEGlyphStorage &glyphStorage)
TTGlyphID lastGlyph = firstGlyph + SWAPW(lookupTable8->glyphCount);
while ((dir == 1 && currGlyph <= glyphCount) || (dir == -1 && currGlyph >= -1)) {
if(LE_STATE_PATIENCE_DECR()) {
LE_DEBUG_BAD_FONT("patience exceeded - state table not moving")
break; // patience exceeded.
}
LookupValue classCode = classCodeOOB;
if (currGlyph == glyphCount || currGlyph == -1) {
// XXX: How do we handle EOT vs. EOL?
@ -179,7 +207,9 @@ void StateTableProcessor2::process(LEGlyphStorage &glyphStorage)
}
}
EntryTableIndex2 entryTableIndex = SWAPW(stateArray[classCode + currentState * nClasses]);
LE_STATE_PATIENCE_CURR(le_int32, currGlyph);
currentState = processStateEntry(glyphStorage, currGlyph, entryTableIndex);
LE_STATE_PATIENCE_INCR(currGlyph);
}
break;
}

View File

@ -42,6 +42,41 @@
U_NAMESPACE_BEGIN
/*
* State table loop detection.
* Detects if too many ( LE_STATE_PATIENCE_COUNT ) state changes occur without moving the glyph index 'g'.
*
* Usage (pseudocode):
*
* {
* LE_STATE_PATIENCE_INIT();
*
* int g=0; // the glyph index - expect it to be moving
*
* for(;;) {
* if(LE_STATE_PATIENCE_DECR()) { // decrements the patience counter
* // ran out of patience, get out.
* break;
* }
*
* LE_STATE_PATIENCE_CURR(int, g); // store the 'current'
* state = newState(state,g);
* g+= <something, could be zero>;
* LE_STATE_PATIENCE_INCR(g); // if g has moved, increment the patience counter. Otherwise leave it.
* }
*
*/
#define LE_STATE_PATIENCE_COUNT 4096 /**< give up if a state table doesn't move the glyph after this many iterations */
#define LE_STATE_PATIENCE_INIT() le_uint32 le_patience_count = LE_STATE_PATIENCE_COUNT
#define LE_STATE_PATIENCE_DECR() --le_patience_count==0
#define LE_STATE_PATIENCE_CURR(type,x) type le_patience_curr=(x)
#define LE_STATE_PATIENCE_INCR(x) if((x)!=le_patience_curr) ++le_patience_count;
struct StateTableHeader
{
le_int16 stateSize;