Back to index

tetex-bin  3.0
t1afmtool.c
Go to the documentation of this file.
00001 /*--------------------------------------------------------------------------
00002   ----- File:        t1afmtool.c 
00003   ----- Author:      Rainer Menzner (Rainer.Menzner@web.de)
00004   ----- Date:        2002-08-17
00005   ----- Description: This file is part of the t1-library. It contains
00006                      functions for generating a fallback set of afm data
00007                    from type 1 font files.
00008   ----- Copyright:   t1lib is copyrighted (c) Rainer Menzner, 1996-2002.
00009                      As of version 0.5, t1lib is distributed under the
00010                    GNU General Public Library Lincense. The
00011                    conditions can be found in the files LICENSE and
00012                    LGPL, which should reside in the toplevel
00013                    directory of the distribution.  Please note that 
00014                    there are parts of t1lib that are subject to
00015                    other licenses:
00016                    The parseAFM-package is copyrighted by Adobe Systems
00017                    Inc.
00018                    The type1 rasterizer is copyrighted by IBM and the
00019                    X11-consortium.
00020   ----- Warranties:  Of course, there's NO WARRANTY OF ANY KIND :-)
00021   ----- Credits:     I want to thank IBM and the X11-consortium for making
00022                      their rasterizer freely available.
00023                    Also thanks to Piet Tutelaers for his ps2pk, from
00024                    which I took the rasterizer sources in a format
00025                    independent from X11.
00026                      Thanks to all people who make free software living!
00027 --------------------------------------------------------------------------*/
00028   
00029 #define T1AFMTOOL_C
00030 
00031 
00032 #include <stdio.h>
00033 #include <sys/types.h>
00034 #include <sys/stat.h>
00035 #include <fcntl.h>
00036 #if defined(_MSC_VER)
00037 # include <io.h>
00038 # include <sys/types.h>
00039 # include <sys/stat.h>
00040 #else
00041 # include <unistd.h>
00042 #endif
00043 #include <stdlib.h>
00044 #include <math.h>
00045 #include <time.h>
00046 #include <string.h>
00047 
00048 #include "../type1/ffilest.h" 
00049 #include "../type1/types.h"
00050 #include "parseAFM.h" 
00051 #include "../type1/objects.h"
00052 #include "../type1/spaces.h"
00053 #include "../type1/util.h"
00054 #include "../type1/fontfcn.h"
00055 #include "../type1/regions.h"
00056 #include "../type1/blues.h"
00057 
00058 
00059 #include "t1types.h"
00060 #include "t1extern.h"
00061 #include "t1finfo.h"
00062 #include "t1base.h"
00063 #include "t1misc.h"
00064 #include "t1set.h"
00065 #include "t1load.h"
00066 #include "t1afmtool.h"
00067 
00068 
00069 #define DELTA_MAX    30
00070 #define ENCODINGSIZE (int) 256
00071 #ifndef T1LIB_IDENT
00072 #define T1LIB_IDENT  "???.???"
00073 #endif
00074 
00075 
00076 extern char *t1_get_abort_message( int number);
00077   
00078 
00079 /* T1_GenerateAFMFallbackInfo(): Generate fallback information from
00080    Type 1 font file by rasterizing every character at 1000 bp. Returns
00081    a pointer to a generated FontInfo struct or NULL in case of an error.
00082    */
00083 FontInfo *T1_GenerateAFMFallbackInfo( int FontID)
00084 {
00085   int i, j;
00086   struct region *area;
00087   struct XYspace *S;    
00088   int mode=0;
00089   char **charnames;
00090   int nochars=0;
00091   FontInfo *pAFMData;
00092   
00093   
00094   /* We return to this if something goes wrong deep in the rasterizer */
00095   if ((i=setjmp( stck_state))!=0) {
00096     T1_errno=T1ERR_TYPE1_ABORT;
00097     sprintf( err_warn_msg_buf, "t1_abort: Reason: %s",
00098             t1_get_abort_message( i));
00099     T1_PrintLog( "T1_GenerateAFMFallbackInfo()", err_warn_msg_buf,
00100               T1LOG_ERROR);
00101     return( NULL);
00102   }
00103   
00104 
00105   /* Check whether font is loaded: */
00106   if (T1_CheckForFontID(FontID)!=1){
00107     sprintf( err_warn_msg_buf,
00108             "Can't generate AFM Info from Font %d (invalid ID)\n", FontID);
00109     T1_PrintLog( "T1_GenerateAFMFallbackInfo()", err_warn_msg_buf,
00110                T1LOG_WARNING);
00111     T1_errno=T1ERR_INVALID_FONTID;
00112     return(NULL);
00113   }
00114 
00115   /* Setup apropriate charspace matrix */
00116   S=(struct XYspace *)IDENTITY;
00117   /* Make this permanent so that scaling it in fontfcnB_ByName will
00118      make a duplicate of it, and this duplicate can thus be safely
00119      destroyed.  Fixes the somewhat smaller memory leak */
00120   S=(struct XYspace *)Permanent
00121     (Transform(S, pFontBase->pFontArray[FontID].FontTransform[0],
00122               pFontBase->pFontArray[FontID].FontTransform[1],
00123               pFontBase->pFontArray[FontID].FontTransform[2],
00124               pFontBase->pFontArray[FontID].FontTransform[3]));
00125 
00126   /* Alloc memory for FontInfo: */
00127   if ((pAFMData=(FontInfo *)malloc( sizeof(FontInfo)))==NULL){
00128     sprintf( err_warn_msg_buf,
00129             "Failed to allocate memory for FontInfo in Font %d!", FontID);
00130     T1_PrintLog( "T1_GenerateAFMFallbackInfo()", err_warn_msg_buf,
00131                T1LOG_WARNING);
00132     T1_errno=T1ERR_ALLOC_MEM;
00133     /* make sure to free S */
00134     if (S) {
00135       KillSpace (S);
00136     }
00137     return( NULL);
00138   }
00139   /* Initialize pointers */
00140   pAFMData->gfi=NULL;
00141   pAFMData->cwi=NULL;
00142   pAFMData->numOfChars=0;
00143   pAFMData->cmi=NULL;
00144   pAFMData->numOfTracks=0;
00145   pAFMData->tkd=NULL;
00146   pAFMData->numOfPairs=0;
00147   pAFMData->pkd=NULL;
00148   pAFMData->numOfComps=0;
00149   pAFMData->ccd=NULL;
00150   
00151   /* Get list of character name */
00152   charnames=T1_GetAllCharNames( FontID);
00153   /* and count number of characters */
00154   nochars=0;
00155   
00156   while (charnames[nochars]!=NULL) 
00157     nochars++;
00158   pAFMData->numOfChars=nochars;
00159   /* Allocate memory for CharMetricInfo area */
00160   if ((pAFMData->cmi=(CharMetricInfo *)
00161        malloc( nochars * sizeof( CharMetricInfo)))==NULL){
00162     sprintf( err_warn_msg_buf,
00163             "Failed to allocate memory for CharMetricsInfo area in Font %d!",
00164             FontID);
00165     T1_PrintLog( "T1_GenerateAFMFallbackInfo()", err_warn_msg_buf,
00166                T1LOG_WARNING);
00167     free( pAFMData);
00168     T1_errno=T1ERR_ALLOC_MEM;
00169     /* make sure to free S */
00170     if (S) {
00171       KillSpace (S);
00172     }
00173     return( NULL);
00174   }
00175   /* Get metrics values */
00176   for (i=0; i<nochars; i++){
00177     area=fontfcnB_ByName( FontID, 0, S, charnames[i], &mode,
00178                        pFontBase->pFontArray[FontID].pType1Data, DO_RASTER);
00179     
00180     if (area==NULL){
00181       sprintf( err_warn_msg_buf,
00182               "Could not get charspace representation of character %d (%s) Font %d!",
00183               i, charnames[i], FontID);
00184       T1_PrintLog( "T1_GenerateAFMFallbackInfo()", err_warn_msg_buf,
00185                  T1LOG_WARNING);
00186       /* Return since we don't know how to fill the values */
00187       for (j=i-1; j>=0; j--)
00188        free( pAFMData->cmi[j].name);
00189       if (pAFMData->cmi!=NULL)
00190        free( pAFMData->cmi);
00191       if (pAFMData!=NULL)
00192        free( pAFMData);
00193       T1_errno=mode;
00194       /* make sure to free S */
00195       if (S) {
00196        KillSpace (S);
00197       }
00198       return( NULL);
00199     }
00200     else if ((pAFMData->cmi[i].name=(char *)
00201              malloc( (size_t)(strlen( charnames[i])+1)))==NULL){
00202       sprintf( err_warn_msg_buf,
00203               "Failed to allocate memory for CharName %d (%s) Font %d!",
00204               i, charnames[i], FontID);
00205       T1_PrintLog( "T1_GenerateAFMFallbackInfo()", err_warn_msg_buf,
00206                  T1LOG_WARNING);
00207       /* NULL-ptr in charnames is prone to SIGSEGV-errors, thus,
00208         we have to return: */
00209       for (j=i; j>=0; j--)
00210        free( pAFMData->cmi[j].name);
00211       free( pAFMData->cmi);
00212       free( pAFMData);
00213       T1_errno=T1ERR_ALLOC_MEM;
00214       /* make sure to free S */
00215       if (S) {
00216        KillSpace (S);
00217       }
00218       return( NULL);
00219     }
00220     else{
00221       strcpy( pAFMData->cmi[i].name, charnames[i]);
00222       pAFMData->cmi[i].code=T1_GetEncodingIndex( FontID, charnames[i]);
00223       pAFMData->cmi[i].wx=NEARESTPEL(area->ending.x);
00224       pAFMData->cmi[i].wy=NEARESTPEL(area->ending.y);
00225       /* We check for a valid BBox and set it to zero otherwise */
00226       if ((int)area->xmax > (int)area->xmin){
00227        pAFMData->cmi[i].charBBox.llx =(int)area->xmin;
00228        pAFMData->cmi[i].charBBox.urx =(int)area->xmax;
00229        pAFMData->cmi[i].charBBox.lly =(int)area->ymin;
00230        pAFMData->cmi[i].charBBox.ury =(int)area->ymax;
00231       }
00232       else{
00233        pAFMData->cmi[i].charBBox.llx =0;
00234        pAFMData->cmi[i].charBBox.urx =0;
00235        pAFMData->cmi[i].charBBox.lly =0;
00236        pAFMData->cmi[i].charBBox.ury =0;
00237       }
00238       pAFMData->cmi[i].ligs=NULL;
00239       /* We are done with area, so get rid of it. Solves the REALLY
00240         HUGE memory leak */
00241       KillRegion (area);
00242     }
00243   }
00244   sprintf( err_warn_msg_buf,
00245           "Generated metric information for %d characters of font %d!",
00246           nochars, FontID);
00247   T1_PrintLog( "T1_GenerateAFMFallbackInfo()", err_warn_msg_buf,
00248               T1LOG_STATISTIC);
00249   
00250   /* make sure to free S */
00251   if (S) {
00252     KillSpace (S);
00253   }
00254   return( pAFMData);  
00255 }
00256 
00257 
00258 /* T1_WriteAFMFallbackFile(): Write a fallback AFM-file from AFM data
00259    genarated for font FontID. returns
00260     0       if successful
00261    -1       if afm-data was loaded from existent AFM-file
00262    -2       if font is not loaded (invalid FontID)
00263    -3       if for some reason the fonts' AFM-data has not been generated
00264    -4       if the file could not be openend
00265    -5       if an error occurred during write
00266    -6       other error.
00267    */
00268 int T1_WriteAFMFallbackFile( int FontID)
00269 {
00270 
00271   int i, j, k, l;
00272 
00273   int nochars;
00274   int capheight, ascender, xheight, descender;
00275   int min=0, delta;
00276   time_t s_clock, *tp;
00277   
00278   char *afmfilename;
00279   FILE *afmfile;
00280   CharMetricInfo **cmi;
00281   
00282 
00283   /* Check for valid font */
00284   if (T1_CheckForFontID(FontID)!=1){
00285     sprintf( err_warn_msg_buf,
00286             "Warning: Invalid FontID, font %d not loaded!",
00287             FontID);
00288     T1_PrintLog( "T1_WriteAFMFallbackFile()", err_warn_msg_buf,
00289               T1LOG_WARNING);
00290     T1_errno=T1ERR_INVALID_FONTID;
00291     return(-2);
00292   }
00293 
00294   /* Check for AFM-data to be existent */
00295   if (pFontBase->pFontArray[FontID].pAFMData==NULL){
00296     sprintf( err_warn_msg_buf,
00297             "Warning: No AFM-Data available for font %d",
00298             FontID);
00299     T1_PrintLog( "T1_WriteAFMFallbackFile()", err_warn_msg_buf,
00300               T1LOG_WARNING);
00301     return(-3);
00302   }
00303 
00304   /* Check if AFM data has been generated from existent afm file -->
00305      no need to generate a new one. */
00306   if ((pFontBase->pFontArray[FontID].info_flags & AFM_SUCCESS) 
00307       || (pFontBase->pFontArray[FontID].info_flags & AFM_SLOPPY_SUCCESS)){
00308     sprintf( err_warn_msg_buf,
00309             "Alert:  Available AFM-Data for font %d is generated from existent AFM-file!",
00310             FontID);
00311     T1_PrintLog( "T1_WriteAFMFallbackFile()", err_warn_msg_buf,
00312                T1LOG_STATISTIC);
00313     return(-1);
00314   }
00315   
00316   /* Construct AFM-filename */
00317   i=strlen(pFontBase->pFontArray[FontID].pFontFileName);
00318   if ((afmfilename=(char *)malloc((size_t)(i + 1) ))==NULL){
00319     sprintf( err_warn_msg_buf,
00320             "Memory allocation error (fontID = %d)",
00321             FontID);
00322     T1_PrintLog( "T1_WriteAFMFallbackFile()", err_warn_msg_buf,
00323                T1LOG_WARNING);
00324     T1_errno=T1ERR_ALLOC_MEM;
00325     return(-6);
00326   }
00327   strcpy( afmfilename, pFontBase->pFontArray[FontID].pFontFileName);
00328   while (afmfilename[i]!='.')
00329     i--;
00330   afmfilename[i+1]='a';
00331   afmfilename[i+2]='f';
00332   afmfilename[i+3]='m';
00333   
00334   if ((afmfile=fopen( afmfilename, "wb"))==NULL){
00335     sprintf( err_warn_msg_buf,
00336             "Could not open %s, (FontID = %d)",
00337             afmfilename, FontID);
00338     T1_PrintLog( "T1_WriteAFMFallbackFile()", err_warn_msg_buf,
00339                T1LOG_WARNING);
00340     free( afmfilename);
00341     T1_errno=T1ERR_FILE_OPEN_ERR;
00342     return(-4);
00343   }
00344 
00345   /* Set the creation time of afm file */
00346   tp=&s_clock;
00347   s_clock=time( tp);
00348 
00349   /* Allocate a pointer-array to the metrics information, 
00350      of the size 256 + number of characters. */
00351   nochars=pFontBase->pFontArray[FontID].pAFMData->numOfChars;
00352   if ((cmi=(CharMetricInfo **)calloc( nochars+ENCODINGSIZE,
00353                                  sizeof(CharMetricInfo *)))==NULL){
00354     sprintf( err_warn_msg_buf,
00355             "Memory allocation error (fontID = %d)",
00356             FontID);
00357     T1_PrintLog( "T1_WriteAFMFallbackFile()", err_warn_msg_buf,
00358                T1LOG_WARNING);
00359     T1_errno=T1ERR_ALLOC_MEM;
00360     return(-6);
00361   }
00362   /* Reset area */
00363   for ( i=0; i<(nochars+ENCODINGSIZE); i++)
00364     cmi[i]=NULL;
00365   /* First half of array is dedicated to encoded characters, the
00366      second half for unencoded characters */
00367   k=ENCODINGSIZE;
00368   for ( i=0; i<nochars; i++){
00369     j=T1_GetEncodingIndex( FontID,
00370                         pFontBase->pFontArray[FontID].pAFMData->cmi[i].name);
00371     if (j>=0)
00372       cmi[j]=&(pFontBase->pFontArray[FontID].pAFMData->cmi[i]);
00373     else
00374       cmi[k++]=&(pFontBase->pFontArray[FontID].pAFMData->cmi[i]);
00375   }
00376   
00377   fprintf(afmfile, "StartFontMetrics 4.0\n");
00378   fprintf(afmfile, "Comment This is %s created from %s by t1lib V. %s.\n",
00379          afmfilename, pFontBase->pFontArray[FontID].pFontFileName,
00380          T1LIB_IDENT);
00381   fprintf(afmfile, "Comment File creation date: %s", ctime(&s_clock));
00382   fprintf(afmfile, "Comment t1lib is copyright (c) Rainer Menzner, 1996-2001.\n");
00383   fprintf(afmfile, "Comment t1lib is distributed under the GNU General Public Library License (LGPL)\n");
00384   fprintf(afmfile, "FontName %s\n", T1_GetFontName( FontID));
00385   fprintf(afmfile, "FullName %s\n", T1_GetFullName( FontID));
00386   fprintf(afmfile, "FamilyName %s\n", T1_GetFamilyName( FontID));
00387   fprintf(afmfile, "Weight %s\n", T1_GetWeight( FontID));
00388   fprintf(afmfile, "ItalicAngle %d\n", (int)T1_GetItalicAngle( FontID));
00389   fprintf(afmfile, "IsFixedPitch %s\n", T1_GetIsFixedPitch( FontID) ? "true" : "false" );
00390   fprintf(afmfile, "FontBBox %d %d %d %d\n",
00391          T1_GetFontBBox( FontID).llx,
00392          T1_GetFontBBox( FontID).lly,
00393          T1_GetFontBBox( FontID).urx,
00394          T1_GetFontBBox( FontID).ury);
00395   fprintf(afmfile, "UnderlinePosition %d\n",
00396          (int)T1_GetUnderlinePosition( FontID));
00397   fprintf(afmfile, "UnderlineThickness %d\n",
00398          (int)T1_GetUnderlineThickness( FontID));
00399 
00400   fprintf(afmfile, "Version %s\n", T1_GetVersion( FontID));
00401   fprintf(afmfile, "Notice %s\n", T1_GetNotice( FontID));
00402   /* Encoding */
00403   if (pFontBase->pFontArray[FontID].pFontEnc==NULL){
00404     if (pFontBase->pFontArray[FontID].info_flags & USES_STANDARD_ENCODING){
00405       fprintf( afmfile, "EncodingScheme AdobeStandardEncoding\n");
00406     }
00407     else{
00408       fprintf( afmfile, "EncodingScheme FontSpecific\n");
00409     }
00410   }
00411   else
00412     fprintf( afmfile, "EncodingScheme FontSpecific\n");
00413   /* Values like capheight ascender xheight and descender */
00414   /* We start with the overshoot positions */
00415   i=pFontBase->pFontArray[FontID].pType1Data->BluesP->numBlueValues;
00416   if (i>0){
00417     /* Capheight */
00418     if ((k=T1_GetEncodingIndex( FontID, "H"))!=-1){
00419       l=T1_GetCharBBox( FontID, (char) k).ury;
00420       delta=10000;
00421       for (j=0; j<i; j++){
00422        if (delta > abs(pFontBase->pFontArray[FontID].pType1Data->BluesP->BlueValues[j] - l)){
00423          min=j;
00424          delta=abs(pFontBase->pFontArray[FontID].pType1Data->BluesP->BlueValues[j] - l);
00425        }
00426       }
00427       if (min % 2) /* index is odd */ 
00428        min--;
00429       /* Check for plausibility */
00430       if (abs( pFontBase->pFontArray[FontID].pType1Data->BluesP->BlueValues[min] - l)
00431          > DELTA_MAX) /* We probably did not found the right position */
00432        capheight=0;
00433       else
00434        capheight=pFontBase->pFontArray[FontID].pType1Data->BluesP->BlueValues[min];
00435     }
00436     else
00437       capheight=0;
00438     /* XHeight */
00439     if ((k=T1_GetEncodingIndex( FontID, "x"))!=-1){
00440       l=T1_GetCharBBox( FontID, (char) k).ury;
00441       delta=10000;
00442       for (j=0; j<i; j++){
00443        if (delta > abs(pFontBase->pFontArray[FontID].pType1Data->BluesP->BlueValues[j] - l)){
00444          min=j;
00445          delta=abs(pFontBase->pFontArray[FontID].pType1Data->BluesP->BlueValues[j] - l);
00446        }
00447       }
00448       if (min % 2) /* index is odd */ 
00449        min--;
00450       /* Check for plausibility */
00451       if (abs( pFontBase->pFontArray[FontID].pType1Data->BluesP->BlueValues[min] - l)
00452          > DELTA_MAX) /* We probably did not found the right position */
00453        xheight=0;
00454       else
00455        xheight=pFontBase->pFontArray[FontID].pType1Data->BluesP->BlueValues[min];
00456     }
00457     else{
00458       xheight=0;
00459     }
00460     /* Ascender */
00461     if ((k=T1_GetEncodingIndex( FontID, "d"))!=-1){
00462       l=T1_GetCharBBox( FontID, (char) k).ury;
00463       delta=10000;
00464       for (j=0; j<i; j++){
00465        if (delta > abs(pFontBase->pFontArray[FontID].pType1Data->BluesP->BlueValues[j] - l)){
00466          min=j;
00467          delta=abs(pFontBase->pFontArray[FontID].pType1Data->BluesP->BlueValues[j] - l);
00468        }
00469       }
00470       if (min % 2) /* index is odd */ 
00471        min--;
00472       /* Check for plausibility */
00473       if (abs( pFontBase->pFontArray[FontID].pType1Data->BluesP->BlueValues[min] - l)
00474          > DELTA_MAX) /* We probably did not found the right position */
00475        ascender=0;
00476       else
00477        ascender=pFontBase->pFontArray[FontID].pType1Data->BluesP->BlueValues[min];
00478     }
00479     else{
00480       ascender=0;
00481     }
00482   }
00483   else{
00484     capheight=0;
00485     xheight=0;
00486     ascender=0;
00487   }
00488   /* And now comes under shoot alignment position */
00489   i=pFontBase->pFontArray[FontID].pType1Data->BluesP->numOtherBlues;
00490   if (i>0){
00491     /* Descender */
00492     if ((k=T1_GetEncodingIndex( FontID, "p"))!=-1){
00493       l=T1_GetCharBBox( FontID, (char) k).lly;
00494       delta=10000;
00495       for (j=0; j<i; j++){
00496        if (delta > abs(pFontBase->pFontArray[FontID].pType1Data->BluesP->OtherBlues[j] - l)){
00497          min=j;
00498          delta=abs(pFontBase->pFontArray[FontID].pType1Data->BluesP->OtherBlues[j] - l);
00499        }
00500       }
00501       if ((min % 2)==0) /* index is even */ 
00502        min++;
00503       /* Check for plausibility */
00504       if (abs( pFontBase->pFontArray[FontID].pType1Data->BluesP->OtherBlues[min] - l)
00505          > DELTA_MAX) /* We probably did not found the right position */
00506        descender=0;
00507       else
00508        descender=pFontBase->pFontArray[FontID].pType1Data->BluesP->OtherBlues[min];
00509     }
00510     else{
00511       descender=0;
00512     }
00513   }
00514   else{
00515     descender=0;
00516   }
00517   if (capheight != 0)
00518     fprintf( afmfile, "CapHeight %d\n", capheight);
00519   if (xheight != 0)
00520     fprintf( afmfile, "XHeight %d\n", xheight);
00521   if (ascender != 0)
00522     fprintf( afmfile, "Ascender %d\n", ascender);
00523   if (descender != 0)
00524     fprintf( afmfile, "Descender %d\n", descender);
00525   if (pFontBase->pFontArray[FontID].pType1Data->BluesP->StdHW != 0.0)
00526     fprintf( afmfile, "StdHW %d\n",
00527             (int) pFontBase->pFontArray[FontID].pType1Data->BluesP->StdHW);
00528   if (pFontBase->pFontArray[FontID].pType1Data->BluesP->StdVW != 0.0)
00529     fprintf( afmfile, "StdVW %d\n",
00530             (int) pFontBase->pFontArray[FontID].pType1Data->BluesP->StdVW);
00531 
00532   /* Now fill in the values (we omit the .notdef) */
00533   fprintf(afmfile, "StartCharMetrics %d\n", nochars-1);
00534   for ( i=0; i<(nochars+ENCODINGSIZE); i++){
00535     if (cmi[i]!=NULL && strcmp(cmi[i]->name,".notdef"))
00536       fprintf( afmfile, "C %3d ; WX %4d ; N %-20s ; B %5d %5d %5d %5d ;\n",
00537               cmi[i]->code, cmi[i]->wx, cmi[i]->name,
00538               cmi[i]->charBBox.llx, cmi[i]->charBBox.lly,
00539               cmi[i]->charBBox.urx, cmi[i]->charBBox.ury);
00540   }
00541   fprintf(afmfile, "EndCharMetrics\nEndFontMetrics\n"); 
00542   
00543   free(cmi);
00544   fclose( afmfile);
00545   return(0);
00546 }
00547 
00548