/******************************************************************************* * 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; }