1216 lines
30 KiB
C
1216 lines
30 KiB
C
/*******************************************************************************
|
|
* 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>
|
|
#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 "windef.h"
|
|
#include "winbase.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).
|
|
*
|
|
*/
|
|
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 */
|
|
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));
|
|
}
|
|
|
|
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 = ((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)
|
|
{
|
|
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;
|
|
}
|