xref: /plan9/sys/src/cmd/gs/src/gp_vms.c (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
1 /* Copyright (C) 1989-2003 artofcode LLC.  All rights reserved.
2 
3   This software is provided AS-IS with no warranty, either express or
4   implied.
5 
6   This software is distributed under license and may not be copied,
7   modified or distributed except as expressly authorized under the terms
8   of the license contained in the file LICENSE in this distribution.
9 
10   For more information about licensing, please refer to
11   http://www.ghostscript.com/licensing/. For information on
12   commercial licensing, go to http://www.artifex.com/licensing/ or
13   contact Artifex Software, Inc., 101 Lucas Valley Road #110,
14   San Rafael, CA  94903, U.S.A., +1(415)492-9861.
15 */
16 
17 /* $Id: gp_vms.c,v 1.38 2004/04/08 16:18:25 giles Exp $ */
18 /* VAX/VMS specific routines for Ghostscript */
19 
20 #include "string_.h"
21 #include "memory_.h"
22 #include "gx.h"
23 #include "gp.h"
24 #include "gpmisc.h"
25 #include "gsstruct.h"
26 #include <stat.h>
27 #include <stdlib.h>		/* for exit() with some compiler versions */
28 #include <errno.h>		/* for exit() with other compiler versions */
29 #include <unixio.h>
30 
31 extern char *getenv(const char *);
32 
33 /* Apparently gcc doesn't allow extra arguments for fopen: */
34 #ifdef VMS			/* DEC C */
35 #  define fopen_VMS fopen
36 #else /* gcc */
37 #  define fopen_VMS(name, mode, m1, m2) fopen(name, mode)
38 #endif
39 
40 
41 /* VMS string descriptor structure */
42 #define DSC$K_DTYPE_T 14
43 #define DSC$K_CLASS_S  1
44 struct dsc$descriptor_s {
45     unsigned short dsc$w_length;
46     unsigned char dsc$b_dtype;
47     unsigned char dsc$b_class;
48     char *dsc$a_pointer;
49 };
50 typedef struct dsc$descriptor_s descrip;
51 
52 /* VMS RMS constants */
53 #define RMS_IS_ERROR_OR_NMF(rmsv) (((rmsv) & 1) == 0)
54 #define RMS$_NMF    99018
55 #define RMS$_NORMAL 65537
56 #define NAM$C_MAXRSS  255
57 
58 struct file_enum_s {
59     uint context, length;
60     descrip pattern;
61     gs_memory_t *memory;
62 };
63 gs_private_st_ptrs1(st_file_enum, struct file_enum_s, "file_enum",
64 	  file_enum_enum_ptrs, file_enum_reloc_ptrs, pattern.dsc$a_pointer);
65 
66 extern uint
67     LIB$FIND_FILE(descrip *, descrip *, uint *, descrip *, descrip *,
68 		  uint *, uint *),
69     LIB$FIND_FILE_END(uint *),
70     SYS$FILESCAN(descrip *, uint *, uint *),
71     SYS$PUTMSG(uint *, int (*)(), descrip *, uint);
72 
73 private uint
strlength(char * str,uint maxlen,char term)74 strlength(char *str, uint maxlen, char term)
75 {
76     uint i = 0;
77 
78     while (i < maxlen && str[i] != term)
79 	i++;
80     return i;
81 }
82 
83 /* Do platform-dependent initialization. */
84 void
gp_init(void)85 gp_init(void)
86 {
87 }
88 
89 /* Do platform-dependent cleanup. */
90 void
gp_exit(int exit_status,int code)91 gp_exit(int exit_status, int code)
92 {
93 }
94 
95 /* Exit the program. */
96 void
gp_do_exit(int exit_status)97 gp_do_exit(int exit_status)
98 {				/* The program returns exit_status = 0 for OK, 1 for failure; */
99     /* VMS has different conventions. */
100     switch (exit_status) {
101 	case 0:
102 	    exit(exit_OK);
103 	case 1:
104 	    exit(exit_FAILED);
105     }
106     exit(exit_status);
107 }
108 
109 /* ------ Date and time ------ */
110 
111 /* Read the current time (in seconds since Jan. 1, 1980) */
112 /* and fraction (in nanoseconds). */
113 void
gp_get_realtime(long * pdt)114 gp_get_realtime(long *pdt)
115 {
116     struct {
117 	uint _l0, _l1;
118     } binary_date, now, difference;
119     long LIB$EDIV(), LIB$SUBX(), SYS$BINTIM(), SYS$GETTIM();
120     long units_per_second = 10000000;
121     char *jan_1_1980 = "1-JAN-1980 00:00:00.00";
122     descrip str_desc;
123 
124     /* For those interested, Wednesday, November 17, 1858 is the base
125        of the Modified Julian Day system adopted by the Smithsonian
126        Astrophysical Observatory in 1957 for satellite tracking.  (The
127        year 1858 preceded the oldest star catalog in use at the
128        observatory.)  VMS uses quadword time stamps which are offsets
129        in 100 nanosecond units from November 17, 1858.  With a 63-bit
130        absolute time representation (sign bit must be clear), VMS will
131        have no trouble with time until 31-JUL-31086 02:48:05.47. */
132 
133     /* Convert January 1, 1980 into a binary absolute time */
134     str_desc.dsc$w_length = strlen(jan_1_1980);
135     str_desc.dsc$a_pointer = jan_1_1980;
136     (void)SYS$BINTIM(&str_desc, &binary_date);
137 
138     /* Compute number of 100 nanosecond units since January 1, 1980.  */
139     (void)SYS$GETTIM(&now);
140     (void)LIB$SUBX(&now, &binary_date, &difference);
141 
142     /* Convert to seconds and nanoseconds.  */
143     (void)LIB$EDIV(&units_per_second, &difference, &pdt[0], &pdt[1]);
144     pdt[1] *= 100;
145 }
146 
147 /* Read the current user CPU time (in seconds) */
148 /* and fraction (in nanoseconds).  */
149 void
gp_get_usertime(long * pdt)150 gp_get_usertime(long *pdt)
151 {
152     gp_get_realtime(pdt);	/* Use an approximation for now.  */
153 }
154 
155 
156 /* ------ Persistent data cache ------*/
157 
158 /* insert a buffer under a (type, key) pair */
gp_cache_insert(int type,byte * key,int keylen,void * buffer,int buflen)159 int gp_cache_insert(int type, byte *key, int keylen, void *buffer, int buflen)
160 {
161     /* not yet implemented */
162     return 0;
163 }
164 
165 /* look up a (type, key) in the cache */
gp_cache_query(int type,byte * key,int keylen,void ** buffer,gp_cache_alloc alloc,void * userdata)166 int gp_cache_query(int type, byte* key, int keylen, void **buffer,
167     gp_cache_alloc alloc, void *userdata)
168 {
169     /* not yet implemented */
170     return -1;
171 }
172 
173 /* ------ Screen management ------ */
174 
175 /* Get the environment variable that specifies the display to use. */
176 const char *
gp_getenv_display(void)177 gp_getenv_display(void)
178 {
179     return getenv("DECW$DISPLAY");
180 }
181 
182 /* ------ Printer accessing ------ */
183 
184 /* Open a connection to a printer.  A null file name means use the */
185 /* standard printer connected to the machine, if any. */
186 /* Return NULL if the connection could not be opened. */
187 FILE *
gp_open_printer(char fname[gp_file_name_sizeof],int binary_mode)188 gp_open_printer(char fname[gp_file_name_sizeof], int binary_mode)
189 {
190     if (strlen(fname) == 0)
191 	return 0;
192     if (binary_mode) {		/*
193 				 * Printing must be done exactly byte to byte,
194 				 * using "passall".  However the standard VMS symbiont
195 				 * does not treat stream-LF files correctly in this respect,
196 				 * but throws away \n characters.  Giving the file
197 				 * the record type "undefined", but accessing it as a
198 				 * normal stream-LF file does the trick.
199 				 */
200 	return fopen_VMS(fname, "w", "rfm = udf", "ctx = stm");
201     } else {			/* Open as a normal text stream file. */
202 	return fopen_VMS(fname, "w", "rfm = var", "rat = cr");
203     }
204 }
205 
206 /* Close the connection to the printer. */
207 void
gp_close_printer(FILE * pfile,const char * fname)208 gp_close_printer(FILE * pfile, const char *fname)
209 {
210     fclose(pfile);
211 }
212 
213 /* ------ File naming and accessing ------ */
214 
215 /* Define the character used for separating file names in a list. */
216 const char gp_file_name_list_separator = ',';
217 
218 /* Define the default scratch file name prefix. */
219 const char gp_scratch_file_name_prefix[] = "_temp_";
220 
221 /* Define the name of the null output file. */
222 const char gp_null_file_name[] = "NLA0:";
223 
224 /* Define the name that designates the current directory. */
225 const char gp_current_directory_name[] = "[]";
226 
227 /* Define the string to be concatenated with the file mode */
228 /* for opening files without end-of-line conversion. */
229 const char gp_fmode_binary_suffix[] = "";
230 
231 /* Define the file modes for binary reading or writing. */
232 const char gp_fmode_rb[] = "r";
233 const char gp_fmode_wb[] = "w";
234 
235 /* Create and open a scratch file with a given name prefix. */
236 /* Write the actual file name at fname. */
237 FILE *
gp_open_scratch_file(const char * prefix,char fname[gp_file_name_sizeof],const char * mode)238 gp_open_scratch_file(const char *prefix, char fname[gp_file_name_sizeof],
239 		     const char *mode)
240 {
241     FILE *f;
242     char tmpdir[gp_file_name_sizeof];
243     int tdlen = gp_file_name_sizeof;
244     int flen[1];
245 
246     if (!gp_file_name_is_absolute(prefix, strlen(prefix)) &&
247 	gp_gettmpdir(tmpdir, &tdlen) == 0) {
248       flen[0] = gp_file_name_sizeof;
249 	if (gp_file_name_combine(tmpdir, tdlen, prefix, strlen(prefix),
250 			     false, fname, flen ) != gp_combine_success ) {
251 	    return NULL;
252 	}
253        fname[ *flen ] = 0;
254     } else {
255 	strcpy(fname, prefix);
256     }
257     if (strlen(fname) + 6 >= gp_file_name_sizeof)
258 	return 0;		/* file name too long */
259     strcat(fname, "XXXXXX");
260    mktemp(fname);
261     f = fopen(fname, mode);
262 
263     if (f == NULL)
264 	eprintf1("**** Could not open temporary file %s\n", fname);
265    return f;
266 }
267 
268 /* Open a file with the given name, as a stream of uninterpreted bytes. */
269 /* We have to do something special if the file was FTP'ed in binary mode. */
270 /* Unfortunately, only DEC C supports the extra arguments to fopen. */
271 FILE *
gp_fopen(const char * fname,const char * mode)272 gp_fopen(const char *fname, const char *mode)
273 {
274 #ifdef __DECC
275 #define FAB$C_FIX 1
276     stat_t buffer;
277 
278     if (stat((char *)fname, &buffer) == 0)
279 	if (buffer.st_fab_rfm == FAB$C_FIX)
280 	    return fopen(fname, mode, "rfm=stmlf", "ctx=stm");
281 #endif
282     return fopen(fname, mode);
283 }
284 
285 /* Set a file into binary or text mode. */
286 int
gp_setmode_binary(FILE * pfile,bool binary)287 gp_setmode_binary(FILE * pfile, bool binary)
288 {
289     return 0;			/* Noop under VMS */
290 }
291 
292 /* ------ Wild card file search procedures ------ */
293 
294 private void
gp_free_enumeration(file_enum * pfen)295 gp_free_enumeration(file_enum * pfen)
296 {
297     if (pfen) {
298 	LIB$FIND_FILE_END(&pfen->context);
299 	gs_free_object(pfen->memory, pfen->pattern.dsc$a_pointer,
300 		       "GP_ENUM(pattern)");
301 	gs_free_object(pfen->memory, pfen,
302 		       "GP_ENUM(file_enum)");
303     }
304 }
305 
306 /* Begin an enumeration.  See gp.h for details. */
307 
308 file_enum *
gp_enumerate_files_init(const char * pat,uint patlen,gs_memory_t * mem)309 gp_enumerate_files_init(const char *pat, uint patlen, gs_memory_t * mem)
310 {
311     file_enum *pfen;
312     uint i, len;
313     char *c, *newpat;
314     bool dot_in_filename = false;
315 
316     pfen = gs_alloc_struct(mem, file_enum, &st_file_enum,
317 			   "GP_ENUM(file_enum)");
318     newpat = (char *)gs_alloc_bytes(mem, patlen + 2, "GP_ENUM(pattern)");
319     if (pfen == 0 || newpat == 0) {
320 	gs_free_object(mem, newpat, "GP_ENUM(pattern)");
321 	gs_free_object(mem, pfen, "GP_ENUM(file_enum)");
322 	return (file_enum *) 0;
323     }
324     /*  Copy the pattern removing backslash quoting characters and
325      *  transforming unquoted question marks, '?', to percent signs, '%'.
326      *  (VAX/VMS uses the wildcard '%' to represent exactly one character
327      *  and '*' to represent zero or more characters.  Any combination and
328      *  number of interspersed wildcards is permitted.)
329      *
330      *  Since VMS requires "*.*" to actually return all files, we add a
331      *  special check for a path ending in "*" and change it into "*.*"
332      *  if a "." wasn't part of the file spec. Thus "[P.A.T.H]*" becomes
333      *  "[P.A.T.H]*.*" but "[P.A.T.H]*.*" or "[P.A.T.H]*.X*" are unmodified.
334      */
335     c = newpat;
336     for (i = 0; i < patlen; pat++, i++)
337 	switch (*pat) {
338 	    case '?':
339 		*c++ = '%';
340 		break;
341 	    case '\\':
342 		i++;
343 		if (i < patlen)
344 		    *c++ = *++pat;
345 		break;
346 	    case '.':
347 	    case ']':
348 		dot_in_filename = *pat == '.';
349 	    default:
350 		*c++ = *pat;
351 		break;
352 	}
353     /* Check for trailing "*" and see if we need to add ".*" */
354     if (pat[-1] == '*' && !dot_in_filename) {
355 	*c++ = '.';
356 	*c++ = '*';
357     }
358     len = c - newpat;
359 
360     /* Pattern may not exceed 255 characters */
361     if (len > 255) {
362 	gs_free_object(mem, newpat, "GP_ENUM(pattern)");
363 	gs_free_object(mem, pfen, "GP_ENUM(file_enum)");
364 	return (file_enum *) 0;
365     }
366     pfen->context = 0;
367     pfen->length = patlen;
368     pfen->pattern.dsc$w_length = len;
369     pfen->pattern.dsc$b_dtype = DSC$K_DTYPE_T;
370     pfen->pattern.dsc$b_class = DSC$K_CLASS_S;
371     pfen->pattern.dsc$a_pointer = newpat;
372     pfen->memory = mem;
373 
374     return pfen;
375 }
376 
377 /* Return the next file name in the enumeration.  The client passes in */
378 /* a scratch string and a max length.  If the name of the next file fits, */
379 /* the procedure returns the length.  If it doesn't fit, the procedure */
380 /* returns max length +1.  If there are no more files, the procedure */
381 /* returns -1. */
382 
383 uint
gp_enumerate_files_next(file_enum * pfen,char * ptr,uint maxlen)384 gp_enumerate_files_next(file_enum * pfen, char *ptr, uint maxlen)
385 {
386     char *c, filnam[NAM$C_MAXRSS];
387     descrip result =
388     {NAM$C_MAXRSS, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
389     uint i, len;
390 
391     result.dsc$a_pointer = filnam;
392 
393     /* Find the next file which matches the pattern */
394     i = LIB$FIND_FILE(&pfen->pattern, &result, &pfen->context,
395 		      (descrip *) 0, (descrip *) 0, (uint *) 0, (uint *) 0);
396 
397     /* Check the return status */
398     if (RMS_IS_ERROR_OR_NMF(i)) {
399 	gp_free_enumeration(pfen);
400 	return (uint)(-1);
401     } else if ((len = strlength(filnam, NAM$C_MAXRSS, ' ')) > maxlen)
402 	return maxlen + 1;
403 
404     /* Copy the returned filename over to the input string ptr */
405     c = ptr;
406     for (i = 0; i < len; i++)
407 	*c++ = filnam[i];
408 
409     return len;
410 }
411 
412 /* Clean up a file enumeration.  This is only called to abandon */
413 /* an enumeration partway through: ...next should do it if there are */
414 /* no more files to enumerate.  This should deallocate the file_enum */
415 /* structure and any subsidiary structures, strings, buffers, etc. */
416 
417 void
gp_enumerate_files_close(file_enum * pfen)418 gp_enumerate_files_close(file_enum * pfen)
419 {
420     gp_free_enumeration(pfen);
421 }
422 
423 const char *
gp_strerror(int errnum)424 gp_strerror(int errnum)
425 {
426     return NULL;
427 }
428 
429 /* -------------- Helpers for gp_file_name_combine_generic ------------- */
430 
gp_file_name_root(const char * fname,uint len)431 uint gp_file_name_root(const char *fname, uint len)
432 {
433     /*
434      *    The root for device:[root.][directory.subdirectory]filename.extension;version
435      *	    is device:[root.][
436      *    The root for device:[directory.subdirectory]filename.extension;version
437      *	    is device:[
438      *    The root for logical:filename.extension;version
439      *	    is logical:
440      */
441     int i, j;
442 
443     if (len == 0)
444 	return 0;
445     /* Search for ':' */
446     for (i = 0; i < len; i++)
447 	if (fname[i] == ':')
448 	    break;
449     if (i == len)
450 	return 0; /* No root. */
451     if (fname[i] == ':')
452 	i++;
453     if (i == len || fname[i] != '[')
454 	return i;
455     /* Search for ']' */
456     i++;
457     for (j = i; j < len; j++)
458 	if (fname[j] == ']')
459 	    break;
460     if (j == len)
461 	return i; /* No ']'. Allowed as a Ghostscript specifics. */
462     j++;
463     if (j == len)
464 	return i; /* Appending "device:[directory.subdirectory]" with "filename.extension;version". */
465     if (fname[j] != '[')
466 	return i; /* Can't append anything, but pass through for checking an absolute path. */
467     return j + 1; /* device:[root.][ */
468 }
469 
gs_file_name_check_separator(const char * fname,int len,const char * item)470 uint gs_file_name_check_separator(const char *fname, int len, const char *item)
471 {
472     if (len > 0) {
473 	/*
474 	 * Ghostscript specifics : an extended syntax like Mac OS.
475 	 * We intentionally don't consider ':' and '[' as separators
476 	 * in forward search, see gp_file_name_combine.
477 	 */
478 	if (fname[0] == ']')
479 	    return 1; /* It is a file separator. */
480 	if (fname[0] == '.')
481 	    return 1; /* It is a directory separator. */
482 	if (fname[0] == '-') {
483 	    if (fname == item + 1 && item[0] == '-')
484 		return 1; /* Two or more parents, cut the first one. */
485 	    return 1;
486 	}
487     } else if (len < 0) {
488 	if (fname[-1] == '.' || fname[-1] == ':' || fname[-1] == '[')
489 	    return 1;
490     }
491     return 0;
492 }
493 
gp_file_name_is_parent(const char * fname,uint len)494 bool gp_file_name_is_parent(const char *fname, uint len)
495 {   /* Ghostscript specifics : an extended syntax like Mac OS. */
496     return len == 1 && fname[0] == '-';
497 }
498 
gp_file_name_is_current(const char * fname,uint len)499 bool gp_file_name_is_current(const char *fname, uint len)
500 {   /* Ghostscript specifics : an extended syntax like Mac OS. */
501     return len == 0;
502 }
503 
gp_file_name_separator(void)504 const char *gp_file_name_separator(void)
505 {   return "]";
506 }
507 
gp_file_name_directory_separator(void)508 const char *gp_file_name_directory_separator(void)
509 {   return ".";
510 }
511 
gp_file_name_parent(void)512 const char *gp_file_name_parent(void)
513 {   return "-";
514 }
515 
gp_file_name_current(void)516 const char *gp_file_name_current(void)
517 {   return "";
518 }
519 
gp_file_name_is_partent_allowed(void)520 bool gp_file_name_is_partent_allowed(void)
521 {   return false;
522 }
523 
gp_file_name_is_empty_item_meanful(void)524 bool gp_file_name_is_empty_item_meanful(void)
525 {   return true;
526 }
527 
528 gp_file_name_combine_result
gp_file_name_combine(const char * prefix,uint plen,const char * fname,uint flen,bool no_sibling,char * buffer,uint * blen)529 gp_file_name_combine(const char *prefix, uint plen, const char *fname, uint flen,
530 		    bool no_sibling, char *buffer, uint *blen)
531 {
532     /*
533      * Reduce it to the general case.
534      *
535      * Implementation restriction : fname must not contain a part of
536      * "device:[root.]["
537      */
538     uint rlen, flen1 = flen, plen1 = plen;
539     const char *fname1 = fname;
540 
541    if ( plen > 0 && prefix[plen-1] == '\0' )
542      plen--;
543 
544     if (plen == 0 && flen == 0) {
545 	/* Not sure that we need this case. */
546 	if (*blen == 0)
547 	    return gp_combine_small_buffer;
548 	buffer[0] = '.';
549 	*blen = 1;
550     }
551     rlen = gp_file_name_root(fname, flen);
552     if (rlen > 0 || plen == 0 || flen == 0) {
553 	if (rlen == 0 && plen != 0) {
554 	    fname1 = prefix;
555 	    flen1 = plen;
556 	}
557 	if (flen1 + 1 > *blen)
558 	    return gp_combine_small_buffer;
559 	memcpy(buffer, fname1, flen1);
560 	buffer[flen1] = 0;
561 	*blen = flen1;
562 	return gp_combine_success;
563     }
564 
565    if ( prefix[plen - 1] == ']' && fname[ 0 ] == '-' )
566      {
567 	memcpy(buffer, prefix, plen - 1 );
568 	fname1 = fname + 1;
569 	flen1 = flen - 1;
570 	memcpy(buffer + plen - 1 , fname1, flen1);
571 	memcpy(buffer + plen + flen1 - 1 , "]" , 1 );
572 	buffer[plen + flen1] = 0;
573 	*blen = plen + flen1;
574 	return gp_combine_success;
575      }
576 
577    if ( prefix[plen - 1] == ':' || (prefix[plen - 1] == ']' &&
578 				     memchr(fname, ']', flen) == 0) )
579        {
580 	/* Just concatenate. */
581 	if (plen + flen + 1 > *blen)
582 	    return gp_combine_small_buffer;
583 	memcpy(buffer, prefix, plen);
584 	memcpy(buffer + plen, fname, flen);
585 	buffer[plen + flen] = 0;
586 	*blen = plen + flen;
587 	return gp_combine_success;
588     }
589    if ( memchr( prefix , '[' , plen ) == 0 &&
590 	memchr( prefix , '.' , plen ) == 0 )
591      {
592 	char* tmp_prefix;
593 	int tmp_plen;
594 
595 	if ( prefix[0] == '/' )
596 	  {
597 	     tmp_prefix = prefix + 1;
598 	     tmp_plen = plen - 1;
599 	  }
600 	else
601 	  {
602 	     tmp_prefix = prefix;
603 	     tmp_plen = plen;
604 	  }
605 	if ( tmp_plen + flen + 2 > *blen)
606 	    return gp_combine_small_buffer;
607 	memcpy(buffer, tmp_prefix, tmp_plen);
608 	memcpy(buffer + tmp_plen , ":" , 1 );
609 	memcpy(buffer + tmp_plen + 1, fname, flen);
610 	if ( memchr( fname , '.' , flen ) != 0 )
611 	  {
612 	     buffer[ tmp_plen + flen + 1] = 0;
613 	     *blen = tmp_plen + flen + 1;
614 	  }
615 	else
616 	  {
617 	     memcpy(buffer + tmp_plen + flen + 1 , "." , 1 );
618 	     buffer[ tmp_plen + flen + 2] = 0;
619 	     *blen = tmp_plen + flen + 2;
620 	  }
621 	return gp_combine_success;
622      }
623     if (prefix[plen - 1] != ']' && fname[0] == '[')
624         return gp_combine_cant_handle;
625     /* Unclose "][" :*/
626     if (fname[0] == '[') {
627 	fname1 = fname + 1;
628 	flen1 = flen - 1;
629     }
630     if (prefix[plen - 1] == ']')
631         plen1 = plen - 1;
632     return gp_file_name_combine_generic(prefix, plen1,
633 	    fname1, flen1, no_sibling, buffer, blen);
634 }
635 
636 /* ------ Font enumeration ------ */
637 
638  /* This is used to query the native os for a list of font names and
639   * corresponding paths. The general idea is to save the hassle of
640   * building a custom fontmap file.
641   */
642 
gp_enumerate_fonts_init(gs_memory_t * mem)643 void *gp_enumerate_fonts_init(gs_memory_t *mem)
644 {
645     return NULL;
646 }
647 
gp_enumerate_fonts_next(void * enum_state,char ** fontname,char ** path)648 int gp_enumerate_fonts_next(void *enum_state, char **fontname, char **path)
649 {
650     return 0;
651 }
652 
gp_enumerate_fonts_free(void * enum_state)653 void gp_enumerate_fonts_free(void *enum_state)
654 {
655 }
656 
657