summaryrefslogtreecommitdiff
path: root/sys/src/libscribble
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/libscribble
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/libscribble')
-rwxr-xr-xsys/src/libscribble/graffiti.c266
-rwxr-xr-xsys/src/libscribble/graffiti.h21
-rwxr-xr-xsys/src/libscribble/hre_api.c1300
-rwxr-xr-xsys/src/libscribble/hre_internal.h149
-rwxr-xr-xsys/src/libscribble/li_recognizer.c2313
-rwxr-xr-xsys/src/libscribble/li_recognizer_internal.h42
-rwxr-xr-xsys/src/libscribble/mkfile31
-rwxr-xr-xsys/src/libscribble/scribbleimpl.h417
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 *);