1 /* Copyright (C) 2000-2012 by George Williams */
2 /*
3  * Redistribution and use in source and binary forms, with or without
4  * modification, are permitted provided that the following conditions are met:
5 
6  * Redistributions of source code must retain the above copyright notice, this
7  * list of conditions and the following disclaimer.
8 
9  * Redistributions in binary form must reproduce the above copyright notice,
10  * this list of conditions and the following disclaimer in the documentation
11  * and/or other materials provided with the distribution.
12 
13  * The name of the author may not be used to endorse or promote products
14  * derived from this software without specific prior written permission.
15 
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include <fontforge-config.h>
29 #include <fontforge-version-extras.h>
30 
31 #include "autosave.h"
32 #include "bitmapchar.h"
33 #include "clipnoui.h"
34 #include "encoding.h"
35 #include "ffgdk.h"
36 #include "ffglib.h"
37 #include "fontforgeui.h"
38 #include "gfile.h"
39 #include "gresource.h"
40 #include "hotkeys.h"
41 #include "lookups.h"
42 #include "prefs.h"
43 #include "start.h"
44 #include "ustring.h"
45 
46 #include <locale.h>
47 #include <stdlib.h>		/* getenv,setenv */
48 #include <sys/stat.h>
49 #include <sys/time.h>
50 #include <sys/types.h>
51 #include <time.h>
52 #include <unistd.h>
53 
54 #if defined(__MINGW32__)
55 #include <windows.h>
56 #define sleep(n) Sleep(1000 * (n))
57 #endif
58 
59 #ifndef _NO_LIBUNICODENAMES
60 #include <libunicodenames.h>	/* need to open a database when we start */
61 extern uninm_names_db names_db; /* Unicode character names and annotations database */
62 extern uninm_blocks_db blocks_db;
63 #endif
64 
65 #ifdef __Mac
66 extern void setup_cocoa_app();
67 #endif
68 
69 #ifdef _NO_LIBPNG
70 #  define PNGLIBNAME	"libpng"
71 #else
72 #  include <png.h>		/* for version number to find up shared image name */
73 #  if !defined(PNG_LIBPNG_VER_MAJOR) || (PNG_LIBPNG_VER_MAJOR==1 && PNG_LIBPNG_VER_MINOR<2)
74 #    define PNGLIBNAME	"libpng"
75 #  else
76 #    define xstr(s) str(s)
77 #    define str(s) #s
78 #    define PNGLIBNAME	"libpng" xstr(PNG_LIBPNG_VER_MAJOR) xstr(PNG_LIBPNG_VER_MINOR)
79 #  endif
80 #endif
81 #ifdef __Mac
82 #  include "carbon.h"
83 /* For reasons obscure to me RunApplicationEventLoop is not defined in */
84 /*  the mac header files if we are in 64 bit mode. Strangely it seems to */
85 /*  be in the libraries and functional */
86 /*
87  * It was found in Dec 2014 that using RunApplicationEventLoop() could induce strange
88  * and extremely frustrating pausing issues on osx. The main generic event handling
89  * seems to work just fine, so there doesn't seem to be a need for this specialized
90  * Application Event Loop.
91  *
92  * See this issue bringing back Breakpad usage and the issues linked in comments 2,3
93  * by adrientetar:
94  * https://github.com/fontforge/fontforge/issues/2120
95  */
96 //#  if __LP64__
97 //extern void RunApplicationEventLoop(void);
98 //#  endif
99 #endif
100 
101 // Must be included after png.h because it messes with setjmp
102 #include "scripting.h"
103 
104 extern int AutoSaveFrequency;
105 int splash = 1;
106 static int localsplash;
107 static int unique = 0;
108 
109 /**
110  * In osx versions prior to 10.9.x a special -psn_ flag was supplied
111  * when fontforge was run by osx in some cases. For opening an sfd
112  * file from finder we need to register the openWith event in order to
113  * get the name of the file to open. So it makes sense to always
114  * register for Apple events on OSX so that we can get those file
115  * names as they come through.
116  */
117 #if defined(__Mac)
118     static int listen_to_apple_events = true; // This was once true, but Apple broke it.
119 #else
120     static int listen_to_apple_events = false;
121 #endif
122 static bool ProcessPythonInitFiles = 1;
123 
_dousage(void)124 static void _dousage(void) {
125     printf( "fontforge [options] [fontfiles]\n" );
126     printf( "\t-new\t\t\t (creates a new font)\n" );
127     printf( "\t-last\t\t\t (loads the last sfd file closed)\n" );
128 #if HANYANG
129     printf( "\t-newkorean\t\t (creates a new korean font)\n" );
130 #endif
131     printf( "\t-recover none|auto|inquire|clean (control error recovery)\n" );
132     printf( "\t-allglyphs\t\t (load all glyphs in the 'glyf' table\n\t\t\t of a truetype collection)\n" );
133     printf( "\t-nosplash\t\t (no splash screen)\n" );
134     printf( "\t-quiet\t\t\t (don't print non-essential information to stderr)\n" );
135     printf( "\t-unique\t\t\t (if a fontforge is already running open\n\t\t\t all arguments in it and have this process exit)\n" );
136     printf( "\t-display display-name\t (sets the X display)\n" );
137     printf( "\t-depth val\t\t (sets the display depth if possible)\n" );
138     printf( "\t-vc val\t\t\t (sets the visual class if possible)\n" );
139     printf( "\t-cmap current|copy|private\t (sets the type of colormap)\n" );
140     printf( "\t-dontopenxdevices\t (in case that fails)\n" );
141     printf( "\t-sync\t\t\t (syncs the display, debugging)\n" );
142     printf( "\t-keyboard ibm|mac|sun|ppc  (generates appropriate hotkeys in menus)\n" );
143 #if MyMemory
144     printf( "\t-memory\t\t\t (turns on memory checks, debugging)\n" );
145 #endif
146 #ifndef _NO_LIBCAIRO
147     printf( "\t-usecairo=yes|no  Use (or not) the cairo library for drawing\n" );
148 #endif
149     printf( "\t-help\t\t\t (displays this message, and exits)\n" );
150     printf( "\t-docs\t\t\t (displays this message, invokes a browser)\n\t\t\t\t (Using the BROWSER environment variable)\n" );
151     printf( "\t-version\t\t (prints the version of fontforge and exits)\n" );
152 #ifndef _NO_PYTHON
153     printf( "\t-lang=py\t\t use python for scripts (may precede -script)\n" );
154 #endif
155 #ifndef _NO_FFSCRIPT
156     printf( "\t-lang=ff\t\t use fontforge's legacy scripting language\n" );
157 #endif
158     printf( "\t-script scriptfile\t (executes scriptfile)\n" );
159     printf( "\t\tmust be the first option (or follow -lang).\n" );
160     printf( "\t\tAll others passed to scriptfile.\n" );
161     printf( "\t-dry scriptfile\t\t (syntax checks scriptfile)\n" );
162     printf( "\t\tmust be the first option. All others passed to scriptfile.\n" );
163     printf( "\t\tOnly for fontforge's own scripting language, not python.\n" );
164     printf( "\t-c script-string\t (executes argument as scripting cmds)\n" );
165     printf( "\t\tmust be the first option. All others passed to the script.\n" );
166     printf( "\n" );
167     printf( "FontForge will read postscript (pfa, pfb, ps, cid), opentype (otf),\n" );
168     printf( "\ttruetype (ttf,ttc), macintosh resource fonts (dfont,bin,hqx),\n" );
169     printf( "\tand bdf and pcf fonts. It will also read its own format --\n" );
170     printf( "\tsfd files.\n" );
171     printf( "If no fontfiles are specified (and -new is not either and there's nothing\n" );
172     printf( "\tto recover) then fontforge will produce an open font dlg.\n" );
173     printf( "If a scriptfile is specified then FontForge will not open the X display\n" );
174     printf( "\tnor will it process any additional arguments. It will execute the\n" );
175     printf( "\tscriptfile and give it any remaining arguments\n" );
176     printf( "If the first argument is an executable filename, and that file's first\n" );
177     printf( "\tline contains \"fontforge\" then it will be treated as a scriptfile.\n\n" );
178     printf( "For more information see:\n\thttp://fontforge.sourceforge.net/\n" );
179     printf( "Send bug reports to:\tfontforge-devel@lists.sourceforge.net\n" );
180 }
181 
dousage(void)182 static void dousage(void) {
183     _dousage();
184 exit(0);
185 }
186 
dohelp(void)187 static void dohelp(void) {
188     _dousage();
189     help("index.html", NULL);
190 exit(0);
191 }
192 
193 struct delayed_event {
194     void *data;
195     void (*func)(void *);
196 };
197 
BuildCharHook(GDisplay * gd)198 static void BuildCharHook(GDisplay *gd) {
199     GWidgetCreateInsChar();
200 }
201 
InsCharHook(GDisplay * gd,unichar_t ch)202 static void InsCharHook(GDisplay *gd,unichar_t ch) {
203     GInsCharSetChar(ch);
204 }
205 
206 extern GImage splashimage_legacy;
207 static GImage *splashimagep;
208 static GWindow splashw;
209 static GTimer *autosave_timer, *splasht;
210 static GFont *splash_font, *splash_italic, *splash_mono;
211 static int as,fh, linecnt;
212 static unichar_t msg[546];
213 static unichar_t *lines[32], *is, *ie;
214 
SplashImageInit()215 static void SplashImageInit() {
216     if (splashimagep == NULL)
217         splashimagep = GGadgetImageCache("splash2020.png");
218     if (splashimagep == NULL)
219 	splashimagep = &splashimage_legacy;
220     return;
221 }
222 
ShowAboutScreen(void)223 void ShowAboutScreen(void) {
224     static int first=1;
225 
226     if ( first ) {
227 	GDrawResize(splashw,splashimagep->u.image->width,splashimagep->u.image->height+linecnt*fh);
228 	first = false;
229     }
230 
231     if ( splasht!=NULL )
232     GDrawCancelTimer(splasht);
233     splasht=NULL;
234 
235     GDrawSetVisible(splashw,true);
236 }
237 
SplashLayout()238 static void SplashLayout() {
239     unichar_t *start, *pt, *lastspace;
240 
241     SplashImageInit();
242 
243     u_strcpy(msg, utf82u_copy("As he drew closer to completing his book on Renaissance printing (The Craft of Printing and the Publication of Shakespeare’s Works), George Williams IV suggested that his son, George Williams V, write a chapter on computer typography. FontForge—previously called PfaEdit—was his response."));
244 
245     GDrawSetFont(splashw,splash_font);
246     linecnt = 0;
247     lines[linecnt++] = msg-1;
248     for ( start = msg; *start!='\0'; start = pt ) {
249 	lastspace = NULL;
250 	for ( pt=start; ; ++pt ) {
251 	    if ( *pt==' ' || *pt=='\0' ) {
252 		if ( GDrawGetTextWidth(splashw,start,pt-start)<splashimagep->u.image->width-10 )
253 		    lastspace = pt;
254 		else
255 	break;
256 		if ( *pt=='\0' )
257 	break;
258 	    }
259 	}
260 	if ( lastspace!=NULL )
261 	    pt = lastspace;
262 	lines[linecnt++] = pt;
263 	if ( *pt ) ++pt;
264     }
265 
266     uc_strcpy(pt," ");
267     pt += u_strlen(pt);
268     lines[linecnt++] = pt;
269 
270     uc_strcpy(pt, " As of 2012 FontForge development continues on GitHub.");
271     pt += u_strlen(pt);
272     lines[linecnt++] = pt;
273 
274     uc_strcpy(pt," ");
275     pt += u_strlen(pt);
276     lines[linecnt++] = pt;
277 
278     uc_strcpy(pt," Version:");
279     uc_strcat(pt,FONTFORGE_VERSION);
280     pt += u_strlen(pt);
281     lines[linecnt++] = pt;
282 
283     // Can be empty if e.g. building from a tarball
284     if (FONTFORGE_GIT_VERSION[0] != '\0') {
285 	uc_strcpy(pt,"  ");
286 	uc_strcat(pt, FONTFORGE_GIT_VERSION);
287 	pt += u_strlen(pt);
288 	lines[linecnt++] = pt;
289     }
290 
291     uc_strcat(pt," Built: ");
292     uc_strcat(pt,FONTFORGE_MODTIME_STR);
293     uc_strcat(pt,"-ML");
294 #ifdef FREETYPE_HAS_DEBUGGER
295     uc_strcat(pt,"-TtfDb");
296 #endif
297 #ifdef _NO_PYTHON
298     uc_strcat(pt,"-NoPython");
299 #endif
300 #ifdef FONTFORGE_CONFIG_USE_DOUBLE
301     uc_strcat(pt,"-D");
302 #endif
303 #ifdef FONTFORGE_CAN_USE_GDK
304     uc_strcat(pt, "-GDK3");
305 #else
306     uc_strcat(pt,"-X11");
307 #endif
308     pt += u_strlen(pt);
309     lines[linecnt++] = pt;
310     lines[linecnt] = NULL;
311     is = u_strchr(msg,'(')+1;
312     ie = u_strchr(msg,')');
313 }
314 
DelayEvent(void (* func)(void *),void * data)315 void DelayEvent(void (*func)(void *), void *data) {
316     struct delayed_event *info = calloc(1,sizeof(struct delayed_event));
317 
318     info->data = data;
319     info->func = func;
320     GDrawRequestTimer(splashw,100,0,info);
321 }
322 
DoDelayedEvents(GEvent * event)323 static void DoDelayedEvents(GEvent *event) {
324     GTimer *t = event->u.timer.timer;
325     struct delayed_event *info = (struct delayed_event *) (event->u.timer.userdata);
326 
327     if ( info!=NULL ) {
328        (info->func)(info->data);
329        free(info);
330     }
331     GDrawCancelTimer(t);
332 }
333 
334 struct argsstruct {
335     int next;
336     int argc;
337     char **argv;
338     int any;
339 };
340 
SendNextArg(struct argsstruct * args)341 static void SendNextArg(struct argsstruct *args) {
342     int i;
343     char *msg;
344     static GTimer *timeout;
345 
346     if ( timeout!=NULL ) {
347 	GDrawCancelTimer(timeout);
348 	timeout = NULL;
349     }
350 
351     for ( i=args->next; i<args->argc; ++i ) {
352 	if ( *args->argv[i]!='-' ||
353 		strcmp(args->argv[i],"-quit")==0 || strcmp(args->argv[i],"--quit")==0 ||
354 		strcmp(args->argv[i],"-new")==0 || strcmp(args->argv[i],"--new")==0 )
355     break;
356     }
357     if ( i>=args->argc ) {
358 	if ( args->any )
359 exit(0);		/* Sent everything */
360 	msg = "-open";
361     } else
362 	msg = args->argv[i];
363     args->next = i+1;
364     args->any  = true;
365 
366     GDrawGrabSelection(splashw,sn_user1);
367     GDrawAddSelectionType(splashw,sn_user1,"STRING",
368 	    copy(msg),strlen(msg),1,
369 	    NULL,NULL);
370 
371 	/* If we just sent the other fontforge a request to die, it will never*/
372 	/*  take the selection back. So we should just die quietly */
373 	/*  But we can't die instantly, or it will never get our death threat */
374 	/*  (it won't have a chance to ask us for the selection if we're dead)*/
375     timeout = GDrawRequestTimer(splashw,1000,0,NULL);
376 }
377 
378 /* When we want to send filenames to another running fontforge we want a */
379 /*  different event handler. We won't have a splash window in that case, */
380 /*  just an invisible utility window on which we perform a little selection */
381 /*  dance */
request_e_h(GWindow gw,GEvent * event)382 static int request_e_h(GWindow gw, GEvent *event) {
383 
384     if ( event->type == et_selclear ) {
385 	SendNextArg( GDrawGetUserData(gw));
386     } else if ( event->type == et_timer )
387 exit( 0 );
388 
389 return( true );
390 }
391 
PingOtherFontForge(int argc,char ** argv)392 static void PingOtherFontForge(int argc, char **argv) {
393     struct argsstruct args;
394 
395     args.next = 1;
396     args.argc = argc;
397     args.argv = argv;
398     args.any  = false;
399     GDrawSetUserData(splashw,&args);
400     SendNextArg(&args);
401     GDrawEventLoop(NULL);
402 exit( 0 );		/* But the event loop should never return */
403 }
404 
start_splash_screen(void)405 static void start_splash_screen(void){
406     GDrawSetVisible(splashw,true);
407     GDrawSync(NULL);
408     GDrawProcessPendingEvents(NULL);
409     GDrawProcessPendingEvents(NULL);
410 
411     splasht = GDrawRequestTimer(splashw,7000,1000,NULL);
412 
413     localsplash = false;
414 }
415 
416 #if defined(__Mac)
417 static FILE *logfile;
418 
419 /* These are the four apple events to which we currently respond */
OpenApplicationAE(const AppleEvent * theAppleEvent,AppleEvent * reply,SRefCon handlerRefcon)420 static pascal OSErr OpenApplicationAE( const AppleEvent * theAppleEvent,
421 	AppleEvent * reply, SRefCon handlerRefcon) {
422  fprintf( logfile, "OPENAPP event received.\n" ); fflush( logfile );
423     if ( localsplash )
424 	start_splash_screen();
425 #ifndef FONTFORGE_CAN_USE_GDK
426     system( "DYLD_LIBRARY_PATH=\"\"; osascript -e 'tell application \"X11\" to activate'" );
427 #endif // FONTFORGE_CAN_USE_GDK
428     if ( fv_list==NULL )
429 	_FVMenuOpen(NULL);
430  fprintf( logfile, " event processed %d.\n", noErr ); fflush( logfile );
431 return( noErr );
432 }
433 
ReopenApplicationAE(const AppleEvent * theAppleEvent,AppleEvent * reply,SRefCon handlerRefcon)434 static pascal OSErr ReopenApplicationAE( const AppleEvent * theAppleEvent,
435 	AppleEvent * reply, SRefCon handlerRefcon) {
436  fprintf( logfile, "ReOPEN event received.\n" ); fflush( logfile );
437     if ( localsplash )
438 	start_splash_screen();
439 #ifndef FONTFORGE_CAN_USE_GDK
440     system( "DYLD_LIBRARY_PATH=\"\"; osascript -e 'tell application \"X11\" to activate'" );
441 #endif // FONTFORGE_CAN_USE_GDK
442     if ( fv_list==NULL )
443 	_FVMenuOpen(NULL);
444  fprintf( logfile, " event processed %d.\n", noErr ); fflush( logfile );
445 return( noErr );
446 }
447 
ShowPreferencesAE(const AppleEvent * theAppleEvent,AppleEvent * reply,SRefCon handlerRefcon)448 static pascal OSErr ShowPreferencesAE( const AppleEvent * theAppleEvent,
449 	AppleEvent * reply, SRefCon handlerRefcon) {
450  fprintf( logfile, "PREFS event received.\n" ); fflush( logfile );
451     if ( localsplash )
452 	start_splash_screen();
453 #ifndef FONTFORGE_CAN_USE_GDK
454     system( "DYLD_LIBRARY_PATH=\"\"; osascript -e 'tell application \"X11\" to activate'" );
455 #endif // FONTFORGE_CAN_USE_GDK
456     DoPrefs();
457  fprintf( logfile, " event processed %d.\n", noErr ); fflush( logfile );
458 return( noErr );
459 }
460 
OpenDocumentsAE(const AppleEvent * theAppleEvent,AppleEvent * reply,SRefCon handlerRefcon)461 static pascal OSErr OpenDocumentsAE( const AppleEvent * theAppleEvent,
462 	AppleEvent * reply, SRefCon handlerRefcon) {
463     AEDescList  docList;
464     long        index;
465     long        count = 0;
466     OSErr       err;
467     char	buffer[2048];
468 
469  fprintf( logfile, "OPEN event received.\n" ); fflush( logfile );
470     if ( localsplash )
471 	start_splash_screen();
472 
473     err = AEGetParamDesc(theAppleEvent, keyDirectObject,
474                          typeAEList, &docList);
475     err = AECountItems(&docList, &count);
476     for(index = 1; index <= count; index++) {
477         AEDesc aDoc;
478         size_t bytecount;
479         void *pathPtr;
480         CFURLRef url;
481         err = AEGetNthDesc(&docList, index, typeFileURL, NULL, &aDoc);
482         if (err != noErr) {
483             continue;
484         }
485         bytecount = AEGetDescDataSize(&aDoc);
486         pathPtr = malloc(bytecount);
487         err = AEGetDescData(&aDoc, pathPtr, bytecount);
488         if (err != noErr) {
489             free(pathPtr);
490             continue;
491         }
492         url = CFURLCreateWithBytes(nil, pathPtr, bytecount,
493                                    kCFStringEncodingUTF8, nil);
494         free(pathPtr);
495         CFURLGetFileSystemRepresentation(url, true, (UInt8*)buffer, sizeof(buffer));
496         CFRelease(url);
497 	ViewPostScriptFont(buffer,0);
498  fprintf( logfile, " file: %s\n", buffer );
499     }
500 #ifndef FONTFORGE_CAN_USE_GDK
501     system( "DYLD_LIBRARY_PATH=\"\"; osascript -e 'tell application \"X11\" to activate'" );
502 #endif // FONTFORGE_CAN_USE_GDK
503     AEDisposeDesc(&docList);
504  fprintf( logfile, " event processed %d.\n", err ); fflush( logfile );
505 
506 return( err );
507 }
508 
AttachErrorCode(AppleEvent * event,OSStatus err)509 static void AttachErrorCode(AppleEvent *event,OSStatus err) {
510     OSStatus returnVal;
511 
512     if ( event==NULL )
513 return;
514 
515     if (event->descriptorType != typeNull) {
516 	/* Check there isn't already an error attached */
517         returnVal = AESizeOfParam(event, keyErrorNumber, NULL, NULL);
518         if (returnVal != noErr ) {	/* Add success if no previous error */
519             AEPutParamPtr(event, keyErrorNumber,
520                         typeSInt32, &err, sizeof(err));
521         }
522     }
523 }
524 
525 static AppleEvent *quit_event = NULL;
we_are_dead(void)526 static void we_are_dead(void) {
527     AttachErrorCode(quit_event,noErr);
528     /* Send the reply (I hope) */
529     AESendMessage(quit_event,NULL, kAENoReply, kAEDefaultTimeout);
530     AEDisposeDesc(quit_event);
531     /* fall off the end of the world and die */
532  fprintf( logfile, " event succeded.\n"); fflush( logfile );
533 }
534 
QuitApplicationAE(const AppleEvent * theAppleEvent,AppleEvent * reply,SInt32 handlerRefcon)535 static pascal OSErr QuitApplicationAE( const AppleEvent * theAppleEvent,
536 	AppleEvent * reply, SInt32 handlerRefcon) {
537     static int first_time = true;
538 
539  fprintf( logfile, "QUIT event received.\n" ); fflush( logfile );
540     quit_event = reply;
541     if ( first_time ) {
542 	atexit( we_are_dead );
543 	first_time = false;
544     }
545     MenuExit(NULL,NULL,NULL);
546     /* if we get here, they canceled the quit, so we return a failure */
547     quit_event = NULL;
548  fprintf( logfile, " event failed %d.\n", errAEEventFailed ); fflush( logfile );
549 return(errAEEventFailed);
550 }
551 
552 /* Install event handlers for the Apple Events we care about */
install_apple_event_handlers(void)553 static  OSErr install_apple_event_handlers(void) {
554     OSErr       err;
555 
556     err     = AEInstallEventHandler(kCoreEventClass, kAEOpenApplication,
557                 NewAEEventHandlerUPP(OpenApplicationAE), 0, false);
558     __Require_noErr(err, CantInstallAppleEventHandler);
559 
560     err     = AEInstallEventHandler(kCoreEventClass, kAEReopenApplication,
561                 NewAEEventHandlerUPP(ReopenApplicationAE), 0, false);
562     __Require_noErr(err, CantInstallAppleEventHandler);
563 
564     err     = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments,
565                 NewAEEventHandlerUPP(OpenDocumentsAE), 0, false);
566     __Require_noErr(err, CantInstallAppleEventHandler);
567 
568     err     = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication,
569                 NewAEEventHandlerUPP(QuitApplicationAE), 0, false);
570     __Require_noErr(err, CantInstallAppleEventHandler);
571 
572     err     = AEInstallEventHandler(kCoreEventClass, kAEShowPreferences,
573                 NewAEEventHandlerUPP(ShowPreferencesAE), 0, false);
574     __Require_noErr(err, CantInstallAppleEventHandler);
575 
576  /* some debugging code, for now */
577  if ( getenv("HOME")!=NULL ) {
578   char buffer[1024];
579   sprintf( buffer, "%s/.FontForge-LogFile.txt", getenv("HOME"));
580   logfile = fopen("/tmp/LogFile.txt","w");
581  }
582  if ( logfile==NULL )
583   logfile = stderr;
584 
585 CantInstallAppleEventHandler:
586     return err;
587 
588 }
589 
DoRealStuff(EventLoopTimerRef timer,void * ignored_data)590 static pascal void DoRealStuff(EventLoopTimerRef timer,void *ignored_data) {
591     GDrawProcessPendingEvents(NULL);
592 }
593 
install_mac_timer(void)594 static void install_mac_timer(void) {
595     EventLoopTimerRef timer;
596 
597     InstallEventLoopTimer(GetMainEventLoop(),
598 	    .001*kEventDurationSecond,.001*kEventDurationSecond,
599 	    NewEventLoopTimerUPP(DoRealStuff), NULL,
600 	    &timer);
601 }
602 #endif
603 
splash_e_h(GWindow gw,GEvent * event)604 static int splash_e_h(GWindow gw, GEvent *event) {
605     GRect old;
606     int i, y, x;
607     static char *foolishness[] = {
608 /* GT: These strings are for fun. If they are offensive or incomprehensible */
609 /* GT: simply translate them as something dull like: "FontForge" */
610 /* GT: This is a spoof of political slogans, designed to point out how foolish they are */
611 	    N_("A free press discriminates\nagainst the illiterate."),
612 	    N_("A free press discriminates\nagainst the illiterate."),
613 /* GT: This is a pun on the old latin drinking song "Gaudeamus igature!" */
614 	    N_("Gaudeamus Ligature!"),
615 	    N_("Gaudeamus Ligature!"),
616 /* GT: Spoof on the bible */
617 	    N_("In the beginning was the letter..."),
618 /* GT: Some wit at MIT came up with this ("ontology recapitulates phylogony" is the original) */
619 	    N_("fontology recapitulates file-ogeny")
620     };
621 
622     switch ( event->type ) {
623       case et_create:
624 	GDrawGrabSelection(gw,sn_user1);
625       break;
626       case et_expose:
627 	GDrawPushClip(gw,&event->u.expose.rect,&old);
628 	GDrawDrawImage(gw,splashimagep,NULL,0,0);
629 	if ((event->u.expose.rect.y+event->u.expose.rect.height) > splashimagep->u.image->height) {
630 	    GDrawSetFont(gw,splash_font);
631 	    y = splashimagep->u.image->height + as + fh/2;
632 	    for ( i=1; i<linecnt; ++i ) {
633 	    // The number 10 comes from lines[linecnt] created in the function SplashLayout. It refers
634 	    // to the line at which we want to make the font monospace. If you add or remove a line,
635 	    // you will need to change this.
636 	    if (i == 10) {
637 		    x = 8+GDrawDrawText(gw,8,y,lines[i-1]+1,0,0x000000);
638 		    GDrawSetFont(gw,splash_mono);
639 	    GDrawDrawText(gw,8,y,lines[i-1]+1,lines[i]-lines[i-1]-1,0x000000);
640 	    } else if ( is>=lines[i-1]+1 && is<lines[i] ) {
641 		    x = 8+GDrawDrawText(gw,8,y,lines[i-1]+1,is-lines[i-1]-1,0x000000);
642 		    GDrawSetFont(gw,splash_italic);
643 		    GDrawDrawText(gw,x,y,is,lines[i]-is,0x000000);
644 		} else if ( ie>=lines[i-1]+1 && ie<lines[i] ) {
645 		    x = 8+GDrawDrawText(gw,8,y,lines[i-1]+1,ie-lines[i-1]-1,0x000000);
646 		    GDrawSetFont(gw,splash_font);
647 		    GDrawDrawText(gw,x,y,ie,lines[i]-ie,0x000000);
648 		} else
649 		    GDrawDrawText(gw,8,y,lines[i-1]+1,lines[i]-lines[i-1]-1,0x000000);
650 		y += fh;
651 	    }
652 	}
653 	GDrawPopClip(gw,&old);
654       break;
655       case et_map:
656 	// The splash screen used to gradually resize the longer it was displayed.
657 	// This was removed. But there is a gxdraw bug which prevents
658 	// the splash from being displayed properly unless a resize occurs.
659 	// So this forces a resize to make it display properly...
660 	GDrawGetSize(gw, &old);
661 	if (old.height < splashimagep->u.image->height) {
662 	    GDrawResize(gw,splashimagep->u.image->width,splashimagep->u.image->height);
663 	}
664 	break;
665       case et_timer:
666       if ( event->u.timer.timer==autosave_timer ) {
667           DoAutoSaves();
668       } else if ( event->u.timer.timer==splasht ) {
669           GGadgetEndPopup();
670           GDrawSetVisible(gw,false);
671           GDrawCancelTimer(splasht);
672           splasht = NULL;
673       } else {
674           DoDelayedEvents(event);
675       }
676       break;
677       case et_char:
678       case et_mousedown:
679       case et_close:
680 	GGadgetEndPopup();
681 	GDrawSetVisible(gw,false);
682       break;
683       case et_mousemove:
684 	GGadgetPreparePopup8(gw,_(foolishness[rand()%(sizeof(foolishness)/sizeof(foolishness[0]))]) );
685       break;
686       case et_selclear:
687 	/* If this happens, it means someone wants to send us a message with a*/
688 	/*  filename to open. So we need to ask for it, process it, and then  */
689 	/*  take the selection back again */
690 	if ( event->u.selclear.sel == sn_user1 ) {
691 	    int len;
692 	    char *arg;
693 	    arg = GDrawRequestSelection(splashw,sn_user1,"STRING",&len);
694 	    if ( arg==NULL )
695 return( true );
696 	    if ( strcmp(arg,"-new")==0 || strcmp(arg,"--new")==0 )
697 		FontNew();
698 	    else if ( strcmp(arg,"-open")==0 || strcmp(arg,"--open")==0 )
699 		_FVMenuOpen(NULL);
700 	    else if ( strcmp(arg,"-quit")==0 || strcmp(arg,"--quit")==0 )
701 		MenuExit(NULL,NULL,NULL);
702 	    else
703 		ViewPostScriptFont(arg,0);
704 	    free(arg);
705 	    GDrawGrabSelection(splashw,sn_user1);
706 	}
707       break;
708       case et_destroy:
709 	IError("Who killed the splash screen?");
710       break;
711     }
712 return( true );
713 }
714 
AddR(char * program_name,char * window_name,char * cmndline_val)715 static void  AddR(char *program_name, char *window_name, char *cmndline_val) {
716 /* Add this command line value to this GUI resource.			*/
717 /* These are the command line options expected when using this routine:	*/
718 /*	-depth, -vc,-cmap or -colormap,-dontopenxdevices, -keyboard	*/
719     char *full;
720     if ((full = malloc(strlen(window_name)+strlen(cmndline_val)+4))!=NULL) {
721 	strcpy(full,window_name);
722 	strcat(full,": ");
723 	strcat(full,cmndline_val);
724 	GResourceAddResourceString(full,program_name);
725 	free(full);
726     }
727 }
728 
ReopenLastFonts(void)729 static int ReopenLastFonts(void) {
730     char buffer[1024];
731     char *ffdir = getFontForgeUserDir(Config);
732     FILE *old;
733     int any = 0;
734 
735     if ( ffdir==NULL ) return false;
736 
737     sprintf( buffer, "%s/FontsOpenAtLastQuit", ffdir );
738     old = fopen(buffer,"r");
739     if ( old==NULL ) {
740         free(ffdir);
741         return false;
742     }
743     while ( fgets(buffer,sizeof(buffer),old)!=NULL ) {
744     if ( ViewPostScriptFont(g_strchomp(buffer),0)!=0 )
745         any = 1;
746     }
747     fclose(old);
748     free(ffdir);
749     return any;
750 }
751 
752 #if defined(__Mac)
753 /* Read a property from the x11 properties files */
754 /* At the moment we want to know if we get the command key, or if the menubar */
755 /*    eats it */
get_mac_x11_prop(char * keystr)756 static int get_mac_x11_prop(char *keystr) {
757     CFPropertyListRef ret;
758     CFStringRef key, appID;
759     int val;
760 
761     appID = CFSTR("com.apple.x11");
762     key   = CFStringCreateWithBytes(NULL,(uint8 *) keystr,strlen(keystr), kCFStringEncodingISOLatin1, 0);
763     ret = CFPreferencesCopyAppValue(key,appID);
764     if ( ret==NULL ) {
765 	/* Sigh. Apple uses a different preference file under 10.5.6 I really */
766 	/*  wish they'd stop making stupid, unnecessary changes */
767 	appID = CFSTR("org.x.X11");
768 	ret = CFPreferencesCopyAppValue(key,appID);
769     }
770     CFRelease(key);
771     if ( ret==NULL )
772 return( -1 );
773     if ( CFGetTypeID(ret)!=CFBooleanGetTypeID()) {
774     CFRelease(ret);
775 return( -2 );
776     }
777     val = CFBooleanGetValue(ret);
778     CFRelease(ret);
779 return( val );
780 }
781 
uses_local_x(int argc,char ** argv)782 static int uses_local_x(int argc,char **argv) {
783     int i;
784     char *arg;
785 
786     for ( i=1; i<argc; ++i ) {
787 	arg = argv[i];
788 	if ( *arg=='-' ) {
789 	    if ( arg[0]=='-' && arg[1]=='-' && arg[2]!='\0')
790 		++arg;
791 	    if ( strcmp(arg,"-display")==0 )
792 return( i+1<argc && strcmp(argv[i+1],":0")!=0 && strcmp(argv[i+1],":0.0")!=0? 2 : 0 );
793 	    if ( strcmp(argv[i],"-c")==0 )
794 return( false );		/* we use a script string, no x display at all */
795 	    if ( strcmp(arg,"-script")==0 )
796 return( false );		/* we use a script, no x display at all */
797 	    if ( strcmp(argv[i],"-")==0 )
798 return( false );		/* script on stdin */
799 	} else {
800 	    /* Is this argument a script file ? */
801 	    FILE *temp = fopen(argv[i],"r");
802 	    char buffer[200];
803 	    if ( temp==NULL )
804 return( true );			/* not a script file, so need local local X */
805 	    buffer[0] = '\0';
806 	    fgets(buffer,sizeof(buffer),temp);
807 	    fclose(temp);
808 	    if ( buffer[0]=='#' && buffer[1]=='!' &&
809 		    (strstr(buffer,"pfaedit")!=NULL || strstr(buffer,"fontforge")!=NULL )) {
810 return( false );		/* is a script file, so no need for X */
811 
812 return( true );			/* not a script, so needs X */
813 	    }
814 	}
815     }
816 return( true );
817 }
818 #endif
819 
820 
821 #if defined(__Mac)
hasquit(int argc,char ** argv)822 static int hasquit( int argc, char **argv ) {
823     int i;
824 
825     for ( i=1; i<argc; ++i )
826 	if ( strcmp(argv[i],"-quit")==0 || strcmp(argv[i],"--quit")==0 )
827 return( true );
828 
829 return( false );
830 }
831 #endif
832 
GrokNavigationMask(void)833 static void GrokNavigationMask(void) {
834     extern int navigation_mask;
835 
836     navigation_mask = GMenuItemParseMask(H_("NavigationMask|None"));
837 }
838 
839 /**
840  * Create the directory basedir/dirname with the given mode.
841  * Silently ignore any errors that might happen.
842  */
ffensuredir(const char * basedir,const char * dirname,mode_t mode)843 static void ffensuredir( const char* basedir, const char* dirname, mode_t mode ) {
844     const int buffersz = PATH_MAX;
845     char buffer[buffersz+1];
846 
847     snprintf(buffer,buffersz,"%s/%s", basedir, dirname );
848     // ignore errors, this is just to help the user aftre all.
849     GFileMkDir( buffer, mode );
850 }
851 
ensureDotFontForgeIsSetup()852 static void ensureDotFontForgeIsSetup() {
853     char *basedir = getFontForgeUserDir(Config);
854     if ( !basedir ) {
855 	return;
856     }
857     ffensuredir( basedir, "",       S_IRWXU );
858     ffensuredir( basedir, "python", S_IRWXU );
859     free(basedir);
860 }
861 
DoAutoRecoveryPostRecover_PromptUserGraphically(SplineFont * sf)862 static void DoAutoRecoveryPostRecover_PromptUserGraphically(SplineFont *sf)
863 {
864     /* Ask user to save-as file */
865     char *buts[4];
866     buts[0] = _("_OK");
867     buts[1] = 0;
868     gwwv_ask( _("Recovery Complete"),(const char **) buts,0,1,_("Your file %s has been recovered.\nYou must now Save your file to continue working on it."), sf->filename );
869     _FVMenuSaveAs( (FontView*)sf->fv );
870 }
871 
872 #if defined(__MINGW32__) && !defined(_NO_LIBCAIRO)
873 /**
874  * \brief Load fonts from the specified folder for the UI to use.
875  * This should only be used if Cairo is used on Windows, which defaults to the
876  * Win32 font backend.
877  * This is an ANSI version, so files which contain characters outside of the
878  * user's locale will fail to be loaded.
879  * \param prefix The folder to read fonts from. Currently the pixmaps folder
880  *               and the folder 'ui-fonts' in the FontForge preferences folder.
881  */
WinLoadUserFonts(const char * prefix)882 static void WinLoadUserFonts(const char *prefix) {
883     HANDLE fileHandle;
884     WIN32_FIND_DATA fileData;
885     char path[MAX_PATH], *ext;
886     HRESULT ret;
887     int i;
888 
889     if (prefix == NULL) {
890         return;
891     }
892     ret = snprintf(path, MAX_PATH, "%s/*.???", prefix);
893     if (ret <= 0 || ret >= MAX_PATH) {
894         return;
895     }
896 
897     fileHandle = FindFirstFileA(path, &fileData);
898     if (fileHandle != INVALID_HANDLE_VALUE) do {
899         ext = strrchr(fileData.cFileName, '.');
900         if (!ext || (strcasecmp(ext, ".ttf") && strcasecmp(ext, ".ttc") &&
901                      strcasecmp(ext,".otf")))
902         {
903             continue;
904         }
905         ret = snprintf(path, MAX_PATH, "%s/%s", prefix, fileData.cFileName);
906         if (ret > 0 && ret < MAX_PATH) {
907             //printf("WIN32-FONT-TEST: %s\n", path);
908             ret = AddFontResourceExA(path, FR_PRIVATE, NULL);
909             //if (ret > 0) {
910             //    printf("\tLOADED FONT OK!\n");
911             //}
912         }
913     } while (FindNextFileA(fileHandle, &fileData) != 0);
914 }
915 #endif
916 
917 
fontforge_main(int argc,char ** argv)918 int fontforge_main( int argc, char **argv ) {
919     const char *load_prefs = getenv("FONTFORGE_LOADPREFS");
920     int i;
921     int recover=2;
922     int any;
923     int next_recent=0;
924     GRect pos;
925     GWindowAttrs wattrs;
926     char *display = NULL;
927     FontRequest rq;
928     int ds, ld;
929     int openflags=0;
930     int doopen=0, quit_request=0;
931     bool use_cairo = true;
932 
933 #if !(GLIB_CHECK_VERSION(2, 35, 0))
934     g_type_init();
935 #endif
936 
937     /* Must be done before we cache the current directory */
938     /* Change to HOME dir if specified on the commandline */
939     for ( i=1; i<argc; ++i ) {
940 	char *pt = argv[i];
941 	if ( pt[0]=='-' && pt[1]=='-' ) ++pt;
942 	if (strcmp(pt,"-home")==0 || strncmp(pt,"-psn_",5)==0) {
943 	    /* OK, I don't know what _-psn_ means, but to GW it means */
944 	    /* we've been started on the mac from the FontForge.app   */
945 	    /* structure, and the current directory is (shudder) "/"  */
946 	    if (getenv("HOME")!=NULL) chdir(getenv("HOME"));
947 	    break;	/* Done - Unnecessary to check more arguments */
948 	}
949 	if (strcmp(pt,"-quiet")==0)
950 	    quiet = 1;
951     }
952 
953     if (!quiet) {
954         time_t tm = FONTFORGE_MODTIME_RAW;
955         struct tm* modtime = gmtime(&tm);
956         fprintf( stderr, "Copyright (c) 2000-%d. See AUTHORS for Contributors.\n", modtime->tm_year+1900 );
957         fprintf( stderr, " License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n" );
958         fprintf( stderr, " with many parts BSD <http://fontforge.org/license.html>. Please read LICENSE.\n" );
959         fprintf( stderr, " Version: %s\n", FONTFORGE_VERSION );
960         fprintf( stderr, " Based on sources from %s"
961 	        "-ML"
962 #ifdef FREETYPE_HAS_DEBUGGER
963 	        "-TtfDb"
964 #endif
965 #ifdef _NO_PYTHON
966 	        "-NoPython"
967 #endif
968 #ifdef FONTFORGE_CONFIG_USE_DOUBLE
969 	        "-D"
970 #endif
971 #ifdef FONTFORGE_CAN_USE_GDK
972             "-GDK3"
973 #endif
974 #ifdef BUILT_WITH_XORG
975             "-Xorg"
976 #endif
977 	        ".\n",
978 	        FONTFORGE_MODTIME_STR );
979         // Can be empty if e.g. building from a tarball
980         if (FONTFORGE_GIT_VERSION[0] != '\0') {
981             fprintf( stderr, " Based on source from git with hash: %s\n", FONTFORGE_GIT_VERSION );
982         }
983     }
984 
985 #if defined(__Mac) && !defined(FONTFORGE_CAN_USE_GDK)
986     /* Start X if they haven't already done so. Well... try anyway */
987     /* Must be before we change DYLD_LIBRARY_PATH or X won't start */
988     /* (osascript depends on a libjpeg which isn't found if we look in /sw/lib first */
989     int local_x = uses_local_x(argc,argv);
990     if ( local_x==1 && getenv("DISPLAY")==NULL ) {
991 	/* Don't start X if we're just going to quit. */
992 	/* if X exists, it isn't needed. If X doesn't exist it's wrong */
993 	if ( !hasquit(argc,argv)) {
994 	    /* This sequence is supposed to bring up an app without a window */
995 	    /*  but X still opens an xterm */
996 	    system( "osascript -e 'tell application \"X11\" to launch'" );
997 	    system( "osascript -e 'tell application \"X11\" to activate'" );
998 	}
999 	setenv("DISPLAY",":0.0",0);
1000     } else if ( local_x==1 && *getenv("DISPLAY")!='/' && strcmp(getenv("DISPLAY"),":0.0")!=0 && strcmp(getenv("DISPLAY"),":0")!=0 )
1001 	/* 10.5.7 uses a named socket or something "/tmp/launch-01ftWX:0" */
1002 	local_x = 0;
1003 #endif
1004 
1005 #if defined(__MINGW32__)
1006     if( getenv("DISPLAY")==NULL ) {
1007 	putenv("DISPLAY=127.0.0.1:0.0");
1008     }
1009     if( getenv("LC_ALL")==NULL ){
1010 	char lang[8];
1011 	char env[32];
1012 	if( GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, lang, 8) > 0 ){
1013 	    strcpy(env, "LC_ALL=");
1014 	    strcat(env, lang);
1015 	    putenv(env);
1016 	}
1017     }
1018 #endif
1019 
1020     FF_SetUiInterface(&gdraw_ui_interface);
1021     FF_SetPrefsInterface(&gdraw_prefs_interface);
1022     FF_SetSCInterface(&gdraw_sc_interface);
1023     FF_SetCVInterface(&gdraw_cv_interface);
1024     FF_SetBCInterface(&gdraw_bc_interface);
1025     FF_SetFVInterface(&gdraw_fv_interface);
1026     FF_SetFIInterface(&gdraw_fi_interface);
1027     FF_SetMVInterface(&gdraw_mv_interface);
1028     FF_SetClipInterface(&gdraw_clip_interface);
1029 #ifndef _NO_PYTHON
1030     PythonUI_Init();
1031 #endif
1032 
1033     FindProgDir(argv[0]);
1034     InitSimpleStuff();
1035 
1036 #if defined(__MINGW32__)
1037     {
1038         char path[MAX_PATH];
1039         unsigned int len = GetModuleFileNameA(NULL, path, MAX_PATH);
1040         path[len] = '\0';
1041 
1042         //The '.exe' must be removed as resources presumes it's not there.
1043         GResourceSetProg(GFileRemoveExtension(GFileNormalizePath(path)));
1044     }
1045 #else
1046     GResourceSetProg(argv[0]);
1047 #endif
1048 
1049 #if defined(__Mac)
1050     /* The mac seems to default to the "C" locale, LANG and LC_MESSAGES are not*/
1051     /*  defined. This means that gettext will not bother to look up any message*/
1052     /*  files -- even if we have a "C" or "POSIX" entry in the locale diretory */
1053     /* Now if X11 gives us the command key, I want to force a rebinding to use */
1054     /*  Cmd rather than Control key -- more mac-like. But I can't do that if   */
1055     /*  there is no locale. So I force a locale if there is none specified */
1056     /* I force the US English locale, because that's the what the messages are */
1057     /*  by default so I'm changing as little as I can. I think. */
1058     /* Now the locale command will treat a LANG which is "" as undefined, but */
1059     /*  gettext will not. So I don't bother to check for null strings or "C"  */
1060     /*  or "POSIX". If they've mucked with the locale perhaps they know what  */
1061     /*  they are doing */
1062     {
1063 #ifndef FONTFORGE_CAN_USE_GDK
1064 	int useCommandKey = get_mac_x11_prop("enable_key_equivalents") <= 0;
1065 
1066 	if ( local_x && useCommandKey )
1067 #endif // FONTFORGE_CAN_USE_GDK
1068 	{
1069 	    hotkeySystemSetCanUseMacCommand( 1 );
1070 
1071 	    /* Ok, we get the command key */
1072 	    if ( getenv("LANG")==NULL && getenv("LC_MESSAGES")==NULL ) {
1073 		setenv("LC_MESSAGES","en_US.UTF-8",0);
1074 	    }
1075 	}
1076     }
1077 #endif // defined(__Mac)
1078 
1079     GMenuSetShortcutDomain("FontForge-MenuShortCuts");
1080     bind_textdomain_codeset("FontForge-MenuShortCuts","UTF-8");
1081     bindtextdomain("FontForge-MenuShortCuts", getLocaleDir());
1082 
1083     bind_textdomain_codeset("FontForge","UTF-8");
1084     bindtextdomain("FontForge", getLocaleDir());
1085     textdomain("FontForge");
1086     GResourceUseGetText();
1087     {
1088 	char path[PATH_MAX];
1089 	snprintf(path, PATH_MAX, "%s%s", getShareDir(), "/pixmaps" );
1090 	GGadgetSetImageDir( path );
1091 
1092 	snprintf(path, PATH_MAX, "%s%s", getShareDir(), "/resources/fontforge.resource" );
1093 	GResourceAddResourceFile(path, GResourceProgramName,false);
1094     }
1095     hotkeysLoad();
1096 //    loadPrefsFiles();
1097     Prefs_LoadDefaultPreferences();
1098 
1099     if ( load_prefs!=NULL && strcasecmp(load_prefs,"Always")==0 )
1100 	LoadPrefs();
1101     if ( default_encoding==NULL )
1102 	default_encoding=FindOrMakeEncoding("ISO8859-1");
1103     if ( default_encoding==NULL )
1104 	default_encoding=&custom;	/* In case iconv is broken */
1105 
1106     // This no longer starts embedded Python unless control passes to the Python executors,
1107     // which exit independently rather than returning here.
1108     CheckIsScript(argc,argv); /* Will run the script and exit if it is a script */
1109 					/* If there is no UI, there is always a script */
1110 			                /*  and we will never return from the above */
1111     if ( load_prefs==NULL ||
1112 	    (strcasecmp(load_prefs,"Always")!=0 &&	/* Already loaded */
1113 	     strcasecmp(load_prefs,"Never")!=0 ))
1114 	LoadPrefs();
1115     GrokNavigationMask();
1116     for ( i=1; i<argc; ++i ) {
1117 	char *pt = argv[i];
1118 	if ( pt[0]=='-' && pt[1]=='-' )
1119 	    ++pt;
1120 	if ( strcmp(pt,"-sync")==0 )
1121 	    GResourceAddResourceString("Gdraw.Synchronize: true",argv[0]);
1122 	else if ( strcmp(pt,"-depth")==0 && i<argc-1 )
1123 	    AddR(argv[0],"Gdraw.Depth", argv[++i]);
1124 	else if ( strcmp(pt,"-vc")==0 && i<argc-1 )
1125 	    AddR(argv[0],"Gdraw.VisualClass", argv[++i]);
1126 	else if ( (strcmp(pt,"-cmap")==0 || strcmp(pt,"-colormap")==0) && i<argc-1 )
1127 	    AddR(argv[0],"Gdraw.Colormap", argv[++i]);
1128 	else if ( (strcmp(pt,"-dontopenxdevices")==0) )
1129 	    AddR(argv[0],"Gdraw.DontOpenXDevices", "true");
1130 	else if ( strcmp(pt,"-keyboard")==0 && i<argc-1 )
1131 	    AddR(argv[0],"Gdraw.Keyboard", argv[++i]);
1132 	else if ( strcmp(pt,"-display")==0 && i<argc-1 )
1133 	    display = argv[++i];
1134 # if MyMemory
1135 	else if ( strcmp(pt,"-memory")==0 )
1136 	    __malloc_debug(5);
1137 # endif
1138 	else if ( strncmp(pt,"-usecairo",strlen("-usecairo"))==0 ) {
1139 	    if ( strcmp(pt,"-usecairo=no")==0 )
1140 	        use_cairo = false;
1141 	    else
1142 	        use_cairo = true;
1143 	    GDrawEnableCairo(use_cairo);
1144 	} else if ( strcmp(pt,"-nosplash")==0 )
1145 	    splash = 0;
1146 	else if ( strcmp(pt,"-quiet")==0 )
1147 	    /* already checked for this earlier, no need to do it again */;
1148 	else if ( strcmp(pt,"-unique")==0 )
1149 	    unique = 1;
1150 	else if ( strcmp(pt,"-recover")==0 && i<argc-1 ) {
1151 	    ++i;
1152 	    if ( strcmp(argv[i],"none")==0 )
1153 		recover=0;
1154 	    else if ( strcmp(argv[i],"clean")==0 )
1155 		recover= -1;
1156 	    else if ( strcmp(argv[i],"auto")==0 )
1157 		recover= 1;
1158 	    else if ( strcmp(argv[i],"inquire")==0 )
1159 		recover= 2;
1160 	    else {
1161 		fprintf( stderr, "Invalid argument to -recover, must be none, auto, inquire or clean\n" );
1162 		dousage();
1163 	    }
1164 	} else if ( strcmp(pt,"-recover=none")==0 ) {
1165 	    recover = 0;
1166 	} else if ( strcmp(pt,"-recover=clean")==0 ) {
1167 	    recover = -1;
1168 	} else if ( strcmp(pt,"-recover=auto")==0 ) {
1169 	    recover = 1;
1170 	} else if ( strcmp(pt,"-recover=inquire")==0 ) {
1171 	    recover = 2;
1172 	} else if ( strcmp(pt,"-docs")==0 )
1173 	    dohelp();
1174 	else if ( strcmp(pt,"-help")==0 )
1175 	    dousage();
1176 	else if ( strcmp(pt,"-version")==0 || strcmp(pt,"-v")==0 || strcmp(pt,"-V")==0 )
1177 	    doversion(FONTFORGE_VERSION);
1178 	else if ( strcmp(pt,"-quit")==0 )
1179 	    quit_request = true;
1180 	else if ( strcmp(pt,"-home")==0 )
1181 	    /* already did a chdir earlier, don't need to do it again */;
1182 #if defined(__Mac)
1183 	else if ( strncmp(pt,"-psn_",5)==0 ) {
1184 	    /* OK, I don't know what _-psn_ means, but to GW it means */
1185 	    /* we've been started on the mac from the FontForge.app   */
1186 	    /* structure, and the current directory was (shudder) "/" */
1187 	    /* (however, we changed to HOME earlier in main routine). */
1188 	    unique = 1;
1189 	    listen_to_apple_events = true; // This has been problematic on Mavericks and later.
1190 	}
1191 #endif
1192     }
1193 #ifdef FONTFORGE_CAN_USE_GDK
1194     gdk_set_allowed_backends("win32,quartz,x11");
1195     gdk_init(&argc, &argv);
1196 #endif
1197     ensureDotFontForgeIsSetup();
1198 #if defined(__MINGW32__) && !defined(_NO_LIBCAIRO)
1199     //Load any custom fonts for the user interface
1200     if (use_cairo) {
1201         char *system_load = getShareDir();
1202         char *user_load = getFontForgeUserDir(Data);
1203         char lbuf[MAX_PATH];
1204         int lret;
1205 
1206         if (system_load != NULL) {
1207             //Follow the FontConfig APPSHAREFONTDIR location
1208             lret = snprintf(lbuf, MAX_PATH, "%s/../fonts", system_load);
1209             if (lret > 0 && lret < MAX_PATH) {
1210                 WinLoadUserFonts(lbuf);
1211             }
1212         }
1213         if (user_load != NULL) {
1214             lret = snprintf(lbuf, MAX_PATH, "%s/%s", user_load, "ui-fonts");
1215             if (lret > 0 && lret < MAX_PATH) {
1216                 WinLoadUserFonts(lbuf);
1217             }
1218             free(user_load);
1219         }
1220     }
1221 #endif
1222     InitImageCache(); // This is in gtextinfo.c. It zeroes imagecache for us.
1223     atexit(&ClearImageCache); // We register the destructor, which is also in gtextinfo.c.
1224     GDrawCreateDisplays(display,argv[0]);
1225     atexit(&GDrawDestroyDisplays); // We register the destructor so that it runs even if we call exit without finishing this function.
1226     default_background = GDrawGetDefaultBackground(screen_display);
1227     InitToolIconClut(default_background);
1228     InitToolIcons();
1229     InitCursors();
1230 
1231     /**
1232      * we have to do a quick sniff of argv[] here to see if the user
1233      * wanted to skip loading these python init files.
1234      */
1235     for ( i=1; i<argc; ++i ) {
1236 	char *pt = argv[i];
1237 
1238 	if ( !strcmp(pt,"-SkipPythonInitFiles")) {
1239 	    ProcessPythonInitFiles = 0;
1240 	}
1241     }
1242 
1243 #ifndef _NO_PYTHON
1244 /*# ifndef GWW_TEST*/
1245     FontForge_InitializeEmbeddedPython(); /* !!!!!! debug (valgrind doesn't like python) */
1246 /*# endif*/
1247 #endif
1248 
1249 #ifndef _NO_PYTHON
1250     if( ProcessPythonInitFiles )
1251 	PyFF_ProcessInitFiles();
1252 #endif
1253 
1254     /* the splash screen used not to have a title bar (wam_nodecor) */
1255     /*  but I found I needed to know how much the window manager moved */
1256     /*  the window around, which I can determine if I have a positioned */
1257     /*  decorated window created at the begining */
1258     /* Actually I don't care any more */
1259     wattrs.mask = wam_events|wam_cursor|wam_bordwidth|wam_backcol|wam_positioned|wam_utf8_wtitle|wam_isdlg;
1260     wattrs.event_masks = ~(1<<et_charup);
1261     wattrs.positioned = 1;
1262     wattrs.cursor = ct_pointer;
1263     wattrs.utf8_window_title = "FontForge";
1264     wattrs.border_width = 2;
1265     wattrs.background_color = 0xffffff;
1266 #ifdef FONTFORGE_CAN_USE_GDK
1267     wattrs.is_dlg = true;
1268 #else
1269     wattrs.is_dlg = !listen_to_apple_events;
1270 #endif
1271     pos.x = pos.y = 200;
1272     SplashImageInit();
1273     pos.width = splashimagep->u.image->width;
1274     pos.height = splashimagep->u.image->height-1; // See splash_e_h:et_map
1275     GDrawBindSelection(NULL,sn_user1,"FontForge");
1276     if ( unique && GDrawSelectionOwned(NULL,sn_user1)) {
1277 	/* Different event handler, not a dialog */
1278 	wattrs.is_dlg = false;
1279 	splashw = GDrawCreateTopWindow(NULL,&pos,request_e_h,NULL,&wattrs);
1280 	PingOtherFontForge(argc,argv);
1281     } else {
1282 	if ( quit_request )
1283 exit( 0 );
1284 	splashw = GDrawCreateTopWindow(NULL,&pos,splash_e_h,NULL,&wattrs);
1285     }
1286 
1287     memset(&rq,0,sizeof(rq));
1288     rq.utf8_family_name = SERIF_UI_FAMILIES;
1289     rq.point_size = 10;
1290     rq.weight = 400;
1291     splash_font = GDrawInstanciateFont(NULL,&rq);
1292     splash_font = GResourceFindFont("Splash.Font",splash_font);
1293     GDrawDecomposeFont(splash_font, &rq);
1294     splash_mono = GDrawInstanciateFont(NULL,&rq);
1295     splash_mono = GResourceFindFont("GTextField.Font",splash_mono);
1296     rq.style = fs_italic;
1297     splash_italic = GDrawInstanciateFont(NULL,&rq);
1298     splash_italic = GResourceFindFont("Splash.ItalicFont",splash_italic);
1299     GDrawSetFont(splashw,splash_font);
1300 
1301     SplashLayout();
1302     localsplash = splash;
1303 
1304    if ( localsplash && !listen_to_apple_events )
1305 	start_splash_screen();
1306 
1307     //
1308     // The below call will initialize the fontconfig cache if required.
1309     // That can take a while the first time it happens.
1310     //
1311    GDrawWindowFontMetrics(splashw,splash_font,&as,&ds,&ld);
1312    fh = as+ds+ld;
1313 
1314     if ( AutoSaveFrequency>0 )
1315 	autosave_timer=GDrawRequestTimer(splashw,2*AutoSaveFrequency*1000,AutoSaveFrequency*1000,NULL);
1316 
1317     GDrawProcessPendingEvents(NULL);
1318     GDrawSetBuildCharHooks(BuildCharHook,InsCharHook);
1319 
1320     any = 0;
1321     if ( recover==-1 )
1322 	CleanAutoRecovery();
1323     else if ( recover )
1324 	any = DoAutoRecoveryExtended( recover-1 );
1325 
1326     openflags = 0;
1327     for ( i=1; i<argc; ++i ) {
1328 	char buffer[1025];
1329 	char *pt = argv[i];
1330 
1331 	GDrawProcessPendingEvents(NULL);
1332 	if ( pt[0]=='-' && pt[1]=='-' && pt[2]!='\0')
1333 	    ++pt;
1334 	if ( strcmp(pt,"-new")==0 ) {
1335 	    FontNew();
1336 	    any = 1;
1337 #  if HANYANG
1338 	} else if ( strcmp(pt,"-newkorean")==0 ) {
1339 	    MenuNewComposition(NULL,NULL,NULL);
1340 	    any = 1;
1341 #  endif
1342 	} else if ( !strcmp(pt,"-SkipPythonInitFiles")) {
1343 	    // already handled above.
1344 	} else if ( strcmp(pt,"-last")==0 ) {
1345 	    if ( next_recent<RECENT_MAX && RecentFiles[next_recent]!=NULL )
1346 		if ( ViewPostScriptFont(RecentFiles[next_recent++],openflags))
1347 		    any = 1;
1348 	} else if ( strcmp(pt,"-sync")==0 || strcmp(pt,"-memory")==0 ||
1349 		    strcmp(pt,"-nosplash")==0 || strcmp(pt,"-recover=none")==0 ||
1350 		    strcmp(pt,"-recover=clean")==0 || strcmp(pt,"-recover=auto")==0 ||
1351 		    strcmp(pt,"-dontopenxdevices")==0 || strcmp(pt,"-unique")==0 ||
1352 		    strncmp(pt,"-usecairo",strlen("-usecairo"))==0 ||
1353 		    strcmp(pt,"-home")==0 || strcmp(pt,"-quiet")==0
1354 		    || strcmp(pt,"-forceuihidden")==0 )
1355 	    /* Already done, needed to be before display opened */;
1356 	else if ( strncmp(pt,"-psn_",5)==0 )
1357 	    /* Already done */;
1358 	else if ( (strcmp(pt,"-depth")==0 || strcmp(pt,"-vc")==0 ||
1359 		    strcmp(pt,"-cmap")==0 || strcmp(pt,"-colormap")==0 ||
1360 		    strcmp(pt,"-keyboard")==0 ||
1361 		    strcmp(pt,"-display")==0 || strcmp(pt,"-recover")==0 ) &&
1362 		i<argc-1 )
1363 	    ++i; /* Already done, needed to be before display opened */
1364 	else if ( strcmp(pt,"-allglyphs")==0 )
1365 	    openflags |= of_all_glyphs_in_ttc;
1366 	else if ( strcmp(pt,"-open")==0 )
1367 	    doopen = true;
1368 	else {
1369 //	    printf("else argv[i]:%s\n", argv[i] );
1370 	    GFileGetAbsoluteName(argv[i],buffer,sizeof(buffer));
1371 	    if ( GFileIsDir(buffer) ) {
1372 		char *fname;
1373 		fname = malloc(strlen(buffer)+strlen("/glyphs/contents.plist")+1);
1374 		strcpy(fname,buffer); strcat(fname,"/glyphs/contents.plist");
1375 		if ( GFileExists(fname)) {
1376 		    /* It's probably a Unified Font Object directory */
1377 		    free(fname);
1378 		    if ( ViewPostScriptFont(buffer,openflags) )
1379 			any = 1;
1380 		} else {
1381 		    strcpy(fname,buffer); strcat(fname,"/font.props");
1382 		    if ( GFileExists(fname)) {
1383 			/* It's probably a sf dir collection */
1384 			free(fname);
1385 			if ( ViewPostScriptFont(buffer,openflags) )
1386 			    any = 1;
1387 		    } else {
1388 			free(fname);
1389 			if ( buffer[strlen(buffer)-1]!='/' ) {
1390 			    /* If dirname doesn't end in "/" we'll be looking in parent dir */
1391 			    buffer[strlen(buffer)+1]='\0';
1392 			    buffer[strlen(buffer)] = '/';
1393 			}
1394 			fname = GetPostScriptFontName(buffer,false,false);
1395 			if ( fname!=NULL )
1396 			    ViewPostScriptFont(fname,openflags);
1397 			any = 1;	/* Even if we didn't get a font, don't bring up dlg again */
1398 			free(fname);
1399 		    }
1400 		}
1401 	    } else if ( ViewPostScriptFont(buffer,openflags)!=0 )
1402 		any = 1;
1403 	}
1404     }
1405     if ( !any && !doopen )
1406 	any = ReopenLastFonts();
1407 
1408 #if defined(__Mac)
1409     if ( listen_to_apple_events ) {
1410 	install_apple_event_handlers();
1411 #ifndef FONTFORGE_CAN_USE_GDK
1412 	install_mac_timer();
1413 	setup_cocoa_app();
1414 
1415 	// WARNING: See declaration of RunApplicationEventLoop() above as to
1416 	// why you might not want to call that function anymore.
1417 	// RunApplicationEventLoop();
1418     } else
1419 #else
1420     }
1421 #endif // FONTFORGE_CAN_USE_GDK
1422 #endif // __Mac
1423     if ( doopen || !any )
1424 	_FVMenuOpen(NULL);
1425     GDrawEventLoop(NULL);
1426     GDrawDestroyDisplays();
1427 
1428 #ifndef _NO_PYTHON
1429 /*# ifndef GWW_TEST*/
1430     FontForge_FinalizeEmbeddedPython(); /* !!!!!! debug (valgrind doesn't like python) */
1431 /*# endif*/
1432 #endif
1433 
1434     // These free menu translations, mostly.
1435     BitmapViewFinishNonStatic();
1436     MetricsViewFinishNonStatic();
1437     CharViewFinishNonStatic();
1438     FontViewFinishNonStatic();
1439 
1440     ClearImageCache(); // This frees the contents of imagecache.
1441     // hotkeysSave();
1442     LastFonts_Save();
1443 
1444 #ifndef _NO_LIBUNICODENAMES
1445     uninm_names_db_close(names_db);	/* close this database before exiting */
1446     uninm_blocks_db_close(blocks_db);
1447 #endif
1448 
1449 return( 0 );
1450 }
1451