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