1111 lines
22 KiB
C
1111 lines
22 KiB
C
|
// Panel.c
|
||
|
// James A. Pittman
|
||
|
// July 23, 1998
|
||
|
|
||
|
// Recognizes an entire panel at once by looping over lines, then looping over
|
||
|
// ink blobs that have large gaps between them, and finally looping over the strokes
|
||
|
// within a blob, using recognition scores to help decide what is a word and what is not.
|
||
|
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <limits.h>
|
||
|
|
||
|
#include "common.h"
|
||
|
#include "inferno.h"
|
||
|
|
||
|
#include "nfeature.h"
|
||
|
#include "engine.h"
|
||
|
#include "Panel.h"
|
||
|
|
||
|
#include "PorkyPost.h"
|
||
|
#include "combiner.h"
|
||
|
|
||
|
#include "Normal.h"
|
||
|
#include "linebrk.h"
|
||
|
#include "privdefs.h"
|
||
|
#include "recoutil.h"
|
||
|
|
||
|
#define STREQ(s,t) !strcmp(s,t)
|
||
|
#define STRDIFF(s,t) strcmp(s,t)
|
||
|
|
||
|
#define PHRASE_GROW_SIZE 4
|
||
|
|
||
|
#define NOT_RECOGNIZED "\a"
|
||
|
|
||
|
#define BASELIMIT 700 // scale ink to this guide hgt if we have a guide
|
||
|
#define MAX_SCALE 10 // We will not scale more than this.
|
||
|
|
||
|
|
||
|
|
||
|
#define MAXPHRASES 50
|
||
|
|
||
|
static int _cdecl ResultCmp(const XRCRESULT *a, const XRCRESULT *b)
|
||
|
{
|
||
|
if (a->cost > b->cost)
|
||
|
return(1);
|
||
|
else if (a->cost < b->cost)
|
||
|
return(-1);
|
||
|
|
||
|
return(0);
|
||
|
}
|
||
|
|
||
|
// Add a map structere to the XRCRESULT containing the stroke
|
||
|
// iDs in the glyph
|
||
|
static int AddStrokeIdFromGlyph(GLYPH *pGlyph, XRCRESULT *pAns)
|
||
|
{
|
||
|
int ret = HRCR_ERROR;
|
||
|
|
||
|
ASSERT(pGlyph);
|
||
|
ASSERT(pAns);
|
||
|
ASSERT(pAns->pMap);
|
||
|
|
||
|
if (pGlyph && pAns && pAns->pMap)
|
||
|
{
|
||
|
int iLastIdx = -1, *piIdx;
|
||
|
|
||
|
pAns->pMap->cStrokes = CframeGLYPH(pGlyph);
|
||
|
piIdx = pAns->pMap->piStrokeIndex = (int *)ExternAlloc(sizeof(*pAns->pMap->piStrokeIndex) * pAns->pMap->cStrokes);
|
||
|
|
||
|
ASSERT(pAns->pMap->piStrokeIndex);
|
||
|
if (!pAns->pMap->piStrokeIndex)
|
||
|
{
|
||
|
return HRCR_MEMERR;
|
||
|
}
|
||
|
|
||
|
// Add the strokes in ascending order
|
||
|
for ( ; pGlyph ; pGlyph = pGlyph->next)
|
||
|
{
|
||
|
if (pGlyph->frame)
|
||
|
{
|
||
|
int iFrame = pGlyph->frame->iframe;
|
||
|
|
||
|
ASSERT(iFrame != iLastIdx);
|
||
|
|
||
|
if (iFrame < iLastIdx)
|
||
|
{
|
||
|
int *piInsert = pAns->pMap->piStrokeIndex;
|
||
|
|
||
|
// Search to find insertion point
|
||
|
for ( ; piInsert < piIdx ; ++piInsert)
|
||
|
{
|
||
|
if (iFrame < *piInsert)
|
||
|
{
|
||
|
int iSwap = *piInsert;
|
||
|
|
||
|
*piInsert = iFrame;
|
||
|
iFrame = iSwap;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
iLastIdx = *piIdx = iFrame;
|
||
|
++piIdx;
|
||
|
|
||
|
ASSERT (piIdx - pAns->pMap->piStrokeIndex <= pAns->pMap->cStrokes);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ret = HRCR_OK;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
// This function was pulled out of MadHwxProcess(), and then modified. I took out the test that
|
||
|
// allows us to skip the post processor when its answer appears certain, so that the numerical
|
||
|
// score on the top 1 word is on the same scale for every word.
|
||
|
|
||
|
|
||
|
static int RecognizeWord(XRC *pxrc, int yDev)
|
||
|
{
|
||
|
XRCRESULT *pAns;
|
||
|
int cAns, ret;
|
||
|
|
||
|
ret = InfProcessHRC((HRC)pxrc, yDev);
|
||
|
|
||
|
if (HRCR_OK != ret)
|
||
|
return ret;
|
||
|
|
||
|
pAns = pxrc->answer.aAlt;
|
||
|
cAns = pxrc->answer.cAlt;
|
||
|
|
||
|
// Inferno, if it cannot featurize the ink,
|
||
|
// Fill in 0 words recognized
|
||
|
|
||
|
if (!(pxrc->nfeatureset) || 0 == cAns)
|
||
|
{
|
||
|
GLYPH *pGlyph = pxrc->pGlyph;
|
||
|
|
||
|
if (pAns[0].szWord)
|
||
|
{
|
||
|
ExternFree(pAns[0].szWord);
|
||
|
}
|
||
|
|
||
|
pAns[0].cost = INT_MAX;
|
||
|
|
||
|
pAns->szWord = (char *)ExternAlloc(sizeof(*pAns->szWord)*(strlen(NOT_RECOGNIZED) + 1));
|
||
|
ASSERT(pAns->szWord);
|
||
|
if (!pAns->szWord)
|
||
|
{
|
||
|
return HRCR_MEMERR;
|
||
|
}
|
||
|
strcpy(pAns->szWord , NOT_RECOGNIZED);
|
||
|
|
||
|
pxrc->answer.cAlt = 1;
|
||
|
pAns->cWords = 1;
|
||
|
pAns->pXRC = pxrc;
|
||
|
|
||
|
pAns->pMap = ExternAlloc(sizeof(*pAns->pMap));
|
||
|
ASSERT(pAns->pMap);
|
||
|
if (!pAns->pMap)
|
||
|
{
|
||
|
ret = HRCR_MEMERR;
|
||
|
goto fail;
|
||
|
}
|
||
|
pAns->pMap->start = 0;
|
||
|
pAns->pMap->len = strlen(pAns->szWord);
|
||
|
|
||
|
memset(&pAns->pMap->alt, 0, sizeof(pAns->pMap->alt));
|
||
|
|
||
|
ret = AddStrokeIdFromGlyph(pGlyph, pAns);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
|
||
|
fail:
|
||
|
if (pAns->szWord)
|
||
|
{
|
||
|
ExternFree(pAns->szWord);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
//!!! BUG - This should be setting up the HRC from the values set in the xrc
|
||
|
//!!! for madcow. We need that xrc passed down to here and set the exact
|
||
|
//!!! same alc, dict mode, lang mode etc in from it.
|
||
|
|
||
|
// set up a the HRC for word recognition (Do not allow white space)
|
||
|
static int initWordHRC(XRC *pMainXrc, GLYPH *pGlyph, HRC *phrc)
|
||
|
{
|
||
|
int cFrame;
|
||
|
HRC hrc = CreateCompatibleHRC((HRC)pMainXrc, NULL);
|
||
|
int ret = HRCR_OK;
|
||
|
XRC *pxrcNew;
|
||
|
|
||
|
*phrc = (HRC)0;
|
||
|
|
||
|
if (!hrc) // don't go to failure as we do not need to destroy an HRC
|
||
|
return HRCR_MEMERR;
|
||
|
|
||
|
// disable ALC_WHITE
|
||
|
//ret = SetAlphabetHRC(hrc, pMainXrc->alc & ~ALC_WHITE, NULL);
|
||
|
ret = SetHwxFlags(hrc, pMainXrc->flags | RECOFLAG_WORDMODE);
|
||
|
if (ret != HRCR_OK)
|
||
|
goto failure;
|
||
|
|
||
|
pxrcNew = (XRC *) hrc;
|
||
|
pxrcNew->answer.cAltMax = MAX_ALT_WORD;
|
||
|
|
||
|
// build glyph of specific frames inside hrc
|
||
|
// we later may be able to alter the API to allow additional frames to be
|
||
|
// added after recognition has already been run
|
||
|
|
||
|
for ( cFrame = 0 ; pGlyph; pGlyph = pGlyph->next, ++cFrame)
|
||
|
{
|
||
|
FRAME *pFrame = pGlyph->frame, *pAddedFrame;
|
||
|
ASSERT(pFrame);
|
||
|
|
||
|
if (!pFrame)
|
||
|
{
|
||
|
ret = HRCR_ERROR;
|
||
|
goto failure;
|
||
|
}
|
||
|
|
||
|
ret = AddPenInputHRC(hrc, RgrawxyFRAME(pFrame), NULL, 0, &(pFrame->info));
|
||
|
if (ret != HRCR_OK)
|
||
|
goto failure;
|
||
|
|
||
|
// Keep globally allocated frame numbers
|
||
|
if ( (pAddedFrame = FrameAtGLYPH(((XRC *)hrc)->pGlyph, cFrame)))
|
||
|
{
|
||
|
pAddedFrame->iframe = pFrame->iframe;
|
||
|
pAddedFrame->rect = pFrame->rect;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
*phrc = hrc;
|
||
|
return HRCR_OK;
|
||
|
|
||
|
failure:
|
||
|
DestroyHRC(hrc);
|
||
|
*phrc = (HRC)0;
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// set up a the HRC for phrase recognition (Allow white space)
|
||
|
static int initPhraseHRC(XRC *pMainXrc, GLYPH *pGlyph, HRC *phrc)
|
||
|
{
|
||
|
int ret = HRCR_OK;
|
||
|
|
||
|
if (HRCR_OK == initWordHRC(pMainXrc, pGlyph, phrc))
|
||
|
{
|
||
|
XRC *pxrcNew = (XRC *)(*phrc);
|
||
|
|
||
|
ret = SetHwxFlags(*phrc, pMainXrc->flags & ~RECOFLAG_WORDMODE);
|
||
|
pxrcNew->answer.cAltMax = MAX_ALT_PHRASE;
|
||
|
|
||
|
if (ret != HRCR_OK)
|
||
|
goto failure;
|
||
|
}
|
||
|
|
||
|
return HRCR_OK;
|
||
|
|
||
|
failure:
|
||
|
DestroyHRC(*phrc);
|
||
|
*phrc = (HRC)0;
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// Special efficient version of ClearRCRESALT() that knows
|
||
|
// there are no mappings.
|
||
|
|
||
|
static void clearAlt(ALTERNATES *p)
|
||
|
{
|
||
|
unsigned int i;
|
||
|
|
||
|
for (i = 0; i < p->cAlt; i++)
|
||
|
ExternFree(p->aAlt[i].szWord);
|
||
|
p->cAlt = 0;
|
||
|
}
|
||
|
|
||
|
/******************************************************************
|
||
|
*
|
||
|
* isolate
|
||
|
*
|
||
|
* Store away a recognized isolated word together with all its alternates
|
||
|
* in the answer set
|
||
|
*
|
||
|
**********************************************************************/
|
||
|
static int isolate(XRC *pXrc, ANSWER_SET *pAnsSet)
|
||
|
{
|
||
|
ALTERNATES *pAlt;
|
||
|
|
||
|
// Only add cases that were succesfully recognized
|
||
|
if (pXrc->answer.cAlt > 0)
|
||
|
{
|
||
|
if (pAnsSet->cAnsSets >= pAnsSet->capSegments)
|
||
|
{
|
||
|
pAnsSet->pAlternates = ExternRealloc(pAnsSet->pAlternates, sizeof(*pAnsSet->pAlternates) * (pAnsSet->cAnsSets + PHRASE_GROW_SIZE) );
|
||
|
}
|
||
|
|
||
|
ASSERT(pAnsSet->pAlternates);
|
||
|
if (! pAnsSet->pAlternates)
|
||
|
{
|
||
|
return HRCR_MEMERR;
|
||
|
}
|
||
|
|
||
|
pAlt = pAnsSet->pAlternates + pAnsSet->cAnsSets;
|
||
|
|
||
|
// Copy over the answers
|
||
|
memcpy(pAlt, &pXrc->answer, sizeof(*pAnsSet->pAlternates));
|
||
|
|
||
|
// Because we copied over the pointers to allocated
|
||
|
// memory make sure allocated buffers in the XRC are not freed
|
||
|
memset(&(pXrc->answer), 0, sizeof(pXrc->answer));
|
||
|
|
||
|
++pAnsSet->cAnsSets;
|
||
|
|
||
|
#ifndef NDEBUG
|
||
|
ValidateALTERNATES(pAlt);
|
||
|
#endif
|
||
|
|
||
|
}
|
||
|
|
||
|
DestroyHRC((HRC)pXrc);
|
||
|
|
||
|
return HRCR_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
/*******************************************************************************
|
||
|
*
|
||
|
* ProcessPhraseAlts
|
||
|
*
|
||
|
* Process phrase alternates. First collect all alternates that have
|
||
|
* a single word and post process them to get modified scores.
|
||
|
* Combine the best multiple words together into a single phrase
|
||
|
*
|
||
|
*******************************************************************************/
|
||
|
static int ProcessPhraseAlts(XRC *pXrc)
|
||
|
{
|
||
|
XRCRESULT *pRes;
|
||
|
unsigned int iAlt, cAlt;
|
||
|
int ret = HRCR_OK;
|
||
|
int cMultWord = 0;
|
||
|
ALTERNATES alt;
|
||
|
|
||
|
memset(&alt, 0, sizeof(alt));
|
||
|
|
||
|
alt.cAltMax = MAXMAXALT;
|
||
|
|
||
|
ASSERT(pXrc);
|
||
|
pRes = pXrc->answer.aAlt;
|
||
|
//cAlt = pXrc->answer.cAlt;
|
||
|
if (pXrc->iSpeed < 50)
|
||
|
{
|
||
|
ASSERT(pXrc->iSpeed >= 0);
|
||
|
cAlt = 10 - (7 * pXrc->iSpeed) / 50;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ASSERT(pXrc->iSpeed <= 100);
|
||
|
cAlt = 10 - (3 * pXrc->iSpeed) / 50 - 4;
|
||
|
}
|
||
|
|
||
|
cAlt = min(cAlt, pXrc->answer.cAlt);
|
||
|
|
||
|
// Dont bother if nothing was recognized
|
||
|
if ( cAlt <= 1)
|
||
|
{
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ASSERT(pRes);
|
||
|
|
||
|
|
||
|
for (iAlt = 0 ; iAlt < cAlt ; ++iAlt, ++pRes)
|
||
|
{
|
||
|
if (1 == pRes->cWords)
|
||
|
{
|
||
|
XRCRESULT *pWordRes = alt.aAlt + alt.cAlt;
|
||
|
|
||
|
memcpy(pWordRes, pRes, sizeof(*pRes));
|
||
|
|
||
|
++alt.cAlt;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// For now dont deal with multi-word phrases alternates
|
||
|
// Free their memory
|
||
|
FreeIdxWORDMAP(pRes);
|
||
|
if (pRes->pMap)
|
||
|
{
|
||
|
ExternFree(pRes->pMap);
|
||
|
}
|
||
|
if (pRes->szWord)
|
||
|
{
|
||
|
ExternFree(pRes->szWord);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Combiner recognizer scores
|
||
|
// for cases with a single word
|
||
|
ret = bNnonlyEnabledXRC(pXrc) ? HRCR_OK : CombineHMMScore(pXrc->pGlyph, &alt, pXrc->nfeatureset->iPrint);
|
||
|
|
||
|
if (ret != HRCR_OK)
|
||
|
{
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
// Run on multiple words and copy back results from single words
|
||
|
// WARNING: only copy back the number you asked the Post processor to
|
||
|
// process. So that the sosts for the unprocessed alternates will be on
|
||
|
// a very different scale
|
||
|
pRes = pXrc->answer.aAlt;
|
||
|
|
||
|
for (iAlt = 0 ; HRCR_OK == ret && iAlt < alt.cAlt ; ++iAlt, ++pRes)
|
||
|
{
|
||
|
memcpy(pRes, alt.aAlt + iAlt, sizeof(*pRes));
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Search for a frame by frame ID
|
||
|
static FRAME *FindFrame(GLYPH *pGlyph, int iFrame)
|
||
|
{
|
||
|
for ( ; pGlyph ; pGlyph = pGlyph->next)
|
||
|
{
|
||
|
if (pGlyph->frame && pGlyph->frame->iframe == iFrame)
|
||
|
{
|
||
|
return pGlyph->frame;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/***********************************************************************
|
||
|
*
|
||
|
* RecognizePhrase
|
||
|
*
|
||
|
* Run recognition on a "chunk" (or phrase) of ink. Calls the recognizer.
|
||
|
* If search comes back with a white space break. Then recurse till get
|
||
|
* single words
|
||
|
*
|
||
|
***********************************************************************/
|
||
|
static int RecognizePhrase(XRC *pMainXrc, GLYPH *pGlyph, int yDev, int iDepth, ANSWER_SET *pAnsSet)
|
||
|
{
|
||
|
int ret;
|
||
|
HRC full;
|
||
|
XRC *pXrc;
|
||
|
int bRedo = 0;
|
||
|
|
||
|
|
||
|
if (iDepth > 0)
|
||
|
{
|
||
|
// When called recursively use Word mode
|
||
|
|
||
|
ret = initWordHRC(pMainXrc, pGlyph, &full);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// First call use panel mode
|
||
|
|
||
|
ret = initPhraseHRC(pMainXrc, pGlyph, &full);
|
||
|
}
|
||
|
|
||
|
|
||
|
if (ret != HRCR_OK)
|
||
|
return ret;
|
||
|
|
||
|
pXrc = (XRC *)full;
|
||
|
|
||
|
ret = RecognizeWord(pXrc, yDev);
|
||
|
if (ret != HRCR_OK)
|
||
|
{
|
||
|
DestroyHRC(full);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
++iDepth;
|
||
|
|
||
|
// Check if any alternates have a word break
|
||
|
if (iDepth <= 1)
|
||
|
{
|
||
|
unsigned int i;
|
||
|
XRCRESULT *pRes = pXrc->answer.aAlt;
|
||
|
|
||
|
|
||
|
for (i = 0 ; i < pXrc->answer.cAlt && !bRedo ; ++i, ++pRes)
|
||
|
{
|
||
|
if (pRes->cWords > 1)
|
||
|
{
|
||
|
bRedo = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
// Did we get a multiword back?
|
||
|
if (pXrc->answer.aAlt->cWords > 1 || bRedo)
|
||
|
{
|
||
|
// Keep recursing till get no word breaks
|
||
|
unsigned int i;
|
||
|
XRCRESULT *pRes = pXrc->answer.aAlt;
|
||
|
|
||
|
for (i = 0 ; i < pRes->cWords && HRCR_OK == ret ; ++i)
|
||
|
{
|
||
|
int iStroke;
|
||
|
GLYPH *pNewGlyph = NULL;
|
||
|
WORDMAP *pMap = pRes->pMap + i;
|
||
|
|
||
|
ASSERT(pMap);
|
||
|
pNewGlyph = NewGLYPH();
|
||
|
|
||
|
if (!pNewGlyph)
|
||
|
{
|
||
|
ret = HRCR_MEMERR;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
for (iStroke = 0 ; pMap && iStroke < pMap->cStrokes ; ++iStroke)
|
||
|
{
|
||
|
FRAME *pFrame = FindFrame(pGlyph, pMap->piStrokeIndex[iStroke]);
|
||
|
|
||
|
ASSERT(pFrame);
|
||
|
if (!pFrame)
|
||
|
{
|
||
|
ret = HRCR_ERROR;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (!AddFrameGLYPH(pNewGlyph, pFrame))
|
||
|
{
|
||
|
ret = HRCR_MEMERR;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (HRCR_OK == ret)
|
||
|
{
|
||
|
ret = RecognizePhrase(pMainXrc, pNewGlyph, yDev, iDepth, pAnsSet);
|
||
|
}
|
||
|
|
||
|
if (pNewGlyph)
|
||
|
{
|
||
|
DestroyGLYPH(pNewGlyph);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Throw away this contect as we have delt with it word by word
|
||
|
DestroyHRC(full);
|
||
|
return (ret);
|
||
|
}
|
||
|
|
||
|
ret = ProcessPhraseAlts(pXrc);
|
||
|
|
||
|
if (ret != HRCR_OK)
|
||
|
{
|
||
|
DestroyHRC(full);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return isolate(pXrc, pAnsSet);
|
||
|
}
|
||
|
|
||
|
// Finds between-stroke gaps large enough to disallow grouping together within a single word,
|
||
|
// and calls RecognizePhrase() for each group of strokes between such gaps.
|
||
|
|
||
|
// Need to be able to go back and put a long-delayed stroke back in the right group.
|
||
|
// Anything stradling 2 groups, or between 2 groups, should be its own group.
|
||
|
|
||
|
// pGuide points to a 1-line guide that we already know our ink is within, so we don't need
|
||
|
// to compute the line number.
|
||
|
|
||
|
static int RecognizeLine ( XRC *pxrc,
|
||
|
INKLINE *pLine,
|
||
|
ANSWER_SET *pAnsSet
|
||
|
)
|
||
|
|
||
|
{
|
||
|
GLYPH **aGlyphs;
|
||
|
int cGlyphs = 0,
|
||
|
i,
|
||
|
last,
|
||
|
right = INT_MIN;
|
||
|
int maxgap;
|
||
|
|
||
|
GLYPH *pScaledGlyph = NULL;
|
||
|
int iRet = HRCR_ERROR;
|
||
|
int cOldAnsSet;
|
||
|
|
||
|
int yDev;
|
||
|
GUIDE OrigGuide;
|
||
|
|
||
|
if (!pxrc || !pLine || !pAnsSet || !pLine->pGlyph)
|
||
|
{
|
||
|
return HRCR_ERROR;
|
||
|
}
|
||
|
|
||
|
// init the output line segmentation
|
||
|
pLine->pResults = NULL;
|
||
|
|
||
|
// point to the guide
|
||
|
if (pxrc->bGuide)
|
||
|
{
|
||
|
OrigGuide = pxrc->guide;
|
||
|
|
||
|
// scale the ink & guide
|
||
|
pScaledGlyph = TranslateAndScaleLine (pLine, &pxrc->guide);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// scale the ink
|
||
|
pScaledGlyph = TranslateAndScaleLine (pLine, NULL);
|
||
|
}
|
||
|
|
||
|
// compute yDev of the scaled ink
|
||
|
yDev = YDeviation (pScaledGlyph);
|
||
|
|
||
|
// compute maxgap
|
||
|
maxgap = 11 * yDev / 2;
|
||
|
|
||
|
// Do the hard breaking of the line based on obvious gaps
|
||
|
aGlyphs = (GLYPH **)_alloca(CframeGLYPH(pScaledGlyph) * sizeof(GLYPH *));
|
||
|
|
||
|
for (; pScaledGlyph; pScaledGlyph = pScaledGlyph->next)
|
||
|
{
|
||
|
FRAME *pFrame = pScaledGlyph->frame;
|
||
|
RECT *pRect = RectFRAME(pFrame);
|
||
|
|
||
|
ASSERT((pRect->left - maxgap) >= INT_MIN);
|
||
|
if (right < (pRect->left - maxgap))
|
||
|
{
|
||
|
if (!(cGlyphs < MAXPHRASES))
|
||
|
goto exit;
|
||
|
|
||
|
aGlyphs[cGlyphs] = NewGLYPH();
|
||
|
|
||
|
if (!aGlyphs[cGlyphs])
|
||
|
goto exit;
|
||
|
|
||
|
aGlyphs[cGlyphs]->frame = pFrame;
|
||
|
right = pRect->right;
|
||
|
cGlyphs++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (!AddFrameGLYPH(aGlyphs[cGlyphs-1], pFrame))
|
||
|
goto exit;
|
||
|
|
||
|
if (right < pRect->right)
|
||
|
right = pRect->right;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cOldAnsSet = pAnsSet->cAnsSets;
|
||
|
|
||
|
last = cGlyphs - 1;
|
||
|
|
||
|
for (i = 0; i < cGlyphs; i++)
|
||
|
{
|
||
|
iRet = RecognizePhrase(pxrc, aGlyphs[i], yDev, 0, pAnsSet);
|
||
|
if (iRet != HRCR_OK)
|
||
|
goto exit;
|
||
|
|
||
|
DestroyGLYPH(aGlyphs[i]);
|
||
|
aGlyphs[i] = NULL;
|
||
|
}
|
||
|
|
||
|
// convert the last answerset alternates to linesegm
|
||
|
if (pAnsSet->cAnsSets > cOldAnsSet)
|
||
|
{
|
||
|
pLine->pResults = GenLineSegm (pAnsSet->cAnsSets - cOldAnsSet, pAnsSet->pAlternates + cOldAnsSet);
|
||
|
if (!pLine->pResults)
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
iRet = HRCR_OK;
|
||
|
|
||
|
exit:
|
||
|
|
||
|
// restore the xrc's guide
|
||
|
if (pxrc->bGuide)
|
||
|
{
|
||
|
pxrc->guide = OrigGuide;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < cGlyphs; i++)
|
||
|
{
|
||
|
if (aGlyphs[i])
|
||
|
{
|
||
|
DestroyGLYPH(aGlyphs[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return iRet;
|
||
|
}
|
||
|
|
||
|
/***********************************************************************
|
||
|
*
|
||
|
* BuildStringFromParts
|
||
|
*
|
||
|
* Merge the isolated words in a array of alternates into a single string
|
||
|
* and keep the alternates for each word in the compound
|
||
|
*************************************************************************/
|
||
|
int BuildStringFromParts(XRC *pXrc, ALTERNATES *ppWords, unsigned int cWords)
|
||
|
{
|
||
|
XRCRESULT *pRes;
|
||
|
WORDMAP *pMaps;
|
||
|
unsigned int len, pos;
|
||
|
char *sz;
|
||
|
int cStroke = 0;
|
||
|
int cTotStroke;
|
||
|
int *piIndex;
|
||
|
|
||
|
cTotStroke = CframeGLYPH(pXrc->pGlyph);
|
||
|
ASSERT(pXrc);
|
||
|
|
||
|
pRes = pXrc->answer.aAlt;
|
||
|
ASSERT(pRes);
|
||
|
|
||
|
pRes->cWords = cWords;
|
||
|
|
||
|
if (cWords <=0)
|
||
|
{
|
||
|
pRes->pMap = NULL;
|
||
|
pRes->szWord = NULL;
|
||
|
pXrc->answer.cAlt = 0;
|
||
|
return cWords;
|
||
|
}
|
||
|
|
||
|
pXrc->answer.cAlt = 1;
|
||
|
|
||
|
ASSERT(cWords);
|
||
|
|
||
|
pMaps = (WORDMAP *)ExternAlloc(sizeof(WORDMAP) * cWords);
|
||
|
ASSERT(pMaps);
|
||
|
if (!pMaps)
|
||
|
goto failure;
|
||
|
|
||
|
// Count total number of chars and number of strokes
|
||
|
// across all alternates
|
||
|
for (len = 0, pos = 0; pos < cWords; pos++)
|
||
|
{
|
||
|
if (ppWords[pos].cAlt)
|
||
|
{
|
||
|
len += strlen(ppWords[pos].aAlt[0].szWord) + 1;
|
||
|
|
||
|
if (ppWords[pos].aAlt[0].pMap)
|
||
|
{
|
||
|
cStroke += ppWords[pos].aAlt[0].pMap->cStrokes;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
ASSERT(len);
|
||
|
|
||
|
// ??? Are all strokes accounted for
|
||
|
ASSERT(cStroke == cTotStroke);
|
||
|
|
||
|
piIndex = (int *)ExternAlloc(sizeof(*pMaps->piStrokeIndex) * cStroke);
|
||
|
|
||
|
pRes->cWords = cWords;
|
||
|
pRes->pMap = pMaps;
|
||
|
pRes->szWord = (char *)ExternAlloc(len * sizeof(*pRes->szWord));
|
||
|
ASSERT(pRes->szWord);
|
||
|
|
||
|
if (!(pRes->szWord))
|
||
|
{
|
||
|
ExternFree(pMaps);
|
||
|
goto failure;
|
||
|
}
|
||
|
pRes->cost = 0;
|
||
|
pRes->pXRC = pXrc;
|
||
|
|
||
|
pos = 0;
|
||
|
sz = pRes->szWord;
|
||
|
|
||
|
// Finally build the string and
|
||
|
// set alternate lists for each word in the string
|
||
|
for (; cWords; cWords--, ppWords++, pMaps++)
|
||
|
{
|
||
|
int cAlt = ppWords->cAlt;
|
||
|
char *szWord;
|
||
|
XRCRESULT *pAltRes;
|
||
|
unsigned int iAlt;
|
||
|
|
||
|
// Should always have something recognized
|
||
|
ASSERT (cAlt > 0);
|
||
|
if (cAlt <= 0)
|
||
|
{
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
szWord = ppWords->aAlt[0].szWord;
|
||
|
|
||
|
if (pos)
|
||
|
{
|
||
|
pos++;
|
||
|
*sz++ = ' ';
|
||
|
}
|
||
|
|
||
|
pMaps->start = (unsigned short int)pos;
|
||
|
strcpy(sz, szWord);
|
||
|
|
||
|
pMaps->len = (unsigned short int)strlen(szWord);
|
||
|
pos += pMaps->len;
|
||
|
sz += pMaps->len;
|
||
|
|
||
|
if (cAlt > 0)
|
||
|
{
|
||
|
pMaps->cStrokes = ppWords->aAlt->pMap->cStrokes;
|
||
|
cStroke -= pMaps->cStrokes;
|
||
|
ASSERT(cStroke >= 0);
|
||
|
|
||
|
pMaps->piStrokeIndex = piIndex + cStroke;
|
||
|
|
||
|
memcpy(pMaps->piStrokeIndex, ppWords->aAlt->pMap->piStrokeIndex, sizeof(*pMaps->piStrokeIndex) * pMaps->cStrokes);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pMaps->cStrokes = 0;
|
||
|
ASSERT(cStroke >= 0);
|
||
|
pMaps->piStrokeIndex = piIndex + cStroke;
|
||
|
}
|
||
|
|
||
|
// Special Case Check for recognition failure
|
||
|
if (1 == cAlt && strcmp(szWord, NOT_RECOGNIZED) == 0)
|
||
|
{
|
||
|
// Free up the memory associated with the alternates
|
||
|
// because we now say there are 0 alternates
|
||
|
ExternFree(ppWords->aAlt[0].szWord);
|
||
|
ExternFree(ppWords->aAlt->pMap->piStrokeIndex);
|
||
|
ExternFree(ppWords->aAlt->pMap);
|
||
|
memset(&pMaps->alt, 0, sizeof(pMaps->alt));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
memcpy(&(pMaps->alt), ppWords, sizeof(ALTERNATES));
|
||
|
}
|
||
|
|
||
|
// Set correct backPointers for each alternate
|
||
|
pAltRes = pMaps->alt.aAlt;
|
||
|
for (iAlt = 0 ; iAlt < pMaps->alt.cAlt ; ++iAlt, ++pAltRes)
|
||
|
{
|
||
|
pAltRes->pXRC = pXrc;
|
||
|
}
|
||
|
|
||
|
pRes->cost += ppWords->aAlt->cost;
|
||
|
|
||
|
if (pRes->cost < 0)
|
||
|
pRes->cost = INT_MAX;
|
||
|
|
||
|
ppWords->cAlt = 0;
|
||
|
}
|
||
|
|
||
|
// Check that we have not forgot a terminating null
|
||
|
ASSERT(strlen(pRes->szWord) < 400);
|
||
|
ASSERT(strlen(pRes->szWord) < len);
|
||
|
|
||
|
ASSERT(cStroke == 0);
|
||
|
return 1;
|
||
|
|
||
|
failure:
|
||
|
pRes->cWords = 0;
|
||
|
pRes->pMap = NULL;
|
||
|
pRes->szWord = NULL;
|
||
|
pRes->cost = 0;
|
||
|
pRes->pXRC = NULL;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// Update the line information in an xrc presumeably after new ink had been added
|
||
|
BOOL UpdateLineInfo (XRC *pxrc)
|
||
|
{
|
||
|
BOOL bRet = FALSE;
|
||
|
GUIDE *pGuide = &(pxrc->guide);
|
||
|
LINEBRK LineBrk;
|
||
|
|
||
|
// do we have a guide?
|
||
|
if (pxrc->bGuide)
|
||
|
{
|
||
|
if (GuideLineSep (pxrc->pGlyph, pGuide, &LineBrk) < 1)
|
||
|
goto exit;
|
||
|
}
|
||
|
// We do not have a guide
|
||
|
else
|
||
|
{
|
||
|
// Run the nn line sep
|
||
|
if (NNLineSep (pxrc->pGlyph, &LineBrk) < 1)
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
// Allocate a line breaking structure in the XRC if needed
|
||
|
if (!pxrc->pLineBrk)
|
||
|
{
|
||
|
// alloc a line brk structure if needed
|
||
|
pxrc->pLineBrk = (LINEBRK *) ExternAlloc (sizeof (*pxrc->pLineBrk));
|
||
|
if (!pxrc->pLineBrk)
|
||
|
{
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
memset (pxrc->pLineBrk, 0, sizeof (*pxrc->pLineBrk));
|
||
|
}
|
||
|
|
||
|
// compare the lines with the old configuration
|
||
|
CompareLineBrk (&LineBrk, pxrc->pLineBrk);
|
||
|
|
||
|
// free the contents of the old structure
|
||
|
FreeLines (pxrc->pLineBrk);
|
||
|
|
||
|
// copy the new one
|
||
|
memcpy (pxrc->pLineBrk, &LineBrk, sizeof (*pxrc->pLineBrk));
|
||
|
|
||
|
bRet = TRUE;
|
||
|
|
||
|
exit:
|
||
|
return bRet;
|
||
|
}
|
||
|
|
||
|
// Performs recognition of a piece of ink in word mode
|
||
|
int WordModeRecognize (XRC *pxrc)
|
||
|
{
|
||
|
GLYPH *pglAll;
|
||
|
INKLINE *pLine;
|
||
|
GUIDE LocalGuide, *pLocalGuide;
|
||
|
int iRet;
|
||
|
XRCRESULT *pRes;
|
||
|
int cRes;
|
||
|
|
||
|
// check the validity of the xrc
|
||
|
if (!pxrc)
|
||
|
{
|
||
|
return HRCR_ERROR;
|
||
|
}
|
||
|
|
||
|
// Preset in case we abort
|
||
|
iRet = HRCR_ERROR;
|
||
|
pxrc->answer.cAlt = 0;
|
||
|
|
||
|
// save the original glyph
|
||
|
pglAll = pxrc->pGlyph;
|
||
|
|
||
|
// refresh the the line information
|
||
|
if (!CreateSingleLine (pxrc) || !pxrc->pLineBrk)
|
||
|
{
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
// point to the guide if any
|
||
|
if (pxrc->bGuide)
|
||
|
{
|
||
|
LocalGuide = pxrc->guide;
|
||
|
pLocalGuide = &LocalGuide;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pLocalGuide = NULL;
|
||
|
}
|
||
|
|
||
|
pLine = pxrc->pLineBrk->pLine;
|
||
|
|
||
|
// if this line is not dirty, or the line is empty then exit
|
||
|
if (!pLine->pGlyph || pLine->cStroke <= 0)
|
||
|
{
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
// Ink preprocessing: scale and translate the line ink if necessary
|
||
|
pxrc->pGlyph = TranslateAndScaleLine (pLine, pLocalGuide);
|
||
|
if (!pxrc->pGlyph)
|
||
|
{
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
// make sure that the line segmentation info is freed
|
||
|
if (pLine->pResults)
|
||
|
{
|
||
|
FreeLineSegm (pLine->pResults);
|
||
|
ExternFree (pLine->pResults);
|
||
|
|
||
|
pLine->pResults = NULL;
|
||
|
}
|
||
|
|
||
|
if (InfProcessHRC ((HRC)pxrc, -1) != HRCR_OK || pxrc->answer.cAlt <= 0)
|
||
|
{
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
pRes = pxrc->answer.aAlt;
|
||
|
cRes = pxrc->answer.cAlt;
|
||
|
|
||
|
if (!bNnonlyEnabledXRC(pxrc))
|
||
|
{
|
||
|
if (CombineHMMScore(pxrc->pGlyph, &(pxrc->answer), pxrc->nfeatureset->iPrint) != HRCR_OK)
|
||
|
{
|
||
|
goto exit;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// we no longer need this glyph
|
||
|
DestroyFramesGLYPH (pxrc->pGlyph);
|
||
|
|
||
|
// generate the line segmentation
|
||
|
// if this function fail, we'll fail as well
|
||
|
if (!WordModeGenLineSegm (pxrc))
|
||
|
{
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
iRet = HRCR_OK;
|
||
|
|
||
|
exit:
|
||
|
// restore back the original ink
|
||
|
pxrc->pGlyph = pglAll;
|
||
|
|
||
|
return iRet;
|
||
|
}
|
||
|
|
||
|
|
||
|
// recognizes a whole panel of ink
|
||
|
// first breaks the lines and then each line is recognized separately
|
||
|
int PanelModeRecognize (XRC *pxrc, DWORD dwRecoMode)
|
||
|
{
|
||
|
ANSWER_SET AnsSet;
|
||
|
INKLINE *pLine;
|
||
|
int iRet, iLine;
|
||
|
|
||
|
// check the validity of the xrc
|
||
|
if (!pxrc)
|
||
|
{
|
||
|
return HRCR_ERROR;
|
||
|
}
|
||
|
|
||
|
// Preset in case we abort
|
||
|
iRet = HRCR_ERROR;
|
||
|
pxrc->answer.cAlt = 0;
|
||
|
|
||
|
// init the AnsSet
|
||
|
memset(&AnsSet, 0, sizeof(AnsSet));
|
||
|
|
||
|
// Prepare the AnsSet
|
||
|
AnsSet.capSegments = 0;
|
||
|
AnsSet.cAnsSets = 0;
|
||
|
AnsSet.pAlternates = NULL;
|
||
|
|
||
|
// refresh the the line information
|
||
|
if (!UpdateLineInfo (pxrc) || !pxrc->pLineBrk)
|
||
|
{
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
// go thru all the lines
|
||
|
for (iLine = 0; iLine < pxrc->pLineBrk->cLine; iLine++)
|
||
|
{
|
||
|
pLine = pxrc->pLineBrk->pLine + iLine;
|
||
|
|
||
|
// if this line empty then skip it
|
||
|
if (!pLine->pGlyph || pLine->cStroke <= 0)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// make sure that the line segmentation info is freed
|
||
|
if (pLine->pResults)
|
||
|
{
|
||
|
FreeLineSegm (pLine->pResults);
|
||
|
ExternFree (pLine->pResults);
|
||
|
|
||
|
pLine->pResults = NULL;
|
||
|
}
|
||
|
|
||
|
// Recognize this line
|
||
|
iRet = RecognizeLine (pxrc, pLine, &AnsSet);
|
||
|
if (iRet != HRCR_OK)
|
||
|
{
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
// if we are in partial incremental mode, then we stop processing here
|
||
|
if (dwRecoMode == RECO_MODE_INCREMENTAL)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
} // iLine Loop
|
||
|
|
||
|
|
||
|
exit:
|
||
|
// if we succeeded, build the answer
|
||
|
if (iRet == HRCR_OK)
|
||
|
{
|
||
|
BuildStringFromParts(pxrc, AnsSet.pAlternates, AnsSet.cAnsSets);
|
||
|
}
|
||
|
|
||
|
// free the answer set
|
||
|
ExternFree(AnsSet.pAlternates);
|
||
|
|
||
|
return iRet;
|
||
|
}
|