1 /*@z38.c:Character Mappings:Declarations@*************************************/
2 /*                                                                           */
3 /*  THE LOUT DOCUMENT FORMATTING SYSTEM (VERSION 3.39)                       */
4 /*  COPYRIGHT (C) 1991, 2008 Jeffrey H. Kingston                             */
5 /*                                                                           */
6 /*  Jeffrey H. Kingston (jeff@it.usyd.edu.au)                                */
7 /*  School of Information Technologies                                       */
8 /*  The University of Sydney 2006                                            */
9 /*  AUSTRALIA                                                                */
10 /*                                                                           */
11 /*  This program is free software; you can redistribute it and/or modify     */
12 /*  it under the terms of the GNU General Public License as published by     */
13 /*  the Free Software Foundation; either Version 3, or (at your option)      */
14 /*  any later version.                                                       */
15 /*                                                                           */
16 /*  This program is distributed in the hope that it will be useful,          */
17 /*  but WITHOUT ANY WARRANTY; without even the implied warranty of           */
18 /*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            */
19 /*  GNU General Public License for more details.                             */
20 /*                                                                           */
21 /*  You should have received a copy of the GNU General Public License        */
22 /*  along with this program; if not, write to the Free Software              */
23 /*  Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA   */
24 /*                                                                           */
25 /*  FILE:         z38.c                                                      */
26 /*  MODULE:       Character Mappings                                         */
27 /*  EXTERNS:      MapLoad(), MapCharEncoding(), MapEncodingName(),           */
28 /*                MapPrintEncodings(), MapPrintResources(), MapSmallCaps()   */
29 /*                                                                           */
30 /*****************************************************************************/
31 #include "externs.h"
32 #define MAX_MAP		 20		/* max number of lcm files           */
33 
34 /*****************************************************************************/
35 /*                                                                           */
36 /*  Should really be private but have been placed in externs because for     */
37 /*  efficiency they are used by z37.c and z34.c                              */
38 /*                                                                           */
39 /*  #define   MAX_CHASH      353                                             */
40 /*  #define   MAP_UPPERCASE    0                                             */
41 /*  #define   MAP_LOWERCASE    1                                             */
42 /*  #define   MAP_UNACCENTED   2                                             */
43 /*  #define   MAP_ACCENT       3                                             */
44 /*  #define   MAPS             4                                             */
45 /*                                                                           */
46 /*  typedef struct mapvec {                                                  */
47 /*    OBJECT      file_name;                                                 */
48 /*    FILE_NUM    fnum;                                                      */
49 /*    BOOLEAN     seen_recoded;                                              */
50 /*    int	  last_page_printed;                                         */
51 /*    OBJECT      name;                                                      */
52 /*    OBJECT      vector[MAX_CHARS];                                         */
53 /*    FULL_CHAR   hash_table[MAX_CHASH];                                     */
54 /*    FULL_CHAR   map[MAPS][MAX_CHARS];                                      */
55 /*  } *MAP_VEC;                                                              */
56 /*                                                                           */
57 /*****************************************************************************/
58 
59 MAP_VEC	MapTable[MAX_MAP];		/* the mappings                      */
60 
61 static	OBJECT	notdef_word;		/* notdef word                       */
62 static	int	maptop;			/* first free slot in MapTable[]     */
63 					/* save 0 for "no mapping"           */
64 
65 
66 /*****************************************************************************/
67 /*                                                                           */
68 /*  void MapInit(void)                                                       */
69 /*                                                                           */
70 /*  Initialize this module.                                                  */
71 /*                                                                           */
72 /*****************************************************************************/
73 
MapInit(void)74 void MapInit(void)
75 {
76   notdef_word = nilobj;
77   maptop = 1;
78 }
79 
80 
81 /*****************************************************************************/
82 /*                                                                           */
83 /*  static int NameInsert(cname)                                             */
84 /*  static FULL_CHAR NameRetrieve(cname)                                     */
85 /*                                                                           */
86 /*****************************************************************************/
87 
88 #define hash(str, pos)							\
89 { FULL_CHAR *p = str;							\
90   for( pos = 2 * *p++;  *p;  pos += *p++);				\
91   pos = pos % MAX_CHASH;						\
92 }
93 
NameInsert(FULL_CHAR * cname,int ccode,MAP_VEC map)94 static void NameInsert(FULL_CHAR *cname, int ccode, MAP_VEC map)
95 { int pos;
96   hash(cname, pos);
97   while( map->hash_table[pos] != (FULL_CHAR) '\0' )
98     pos = (pos + 1) % MAX_CHASH;
99   map->vector[ccode] = MakeWord(WORD, cname, no_fpos);
100   map->hash_table[pos] = ccode;
101 } /* end NameInsert */
102 
NameRetrieve(FULL_CHAR * cname,MAP_VEC map)103 static FULL_CHAR NameRetrieve(FULL_CHAR *cname, MAP_VEC map)
104 { int pos;  FULL_CHAR ch;
105   hash(cname, pos);
106   while( (ch = map->hash_table[pos]) != (FULL_CHAR) '\0' )
107   {
108     if( StringEqual(string(map->vector[ch]), cname) )
109       return ch;
110     pos = (pos + 1) % MAX_CHASH;
111   }
112   return ch;
113 } /* end NameRetrieve */
114 
115 
116 /*@::MapLoad()@***************************************************************/
117 /*                                                                           */
118 /*  MAPPING MapLoad(file_name, recoded)                                      */
119 /*                                                                           */
120 /*  Declare file_name to be a character mapping (LCM) file.  A file may be   */
121 /*  so declared more than once.  Parameter recoded is true if the font that  */
122 /*  uses this mapping declares that it needs to be recoded, which in turn    */
123 /*  means that this mapping might have to be printed out.  Whether or not it */
124 /*  is actually printed depends upon whether we print a font that uses it    */
125 /*  and that requires recoding.                                              */
126 /*                                                                           */
127 /*****************************************************************************/
128 
MapLoad(OBJECT file_name,BOOLEAN recoded)129 MAPPING MapLoad(OBJECT file_name, BOOLEAN recoded)
130 { FILE *fp;  MAP_VEC map;  MAPPING res;
131   int i, m, curr_line_num, line_pos, prev_code, dc, count;
132   unsigned int oc;
133   int status;
134   FULL_CHAR buff[MAX_BUFF], cn[MAX_BUFF], ch, mapname[MAX_BUFF],
135   mapval[MAX_BUFF];
136   debug2(DCM,D, "MapLoad(%s, %s)", EchoObject(file_name), bool(recoded));
137 
138   /* if the file name is "-", it means no mapping file is supplied */
139   if( StringEqual(string(file_name), AsciiToFull("-")) )
140   { debug1(DCM, D, "MapLoad returning 0 (file name is %s)",
141       string(file_name));
142     return (MAPPING) 0;
143   }
144 
145   /* if seen this file name before, just update seen_recoded and return prev */
146   for( res = 1;  res < maptop;  res++ )
147   {
148     if( StringEqual(string(MapTable[res]->file_name), string(file_name)) )
149     { Dispose(file_name);
150       MapTable[res]->seen_recoded = MapTable[res]->seen_recoded || recoded;
151       debug1(DCM, D, "MapLoad returning %d (not new)", res);
152       return res;
153     }
154   }
155 
156   /* initialize PostScript name of all undefined characters */
157   if( notdef_word == nilobj )
158     notdef_word = MakeWord(WORD, AsciiToFull(".notdef"), no_fpos);
159 
160   /* new, so allocate a new slot in MapTable for this new mapping */
161   if( maptop == MAX_MAP )
162     Error(38, 1, "too many character mappings", FATAL, &fpos(file_name));
163   ifdebug(DMA, D, DebugRegisterUsage(MEM_CMAPS, 1, sizeof(struct mapvec)));
164   MapTable[res = maptop++] = map = (MAP_VEC) malloc( sizeof(struct mapvec) );
165   if( map == (MAP_VEC) NULL )
166     Error(38, 2, "run out of memory when loading character mapping",
167       FATAL, &fpos(file_name));
168 
169   /* initialize all the fields */
170   map->file_name = file_name;
171   debug0(DFS, D, "  calling DefineFile from MapLoad");
172   map->fnum = DefineFile(string(file_name), STR_EMPTY, &fpos(file_name),
173     MAPPING_FILE, MAPPING_PATH);
174   fp = OpenFile(map->fnum, FALSE, FALSE);
175   if( fp == NULL )  Error(38, 3, "cannot open character mapping file %s",
176       FATAL, PosOfFile(map->fnum), FileName(map->fnum));
177   map->seen_recoded = recoded;
178   map->last_page_printed = 0;
179   StringCopy(buff, AsciiToFull("vec"));
180   StringCat(buff, StringInt(maptop));
181   map->name = MakeWord(WORD, buff, no_fpos);
182   for( m = 0;  m < MAPS;  m++ )
183   { for( i = 0;  i < MAX_CHARS;  i++ )
184       map->map[m][i] = '\0';
185   }
186 
187   /* unaccented map is defined to be self as default */
188   for( i = 0;  i < MAX_CHARS;  i++ )
189     map->map[MAP_UNACCENTED][i] = i;
190 
191   for( i = 0;  i < MAX_CHARS; i++ )  map->vector[i] = notdef_word;
192   for( i = 0;  i < MAX_CHASH; i++ )  map->hash_table[i] = 0;
193 
194   /* first pass through the file; read character codes and names only */
195   prev_code = -1;  curr_line_num = 0;
196   while( (status = ReadOneLine(fp, buff, MAX_BUFF)) != 0 )
197   {
198     /* skip comment lines and blank lines */
199     curr_line_num++;
200     for( i = 0;  buff[i] == ' ' || buff[i] == '\t';  i++ );
201     if( buff[i] == '#' || buff[i] == '\0' )  continue;
202 
203     /* parse line and check validity of decimal and octal character codes */
204     count = sscanf( (char *) buff, "%d %o %s", &dc, &oc, cn);
205     if( count < 2 )
206       Error(38, 4, "character code(s) missing in mapping file (line %d)",
207 	FATAL, &fpos(file_name), curr_line_num);
208     if( dc != oc )
209       Error(38, 5, "decimal and octal codes disagree in mapping file (line %d)",
210 	FATAL, &fpos(file_name), curr_line_num);
211     if( dc < 1 && !StringEqual(cn, STR_NOCHAR) )
212       Error(38, 6, "code %d too small (min is 1) in mapping file (line %d)",
213 	FATAL, &fpos(file_name), dc, curr_line_num);
214     if( dc < prev_code )
215       Error(38, 7, "code %d out of order in mapping file (line %d)",
216 	FATAL, &fpos(file_name), dc, curr_line_num);
217     if( dc == prev_code )
218       Error(38, 8, "code %d repeated in mapping file (line %d)",
219 	FATAL, &fpos(file_name), dc, curr_line_num);
220     if( dc > MAX_CHARS )
221       Error(38, 9, "code %d too large (max is %d) in mapping file (line %d)",
222 	FATAL, &fpos(file_name), dc, MAX_CHARS, curr_line_num);
223     prev_code = dc;
224 
225     /* insert character name, if any */
226     debug2(DCM, DD, "  line %d: %s", curr_line_num, cn);
227     if( count >= 3 && !StringEqual(cn, STR_NOCHAR) )
228     {
229       /* insert (cn, dc) pair into hash table; name may be repeated */
230       if( (ch = NameRetrieve(cn, map)) != 0 )
231 	map->vector[dc] = map->vector[ch];
232       else
233 	NameInsert(cn, dc, map);
234     }
235   }
236 
237   /* second pass through the file: read mappings */
238   rewind(fp);
239   curr_line_num = 0;
240   while( (status = ReadOneLine(fp, buff, MAX_BUFF)) != 0 )
241   {
242     /* skip comment lines and blank lines */
243     curr_line_num++;
244     for( i = 0;  buff[i] == ' ' || buff[i] == '\t';  i++ );
245     if( buff[i] == '#' || buff[i] == '\0' )  continue;
246 
247     /* parse line */
248     count = sscanf( (char *) buff, "%d %o %s%n", &dc, &oc, cn, &line_pos);
249 
250     /* find and insert the maps */
251     while( sscanf( (char *) &buff[line_pos], "%s %[^;];%n",
252       mapname, mapval, &i) == 2 )
253     {
254       debug3(DCM, DD, "  line %d: %s %s", curr_line_num, mapname, mapval);
255       line_pos += i;
256       if( StringEqual(mapname, AsciiToFull("UC")) )
257 	m = MAP_UPPERCASE;
258       else if( StringEqual(mapname, AsciiToFull("LC")) )
259 	m = MAP_LOWERCASE;
260       else if( StringEqual(mapname, AsciiToFull("UA")) )
261 	m = MAP_UNACCENTED;
262       else if( StringEqual(mapname, AsciiToFull("AC")) )
263 	m = MAP_ACCENT;
264       else
265 	Error(38, 10, "unknown mapping name %s in mapping file %s (line %d)",
266 	  FATAL, &fpos(file_name), mapname, FileName(map->fnum), curr_line_num);
267       ch = NameRetrieve(mapval, map);
268       if( ch == (FULL_CHAR) '\0' )
269 	Error(38, 11, "unknown character %s in mapping file %s (line %d)",
270 	  FATAL, &fpos(file_name), mapval, FileName(map->fnum), curr_line_num);
271       map->map[m][dc] = ch;
272     }
273   }
274   fclose(fp);
275   debug1(DCM, D, "MapLoad returning %d (new mapping)", res);
276   return res;
277 } /* end MapLoad */
278 
279 
280 /*@::MapCharEncoding(), MapEncodingName(), MapPrintEncodings()@***************/
281 /*                                                                           */
282 /*  FULL_CHAR MapCharEncoding(str, map)                                      */
283 /*                                                                           */
284 /*  Returns the character code corresponding to character name str in        */
285 /*  MAPPING enc, or 0 if not found.                                          */
286 /*                                                                           */
287 /*****************************************************************************/
288 
MapCharEncoding(FULL_CHAR * str,MAPPING m)289 FULL_CHAR MapCharEncoding(FULL_CHAR *str, MAPPING m)
290 { MAP_VEC map;
291   map = MapTable[m];
292   return (FULL_CHAR) NameRetrieve(str, map);
293 } /* end MapCharEncoding */
294 
295 
296 /*****************************************************************************/
297 /*                                                                           */
298 /*  FULL_CHAR *MapEncodingName(m)                                            */
299 /*                                                                           */
300 /*  Returns the PostScript name of the encoding vector of mapping m          */
301 /*                                                                           */
302 /*****************************************************************************/
303 
MapEncodingName(MAPPING m)304 FULL_CHAR *MapEncodingName(MAPPING m)
305 { assert( m < maptop, "MapEncodingName: m out of range!" );
306   return string(MapTable[m]->name);
307 } /* end MapEncodingName */
308 
309 
310 /*****************************************************************************/
311 /*                                                                           */
312 /*  void MapEnsurePrinted(MAPPING m, int curr_page)                          */
313 /*                                                                           */
314 /*  Ensure that MAPPING m is printed on page curr_page, if required.         */
315 /*  It's required if it has neither been printed on the current page         */
316 /*  already, nor on page 1 (page 1 is really the entire document setup).     */
317 /*                                                                           */
318 /*****************************************************************************/
319 
MapEnsurePrinted(MAPPING m,int curr_page)320 void MapEnsurePrinted(MAPPING m, int curr_page)
321 { MAP_VEC map = MapTable[m];
322   assert( map->seen_recoded, "MapEnsurePrinted: not seen_recoded!" );
323   if( map->last_page_printed < curr_page && map->last_page_printed != 1 )
324   { map->last_page_printed = curr_page;
325     BackEnd->PrintMapping(m);
326   }
327 }
328 
329 
330 /*****************************************************************************/
331 /*                                                                           */
332 /*  MapPrintEncodings()                                                      */
333 /*                                                                           */
334 /*  Print all encoding vectors in existence so far; this counts as printing  */
335 /*  them on "page 1", but in fact they will appear in the document setup     */
336 /*  section.                                                                 */
337 /*                                                                           */
338 /*****************************************************************************/
339 
MapPrintEncodings()340 void MapPrintEncodings()
341 { MAPPING m;  MAP_VEC map;
342   for( m = 1;  m < maptop;  m++ )
343   { if( MapTable[m]->seen_recoded )
344     { BackEnd->PrintMapping(m);
345       map = MapTable[m];
346       map->last_page_printed = 1;
347     }
348   }
349 } /* end MapPrintEncodings */
350 
351 
352 /*****************************************************************************/
353 /*                                                                           */
354 /*  MapPrintPSResources(fp)                                                  */
355 /*                                                                           */
356 /*  Print PostScript resource entries for all encoding vectors on file fp.   */
357 /*                                                                           */
358 /*****************************************************************************/
359 
MapPrintPSResources(FILE * fp)360 void MapPrintPSResources(FILE *fp)
361 { MAPPING m;  MAP_VEC map;
362   for( m = 1;  m < maptop;  m++ )  if( MapTable[m]->seen_recoded )
363   { map = MapTable[m];
364     fprintf(fp, "%%%%+ encoding %s%s", string(map->name), (char *) STR_NEWLINE);
365   }
366 } /* end MapPrintPSResources */
367 
368 
369 /*@@**************************************************************************/
370 /*                                                                           */
371 /*  OBJECT DoWord(buff, q, x, fnum)                                          */
372 /*                                                                           */
373 /*  Replace WORD or QWORD x by a small caps version, based on word_font(x).  */
374 /*                                                                           */
375 /*****************************************************************************/
376 
DoWord(FULL_CHAR * buff,FULL_CHAR * q,OBJECT x,FONT_NUM fnum)377 static OBJECT DoWord(FULL_CHAR *buff, FULL_CHAR *q, OBJECT x, FONT_NUM fnum)
378 { OBJECT res;
379   *q++ = '\0';
380   res = MakeWord(type(x), buff, &fpos(x));
381   word_font(res) = fnum;
382   word_colour(res) = word_colour(x);
383   word_underline_colour(res) = word_underline_colour(x);
384   word_texture(res) = word_texture(x);
385   word_outline(res) = word_outline(x);
386   word_language(res) = word_language(x);
387   word_baselinemark(res) = word_baselinemark(x);
388   word_strut(res) = word_strut(x);
389   word_ligatures(res) = word_ligatures(x);
390   word_hyph(res) = word_hyph(x);
391   underline(res) = UNDER_OFF;
392   return res;
393 } /* end DoWord */
394 
395 
396 /*****************************************************************************/
397 /*                                                                           */
398 /*  OBJECT DoVShift(x, vshift, chld)                                         */
399 /*                                                                           */
400 /*  Make an new VSHIFT object with the given shift and child.                */
401 /*                                                                           */
402 /*****************************************************************************/
403 
DoVShift(OBJECT x,FULL_LENGTH vshift,OBJECT chld)404 static OBJECT DoVShift(OBJECT x, FULL_LENGTH vshift, OBJECT chld)
405 { OBJECT res;
406   New(res, VSHIFT);
407   FposCopy(fpos(res), fpos(x));
408   shift_type(res) = GAP_DEC;
409   units(shift_gap(res)) = FIXED_UNIT;
410   mode(shift_gap(res)) = EDGE_MODE;
411   width(shift_gap(res)) = vshift;
412   underline(res) = UNDER_OFF;
413   Link(res, chld);
414   return res;
415 }
416 
417 /*****************************************************************************/
418 /*                                                                           */
419 /*  void DoAddGap(new_acat)                                                  */
420 /*                                                                           */
421 /*  Add a new 0i gap object to new_acat.                                     */
422 /*                                                                           */
423 /*****************************************************************************/
424 
DoAddGap(OBJECT new_acat)425 static void DoAddGap(OBJECT new_acat)
426 { OBJECT new_g;
427   New(new_g, GAP_OBJ);
428   FposCopy(fpos(new_g), fpos(new_acat));
429   hspace(new_g) = vspace(new_g) = 0;
430   SetGap(gap(new_g), TRUE, FALSE, TRUE, FIXED_UNIT, EDGE_MODE, 0*IN);
431   underline(new_g) = UNDER_OFF;
432   Link(new_acat, new_g);
433 }
434 
435 /*@::MapSmallCaps()@**********************************************************/
436 /*                                                                           */
437 /*  OBJECT MapSmallCaps(x, style)                                            */
438 /*                                                                           */
439 /*  Replace WORD or QWORD x by a small caps version, based on word_font(x).  */
440 /*                                                                           */
441 /*****************************************************************************/
442 #define	INIT		0
443 #define	ALL_NON		1
444 #define	ALL_TRANS	2
445 #define	MIXED_NON	3
446 #define	MIXED_TRANS	4
447 #define transformable(ch)	(uc[ch] != '\0')
448 
449 /* basically temporaries but remembered from call to call for recycling */
450 static OBJECT		font_change_word = nilobj;
451 static FULL_LENGTH	font_change_length = 0;
452 
MapSmallCaps(OBJECT x,STYLE * style)453 OBJECT MapSmallCaps(OBJECT x, STYLE *style)
454 { MAPPING m;  int i;  OBJECT new_y, new_x = nilobj, new_acat = nilobj, tmp;
455   FULL_CHAR buff[MAX_BUFF], *uc, *p, *q;
456   FONT_NUM small_font = 0;  FULL_LENGTH vshift = 0;  int state;  STYLE new_style;
457   assert( is_word(type(x)), "MapSmallCaps: !is_word(type(x))" );
458   debug2(DCM, D, "MapSmallCaps(%s %s)", Image(type(x)), string(x));
459 
460   /* get the mapping and return if there isn't one for this font */
461   m = FontMapping(word_font(x), &fpos(x));
462   if( m == 0 )
463   { debug0(DCM, D, "MapSmallCaps returning unchanged (mapping is 0)");
464     return x;
465   }
466   assert( 1 <= m && m < maptop, "MapSmallCaps: mapping out of range!" );
467   uc = MapTable[m]->map[MAP_UPPERCASE];
468 
469   /* if plain text, apply the mapping and exit */
470   if( !(BackEnd->scale_avail) )
471   {
472     for( i = 0;  string(x)[i] != '\0';  i++ )
473       if( uc[string(x)[i]] != '\0' )
474         string(x)[i] = uc[string(x)[i]];
475     debug1(DCM, D, "MapSmallCaps returning (plain text) %s", EchoObject(x));
476     return x;
477   }
478 
479   /* make sure the small caps size is a reasonable one */
480   if( smallcaps_len(*style) <= 0 )
481     Error(38, 12, "small caps size is zero or negative", FATAL, &fpos(x));
482 
483   /* set up the font change word if not already done */
484   if( font_change_length != smallcaps_len(*style) )
485   { char tmp[100];
486     font_change_length = smallcaps_len(*style);
487     sprintf(tmp, "%.2ff", (float) font_change_length / FR);
488     font_change_word = MakeWord(WORD, AsciiToFull(tmp), no_fpos);
489   }
490 
491   state = INIT;  q = buff;
492   for( p = string(x);  *p != '\0';  p++ )
493   {
494     debug2(DCM, DD, " examining %c (%s)", *p,
495       transformable(*p) ? "transformable" : "not transformable");
496     switch( state )
497     {
498       case INIT:
499 
500         /* this state is for when we are at the first character */
501         if( transformable(*p) )
502         { *q++ = uc[*p];
503 
504 	  /* work out what the smaller font is going to be, and the vshift */
505 	  StyleCopy(new_style, *style);
506 	  FontChange(&new_style, font_change_word);
507 	  small_font = font(new_style);
508 	  vshift = word_baselinemark(x) ? 0 :
509 	    (FontHalfXHeight(word_font(x)) - FontHalfXHeight(small_font));
510 
511           state = ALL_TRANS;
512         }
513         else
514         { *q++ = *p;
515           state = ALL_NON;
516         }
517         break;
518 
519 
520       case ALL_NON:
521 
522         /* in this state, all characters so far are non-transformable */
523         if( transformable(*p) )
524         {
525 	  /* work out what the smaller font is going to be */
526 	  StyleCopy(new_style, *style);
527 	  FontChange(&new_style, font_change_word);
528 	  small_font = font(new_style);
529 	  vshift = word_baselinemark(x) ? 0 :
530 	    (FontHalfXHeight(word_font(x)) - FontHalfXHeight(small_font));
531 
532 	  /* make a new WORD out of the current contents of buff */
533 	  new_y = DoWord(buff, q, x, word_font(x));
534 
535 	  /* construct the skeleton of the result to replace x */
536 	  New(new_x, ONE_COL);
537 	  FposCopy(fpos(new_x), fpos(x));
538 	  New(new_acat, ACAT);
539 	  FposCopy(fpos(new_acat), fpos(x));
540 	  Link(new_x, new_acat);
541 	  Link(new_acat, new_y);
542 	  DoAddGap(new_acat);
543 
544 	  /* start off a new buffer with *p */
545 	  q = buff;
546 	  *q++ = uc[*p];
547 	  state = MIXED_TRANS;
548         }
549         else *q++ = *p;
550         break;
551 
552 
553       case ALL_TRANS:
554 
555         /* in this state, all characters so far are transformable */
556         if( transformable(*p) ) *q++ = uc[*p];
557         else
558         {
559 	  /* make a new @VShift WORD out of the current contents of buff */
560 	  tmp = DoWord(buff, q, x, small_font);
561 	  new_y = DoVShift(x, vshift, tmp);
562 
563 	  /* construct the skeleton of the result to replace x */
564 	  New(new_x, ONE_COL);
565 	  FposCopy(fpos(new_x), fpos(x));
566 	  New(new_acat, ACAT);
567 	  FposCopy(fpos(new_acat), fpos(x));
568 	  Link(new_x, new_acat);
569 	  Link(new_acat, new_y);
570 	  DoAddGap(new_acat);
571 
572 	  /* start off a new buffer with *p */
573 	  q = buff;
574 	  *q++ = *p;
575 	  state = MIXED_NON;
576         }
577         break;
578 
579 
580       case MIXED_NON:
581 
582         /* in this state the previous char was non-transformable, but */
583         /* there have been characters before that that were transformable */
584         if( transformable(*p) )
585         {
586 	  /* make a new WORD out of the current contents of buff */
587 	  new_y = DoWord(buff, q, x, word_font(x));
588 
589 	  /* link the new word into the growing structure that replaces x */
590 	  Link(new_acat, new_y);
591 	  DoAddGap(new_acat);
592 
593 	  /* start off a new buffer with *p */
594 	  q = buff;
595 	  *q++ = uc[*p];
596 	  state = MIXED_TRANS;
597         }
598         else *q++ = *p;
599         break;
600 
601 
602       case MIXED_TRANS:
603 
604         /* in this state the previous char was transformable, but there */
605         /* have been characters before that that were non-transformable */
606         if( transformable(*p) ) *q++ = uc[*p];
607         else
608         {
609 	  /* make a new @VShift WORD out of the current contents of buff */
610 	  tmp = DoWord(buff, q, x, small_font);
611 	  new_y = DoVShift(x, vshift, tmp);
612 
613 	  /* link the new word into the growing structure that replaces x */
614 	  Link(new_acat, new_y);
615 	  DoAddGap(new_acat);
616 
617 	  /* start off a new buffer with *p */
618 	  q = buff;
619 	  *q++ = *p;
620 	  state = MIXED_NON;
621         }
622         break;
623 
624     }
625   }
626 
627   /* now at termination, clean up the structure */
628   switch( state )
629   {
630     case INIT:
631     case ALL_NON:
632 
633       /* original x is OK as is: either empty or all non-transformable */
634       break;
635 
636 
637     case ALL_TRANS:
638 
639       /* make a new @VShift WORD and replace x with it */
640       tmp = DoWord(buff, q, x, small_font);
641       new_x = DoVShift(x, vshift, tmp);
642       ReplaceNode(new_x, x);
643       Dispose(x);
644       x = new_x;
645       break;
646 
647 
648     case MIXED_NON:
649 
650       /* make a new WORD, add to new_acat, and replace x */
651       new_y = DoWord(buff, q, x, word_font(x));
652       Link(new_acat, new_y);
653       ReplaceNode(new_x, x);
654       Dispose(x);
655       x = new_x;
656       break;
657 
658 
659     case MIXED_TRANS:
660 
661       /* make a new @VShift WORD, add to new_acat, and replace x */
662       tmp = DoWord(buff, q, x, small_font);
663       new_y = DoVShift(x, vshift, tmp);
664       Link(new_acat, new_y);
665       ReplaceNode(new_x, x);
666       Dispose(x);
667       x = new_x;
668       break;
669   }
670   debug1(DCM, D, "MapSmallCaps returning %s", EchoObject(x));
671   return x;
672 } /* end MapSmallCaps */
673 
674 
675 /*****************************************************************************/
676 /*                                                                           */
677 /*  BOOLEAN MapIsLowerCase(FULL_CHAR ch, MAPPING m)                          */
678 /*                                                                           */
679 /*  Returns TRUE if ch is a lower-case character in mapping m; i.e. if it    */
680 /*  has a corresponding upper-case character.                                */
681 /*                                                                           */
682 /*****************************************************************************/
683 
MapIsLowerCase(FULL_CHAR ch,MAPPING m)684 BOOLEAN MapIsLowerCase(FULL_CHAR ch, MAPPING m)
685 { BOOLEAN res;
686   debug2(DCM, D, "MapIsLowerCase(%c, %d)", ch, m);
687   res = (MapTable[m]->map[MAP_UPPERCASE][ch] != '\0');
688   debug1(DCM, D, "MapIsLowerCase returning %s", bool(res));
689   return res;
690 } /* end MapIsLowerCase */
691