ref: 314faec39447f98c0dfd96a91dbd25fd757d106d
dir: /sys/src/libscribble/hre_api.c/
/* * 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 rec_info* make_rec_info(char* directory,char* name,char** subset); static void delete_rec_info(rec_info* ri); 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; 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. */ /* * 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); } } /* * 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); }