/* tfmfile.c -- readers for TFM, AFM, OTFM-0 and OTFM-1 files */ /* * Copyright (C) 2000, Matias Atria * * 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 2 of the License, 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 */ #include /* tex-file.h needs this */ #include #include #include #include #include #include "mdvi.h" #include "private.h" #ifdef WITH_AFM_FILES #undef TRUE #undef FALSE #include "afmparse.h" #endif typedef struct tfmpool { struct tfmpool *next; struct tfmpool *prev; char *short_name; int links; TFMInfo tfminfo; } TFMPool; static ListHead tfmpool = {NULL, NULL, 0}; static DviHashTable tfmhash; #define TFM_HASH_SIZE 31 #ifdef WORD_LITTLE_ENDIAN static inline void swap_array(Uint32 *ptr, int n) { Uint32 i; while(n-- > 0) { i = *ptr; *ptr++ = ((i & 0xff000000) >> 24) | ((i & 0x00ff0000) >> 8) | ((i & 0x0000ff00) << 8) | ((i & 0x000000ff) << 24); } } #endif #ifdef WITH_AFM_FILES /* reading of AFM files */ /* macro to convert between AFM and TFM units */ #define AFM2TFM(x) FROUND((double)(x) * 0x100000 / 1000) int afm_load_file(const char *filename, TFMInfo *info) { /* the information we want is: * - tfmwidth * - width and heights * - character origins */ FontInfo *fi = NULL; int status; CharMetricInfo *cm; FILE *in; in = fopen(filename, "r"); if(in == NULL) return -1; status = afm_parse_file(in, &fi, P_GM); fclose(in); if(status != ok) { error(_("%s: Error reading AFM data\n"), filename); return -1; } /* aim high */ info->chars = xnalloc(TFMChar, 256); info->loc = 256; info->hic = 0; info->design = 0xa00000; /* fake -- 10pt */ info->checksum = 0; /* no checksum */ info->type = DviFontAFM; xstrncpy(info->coding, fi->gfi->encodingScheme, 63); xstrncpy(info->family, fi->gfi->familyName, 63); /* now get the data */ for(cm = fi->cmi; cm < fi->cmi + fi->numOfChars; cm++) { int code; TFMChar *ch; code = cm->code; if(code < 0 || code > 255) continue; /* ignore it */ ch = &info->chars[code]; ch->present = 1; if(code < info->loc) info->loc = code; if(code > info->hic) info->hic = code; ch->advance = AFM2TFM(cm->wx); /* this is the `leftSideBearing' */ ch->left = AFM2TFM(cm->charBBox.llx); /* this is the height (ascent - descent) -- the sign is to follow * TeX conventions, as opposed to Adobe's ones */ ch->depth = -AFM2TFM(cm->charBBox.lly); /* this is the width (rightSideBearing - leftSideBearing) */ ch->right = AFM2TFM(cm->charBBox.urx); /* this is the `ascent' */ ch->height = AFM2TFM(cm->charBBox.ury); } /* we don't need this anymore */ afm_free_fontinfo(fi); /* optimize storage */ if(info->loc > 0 || info->hic < 256) { memmove(&info->chars[0], &info->chars[info->loc], (info->hic - info->loc + 1) * sizeof(TFMChar)); info->chars = xrealloc(info->chars, (info->hic - info->loc + 1) * sizeof(TFMChar)); } /* we're done */ return 0; } #endif /* WITH_AFM_FILES */ int tfm_load_file(const char *filename, TFMInfo *info) { int lf, lh, bc, ec, nw, nh, nd, ne; int i, n; Uchar *tfm; Uchar *ptr; struct stat st; int size; FILE *in; Int32 *cb; Int32 *charinfo; Int32 *widths; Int32 *heights; Int32 *depths; Uint32 checksum; in = fopen(filename, "r"); if(in == NULL) return -1; tfm = NULL; DEBUG((DBG_FONTS, "(mt) reading TFM file `%s'\n", filename)); /* We read the entire TFM file into core */ if(fstat(fileno(in), &st) < 0) return -1; if(st.st_size == 0) goto bad_tfm; /* allocate a word-aligned buffer to hold the file */ size = 4 * ROUND(st.st_size, 4); if(size != st.st_size) warning(_("Warning: TFM file `%s' has suspicious size\n"), filename); tfm = (Uchar *)xmalloc(size); if(fread(tfm, st.st_size, 1, in) != 1) goto error; /* we don't need this anymore */ fclose(in); in = NULL; /* not a checksum, but serves a similar purpose */ checksum = 0; ptr = tfm; /* get the counters */ lf = muget2(ptr); lh = muget2(ptr); checksum += 6 + lh; bc = muget2(ptr); ec = muget2(ptr); checksum += ec - bc + 1; nw = muget2(ptr); checksum += nw; nh = muget2(ptr); checksum += nh; nd = muget2(ptr); checksum += nd; checksum += muget2(ptr); /* skip italics correction count */ checksum += muget2(ptr); /* skip lig/kern table size */ checksum += muget2(ptr); /* skip kern table size */ ne = muget2(ptr); checksum += ne; checksum += muget2(ptr); /* skip # of font parameters */ size = ec - bc + 1; cb = (Int32 *)tfm; cb += 6 + lh; charinfo = cb; cb += size; widths = cb; cb += nw; heights = cb; cb += nh; depths = cb; if(widths[0] || heights[0] || depths[0] || checksum != lf || bc - 1 > ec || ec > 255 || ne > 256) goto bad_tfm; /* from this point on, no error checking is done */ /* now we're at the header */ /* get the checksum */ info->checksum = muget4(ptr); /* get the design size */ info->design = muget4(ptr); /* get the coding scheme */ if(lh > 2) { /* get the coding scheme */ i = n = msget1(ptr); if(n < 0 || n > 39) { warning(_("%s: font coding scheme truncated to 40 bytes\n"), filename); n = 39; } memcpy(info->coding, ptr, n); info->coding[n] = 0; ptr += i; } else strcpy(info->coding, "FontSpecific"); /* get the font family */ if(lh > 12) { n = msget1(ptr); if(n > 0) { i = Max(n, 63); memcpy(info->family, ptr, i); info->family[i] = 0; } else strcpy(info->family, "unspecified"); ptr += n; } /* now we don't read from `ptr' anymore */ info->loc = bc; info->hic = ec; info->type = DviFontTFM; /* allocate characters */ info->chars = xnalloc(TFMChar, size); #ifdef WORD_LITTLE_ENDIAN /* byte-swap the three arrays at once (they are consecutive in memory) */ swap_array((Uint32 *)widths, nw + nh + nd); #endif /* get the relevant data */ ptr = (Uchar *)charinfo; for(i = bc; i <= ec; ptr += 3, i++) { int ndx; ndx = (int)*ptr; ptr++; info->chars[i-bc].advance = widths[ndx]; /* TFM files lack this information */ info->chars[i-bc].left = 0; info->chars[i-bc].right = widths[ndx]; info->chars[i-bc].present = (ndx != 0); if(ndx) { ndx = ((*ptr >> 4) & 0xf); info->chars[i-bc].height = heights[ndx]; ndx = (*ptr & 0xf); info->chars[i-bc].depth = depths[ndx]; } } /* free everything */ xfree(tfm); return 0; bad_tfm: error(_("%s: File corrupted, or not a TFM file\n"), filename); error: if(tfm) xfree(tfm); if(in) fclose(in); return -1; } static int ofm1_load_file(FILE *in, TFMInfo *info) { int lf, lh, bc, ec, nw, nh, nd; int nco, ncw, npc; int i; int n; int size; Int32 *tfm; Int32 *widths; Int32 *heights; Int32 *depths; TFMChar *tch; TFMChar *end; lf = fuget4(in); lh = fuget4(in); bc = fuget4(in); ec = fuget4(in); nw = fuget4(in); nh = fuget4(in); nd = fuget4(in); fuget4(in); /* italics */ fuget4(in); /* lig-kern */ fuget4(in); /* kern */ fuget4(in); /* extensible recipe */ fuget4(in); /* parameters */ fuget4(in); /* direction */ nco = fuget4(in); ncw = fuget4(in); npc = fuget4(in); /* get the checksum */ info->checksum = fuget4(in); /* the design size */ info->design = fuget4(in); /* get the coding scheme */ if(lh > 2) { /* get the coding scheme */ i = n = fsget1(in); if(n < 0 || n > 39) n = 39; fread(info->coding, 39, 1, in); info->coding[n] = 0; } else strcpy(info->coding, "FontSpecific"); /* get the font family */ if(lh > 12) { n = fsget1(in); if(n > 0) { i = Max(n, 63); fread(info->family, i, 1, in); info->family[i] = 0; } else strcpy(info->family, "unspecified"); } tfm = NULL; /* jump to the beginning of the char-info table */ fseek(in, 4L*nco, SEEK_SET); size = ec - bc + 1; info->loc = bc; info->hic = ec; info->chars = xnalloc(TFMChar, size); end = info->chars + size; for(tch = info->chars, i = 0; i < ncw; i++) { TFMChar ch; int nr; /* in the characters we store the actual indices */ ch.advance = fuget2(in); ch.height = fuget1(in); ch.depth = fuget1(in); /* skip 2nd word */ fuget4(in); /* get # of repeats */ nr = fuget2(in); /* skip parameters */ fseek(in, (long)npc * 2, SEEK_CUR); /* if npc is odd, skip padding */ if(npc & 1) fuget2(in); /* now repeat the character */ while(nr-- >= 0 && tch < end) memcpy(tch++, &ch, sizeof(TFMChar)); if(tch == end) goto bad_tfm; } /* I wish we were done, but we aren't */ /* get the widths, heights and depths */ size = nw + nh + nd; tfm = xnalloc(Int32, size); /* read them in one sweep */ if(fread(tfm, 4, size, in) != size) { xfree(tfm); goto bad_tfm; } /* byte-swap things if necessary */ #ifdef WORD_LITTLE_ENDIAN swap_array((Uint32 *)tfm, size); #endif widths = tfm; heights = widths + nw; depths = heights + nh; if(widths[0] || heights[0] || depths[0]) goto bad_tfm; /* now fix the characters */ size = ec - bc + 1; for(tch = info->chars; tch < end; tch++) { tch->present = (tch->advance != 0); tch->advance = widths[tch->advance]; tch->height = heights[tch->height]; tch->depth = depths[tch->depth]; tch->left = 0; tch->right = tch->advance; } /* NOW we're done */ xfree(tfm); return 0; bad_tfm: if(tfm) xfree(tfm); return -1; } /* we don't read OFM files into memory, because they can potentially be large */ int ofm_load_file(const char *filename, TFMInfo *info) { int lf, lh, bc, ec, nw, nh, nd; int i, n; Int32 *tfm; Uchar *ptr; int size; FILE *in; Int32 *cb; Int32 *charinfo; Int32 *widths; Int32 *heights; Int32 *depths; Uint32 checksum; int olevel; int nwords; in = fopen(filename, "r"); if(in == NULL) return -1; /* not a checksum, but serves a similar purpose */ checksum = 0; /* get the counters */ /* get file level */ olevel = fsget2(in); if(olevel != 0) goto bad_tfm; olevel = fsget2(in); if(olevel != 0) { DEBUG((DBG_FONTS, "(mt) reading Level-1 OFM file `%s'\n", filename)); /* we handle level-1 files separately */ if(ofm1_load_file(in, info) < 0) goto bad_tfm; return 0; } DEBUG((DBG_FONTS, "(mt) reading Level-0 OFM file `%s'\n", filename)); nwords = 14; lf = fuget4(in); checksum = nwords; lh = fuget4(in); checksum += lh; bc = fuget4(in); ec = fuget4(in); checksum += 2 * (ec - bc + 1); nw = fuget4(in); checksum += nw; nh = fuget4(in); checksum += nh; nd = fuget4(in); checksum += nd; checksum += fuget4(in); /* skip italics correction count */ checksum += 2*fuget4(in); /* skip lig/kern table size */ checksum += fuget4(in); /* skip kern table size */ checksum += 2*fuget4(in); /* skip extensible recipe count */ checksum += fuget4(in); /* skip # of font parameters */ /* I have found several .ofm files that seem to have the * font-direction word missing, so we try to detect that here */ if(checksum == lf + 1) { DEBUG((DBG_FONTS, "(mt) font direction missing in `%s'\n", filename)); checksum--; nwords--; } else { /* skip font direction */ fuget4(in); } if(checksum != lf || bc > ec + 1 || ec > 65535) goto bad_tfm; /* now we're at the header */ /* get the checksum */ info->checksum = fuget4(in); /* get the design size */ info->design = fuget4(in); /* get the coding scheme */ if(lh > 2) { /* get the coding scheme */ i = n = fsget1(in); if(n < 0 || n > 39) { warning(_("%s: font coding scheme truncated to 40 bytes\n"), filename); n = 39; } fread(info->coding, 39, 1, in); info->coding[n] = 0; ptr += i; } else strcpy(info->coding, "FontSpecific"); /* get the font family */ if(lh > 12) { n = fsget1(in); if(n > 0) { i = Max(n, 63); fread(info->family, i, 1, in); info->family[i] = 0; } else strcpy(info->family, "unspecified"); } /* now skip anything else in the header */ fseek(in, 4L*(nwords + lh), SEEK_SET); /* and read everything at once */ size = 2*(ec - bc + 1) + nw + nh + nd; tfm = xnalloc(Int32, size * sizeof(Int32)); if(fread(tfm, 4, size, in) != size) { xfree(tfm); goto bad_tfm; } /* byte-swap all the tables at once */ #ifdef WORD_LITTLE_ENDIAN swap_array((Uint32 *)tfm, size); #endif cb = tfm; charinfo = cb; cb += 2*(ec - bc + 1); widths = cb; cb += nw; heights = cb; cb += nh; depths = cb; if(widths[0] || heights[0] || depths[0]) { xfree(tfm); goto bad_tfm; } /* from this point on, no error checking is done */ /* we don't need this anymore */ fclose(in); /* now we don't read from `ptr' anymore */ info->loc = bc; info->hic = ec; info->type = DviFontTFM; /* allocate characters */ info->chars = xnalloc(TFMChar, size); /* get the relevant data */ ptr = (Uchar *)charinfo; for(i = bc; i <= ec; ptr += 4, i++) { int ndx; ndx = muget2(ptr); info->chars[i-bc].advance = widths[ndx]; /* TFM files lack this information */ info->chars[i-bc].left = 0; info->chars[i-bc].right = widths[ndx]; info->chars[i-bc].present = (ndx != 0); ndx = muget1(ptr); info->chars[i-bc].height = heights[ndx]; ndx = muget1(ptr); info->chars[i-bc].depth = depths[ndx]; } xfree(tfm); return 0; bad_tfm: error(_("%s: File corrupted, or not a TFM file\n"), filename); fclose(in); return -1; } char *lookup_font_metrics(const char *name, int *type) { char *file; switch(*type) { #ifndef WITH_AFM_FILES case DviFontAny: #endif case DviFontTFM: file = kpse_find_tfm(name); break; case DviFontOFM: { file = kpse_find_ofm(name); /* we may have gotten a TFM back */ if(file != NULL) { const char *ext = file_extension(file); if(ext && STREQ(ext, "tfm")) *type = DviFontTFM; } break; } #ifdef WITH_AFM_FILES case DviFontAFM: file = kpse_find_file(name, kpse_afm_format, 0); break; case DviFontAny: file = kpse_find_file(name, kpse_afm_format, 0); *type = DviFontAFM; if(file == NULL) { file = kpse_find_tfm(name); *type = DviFontTFM; } break; #endif default: return NULL; } return file; } /* * The next two functions are just wrappers for the font metric loaders, * and use the pool of TFM data */ /* this is how we interpret arguments: * - if filename is NULL, we look for files of the given type, * unless type is DviFontAny, in which case we try all the * types we know of. * - if filename is not NULL, we look at `type' to decide * how to read the file. If type is DviFontAny, we just * return an error. */ TFMInfo *get_font_metrics(const char *short_name, int type, const char *filename) { TFMPool *tfm = NULL; int status; char *file; if(tfmpool.count) { tfm = (TFMPool *)mdvi_hash_lookup(&tfmhash, MDVI_KEY(short_name)); if(tfm != NULL) { DEBUG((DBG_FONTS, "(mt) reusing metric file `%s' (%d links)\n", short_name, tfm->links)); tfm->links++; return &tfm->tfminfo; } } file = filename ? (char *)filename : lookup_font_metrics(short_name, &type); if(file == NULL) return NULL; tfm = xalloc(TFMPool); DEBUG((DBG_FONTS, "(mt) loading font metric data from `%s'\n", file, file)); switch(type) { case DviFontTFM: status = tfm_load_file(file, &tfm->tfminfo); break; case DviFontOFM: status = ofm_load_file(file, &tfm->tfminfo); break; #ifdef WITH_AFM_FILES case DviFontAFM: status = afm_load_file(file, &tfm->tfminfo); break; #endif default: status = -1; break; } if(file != filename) xfree(file); if(status < 0) { xfree(tfm); return NULL; } tfm->short_name = xstrdup(short_name); /* add it to the pool */ if(tfmpool.count == 0) mdvi_hash_create(&tfmhash, TFM_HASH_SIZE); mdvi_hash_add(&tfmhash, MDVI_KEY(tfm->short_name), tfm, MDVI_HASH_UNCHECKED); listh_prepend(&tfmpool, LIST(tfm)); tfm->links = 1; return &tfm->tfminfo; } void free_font_metrics(TFMInfo *info) { TFMPool *tfm; if(tfmpool.count == 0) return; /* get the entry -- can't use the hash table for this, because * we don't have the short name */ for(tfm = (TFMPool *)tfmpool.head; tfm; tfm = tfm->next) if(info == &tfm->tfminfo) break; if(tfm == NULL) return; if(--tfm->links > 0) { DEBUG((DBG_FONTS, "(mt) %s not removed, still in use\n", tfm->short_name)); return; } mdvi_hash_remove_ptr(&tfmhash, MDVI_KEY(tfm->short_name)); DEBUG((DBG_FONTS, "(mt) removing unused TFM data for `%s'\n", tfm->short_name)); listh_remove(&tfmpool, LIST(tfm)); xfree(tfm->short_name); xfree(tfm->tfminfo.chars); xfree(tfm); } void flush_font_metrics(void) { TFMPool *ptr; for(; (ptr = (TFMPool *)tfmpool.head); ) { tfmpool.head = LIST(ptr->next); xfree(ptr->short_name); xfree(ptr->tfminfo.chars); xfree(ptr); } mdvi_hash_reset(&tfmhash, 0); }