/*@z38.c:Character Mappings:Declarations@*************************************/ /* */ /* THE LOUT DOCUMENT FORMATTING SYSTEM (VERSION 3.41) */ /* COPYRIGHT (C) 1991, 2023 Jeffrey H. Kingston */ /* */ /* Jeffrey H. Kingston (jeff@it.usyd.edu.au) */ /* School of Information Technologies */ /* The University of Sydney 2006 */ /* AUSTRALIA */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ /* the Free Software Foundation; either Version 3, or (at your option) */ /* any later version. */ /* */ /* This program is distributed in the hope that it will be useful, */ /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ /* GNU General Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License */ /* along with this program; if not, write to the Free Software */ /* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA */ /* */ /* FILE: z38.c */ /* MODULE: Character Mappings */ /* EXTERNS: MapLoad(), MapCharEncoding(), MapEncodingName(), */ /* MapPrintEncodings(), MapPrintResources(), MapSmallCaps() */ /* */ /*****************************************************************************/ #include "externs.h" #define MAX_MAP 20 /* max number of lcm files */ /*****************************************************************************/ /* */ /* Should really be private but have been placed in externs because for */ /* efficiency they are used by z37.c and z34.c */ /* */ /* #define MAX_CHASH 353 */ /* #define MAP_UPPERCASE 0 */ /* #define MAP_LOWERCASE 1 */ /* #define MAP_UNACCENTED 2 */ /* #define MAP_ACCENT 3 */ /* #define MAPS 4 */ /* */ /* typedef struct mapvec { */ /* OBJECT file_name; */ /* FILE_NUM fnum; */ /* BOOLEAN seen_recoded; */ /* int last_page_printed; */ /* OBJECT name; */ /* OBJECT vector[MAX_CHARS]; */ /* FULL_CHAR hash_table[MAX_CHASH]; */ /* FULL_CHAR map[MAPS][MAX_CHARS]; */ /* } *MAP_VEC; */ /* */ /*****************************************************************************/ MAP_VEC MapTable[MAX_MAP]; /* the mappings */ static OBJECT notdef_word; /* notdef word */ static int maptop; /* first free slot in MapTable[] */ /* save 0 for "no mapping" */ /*****************************************************************************/ /* */ /* void MapInit(void) */ /* */ /* Initialize this module. */ /* */ /*****************************************************************************/ void MapInit(void) { notdef_word = nilobj; maptop = 1; } /*****************************************************************************/ /* */ /* static int NameInsert(cname) */ /* static FULL_CHAR NameRetrieve(cname) */ /* */ /*****************************************************************************/ #define hash(str, pos) \ { FULL_CHAR *p = str; \ for( pos = 2 * *p++; *p; pos += *p++); \ pos = pos % MAX_CHASH; \ } static void NameInsert(FULL_CHAR *cname, int ccode, MAP_VEC map) { int pos; hash(cname, pos); while( map->hash_table[pos] != (FULL_CHAR) '\0' ) pos = (pos + 1) % MAX_CHASH; map->vector[ccode] = MakeWord(WORD, cname, no_fpos); map->hash_table[pos] = ccode; } /* end NameInsert */ static FULL_CHAR NameRetrieve(FULL_CHAR *cname, MAP_VEC map) { int pos; FULL_CHAR ch; hash(cname, pos); while( (ch = map->hash_table[pos]) != (FULL_CHAR) '\0' ) { if( StringEqual(string(map->vector[ch]), cname) ) return ch; pos = (pos + 1) % MAX_CHASH; } return ch; } /* end NameRetrieve */ /*@::MapLoad()@***************************************************************/ /* */ /* MAPPING MapLoad(file_name, recoded) */ /* */ /* Declare file_name to be a character mapping (LCM) file. A file may be */ /* so declared more than once. Parameter recoded is true if the font that */ /* uses this mapping declares that it needs to be recoded, which in turn */ /* means that this mapping might have to be printed out. Whether or not it */ /* is actually printed depends upon whether we print a font that uses it */ /* and that requires recoding. */ /* */ /*****************************************************************************/ MAPPING MapLoad(OBJECT file_name, BOOLEAN recoded) { FILE *fp; MAP_VEC map; MAPPING res; int i, m, curr_line_num, line_pos, prev_code, dc, count; unsigned int oc; int status; FULL_CHAR buff[MAX_BUFF], cn[MAX_BUFF], ch, mapname[MAX_BUFF], mapval[MAX_BUFF]; debug2(DCM,D, "MapLoad(%s, %s)", EchoObject(file_name), bool(recoded)); /* if the file name is "-", it means no mapping file is supplied */ if( StringEqual(string(file_name), AsciiToFull("-")) ) { debug1(DCM, D, "MapLoad returning 0 (file name is %s)", string(file_name)); return (MAPPING) 0; } /* if seen this file name before, just update seen_recoded and return prev */ for( res = 1; res < maptop; res++ ) { if( StringEqual(string(MapTable[res]->file_name), string(file_name)) ) { Dispose(file_name); MapTable[res]->seen_recoded = MapTable[res]->seen_recoded || recoded; debug1(DCM, D, "MapLoad returning %d (not new)", res); return res; } } /* initialize PostScript name of all undefined characters */ if( notdef_word == nilobj ) notdef_word = MakeWord(WORD, AsciiToFull(".notdef"), no_fpos); /* new, so allocate a new slot in MapTable for this new mapping */ if( maptop == MAX_MAP ) Error(38, 1, "too many character mappings", FATAL, &fpos(file_name)); ifdebug(DMA, D, DebugRegisterUsage(MEM_CMAPS, 1, sizeof(struct mapvec))); MapTable[res = maptop++] = map = (MAP_VEC) malloc( sizeof(struct mapvec) ); if( map == (MAP_VEC) NULL ) Error(38, 2, "run out of memory when loading character mapping", FATAL, &fpos(file_name)); /* initialize all the fields */ map->file_name = file_name; debug0(DFS, D, " calling DefineFile from MapLoad"); map->fnum = DefineFile(string(file_name), STR_EMPTY, &fpos(file_name), MAPPING_FILE, MAPPING_PATH); fp = OpenFile(map->fnum, FALSE, FALSE); if( fp == NULL ) Error(38, 3, "cannot open character mapping file %s", FATAL, PosOfFile(map->fnum), FileName(map->fnum)); map->seen_recoded = recoded; map->last_page_printed = 0; StringCopy(buff, AsciiToFull("vec")); StringCat(buff, StringInt(maptop)); map->name = MakeWord(WORD, buff, no_fpos); for( m = 0; m < MAPS; m++ ) { for( i = 0; i < MAX_CHARS; i++ ) map->map[m][i] = '\0'; } /* unaccented map is defined to be self as default */ for( i = 0; i < MAX_CHARS; i++ ) map->map[MAP_UNACCENTED][i] = i; for( i = 0; i < MAX_CHARS; i++ ) map->vector[i] = notdef_word; for( i = 0; i < MAX_CHASH; i++ ) map->hash_table[i] = 0; /* first pass through the file; read character codes and names only */ prev_code = -1; curr_line_num = 0; while( (status = ReadOneLine(fp, buff, MAX_BUFF)) != 0 ) { /* skip comment lines and blank lines */ curr_line_num++; for( i = 0; buff[i] == ' ' || buff[i] == '\t'; i++ ); if( buff[i] == '#' || buff[i] == '\0' ) continue; /* parse line and check validity of decimal and octal character codes */ count = sscanf( (char *) buff, "%d %o %s", &dc, &oc, cn); if( count < 2 ) Error(38, 4, "character code(s) missing in mapping file (line %d)", FATAL, &fpos(file_name), curr_line_num); if( dc != oc ) Error(38, 5, "decimal and octal codes disagree in mapping file (line %d)", FATAL, &fpos(file_name), curr_line_num); if( dc < 1 && !StringEqual(cn, STR_NOCHAR) ) Error(38, 6, "code %d too small (min is 1) in mapping file (line %d)", FATAL, &fpos(file_name), dc, curr_line_num); if( dc < prev_code ) Error(38, 7, "code %d out of order in mapping file (line %d)", FATAL, &fpos(file_name), dc, curr_line_num); if( dc == prev_code ) Error(38, 8, "code %d repeated in mapping file (line %d)", FATAL, &fpos(file_name), dc, curr_line_num); if( dc > MAX_CHARS ) Error(38, 9, "code %d too large (max is %d) in mapping file (line %d)", FATAL, &fpos(file_name), dc, MAX_CHARS, curr_line_num); prev_code = dc; /* insert character name, if any */ debug2(DCM, DD, " line %d: %s", curr_line_num, cn); if( count >= 3 && !StringEqual(cn, STR_NOCHAR) ) { /* insert (cn, dc) pair into hash table; name may be repeated */ if( (ch = NameRetrieve(cn, map)) != 0 ) map->vector[dc] = map->vector[ch]; else NameInsert(cn, dc, map); } } /* second pass through the file: read mappings */ rewind(fp); curr_line_num = 0; while( (status = ReadOneLine(fp, buff, MAX_BUFF)) != 0 ) { /* skip comment lines and blank lines */ curr_line_num++; for( i = 0; buff[i] == ' ' || buff[i] == '\t'; i++ ); if( buff[i] == '#' || buff[i] == '\0' ) continue; /* parse line */ count = sscanf( (char *) buff, "%d %o %s%n", &dc, &oc, cn, &line_pos); /* find and insert the maps */ while( sscanf( (char *) &buff[line_pos], "%s %[^;];%n", mapname, mapval, &i) == 2 ) { debug3(DCM, DD, " line %d: %s %s", curr_line_num, mapname, mapval); line_pos += i; if( StringEqual(mapname, AsciiToFull("UC")) ) m = MAP_UPPERCASE; else if( StringEqual(mapname, AsciiToFull("LC")) ) m = MAP_LOWERCASE; else if( StringEqual(mapname, AsciiToFull("UA")) ) m = MAP_UNACCENTED; else if( StringEqual(mapname, AsciiToFull("AC")) ) m = MAP_ACCENT; else Error(38, 10, "unknown mapping name %s in mapping file %s (line %d)", FATAL, &fpos(file_name), mapname, FileName(map->fnum), curr_line_num); ch = NameRetrieve(mapval, map); if( ch == (FULL_CHAR) '\0' ) Error(38, 11, "unknown character %s in mapping file %s (line %d)", FATAL, &fpos(file_name), mapval, FileName(map->fnum), curr_line_num); map->map[m][dc] = ch; } } fclose(fp); debug1(DCM, D, "MapLoad returning %d (new mapping)", res); return res; } /* end MapLoad */ /*@::MapCharEncoding(), MapEncodingName(), MapPrintEncodings()@***************/ /* */ /* FULL_CHAR MapCharEncoding(str, map) */ /* */ /* Returns the character code corresponding to character name str in */ /* MAPPING enc, or 0 if not found. */ /* */ /*****************************************************************************/ FULL_CHAR MapCharEncoding(FULL_CHAR *str, MAPPING m) { MAP_VEC map; map = MapTable[m]; return (FULL_CHAR) NameRetrieve(str, map); } /* end MapCharEncoding */ /*****************************************************************************/ /* */ /* FULL_CHAR *MapEncodingName(m) */ /* */ /* Returns the PostScript name of the encoding vector of mapping m */ /* */ /*****************************************************************************/ FULL_CHAR *MapEncodingName(MAPPING m) { assert( m < maptop, "MapEncodingName: m out of range!" ); return string(MapTable[m]->name); } /* end MapEncodingName */ /*****************************************************************************/ /* */ /* void MapEnsurePrinted(MAPPING m, int curr_page) */ /* */ /* Ensure that MAPPING m is printed on page curr_page, if required. */ /* It's required if it has neither been printed on the current page */ /* already, nor on page 1 (page 1 is really the entire document setup). */ /* */ /*****************************************************************************/ void MapEnsurePrinted(MAPPING m, int curr_page) { MAP_VEC map = MapTable[m]; assert( map->seen_recoded, "MapEnsurePrinted: not seen_recoded!" ); if( map->last_page_printed < curr_page && map->last_page_printed != 1 ) { map->last_page_printed = curr_page; BackEnd->PrintMapping(m); } } /*****************************************************************************/ /* */ /* MapPrintEncodings() */ /* */ /* Print all encoding vectors in existence so far; this counts as printing */ /* them on "page 1", but in fact they will appear in the document setup */ /* section. */ /* */ /*****************************************************************************/ void MapPrintEncodings() { MAPPING m; MAP_VEC map; for( m = 1; m < maptop; m++ ) { if( MapTable[m]->seen_recoded ) { BackEnd->PrintMapping(m); map = MapTable[m]; map->last_page_printed = 1; } } } /* end MapPrintEncodings */ /*****************************************************************************/ /* */ /* MapPrintPSResources(fp) */ /* */ /* Print PostScript resource entries for all encoding vectors on file fp. */ /* */ /*****************************************************************************/ void MapPrintPSResources(FILE *fp) { MAPPING m; MAP_VEC map; for( m = 1; m < maptop; m++ ) if( MapTable[m]->seen_recoded ) { map = MapTable[m]; fprintf(fp, "%%%%+ encoding %s%s", string(map->name), (char *) STR_NEWLINE); } } /* end MapPrintPSResources */ /*@@**************************************************************************/ /* */ /* OBJECT DoWord(buff, q, x, fnum) */ /* */ /* Replace WORD or QWORD x by a small caps version, based on word_font(x). */ /* */ /*****************************************************************************/ static OBJECT DoWord(FULL_CHAR *buff, FULL_CHAR *q, OBJECT x, FONT_NUM fnum) { OBJECT res; *q++ = '\0'; res = MakeWord(type(x), buff, &fpos(x)); word_font(res) = fnum; word_colour(res) = word_colour(x); word_underline_colour(res) = word_underline_colour(x); word_texture(res) = word_texture(x); word_outline(res) = word_outline(x); word_language(res) = word_language(x); word_baselinemark(res) = word_baselinemark(x); word_strut(res) = word_strut(x); word_ligatures(res) = word_ligatures(x); word_hyph(res) = word_hyph(x); underline(res) = UNDER_OFF; return res; } /* end DoWord */ /*****************************************************************************/ /* */ /* OBJECT DoVShift(x, vshift, chld) */ /* */ /* Make an new VSHIFT object with the given shift and child. */ /* */ /*****************************************************************************/ static OBJECT DoVShift(OBJECT x, FULL_LENGTH vshift, OBJECT chld) { OBJECT res; New(res, VSHIFT); FposCopy(fpos(res), fpos(x)); shift_type(res) = GAP_DEC; units(shift_gap(res)) = FIXED_UNIT; mode(shift_gap(res)) = EDGE_MODE; width(shift_gap(res)) = vshift; underline(res) = UNDER_OFF; Link(res, chld); return res; } /*****************************************************************************/ /* */ /* void DoAddGap(new_acat) */ /* */ /* Add a new 0i gap object to new_acat. */ /* */ /*****************************************************************************/ static void DoAddGap(OBJECT new_acat) { OBJECT new_g; New(new_g, GAP_OBJ); FposCopy(fpos(new_g), fpos(new_acat)); hspace(new_g) = vspace(new_g) = 0; SetGap(gap(new_g), TRUE, FALSE, TRUE, FIXED_UNIT, EDGE_MODE, 0*IN); underline(new_g) = UNDER_OFF; Link(new_acat, new_g); } /*@::MapSmallCaps()@**********************************************************/ /* */ /* OBJECT MapSmallCaps(x, style) */ /* */ /* Replace WORD or QWORD x by a small caps version, based on word_font(x). */ /* */ /*****************************************************************************/ #define INIT 0 #define ALL_NON 1 #define ALL_TRANS 2 #define MIXED_NON 3 #define MIXED_TRANS 4 #define transformable(ch) (uc[ch] != '\0') /* basically temporaries but remembered from call to call for recycling */ static OBJECT font_change_word = nilobj; static FULL_LENGTH font_change_length = 0; OBJECT MapSmallCaps(OBJECT x, STYLE *style) { MAPPING m; int i; OBJECT new_y, new_x = nilobj, new_acat = nilobj, tmp; FULL_CHAR buff[MAX_BUFF], *uc, *p, *q; FONT_NUM small_font = 0; FULL_LENGTH vshift = 0; int state; STYLE new_style; assert( is_word(type(x)), "MapSmallCaps: !is_word(type(x))" ); debug2(DCM, D, "MapSmallCaps(%s %s)", Image(type(x)), string(x)); /* get the mapping and return if there isn't one for this font */ m = FontMapping(word_font(x), &fpos(x)); if( m == 0 ) { debug0(DCM, D, "MapSmallCaps returning unchanged (mapping is 0)"); return x; } assert( 1 <= m && m < maptop, "MapSmallCaps: mapping out of range!" ); uc = MapTable[m]->map[MAP_UPPERCASE]; /* if plain text, apply the mapping and exit */ if( !(BackEnd->scale_avail) ) { for( i = 0; string(x)[i] != '\0'; i++ ) if( uc[string(x)[i]] != '\0' ) string(x)[i] = uc[string(x)[i]]; debug1(DCM, D, "MapSmallCaps returning (plain text) %s", EchoObject(x)); return x; } /* make sure the small caps size is a reasonable one */ if( smallcaps_len(*style) <= 0 ) Error(38, 12, "small caps size is zero or negative", FATAL, &fpos(x)); /* set up the font change word if not already done */ if( font_change_length != smallcaps_len(*style) ) { char tmp[100]; font_change_length = smallcaps_len(*style); sprintf(tmp, "%.2ff", (float) font_change_length / FR); font_change_word = MakeWord(WORD, AsciiToFull(tmp), no_fpos); } state = INIT; q = buff; for( p = string(x); *p != '\0'; p++ ) { debug2(DCM, DD, " examining %c (%s)", *p, transformable(*p) ? "transformable" : "not transformable"); switch( state ) { case INIT: /* this state is for when we are at the first character */ if( transformable(*p) ) { *q++ = uc[*p]; /* work out what the smaller font is going to be, and the vshift */ StyleCopy(new_style, *style); FontChange(&new_style, font_change_word); small_font = font(new_style); vshift = word_baselinemark(x) ? 0 : (FontHalfXHeight(word_font(x)) - FontHalfXHeight(small_font)); state = ALL_TRANS; } else { *q++ = *p; state = ALL_NON; } break; case ALL_NON: /* in this state, all characters so far are non-transformable */ if( transformable(*p) ) { /* work out what the smaller font is going to be */ StyleCopy(new_style, *style); FontChange(&new_style, font_change_word); small_font = font(new_style); vshift = word_baselinemark(x) ? 0 : (FontHalfXHeight(word_font(x)) - FontHalfXHeight(small_font)); /* make a new WORD out of the current contents of buff */ new_y = DoWord(buff, q, x, word_font(x)); /* construct the skeleton of the result to replace x */ New(new_x, ONE_COL); FposCopy(fpos(new_x), fpos(x)); New(new_acat, ACAT); FposCopy(fpos(new_acat), fpos(x)); Link(new_x, new_acat); Link(new_acat, new_y); DoAddGap(new_acat); /* start off a new buffer with *p */ q = buff; *q++ = uc[*p]; state = MIXED_TRANS; } else *q++ = *p; break; case ALL_TRANS: /* in this state, all characters so far are transformable */ if( transformable(*p) ) *q++ = uc[*p]; else { /* make a new @VShift WORD out of the current contents of buff */ tmp = DoWord(buff, q, x, small_font); new_y = DoVShift(x, vshift, tmp); /* construct the skeleton of the result to replace x */ New(new_x, ONE_COL); FposCopy(fpos(new_x), fpos(x)); New(new_acat, ACAT); FposCopy(fpos(new_acat), fpos(x)); Link(new_x, new_acat); Link(new_acat, new_y); DoAddGap(new_acat); /* start off a new buffer with *p */ q = buff; *q++ = *p; state = MIXED_NON; } break; case MIXED_NON: /* in this state the previous char was non-transformable, but */ /* there have been characters before that that were transformable */ if( transformable(*p) ) { /* make a new WORD out of the current contents of buff */ new_y = DoWord(buff, q, x, word_font(x)); /* link the new word into the growing structure that replaces x */ Link(new_acat, new_y); DoAddGap(new_acat); /* start off a new buffer with *p */ q = buff; *q++ = uc[*p]; state = MIXED_TRANS; } else *q++ = *p; break; case MIXED_TRANS: /* in this state the previous char was transformable, but there */ /* have been characters before that that were non-transformable */ if( transformable(*p) ) *q++ = uc[*p]; else { /* make a new @VShift WORD out of the current contents of buff */ tmp = DoWord(buff, q, x, small_font); new_y = DoVShift(x, vshift, tmp); /* link the new word into the growing structure that replaces x */ Link(new_acat, new_y); DoAddGap(new_acat); /* start off a new buffer with *p */ q = buff; *q++ = *p; state = MIXED_NON; } break; } } /* now at termination, clean up the structure */ switch( state ) { case INIT: case ALL_NON: /* original x is OK as is: either empty or all non-transformable */ break; case ALL_TRANS: /* make a new @VShift WORD and replace x with it */ tmp = DoWord(buff, q, x, small_font); new_x = DoVShift(x, vshift, tmp); ReplaceNode(new_x, x); Dispose(x); x = new_x; break; case MIXED_NON: /* make a new WORD, add to new_acat, and replace x */ new_y = DoWord(buff, q, x, word_font(x)); Link(new_acat, new_y); ReplaceNode(new_x, x); Dispose(x); x = new_x; break; case MIXED_TRANS: /* make a new @VShift WORD, add to new_acat, and replace x */ tmp = DoWord(buff, q, x, small_font); new_y = DoVShift(x, vshift, tmp); Link(new_acat, new_y); ReplaceNode(new_x, x); Dispose(x); x = new_x; break; } debug1(DCM, D, "MapSmallCaps returning %s", EchoObject(x)); return x; } /* end MapSmallCaps */ /*****************************************************************************/ /* */ /* BOOLEAN MapIsLowerCase(FULL_CHAR ch, MAPPING m) */ /* */ /* Returns TRUE if ch is a lower-case character in mapping m; i.e. if it */ /* has a corresponding upper-case character. */ /* */ /*****************************************************************************/ BOOLEAN MapIsLowerCase(FULL_CHAR ch, MAPPING m) { BOOLEAN res; debug2(DCM, D, "MapIsLowerCase(%c, %d)", ch, m); res = (MapTable[m]->map[MAP_UPPERCASE][ch] != '\0'); debug1(DCM, D, "MapIsLowerCase returning %s", bool(res)); return res; } /* end MapIsLowerCase */