diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/libscribble |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/libscribble')
-rwxr-xr-x | sys/src/libscribble/graffiti.c | 266 | ||||
-rwxr-xr-x | sys/src/libscribble/graffiti.h | 21 | ||||
-rwxr-xr-x | sys/src/libscribble/hre_api.c | 1300 | ||||
-rwxr-xr-x | sys/src/libscribble/hre_internal.h | 149 | ||||
-rwxr-xr-x | sys/src/libscribble/li_recognizer.c | 2313 | ||||
-rwxr-xr-x | sys/src/libscribble/li_recognizer_internal.h | 42 | ||||
-rwxr-xr-x | sys/src/libscribble/mkfile | 31 | ||||
-rwxr-xr-x | sys/src/libscribble/scribbleimpl.h | 417 |
8 files changed, 4539 insertions, 0 deletions
diff --git a/sys/src/libscribble/graffiti.c b/sys/src/libscribble/graffiti.c new file mode 100755 index 000000000..5ccc4a38f --- /dev/null +++ b/sys/src/libscribble/graffiti.c @@ -0,0 +1,266 @@ +/* + * Graffiti.c is based on the file Scribble.c copyrighted + * by Keith Packard: + * + * Copyright © 1999 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Keith Packard not be used in + * advertising or publicity pertaining to distribution of the software without + * specific, written prior permission. Keith Packard makes no + * representations about the suitability of this software for any purpose. It + * is provided "as is" without express or implied warranty. + * + * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <scribble.h> + +#include "scribbleimpl.h" +#include "graffiti.h" + +int ScribbleDebug; + +char *cl_name[3] = { + DEFAULT_LETTERS_FILE, + DEFAULT_DIGITS_FILE, + DEFAULT_PUNC_FILE +}; + +Rune +recognize (Scribble *s) +{ + struct graffiti *graf = s->graf; + Stroke *ps = &s->ps; + Rune rune; + int c; + int nr; + rec_alternative *ret; + + if (ps->npts == 0) + return '\0'; + + c = recognizer_translate( + graf->rec[s->puncShift ? CS_PUNCTUATION : s->curCharSet], + 1, ps, false, &nr, &ret); + if (c != -1) + delete_rec_alternative_array(nr, ret, false); + + rune = '\0'; + + switch (c) { + case '\0': + if(ScribbleDebug)fprint(2, "(case '\\0')\n"); + break; + case 'A': /* space */ + rune = ' '; + if(ScribbleDebug)fprint(2, "(case A) character = ' %C' (0x%x)\n", rune, rune); + break; + case 'B': /* backspace */ + rune = '\b'; + if(ScribbleDebug)fprint(2, "(case B) character = \\b (0x%x)\n", rune); + break; + case 'N': /* numlock */ + if(ScribbleDebug)fprint(2, "(case N)\n"); + if (s->curCharSet == CS_DIGITS) { + s->curCharSet = CS_LETTERS; + } else { + s->curCharSet = CS_DIGITS; + } + s->tmpShift = 0; + s->puncShift = 0; + s->ctrlShift = 0; + break; + case 'P': /* usually puncshift, but we'll make it CTRL */ + if(ScribbleDebug)fprint(2, "(case P)\n"); + s->ctrlShift = !s->ctrlShift; + s->tmpShift = 0; + s->puncShift = 0; + break; + case 'R': /* newline */ + rune = '\n'; + if(ScribbleDebug)fprint(2, "(case R) character = \\n (0x%x)\n", rune); + break; + case 'S': /* shift */ + if(ScribbleDebug)fprint(2, "(case S)\n"); + s->puncShift = 0; + s->ctrlShift = 0; + if (s->capsLock) { + s->capsLock = 0; + s->tmpShift = 0; + break; + } + if (s->tmpShift == 0) { + s->tmpShift++; + break; + } + /* fall through */ + case 'L': /* caps lock */ + if(ScribbleDebug)fprint(2, "(case L)\n"); + s->capsLock = !s->capsLock; + break; + case '.': /* toggle punctuation mode */ + if (s->puncShift) { + s->puncShift = 0; + } else { + s->puncShift = 1; + s->ctrlShift = 0; + s->tmpShift = 0; + return rune; + } + rune = '.'; + if(0)fprint(2, "(case .) character = %c (0x%x)\n", rune, rune); + break; + default: + if ('A' <= c && c <= 'Z') { + if(ScribbleDebug)fprint(2, "(bad case?) character = %c (0x%x)\n", c, c); + return rune; + } + rune = c; + if (s->ctrlShift) + { + if (c < 'a' || 'z' < c) + { + if(ScribbleDebug)fprint(2, "(default) character = %c (0x%x)\n", rune, rune); + return rune; + } + rune = rune & 0x1f; + } else if ((s->capsLock && !s->tmpShift) || + (!s->capsLock && s->tmpShift)) + { + if (rune < 0xff) + rune = toupper(rune); + } + s->tmpShift = 0; + s->puncShift = 0; + s->ctrlShift = 0; + if(ScribbleDebug)fprint(2, "(default) character = %c (0x%x)\n", rune, rune); + } + return rune; +} + +/* This procedure is called to initialize pg by loading the three + * recognizers, loading the initial set of three classifiers, and + * loading & verifying the recognizer extension functions. If the + * directory $HOME/.recognizers exists, the classifier files will be + * loaded from that directory. If not, or if there is an error, the + * default files (directory specified in Makefile) will be loaded + * instead. Returns non-zero on success, 0 on failure. (Adapted from + * package tkgraf/src/GraffitiPkg.c. + */ + +static int +graffiti_load_recognizers(struct graffiti *pg) +{ + bool usingDefault; + char* homedir; + int i; + rec_fn *fns; + + /* First, load the recognizers... */ + /* call recognizer_unload if an error ? */ + for (i = 0; i < NUM_RECS; i++) { + /* Load the recognizer itself... */ + pg->rec[i] = recognizer_load(DEFAULT_REC_DIR, "", nil); + if (pg->rec[i] == nil) { + fprint(2,"Error loading recognizer from %s.", DEFAULT_REC_DIR); + return 0; + } + if ((* (int *)(pg->rec[i])) != 0xfeed) { + fprint(2,"Error in recognizer_magic."); + return 0; + } + } + + /* ...then figure out where the classifiers are... */ + if ( (homedir = (char*)getenv("home")) == nil ) { + if(0)fprint(2, "no homedir, using = %s\n", REC_DEFAULT_USER_DIR); + strecpy(pg->cldir, pg->cldir+sizeof pg->cldir, REC_DEFAULT_USER_DIR); + usingDefault = true; + } else { + if(0)fprint(2, "homedir = %s\n", homedir); + snprint(pg->cldir, sizeof pg->cldir, "%s/%s", homedir, CLASSIFIER_DIR); + usingDefault = false; + } + + /* ...then load the classifiers... */ + for (i = 0; i < NUM_RECS; i++) { + int rec_return; + char *s; + + rec_return = recognizer_load_state(pg->rec[i], pg->cldir, cl_name[i]); + if ((rec_return == -1) && (usingDefault == false)) { + if(0)fprint(2, "Unable to load custom classifier file %s/%s.\nTrying default classifier file instead.\nOriginal error: %s\n ", + pg->cldir, cl_name[i], + (s = recognizer_error(pg->rec[i])) ? s : "(none)"); + rec_return = recognizer_load_state(pg->rec[i], + REC_DEFAULT_USER_DIR, cl_name[i]); + } + if (rec_return == -1) { + fprint(2, "Unable to load default classifier file %s.\nOriginal error: %s\n", + cl_name[i], + (s = recognizer_error(pg->rec[i])) ? s : "(none)"); + return 0; + } + } + + /* We have recognizers and classifiers now. */ + /* Get the vector of LIextension functions.. */ + fns = recognizer_get_extension_functions(pg->rec[CS_LETTERS]); + if (fns == nil) { + fprint(2, "LI Recognizer Training:No extension functions!"); + return 0; + } + + /* ... and make sure the training & get-classes functions are okay. */ + if( (pg->rec_train = (li_recognizer_train)fns[LI_TRAIN]) == nil ) { + fprint(2, + "LI Recognizer Training:li_recognizer_train() not found!"); + if (fns != nil) { + free(fns); + } + return 0; + } + + if( (pg->rec_getClasses = (li_recognizer_getClasses)fns[LI_GET_CLASSES]) == nil ) { + fprint(2, + "LI Recognizer Training:li_recognizer_getClasses() not found!"); + if (fns != nil) { + free(fns); + } + return 0; + } + free(fns); + return 1; +} + +Scribble * +scribblealloc(void) +{ + Scribble *s; + + s = mallocz(sizeof(Scribble), 1); + if (s == nil) + sysfatal("Initialize: %r"); + s->curCharSet = CS_LETTERS; + + s->graf = mallocz(sizeof(struct graffiti), 1); + if (s->graf == nil) + sysfatal("Initialize: %r"); + + graffiti_load_recognizers(s->graf); + + return s; +} diff --git a/sys/src/libscribble/graffiti.h b/sys/src/libscribble/graffiti.h new file mode 100755 index 000000000..4c409ea19 --- /dev/null +++ b/sys/src/libscribble/graffiti.h @@ -0,0 +1,21 @@ + +#define NUM_RECS 3 +#define DEFAULT_REC_DIR "classsifiers" +#define REC_DEFAULT_USER_DIR "/sys/lib/scribble/classifiers" +#define CLASSIFIER_DIR "lib/classifiers" +#define DEFAULT_LETTERS_FILE "letters.cl" +#define DEFAULT_DIGITS_FILE "digits.cl" +#define DEFAULT_PUNC_FILE "punc.cl" + +struct graffiti { + /* 3 recognizers, one each for letters, digits, and punctuation: */ + recognizer rec[3]; + /* directory in which the current classifier files are found: */ + char cldir[200]; + /* pointer to training function: */ + li_recognizer_train rec_train; + /* pointer to function that lists the characters in the classifier file */ + li_recognizer_getClasses rec_getClasses; +}; + +extern char *cl_name[3]; diff --git a/sys/src/libscribble/hre_api.c b/sys/src/libscribble/hre_api.c new file mode 100755 index 000000000..240dcdcb2 --- /dev/null +++ b/sys/src/libscribble/hre_api.c @@ -0,0 +1,1300 @@ +/* + * hre_api.c: Implementation of HRE API + * Author: James & + * Created On: Wed Dec 9 13:49:14 1992 + * Last Modified By: James Kempf + * Last Modified On: Fri Sep 23 13:49:04 1994 + * Update Count: 137 + * Copyright (c) 1994 by Sun Microsystems Computer Company + * All rights reserved. + * + * Use and copying of this software and preparation of + * derivative works based upon this software are permitted. + * Any distribution of this software or derivative works + * must comply with all applicable United States export control + * laws. + * + * This software is made available as is, and Sun Microsystems + * Computer Company makes no warranty about the software, its + * performance, or its conformity to any specification + */ + +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <scribble.h> + +#include "scribbleimpl.h" +#include "hre_internal.h" + +/* ari -- prototype for rii function */ +recognizer __recognizer_internal_initialize(rec_info* ri); + +/*Version number of API.*/ + +char* REC_VERSION = "2.0"; + +/*Domain name for internationalized text.*/ + +#define INTL_DOMAIN "recognition_manager" + +/* XXX -- Intl Hack -- Jay & Ari */ +#define dgettext(domain, msg) (msg) +#define bindtextdomain(dirname, domain) + +/* + * These magic numbers are used to ensure the integrity of the + * recognizer structure. +*/ + + +#define REC_MAGIC 0xfeed +#define REC_END_MAGIC 0xbeef + +/*Check the recognizer for validity*/ + +#define RI_CHECK_MAGIC(rec) \ + ( (rec != nil) && \ + (((recognizer)rec)->recognizer_magic == REC_MAGIC) && \ + (((recognizer)rec)->recognizer_end_magic == REC_END_MAGIC) &&\ + (((recognizer)rec)->recognizer_version == REC_VERSION) ) + +/*The name of the initialization & finalization functions.*/ + +/* static char rii_name[] = "__recognizer_internal_initialize"; +static char rif_name[] = "__recognizer_internal_finalize"; */ + +/*User home directory for recognizer info.*/ +/* ari -- changed USERRECHOME from ".recognizers" */ +#define HOME "HOME" +#define USERRECHOME ".classifiers" + +/*Local functions*/ + +static char* shared_library_name(char* directory,char* locale,char* name); +static rec_info* make_rec_info(char* directory,char* name,char** subset); +static void delete_rec_info(rec_info* ri); +static int check_for_user_home(void); +static void intl_initialize(void); + +static void cleanup_rec_element(rec_element* re,bool delete_points_p); + +/*The last error.*/ + +static char* the_last_error = nil; + +static char *safe_malloc (int nbytes) +{ + char *res = malloc(nbytes); + if (res == nil) { + sysfatal("malloc failure"); + } + return (res); +} + + +/* + * Implementation of API functions +*/ + +/* + * recognizer_load - Load the recognizer matching the rec_info struct. + * If name is not null, then load the recognizer having that name. Returns + * the recognizer object, or null if it can't load the recognizer, and + * sets errno to indicate why. +*/ + +recognizer +recognizer_load(char* directory, char* name, char** subset) +{ + recognizer rec; /*the recognizer*/ + rec_info* rinf; /*rec_info for recognizer information*/ + static bool intl_init = false; /*true if recog. manager initted.*/ + + if( intl_init == false ) { + intl_init = true; + intl_initialize(); + } + + /*The name takes precedence.*/ + rinf = make_rec_info(directory, name, subset); + if (rinf == nil) { + the_last_error = + dgettext(INTL_DOMAIN, + "Ran out of memory during prelinking initialization."); + return((recognizer)nil); + } +/* fprint(2, "Got past make_rec_info.\n"); */ + + /*Let recognition code create recognizer and initialize*/ + rec = __recognizer_internal_initialize(rinf); + if (rec == nil) { + return((recognizer)nil); + } +/* fprint(2, "Did rii.\n"); */ + /*Check whether it's been correctly initialized*/ + + if( rec->recognizer_load_state == nil || + rec->recognizer_save_state == nil || + rec->recognizer_load_dictionary == nil || + rec->recognizer_save_dictionary == nil || + rec->recognizer_free_dictionary == nil || + rec->recognizer_add_to_dictionary == nil || + rec->recognizer_delete_from_dictionary == nil || + rec->recognizer_error == nil || + rec->recognizer_set_context == nil || + rec->recognizer_get_context == nil || + rec->recognizer_clear == nil || + rec->recognizer_get_buffer == nil || + rec->recognizer_set_buffer == nil || + rec->recognizer_translate == nil || + rec->recognizer_get_extension_functions == nil || + rec->recognizer_get_gesture_names == nil || + rec->recognizer_set_gesture_action == nil + ) { + + recognizer_unload(rec); +/* fprint(2, "Unloading b/c null function pointer.\n"); */ + the_last_error = + dgettext(INTL_DOMAIN, + "One or more recognizer function pointers is nil."); + return((recognizer)nil); + } + + + /*Set the rec_info structure.*/ + + rec->recognizer_info = rinf; + + /*Check whether home directory is there for recognizer info.*/ + +/* + * ari -- don't bother. We're not going to load from each user's + * home directory at this point. Instead, we'll use a stupid + * little a-b-c file because it loads FAST. + * + * if( check_for_user_home() < 0 ) { + * recognizer_unload(rec); + * return((recognizer)nil); + * } + */ + /*We got it!*/ +/* fprint(2, "Done.\n"); */ + + return(rec); +} + +/* + * recognizer_unload - Unload the recognizer. +*/ + +int +recognizer_unload(recognizer rec) +{ + /*Make sure magic numbers right.*/ + + if( !RI_CHECK_MAGIC(rec) ) { + the_last_error = dgettext(INTL_DOMAIN,"Bad recognizer object."); + return(-1); + } + + return __recognizer_internal_finalize(rec); +} + +/* + * recognizer_load_state-Get any recognizer state associated with name + * in dir. Note that name may not be simple file name, since + * there may be more than one file involved. Return 0 if successful, + * -1 if not. +*/ + +int recognizer_load_state(recognizer rec, char* dir, char* name) +{ + /*Make sure magic numbers right.*/ + + if( !RI_CHECK_MAGIC(rec) ) { + the_last_error = dgettext(INTL_DOMAIN,"Bad recognizer object."); + return(-1); + } + + /*Do the function.*/ + + return(rec->recognizer_load_state(rec, dir, name)); +} + +/* + * recognizer_save_state-Save any recognizer state to name + * in dir. Note that name may not be a simple file name, since + * there may be more than one file involved. Return 0 if successful, + * -1 if not. +*/ + +int recognizer_save_state(recognizer rec,char* dir,char* name) +{ + /*Make sure magic numbers right.*/ + + if( !RI_CHECK_MAGIC(rec) ) { + the_last_error = dgettext(INTL_DOMAIN,"Bad recognizer object."); + return(-1); + } + + /*Do the function.*/ + + return(rec->recognizer_save_state(rec,dir,name)); +} + +/* + * recognizer_load_dictionary-Load dictionary, return pointer + * to it, or nil if error. +*/ + +wordset recognizer_load_dictionary(recognizer rec,char* dir,char* name) +{ + /*Make sure magic numbers right.*/ + + if( !RI_CHECK_MAGIC(rec) ) { + the_last_error = dgettext(INTL_DOMAIN,"Bad recognizer object."); + return(nil); + } + + /*Do the function.*/ + + return(rec->recognizer_load_dictionary(rec,dir,name)); +} + +/* + * recognizer_save_dictionary-Save the dictionary to the file, return 0 if + * OK, -1 if error. +*/ + +int recognizer_save_dictionary(recognizer rec,char* dir,char* name,wordset dict) +{ + /*Make sure magic numbers right.*/ + + if( !RI_CHECK_MAGIC(rec) ) { + the_last_error = dgettext(INTL_DOMAIN,"Bad recognizer object."); + return(-1); + } + + /*Do the function.*/ + + return(rec->recognizer_save_dictionary(rec,dir,name,dict)); +} + +/* + * recognizer_free_dictionary-Free the dictionary, return 0 if + * OK, -1 if error. +*/ + +int recognizer_free_dictionary(recognizer rec,wordset dict) +{ + /*Make sure magic numbers right.*/ + + if( !RI_CHECK_MAGIC(rec) ) { + the_last_error = dgettext(INTL_DOMAIN,"Bad recognizer object."); + return(-1); + } + + /*Do the function.*/ + + return(rec->recognizer_free_dictionary(rec,dict)); +} + +/* + * recognizer_add_to_dictionary-Add word to the dictionary, + * return 0 if OK, -1 if error. +*/ + + +int recognizer_add_to_dictionary(recognizer rec,letterset* word,wordset dict) +{ + /*Make sure magic numbers right.*/ + + if( !RI_CHECK_MAGIC(rec) ) { + the_last_error = dgettext(INTL_DOMAIN,"Bad recognizer object."); + return(-1); + } + + /*Do the function.*/ + + return(rec->recognizer_add_to_dictionary(rec,word,dict)); +} + +/* + * recognizer_delete_from_dictionary-Delete word from the dictionary, + * return 0 if OK, -1 if error. +*/ + +int +recognizer_delete_from_dictionary(recognizer rec,letterset* word,wordset dict) +{ + /*Make sure magic numbers right.*/ + + if( !RI_CHECK_MAGIC(rec) ) { + the_last_error = dgettext(INTL_DOMAIN,"Bad recognizer object."); + return(-1); + } + + /*Do the function.*/ + + return(rec->recognizer_delete_from_dictionary(rec,word,dict)); +} + +/* + * recognizer_get_info-Get a pointers to the rec_info + * giving the locales and subsets supported by the recognizer + * and the shared library pathname. +*/ + +const rec_info* +recognizer_get_info(recognizer rec) +{ + /*Make sure magic numbers right.*/ + + if( !RI_CHECK_MAGIC(rec) ) { + the_last_error = dgettext(INTL_DOMAIN,"Bad recognizer object."); + return((rec_info*)nil); + } + + /*Return the rec_info object.*/ + + return(rec->recognizer_info); +} + +/* + * recognizer_manager_version-Return the version number string of the + * recognition manager. +*/ + +const char* recognizer_manager_version(recognizer rec) +{ + /*Make sure magic numbers right.*/ + + if( !RI_CHECK_MAGIC(rec) ) { + the_last_error = dgettext(INTL_DOMAIN,"Bad recognizer object."); + return(nil); + } + + return(rec->recognizer_version); + +} +/* + * recognizer_error-Return the last error message, or nil if none. +*/ + +char* recognizer_error(recognizer rec) +{ + + /*Make sure magic numbers right and function there.*/ + + if( !RI_CHECK_MAGIC(rec) && the_last_error == nil ) { + return(dgettext(INTL_DOMAIN,"Bad recognizer object.")); + + } else if( the_last_error != nil ) { + char* error = the_last_error; + + the_last_error = nil; + return(error); + } + + /*Do the function.*/ + + return(rec->recognizer_error(rec)); +} + +/* + * recognizer_set_context-Set the recognition context for translation. + * Return 0 if successful, -1 if error. +*/ + +int recognizer_set_context(recognizer rec,rc* rec_xt) +{ + + /*Make sure magic numbers right.*/ + + if( !RI_CHECK_MAGIC(rec) ) { + the_last_error = dgettext(INTL_DOMAIN,"Bad recognizer object."); + return(-1); + } + + /*Do the function.*/ + + return(rec->recognizer_set_context(rec,rec_xt)); +} + +/* + * recognzier_get_context-Get the recognition context for translation. + * If none or error, return nil. +*/ + +rc* recognizer_get_context(recognizer rec) +{ + + /*Make sure magic numbers right.*/ + + if( !RI_CHECK_MAGIC(rec) ) { + the_last_error = dgettext(INTL_DOMAIN,"Bad recognizer object."); + return(nil); + } + + /*Do the function.*/ + + return(rec->recognizer_get_context(rec)); +} + +/* + * recognizer_clear-Clear buffer and recognition context. + * Return 0 if success, else -1. +*/ + +int recognizer_clear(recognizer rec,bool delete_points_p) +{ + + /*Make sure magic numbers right.*/ + + if( !RI_CHECK_MAGIC(rec) ) { + the_last_error = dgettext(INTL_DOMAIN,"Bad recognizer object."); + return(-1); + } + + /*Do the function.*/ + + return(rec->recognizer_clear(rec,delete_points_p)); +} + +/*recognizer_get_buffer-Get stroke buffer. Return 0 if success, else -1.*/ + + +int recognizer_get_buffer(recognizer rec, uint* nstrokes,Stroke** strokes) +{ + + /*Make sure magic numbers right.*/ + + if( !RI_CHECK_MAGIC(rec) ) { + the_last_error = dgettext(INTL_DOMAIN,"Bad recognizer object."); + return(-1); + } + + /*Do the function.*/ + + return(rec->recognizer_get_buffer(rec,nstrokes,strokes)); + +} + +/* + * recognizer_set_buffer-Set stroke buffer to arg. Return 0 if success, else + * return -1. +*/ + +int recognizer_set_buffer(recognizer rec,uint nstrokes,Stroke* strokes) +{ + + /*Make sure magic numbers right.*/ + + if( !RI_CHECK_MAGIC(rec) ) { + the_last_error = dgettext(INTL_DOMAIN,"Bad recognizer object."); + return(-1); + } + + /*Do the function.*/ + + return(rec->recognizer_set_buffer(rec,nstrokes,strokes)); + +} + +/* + * recognizer_translate-Translate the strokes in the current context, including + * buffered strokes. If nstrokes == 0 or strokes == nil, return + * translation of stroke buffer. +*/ + +int recognizer_translate(recognizer rec, + uint nstrokes, + Stroke* strokes, + bool correlate_p, + int* nret, + rec_alternative** ret) +{ + int retval; + char msg[80]; + /*Make sure magic numbers right.*/ + + if( !RI_CHECK_MAGIC(rec) ) { + the_last_error = dgettext(INTL_DOMAIN, msg); + return(-1); + } + +/* ari */ +/* { + * uint i; + * Stroke ari_pstr; + * pen_point* ari_pts; + * int ari; + * for (i = 0; i < nstrokes; i++) { + * ari_pstr = strokes[i]; + * ari_pts = ari_pstr.ps_pts; + * fprint(2, "\nrecognizer_translate: ari_pts = %ld, sizeof(Time) = %d, sizeof(ari_pts[0] = %d, %d points are...\n", ari_pts, sizeof(Time), sizeof(ari_pts[0]), ari_pstr.ps_npts); + * for (ari = 0; ari < ari_pstr.ps_npts; ari++) + * fprint(2, "%ld -- (%d, %d) ", ari_pts[ari], ari_pts[ari].x, ari_pts[ari].y); + * } + * } +*/ + /*Do the function.*/ +/* ari -- this is calling cmu_recognizer_translate */ + retval = rec->recognizer_translate(rec, + nstrokes, + strokes, + correlate_p, + nret, + ret); + return (retval); +} + + +/* + * recognizer_get_extension_functions-Return a null terminated array + * of functions providing extended functionality. Their interfaces + * will change depending on the recognizer. +*/ + +rec_fn* recognizer_get_extension_functions(recognizer rec) +{ + /*Make sure magic numbers right.*/ + + if( !RI_CHECK_MAGIC(rec) ) { + the_last_error = dgettext(INTL_DOMAIN,"Bad recognizer object."); + return((rec_fn*)nil); + } + + /*Do the function.*/ + + return(rec->recognizer_get_extension_functions(rec)); +} + + +/* + * recognizer_get_gesture_names - Return a null terminated array of + * gesture name strings. +*/ + +char** +recognizer_get_gesture_names(recognizer rec) +{ + /*Make sure magic numbers right.*/ + + if( !RI_CHECK_MAGIC(rec) ) { + the_last_error = dgettext(INTL_DOMAIN,"Bad recognizer object."); + return(nil); + } + + /*Do the function.*/ + + return(rec->recognizer_get_gesture_names(rec)); +} + +/* + * recognizer_set_gesture_action-Set the action function for the gesture. +*/ + +xgesture +recognizer_train_gestures(recognizer rec,char* name,xgesture fn,void* wsinfo) +{ + /*Make sure magic numbers right.*/ + + if( !RI_CHECK_MAGIC(rec) ) { + the_last_error = dgettext(INTL_DOMAIN,"Bad recognizer object."); + return((xgesture)-1); + } + + /*Do the function.*/ + + return(rec->recognizer_set_gesture_action(rec,name,fn,wsinfo)); +} + +/* + * Local functions. +*/ + +/* + * shared_library_name-Get the full pathname to the shared library, + * based on the recognizer name and the environment. +*/ + + +static char* shared_library_name(char* directory,char* locale,char* name) +{ + char* ret; + int len = strlen(name); + + /*If directory is there, it takes precedence.*/ + + if( directory != nil ) { + ret = (char*)safe_malloc(strlen(directory) + len + 2); + strcpy(ret,directory); + strcat(ret,"/"); + strcat(ret,name); + } else { + char* dir; + + /*First try the environment variable.*/ + + if( (dir = getenv(RECHOME)) == nil ) { + dir = "REC_DEFAULT_HOME_DIR"; + + } + + ret = (char*)safe_malloc(strlen(dir) + strlen(locale) + len + 3); + /*Form the pathname.*/ + strcpy(ret,dir); + strcat(ret,"/"); + strcat(ret,locale); + strcat(ret,"/"); + strcat(ret,name); + } + + return(ret); +} + +/* + * intl_initialize-Initialize the internationaliztion of messages for + * the recognition manager. +*/ + +static void intl_initialize(void) +{ + char* dirname; + + /*Get recognizer home directory name from environment.*/ + + if( (dirname = getenv(RECHOME)) == nil ) { + dirname = "REC_DEFAULT_HOME_DIR"; + } + + /*Bind the text domain.*/ + USED(dirname); + bindtextdomain(dirname, INTL_DOMAIN); +} + + +/*make_rec_info-Create a rec_info structure*/ + +static rec_info* make_rec_info(char*, char*, char** subset) +{ + int i,len; + rec_info* ri; + char* locale; + + ri = (rec_info*)safe_malloc(sizeof(rec_info)); + ri->ri_locale = nil; + ri->ri_name = nil; + ri->ri_subset = nil; + + /*Get locale*/ + + if( (locale = getenv(LANG)) == nil ) { + locale = strdup(REC_DEFAULT_LOCALE); + } + + if( (ri->ri_locale = strdup(locale)) == nil ) { + delete_rec_info(ri); + return(nil); + } + + /*Get shared library pathname.*/ + + /*Initialize the subset information.*/ + + if( subset != nil ) { + + /*Count the subset strings.*/ + + for( len = 1; subset[len] != nil; len++ ) ; + + /*Copy the subset strings.*/ + + ri->ri_subset = (char**)safe_malloc((len +1)*sizeof(char*)); + + for( i = 0; i < len; i++ ) { + if( subset[i] != nil ) { + if( (ri->ri_subset[i] = strdup(subset[i])) == nil ) { + delete_rec_info(ri); + return(nil); + } + } else { + ri->ri_subset[i] = subset[i]; + } + } + + ri->ri_subset[i] = nil; + + } else { + + ri->ri_subset = nil; + } + + return(ri); +} + +static void delete_rec_info(rec_info* ri) +{ + if( ri != nil ) { + if( ri->ri_locale != nil ) { + free(ri->ri_locale); + } +/* + * if( ri->ri_name != nil ) { + * free(ri->ri_name); + * } + */ + if( ri->ri_subset != nil ) { + int i; + for( i = 0; ri->ri_subset[i] != nil; i++) { + free(ri->ri_subset[i]); + } + free(ri->ri_subset); + } + free(ri); + } +} + +/*check_for_user_home-Check whether USERRECHOME has been created.*/ + +static int check_for_user_home() +{ + char* homedir = getenv(HOME); + char* rechome; + Dir *dir; + + if( homedir == nil ) { + the_last_error = "Home environment variable HOME not set."; + return(-1); + } + + rechome = (char*)safe_malloc(strlen(homedir) + strlen(USERRECHOME) + 2); + + /*Form name.*/ + + strcpy(rechome,homedir); + strcat(rechome,"/"); + strcat(rechome,USERRECHOME); + + /*Create directory.*/ + + dir = dirstat(rechome); + if (dir != nil) { + if (dir->mode & DMDIR) { + free(dir); + free(rechome); + return 0; + } + free(dir); + } else { + int fd; + if ((fd = create(rechome, OREAD, DMDIR|0755)) >= 0) { + close(fd); + free(rechome); + return(0); + } + } + free(rechome); + return(-1); +} + +/* + * Constructor functions for making structures. + * + * The general philosophy here is that we control all memory + * in connected data structures, *except* for pen_point arrays. + * There are likely to be lots and lots of points, they are likely + * to come from the window system; so if we wanted to control them, + * we would have to copy which would be slow. We require the client + * to deal with them directly, or the client can give us permission + * to delete them. +*/ + +/* + * recognizer +*/ + + +recognizer make_recognizer(rec_info* rif) +{ + recognizer rec; + + /*Allocate it.*/ + + rec = (recognizer)safe_malloc(sizeof(*rec)); + rec->recognizer_magic = REC_MAGIC; + rec->recognizer_version = REC_VERSION; + rec->recognizer_info = rif; + rec->recognizer_specific = nil; + rec->recognizer_end_magic = REC_END_MAGIC; + rec->recognizer_load_state = nil; + rec->recognizer_save_state = nil; + rec->recognizer_load_dictionary = nil; + rec->recognizer_save_dictionary = nil; + rec->recognizer_free_dictionary = nil; + rec->recognizer_add_to_dictionary = nil; + rec->recognizer_delete_from_dictionary = nil; + rec->recognizer_error = nil; + rec->recognizer_set_context = nil; + rec->recognizer_get_context = nil; + rec->recognizer_clear = nil; + rec->recognizer_get_buffer = nil; + rec->recognizer_set_buffer = nil; + rec->recognizer_translate = nil; + rec->recognizer_get_extension_functions = nil; + rec->recognizer_get_gesture_names = nil; + rec->recognizer_set_gesture_action = nil; + return(rec); +} + +void delete_recognizer(recognizer rec) +{ + + if( rec != nil ) { + if( rec->recognizer_info != nil ) { + delete_rec_info(rec->recognizer_info); + } + free(rec); + } +} + +/* + * rec_alternative +*/ + +rec_alternative* make_rec_alternative_array(uint size) +{ + int i; + rec_alternative* ri; + + ri = (rec_alternative*) safe_malloc(size * sizeof(rec_alternative)); + + for( i = 0; i < size; i++ ) { + ri[i].ra_elem.re_type = REC_NONE; + ri[i].ra_elem.re_result.aval = nil; + ri[i].ra_elem.re_conf = 0; + ri[i].ra_nalter = 0; + ri[i].ra_next = nil; + } + + return(ri); +} + +rec_alternative* + initialize_rec_alternative(rec_alternative* ra, uint nelm) +{ + if( ra != nil ) { + if( (ra->ra_next = make_rec_alternative_array(nelm)) == nil ) { + return(nil); + } + + ra->ra_nalter = nelm; + } + + return(ra); +} + +void delete_rec_alternative_array(uint nalter, + rec_alternative* ra, + bool delete_points_p) +{ + int i; + + if( ra != nil ) { + + for( i = 0; i < nalter; i++ ) { + cleanup_rec_element(&ra[i].ra_elem,delete_points_p); + + /*Now do the next one down the line.*/ + + if( ra[i].ra_nalter > 0 ) { + delete_rec_alternative_array(ra[i].ra_nalter, + ra[i].ra_next, + delete_points_p); + } + } + + free(ra); + } +} + + +/*initialize_rec_element-Initialize a recognition element.*/ + +rec_element* +initialize_rec_element(rec_element* re, + char type, + uint size, + void* trans, + rec_confidence conf) +{ + if( re != nil ) { + + re->re_type = type; + re->re_conf = conf; + re->re_result.aval = nil; + + switch (type) { + + case REC_GESTURE: + if( size > 0 && trans != nil ) { + re->re_result.gval = + (gesture*)safe_malloc(sizeof(gesture)); + memcpy((void*)re->re_result.gval,trans,sizeof(gesture)); + } + break; + + case REC_ASCII: + case REC_VAR: + case REC_OTHER: + if( size > 0 && trans != nil ) { + re->re_result.aval = + (char*)safe_malloc((size+1)*sizeof(char)); + memcpy((void*)re->re_result.aval,trans,size*sizeof(char)); + re->re_result.aval[size] = '\000'; + } + break; + + case REC_WCHAR: + if( size > 0 && trans != nil ) { + re->re_result.wval = + (wchar_t*)safe_malloc((size+1)*sizeof(wchar_t)); + memcpy((void*)re->re_result.wval,trans,size*sizeof(wchar_t)); + re->re_result.wval[size] = '\000'; + } + break; + + case REC_CORR: + if( size > 0 && trans != nil ) { + re->re_result.rcval = + (rec_correlation*)safe_malloc(sizeof(rec_correlation)); + memcpy((void*)re->re_result.rcval, + trans, + sizeof(rec_correlation)); + } + break; + + default: + return(nil); + } + + } + + return(re); +} + +static void cleanup_rec_element(rec_element* re,bool delete_points_p) +{ + switch(re->re_type) { + + case REC_NONE: + break; + + case REC_ASCII: + case REC_VAR: + case REC_WCHAR: + case REC_OTHER: + free(re->re_result.aval); + break; + + case REC_GESTURE: + delete_gesture_array(1,re->re_result.gval,true); + break; + + case REC_CORR: + delete_rec_correlation(re->re_result.rcval, + delete_points_p); + break; + + } + +} + +/* + * rec_correlation +*/ + + +rec_correlation* +make_rec_correlation(char type, + uint size, + void* trans, + rec_confidence conf, + uint ps_size) +{ + rec_correlation* rc; + + rc = (rec_correlation*)safe_malloc(sizeof(rec_correlation)); + + rc->ro_nstrokes = ps_size; + + /*First initialize element.*/ + + if( initialize_rec_element(&(rc->ro_elem), + type, + size, + trans, + conf) == nil ) { + return(nil); + } + + if( (rc->ro_strokes = make_Stroke_array(ps_size)) == nil ) { + return(nil); + } + + rc->ro_start = (uint*)safe_malloc(ps_size * sizeof(int)); + rc->ro_stop = (uint*)safe_malloc(ps_size * sizeof(int)); + return(rc); +} + +void delete_rec_correlation(rec_correlation* rc,bool delete_points_p) +{ + if( rc != nil ) { + + cleanup_rec_element(&rc->ro_elem,delete_points_p); + + delete_Stroke_array(rc->ro_nstrokes,rc->ro_strokes,delete_points_p); + + if( rc->ro_start != nil ) { + free(rc->ro_start); + } + + if( rc->ro_stop != nil ) { + free(rc->ro_stop); + } + + free(rc); + } + +} + + +/* + * rec_fn +*/ + + +rec_fn* make_rec_fn_array(uint size) +{ + rec_fn* ri = (rec_fn*)safe_malloc((size + 1) * sizeof(rec_fn)); + int i; + + for( i = 0; i < size; i++ ) { + ri[i] = nil; + } + + ri[i] = nil; + + return(ri); +} + +void delete_rec_fn_array(rec_fn* rf) +{ + if( rf != nil ) { + free(rf); + } +} + +/* + * Stroke +*/ + + +Stroke* make_Stroke_array(uint size) +{ + int i; + Stroke* ri; + + ri = (Stroke*) safe_malloc(size * sizeof(Stroke)); + for( i = 0; i < size; i++ ) { + ri[i].npts = 0; + ri[i].pts = nil; + } + + return(ri); +} + +Stroke* initialize_Stroke(Stroke* ps, + uint npts, + pen_point* pts) +{ + if( ps != nil ) { + ps->npts = npts; + ps->pts = pts; + } + return (ps); +} + +void delete_Stroke_array(uint size,Stroke* ps,bool delete_points_p) +{ + int i; + + if( ps != nil ) { + + for( i = 0; i < size; i++ ) { + if( delete_points_p ) { + delete_pen_point_array(ps[i].pts); + } + } + + free(ps); + } +} + +/* + * pen_point +*/ + +void delete_pen_point_array(pen_point* pp) +{ + if( pp != nil ) { + free(pp); + } +} + +/* + * gesture +*/ + +gesture* +make_gesture_array(uint size) +{ + return((gesture*)safe_malloc(size * sizeof(gesture))); +} + +gesture* initialize_gesture(gesture* g, + char* name, + uint nhs, + pen_point* hspots, + pen_rect bbox, + xgesture fn, + void* wsinfo) +{ + if( g != nil ) { + + /*We don't do points, 'cause they come from the window system.*/ + + g->g_nhs = nhs; + g->g_hspots = hspots; + + g->g_name = strdup(name); + + g->g_bbox = bbox; + g->g_action = fn; + g->g_wsinfo = wsinfo; + } + return(g); +} + +void +delete_gesture_array(uint size,gesture* ga,bool delete_points_p) +{ + int i; + + if( ga != nil ) { + + for( i = 0; i < size; i++ ) { + + free(ga[i].g_name); + + if( delete_points_p ) { + delete_pen_point_array(ga[i].g_hspots); + } + } + + free(ga); + } +} + +/* + * copy fns for stroke buffer management. +*/ + +static Stroke* +copy_Stroke(Stroke* ps1,Stroke* ps2) +{ + initialize_Stroke(ps1, + ps2->npts, + ps2->pts); + return(ps1); + +} + +Stroke* + copy_Stroke_array(uint nstrokes, + Stroke* strokes) +{ + int i; + Stroke* ps = make_Stroke_array(nstrokes); + + if( ps != nil ) { + + for( i = 0; i < nstrokes; i++ ) { + + copy_Stroke(&ps[i],&strokes[i]); + + } + + } + + return(ps); +} + +uint* + copy_state_trans_array(uint ntrans,uint* trans) +{ + uint* pt = (uint*)safe_malloc(ntrans*sizeof(uint)); + int i; + + for( i = 0; i < ntrans; i++ ) { + pt[i] = trans[i]; + } + return(pt); + +} + +Stroke* +concatenate_Strokes(int nstrokes1, + Stroke* strokes1, + int nstrokes2, + Stroke* strokes2, + int* nstrokes3, + Stroke** strokes3) +{ + int i; + int ns; + Stroke* ps; + + /*Measure new strokes*/ + + ns = nstrokes1 + nstrokes2; + + /*Allocate memory*/ + + if( (ps = make_Stroke_array(ns)) == nil ) { + return(nil); + } + + /*Copy old ones into new.*/ + + for( i = 0; i < nstrokes1; i++ ) { + if( copy_Stroke(&ps[i],&strokes1[i]) == nil ) { + delete_Stroke_array(ns,ps,false); + return(nil); + } + } + + for( ; i < ns; i++ ) { + if( copy_Stroke(&ps[i],&strokes2[i - nstrokes1]) == nil ) { + delete_Stroke_array(ns,ps,false); + return(nil); + } + } + + *nstrokes3 = ns; + *strokes3 = ps; + + return(ps); +} diff --git a/sys/src/libscribble/hre_internal.h b/sys/src/libscribble/hre_internal.h new file mode 100755 index 000000000..4fb73b2f3 --- /dev/null +++ b/sys/src/libscribble/hre_internal.h @@ -0,0 +1,149 @@ +/* + * hre_internal.h: Internal Interface for Recognizer. + * Author: James Kempf + * Created On: Thu Nov 5 10:54:18 1992 + * Last Modified By: James Kempf + * Last Modified On: Fri Sep 23 13:51:15 1994 + * Update Count: 99 + * Copyright (c) 1994 by Sun Microsystems Computer Company + * All rights reserved. + * + * Use and copying of this software and preparation of + * derivative works based upon this software are permitted. + * Any distribution of this software or derivative works + * must comply with all applicable United States export control + * laws. + * + * This software is made available as is, and Sun Microsystems + * Computer Company makes no warranty about the software, its + * performance, or its conformity to any specification + */ + +/*Avoids forward reference problem.*/ + +/* + * Internal view of wordset. The recognition engine uses this view to + * maintain information about which recognizer object this wordset + * belongs to, which file (in case it needs to be saved), and internal + * data structures. +*/ + +struct _wordset { + char* ws_pathname; /*Path name to word set file.*/ + recognizer ws_recognizer; /*To whom it belongs.*/ + void* ws_internal; /*Internal data structures.*/ +}; + +/* + * Internal view of the recognizer struct. This view is only available + * to OEM clients who implement a recognizer shared library. Clients + * of the recognizer itself see it as an opaque data type. The struct + * contains a function pointer for each function in the client API. +*/ + +struct _Recognizer { + uint recognizer_magic; + char *recognizer_version; + + rec_info *recognizer_info; + void *recognizer_specific; + int (*recognizer_load_state)(struct _Recognizer*, char*, char*); + int (*recognizer_save_state)(struct _Recognizer*, char*, char*); + char* (*recognizer_error)(struct _Recognizer*); + wordset (*recognizer_load_dictionary)(struct _Recognizer*, char*, char*); + int (*recognizer_save_dictionary)(struct _Recognizer*, char*, char*, wordset); + + int (*recognizer_free_dictionary)(struct _Recognizer*, wordset); + int (*recognizer_add_to_dictionary)(struct _Recognizer*, letterset*, wordset); + int (*recognizer_delete_from_dictionary)(struct _Recognizer*, letterset*, wordset); + int (*recognizer_set_context)(struct _Recognizer*,rc*); + rc* (*recognizer_get_context)(struct _Recognizer*); + + int (*recognizer_clear)(struct _Recognizer*, bool); + int (*recognizer_get_buffer)(struct _Recognizer*, uint*, Stroke**); + + int (*recognizer_set_buffer)(struct _Recognizer*, uint, Stroke*); + int (*recognizer_translate)(struct _Recognizer*, uint, Stroke*, bool, int*, rec_alternative**); + rec_fn* (*recognizer_get_extension_functions)(struct _Recognizer*); + char** (*recognizer_get_gesture_names)(struct _Recognizer*); + xgesture (*recognizer_set_gesture_action)(struct _Recognizer*, char*, xgesture, void*); + uint recognizer_end_magic; +}; + +/* + * recognizer_internal_initialize - Allocate and initialize the recognizer + * object. The recognition shared library has the responsibility for filling + * in all the function pointers for the recognition functions. This + * function must be defined as a global function within the shared + * library, so it can be accessed using dlsym() when the recognizer + * shared library is loaded. It returns NULL if an error occured and + * sets errno to indicate what. +*/ + +typedef recognizer (*recognizer_internal_initialize)(rec_info* ri); + +/*Function header definition for recognizer internal initializer.*/ + +#define RECOGNIZER_INITIALIZE(_a) \ + recognizer __recognizer_internal_initialize(rec_info* _a) + +/* + * recognizer_internal_finalize - Deallocate and deinitialize the recognizer + * object. If the recognizer has allocated any additional storage, it should + * be deallocated as well. Returns 0 if successful, -1 if the argument + * wasn't a recognizer or wasn't a recognizer handled by this library. +*/ + +typedef int (*recognizer_internal_finalize)(recognizer r); + +#define RECOGNIZER_FINALIZE(_a) \ + int __recognizer_internal_finalize(recognizer _a) + +/* + * The following are for creating HRE structures. + */ + +recognizer make_recognizer(rec_info* ri); +void delete_recognizer(recognizer rec); + +RECOGNIZER_FINALIZE(_a); +rec_alternative* make_rec_alternative_array(uint size); +rec_correlation* make_rec_correlation(char type, uint size, void* trans, rec_confidence conf, uint ps_size); + +rec_fn* +make_rec_fn_array(uint size); +void +delete_rec_fn_array(rec_fn* rf); + +gesture* +initialize_gesture(gesture* g, + char* name, + uint nhs, + pen_point* hspots, + pen_rect bbox, + xgesture cback, + void* wsinfo); +gesture* +make_gesture_array(uint size); +void +delete_gesture_array(uint size,gesture* ga,bool delete_points_p); + +Stroke* +concatenate_Strokes(int nstrokes1, + Stroke* strokes1, + int nstrokes2, + Stroke* strokes2, + int* nstrokes3, + Stroke** strokes3); + +rec_alternative* initialize_rec_alternative(rec_alternative* ra, uint); + +rec_element* initialize_rec_element(rec_element*, char, uint, void*, rec_confidence); + +/* + * Pathnames, etc. +*/ + +#define REC_DEFAULT_LOCALE "C" +#define RECHOME "RECHOME" +#define LANG "LANG" diff --git a/sys/src/libscribble/li_recognizer.c b/sys/src/libscribble/li_recognizer.c new file mode 100755 index 000000000..e7a20805d --- /dev/null +++ b/sys/src/libscribble/li_recognizer.c @@ -0,0 +1,2313 @@ +/* + * li_recognizer.c + * + * Copyright 2000 Compaq Computer Corporation. + * Copying or modifying this code for any purpose is permitted, + * provided that this copyright notice is preserved in its entirety + * in all copies or modifications. + * COMPAQ COMPUTER CORPORATION MAKES NO WARRANTIES, EXPRESSED OR + * IMPLIED, AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR + * + * + * Adapted from cmu_recognizer.c by Jay Kistler. + * + * Where is the CMU copyright???? Gotta track it down - Jim Gettys + * + * Credit to Dean Rubine, Jim Kempf, and Ari Rapkin. + */ + +#include <u.h> +#include <libc.h> +#include <stdio.h> +#include <draw.h> +#include <scribble.h> +#include "scribbleimpl.h" + +#include "hre_internal.h" +#include "li_recognizer_internal.h" + +int lidebug = 0; + +#define LI_MAGIC 0xACCBADDD + +#define CHECK_LI_MAGIC(_a) \ + ((_a) != nil && ((li_recognizer*)(_a))->li_magic == LI_MAGIC) + + +static void lialg_initialize(rClassifier *); +static int lialg_read_classifier_digest(rClassifier *); +static int lialg_canonicalize_examples(rClassifier *); +static char *lialg_recognize_stroke(rClassifier *, point_list *); +static void lialg_compute_lpf_parameters(void); + + +char* li_err_msg = nil; + +#define bcopy(s1,s2,n) memmove(s2,s1,n) + +/*Freeing classifier*/ + +static void +free_rClassifier(rClassifier* rc); + +/* + * Point List Support +*/ + +static point_list* +add_example(point_list* l,int npts,pen_point* pts) +{ + pen_point* lpts = mallocz(npts*sizeof(pen_point), 1); + point_list *p = malloc(sizeof(point_list)); + + p->npts = npts; + p->pts = lpts; + p->next = l; /*Order doesn't matter, so we stick on end.*/ + + /*Copy points.*/ + + bcopy(pts, lpts, npts * sizeof(pen_point)); + + return(p); +} + + +static void +delete_examples(point_list* l) +{ + point_list* p; + + for( ; l != nil; l = p ) { + p = l->next; + free(l->pts); + free(l); + } +} + +/* + * Local functions + */ + +/* + * recognize_internal-Form Vector, use Classifier to classify, return char. + */ + +static char* +recognize_internal(rClassifier* rec, Stroke* str, int*) +{ + char *res; + point_list *stroke; + + stroke = add_example(nil, str->npts, str->pts); + if (stroke == nil) return(nil); + + res = lialg_recognize_stroke(rec, stroke); + + delete_examples(stroke); + return(res); +} + +/* + * file_path-Construct pathname, check for proper extension. + */ + +static int + file_path(char* dir,char* filename,char* pathname) +{ + char* dot; + + /*Check for proper extension on file name.*/ + + dot = strrchr(filename,'.'); + + if( dot == nil ) { + return(-1); + } + + /*Determine whether a gesture or character classifier.*/ + + if( strcmp(dot,LI_CLASSIFIER_EXTENSION) != 0 ) { + return(-1); + } + + /*Concatenate directory and filename into pathname.*/ + + strcpy(pathname,dir); + strcat(pathname,"/"); + strcat(pathname,filename); + + return(0); +} + +/*read_classifier_points-Read points so classifier can be extended.*/ + +static int +read_classifier_points(FILE* fd,int nclss,point_list** ex,char** cnames) +{ + int i,j,k; + char buf[BUFSIZ]; + int nex = 0; + char* names[MAXSCLASSES]; + point_list* examples[MAXSCLASSES]; + pen_point* pts; + int npts; + + /*Initialize*/ + + for( i = 0; i < MAXSCLASSES; i++ ) { + names[i] = nil; + examples[i] = nil; + } + + /*Go thru classes.*/ + + for( k = 0; k < nclss; k++ ) { + + /*Read class name and number of examples.*/ + + if( fscanf(fd,"%d %s",&nex,buf) != 2 ) + goto unallocate; + + /*Save class name*/ + + names[k] = strdup(buf); + + /*Read examples.*/ + + for( i = 0; i < nex; i++ ) { + + /*Read number of points.*/ + + if( fscanf(fd,"%d",&npts) != 1 ) + goto unallocate; /*Boy would I like exceptions!*/ + + /*Allocate array for points.*/ + + if( (pts = mallocz(npts*sizeof(pen_point), 1)) == nil ) + goto unallocate; + + /*Read in points.*/ + + for( j = 0; j < npts; j++ ) { + int x,y; + if( fscanf(fd,"%d %d",&x,&y) != 2 ) { + delete_pen_point_array(pts); + goto unallocate; + } + pts[j].Point = Pt(x, y); + } + + /*Add example*/ + + if( (examples[k] = add_example(examples[k],npts,pts)) == nil ) { + delete_pen_point_array(pts); + goto unallocate; + } + + delete_pen_point_array(pts); + + } + } + +/* ari -- end of list of classes */ +/* fprint(2, "]\n"); */ + + /*Transfer to recognizer.*/ + + bcopy(examples,ex,sizeof(examples)); + bcopy(names,cnames,sizeof(names)); + + return(0); + + /*Error. Deallocate memory and return.*/ + +unallocate: + for( ; k >= 0; k-- ) { + delete_examples(examples[k]); + free(names[k]); + } + + return(-1); +} + +/*read_classifier-Read a classifier file.*/ + +static int read_classifier(FILE* fd,rClassifier* rc) +{ + + li_err_msg = nil; + + /*Read in classifier file.*/ + + if(fscanf(fd, "%d", &rc->nclasses) != 1) + return -1; + + /*Read in the example points, so classifier can be extended.*/ + + if( read_classifier_points(fd,rc->nclasses,rc->ex,rc->cnames) != 0 ) + return -1; + + return(0); +} + +/* + * Extension Functions +*/ + +/* getClasses and clearState are by Ari */ + +static int +recognizer_getClasses (recognizer r, char ***list, int *nc) +{ + int i, nclasses; + li_recognizer* rec; + char **ret; + + rec = (li_recognizer*)r->recognizer_specific; + + /*Check for LI recognizer.*/ + + if( !CHECK_LI_MAGIC(rec) ) { + li_err_msg = "Not a LI recognizer"; + return(-1); + } + + *nc = nclasses = rec->li_rc.nclasses; + ret = malloc(nclasses*sizeof(char*)); + + for (i = 0; i < nclasses; i++) + ret[i] = rec->li_rc.cnames[i]; /* only the 1st char of the cname */ + *list = ret; + return 0; +} + +static int +recognizer_clearState (recognizer) +{ + /*This operation isn't supported by the LI recognizer.*/ + + li_err_msg = "Clearing state is not supported by the LI recognizer"; + + return(-1); +} + +static bool isa_li(recognizer r) +{ return(CHECK_LI_MAGIC(r)); } + +static int +recognizer_train(recognizer, rc*, uint, Stroke*, rec_element*, bool) +{ + /*This operation isn't supported by the LI recognizer.*/ + + li_err_msg = "Training is not supported by the LI recognizer"; + + return(-1); +} + +int +li_recognizer_get_example (recognizer r, + int class, + int instance, + char **name, + pen_point **points, + int *npts) +{ + li_recognizer *rec = (li_recognizer*)r->recognizer_specific; + int nclasses = rec->li_rc.nclasses; + point_list *pl; + + if( !CHECK_LI_MAGIC(rec) ) { + li_err_msg = "Not a LI recognizer"; + return(-1); + } + if (class > nclasses) + return -1; + pl = rec->li_rc.canonex[class]; + while (instance && pl) + { + pl = pl->next; + instance--; + } + if (!pl) + return -1; + *name = rec->li_rc.cnames[class]; + *points = pl->pts; + *npts = pl->npts; + return pl->npts; /* I hope [sjm] */ +} + +/* + * API Functions + */ + + +/*li_recognizer_load-Load a classifier file.*/ + +static int li_recognizer_load(recognizer r, char* dir, char* filename) +{ + FILE *fd; + char* pathname; + li_recognizer* rec; + rClassifier* rc; + + rec = (li_recognizer*)r->recognizer_specific; + + /*Make sure recognizer's OK*/ + + if( !CHECK_LI_MAGIC(rec) ) { + li_err_msg = "Not a LI recognizer"; + return(-1); + } + + rc = &(rec->li_rc); + + /*Check parameters.*/ + + if( filename == nil ) { + li_err_msg = "Invalid parameters"; + return(-1); + } + + /*We let the directory be null.*/ + + if( dir == nil || (int)strlen(dir) <= 0 ) { + dir = "."; + } + + if(0)fprint(2, "dir = %s, filename = %s\n", + dir, filename); + + /*Make full pathname and check filename*/ + + pathname = malloc((strlen(dir) + strlen(filename) + 2)*sizeof(char)); + if( file_path(dir, filename, pathname) == -1 ) { + free(pathname); + li_err_msg = "Not a LI recognizer classifier file"; + return -1; + } + + /* Try to short-circuit the full classifier-file processing. */ + rc->file_name = pathname; + if (lialg_read_classifier_digest(rc) == 0) + return(0); + rc->file_name = nil; + + /*Open the file*/ + + if( (fd = fopen(pathname,"r")) == nil ) { + li_err_msg = "Can't open classifier file"; + if(0)fprint(2, "Can't open %s.\n", pathname); + free(pathname); + return(-1); + } + + /*If rClassifier is OK, then delete it first.*/ + + if( rc->file_name != nil ) { + free_rClassifier(rc); + } + + /*Read classifier.*/ + + if( read_classifier(fd,rc) < 0 ) { + free(pathname); + return(-1); + } + + /*Close file.*/ + + fclose(fd); + + /*Add classifier name.*/ + + rc->file_name = pathname; + + /* Canonicalize examples. */ + if (lialg_canonicalize_examples(rc) != 0) { + free(pathname); + rc->file_name = nil; + return -1; + } + + return(0); +} + +/*li_recognizer_save-Save a classifier file.*/ + +static int li_recognizer_save(recognizer, char*, char*) +{ + /*This operation isn't supported by the LI recognizer.*/ + + li_err_msg = "Saving is not supported by the LI recognizer"; + return -1; +} + +static wordset +li_recognizer_load_dictionary(recognizer, char*, char*) +{ + /*This operation isn't supported by the LI recognizer.*/ + + li_err_msg = "Dictionaries are not supported by the LI recognizer"; + return nil; +} + +static int +li_recognizer_save_dictionary(recognizer, char*, char*, wordset) +{ + /*This operation isn't supported by the LI recognizer.*/ + li_err_msg = "Dictionaries are not supported by the LI recognizer"; + return -1; +} + +static int +li_recognizer_free_dictionary(recognizer, wordset) +{ + /*This operation isn't supported by the LI recognizer.*/ + + li_err_msg = "Dictionaries are not supported by the LI recognizer"; + + return -1; + +} + +static int +li_recognizer_add_to_dictionary(recognizer, letterset*, wordset) +{ + /*This operation isn't supported by the LI recognizer.*/ + li_err_msg = "Dictionaries are not supported by the LI recognizer"; + return -1; +} + +static int +li_recognizer_delete_from_dictionary(recognizer, letterset*, wordset) +{ + /*This operation isn't supported by the LI recognizer.*/ + li_err_msg = "Dictionaries are not supported by the LI recognizer"; + return -1; +} + +static char* +li_recognizer_error(recognizer rec) +{ + char* ret = li_err_msg; + + /*Check for LI recognizer.*/ + + if( !CHECK_LI_MAGIC(rec->recognizer_specific) ) { + li_err_msg = "Not a LI recognizer"; + return nil; + } + li_err_msg = nil; + return ret; +} + +static int +li_recognizer_clear(recognizer r, bool) +{ + li_recognizer* rec; + + rec = (li_recognizer*)r->recognizer_specific; + /*Check for LI recognizer.*/ + if( !CHECK_LI_MAGIC(rec) ) { + li_err_msg = "Not a LI recognizer"; + return 0; + } + return 0; +} + +static int +li_recognizer_set_context(recognizer, rc*) +{ + /*This operation isn't supported by the LI recognizer.*/ + li_err_msg = "Contexts are not supported by the LI recognizer"; + return -1; +} + +static rc* +li_recognizer_get_context(recognizer) +{ + /*This operation isn't supported by the LI recognizer.*/ + li_err_msg = "Contexts are not supported by the LI recognizer"; + return nil; +} + +static int +li_recognizer_get_buffer(recognizer, uint*, Stroke**) +{ + /*This operation isn't supported by the LI recognizer.*/ + li_err_msg = "Buffer get/set are not supported by the LI recognizer"; + return -1; +} + +static int +li_recognizer_set_buffer(recognizer, uint, Stroke*) +{ + /*This operation isn't supported by the LI recognizer.*/ + li_err_msg = "Buffer get/set are not supported by the LI recognizer"; + return -1; +} + +static int +li_recognizer_translate(recognizer r, uint ncs, Stroke* tps, bool, int* nret, rec_alternative** ret) +{ + char* clss; + li_recognizer* rec; + int conf; + rClassifier* rc; + + rec = (li_recognizer*)r->recognizer_specific; + + *nret = 0; + *ret = nil; + + /*Check for LI recognizer.*/ + + if( !CHECK_LI_MAGIC(rec) ) { + li_err_msg = "Not a LI recognizer"; + return(-1); + } + + rc = &(rec->li_rc); + + /*Check for valid parameters.*/ + if (ncs < 1) { + li_err_msg = "Invalid parameters: ncs"; + return(-1); + } + if( tps == nil) { + li_err_msg = "Invalid parameters: tps"; + return(-1); + } + if( nret == nil) { + li_err_msg = "Invalid parameters: nret"; + return(-1); + } + if( ret == nil) { + li_err_msg = "Invalid parameters: ret"; + return(-1); + } + + /* + * Go through the stroke array and recognize. Since this is a single + * stroke recognizer, each stroke is treated as a separate + * character or gesture. We allow only characters or gestures + * to be recognized at one time, since otherwise, handling + * the display of segmentation would be difficult. + */ + clss = recognize_internal(rc,tps,&conf); + if (clss == nil) { + *nret = 1; + return(0); + } + + /*Return values.*/ + *nret = 1; + return(*clss); +} + + +static rec_fn* +li_recognizer_get_extension_functions(recognizer rec) +{ + rec_fn* ret; + + /*Check for LI recognizer.*/ + + if( !CHECK_LI_MAGIC(rec->recognizer_specific) ) { + li_err_msg = "Not a LI recognizer"; + return(nil); + } + + ret = make_rec_fn_array(LI_NUM_EX_FNS); + +/* ari -- clearState & getClasses are mine */ + ret[LI_GET_CLASSES] = (rec_fn)recognizer_getClasses; + ret[LI_CLEAR] = (rec_fn)recognizer_clearState; + ret[LI_ISA_LI] = (rec_fn)isa_li; + ret[LI_TRAIN] = (rec_fn)recognizer_train; + + return(ret); +} + +static char** +li_recognizer_get_gesture_names(recognizer) +{ + /*This operation isn't supported by the LI recognizer.*/ + li_err_msg = "Gestures are not supported by the LI recognizer"; + return nil; +} + +static xgesture +li_recognizer_set_gesture_action(recognizer, char*, xgesture, void*) +{ + /*This operation isn't supported by the LI recognizer.*/ + li_err_msg = "Gestures are not supported by the LI recognizer"; + return nil; +} + +/* + * Exported Functions +*/ + +/*RECOGNIZER_INITIALIZE-Initialize the recognizer.*/ + +/* note from ari: this expands via pre-processor to + * + * recognizer __recognizer_internal_initialize(rec_info* ri) + */ + +RECOGNIZER_INITIALIZE(ri) +{ + recognizer r; + li_recognizer* rec; + int i; + + /*Check that locale matches.*/ + + if( strcmp(ri->ri_locale,LI_SUPPORTED_LOCALE) != 0 ) { + li_err_msg = "Not a supported locale"; +/* fprint(2, "Locale error.\n");*/ + return(nil); + } + + /* + * Check that character sets match. Note that this is only approximate, + * since the classifier file will have more information. + */ + + if( ri->ri_subset != nil ) { + for(i = 0; ri->ri_subset[i] != nil; i++ ) { + + if( strcmp(ri->ri_subset[i],UPPERCASE) != 0 && + strcmp(ri->ri_subset[i],LOWERCASE) != 0 && + strcmp(ri->ri_subset[i],DIGITS) != 0 && + strcmp(ri->ri_subset[i],GESTURE) != 0 ) { + li_err_msg = "Not a supported character set"; +/* fprint(2, "charset error.\n"); */ + + return(nil); + } + } + } + +/* ari */ + r = make_recognizer(ri); +/* fprint(2, "past make_recognizer.\n"); */ + + if( r == nil ) { + li_err_msg = "Can't allocate storage"; + + return(nil); + } + + /*Make a LI recognizer structure.*/ + + + /* rec = (li_recognizer*)safe_malloc(sizeof(li_recognizer))) == nil ); */ + + rec = malloc(sizeof(li_recognizer)); + + r->recognizer_specific = rec; + + rec->li_rc.file_name = nil; + rec->li_rc.nclasses = 0; + + /*Initialize the recognizer struct.*/ + + r->recognizer_load_state = li_recognizer_load; + r->recognizer_save_state = li_recognizer_save; + r->recognizer_load_dictionary = li_recognizer_load_dictionary; + r->recognizer_save_dictionary = li_recognizer_save_dictionary; + r->recognizer_free_dictionary = li_recognizer_free_dictionary; + r->recognizer_add_to_dictionary = li_recognizer_add_to_dictionary; + r->recognizer_delete_from_dictionary = li_recognizer_delete_from_dictionary; + r->recognizer_error = li_recognizer_error; + r->recognizer_translate = li_recognizer_translate; + r->recognizer_get_context = li_recognizer_get_context; + r->recognizer_set_context = li_recognizer_set_context; + r->recognizer_get_buffer = li_recognizer_get_buffer; + r->recognizer_set_buffer = li_recognizer_set_buffer; + r->recognizer_clear = li_recognizer_clear; + r->recognizer_get_extension_functions = + li_recognizer_get_extension_functions; + r->recognizer_get_gesture_names = li_recognizer_get_gesture_names; + r->recognizer_set_gesture_action = + li_recognizer_set_gesture_action; + + /*Initialize LI Magic Number.*/ + + rec->li_magic = LI_MAGIC; + + /*Initialize rClassifier.*/ + + rec->li_rc.file_name = nil; + + for( i = 0; i < MAXSCLASSES; i++ ) { + rec->li_rc.ex[i] = nil; + rec->li_rc.cnames[i] = nil; + } + + lialg_initialize(&rec->li_rc); + + /*Get rid of error message. Not needed here.*/ + li_err_msg = nil; + + return(r); +} + +/*free_rClassifier-Free the rClassifier.*/ + +static void +free_rClassifier(rClassifier* rc) +{ + int i; + + if( rc->file_name != nil) { + free(rc->file_name); + } + + for( i = 0; rc->ex[i] != nil; i++) { + delete_examples(rc->ex[i]); + free(rc->cnames[i]); + } + +} + +/*RECOGNIZER_FINALIZE-Deallocate the recognizer, finalize.*/ + +RECOGNIZER_FINALIZE(r) +{ + li_recognizer* rec = (li_recognizer*)r->recognizer_specific; + + /*Make sure this is a li_recognizer first*/ + if( !CHECK_LI_MAGIC(rec) ) { + li_err_msg = "Not a LI recognizer"; + return(-1); + } + + /*Deallocate rClassifier resources.*/ + + free_rClassifier(&(rec->li_rc)); + + /*Deallocate the li_recognizer struct.*/ + free(rec); + + /*Deallocate the recognizer*/ + delete_recognizer(r); + + return(0); +} + + +/* ************************************************** + + Implementation of the Li/Yeung recognition algorithm + +************************************************** */ + +#define WORST_SCORE 0x7fffffff + +/* Dynamic programming parameters */ +#define DP_BAND 3 +#define MIN_SIM 0 +#define MAX_DIST 0x7fffffff +#define SIM_THLD 60 /* x 100 */ +#define DIST_THLD 3200 /* x 100 */ + +/* Low-pass filter parameters -- empirically derived */ +#define LP_FILTER_WIDTH 6 +#define LP_FILTER_ITERS 8 +#define LP_FILTER_THLD 250 /* x 100 */ +#define LP_FILTER_MIN 5 + +/* Pseudo-extrema parameters -- empirically derived */ +#define PE_AL_THLD 1500 /* x 100 */ +#define PE_ATCR_THLD 135 /* x 100 */ + +/* Contour-angle derivation parameters */ +#define T_ONE 1 +#define T_TWO 20 + +/* Pre-processing and canonicalization parameters */ +#define CANONICAL_X 108 +#define CANONICAL_Y 128 +#define DIST_SQ_THRESHOLD (3*3) /* copied from fv.h */ +#define NCANONICAL 50 + +/* Tap-handling parameters */ +#define TAP_CHAR "." +#define TAP_TIME_THLD 150 /* msec */ +#define TAP_DIST_THLD 75 /* dx * dx + dy * dy */ +#define TAP_PATHLEN 1000 /* x 100 */ + + +/* region types */ +#define RGN_CONVEX 0 +#define RGN_CONCAVE 1 +#define RGN_PLAIN 2 +#define RGN_PSEUDO 3 + + +typedef struct RegionList { + int start; + int end; + int type; + struct RegionList *next; +} region_list; + + +/* direction-code table; indexed by dx, dy */ +static int lialg_dctbl[3][3] = {{1, 0, 7}, {2, 0x7FFFFFFF, 6}, {3, 4, 5}}; + +/* low-pass filter weights */ +static int lialg_lpfwts[2 * LP_FILTER_WIDTH + 1]; +static int lialg_lpfconst = -1; + + +static int lialg_preprocess_stroke(point_list *); +static point_list *lialg_compute_dominant_points(point_list *); +static point_list *lialg_interpolate_points(point_list *); +static void lialg_bresline(pen_point *, pen_point *, point_list *, int *); +static void lialg_compute_chain_code(point_list *); +static void lialg_compute_unit_chain_code(point_list *); +static region_list *lialg_compute_regions(point_list *); +static point_list *lialg_compute_dompts(point_list *, region_list *); +static int *lialg_compute_contour_angle_set(point_list *, region_list *); +static void lialg_score_stroke(point_list *, point_list *, int *, int *); +static int lialg_compute_similarity(point_list *, point_list *); +static int lialg_compute_distance(point_list *, point_list *); + +static int lialg_read_classifier_digest(rClassifier *); + +static int lialg_canonicalize_examples(rClassifier *); +static int lialg_canonicalize_example_stroke(point_list *); +static int lialg_compute_equipoints(point_list *); + +static int lialg_compute_pathlen(point_list *); +static int lialg_compute_pathlen_subset(point_list *, int, int); +static int lialg_filter_points(point_list *); +static int lialg_translate_points(point_list *, int, int, int, int); +static void lialg_get_bounding_box(point_list *, int *, int *, int *, int *); +static void lialg_compute_lpf_parameters(); +static int isqrt(int); +static int likeatan(int, int); +static int quadr(int); + + +/************************************************************* + + Core routines for the Li/Yeung recognition algorithm + + *************************************************************/ + +static void lialg_initialize(rClassifier *rec) { + int i; + + /* Initialize the dompts arrays. */ + for (i = 0; i < MAXSCLASSES; i++) { + rec->dompts[i] = nil; + } +} + + +/* + * Main recognition routine -- called by HRE API. + */ +static char *lialg_recognize_stroke(rClassifier *rec, point_list *stroke) { + int i; + char *name = nil; + point_list *input_dompts = nil; + char *best_name = nil; + int best_score = WORST_SCORE; + char *curr_name; + point_list *curr_dompts; + + /* (void)gettimeofday(&stv, nil);*/ + + if (stroke->npts < 1) goto done; + + /* Check for tap. */ + + /* First thing is to filter out ``close points.'' */ + if (lialg_filter_points(stroke) != 0) return(nil); + + /* Unfortunately, we don't have the actual time that each point */ + /* was recorded (i.e., dt is invalid). Hence, we have to use a */ + /* heuristic based on total distance and the number of points. */ + if (stroke->npts == 1 || lialg_compute_pathlen(stroke) < TAP_PATHLEN) + return(TAP_CHAR); + + /* Pre-process input stroke. */ + if (lialg_preprocess_stroke(stroke) != 0) goto done; + + /* Compute its dominant points. */ + input_dompts = lialg_compute_dominant_points(stroke); + if (input_dompts == nil) goto done; + + /* Score input stroke against every class in classifier. */ + for (i = 0, curr_name = rec->cnames[i], curr_dompts = rec->dompts[i]; + i < MAXSCLASSES && curr_name != nil && curr_dompts != nil; + i++, curr_name = rec->cnames[i], curr_dompts = rec->dompts[i]) { + int sim; + int dist; + int curr_score; + + lialg_score_stroke(input_dompts, curr_dompts, &sim, &dist); + curr_score = dist; + + if (lidebug && curr_score < DIST_THLD) + fprint(2, "(%s, %d, %d) ", curr_name, sim, dist); + + /* Is it the best so far? */ + if (curr_score < best_score && curr_score <= DIST_THLD) { + best_score = curr_score; + best_name = curr_name; + } + } + + if (lidebug) + fprint(2, "\n"); + + /* No errors. */ + name = best_name; + +done: + delete_examples(input_dompts); + return(name); +} + + +static int lialg_preprocess_stroke(point_list *points) { + int minx, miny, maxx, maxy, xrange, yrange, scale, xoff, yoff; + + /* Filter out points that are too close. */ + /* We did this earlier, when we checked for a tap. */ +/* + if (lialg_filter_points(points) != 0) return(-1); +*/ + +/* assert(points->npts > 0);*/ + + /* Scale up to avoid conversion errors. */ + lialg_get_bounding_box(points, &minx, &miny, &maxx, &maxy); + xrange = maxx - minx; + yrange = maxy - miny; + scale = ( ((100 * xrange + CANONICAL_X / 2) / CANONICAL_X) > + ((100 * yrange + CANONICAL_Y / 2) / CANONICAL_Y)) + ? (100 * CANONICAL_X + xrange / 2) / xrange + : (100 * CANONICAL_Y + yrange / 2) / yrange; + if (lialg_translate_points(points, minx, miny, scale, scale) != 0) + return(-1); + + /* Center the stroke. */ + lialg_get_bounding_box(points, &minx, &miny, &maxx, &maxy); + xrange = maxx - minx; + yrange = maxy - miny; + xoff = -((CANONICAL_X - xrange + 1) / 2); + yoff = -((CANONICAL_Y - yrange + 1) / 2); + if (lialg_translate_points(points, xoff, yoff, 100, 100) != 0) return(-1); + + /* Store the x and y ranges in the point list. */ + xrange = maxx - minx; + yrange = maxy - miny; + points->xrange = xrange; + points->yrange = yrange; + + if (lidebug) { + int i; + fprint(2, "After pre-processing: %d %d %d %d\n", + minx, miny, maxx, maxy); + for (i = 0; i < points->npts; i++) + fprint(2, " (%P)\n", points->pts[i].Point); + fflush(stderr); + } + + return(0); +} + + +static point_list *lialg_compute_dominant_points(point_list *points) { + point_list *ipts; + region_list *regions; + point_list *dpts; + + /* Interpolate points. */ + ipts = lialg_interpolate_points(points); + if (ipts == nil) return(nil); + if (lidebug) { + int j; + fprint(2, "After interpolation: %d ipts\n", ipts->npts); + for (j = 0; j < ipts->npts; j++) { + fprint(2, " (%P), %lud\n", ipts->pts[j].Point, ipts->pts[j].chaincode); + } + fflush(stderr); + } + + /* Compute regions. */ + regions = lialg_compute_regions(ipts); +/* assert(regions != nil);*/ + + /* Compute dominant points. */ + dpts = lialg_compute_dompts(ipts, regions); + if (lidebug) { + int j; + fprint(2, "Dominant points: "); + for (j = 0; j < dpts->npts; j++) { + fprint(2, "%P (%lud) ", dpts->pts[j].Point, dpts->pts[j].chaincode); + } + fprint(2, "\n"); + fflush(stderr); + } + + /* Delete region data structure. */ + { + region_list *curr, *next; + for (curr = regions; curr != nil; ) { + next = curr->next; + free(curr); + curr = next; + } + } + delete_examples(ipts); + return(dpts); +} + +/* Input points are assumed to be integer-valued! */ +static point_list *lialg_interpolate_points(point_list *points) { + int i, j; + int maxpts; + point_list *newpts; + + /* Compute an upper-bound on the number of interpolated points. */ + maxpts = 0; + for (i = 0; i < (points->npts - 1); i++) { + pen_point *pta = &(points->pts[i]); + pen_point *ptb = &(points->pts[i+1]); + maxpts += abs(pta->x - ptb->x) + abs(pta->y - ptb->y); + } + + /* Allocate an array of the requisite size. */ + maxpts += points->npts; + /* newpts = (point_list *)safe_malloc(sizeof(point_list)); */ + newpts = malloc(sizeof(point_list)); + newpts->pts = mallocz(maxpts*sizeof(pen_point), 1); + if (newpts->pts == nil) { + free(newpts); + return(nil); + } + newpts->npts = maxpts; + newpts->next = nil; + + /* Interpolate each of the segments. */ + j = 0; + for (i = 0; i < (points->npts - 1); i++) { + pen_point *startpt = &(points->pts[i]); + pen_point *endpt = &(points->pts[i+1]); + + lialg_bresline(startpt, endpt, newpts, &j); + + j--; /* end point gets recorded as start point of next segment! */ + } + + /* Add-in last point. */ + newpts->pts[j++] = points->pts[points->npts - 1]; + newpts->npts = j; + + /* Compute the chain code for P (the list of points). */ + lialg_compute_unit_chain_code(newpts); + + return(newpts); +} + + +/* This implementation is due to Kenny Hoff. */ +static void lialg_bresline(pen_point *startpt, pen_point *endpt, + point_list *newpts, int *j) { + int Ax, Ay, Bx, By, dX, dY, Xincr, Yincr; + + Ax = startpt->x; + Ay = startpt->y; + Bx = endpt->x; + By = endpt->y; + + /* INITIALIZE THE COMPONENTS OF THE ALGORITHM THAT ARE NOT AFFECTED */ + /* BY THE SLOPE OR DIRECTION OF THE LINE */ + dX = abs(Bx-Ax); /* store the change in X and Y of the line endpoints */ + dY = abs(By-Ay); + + /* DETERMINE "DIRECTIONS" TO INCREMENT X AND Y (REGARDLESS OF DECISION) */ + if (Ax > Bx) { Xincr=-1; } else { Xincr=1; } /* which direction in X? */ + if (Ay > By) { Yincr=-1; } else { Yincr=1; } /* which direction in Y? */ + + /* DETERMINE INDEPENDENT VARIABLE (ONE THAT ALWAYS INCREMENTS BY 1 (OR -1) ) */ + /* AND INITIATE APPROPRIATE LINE DRAWING ROUTINE (BASED ON FIRST OCTANT */ + /* ALWAYS). THE X AND Y'S MAY BE FLIPPED IF Y IS THE INDEPENDENT VARIABLE. */ + if (dX >= dY) { /* if X is the independent variable */ + int dPr = dY<<1; /* amount to increment decision if right is chosen (always) */ + int dPru = dPr - (dX<<1); /* amount to increment decision if up is chosen */ + int P = dPr - dX; /* decision variable start value */ + + /* process each point in the line one at a time (just use dX) */ + for (; dX>=0; dX--) { + newpts->pts[*j].x = Ax; + newpts->pts[*j].y = Ay; + (*j)++; + + if (P > 0) { /* is the pixel going right AND up? */ + Ax+=Xincr; /* increment independent variable */ + Ay+=Yincr; /* increment dependent variable */ + P+=dPru; /* increment decision (for up) */ + } else { /* is the pixel just going right? */ + Ax+=Xincr; /* increment independent variable */ + P+=dPr; /* increment decision (for right) */ + } + } + } else { /* if Y is the independent variable */ + int dPr = dX<<1; /* amount to increment decision if right is chosen (always) */ + int dPru = dPr - (dY<<1); /* amount to increment decision if up is chosen */ + int P = dPr - dY; /* decision variable start value */ + + /* process each point in the line one at a time (just use dY) */ + for (; dY>=0; dY--) { + newpts->pts[*j].x = Ax; + newpts->pts[*j].y = Ay; + (*j)++; + + if (P > 0) { /* is the pixel going up AND right? */ + Ax+=Xincr; /* increment dependent variable */ + Ay+=Yincr; /* increment independent variable */ + P+=dPru; /* increment decision (for up) */ + } else { /* is the pixel just going up? */ + Ay+=Yincr; /* increment independent variable */ + P+=dPr; /* increment decision (for right) */ + } + } + } +} + +static void lialg_compute_chain_code(point_list *pts) { + int i; + + for (i = 0; i < (pts->npts - 1); i++) { + pen_point *startpt = &(pts->pts[i]); + pen_point *endpt = &(pts->pts[i+1]); + int dx = endpt->x - startpt->x; + int dy = endpt->y - startpt->y; + int tmp = quadr(likeatan(dy, dx)); + int dircode = (12 - tmp) % 8; + + startpt->chaincode = dircode; + } +} + + +static void lialg_compute_unit_chain_code(point_list *pts) { + int i; + + for (i = 0; i < (pts->npts - 1); i++) { + pen_point *startpt = &(pts->pts[i]); + pen_point *endpt = &(pts->pts[i+1]); + int dx = endpt->x - startpt->x; + int dy = endpt->y - startpt->y; + int dircode = lialg_dctbl[dx+1][dy+1]; + + startpt->chaincode = dircode; + } +} + + +static region_list *lialg_compute_regions(point_list *pts) { + region_list *regions; + region_list *curr_reg; + int *R[2 + LP_FILTER_ITERS]; + int *junk; + int *curr, *next; + int i, j; + + /* Initialize low-pass filter parameters if necessary. */ + if (lialg_lpfconst == -1) + lialg_compute_lpf_parameters(); + + /* Allocate a 2 x pts->npts array for use in computing the (filtered) Angle set, A_n. */ + junk = malloc((2 + LP_FILTER_ITERS) * pts->npts*sizeof(int)); + for (i = 0; i < (2 + LP_FILTER_ITERS); i++) + R[i] = junk + (i * pts->npts); + curr = R[0]; + + /* Compute the Angle set, A, in the first element of array R. */ + /* Values in R are in degrees, x 100. */ + curr[0] = 18000; /* a_0 */ + for (i = 1; i < (pts->npts - 1); i++) { + int d_i = pts->pts[i].chaincode; + int d_iminusone = pts->pts[i-1].chaincode; + int a_i; + + if (d_iminusone < d_i) + d_iminusone += 8; + + a_i = (d_iminusone - d_i) % 8; + + /* convert to degrees, x 100 */ + curr[i] = ((12 - a_i) % 8) * 45 * 100; + } + curr[pts->npts - 1] = 18000; /* a_L-1 */ + + /* Perform a number of filtering iterations. */ + next = R[1]; + for (j = 0; j < LP_FILTER_ITERS; j++, curr = R[j], next = R[j+1]) { + for (i = 0; i < pts->npts; i++) { + int k; + + next[i] = 0; + + for (k = i - LP_FILTER_WIDTH; k <= i + LP_FILTER_WIDTH; k++) { + int oldval = (k < 0 || k >= pts->npts) ? 18000 : curr[k]; + next[i] += oldval * lialg_lpfwts[k - (i - LP_FILTER_WIDTH)]; /* overflow? */ + } + + next[i] /= lialg_lpfconst; + } + } + + /* Do final thresholding around PI. */ + /* curr and next are set-up correctly at end of previous loop! */ + for (i = 0; i < pts->npts; i++) + next[i] = (abs(curr[i] - 18000) < LP_FILTER_THLD) ? 18000 : curr[i]; + curr = next; + + /* Debugging. */ + if (lidebug > 1) { + for (i = 0; i < pts->npts; i++) { + fprint(2, "%3d: (%P) %lud ", + i, pts->pts[i].Point, pts->pts[i].chaincode); + for (j = 0; j < 2 + LP_FILTER_ITERS; j++) + fprint(2, "%d ", R[j][i]); + fprint(2, "\n"); + } + } + + /* Do the region segmentation. */ + { + int start, end; + int currtype; + +#define RGN_TYPE(val) (((val)==18000)?RGN_PLAIN:((val)<18000?RGN_CONCAVE:RGN_CONVEX)) + + start = 0; + currtype = RGN_TYPE(curr[0]); + regions = malloc(sizeof(region_list)); + curr_reg = regions; + curr_reg->start = start; + curr_reg->end = 0; + curr_reg->type = currtype; + curr_reg->next = nil; + for (i = 1; i < pts->npts; i++) { + int nexttype = RGN_TYPE(curr[i]); + + if (nexttype != currtype) { + region_list *next_reg; + + end = i - 1; + curr_reg->end = end; + if (lidebug > 1) + fprint(2, " (%d, %d), %d\n", start, end, currtype); + + start = i; + currtype = nexttype; + next_reg = malloc(sizeof(region_list)); + next_reg->start = start; + next_reg->end = 0; + next_reg->type = nexttype; + next_reg->next = nil; + + curr_reg->next = next_reg; + curr_reg = next_reg; + } + } + end = i - 1; + curr_reg->end = end; + if (lidebug > 1) + fprint(2, " (%d, %d), %d\n", start, end, currtype); + + /* Filter out convex/concave regions that are too short. */ + for (curr_reg = regions; curr_reg; curr_reg = curr_reg->next) + if (curr_reg->type == RGN_PLAIN) { + region_list *next_reg; + + for (next_reg = curr_reg->next; + next_reg != nil && + (next_reg->end - next_reg->start) < LP_FILTER_MIN; + next_reg = curr_reg->next) { + /* next_reg must not be plain, and it must be followed by a plain */ + /* assert(next_reg->type != RGN_PLAIN); */ + /* assert(next_reg->next != nil && (next_reg->next)->type == RGN_PLAIN); */ + + curr_reg->next = (next_reg->next)->next; + curr_reg->end = (next_reg->next)->end; + + free(next_reg->next); + free(next_reg); + } + } + + /* Add-in pseudo-extremes. */ + { + region_list *tmp, *prev_reg; + + tmp = regions; + regions = nil; + prev_reg = nil; + for (curr_reg = tmp; curr_reg; curr_reg = curr_reg->next) { + if (curr_reg->type == RGN_PLAIN) { + int arclen = lialg_compute_pathlen_subset(pts, + curr_reg->start, + curr_reg->end); + int dx = pts->pts[curr_reg->end].x - + pts->pts[curr_reg->start].x; + int dy = pts->pts[curr_reg->end].y - + pts->pts[curr_reg->start].y; + int chordlen = isqrt(10000 * (dx * dx + dy * dy)); + int atcr = (chordlen == 0) ? 0 : (100 * arclen + chordlen / 2) / chordlen; + + if (lidebug) + fprint(2, "%d, %d, %d\n", arclen, chordlen, atcr); + + /* Split region if necessary. */ + if (arclen >= PE_AL_THLD && atcr >= PE_ATCR_THLD) { + int mid = curr_reg->start + (curr_reg->end - curr_reg->start) / 2; + int end = curr_reg->end; + region_list *saved_next = curr_reg->next; + + curr_reg->end = mid - 1; + if (prev_reg == nil) + regions = curr_reg; + else + prev_reg->next = curr_reg; + prev_reg = curr_reg; + + /* curr_reg = (region_list *)safe_malloc(sizeof(region_list));*/ + curr_reg = malloc(sizeof(region_list)); + curr_reg->start = mid; + curr_reg->end = mid; + curr_reg->type = RGN_PSEUDO; + curr_reg->next = nil; + prev_reg->next = curr_reg; + prev_reg = curr_reg; + + /* curr_reg = (region_list *)malloc(sizeof(region_list)); */ + curr_reg = malloc(sizeof(region_list)); + curr_reg->start = mid + 1; + curr_reg->end = end; + curr_reg->type = RGN_PLAIN; + curr_reg->next = nil; + prev_reg->next = curr_reg; + prev_reg = curr_reg; + + curr_reg->next = saved_next; + continue; + } + } + + if (prev_reg == nil) + regions = curr_reg; + else + prev_reg->next = curr_reg; + prev_reg = curr_reg; + } + } + } + + free(junk); + return(regions); +} + + +static point_list *lialg_compute_dompts(point_list *pts, region_list *regions) { + point_list *dpts; + int ndpts; + int *cas; + int nonplain; + region_list *r; + region_list *curr; + int dp; + int previx; + int currix; + + /* Compute contour angle set. */ + cas = lialg_compute_contour_angle_set(pts, regions); + + /* Dominant points include: start_pt, end_pt, extrema_of_non_plain_regions, midpts of the preceding. */ + nonplain = 0; + for (r = regions; r != nil; r = r->next) + if (r->type != RGN_PLAIN) + nonplain++; + ndpts = 2 * (2 + nonplain) - 1; + /* dpts = (point_list *)safe_malloc(sizeof(point_list)); */ + dpts = malloc(sizeof(point_list)); + dpts->pts = mallocz(ndpts*sizeof(pen_point), 1); + if (dpts->pts == nil) { + free(dpts); + return(nil); + } + dpts->npts = ndpts; + dpts->next = nil; + + /* Pick out dominant points. */ + + /* Record start point. */ + dp = 0; + previx = 0; + dpts->pts[dp++] = pts->pts[previx]; + + for (curr = regions; curr != nil; curr = curr->next) + if (curr->type != RGN_PLAIN) { + int max_v = 0; + int min_v = 0x7fffffff; /* maxint */ + int max_ix = -1; + int min_ix = -1; + int i; + + for (i = curr->start; i <= curr->end; i++) { + int v = cas[i]; + if (v > max_v) { max_v = v; max_ix = i; } + if (v < min_v) { min_v = v; min_ix = i; } + if (lidebug > 1) + fprint(2, " %d\n", v); + } + + currix = (curr->type == RGN_CONVEX ? max_ix : min_ix); + + /* Record midpoint. */ + dpts->pts[dp++] = pts->pts[previx + (currix - previx) / 2]; + + /* Record extreme point. */ + dpts->pts[dp++] = pts->pts[currix]; + + previx = currix; + } + + /* Record last mid-point and end point. */ + currix = pts->npts - 1; + dpts->pts[dp++] = pts->pts[previx + (currix - previx) / 2]; + dpts->pts[dp] = pts->pts[currix]; + + /* Compute chain-code. */ + lialg_compute_chain_code(dpts); + + free(cas); + return(dpts); +} + + +static int *lialg_compute_contour_angle_set(point_list *pts, + region_list *regions) { + int *V; + region_list *curr_reg; + int i; + + V = malloc(pts->npts*sizeof(int)); + + V[0] = 18000; + for (curr_reg = regions; curr_reg != nil; curr_reg = curr_reg->next) { + for (i = curr_reg->start; i <= curr_reg->end; i++) { + if (curr_reg->type == RGN_PLAIN) { + V[i] = 18000; + } else { + /* For now, simply choose the mid-point. */ + int isMidPt = i == (curr_reg->start + + (curr_reg->end - curr_reg->start) / 2); + V[i] = (curr_reg->type == RGN_CONVEX) + ? (isMidPt ? 18000 : 0) + : (isMidPt ? 0 : 18000); + } + } + } + V[pts->npts - 1] = 18000; + + return(V); +} + + +/* + * First compute the similarity between the two strings. + * If it's above a threshold, compute the distance between + * the two and return it as the ``score.'' + * Otherwise, return the constant WORST_SCORE. + * + */ +static void lialg_score_stroke(point_list *input_dompts, point_list *curr_dompts, int *sim, int *dist) { + *sim = MIN_SIM; + *dist = MAX_DIST; + + *sim = lialg_compute_similarity(input_dompts, curr_dompts); + if (*sim < SIM_THLD) goto done; + + *dist = lialg_compute_distance(input_dompts, curr_dompts); + +done: + if (lidebug) + fprint(2, "%d, %d\n", *sim, *dist); +} + + +static int lialg_compute_similarity(point_list *input_dompts, point_list *curr_dompts) { + int sim; + point_list *A, *B; + int N, M; + int **G; + int *junk; + int i, j; + + /* A is the longer sequence, length N. */ + /* B is the shorter sequence, length M. */ + if (input_dompts->npts >= curr_dompts->npts) { + A = input_dompts; + N = input_dompts->npts; + B = curr_dompts; + M = curr_dompts->npts; + } else { + A = curr_dompts; + N = curr_dompts->npts; + B = input_dompts; + M = input_dompts->npts; + } + + /* Allocate and initialize the Gain matrix, G. */ + /* The size of G is M x (N + 1). */ + /* Note that row 0 is unused. */ + /* Similarities are x 10. */ + G = malloc(M*sizeof(int *)); + junk = malloc(M * (N + 1) * sizeof(int)); + for (i = 0; i < M; i++) + G[i] = junk + (i * (N + 1)); + + for (i = 1; i < M; i++) { + int bval = B->pts[i-1].chaincode; + + /* Source column. */ + G[i][0] = 0; + + for (j = 1; j < N; j++) { + int aval = A->pts[j-1].chaincode; + int diff = abs(bval - aval); + if (diff > 4) diff = 8 - diff; + + G[i][j] = (diff == 0) + ? 10 + : (diff == 1) + ? 6 + : 0; + } + + /* Sink column. */ + G[i][N] = 0; + } + + /* Do the DP algorithm. */ + /* Proceed in column order, from highest column to the lowest. */ + /* Within each column, proceed from the highest row to the lowest. */ + /* Skip the highest column. */ + for (j = N - 1; j >= 0; j--) + for (i = M - 1; i > 0; i--) { + int max = G[i][j + 1]; + + if (i < (M - 1)) { + int tmp = G[i + 1][j + 1]; + if (tmp > max) max = tmp; + } + + G[i][j] += max; + } + + sim = (10 * G[1][0] + (N - 1) / 2) / (N - 1); + + if (G != nil) + free(G); + if (junk != nil) + free(junk); + return(sim); +} + + +static int lialg_compute_distance(point_list *input_dompts, + point_list *curr_dompts) { + int dist; + point_list *A, *B; + int N, M; + int **C; + int *junk; + int *BE; + int *TE; + int i, j; + + /* A is the longer sequence, length N. */ + /* B is the shorter sequence, length M. */ + if (input_dompts->npts >= curr_dompts->npts) { + A = input_dompts; + N = input_dompts->npts; + B = curr_dompts; + M = curr_dompts->npts; + } + else { + A = curr_dompts; + N = curr_dompts->npts; + B = input_dompts; + M = input_dompts->npts; + } + + /* Construct the helper vectors, BE and TE, which say for each column */ + /* what are the ``bottom'' and ``top'' rows of interest. */ + BE = malloc((N + 1)*sizeof(int)); + TE = malloc((N + 1)*sizeof(int)); + + for (j = 1; j <= N; j++) { + int bot, top; + + bot = j + (M - DP_BAND); + if (bot > M) bot = M; + BE[j] = bot; + + top = j - (N - DP_BAND); + if (top < 1) top = 1; + TE[j] = top; + } + + /* Allocate and initialize the Cost matrix, C. */ + /* The size of C is (M + 1) x (N + 1). */ + /* Note that row and column 0 are unused. */ + /* Costs are x 100. */ + /* C = (int **)safe_malloc((M + 1) * sizeof(int *)); */ + C = malloc((M + 1)*sizeof( int *)); + junk = malloc((M + 1) * (N + 1)*sizeof(int)); + for (i = 0; i <= M; i++) + C[i] = junk + (i * (N + 1)); + + for (i = 1; i <= M; i++) { + int bx = B->pts[i-1].x; + int by = B->pts[i-1].y; + + for (j = 1; j <= N; j++) { + int ax = A->pts[j-1].x; + int ay = A->pts[j-1].y; + int dx = bx - ax; + int dy = by - ay; + int dist = isqrt(10000 * (dx * dx + dy * dy)); + + C[i][j] = dist; + } + } + + /* Do the DP algorithm. */ + /* Proceed in column order, from highest column to the lowest. */ + /* Within each column, proceed from the highest row to the lowest. */ + for (j = N; j > 0; j--) + for (i = M; i > 0; i--) { + int min = MAX_DIST; + + if (i > BE[j] || i < TE[j] || (j == N && i == M)) + continue; + + if (j < N) { + if (i >= TE[j+1]) { + int tmp = C[i][j+1]; + if (tmp < min) + min = tmp; + } + + if (i < M) { + int tmp = C[i+1][j+1]; + if (tmp < min) + min = tmp; + } + } + + if (i < BE[j]) { + int tmp = C[i+1][j]; + if (tmp < min) min = tmp; + } + + C[i][j] += min; + } + + dist = (C[1][1] + N / 2) / N; + + if (C != nil) free(C); + if (junk != nil) free(junk); + if (BE != nil) free(BE); + if (TE != nil) free(TE); + return(dist); +} + + +/************************************************************* + + Digest-processing routines + + *************************************************************/ + +static int lialg_read_classifier_digest(rClassifier *rec) { + int nclasses; + FILE *fp; + + /* Try to open the corresponding digest file. */ + { + char *clx_path; + char *dot; + + /* Get a copy of the filename, with some room on the end. */ + /* clx_path = safe_malloc(strlen(rec->file_name) + 5); */ + clx_path = malloc((strlen(rec->file_name) + 5) *sizeof(char)); + strcpy(clx_path, rec->file_name); + + /* Truncate the path after the last dot. */ + dot = strrchr(clx_path, '.'); + if (dot == nil) { free(clx_path); return(-1); } + *(dot + 1) = 0; + + /* Append the classifier-digest extension. */ + strcat(clx_path, "clx"); + + fp = fopen(clx_path, "r"); + if (fp == nil) { + free(clx_path); + return(-1); + } + + free(clx_path); + } + + /* Read-in the name and dominant points for each class. */ + for (nclasses = 0; !feof(fp); nclasses++) { + point_list *dpts = nil; + char class[BUFSIZ]; + int npts; + int j; + + if (fscanf(fp, "%s %d", class, &npts) != 2) { + if (feof(fp)) break; + + goto failed; + } + rec->cnames[nclasses] = strdup(class); + + /* Allocate a dominant-points list. */ + /* dpts = (point_list *)safe_malloc(sizeof(point_list)); */ + dpts = malloc(sizeof(point_list)); + dpts->pts = mallocz(npts*sizeof(pen_point), 1); + if (dpts->pts == nil) goto failed; + dpts->npts = npts; + dpts->next = nil; + + /* Read in each point. */ + for (j = 0; j < npts; j++) { + int x, y; + + if (fscanf(fp, "%d %d", &x, &y) != 2) goto failed; + dpts->pts[j].x = x; + dpts->pts[j].y = y; + } + + /* Compute the chain-code. */ + lialg_compute_chain_code(dpts); + + /* Store the list in the rec data structure. */ + rec->dompts[nclasses] = dpts; + + continue; + +failed: + fprint(2, "read_classifier_digest failed...\n"); + for (; nclasses >= 0; nclasses--) { + if (rec->cnames[nclasses] != nil) { + free(rec->cnames[nclasses]); + rec->cnames[nclasses] = nil; + } + if (rec->dompts[nclasses] != nil) { + delete_examples(rec->dompts[nclasses]); + rec->dompts[nclasses] = nil; + } + } + if (dpts != nil) + delete_examples(dpts); + fclose(fp); + return(-1); + } + + fclose(fp); + return(0); +} + + +/************************************************************* + + Canonicalization routines + + *************************************************************/ + +static int lialg_canonicalize_examples(rClassifier *rec) { + int i; + int nclasses; + + if (lidebug) { + fprint(2, "lialg_canonicalize_examples working on %s\n", + rec->file_name); + } + /* Initialize canonical-example arrays. */ + for (i = 0; i < MAXSCLASSES; i++) { + rec->canonex[i] = nil; + } + + /* Figure out number of classes. */ + for (nclasses = 0; + nclasses < MAXSCLASSES && rec->cnames[nclasses] != nil; + nclasses++) + ; + + /* Canonicalize the examples for each class. */ + for (i = 0; i < nclasses; i++) { + int j, k; + int nex; + point_list *pts, *tmp, *avg; + int maxxrange, maxyrange; + int minx, miny, maxx, maxy; + int avgxrange, avgyrange, avgxoff, avgyoff, avgscale; + + + if (lidebug) { + fprint(2, "lialg_canonicalize_examples working on class %s\n", + rec->cnames[i]); + } + /* Make a copy of the examples. */ + pts = nil; + tmp = rec->ex[i]; + for (nex = 0; tmp != nil; nex++, tmp = tmp->next) { + if ((pts = add_example(pts, tmp->npts, tmp->pts)) == nil) { + delete_examples(pts); + return(-1); + } + } + + /* Canonicalize each example, and derive the max x and y ranges. */ + maxxrange = 0; + maxyrange = 0; + for (j = 0, tmp = pts; j < nex; j++, tmp = tmp->next) { + if (lialg_canonicalize_example_stroke(tmp) != 0) { + if (lidebug) { + fprint(2, "lialg_canonicalize_example_stroke returned error\n"); + } + return(-1); + } + + if (tmp->xrange > maxxrange) maxxrange = tmp->xrange; + if (tmp->yrange > maxyrange) maxyrange = tmp->yrange; + } + + /* Normalize max ranges. */ + if (((100 * maxxrange + CANONICAL_X / 2) / CANONICAL_X) > + ((100 * maxyrange + CANONICAL_Y / 2) / CANONICAL_Y)) { + maxyrange = (maxyrange * CANONICAL_X + maxxrange / 2) / maxxrange; + maxxrange = CANONICAL_X; + } + else { + maxxrange = (maxxrange * CANONICAL_Y + maxyrange / 2) / maxyrange; + maxyrange = CANONICAL_Y; + } + + /* Re-scale each example to max ranges. */ + for (j = 0, tmp = pts; j < nex; j++, tmp = tmp->next) { + int scalex = (tmp->xrange == 0) ? 100 : (100 * maxxrange + tmp->xrange / 2) / tmp->xrange; + int scaley = (tmp->yrange == 0) ? 100 : (100 * maxyrange + tmp->yrange / 2) / tmp->yrange; + if (lialg_translate_points(tmp, 0, 0, scalex, scaley) != 0) { + delete_examples(pts); + return(-1); + } + } + + /* Average the examples; leave average in first example. */ + avg = pts; /* careful aliasing!! */ + for (k = 0; k < NCANONICAL; k++) { + int xsum = 0; + int ysum = 0; + + for (j = 0, tmp = pts; j < nex; j++, tmp = tmp->next) { + xsum += tmp->pts[k].x; + ysum += tmp->pts[k].y; + } + + avg->pts[k].x = (xsum + j / 2) / j; + avg->pts[k].y = (ysum + j / 2) / j; + } + + /* Compute BB of averaged stroke and re-scale. */ + lialg_get_bounding_box(avg, &minx, &miny, &maxx, &maxy); + avgxrange = maxx - minx; + avgyrange = maxy - miny; + avgscale = (((100 * avgxrange + CANONICAL_X / 2) / CANONICAL_X) > + ((100 * avgyrange + CANONICAL_Y / 2) / CANONICAL_Y)) + ? (100 * CANONICAL_X + avgxrange / 2) / avgxrange + : (100 * CANONICAL_Y + avgyrange / 2) / avgyrange; + if (lialg_translate_points(avg, minx, miny, avgscale, avgscale) != 0) { + delete_examples(pts); + return(-1); + } + + /* Re-compute the x and y ranges and center the stroke. */ + lialg_get_bounding_box(avg, &minx, &miny, &maxx, &maxy); + avgxrange = maxx - minx; + avgyrange = maxy - miny; + avgxoff = -((CANONICAL_X - avgxrange + 1) / 2); + avgyoff = -((CANONICAL_Y - avgyrange + 1) / 2); + if (lialg_translate_points(avg, avgxoff, avgyoff, 100, 100) != 0) { + delete_examples(pts); + return(-1); + } + + /* Create a point list to serve as the ``canonical representation. */ + if ((rec->canonex[i] = add_example(nil, avg->npts, avg->pts)) == nil) { + delete_examples(pts); + return(-1); + } + (rec->canonex[i])->xrange = maxx - minx; + (rec->canonex[i])->yrange = maxy - miny; + + if (lidebug) { + fprint(2, "%s, avgpts = %d\n", rec->cnames[i], avg->npts); + for (j = 0; j < avg->npts; j++) { + fprint(2, " (%P)\n", avg->pts[j].Point); + } + } + + /* Compute dominant points of canonical representation. */ + rec->dompts[i] = lialg_compute_dominant_points(avg); + + /* Clean up. */ + delete_examples(pts); + } + + /* Sanity check. */ + for (i = 0; i < nclasses; i++) { + char *best_name = lialg_recognize_stroke(rec, rec->canonex[i]); + + if (best_name != rec->cnames[i]) + fprint(2, "%s, best = %s\n", rec->cnames[i], best_name); + } + + return(0); +} + + +static int lialg_canonicalize_example_stroke(point_list *points) { + int minx, miny, maxx, maxy, xrange, yrange, scale; + + /* Filter out points that are too close. */ + if (lialg_filter_points(points) != 0) return(-1); + + /* Must be at least two points! */ + if (points->npts < 2) { + if (lidebug) { + fprint(2, "lialg_canonicalize_example_stroke: npts=%d\n", + points->npts); + } + return(-1); + } + + /* Scale up to avoid conversion errors. */ + lialg_get_bounding_box(points, &minx, &miny, &maxx, &maxy); + xrange = maxx - minx; + yrange = maxy - miny; + scale = (((100 * xrange + CANONICAL_X / 2) / CANONICAL_X) > + ((100 * yrange + CANONICAL_Y / 2) / CANONICAL_Y)) + ? (100 * CANONICAL_X + xrange / 2) / xrange + : (100 * CANONICAL_Y + yrange / 2) / yrange; + if (lialg_translate_points(points, minx, miny, scale, scale) != 0) { + if (lidebug) { + fprint(2, "lialg_translate_points (minx=%d,miny=%d,scale=%d) returned error\n", minx, miny, scale); + } + return(-1); + } + + /* Compute an equivalent stroke with equi-distant points. */ + if (lialg_compute_equipoints(points) != 0) return(-1); + + /* Re-translate the points to the origin. */ + lialg_get_bounding_box(points, &minx, &miny, &maxx, &maxy); + if (lialg_translate_points(points, minx, miny, 100, 100) != 0) { + if (lidebug) { + fprint(2, "lialg_translate_points (minx=%d,miny=%d) returned error\n", minx, miny); + } + return(-1); + } + + /* Store the x and y ranges in the point list. */ + xrange = maxx - minx; + yrange = maxy - miny; + points->xrange = xrange; + points->yrange = yrange; + + if (lidebug) { + int i; + fprint(2, "Canonicalized: %d, %d, %d, %d\n", minx, miny, maxx, maxy); + for (i = 0; i < points->npts; i++) + fprint(2, " (%P)\n", points->pts[i].Point); + fflush(stderr); + } + + return(0); +} + + +static int lialg_compute_equipoints(point_list *points) { + pen_point *equipoints = mallocz(NCANONICAL*sizeof(pen_point), 1); + int nequipoints = 0; + int pathlen = lialg_compute_pathlen(points); + int equidist = (pathlen + (NCANONICAL - 1) / 2) / (NCANONICAL - 1); + int i; + int dist_since_last_eqpt; + int remaining_seglen; + int dist_to_next_eqpt; + + if (equipoints == nil) { + fprint(2, "can't allocate memory in lialg_compute_equipoints"); + return(-1); + } + + if (lidebug) { + fprint(2, "compute_equipoints: npts = %d, pathlen = %d, equidist = %d\n", + points->npts, pathlen, equidist); + fflush(stderr); + } + + /* First original point is an equipoint. */ + equipoints[0] = points->pts[0]; + nequipoints++; + dist_since_last_eqpt = 0; + + for (i = 1; i < points->npts; i++) { + int dx1 = points->pts[i].x - points->pts[i-1].x; + int dy1 = points->pts[i].y - points->pts[i-1].y; + int endx = 100 * points->pts[i-1].x; + int endy = 100 * points->pts[i-1].y; + remaining_seglen = isqrt(10000 * (dx1 * dx1 + dy1 * dy1)); + dist_to_next_eqpt = equidist - dist_since_last_eqpt; + + while (remaining_seglen >= dist_to_next_eqpt) { + if (dx1 == 0) { + /* x-coordinate stays the same */ + if (dy1 >= 0) + endy += dist_to_next_eqpt; + else + endy -= dist_to_next_eqpt; + } + else { + int slope = (100 * dy1 + dx1 / 2) / dx1; + int tmp = isqrt(10000 + slope * slope); + int dx = (100 * dist_to_next_eqpt + tmp / 2) / tmp; + int dy = (slope * dx + 50) / 100; + + if (dy < 0) dy = -dy; + if (dx1 >= 0) + endx += dx; + else + endx -= dx; + if (dy1 >= 0) + endy += dy; + else + endy -= dy; + } + + equipoints[nequipoints].x = (endx + 50) / 100; + equipoints[nequipoints].y = (endy + 50) / 100; + nequipoints++; +/* assert(nequipoints <= NCANONICAL);*/ + dist_since_last_eqpt = 0; + remaining_seglen -= dist_to_next_eqpt; + dist_to_next_eqpt = equidist; + } + + dist_since_last_eqpt += remaining_seglen; + } + + /* Take care of last equipoint. */ + if (nequipoints == NCANONICAL) { + /* Good. */ + } else if (nequipoints == (NCANONICAL - 1)) { + /* Make last original point the last equipoint. */ + equipoints[nequipoints] = points->pts[points->npts - 1]; + } else { + if (lidebug) { + fprint(2,"lialg_compute_equipoints: nequipoints = %d\n", + nequipoints); + } +/* assert(false);*/ + return(-1); + } + + points->npts = NCANONICAL; + delete_pen_point_array(points->pts); + points->pts = equipoints; + return(0); +} + + +/************************************************************* + + Utility routines + + *************************************************************/ + +/* Result is x 100. */ +static int lialg_compute_pathlen(point_list *points) { + return(lialg_compute_pathlen_subset(points, 0, points->npts - 1)); +} + + +/* Result is x 100. */ +static int lialg_compute_pathlen_subset(point_list *points, + int start, int end) { + int pathlen; + int i; + + pathlen = 0; + for (i = start + 1; i <= end; i++) { + int dx = points->pts[i].x - points->pts[i-1].x; + int dy = points->pts[i].y - points->pts[i-1].y; + int dist = isqrt(10000 * (dx * dx + dy * dy)); + pathlen += dist; + } + + return(pathlen); +} + + +/* Note that this does NOT update points->xrange and points->yrange! */ +static int lialg_filter_points(point_list *points) { + int filtered_npts; + pen_point *filtered_pts = mallocz(points->npts*sizeof(pen_point), 1); + int i; + + if (filtered_pts == nil) { + fprint(2, "can't allocate memory in lialg_filter_points"); + return(-1); + } + + filtered_pts[0] = points->pts[0]; + filtered_npts = 1; + for (i = 1; i < points->npts; i++) { + int j = filtered_npts - 1; + int dx = points->pts[i].x - filtered_pts[j].x; + int dy = points->pts[i].y - filtered_pts[j].y; + int magsq = dx * dx + dy * dy; + + if (magsq >= DIST_SQ_THRESHOLD) { + filtered_pts[filtered_npts] = points->pts[i]; + filtered_npts++; + } + } + + points->npts = filtered_npts; + delete_pen_point_array(points->pts); + points->pts = filtered_pts; + return(0); +} + + +/* scalex and scaley are x 100. */ +/* Note that this does NOT update points->xrange and points->yrange! */ +static int lialg_translate_points(point_list *points, + int minx, int miny, + int scalex, int scaley) { + int i; + + for (i = 0; i < points->npts; i++) { + points->pts[i].x = ((points->pts[i].x - minx) * scalex + 50) / 100; + points->pts[i].y = ((points->pts[i].y - miny) * scaley + 50) / 100; + } + + return(0); +} + + +static void lialg_get_bounding_box(point_list *points, + int *pminx, int *pminy, + int *pmaxx, int *pmaxy) { + int minx, miny, maxx, maxy; + int i; + + minx = maxx = points->pts[0].x; + miny = maxy = points->pts[0].y; + for (i = 1; i < points->npts; i++) { + pen_point *pt = &(points->pts[i]); + if (pt->x < minx) minx = pt->x; + else if (pt->x > maxx) maxx = pt->x; + if (pt->y < miny) miny = pt->y; + else if (pt->y > maxy) maxy = pt->y; + } + + *pminx = minx; + *pminy = miny; + *pmaxx = maxx; + *pmaxy = maxy; +} + + +int wtvals[] = {100, 104, 117, 143, 189, 271, 422}; + +static void lialg_compute_lpf_parameters(void) { + int i; + + for (i = LP_FILTER_WIDTH; i >= 0; i--) { +// double x = 0.04 * (i * i); +// double tmp = 100.0 * exp(x); +// int wt = floor((double)tmp); + int wt = wtvals[i]; + lialg_lpfwts[LP_FILTER_WIDTH - i] = wt; + lialg_lpfwts[LP_FILTER_WIDTH + i] = wt; + } + lialg_lpfconst = 0; + for (i = 0; i < (2 * LP_FILTER_WIDTH + 1); i++) { + lialg_lpfconst += lialg_lpfwts[i]; + } +} + + +/* Code from Joseph Hall (jnhall@sat.mot.com). */ +static int isqrt(int n) { + register int i; + register long k0, k1, nn; + + for (nn = i = n, k0 = 2; i > 0; i >>= 2, k0 <<= 1) + ; + nn <<= 2; + for (;;) { + k1 = (nn / k0 + k0) >> 1; + if (((k0 ^ k1) & ~1) == 0) + break; + k0 = k1; + } + return (int) ((k1 + 1) >> 1); +} + + +/* Helper routines from Mark Hayter. */ +static int likeatan(int tantop, int tanbot) { + int t; + /* Use tan(theta)=top/bot --> order for t */ + /* t in range 0..0x40000 */ + + if ((tantop == 0) && (tanbot == 0)) + t = 0; + else + { + t = (tantop << 16) / (abs(tantop) + abs(tanbot)); + if (tanbot < 0) + t = 0x20000 - t; + else + if (tantop < 0) t = 0x40000 + t; + } + return t; +} + + +static int quadr(int t) { + return (8 - (((t + 0x4000) >> 15) & 7)) & 7; +} diff --git a/sys/src/libscribble/li_recognizer_internal.h b/sys/src/libscribble/li_recognizer_internal.h new file mode 100755 index 000000000..ebad8048d --- /dev/null +++ b/sys/src/libscribble/li_recognizer_internal.h @@ -0,0 +1,42 @@ +/* + * li_recognizer_internal.h + * + * Adapted from cmu_recognizer_internal.h. + * Credit to Dean Rubine, Jim Kempf, and Ari Rapkin. + */ + +#define MAXSCLASSES 100 + +typedef struct PointList { + Stroke; + int xrange, yrange; + struct PointList* next; +} point_list; + +typedef struct { + char* file_name; /*The classifier file name.*/ + int nclasses; /*Number of symbols in class */ + point_list* ex[MAXSCLASSES]; /*The training examples.*/ + char* cnames[MAXSCLASSES]; /*The class names.*/ + point_list* canonex[MAXSCLASSES]; /*Canonicalized vrsions of strokes */ + point_list* dompts[MAXSCLASSES]; /*Dominant points */ +} rClassifier; + + +/*This structure contains extra fields for instance-specific data.*/ + +typedef struct { + /*Instance-specific data.*/ + uint li_magic; /*Just to make sure nobody's cheating.*/ + rClassifier li_rc; /*The character classifier.*/ +} li_recognizer; + + +/*Name of the default classifier file.*/ +#define LI_DEFAULT_CLASSIFIER_FILE "default.cl" + +/*Classifier file extension.*/ +#define LI_CLASSIFIER_EXTENSION ".cl" + +/*Locale supported by recognizer.*/ +#define LI_SUPPORTED_LOCALE REC_DEFAULT_LOCALE diff --git a/sys/src/libscribble/mkfile b/sys/src/libscribble/mkfile new file mode 100755 index 000000000..fa56912e1 --- /dev/null +++ b/sys/src/libscribble/mkfile @@ -0,0 +1,31 @@ +</$objtype/mkfile + +LIB=/$objtype/lib/libscribble.a + +OFILES=\ + li_recognizer.$O\ + hre_api.$O\ + graffiti.$O\ + +HFILES = \ + li_recognizer_internal.h\ + hre_internal.h\ + graffiti.h\ + scribbleimpl.h\ + +UPDATE=\ + mkfile\ + $HFILES\ + ${OFILES:%.$O=%.c}\ + ${LIB:/$objtype/%=/386/%}\ + +all:V: $LIB syms + +< /sys/src/cmd/mksyslib + +syms: $HFILES + $CC -a li_recognizer.c >syms + +graffiti.$O: scribbleimpl.h graffiti.h +hre_api.$O: scribbleimpl.h hre_internal.h +li_recognizer.$O: scribbleimpl.h hre_internal.h li_recognizer_internal.h diff --git a/sys/src/libscribble/scribbleimpl.h b/sys/src/libscribble/scribbleimpl.h new file mode 100755 index 000000000..11d32218b --- /dev/null +++ b/sys/src/libscribble/scribbleimpl.h @@ -0,0 +1,417 @@ +/* + * scribble.h: User-Level API for Handwriting Recognition + * Author: James Kempf + * Created On: Mon Nov 2 14:01:25 1992 + * Last Modified By: Sape Mullender + * Last Modified On: Fri Aug 25 10:24:50 EDT 2000 + * Copyright (c) 1994 by Sun Microsystems Computer Company + * All rights reserved. + * + * Use and copying of this software and preparation of + * derivative works based upon this software are permitted. + * Any distribution of this software or derivative works + * must comply with all applicable United States export control + * laws. + * + * This software is made available as is, and Sun Microsystems + * Computer Company makes no warranty about the software, its + * performance, or its conformity to any specification + */ + +/* + * Opaque type for the recognizer. The toolkit must access through + * appropriate access functions. + */ +#pragma incomplete struct _Recognizer +typedef struct _Recognizer* recognizer; + +/* + * Opaque type for recognizers to implement dictionaries. + */ + +typedef struct _wordset *wordset; +typedef struct rc rc; +typedef struct rec_correlation rec_correlation; +typedef struct rec_alternative rec_alternative; +typedef struct rec_element rec_element; +typedef struct gesture gesture; +typedef uint wchar_t; + +/* Scalar Type Definitions */ + +/* For better readibility.*/ + +typedef int bool; + +#define true 1 +#define false 0 + +/*For pointers to extra functions on recognizer.*/ + +typedef void (*rec_fn)(); + +/* + * rec_confidence is an integer between 0-100 giving the confidence of the + * recognizer in a particular result. + */ + +typedef uchar rec_confidence; + +/**************** RECOGNIZER CONFIGURATION INFORMATION *******************/ + +/* + * Recognizer information. Gives the locale, category of the character + * set returned by the recognizer, and any subsets to which the + * recognition can be limited. The locale and category should be + * suitable for the setlocale(3). Those recognizers which don't do text + * can simply report a blank locale and category, and report the + * graphics types they recognize in the subset. + */ + +typedef struct { + char* ri_locale; /*The locale of the character set.*/ + char* ri_name; /*Complete pathname to the recognizer.*/ + char** ri_subset; /*Null terminated list of subsets supported*/ +} rec_info; + +/*These define a set of common character subset names.*/ + +#define GESTURE "GESTURE" /* gestures only */ +#define MATHSET "MATHSET" /* %^*()_+={}<>,/. */ +#define MONEYSET "MONEYSET" /* $, maybe cent, pound, and yen */ +#define WHITESPACE "WHITESPACE" /* gaps are recognized as space */ +#define KANJI_JIS1 "KANJI_JIS1" /* the JIS1 kanji only */ +#define KANJI_JIS1_PLUS "KANJI_JIS1_PLUS" /* JIS1 plus some JIS2 */ +#define KANJI_JIS2 "KANJI_JIS2" /* the JIS1 + JIS2 kanji */ +#define HIRIGANA "HIRIGANA" /* the hirigana */ +#define KATAKANA "KATAKANA" /* the katakana */ +#define UPPERCASE "UPPERCASE" /* upper case alphabetics, no digits */ +#define LOWERCASE "LOWERCASE" /* lower case alphabetics, no digits */ +#define DIGITS "DIGITS" /* digits 0-9 only */ +#define PUNCTUATION "PUNCTUATION" /* \!-;'"?()&., */ +#define NONALPHABETIC "NONALPHABETIC" /* all nonalphabetics, no digits */ +#define ASCII "ASCII" /* the ASCII character set */ +#define ISO_LATIN12 "ISO_LATIN12" /* The ISO Latin 12 characters */ + + +/******************** RECOGNITION INPUT STRUCTURES ***********************/ + +/* + * WINDOW SYSTEM INTERFACE +*/ + +/*Bounding box. Structurally identical to Rectangle.*/ + +typedef Rectangle pen_rect; + + +/* + * RECOGNITION CONTEXT + */ + +/* Structure for reporting writing area geometric constraints. */ + +typedef struct { + pen_rect pr_area; + short pr_row, pr_col; +} pen_frame; + +/* + * Structure for describing a set of letters to constrain recognition. + * ls_type is the same as the re_type field for rec_element below. +*/ + +typedef struct _letterset { + char ls_type; + union _ls_set { + char* aval; + wchar_t* wval; + } ls_set; +} letterset; + +/********************* RECOGNITION RETURN VALUES *************************/ + + +/*Different types in union. "Other" indicates a cast is needed.*/ + +#define REC_NONE 0x0 /*No return value*/ +#define REC_GESTURE 0x1 /*Gesture.*/ +#define REC_ASCII 0x2 /*Array of 8 bit ASCII*/ +#define REC_VAR 0x4 /*Array of variable width characters. */ +#define REC_WCHAR 0x8 /*Array of Unicode (wide) characters. */ +#define REC_OTHER 0x10 /*Undefined type.*/ +#define REC_CORR 0x20 /*rec_correlation struct*/ + +/* + * Recognition elements. A recognition element is a structure having a + * confidence level member, and a union, along with a flag indicating + * the union type. The union contains a pointer to the result. This + * is the basic recognition return value, corresponding to one + * recognized word, letter, or group of letters. +*/ + +struct rec_element { + char re_type; /*Union type flag.*/ + union { + gesture * gval; /*Gesture.*/ + char* aval; /*ASCII and variable width.*/ + wchar_t* wval; /*Unicode.*/ + rec_correlation* rcval; /*rec_correlation*/ + } re_result; + rec_confidence re_conf; /*Confidence (0-100).*/ +}; + +/* + * Recognition alternative. The recognition alternative gives + * a translated element for a particular segmentation, and + * a pointer to an array of alternatives for the next position + * in the segmentation thread. +*/ + +struct rec_alternative { + rec_element ra_elem; /*the translated element*/ + uint ra_nalter; /*number of next alternatives*/ + rec_alternative* ra_next; /*the array of next alternatives*/ +}; + +/************************** GESTURES **************************/ + +/* + * Gestures. The toolkit initializes the recognizer with a + * set of gestures having appropriate callbacks. + * When a gesture is recognized, it is returned as part of a + * recognition element. The recognizer fills in the bounding + * box and hotspots. The toolkit fills in any additional values, + * such as the current window, and calls the callback. +*/ + +struct gesture { + char* g_name; /*The gesture's name.*/ + uint g_nhs; /*Number of hotspots.*/ + pen_point* g_hspots; /*The hotspots.*/ + pen_rect g_bbox; /*The bounding box.*/ + void (*g_action)(gesture*); /*Pointer to execution function.*/ + void* g_wsinfo; /*For toolkit to fill in.*/ +}; + +typedef void (*xgesture)(gesture*); + +/* + * Recognition correlation. A recognition correlation is a recognition + * of the stroke input along with a correlation between the stroke + * input and the recognized text. The rec_correlation struct contains + * a pointer to an arrray of pointers to strokes, and + * two arrays of integers, giving the starting point and + * stopping point of each corresponding recogition element returned + * in the strokes. + */ + +struct rec_correlation { + rec_element ro_elem; /*The recognized alternative.*/ + uint ro_nstrokes; /*Number of strokes.*/ + Stroke* ro_strokes; /*Array of strokes.*/ + uint* ro_start; /*Starting index of points.*/ + uint* ro_stop; /*Stopping index of points.*/ +}; + +/* + * ADMINISTRATION + */ + +/* + * recognizer_load - If directory is not NULL, then use it as a pathname + * to find the recognizer. Otherwise, use the default naming conventions + * to find the recognizer having file name name. The subset argument + * contains a null-terminated array of names for character subsets which + * the recognizer should translate. + */ + +recognizer recognizer_load(char*, char*, char**); + +/* + * recognizer_unload - Unload the recognizer. + */ + +int recognizer_unload(recognizer); + +/* + * recognizer_get_info-Get a pointer to a rec_info + * giving the locale and subsets supported by the recognizer, and shared + * library pathname. + */ + +const rec_info* recognizer_get_info(recognizer); + +/* + * recognizer_manager_version-Return the version number string of the + * recognition manager. + */ + +const char* recognizer_manager_version(recognizer); + +/* + * recognizer_load_state-Get any recognizer state associated with name + * in dir. Note that name may not be simple file name, since + * there may be more than one file involved. Return 0 if successful, + * -1 if not. + */ + +int recognizer_load_state(recognizer, char*, char*); + +/* + * recognizer_save_state-Save any recognizer state to name + * in dir. Note that name may not be a simple file name, since + * there may be more than one file involved. Return 0 if successful, + * -1 if not. + */ + +int recognizer_save_state(recognizer, char*, char*); + +/* + * recognizer_error-Return the last error message, or NULL if none. + */ + +char* recognizer_error(recognizer); + +/* + * DICTIONARIES + */ + +/* recognizer_load_dictionary-Load a dictionary from the directory + * dir and file name. Return the dictionary pointer if successful, + * otherwise NULL. + */ + +wordset recognizer_load_dictionary(recognizer, char*, char*); + +/* recoginzer_save_dictionary-Save the dictionary to the file. Return 0 + * successful, -1 if error occurs. + */ + +int recognizer_save_dictionary(recognizer, char*, char*, wordset); + +/* + * recognizer_free_dictionary-Free the dictionary. Return 0 if successful, + * -1 if error occurs. + */ + +int recognizer_free_dictionary(recognizer, wordset); + +/* + * recognizer_add_to_dictionary-Add the word to the dictionary. Return 0 + * if successful, -1 if error occurs. + */ + +int recognizer_add_to_dictionary(recognizer, letterset*, wordset); + +/* + * recognizer_delete_from_dictionary-Delete the word from the dictionary. + * Return 0 if successful, -1 if error occurs. + */ + +int recognizer_delete_from_dictionary(recognizer, letterset*, wordset); + +/* + * TRANSLATION + */ + +/* recognizer_set/get_context - Set/get the recognition context for + * subsequent buffering and translation. recognizer_set_context() + * returns -1 if an error occurs, otherwise 0. recognizer_get_context() + * returns NULL if no context has been set. The context is copied to avoid + * potential memory deallocation problems. + */ + +int recognizer_set_context(recognizer, rc*); +rc* recognizer_get_context(recognizer); + +/* recognizer_clear - Set stroke buffer to NULL and clear the context. + * Returns -1 if an error occurred, otherwise 0. Both the context and the + * stroke buffer are deallocated. If delete_points_p is true, delete the + * points also. + */ + +int recognizer_clear(recognizer, bool); + +/* recognizer_get/set_buffer - Get/set the stroke buffer. The stroke buffer + * is copied to avoid potential memory allocation problems. Returns -1 if + * an error occurs, otherwise 0. + */ + +int recognizer_get_buffer(recognizer, uint*, Stroke**); +int recognizer_set_buffer(recognizer, uint, Stroke*); + +/* recognizer_translate - Copy the strokes argument into the stroke buffer and + * translate the buffer. If correlate_p is true, then provide stroke + * correlations as well. If either nstrokes is 0 or strokes is NULL, then + * just translate the stroke buffer and return the translation. Return an + * array of alternative translation segmentations in the ret pointer and the + * number of alternatives in nret, or NULL and 0 if there is no translation. + * The direction of segmentation is as specified by the rc_direction field in + * the buffered recognition context. Returns -1 if an error occurred, + * otherwise 0. + */ + +int recognizer_translate(recognizer, uint, Stroke*, bool, + int*, rec_alternative**); + +/* + * recognizer_get_extension_functions-Return a null terminated array + * of functions providing extended functionality. Their interfaces + * will change depending on the recognizer. + */ + +rec_fn* recognizer_get_extension_functions(recognizer); + +/* + * GESTURE SUPPORT +*/ + +/* + * recognizer_get_gesture_names - Return a null terminated array of + * character strings containing the gesture names. + */ + +char** recognizer_get_gesture_names(recognizer); + +/* + * recognizer_set_gesture_action-Set the action function associated with the + * name. + */ + +xgesture recognizer_set_gesture_action(recognizer, char*, xgesture, void*); + +/* + * The following functions are for deleting data structures returned + * by the API functions. + */ + +void delete_rec_alternative_array(uint, rec_alternative*, bool); +void delete_rec_correlation(rec_correlation*, bool); + +/* + * These are used by clients to create arrays for passing to API + * functions. + */ + +Stroke* make_Stroke_array(uint); +void delete_Stroke_array(uint, Stroke*, bool); + +pen_point* make_pen_point_array(uint); +void delete_pen_point_array(pen_point*); + +Stroke* copy_Stroke_array(uint, Stroke*); + +/*Extension function interfaces and indices.*/ + +#define LI_ISA_LI 0 /*Is this a li recognizer?.*/ +#define LI_TRAIN 1 /*Train recognizer*/ +#define LI_CLEAR 2 /* ari's clear-state extension fn. */ +#define LI_GET_CLASSES 3 /* ari's get-classes extension fn. */ +#define LI_NUM_EX_FNS 4 /*Number of extension functions*/ + +typedef bool (*li_isa_li)(recognizer r); +typedef int (*li_recognizer_train)(recognizer, rc*, uint, + Stroke*, rec_element*, bool); +typedef int (*li_recognizer_clearState)(recognizer); +typedef int (*li_recognizer_getClasses)(recognizer, char ***, int *); |