| /******************************************************************************* | |
| * Adobe Font Metric (AFM) file parsing finctions 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 <string.h> | |
| #include <stdlib.h> | |
| #include <stdio.h> | |
| #include <dirent.h> | |
| #include <errno.h> | |
| #include <ctype.h> | |
| #include <limits.h> /* INT_MIN */ | |
| #ifdef HAVE_FLOAT_H | |
| #include <float.h> /* FLT_MAX */ | |
| #endif | |
| #include "winnt.h" | |
| #include "winerror.h" | |
| #include "winreg.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). | |
| * | |
| */ | |
| inline static 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. | |
| * | |
| */ | |
| inline static 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 */ | |
| LONG_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)); | |
| } | |
| inline static 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 = ((LONG)(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 | |
| * [afmdirs] section of the Wine configuration file. | |
| * | |
| * If this function fails (returns FALSE), the dirver 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) | |
| { | |
| CHAR name_buf[256], value_buf[256]; | |
| INT i = 0; | |
| HKEY hkey; | |
| DWORD type, name_len, value_len; | |
| if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, | |
| "Software\\Wine\\Wine\\Config\\afmdirs", | |
| 0, KEY_READ, &hkey) != ERROR_SUCCESS) | |
| return TRUE; | |
| name_len = sizeof(name_buf); | |
| value_len = sizeof(value_buf); | |
| while (RegEnumValueA(hkey, i++, name_buf, &name_len, NULL, &type, value_buf, | |
| &value_len) == ERROR_SUCCESS) | |
| { | |
| value_buf[sizeof(value_buf) - 1] = '\0'; | |
| if (ReadAFMDir(value_buf) == FALSE) | |
| { | |
| RegCloseKey(hkey); | |
| return FALSE; | |
| } | |
| /* initialize lengths for new iteration */ | |
| name_len = sizeof(name_buf); | |
| value_len = sizeof(value_buf); | |
| } | |
| RegCloseKey(hkey); | |
| return TRUE; | |
| } |