|  | /******************************************************************************* | 
|  | *  Adobe Font Metric (AFM) file parsing functions for Wine PostScript driver. | 
|  | *  See http://partners.adobe.com/asn/developer/pdfs/tn/5004.AFM_Spec.pdf. | 
|  | * | 
|  | *  Copyright 2001  Ian Pilcher | 
|  | * | 
|  | * This library is free software; you can redistribute it and/or | 
|  | * modify it under the terms of the GNU Lesser General Public | 
|  | * License as published by the Free Software Foundation; either | 
|  | * version 2.1 of the License, or (at your option) any later version. | 
|  | * | 
|  | * This library 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 | 
|  | * Lesser General Public License for more details. | 
|  | * | 
|  | * You should have received a copy of the GNU Lesser General Public | 
|  | * License along with this library; if not, write to the Free Software | 
|  | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | 
|  | * | 
|  | *  NOTE:  Many of the functions in this file can return either fatal errors | 
|  | *  	(memory allocation failure) or non-fatal errors (unusable AFM file). | 
|  | *  	Fatal errors are indicated by returning FALSE; see individual function | 
|  | *  	descriptions for how they indicate non-fatal errors. | 
|  | * | 
|  | */ | 
|  |  | 
|  | #include "config.h" | 
|  | #include "wine/port.h" | 
|  |  | 
|  | #include <string.h> | 
|  | #include <stdlib.h> | 
|  | #include <stdarg.h> | 
|  | #include <stdio.h> | 
|  | #ifdef HAVE_DIRENT_H | 
|  | # include <dirent.h> | 
|  | #endif | 
|  | #include <errno.h> | 
|  | #include <ctype.h> | 
|  | #include <limits.h> 	    /* INT_MIN */ | 
|  |  | 
|  | #ifdef HAVE_FLOAT_H | 
|  | #include <float.h>  	    /* FLT_MAX */ | 
|  | #endif | 
|  |  | 
|  | #include "windef.h" | 
|  | #include "winbase.h" | 
|  | #include "winerror.h" | 
|  | #include "winreg.h" | 
|  | #include "winnls.h" | 
|  | #include "psdrv.h" | 
|  | #include "wine/debug.h" | 
|  |  | 
|  | WINE_DEFAULT_DEBUG_CHANNEL(psdrv); | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  ReadLine | 
|  | * | 
|  | *  Reads a line from a text file into the buffer and trims trailing whitespace. | 
|  | *  Can handle DOS and Unix text files, including weird DOS EOF.  Returns FALSE | 
|  | *  for unexpected I/O errors; otherwise returns TRUE and sets *p_result to | 
|  | *  either the number of characters in the returned string or one of the | 
|  | *  following: | 
|  | * | 
|  | *  	0:  	    Blank (or all whitespace) line.  This is just a special case | 
|  | *  	    	    of the normal behavior. | 
|  | * | 
|  | *  	EOF:	    End of file has been reached. | 
|  | * | 
|  | *  	INT_MIN:    Buffer overflow.  Returned string is truncated (duh!) and | 
|  | *  	    	    trailing whitespace is *not* trimmed.  Remaining text in | 
|  | *  	    	    line is discarded.  (I.e. the file pointer is positioned at | 
|  | *  	    	    the beginning of the next line.) | 
|  | * | 
|  | */ | 
|  | static BOOL ReadLine(FILE *file, CHAR buffer[], INT bufsize, INT *p_result) | 
|  | { | 
|  | CHAR   *cp; | 
|  | INT    i; | 
|  |  | 
|  | if (fgets(buffer, bufsize, file) == NULL) | 
|  | { | 
|  | if (feof(file) == 0)	    	    	    	/* EOF or error? */ | 
|  | { | 
|  | ERR("%s\n", strerror(errno)); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | *p_result = EOF; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | cp = strchr(buffer, '\n'); | 
|  | if (cp == NULL) | 
|  | { | 
|  | i = strlen(buffer); | 
|  |  | 
|  | if (i == bufsize - 1)	    /* max possible; was line truncated? */ | 
|  | { | 
|  | do | 
|  | i = fgetc(file);    	    	/* find the newline or EOF */ | 
|  | while (i != '\n' && i != EOF); | 
|  |  | 
|  | if (i == EOF) | 
|  | { | 
|  | if (feof(file) == 0)	    	    	/* EOF or error? */ | 
|  | { | 
|  | ERR("%s\n", strerror(errno)); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | WARN("No newline at EOF\n"); | 
|  | } | 
|  |  | 
|  | *p_result = INT_MIN; | 
|  | return TRUE; | 
|  | } | 
|  | else	    	    	    	/* no newline and not truncated */ | 
|  | { | 
|  | if (strcmp(buffer, "\x1a") == 0)	    /* test for DOS EOF */ | 
|  | { | 
|  | *p_result = EOF; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | WARN("No newline at EOF\n"); | 
|  | cp = buffer + i;	/* points to \0 where \n should have been */ | 
|  | } | 
|  | } | 
|  |  | 
|  | do | 
|  | { | 
|  | *cp = '\0'; 	    	    	    	/* trim trailing whitespace */ | 
|  | if (cp == buffer) | 
|  | break;  	    	    	    	/* don't underflow buffer */ | 
|  | --cp; | 
|  | } | 
|  | while (isspace(*cp)); | 
|  |  | 
|  | *p_result = strlen(buffer); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  FindLine | 
|  | * | 
|  | *  Finds a line in the file that begins with the given string.  Returns FALSE | 
|  | *  for unexpected I/O errors; returns an empty (zero character) string if the | 
|  | *  requested line is not found. | 
|  | * | 
|  | *  NOTE:  The file pointer *MUST* be positioned at the beginning of a line when | 
|  | *  	this function is called.  Otherwise, an infinite loop can result. | 
|  | * | 
|  | */ | 
|  | static BOOL FindLine(FILE *file, CHAR buffer[], INT bufsize, LPCSTR key) | 
|  | { | 
|  | INT     len = strlen(key); | 
|  | LONG    start = ftell(file); | 
|  |  | 
|  | do | 
|  | { | 
|  | INT 	result; | 
|  | BOOL	ok; | 
|  |  | 
|  | ok = ReadLine(file, buffer, bufsize, &result); | 
|  | if (ok == FALSE) | 
|  | return FALSE; | 
|  |  | 
|  | if (result > 0 && strncmp(buffer, key, len) == 0) | 
|  | return TRUE; | 
|  |  | 
|  | if (result == EOF) | 
|  | { | 
|  | rewind(file); | 
|  | } | 
|  | else if (result == INT_MIN) | 
|  | { | 
|  | WARN("Line beginning '%32s...' is too long; ignoring\n", buffer); | 
|  | } | 
|  | } | 
|  | while (ftell(file) != start); | 
|  |  | 
|  | WARN("Couldn't find line '%s...' in AFM file\n", key); | 
|  | buffer[0] = '\0'; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  DoubleToFloat | 
|  | * | 
|  | *  Utility function to convert double to float while checking for overflow. | 
|  | *  Will also catch strtod overflows, since HUGE_VAL > FLT_MAX (at least on | 
|  | *  Linux x86/gcc). | 
|  | * | 
|  | */ | 
|  | static inline BOOL DoubleToFloat(float *p_f, double d) | 
|  | { | 
|  | if (d > (double)FLT_MAX || d < -(double)FLT_MAX) | 
|  | return FALSE; | 
|  |  | 
|  | *p_f = (float)d; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  Round | 
|  | * | 
|  | *  Utility function to add or subtract 0.5 before converting to integer type. | 
|  | * | 
|  | */ | 
|  | static inline float Round(float f) | 
|  | { | 
|  | return (f >= 0.0) ? (f + 0.5) : (f - 0.5); | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  ReadFloat | 
|  | * | 
|  | *  Finds and parses a line of the form '<key> <value>', where value is a | 
|  | *  number.  Sets *p_found to FALSE if a corresponding line cannot be found, or | 
|  | *  it cannot be parsed; also sets *p_ret to 0.0, so calling functions can just | 
|  | *  skip the check of *p_found if the item is not required. | 
|  | * | 
|  | */ | 
|  | static BOOL ReadFloat(FILE *file, CHAR buffer[], INT bufsize, LPCSTR key, | 
|  | FLOAT *p_ret, BOOL *p_found) | 
|  | { | 
|  | CHAR    *cp, *end_ptr; | 
|  | double  d; | 
|  |  | 
|  | if (FindLine(file, buffer, bufsize, key) == FALSE) | 
|  | return FALSE; | 
|  |  | 
|  | if (buffer[0] == '\0')  	    /* line not found */ | 
|  | { | 
|  | *p_found = FALSE; | 
|  | *p_ret = 0.0; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | cp = buffer + strlen(key);	    	    	    /* first char after key */ | 
|  | errno = 0; | 
|  | d = strtod(cp, &end_ptr); | 
|  |  | 
|  | if (end_ptr == cp || errno != 0 || DoubleToFloat(p_ret, d) == FALSE) | 
|  | { | 
|  | WARN("Error parsing line '%s'\n", buffer); | 
|  | *p_found = FALSE; | 
|  | *p_ret = 0.0; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | *p_found = TRUE; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  ReadInt | 
|  | * | 
|  | *  See description of ReadFloat. | 
|  | * | 
|  | */ | 
|  | static BOOL ReadInt(FILE *file, CHAR buffer[], INT bufsize, LPCSTR key, | 
|  | INT *p_ret, BOOL *p_found) | 
|  | { | 
|  | BOOL    retval; | 
|  | FLOAT   f; | 
|  |  | 
|  | retval = ReadFloat(file, buffer, bufsize, key, &f, p_found); | 
|  | if (retval == FALSE || *p_found == FALSE) | 
|  | { | 
|  | *p_ret = 0; | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | f = Round(f); | 
|  |  | 
|  | if (f > (FLOAT)INT_MAX || f < (FLOAT)INT_MIN) | 
|  | { | 
|  | WARN("Error parsing line '%s'\n", buffer); | 
|  | *p_ret = 0; | 
|  | *p_found = FALSE; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | *p_ret = (INT)f; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  ReadString | 
|  | * | 
|  | *  Returns FALSE on I/O error or memory allocation failure; sets *p_str to NULL | 
|  | *  if line cannot be found or can't be parsed. | 
|  | * | 
|  | */ | 
|  | static BOOL ReadString(FILE *file, CHAR buffer[], INT bufsize, LPCSTR key, | 
|  | LPSTR *p_str) | 
|  | { | 
|  | CHAR    *cp; | 
|  |  | 
|  | if (FindLine(file, buffer, bufsize, key) == FALSE) | 
|  | return FALSE; | 
|  |  | 
|  | if (buffer[0] == '\0') | 
|  | { | 
|  | *p_str = NULL; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | cp = buffer + strlen(key);	    	    	    /* first char after key */ | 
|  | if (*cp == '\0') | 
|  | { | 
|  | *p_str = NULL; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | while (isspace(*cp))    	    	/* find first non-whitespace char */ | 
|  | ++cp; | 
|  |  | 
|  | *p_str = HeapAlloc(PSDRV_Heap, 0, strlen(cp) + 1); | 
|  | if (*p_str == NULL) | 
|  | return FALSE; | 
|  |  | 
|  | strcpy(*p_str, cp); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  ReadBBox | 
|  | * | 
|  | *  Similar to ReadFloat above. | 
|  | * | 
|  | */ | 
|  | static BOOL ReadBBox(FILE *file, CHAR buffer[], INT bufsize, AFM *afm, | 
|  | BOOL *p_found) | 
|  | { | 
|  | CHAR    *cp, *end_ptr; | 
|  | double  d; | 
|  |  | 
|  | if (FindLine(file, buffer, bufsize, "FontBBox") == FALSE) | 
|  | return FALSE; | 
|  |  | 
|  | if (buffer[0] == '\0') | 
|  | { | 
|  | *p_found = FALSE; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | errno = 0; | 
|  |  | 
|  | cp = buffer + sizeof("FontBBox"); | 
|  | d = strtod(cp, &end_ptr); | 
|  | if (end_ptr == cp || errno != 0 || | 
|  | DoubleToFloat(&(afm->FontBBox.llx), d) == FALSE) | 
|  | goto parse_error; | 
|  |  | 
|  | cp = end_ptr; | 
|  | d = strtod(cp, &end_ptr); | 
|  | if (end_ptr == cp || errno != 0 || | 
|  | DoubleToFloat(&(afm->FontBBox.lly), d) == FALSE) | 
|  | goto parse_error; | 
|  |  | 
|  | cp = end_ptr; | 
|  | d = strtod(cp, &end_ptr); | 
|  | if (end_ptr == cp || errno != 0 | 
|  | || DoubleToFloat(&(afm->FontBBox.urx), d) == FALSE) | 
|  | goto parse_error; | 
|  |  | 
|  | cp = end_ptr; | 
|  | d = strtod(cp, &end_ptr); | 
|  | if (end_ptr == cp || errno != 0 | 
|  | || DoubleToFloat(&(afm->FontBBox.ury), d) == FALSE) | 
|  | goto parse_error; | 
|  |  | 
|  | *p_found = TRUE; | 
|  | return TRUE; | 
|  |  | 
|  | parse_error: | 
|  | WARN("Error parsing line '%s'\n", buffer); | 
|  | *p_found = FALSE; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  ReadWeight | 
|  | * | 
|  | *  Finds and parses the 'Weight' line of an AFM file.  Only tries to determine | 
|  | *  if a font is bold (FW_BOLD) or not (FW_NORMAL) -- ignoring all those cute | 
|  | *  little FW_* typedefs in the Win32 doc.  AFAICT, this is what the Windows | 
|  | *  PostScript driver does. | 
|  | * | 
|  | */ | 
|  | static const struct { LPCSTR keyword; INT weight; } afm_weights[] = | 
|  | { | 
|  | { "REGULAR",	FW_NORMAL }, | 
|  | { "NORMAL",         FW_NORMAL }, | 
|  | { "ROMAN",	    	FW_NORMAL }, | 
|  | { "BOLD",	    	FW_BOLD }, | 
|  | { "BOOK",	    	FW_NORMAL }, | 
|  | { "MEDIUM",     	FW_NORMAL }, | 
|  | { "LIGHT",	    	FW_NORMAL }, | 
|  | { "BLACK",	    	FW_BOLD }, | 
|  | { "HEAVY",	    	FW_BOLD }, | 
|  | { "DEMI",	    	FW_BOLD }, | 
|  | { "ULTRA",	    	FW_BOLD }, | 
|  | { "SUPER" ,     	FW_BOLD }, | 
|  | { NULL, 	    	0 } | 
|  | }; | 
|  |  | 
|  | static BOOL ReadWeight(FILE *file, CHAR buffer[], INT bufsize, AFM *afm, | 
|  | BOOL *p_found) | 
|  | { | 
|  | LPSTR   sz; | 
|  | CHAR    *cp; | 
|  | INT     i; | 
|  |  | 
|  | if (ReadString(file, buffer, bufsize, "Weight", &sz) == FALSE) | 
|  | return FALSE; | 
|  |  | 
|  | if (sz == NULL) | 
|  | { | 
|  | *p_found = FALSE; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | for (cp = sz; *cp != '\0'; ++cp) | 
|  | *cp = toupper(*cp); | 
|  |  | 
|  | for (i = 0; afm_weights[i].keyword != NULL; ++i) | 
|  | { | 
|  | if (strstr(sz, afm_weights[i].keyword) != NULL) | 
|  | { | 
|  | afm->Weight = afm_weights[i].weight; | 
|  | *p_found = TRUE; | 
|  | HeapFree(PSDRV_Heap, 0, sz); | 
|  | return TRUE; | 
|  | } | 
|  | } | 
|  |  | 
|  | WARN("Unknown weight '%s'; treating as Roman\n", sz); | 
|  |  | 
|  | afm->Weight = FW_NORMAL; | 
|  | *p_found = TRUE; | 
|  | HeapFree(PSDRV_Heap, 0, sz); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  ReadFixedPitch | 
|  | * | 
|  | */ | 
|  | static BOOL ReadFixedPitch(FILE *file, CHAR buffer[], INT bufsize, AFM *afm, | 
|  | BOOL *p_found) | 
|  | { | 
|  | LPSTR   sz; | 
|  |  | 
|  | if (ReadString(file, buffer, bufsize, "IsFixedPitch", &sz) == FALSE) | 
|  | return FALSE; | 
|  |  | 
|  | if (sz == NULL) | 
|  | { | 
|  | *p_found = FALSE; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | if (strcasecmp(sz, "false") == 0) | 
|  | { | 
|  | afm->IsFixedPitch = FALSE; | 
|  | *p_found = TRUE; | 
|  | HeapFree(PSDRV_Heap, 0, sz); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | if (strcasecmp(sz, "true") == 0) | 
|  | { | 
|  | afm->IsFixedPitch = TRUE; | 
|  | *p_found = TRUE; | 
|  | HeapFree(PSDRV_Heap, 0, sz); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | WARN("Can't parse line '%s'\n", buffer); | 
|  |  | 
|  | *p_found = FALSE; | 
|  | HeapFree(PSDRV_Heap, 0, sz); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  ReadFontMetrics | 
|  | * | 
|  | *  Allocates space for the AFM on the driver heap and reads basic font metrics. | 
|  | *  Returns FALSE for memory allocation failure; sets *p_afm to NULL if AFM file | 
|  | *  is unusable. | 
|  | * | 
|  | */ | 
|  | static BOOL ReadFontMetrics(FILE *file, CHAR buffer[], INT bufsize, AFM **p_afm) | 
|  | { | 
|  | AFM     *afm; | 
|  | BOOL    retval, found; | 
|  |  | 
|  | *p_afm = afm = HeapAlloc(PSDRV_Heap, 0, sizeof(*afm)); | 
|  | if (afm == NULL) | 
|  | return FALSE; | 
|  |  | 
|  | retval = ReadWeight(file, buffer, bufsize, afm, &found); | 
|  | if (retval == FALSE || found == FALSE) | 
|  | goto cleanup_afm; | 
|  |  | 
|  | retval = ReadFloat(file, buffer, bufsize, "ItalicAngle", | 
|  | &(afm->ItalicAngle), &found); | 
|  | if (retval == FALSE || found == FALSE) | 
|  | goto cleanup_afm; | 
|  |  | 
|  | retval = ReadFixedPitch(file, buffer, bufsize, afm, &found); | 
|  | if (retval == FALSE || found == FALSE) | 
|  | goto cleanup_afm; | 
|  |  | 
|  | retval = ReadBBox(file, buffer, bufsize, afm, &found); | 
|  | if (retval == FALSE || found == FALSE) | 
|  | goto cleanup_afm; | 
|  |  | 
|  | retval = ReadFloat(file, buffer, bufsize, "UnderlinePosition", | 
|  | &(afm->UnderlinePosition), &found); | 
|  | if (retval == FALSE || found == FALSE) | 
|  | goto cleanup_afm; | 
|  |  | 
|  | retval = ReadFloat(file, buffer, bufsize, "UnderlineThickness", | 
|  | &(afm->UnderlineThickness), &found); | 
|  | if (retval == FALSE || found == FALSE) | 
|  | goto cleanup_afm; | 
|  |  | 
|  | retval = ReadFloat(file, buffer, bufsize, "Ascender",    	/* optional */ | 
|  | &(afm->Ascender), &found); | 
|  | if (retval == FALSE) | 
|  | goto cleanup_afm; | 
|  |  | 
|  | retval = ReadFloat(file, buffer, bufsize, "Descender",   	/* optional */ | 
|  | &(afm->Descender), &found); | 
|  | if (retval == FALSE) | 
|  | goto cleanup_afm; | 
|  |  | 
|  | afm->WinMetrics.usUnitsPerEm = 1000; | 
|  | afm->WinMetrics.sTypoAscender = (SHORT)Round(afm->Ascender); | 
|  | afm->WinMetrics.sTypoDescender = (SHORT)Round(afm->Descender); | 
|  |  | 
|  | if (afm->WinMetrics.sTypoAscender == 0) | 
|  | afm->WinMetrics.sTypoAscender = (SHORT)Round(afm->FontBBox.ury); | 
|  |  | 
|  | if (afm->WinMetrics.sTypoDescender == 0) | 
|  | afm->WinMetrics.sTypoDescender = (SHORT)Round(afm->FontBBox.lly); | 
|  |  | 
|  | afm->WinMetrics.sTypoLineGap = 1200 - | 
|  | (afm->WinMetrics.sTypoAscender - afm->WinMetrics.sTypoDescender); | 
|  | if (afm->WinMetrics.sTypoLineGap < 0) | 
|  | afm->WinMetrics.sTypoLineGap = 0; | 
|  |  | 
|  | return TRUE; | 
|  |  | 
|  | cleanup_afm:    	    	    	/* handle fatal or non-fatal errors */ | 
|  | HeapFree(PSDRV_Heap, 0, afm); | 
|  | *p_afm = NULL; | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  ParseC | 
|  | * | 
|  | *  Fatal error:    	return FALSE (none defined) | 
|  | * | 
|  | *  Non-fatal error:	leave metrics->C set to INT_MAX | 
|  | * | 
|  | */ | 
|  | static BOOL ParseC(LPSTR sz, OLD_AFMMETRICS *metrics) | 
|  | { | 
|  | int     base = 10; | 
|  | long    l; | 
|  | CHAR    *cp, *end_ptr; | 
|  |  | 
|  | cp = sz + 1; | 
|  |  | 
|  | if (*cp == 'H') | 
|  | { | 
|  | base = 16; | 
|  | ++cp; | 
|  | } | 
|  |  | 
|  | errno = 0; | 
|  | l = strtol(cp, &end_ptr, base); | 
|  | if (end_ptr == cp || errno != 0 || l > INT_MAX || l < INT_MIN) | 
|  | { | 
|  | WARN("Error parsing character code '%s'\n", sz); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | metrics->C = (INT)l; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  ParseW | 
|  | * | 
|  | *  Fatal error:    	return FALSE (none defined) | 
|  | * | 
|  | *  Non-fatal error:	leave metrics->WX set to FLT_MAX | 
|  | * | 
|  | */ | 
|  | static BOOL ParseW(LPSTR sz, OLD_AFMMETRICS *metrics) | 
|  | { | 
|  | CHAR    *cp, *end_ptr; | 
|  | BOOL    vector = TRUE; | 
|  | double  d; | 
|  |  | 
|  | cp = sz + 1; | 
|  |  | 
|  | if (*cp == '0') | 
|  | ++cp; | 
|  |  | 
|  | if (*cp == 'X') | 
|  | { | 
|  | vector = FALSE; | 
|  | ++cp; | 
|  | } | 
|  |  | 
|  | if (!isspace(*cp)) | 
|  | goto parse_error; | 
|  |  | 
|  | errno = 0; | 
|  | d = strtod(cp, &end_ptr); | 
|  | if (end_ptr == cp || errno != 0 || | 
|  | DoubleToFloat(&(metrics->WX), d) == FALSE) | 
|  | goto parse_error; | 
|  |  | 
|  | if (vector == FALSE) | 
|  | return TRUE; | 
|  |  | 
|  | /*	Make sure that Y component of vector is zero */ | 
|  |  | 
|  | d = strtod(cp, &end_ptr);	    	    	    	    /* errno == 0 */ | 
|  | if (end_ptr == cp || errno != 0 || d != 0.0) | 
|  | { | 
|  | metrics->WX = FLT_MAX; | 
|  | goto parse_error; | 
|  | } | 
|  |  | 
|  | return TRUE; | 
|  |  | 
|  | parse_error: | 
|  | WARN("Error parsing character width '%s'\n", sz); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | * | 
|  | *  ParseB | 
|  | * | 
|  | *  Fatal error:    	return FALSE (none defined) | 
|  | * | 
|  | *  Non-fatal error:	leave metrics->B.ury set to FLT_MAX | 
|  | * | 
|  | */ | 
|  | static BOOL ParseB(LPSTR sz, OLD_AFMMETRICS *metrics) | 
|  | { | 
|  | CHAR    *cp, *end_ptr; | 
|  | double  d; | 
|  |  | 
|  | errno = 0; | 
|  |  | 
|  | cp = sz + 1; | 
|  | d = strtod(cp, &end_ptr); | 
|  | if (end_ptr == cp || errno != 0 || | 
|  | DoubleToFloat(&(metrics->B.llx), d) == FALSE) | 
|  | goto parse_error; | 
|  |  | 
|  | cp = end_ptr; | 
|  | d = strtod(cp, &end_ptr); | 
|  | if (end_ptr == cp || errno != 0 || | 
|  | DoubleToFloat(&(metrics->B.lly), d) == FALSE) | 
|  | goto parse_error; | 
|  |  | 
|  | cp = end_ptr; | 
|  | d = strtod(cp, &end_ptr); | 
|  | if (end_ptr == cp || errno != 0 || | 
|  | DoubleToFloat(&(metrics->B.urx), d) == FALSE) | 
|  | goto parse_error; | 
|  |  | 
|  | cp = end_ptr; | 
|  | d = strtod(cp, &end_ptr); | 
|  | if (end_ptr == cp || errno != 0 || | 
|  | DoubleToFloat(&(metrics->B.ury), d) == FALSE) | 
|  | goto parse_error; | 
|  |  | 
|  | return TRUE; | 
|  |  | 
|  | parse_error: | 
|  | WARN("Error parsing glyph bounding box '%s'\n", sz); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  ParseN | 
|  | * | 
|  | *  Fatal error:    	return FALSE (PSDRV_GlyphName failure) | 
|  | * | 
|  | *  Non-fatal error:	leave metrics-> set to NULL | 
|  | * | 
|  | */ | 
|  | static BOOL ParseN(LPSTR sz, OLD_AFMMETRICS *metrics) | 
|  | { | 
|  | CHAR    save, *cp, *end_ptr; | 
|  |  | 
|  | cp = sz + 1; | 
|  |  | 
|  | while (isspace(*cp)) | 
|  | ++cp; | 
|  |  | 
|  | end_ptr = cp; | 
|  |  | 
|  | while (*end_ptr != '\0' && !isspace(*end_ptr)) | 
|  | ++end_ptr; | 
|  |  | 
|  | if (end_ptr == cp) | 
|  | { | 
|  | WARN("Error parsing glyph name '%s'\n", sz); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | save = *end_ptr; | 
|  | *end_ptr = '\0'; | 
|  |  | 
|  | metrics->N = PSDRV_GlyphName(cp); | 
|  | if (metrics->N == NULL) | 
|  | return FALSE; | 
|  |  | 
|  | *end_ptr = save; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  ParseCharMetrics | 
|  | * | 
|  | *  Parses the metrics line for a single glyph in an AFM file.  Returns FALSE on | 
|  | *  fatal error; sets *metrics to 'badmetrics' on non-fatal error. | 
|  | * | 
|  | */ | 
|  | static const OLD_AFMMETRICS badmetrics = | 
|  | { | 
|  | INT_MAX,	    	    	    	    	    /* C */ | 
|  | INT_MAX,	    	    	    	    	    /* UV */ | 
|  | FLT_MAX,	    	    	    	    	    /* WX */ | 
|  | NULL,   	    	    	    	    	    /* N */ | 
|  | { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX }, 	    /* B */ | 
|  | NULL    	    	    	    	    	    /* L */ | 
|  | }; | 
|  |  | 
|  | static BOOL ParseCharMetrics(LPSTR buffer, INT len, OLD_AFMMETRICS *metrics) | 
|  | { | 
|  | CHAR    *cp = buffer; | 
|  |  | 
|  | *metrics = badmetrics; | 
|  |  | 
|  | while (*cp != '\0') | 
|  | { | 
|  | while (isspace(*cp)) | 
|  | ++cp; | 
|  |  | 
|  | switch(*cp) | 
|  | { | 
|  | case 'C':	if (ParseC(cp, metrics) == FALSE) | 
|  | return FALSE; | 
|  | break; | 
|  |  | 
|  | case 'W':	if (ParseW(cp, metrics) == FALSE) | 
|  | return FALSE; | 
|  | break; | 
|  |  | 
|  | case 'N':	if (ParseN(cp, metrics) == FALSE) | 
|  | return FALSE; | 
|  | break; | 
|  |  | 
|  | case 'B':	if (ParseB(cp, metrics) == FALSE) | 
|  | return FALSE; | 
|  | break; | 
|  | } | 
|  |  | 
|  | cp = strchr(cp, ';'); | 
|  | if (cp == NULL) | 
|  | { | 
|  | WARN("No terminating semicolon\n"); | 
|  | break; | 
|  | } | 
|  |  | 
|  | ++cp; | 
|  | } | 
|  |  | 
|  | if (metrics->C == INT_MAX || metrics->WX == FLT_MAX || metrics->N == NULL || | 
|  | metrics->B.ury == FLT_MAX) | 
|  | { | 
|  | *metrics = badmetrics; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  IsWinANSI | 
|  | * | 
|  | *  Checks whether Unicode value is part of Microsoft code page 1252 | 
|  | * | 
|  | */ | 
|  | static const LONG ansiChars[21] = | 
|  | { | 
|  | 0x0152, 0x0153, 0x0160, 0x0161, 0x0178, 0x017d, 0x017e, 0x0192, 0x02c6, | 
|  | 0x02c9, 0x02dc, 0x03bc, 0x2013, 0x2014, 0x2026, 0x2030, 0x2039, 0x203a, | 
|  | 0x20ac, 0x2122, 0x2219 | 
|  | }; | 
|  |  | 
|  | static int cmpUV(const void *a, const void *b) | 
|  | { | 
|  | return (int)(*((const LONG *)a) - *((const LONG *)b)); | 
|  | } | 
|  |  | 
|  | static inline BOOL IsWinANSI(LONG uv) | 
|  | { | 
|  | if ((0x0020 <= uv && uv <= 0x007e) || (0x00a0 <= uv && uv <= 0x00ff) || | 
|  | (0x2018 <= uv && uv <= 0x201a) || (0x201c <= uv && uv <= 0x201e) || | 
|  | (0x2020 <= uv && uv <= 0x2022)) | 
|  | return TRUE; | 
|  |  | 
|  | if (bsearch(&uv, ansiChars, 21, sizeof(INT), cmpUV) != NULL) | 
|  | return TRUE; | 
|  |  | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  Unicodify | 
|  | * | 
|  | *  Determines Unicode value (UV) for each glyph, based on font encoding. | 
|  | * | 
|  | *  	FontSpecific:	Usable encodings (0x20 - 0xff) are mapped into the | 
|  | *  	    	    	Unicode private use range U+F020 - U+F0FF. | 
|  | * | 
|  | *  	other:	    	UV determined by glyph name, based on Adobe Glyph List. | 
|  | * | 
|  | *  Also does some font metric calculations that require UVs to be known. | 
|  | * | 
|  | */ | 
|  | static int UnicodeGlyphByNameIndex(const void *a, const void *b) | 
|  | { | 
|  | return ((const UNICODEGLYPH *)a)->name->index - | 
|  | ((const UNICODEGLYPH *)b)->name->index; | 
|  | } | 
|  |  | 
|  | static VOID Unicodify(AFM *afm, OLD_AFMMETRICS *metrics) | 
|  | { | 
|  | INT     i; | 
|  |  | 
|  | if (strcmp(afm->EncodingScheme, "FontSpecific") == 0) | 
|  | { | 
|  | for (i = 0; i < afm->NumofMetrics; ++i) | 
|  | { | 
|  | if (metrics[i].C >= 0x20 && metrics[i].C <= 0xff) | 
|  | { | 
|  | metrics[i].UV = metrics[i].C | 0xf000L; | 
|  | } | 
|  | else | 
|  | { | 
|  | TRACE("Unencoded glyph '%s'\n", metrics[i].N->sz); | 
|  | metrics[i].UV = -1L; | 
|  | } | 
|  | } | 
|  |  | 
|  | afm->WinMetrics.sAscender = (SHORT)Round(afm->FontBBox.ury); | 
|  | afm->WinMetrics.sDescender = (SHORT)Round(afm->FontBBox.lly); | 
|  | } | 
|  | else    	    	    	    	    	/* non-FontSpecific encoding */ | 
|  | { | 
|  | UNICODEGLYPH	ug, *p_ug; | 
|  |  | 
|  | PSDRV_IndexGlyphList();     	/* for fast searching of glyph names */ | 
|  |  | 
|  | afm->WinMetrics.sAscender = afm->WinMetrics.sDescender = 0; | 
|  |  | 
|  | for (i = 0; i < afm->NumofMetrics; ++i) | 
|  | { | 
|  | ug.name = metrics[i].N; | 
|  | p_ug = bsearch(&ug, PSDRV_AGLbyName, PSDRV_AGLbyNameSize, | 
|  | sizeof(ug), UnicodeGlyphByNameIndex); | 
|  | if (p_ug == NULL) | 
|  | { | 
|  | TRACE("Glyph '%s' not in Adobe Glyph List\n", ug.name->sz); | 
|  | metrics[i].UV = -1L; | 
|  | } | 
|  | else | 
|  | { | 
|  | metrics[i].UV = p_ug->UV; | 
|  |  | 
|  | if (IsWinANSI(p_ug->UV)) | 
|  | { | 
|  | SHORT   ury = (SHORT)Round(metrics[i].B.ury); | 
|  | SHORT   lly = (SHORT)Round(metrics[i].B.lly); | 
|  |  | 
|  | if (ury > afm->WinMetrics.sAscender) | 
|  | afm->WinMetrics.sAscender = ury; | 
|  | if (lly < afm->WinMetrics.sDescender) | 
|  | afm->WinMetrics.sDescender = lly; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (afm->WinMetrics.sAscender == 0) | 
|  | afm->WinMetrics.sAscender = (SHORT)Round(afm->FontBBox.ury); | 
|  | if (afm->WinMetrics.sDescender == 0) | 
|  | afm->WinMetrics.sDescender = (SHORT)Round(afm->FontBBox.lly); | 
|  | } | 
|  |  | 
|  | afm->WinMetrics.sLineGap = | 
|  | 1150 - (afm->WinMetrics.sAscender - afm->WinMetrics.sDescender); | 
|  | if (afm->WinMetrics.sLineGap < 0) | 
|  | afm->WinMetrics.sLineGap = 0; | 
|  |  | 
|  | afm->WinMetrics.usWinAscent = (afm->WinMetrics.sAscender > 0) ? | 
|  | afm->WinMetrics.sAscender : 0; | 
|  | afm->WinMetrics.usWinDescent = (afm->WinMetrics.sDescender < 0) ? | 
|  | -(afm->WinMetrics.sDescender) : 0; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  ReadCharMetrics | 
|  | * | 
|  | *  Reads metrics for all glyphs.  *p_metrics will be NULL on non-fatal error. | 
|  | * | 
|  | */ | 
|  | static int OldAFMMetricsByUV(const void *a, const void *b) | 
|  | { | 
|  | return ((const OLD_AFMMETRICS *)a)->UV - ((const OLD_AFMMETRICS *)b)->UV; | 
|  | } | 
|  |  | 
|  | static BOOL ReadCharMetrics(FILE *file, CHAR buffer[], INT bufsize, AFM *afm, | 
|  | AFMMETRICS **p_metrics) | 
|  | { | 
|  | BOOL    	    retval, found; | 
|  | OLD_AFMMETRICS  *old_metrics, *encoded_metrics; | 
|  | AFMMETRICS	    *metrics; | 
|  | INT     	    i, len; | 
|  |  | 
|  | retval = ReadInt(file, buffer, bufsize, "StartCharMetrics", | 
|  | &(afm->NumofMetrics), &found); | 
|  | if (retval == FALSE || found == FALSE) | 
|  | { | 
|  | *p_metrics = NULL; | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | old_metrics = HeapAlloc(PSDRV_Heap, 0, | 
|  | afm->NumofMetrics * sizeof(*old_metrics)); | 
|  | if (old_metrics == NULL) | 
|  | return FALSE; | 
|  |  | 
|  | for (i = 0; i < afm->NumofMetrics; ++i) | 
|  | { | 
|  | retval = ReadLine(file, buffer, bufsize, &len); | 
|  | if (retval == FALSE) | 
|  | goto cleanup_old_metrics; | 
|  |  | 
|  | if(len > 0) | 
|  | { | 
|  | retval = ParseCharMetrics(buffer, len, old_metrics + i); | 
|  | if (retval == FALSE || old_metrics[i].C == INT_MAX) | 
|  | goto cleanup_old_metrics; | 
|  |  | 
|  | continue; | 
|  | } | 
|  |  | 
|  | switch (len) | 
|  | { | 
|  | case 0: 	    --i; | 
|  | continue; | 
|  |  | 
|  | case INT_MIN:   WARN("Ignoring long line '%32s...'\n", buffer); | 
|  | goto cleanup_old_metrics;	    /* retval == TRUE */ | 
|  |  | 
|  | case EOF:	    WARN("Unexpected EOF\n"); | 
|  | goto cleanup_old_metrics;  	    /* retval == TRUE */ | 
|  | } | 
|  | } | 
|  |  | 
|  | Unicodify(afm, old_metrics);    /* wait until glyph names have been read */ | 
|  |  | 
|  | qsort(old_metrics, afm->NumofMetrics, sizeof(*old_metrics), | 
|  | OldAFMMetricsByUV); | 
|  |  | 
|  | for (i = 0; old_metrics[i].UV == -1; ++i);      /* count unencoded glyphs */ | 
|  |  | 
|  | afm->NumofMetrics -= i; | 
|  | encoded_metrics = old_metrics + i; | 
|  |  | 
|  | afm->Metrics = *p_metrics = metrics = HeapAlloc(PSDRV_Heap, 0, | 
|  | afm->NumofMetrics * sizeof(*metrics)); | 
|  | if (afm->Metrics == NULL) | 
|  | goto cleanup_old_metrics;   	    	    	    /* retval == TRUE */ | 
|  |  | 
|  | for (i = 0; i < afm->NumofMetrics; ++i, ++metrics, ++encoded_metrics) | 
|  | { | 
|  | metrics->C = encoded_metrics->C; | 
|  | metrics->UV = encoded_metrics->UV; | 
|  | metrics->WX = encoded_metrics->WX; | 
|  | metrics->N = encoded_metrics->N; | 
|  | } | 
|  |  | 
|  | HeapFree(PSDRV_Heap, 0, old_metrics); | 
|  |  | 
|  | afm->WinMetrics.sAvgCharWidth = PSDRV_CalcAvgCharWidth(afm); | 
|  |  | 
|  | return TRUE; | 
|  |  | 
|  | cleanup_old_metrics:    	    	/* handle fatal or non-fatal errors */ | 
|  | HeapFree(PSDRV_Heap, 0, old_metrics); | 
|  | *p_metrics = NULL; | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  BuildAFM | 
|  | * | 
|  | *  Builds the AFM for a PostScript font and adds it to the driver font list. | 
|  | *  Returns FALSE only on an unexpected error (memory allocation or I/O error). | 
|  | * | 
|  | */ | 
|  | static BOOL BuildAFM(FILE *file) | 
|  | { | 
|  | CHAR    	buffer[258];    	/* allow for <cr>, <lf>, and <nul> */ | 
|  | AFM     	*afm; | 
|  | AFMMETRICS	*metrics; | 
|  | LPSTR   	font_name, full_name, family_name, encoding_scheme; | 
|  | BOOL    	retval, added; | 
|  |  | 
|  | retval = ReadFontMetrics(file, buffer, sizeof(buffer), &afm); | 
|  | if (retval == FALSE || afm == NULL) | 
|  | return retval; | 
|  |  | 
|  | retval = ReadString(file, buffer, sizeof(buffer), "FontName", &font_name); | 
|  | if (retval == FALSE || font_name == NULL) | 
|  | goto cleanup_afm; | 
|  |  | 
|  | retval = ReadString(file, buffer, sizeof(buffer), "FullName", &full_name); | 
|  | if (retval == FALSE || full_name == NULL) | 
|  | goto cleanup_font_name; | 
|  |  | 
|  | retval = ReadString(file, buffer, sizeof(buffer), "FamilyName", | 
|  | &family_name); | 
|  | if (retval == FALSE || family_name == NULL) | 
|  | goto cleanup_full_name; | 
|  |  | 
|  | retval = ReadString(file, buffer, sizeof(buffer), "EncodingScheme", | 
|  | &encoding_scheme); | 
|  | if (retval == FALSE || encoding_scheme == NULL) | 
|  | goto cleanup_family_name; | 
|  |  | 
|  | afm->FontName = font_name; | 
|  | afm->FullName = full_name; | 
|  | afm->FamilyName = family_name; | 
|  | afm->EncodingScheme = encoding_scheme; | 
|  |  | 
|  | retval = ReadCharMetrics(file, buffer, sizeof(buffer), afm, &metrics); | 
|  | if (retval == FALSE || metrics == FALSE) | 
|  | goto cleanup_encoding_scheme; | 
|  |  | 
|  | retval = PSDRV_AddAFMtoList(&PSDRV_AFMFontList, afm, &added); | 
|  | if (retval == FALSE || added == FALSE) | 
|  | goto cleanup_encoding_scheme; | 
|  |  | 
|  | return TRUE; | 
|  |  | 
|  | /* clean up after fatal or non-fatal errors */ | 
|  |  | 
|  | cleanup_encoding_scheme: | 
|  | HeapFree(PSDRV_Heap, 0, encoding_scheme); | 
|  | cleanup_family_name: | 
|  | HeapFree(PSDRV_Heap, 0, family_name); | 
|  | cleanup_full_name: | 
|  | HeapFree(PSDRV_Heap, 0, full_name); | 
|  | cleanup_font_name: | 
|  | HeapFree(PSDRV_Heap, 0, font_name); | 
|  | cleanup_afm: | 
|  | HeapFree(PSDRV_Heap, 0, afm); | 
|  |  | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  ReadAFMFile | 
|  | * | 
|  | *  Reads font metrics from Type 1 AFM file.  Only returns FALSE for | 
|  | *  unexpected errors (memory allocation or I/O). | 
|  | * | 
|  | */ | 
|  | static BOOL ReadAFMFile(LPCSTR filename) | 
|  | { | 
|  | FILE    *f; | 
|  | BOOL    retval; | 
|  |  | 
|  | TRACE("%s\n", filename); | 
|  |  | 
|  | f = fopen(filename, "r"); | 
|  | if (f == NULL) | 
|  | { | 
|  | WARN("%s: %s\n", filename, strerror(errno)); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | retval = BuildAFM(f); | 
|  |  | 
|  | fclose(f); | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  ReadAFMDir | 
|  | * | 
|  | *  Reads all Type 1 AFM files in a directory. | 
|  | * | 
|  | */ | 
|  | static BOOL ReadAFMDir(LPCSTR dirname) | 
|  | { | 
|  | struct dirent   *dent; | 
|  | DIR     	    *dir; | 
|  | CHAR    	    filename[256]; | 
|  |  | 
|  | dir = opendir(dirname); | 
|  | if (dir == NULL) | 
|  | { | 
|  | WARN("%s: %s\n", dirname, strerror(errno)); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | while ((dent = readdir(dir)) != NULL) | 
|  | { | 
|  | CHAR	*file_extension = strchr(dent->d_name, '.'); | 
|  | int 	fn_len; | 
|  |  | 
|  | if (file_extension == NULL || strcasecmp(file_extension, ".afm") != 0) | 
|  | continue; | 
|  |  | 
|  | fn_len = snprintf(filename, 256, "%s/%s", dirname, dent->d_name); | 
|  | if (fn_len < 0 || fn_len > sizeof(filename) - 1) | 
|  | { | 
|  | WARN("Path '%s/%s' is too long\n", dirname, dent->d_name); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (ReadAFMFile(filename) == FALSE) | 
|  | { | 
|  | closedir(dir); | 
|  | return FALSE; | 
|  | } | 
|  | } | 
|  |  | 
|  | closedir(dir); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | *  PSDRV_GetType1Metrics | 
|  | * | 
|  | *  Reads font metrics from Type 1 AFM font files in directories listed in the | 
|  | *  HKEY_CURRENT_USER\\Software\\Wine\\Fonts\\AFMPath registry string. | 
|  | * | 
|  | *  If this function fails (returns FALSE), the driver will fail to initialize | 
|  | *  and the driver heap will be destroyed, so it's not necessary to HeapFree | 
|  | *  everything in that event. | 
|  | * | 
|  | */ | 
|  | BOOL PSDRV_GetType1Metrics(void) | 
|  | { | 
|  | static const WCHAR pathW[] = {'A','F','M','P','a','t','h',0}; | 
|  | HKEY hkey; | 
|  | DWORD len; | 
|  | LPWSTR valueW; | 
|  | LPSTR valueA, ptr; | 
|  |  | 
|  | /* @@ Wine registry key: HKCU\Software\Wine\Fonts */ | 
|  | if (RegOpenKeyA(HKEY_CURRENT_USER, "Software\\Wine\\Fonts", &hkey) != ERROR_SUCCESS) | 
|  | return TRUE; | 
|  |  | 
|  | if (RegQueryValueExW( hkey, pathW, NULL, NULL, NULL, &len ) == ERROR_SUCCESS) | 
|  | { | 
|  | len += sizeof(WCHAR); | 
|  | valueW = HeapAlloc( PSDRV_Heap, 0, len ); | 
|  | if (RegQueryValueExW( hkey, pathW, NULL, NULL, (LPBYTE)valueW, &len ) == ERROR_SUCCESS) | 
|  | { | 
|  | len = WideCharToMultiByte( CP_UNIXCP, 0, valueW, -1, NULL, 0, NULL, NULL ); | 
|  | valueA = HeapAlloc( PSDRV_Heap, 0, len ); | 
|  | WideCharToMultiByte( CP_UNIXCP, 0, valueW, -1, valueA, len, NULL, NULL ); | 
|  | TRACE( "got AFM font path %s\n", debugstr_a(valueA) ); | 
|  | ptr = valueA; | 
|  | while (ptr) | 
|  | { | 
|  | LPSTR next = strchr( ptr, ':' ); | 
|  | if (next) *next++ = 0; | 
|  | if (!ReadAFMDir( ptr )) | 
|  | { | 
|  | RegCloseKey(hkey); | 
|  | return FALSE; | 
|  | } | 
|  | ptr = next; | 
|  | } | 
|  | HeapFree( PSDRV_Heap, 0, valueA ); | 
|  | } | 
|  | HeapFree( PSDRV_Heap, 0, valueW ); | 
|  | } | 
|  |  | 
|  | RegCloseKey(hkey); | 
|  | return TRUE; | 
|  | } |