hpftodit.cpp revision 114402
1// -*- C++ -*- 2/* Copyright (C) 1994, 2000, 2001, 2003 Free Software Foundation, Inc. 3 Written by James Clark (jjc@jclark.com) 4 5This file is part of groff. 6 7groff is free software; you can redistribute it and/or modify it under 8the terms of the GNU General Public License as published by the Free 9Software Foundation; either version 2, or (at your option) any later 10version. 11 12groff is distributed in the hope that it will be useful, but WITHOUT ANY 13WARRANTY; without even the implied warranty of MERCHANTABILITY or 14FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15for more details. 16 17You should have received a copy of the GNU General Public License along 18with groff; see the file COPYING. If not, write to the Free Software 19Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ 20 21/* 22TODO 23put human readable font name in device file 24devise new names for useful characters 25use --- for unnamed characters 26option to specify symbol sets to look in 27make it work with TrueType fonts 28put 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 43extern "C" const char *Version_string; 44 45#define SIZEOF(v) (sizeof(v)/sizeof(v[0])) 46 47const int MULTIPLIER = 3; 48 49inline 50int scale(int n) 51{ 52 return n * MULTIPLIER; 53} 54 55// tags in TFM file 56 57enum 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 85enum { 86 ENUM_TYPE = 1, 87 BYTE_TYPE = 2, 88 USHORT_TYPE = 3, 89 FLOAT_TYPE = 5, 90 SIGNED_SHORT_TYPE = 17 91 }; 92 93 94typedef unsigned char byte; 95typedef unsigned short uint16; 96typedef short int16; 97typedef unsigned int uint32; 98 99class File { 100public: 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); 107private: 108 unsigned char *buf_; 109 const unsigned char *ptr_; 110 const unsigned char *end_; 111}; 112 113struct entry { 114 char present; 115 uint16 type; 116 uint32 count; 117 uint32 value; 118 entry() : present(0) { } 119}; 120 121struct char_info { 122 uint16 msl; 123 uint16 width; 124 int16 ascent; 125 int16 descent; 126 int16 left_extent; 127 uint16 right_extent; 128 uint16 symbol_set; 129 unsigned char code; 130}; 131 132const uint16 NO_SYMBOL_SET = 0; 133 134struct 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 141struct symbol_set { 142 uint16 select; 143 uint16 index[256]; 144}; 145 146#define SYMBOL_SET(n, c) ((n) * 32 + ((c) - 64)) 147 148uint16 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 155uint16 special_symbol_sets[] = { 156 SYMBOL_SET(8, 'M'), 157 SYMBOL_SET(5, 'M'), 158 SYMBOL_SET(15, 'U'), 159 0 160 }; 161 162entry tags[max_tag + 1 - min_tag]; 163 164char_info *char_table; 165uint32 nchars; 166 167unsigned int msl_name_table_size = 0; 168name_list **msl_name_table = 0; 169 170unsigned int n_symbol_sets; 171symbol_set *symbol_set_table; 172 173static int special_flag = 0; 174static int italic_flag = 0; 175static int italic_sep; 176 177static void usage(FILE *stream); 178static void usage(); 179static const char *xbasename(const char *); 180static void read_tags(File &); 181static void check_type(); 182static void check_units(File &); 183static int read_map(const char *); 184static void require_tag(tag_type); 185static void dump_tags(File &f); 186static void output_spacewidth(); 187static void output_pclweight(); 188static void output_pclproportional(); 189static void read_and_output_pcltypeface(File &); 190static void output_pclstyle(); 191static void output_slant(); 192static void output_ligatures(); 193static void read_symbol_sets(File &); 194static void read_and_output_kernpairs(File &); 195static void output_charset(); 196static void read_char_table(File &f); 197 198inline 199entry &tag_info(tag_type t) 200{ 201 return tags[t - min_tag]; 202} 203 204int 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 277static 278void usage(FILE *stream) 279{ 280 fprintf(stream, "usage: %s [-s] [-i n] tfm_file map_file output_font\n", 281 program_name); 282} 283static 284void usage() 285{ 286 usage(stderr); 287 exit(1); 288} 289 290File::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 313void File::skip(int n) 314{ 315 if (end_ - ptr_ < n) 316 fatal("unexpected end of file"); 317 ptr_ += n; 318} 319 320void File::seek(uint32 n) 321{ 322 if ((uint32)(end_ - buf_) < n) 323 fatal("unexpected end of file"); 324 ptr_ = buf_ + n; 325} 326 327byte File::get_byte() 328{ 329 if (ptr_ >= end_) 330 fatal("unexpected end of file"); 331 return *ptr_++; 332} 333 334uint16 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 342uint32 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 352static 353void 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 374static 375void 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 385static 386void 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 402static 403void require_tag(tag_type t) 404{ 405 if (!tag_info(t).present) 406 fatal("tag %1 missing", int(t)); 407} 408 409static 410void output_spacewidth() 411{ 412 require_tag(word_spacing_tag); 413 printf("spacewidth %d\n", scale(tag_info(word_spacing_tag).value)); 414} 415 416static 417void 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 467static 468void 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 if (char_table[i].ascent < 0) 489 char_table[i].ascent = 0; 490 } 491 492 require_tag(descent_tag); 493 f.seek(tag_info(descent_tag).value); 494 for (i = 0; i < nchars; i++) { 495 char_table[i].descent = f.get_uint16(); 496 if (char_table[i].descent > 0) 497 char_table[i].descent = 0; 498 } 499 500 require_tag(left_extent_tag); 501 f.seek(tag_info(left_extent_tag).value); 502 for (i = 0; i < nchars; i++) 503 char_table[i].left_extent = int16(f.get_uint16()); 504 505 require_tag(right_extent_tag); 506 f.seek(tag_info(right_extent_tag).value); 507 for (i = 0; i < nchars; i++) 508 char_table[i].right_extent = f.get_uint16(); 509} 510 511static 512void output_pclweight() 513{ 514 require_tag(stroke_weight_tag); 515 int stroke_weight = tag_info(stroke_weight_tag).value; 516 int pcl_stroke_weight; 517 if (stroke_weight < 128) 518 pcl_stroke_weight = -3; 519 else if (stroke_weight == 128) 520 pcl_stroke_weight = 0; 521 else if (stroke_weight <= 145) 522 pcl_stroke_weight = 1; 523 else if (stroke_weight <= 179) 524 pcl_stroke_weight = 3; 525 else 526 pcl_stroke_weight = 4; 527 printf("pclweight %d\n", pcl_stroke_weight); 528} 529 530static 531void output_pclproportional() 532{ 533 require_tag(spacing_tag); 534 printf("pclproportional %d\n", tag_info(spacing_tag).value == 0); 535} 536 537static 538void read_and_output_pcltypeface(File &f) 539{ 540 printf("pcltypeface "); 541 require_tag(typeface_tag); 542 f.seek(tag_info(typeface_tag).value); 543 for (uint32 i = 0; i < tag_info(typeface_tag).count; i++) { 544 unsigned char c = f.get_byte(); 545 if (c == '\0') 546 break; 547 putchar(c); 548 } 549 printf("\n"); 550} 551 552static 553void output_pclstyle() 554{ 555 unsigned pcl_style = 0; 556 // older tfms don't have the posture tag 557 if (tag_info(posture_tag).present) { 558 if (tag_info(posture_tag).value) 559 pcl_style |= 1; 560 } 561 else { 562 require_tag(slant_tag); 563 if (tag_info(slant_tag).value != 0) 564 pcl_style |= 1; 565 } 566 require_tag(appearance_width_tag); 567 if (tag_info(appearance_width_tag).value < 100) // guess 568 pcl_style |= 4; 569 printf("pclstyle %d\n", pcl_style); 570} 571 572static 573void output_slant() 574{ 575 require_tag(slant_tag); 576 int slant = int16(tag_info(slant_tag).value); 577 if (slant != 0) 578 printf("slant %f\n", slant/100.0); 579} 580 581static 582void output_ligatures() 583{ 584 // don't use ligatures for fixed space font 585 require_tag(spacing_tag); 586 if (tag_info(spacing_tag).value != 0) 587 return; 588 static const char *ligature_names[] = { 589 "fi", "fl", "ff", "ffi", "ffl" 590 }; 591 592 static const char *ligature_chars[] = { 593 "fi", "fl", "ff", "Fi", "Fl" 594 }; 595 596 unsigned ligature_mask = 0; 597 unsigned int i; 598 for (i = 0; i < nchars; i++) { 599 uint16 msl = char_table[i].msl; 600 if (msl < msl_name_table_size 601 && char_table[i].symbol_set != NO_SYMBOL_SET) { 602 for (name_list *p = msl_name_table[msl]; p; p = p->next) 603 for (unsigned int j = 0; j < SIZEOF(ligature_chars); j++) 604 if (strcmp(p->name, ligature_chars[j]) == 0) { 605 ligature_mask |= 1 << j; 606 break; 607 } 608 } 609 } 610 if (ligature_mask) { 611 printf("ligatures"); 612 for (i = 0; i < SIZEOF(ligature_names); i++) 613 if (ligature_mask & (1 << i)) 614 printf(" %s", ligature_names[i]); 615 printf(" 0\n"); 616 } 617} 618 619static 620void read_and_output_kernpairs(File &f) 621{ 622 if (tag_info(pair_kern_tag).present) { 623 printf("kernpairs\n"); 624 f.seek(tag_info(pair_kern_tag).value); 625 uint16 n_pairs = f.get_uint16(); 626 for (int i = 0; i < n_pairs; i++) { 627 uint16 i1 = f.get_uint16(); 628 uint16 i2 = f.get_uint16(); 629 int16 val = int16(f.get_uint16()); 630 if (char_table[i1].symbol_set != NO_SYMBOL_SET 631 && char_table[i2].symbol_set != NO_SYMBOL_SET 632 && char_table[i1].msl < msl_name_table_size 633 && char_table[i2].msl < msl_name_table_size) { 634 for (name_list *p = msl_name_table[char_table[i1].msl]; 635 p; 636 p = p->next) 637 for (name_list *q = msl_name_table[char_table[i2].msl]; 638 q; 639 q = q->next) 640 printf("%s %s %d\n", p->name, q->name, scale(val)); 641 } 642 } 643 } 644} 645 646static 647void output_charset() 648{ 649 require_tag(slant_tag); 650 double slant_angle = int16(tag_info(slant_tag).value)*PI/18000.0; 651 double slant = sin(slant_angle)/cos(slant_angle); 652 653 require_tag(x_height_tag); 654 require_tag(lower_ascent_tag); 655 require_tag(lower_descent_tag); 656 657 printf("charset\n"); 658 unsigned int i; 659 for (i = 0; i < nchars; i++) { 660 uint16 msl = char_table[i].msl; 661 if (msl < msl_name_table_size 662 && msl_name_table[msl]) { 663 if (char_table[i].symbol_set != NO_SYMBOL_SET) { 664 printf("%s\t%d,%d", 665 msl_name_table[msl]->name, 666 scale(char_table[i].width), 667 scale(char_table[i].ascent)); 668 int depth = scale(- char_table[i].descent); 669 if (depth < 0) 670 depth = 0; 671 int italic_correction = 0; 672 int left_italic_correction = 0; 673 int subscript_correction = 0; 674 if (italic_flag) { 675 italic_correction = scale(char_table[i].right_extent 676 - char_table[i].width 677 + italic_sep); 678 if (italic_correction < 0) 679 italic_correction = 0; 680 subscript_correction = int((tag_info(x_height_tag).value 681 * slant * .8) + .5); 682 if (subscript_correction > italic_correction) 683 subscript_correction = italic_correction; 684 left_italic_correction = scale(italic_sep 685 - char_table[i].left_extent); 686 } 687 if (subscript_correction != 0) 688 printf(",%d,%d,%d,%d", 689 depth, italic_correction, left_italic_correction, 690 subscript_correction); 691 else if (left_italic_correction != 0) 692 printf(",%d,%d,%d", depth, italic_correction, left_italic_correction); 693 else if (italic_correction != 0) 694 printf(",%d,%d", depth, italic_correction); 695 else if (depth != 0) 696 printf(",%d", depth); 697 // This is fairly arbitrary. Fortunately it doesn't much matter. 698 unsigned type = 0; 699 if (char_table[i].ascent > (int16(tag_info(lower_ascent_tag).value)*9)/10) 700 type |= 2; 701 if (char_table[i].descent < (int16(tag_info(lower_descent_tag).value)*9)/10) 702 type |= 1; 703 printf("\t%d\t%d\n", 704 type, 705 char_table[i].symbol_set*256 + char_table[i].code); 706 for (name_list *p = msl_name_table[msl]->next; p; p = p->next) 707 printf("%s\t\"\n", p->name); 708 } 709 else 710 warning("MSL %1 not in any of the searched symbol sets", msl); 711 } 712 } 713} 714 715static 716void dump_tags(File &f) 717{ 718 int i; 719 for (i = min_tag; i <= max_tag; i++) { 720 enum tag_type t = tag_type(i); 721 if (tag_info(t).present) { 722 fprintf(stderr, 723 "%d %d %d %d\n", i, tag_info(t).type, tag_info(t).count, 724 tag_info(t).value); 725 if (tag_info(t).type == FLOAT_TYPE 726 && tag_info(t).count == 1) { 727 f.seek(tag_info(t).value); 728 uint32 num = f.get_uint32(); 729 uint32 den = f.get_uint32(); 730 fprintf(stderr, "(%u/%u = %g)\n", num, den, (double)num/den); 731 } 732 } 733 } 734} 735 736static 737int read_map(const char *file) 738{ 739 errno = 0; 740 FILE *fp = fopen(file, "r"); 741 if (!fp) { 742 error("can't open `%1': %2", file, strerror(errno)); 743 return 0; 744 } 745 current_filename = file; 746 char buf[512]; 747 current_lineno = 0; 748 while (fgets(buf, int(sizeof(buf)), fp)) { 749 current_lineno++; 750 char *ptr = buf; 751 while (csspace(*ptr)) 752 ptr++; 753 if (*ptr == '\0' || *ptr == '#') 754 continue; 755 ptr = strtok(ptr, " \n\t"); 756 if (!ptr) 757 continue; 758 int n; 759 if (sscanf(ptr, "%d", &n) != 1) { 760 error("bad map file"); 761 fclose(fp); 762 return 0; 763 } 764 if (n < 0) { 765 error("negative code"); 766 fclose(fp); 767 return 0; 768 } 769 if ((size_t)n >= msl_name_table_size) { 770 size_t old_size = msl_name_table_size; 771 name_list **old_table = msl_name_table; 772 msl_name_table_size = n + 256; 773 msl_name_table = new name_list *[msl_name_table_size]; 774 if (old_table) { 775 memcpy(msl_name_table, old_table, old_size*sizeof(name_list *)); 776 a_delete old_table; 777 } 778 for (size_t i = old_size; i < msl_name_table_size; i++) 779 msl_name_table[i] = 0; 780 } 781 ptr = strtok(0, " \n\t"); 782 if (!ptr) { 783 error("missing names"); 784 fclose(fp); 785 return 0; 786 } 787 for (; ptr; ptr = strtok(0, " \n\t")) 788 msl_name_table[n] = new name_list(ptr, msl_name_table[n]); 789 } 790 fclose(fp); 791 return 1; 792} 793 794static 795const char *xbasename(const char *s) 796{ 797 // DIR_SEPS[] are possible directory separator characters, see 798 // nonposix.h. We want the rightmost separator of all possible 799 // ones. Example: d:/foo\\bar. 800 const char *b = strrchr(s, DIR_SEPS[0]), *b1; 801 const char *sep = &DIR_SEPS[1]; 802 803 while (*sep) 804 { 805 b1 = strrchr(s, *sep); 806 if (b1 && (!b || b1 > b)) 807 b = b1; 808 sep++; 809 } 810 return b ? b + 1 : s; 811} 812