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