Initial import from FreeBSD RELENG_4:
[dragonfly.git] / contrib / groff / src / utils / hpftodit / hpftodit.cc
1 // -*- C++ -*-
2 /* Copyright (C) 1994, 2000, 2001 Free Software Foundation, Inc.
3      Written by James Clark (jjc@jclark.com)
4
5 This file is part of groff.
6
7 groff is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 2, or (at your option) any later
10 version.
11
12 groff is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 for more details.
16
17 You should have received a copy of the GNU General Public License along
18 with groff; see the file COPYING.  If not, write to the Free Software
19 Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
20
21 /*
22 TODO
23 put human readable font name in device file
24 devise new names for useful characters
25 use --- for unnamed characters
26 option to specify symbol sets to look in
27 make it work with TrueType fonts
28 put filename in error messages (or fix lib)
29 */
30
31 #include "lib.h"
32
33 #include <stdlib.h>
34 #include <math.h>
35 #include <errno.h>
36 #include "assert.h"
37 #include "posix.h"
38 #include "errarg.h"
39 #include "error.h"
40 #include "cset.h"
41 #include "nonposix.h"
42
43 extern "C" const char *Version_string;
44
45 #define SIZEOF(v) (sizeof(v)/sizeof(v[0]))
46
47 const int MULTIPLIER = 3;
48
49 inline
50 int scale(int n)
51 {
52   return n * MULTIPLIER;
53 }
54
55 // tags in TFM file
56
57 enum tag_type {
58   min_tag = 400,
59   type_tag = 400,
60   symbol_set_tag = 404,
61   msl_tag = 403,
62   inches_per_point_tag = 406,
63   design_units_per_em_tag = 408,
64   posture_tag = 409,
65   stroke_weight_tag = 411,
66   spacing_tag = 412,
67   slant_tag = 413,
68   appearance_width_tag = 414,
69   word_spacing_tag = 421,
70   x_height_tag = 424,
71   lower_ascent_tag = 427,
72   lower_descent_tag = 428,
73   width_tag = 433,
74   left_extent_tag = 435,
75   right_extent_tag = 436,
76   ascent_tag = 437,
77   descent_tag = 438,
78   pair_kern_tag = 439,
79   typeface_tag = 442,
80   max_tag = 443
81   };
82
83 // types in TFM file
84
85 enum {
86   ENUM_TYPE = 1,
87   BYTE_TYPE = 2,
88   USHORT_TYPE = 3,
89   FLOAT_TYPE = 5,
90   SIGNED_SHORT_TYPE = 17
91   };
92
93
94 typedef unsigned char byte;
95 typedef unsigned short uint16;
96 typedef short int16;
97 typedef unsigned int uint32;
98
99 class File {
100 public:
101   File(const char *);
102   void skip(int n);
103   byte get_byte();
104   uint16 get_uint16();
105   uint32 get_uint32();
106   void seek(uint32 n);
107 private:
108   unsigned char *buf_;
109   const unsigned char *ptr_;
110   const unsigned char *end_;
111 };
112
113 struct entry {
114   char present;
115   uint16 type;
116   uint32 count;
117   uint32 value;
118   entry() : present(0) { }
119 };
120
121 struct char_info {
122   uint16 msl;
123   uint16 width;
124   uint16 ascent;
125   int16 descent;
126   int16 left_extent;
127   uint16 right_extent;
128   uint16 symbol_set;
129   unsigned char code;
130 };
131
132 const uint16 NO_SYMBOL_SET = 0;
133
134 struct name_list {
135   char *name;
136   name_list *next;
137   name_list(const char *s, name_list *p) : name(strsave(s)), next(p) { }
138   ~name_list() { a_delete name; }
139 };
140
141 struct symbol_set {
142   uint16 select;
143   uint16 index[256];
144 };
145
146 #define SYMBOL_SET(n, c) ((n) * 32 + ((c) - 64))
147
148 uint16 text_symbol_sets[] = {
149   SYMBOL_SET(0, 'N'),           // Latin 1
150   SYMBOL_SET(6, 'J'),           // Microsoft Publishing
151   SYMBOL_SET(2, 'N'),           // Latin 2
152   0
153   };
154
155 uint16 special_symbol_sets[] = {
156   SYMBOL_SET(8, 'M'),
157   SYMBOL_SET(5, 'M'),
158   SYMBOL_SET(15, 'U'),
159   0
160   };
161
162 entry tags[max_tag + 1 - min_tag];
163
164 char_info *char_table;
165 uint32 nchars;
166
167 unsigned int msl_name_table_size = 0;
168 name_list **msl_name_table = 0;
169
170 unsigned int n_symbol_sets;
171 symbol_set *symbol_set_table;
172
173 static int special_flag = 0;
174 static int italic_flag = 0;
175 static int italic_sep;
176
177 static void usage(FILE *stream);
178 static void usage();
179 static const char *xbasename(const char *);
180 static void read_tags(File &);
181 static void check_type();
182 static void check_units(File &);
183 static int read_map(const char *);
184 static void require_tag(tag_type);
185 static void dump_tags(File &f);
186 static void output_spacewidth();
187 static void output_pclweight();
188 static void output_pclproportional();
189 static void read_and_output_pcltypeface(File &);
190 static void output_pclstyle();
191 static void output_slant();
192 static void output_ligatures();
193 static void read_symbol_sets(File &);
194 static void read_and_output_kernpairs(File &);
195 static void output_charset();
196 static void read_char_table(File &f);
197
198 inline
199 entry &tag_info(tag_type t)
200 {
201   return tags[t - min_tag];
202 }
203
204 int main(int argc, char **argv)
205 {
206   program_name = argv[0];
207
208   int opt;
209   int debug_flag = 0;
210
211   static const struct option long_options[] = {
212     { "help", no_argument, 0, CHAR_MAX + 1 },
213     { "version", no_argument, 0, 'v' },
214     { NULL, 0, 0, 0 }
215   };
216   while ((opt = getopt_long(argc, argv, "dsvi:", long_options, NULL)) != EOF) {
217     switch (opt) {
218     case 'd':
219       debug_flag = 1;
220       break;
221     case 's':
222       special_flag = 1;
223       break;
224     case 'i':
225       italic_flag = 1;
226       italic_sep = atoi(optarg);
227       break;
228     case 'v':
229       {
230         printf("GNU hpftodit (groff) version %s\n", Version_string);
231         exit(0);
232       }
233       break;
234     case CHAR_MAX + 1: // --help
235       usage(stdout);
236       exit(0);
237       break;
238     case '?':
239       usage();
240       break;
241     default:
242       assert(0);
243     }
244   }
245   if (argc - optind != 3)
246     usage();
247   File f(argv[optind]);
248   if (!read_map(argv[optind + 1]))
249     exit(1);
250   current_filename = 0;
251   current_lineno = -1;          // no line numbers
252   if (freopen(argv[optind + 2], "w", stdout) == 0)
253     fatal("cannot open `%1': %2", argv[optind + 2], strerror(errno));
254   current_filename = argv[optind];
255   printf("name %s\n", xbasename(argv[optind + 2]));
256   if (special_flag)
257     printf("special\n");
258   read_tags(f);
259   check_type();
260   check_units(f);
261   if (debug_flag)
262     dump_tags(f);
263   read_char_table(f);
264   output_spacewidth();
265   output_slant();
266   read_and_output_pcltypeface(f);
267   output_pclproportional();
268   output_pclweight();
269   output_pclstyle();
270   read_symbol_sets(f);
271   output_ligatures();
272   read_and_output_kernpairs(f);
273   output_charset();
274   return 0;
275 }
276
277 static
278 void usage(FILE *stream)
279 {
280   fprintf(stream, "usage: %s [-s] [-i n] tfm_file map_file output_font\n",
281           program_name);
282 }
283 static
284 void usage()
285 {
286   usage(stderr);
287   exit(1);
288 }
289
290 File::File(const char *s)
291 {
292   // We need to read the file in binary mode because hpftodit relies
293   // on byte counts.
294   int fd = open(s, O_RDONLY | O_BINARY);
295   if (fd < 0)
296     fatal("cannot open `%1': %2", s, strerror(errno));
297   current_filename = s;
298   struct stat sb;
299   if (fstat(fd, &sb) < 0)
300     fatal("cannot stat: %1", strerror(errno));
301   if (!S_ISREG(sb.st_mode))
302     fatal("not a regular file");
303   buf_ = new unsigned char[sb.st_size];
304   long nread = read(fd, buf_, sb.st_size);
305   if (nread < 0)
306     fatal("read error: %1", strerror(errno));
307   if (nread != sb.st_size)
308     fatal("read unexpected number of bytes");
309   ptr_ = buf_;
310   end_ = buf_ + sb.st_size;
311 }
312
313 void File::skip(int n)
314 {
315   if (end_ - ptr_ < n)
316     fatal("unexpected end of file");
317   ptr_ += n;
318 }
319
320 void File::seek(uint32 n)
321 {
322   if ((uint32)(end_ - buf_) < n)
323     fatal("unexpected end of file");
324   ptr_ = buf_ + n;
325 }
326
327 byte File::get_byte()
328 {
329   if (ptr_ >= end_)
330     fatal("unexpected end of file");
331   return *ptr_++;
332 }
333
334 uint16 File::get_uint16()
335 {
336   if (end_ - ptr_ < 2)
337     fatal("unexpected end of file");
338   uint16 n = *ptr_++;
339   return n + (*ptr_++ << 8);
340 }
341
342 uint32 File::get_uint32()
343 {
344   if (end_ - ptr_ < 4)
345     fatal("unexpected end of file");
346   uint32 n = *ptr_++;
347   for (int i = 0; i < 3; i++)
348     n += *ptr_++ << (i + 1)*8;
349   return n;
350 }
351
352 static
353 void read_tags(File &f)
354 {
355   if (f.get_byte() != 'I' || f.get_byte() != 'I')
356     fatal("not an Intel format TFM file");
357   f.skip(6);
358   uint16 ntags = f.get_uint16();
359   entry dummy;
360   for (uint16 i = 0; i < ntags; i++) {
361     uint16 tag = f.get_uint16();
362     entry *p;
363     if (min_tag <= tag && tag <= max_tag)
364       p = tags + (tag - min_tag);
365     else
366       p = &dummy;
367     p->present = 1;
368     p->type = f.get_uint16();
369     p->count = f.get_uint32();
370     p->value = f.get_uint32();
371   }
372 }
373
374 static
375 void check_type()
376 {
377   require_tag(type_tag);
378   if (tag_info(type_tag).value != 0) {
379     if (tag_info(type_tag).value == 2)
380       fatal("cannot handle TrueType tfm files");
381     fatal("unknown type tag %1", int(tag_info(type_tag).value));
382   }
383 }
384
385 static
386 void check_units(File &f)
387 {
388   require_tag(design_units_per_em_tag);
389   f.seek(tag_info(design_units_per_em_tag).value);
390   uint32 num = f.get_uint32();
391   uint32 den = f.get_uint32();
392   if (num != 8782 || den != 1)
393     fatal("design units per em != 8782/1");
394   require_tag(inches_per_point_tag);
395   f.seek(tag_info(inches_per_point_tag).value);
396   num = f.get_uint32();
397   den = f.get_uint32();
398   if (num != 100 || den != 7231)
399     fatal("inches per point not 100/7231");
400 }
401
402 static
403 void require_tag(tag_type t)
404 {
405   if (!tag_info(t).present)
406     fatal("tag %1 missing", int(t));
407 }
408
409 static
410 void output_spacewidth()
411 {
412   require_tag(word_spacing_tag);
413   printf("spacewidth %d\n", scale(tag_info(word_spacing_tag).value));
414 }
415
416 static
417 void read_symbol_sets(File &f)
418 {
419   uint32 symbol_set_dir_length = tag_info(symbol_set_tag).count;
420   n_symbol_sets = symbol_set_dir_length/14;
421   symbol_set_table = new symbol_set[n_symbol_sets];
422   unsigned int i;
423   for (i = 0; i < n_symbol_sets; i++) {
424     f.seek(tag_info(symbol_set_tag).value + i*14);
425     (void)f.get_uint32();
426     uint32 off1 = f.get_uint32();
427     uint32 off2 = f.get_uint32();
428     (void)f.get_uint16();               // what's this for?
429     f.seek(off1);
430     unsigned int j;
431     uint16 kind = 0;
432     for (j = 0; j < off2 - off1; j++) {
433       unsigned char c = f.get_byte();
434       if ('0' <= c && c <= '9')
435         kind = kind*10 + (c - '0');
436       else if ('A' <= c && c <= 'Z')
437         kind = kind*32 + (c - 64);
438     }
439     symbol_set_table[i].select = kind;
440     for (j = 0; j < 256; j++)
441       symbol_set_table[i].index[j] = f.get_uint16();
442   }
443   for (i = 0; i < nchars; i++)
444     char_table[i].symbol_set = NO_SYMBOL_SET;
445
446   uint16 *symbol_set_selectors = (special_flag
447                                   ? special_symbol_sets
448                                   : text_symbol_sets);
449   for (i = 0; symbol_set_selectors[i] != 0; i++) {
450     unsigned int j;
451     for (j = 0; j < n_symbol_sets; j++)
452       if (symbol_set_table[j].select == symbol_set_selectors[i])
453         break;
454     if (j < n_symbol_sets) {
455       for (int k = 0; k < 256; k++) {
456         uint16 index = symbol_set_table[j].index[k];
457         if (index != 0xffff
458             && char_table[index].symbol_set == NO_SYMBOL_SET) {
459           char_table[index].symbol_set = symbol_set_table[j].select;
460           char_table[index].code = k;
461         }
462       }
463     }
464   }
465 }
466
467 static
468 void read_char_table(File &f)
469 {
470   require_tag(msl_tag);
471   nchars = tag_info(msl_tag).count;
472   char_table = new char_info[nchars];
473
474   f.seek(tag_info(msl_tag).value);
475   uint32 i;
476   for (i = 0; i < nchars; i++)
477     char_table[i].msl = f.get_uint16();
478   
479   require_tag(width_tag);
480   f.seek(tag_info(width_tag).value);
481   for (i = 0; i < nchars; i++)
482     char_table[i].width = f.get_uint16();
483
484   require_tag(ascent_tag);
485   f.seek(tag_info(ascent_tag).value);
486   for (i = 0; i < nchars; i++) {
487     char_table[i].ascent = f.get_uint16();
488   }
489
490   require_tag(descent_tag);
491   f.seek(tag_info(descent_tag).value);
492   for (i = 0; i < nchars; i++) {
493     char_table[i].descent = f.get_uint16();
494     if (char_table[i].descent > 0)
495       char_table[i].descent = 0;
496   }
497
498   require_tag(left_extent_tag);
499   f.seek(tag_info(left_extent_tag).value);
500   for (i = 0; i < nchars; i++)
501     char_table[i].left_extent = int16(f.get_uint16());
502
503   require_tag(right_extent_tag);
504   f.seek(tag_info(right_extent_tag).value);
505   for (i = 0; i < nchars; i++)
506     char_table[i].right_extent = f.get_uint16();
507 }
508
509 static
510 void output_pclweight()
511 {
512   require_tag(stroke_weight_tag);
513   int stroke_weight = tag_info(stroke_weight_tag).value;
514   int pcl_stroke_weight;
515   if (stroke_weight < 128)
516     pcl_stroke_weight = -3;
517   else if (stroke_weight == 128)
518     pcl_stroke_weight = 0;
519   else if (stroke_weight <= 145)
520     pcl_stroke_weight = 1;
521   else if (stroke_weight <= 179)
522     pcl_stroke_weight = 3;
523   else
524     pcl_stroke_weight = 4;
525   printf("pclweight %d\n", pcl_stroke_weight);
526 }
527
528 static
529 void output_pclproportional()
530 {
531   require_tag(spacing_tag);
532   printf("pclproportional %d\n", tag_info(spacing_tag).value == 0);
533 }
534
535 static
536 void read_and_output_pcltypeface(File &f)
537 {
538   printf("pcltypeface ");
539   require_tag(typeface_tag);
540   f.seek(tag_info(typeface_tag).value);
541   for (uint32 i = 0; i < tag_info(typeface_tag).count; i++) {
542     unsigned char c = f.get_byte();
543     if (c == '\0')
544       break;
545     putchar(c);
546   }
547   printf("\n");
548 }
549
550 static
551 void output_pclstyle()
552 {
553   unsigned pcl_style = 0;
554   // older tfms don't have the posture tag
555   if (tag_info(posture_tag).present) {
556     if (tag_info(posture_tag).value)
557       pcl_style |= 1;
558   }
559   else {
560     require_tag(slant_tag);
561     if (tag_info(slant_tag).value != 0)
562       pcl_style |= 1;
563   }
564   require_tag(appearance_width_tag);
565   if (tag_info(appearance_width_tag).value < 100) // guess
566     pcl_style |= 4;
567   printf("pclstyle %d\n", pcl_style);
568 }
569
570 static
571 void output_slant()
572 {
573   require_tag(slant_tag);
574   int slant = int16(tag_info(slant_tag).value);
575   if (slant != 0)
576     printf("slant %f\n", slant/100.0);
577 }
578
579 static
580 void output_ligatures()
581 {
582   // don't use ligatures for fixed space font
583   require_tag(spacing_tag);
584   if (tag_info(spacing_tag).value != 0)
585     return;
586   static const char *ligature_names[] = {
587     "fi", "fl", "ff", "ffi", "ffl"
588     };
589
590   static const char *ligature_chars[] = {
591     "fi", "fl", "ff", "Fi", "Fl"
592     };
593   
594   unsigned ligature_mask = 0;
595   unsigned int i;
596   for (i = 0; i < nchars; i++) {
597     uint16 msl = char_table[i].msl;
598     if (msl < msl_name_table_size
599         && char_table[i].symbol_set != NO_SYMBOL_SET) {
600       for (name_list *p = msl_name_table[msl]; p; p = p->next)
601         for (unsigned int j = 0; j < SIZEOF(ligature_chars); j++)
602           if (strcmp(p->name, ligature_chars[j]) == 0) {
603             ligature_mask |= 1 << j;
604             break;
605           }
606       }
607     }
608   if (ligature_mask) {
609     printf("ligatures");
610     for (i = 0; i < SIZEOF(ligature_names); i++)
611       if (ligature_mask & (1 << i))
612         printf(" %s", ligature_names[i]);
613     printf(" 0\n");
614   }
615 }
616
617 static
618 void read_and_output_kernpairs(File &f)
619 {
620   if (tag_info(pair_kern_tag).present) {
621     printf("kernpairs\n");
622     f.seek(tag_info(pair_kern_tag).value);
623     uint16 n_pairs = f.get_uint16();
624     for (int i = 0; i < n_pairs; i++) {
625       uint16 i1 = f.get_uint16();
626       uint16 i2 = f.get_uint16();
627       int16 val = int16(f.get_uint16());
628       if (char_table[i1].symbol_set != NO_SYMBOL_SET
629           && char_table[i2].symbol_set != NO_SYMBOL_SET
630           && char_table[i1].msl < msl_name_table_size
631           && char_table[i2].msl < msl_name_table_size) {
632         for (name_list *p = msl_name_table[char_table[i1].msl];
633              p;
634              p = p->next)
635           for (name_list *q = msl_name_table[char_table[i2].msl];
636                q;
637                q = q->next)
638             printf("%s %s %d\n", p->name, q->name, scale(val));
639       }
640     }
641   }
642 }
643
644 static 
645 void output_charset()
646 {
647   require_tag(slant_tag);
648   double slant_angle = int16(tag_info(slant_tag).value)*PI/18000.0;
649   double slant = sin(slant_angle)/cos(slant_angle);
650
651   require_tag(x_height_tag);
652   require_tag(lower_ascent_tag);
653   require_tag(lower_descent_tag);
654
655   printf("charset\n");
656   unsigned int i;
657   for (i = 0; i < nchars; i++) {
658     uint16 msl = char_table[i].msl;
659     if (msl < msl_name_table_size
660         && msl_name_table[msl]) {
661       if (char_table[i].symbol_set != NO_SYMBOL_SET) {
662         printf("%s\t%d,%d",
663                msl_name_table[msl]->name,
664                scale(char_table[i].width),
665                scale(char_table[i].ascent));
666         int depth = scale(- char_table[i].descent);
667         if (depth < 0)
668           depth = 0;
669         int italic_correction = 0;
670         int left_italic_correction = 0;
671         int subscript_correction = 0;
672         if (italic_flag) {
673           italic_correction = scale(char_table[i].right_extent 
674                                     - char_table[i].width
675                                     + italic_sep);
676           if (italic_correction < 0)
677             italic_correction = 0;
678           subscript_correction = int((tag_info(x_height_tag).value
679                                       * slant * .8) + .5);
680           if (subscript_correction > italic_correction)
681             subscript_correction = italic_correction;
682           left_italic_correction = scale(italic_sep
683                                          - char_table[i].left_extent);
684         }
685         if (subscript_correction != 0)
686           printf(",%d,%d,%d,%d",
687                  depth, italic_correction, left_italic_correction,
688                  subscript_correction);
689         else if (left_italic_correction != 0)
690           printf(",%d,%d,%d", depth, italic_correction, left_italic_correction);
691         else if (italic_correction != 0)
692           printf(",%d,%d", depth, italic_correction);
693         else if (depth != 0)
694           printf(",%d", depth);
695         // This is fairly arbitrary.  Fortunately it doesn't much matter.
696         unsigned type = 0;
697         if (char_table[i].ascent > (tag_info(lower_ascent_tag).value*9)/10)
698           type |= 2;
699         if (char_table[i].descent < (int16(tag_info(lower_descent_tag).value)*9)/10)
700           type |= 1;
701         printf("\t%d\t%d\n",
702                type,
703                char_table[i].symbol_set*256 + char_table[i].code);
704         for (name_list *p = msl_name_table[msl]->next; p; p = p->next)
705           printf("%s\t\"\n", p->name);
706       }
707       else
708         warning("MSL %1 not in any of the searched symbol sets", msl);
709     }
710   }
711 }
712
713 static
714 void dump_tags(File &f)
715 {
716   int i;
717   for (i = min_tag; i <= max_tag; i++) {
718     enum tag_type t = tag_type(i);
719     if (tag_info(t).present) {
720       fprintf(stderr,
721               "%d %d %d %d\n", i, tag_info(t).type, tag_info(t).count,
722               tag_info(t).value);
723       if (tag_info(t).type == FLOAT_TYPE
724           && tag_info(t).count == 1) {
725         f.seek(tag_info(t).value);
726         uint32 num = f.get_uint32();
727         uint32 den = f.get_uint32();
728         fprintf(stderr, "(%u/%u = %g)\n", num, den, (double)num/den);
729       }
730     }
731   }
732 }
733
734 static
735 int read_map(const char *file)
736 {
737   errno = 0;
738   FILE *fp = fopen(file, "r");
739   if (!fp) {
740     error("can't open `%1': %2", file, strerror(errno));
741     return 0;
742   }
743   current_filename = file;
744   char buf[512];
745   current_lineno = 0;
746   while (fgets(buf, int(sizeof(buf)), fp)) {
747     current_lineno++;
748     char *ptr = buf;
749     while (csspace(*ptr))
750       ptr++;
751     if (*ptr == '\0' || *ptr == '#')
752       continue;
753     ptr = strtok(ptr, " \n\t");
754     if (!ptr)
755       continue;
756     int n;
757     if (sscanf(ptr, "%d", &n) != 1) {
758       error("bad map file");
759       fclose(fp);
760       return 0;
761     }
762     if (n < 0) {
763       error("negative code");
764       fclose(fp);
765       return 0;
766     }
767     if ((size_t)n >= msl_name_table_size) {
768       size_t old_size = msl_name_table_size;
769       name_list **old_table = msl_name_table;
770       msl_name_table_size = n + 256;
771       msl_name_table = new name_list *[msl_name_table_size];
772       if (old_table) {
773         memcpy(msl_name_table, old_table, old_size*sizeof(name_list *));
774         a_delete old_table;
775       }
776       for (size_t i = old_size; i < msl_name_table_size; i++)
777         msl_name_table[i] = 0;
778     }
779     ptr = strtok(0, " \n\t");
780     if (!ptr) {
781       error("missing names");
782       fclose(fp);
783       return 0;
784     }
785     for (; ptr; ptr = strtok(0, " \n\t"))
786       msl_name_table[n] = new name_list(ptr, msl_name_table[n]);
787   }
788   fclose(fp);
789   return 1;
790 }
791
792 static
793 const char *xbasename(const char *s)
794 {
795   // DIR_SEPS[] are possible directory separator characters, see
796   // nonposix.h.  We want the rightmost separator of all possible
797   // ones.  Example: d:/foo\\bar.
798   const char *b = strrchr(s, DIR_SEPS[0]), *b1;
799   const char *sep = &DIR_SEPS[1];
800
801   while (*sep)
802     {
803       b1 = strrchr(s, *sep);
804       if (b1 && (!b || b1 > b))
805         b = b1;
806       sep++;
807     }
808   return b ? b + 1 : s;
809 }