/*************************************************
*    The PMW Music Typesetter - 3rd incarnation  *
*************************************************/

/* Copyright (c) Philip Hazel, 1991 - 2020 */

/* Written by Philip Hazel, starting November 1991 */
/* Re-coded for Unicode support: August 2005 */
/* This file last modified: November 2020 */


/* This file contains code for handling text fonts */


#include "pmwhdr.h"
#include "readhdr.h"


/* Entries in the character name to Unicode code point table */

typedef struct an2uencod {
  uschar *name;                /* Adobe character name */
  int code;                    /* Unicode code point */
  int poffset;                 /* Offset for printing certain chars */
} an2uencod;

/* This table translates character names from PostScript fonts that use Adobe's
standard encoding into Unicode. In addition, for characters whose Unicode
values are greater than LOWCHARLIMIT, it includes the offset above LOWCHARLIMIT
that we use for printing these characters. */

static an2uencod an2ulist[] = {
  { US"A",               0x0041, -1 },
  { US"AE",              0x00c6, -1 },
  { US"Aacute",          0x00c1, -1 },
  { US"Abreve",          0x0102, -1 },
  { US"Acircumflex",     0x00c2, -1 },
  { US"Adieresis",       0x00c4, -1 },
  { US"Agrave",          0x00c0, -1 },
  { US"Amacron",         0x0100, -1 },
  { US"Aogonek",         0x0104, -1 },
  { US"Aring",           0x00c5, -1 },
  { US"Atilde",          0x00c3, -1 },
  { US"B",               0x0042, -1 },
  { US"C",               0x0043, -1 },
  { US"Cacute",          0x0106, -1 },
  { US"Ccaron",          0x010c, -1 },
  { US"Ccedilla",        0x00c7, -1 },
  { US"Ccircumflex",     0x0108, -1 },
  { US"Cdotaccent",      0x010a, -1 },
  { US"D",               0x0044, -1 },
  { US"Dcaron",          0x010e, -1 },
  { US"Dcroat",          0x0110, -1 },
  { US"Delta",           0x0394, +0 },
  { US"E",               0x0045, -1 },
  { US"Eacute",          0x00c9, -1 },
  { US"Ebreve",          0x0114, -1 },
  { US"Ecaron",          0x011a, -1 },
  { US"Ecircumflex",     0x00ca, -1 },
  { US"Edieresis",       0x00cb, -1 },
  { US"Edotaccent",      0x0116, -1 },
  { US"Egrave",          0x00c8, -1 },
  { US"Emacron",         0x0112, -1 },
  { US"Eng",             0x014a, -1 },
  { US"Eogonek",         0x0118, -1 },
  { US"Eth",             0x00d0, -1 },
  { US"Euro",            0x20ac, +1 },
  { US"F",               0x0046, -1 },
  { US"G",               0x0047, -1 },
  { US"Gbreve",          0x011e, -1 },
  { US"Gcircumflex",     0x011c, -1 },
  { US"Gcommaaccent",    0x0122, -1 },
  { US"Gdotaccent",      0x0120, -1 },
  { US"H",               0x0048, -1 },
  { US"Hbar",            0x0126, -1 },
  { US"Hcircumflex",     0x0124, -1 },
  { US"I",               0x0049, -1 },
  { US"IJ",              0x0132, -1 },
  { US"Iacute",          0x00cd, -1 },
  { US"Ibreve",          0x012c, -1 },
  { US"Icircumflex",     0x00ce, -1 },
  { US"Idieresis",       0x00cf, -1 },
  { US"Idotaccent",      0x0130, -1 },
  { US"Igrave",          0x00cc, -1 },
  { US"Imacron",         0x012a, -1 },
  { US"Iogonek",         0x012e, -1 },
  { US"Itilde",          0x0128, -1 },
  { US"J",               0x004a, -1 },
  { US"Jcircumflex",     0x0134, -1 },
  { US"K",               0x004b, -1 },
  { US"Kcommaaccent",    0x0136, -1 },
  { US"L",               0x004c, -1 },
  { US"Lacute",          0x0139, -1 },
  { US"Lcaron",          0x013d, -1 },
  { US"Lcommaaccent",    0x013b, -1 },
  { US"Ldot",            0x013f, -1 },
  { US"Lslash",          0x0141, -1 },
  { US"M",               0x004d, -1 },
  { US"N",               0x004e, -1 },
  { US"NBspace",         0x00a0, -1 },  /* Added Nov-2019 */
  { US"Nacute",          0x0143, -1 },
  { US"Ncaron",          0x0147, -1 },
  { US"Ncommaaccent",    0x0145, -1 },
  { US"Ntilde",          0x00d1, -1 },
  { US"O",               0x004f, -1 },
  { US"OE",              0x0152, -1 },
  { US"Oacute",          0x00d3, -1 },
  { US"Obreve",          0x014e, -1 },
  { US"Ocircumflex",     0x00d4, -1 },
  { US"Odieresis",       0x00d6, -1 },
  { US"Ograve",          0x00d2, -1 },
  { US"Ohungarumlaut",   0x0150, -1 },
  { US"Omacron",         0x014c, -1 },
  { US"Oslash",          0x00d8, -1 },
  { US"Otilde",          0x00d5, -1 },
  { US"P",               0x0050, -1 },
  { US"Q",               0x0051, -1 },
  { US"R",               0x0052, -1 },
  { US"Racute",          0x0154, -1 },
  { US"Rcaron",          0x0158, -1 },
  { US"Rcommaaccent",    0x0156, -1 },
  { US"S",               0x0053, -1 },
  { US"Sacute",          0x015a, -1 },
  { US"Scaron",          0x0160, -1 },
  { US"Scedilla",        0x015e, -1 },
  { US"Scircumflex",     0x015c, -1 },
  { US"Scommaaccent",    0x0218, +2 },
  { US"T",               0x0054, -1 },
  { US"Tbar",            0x0166, -1 },
  { US"Tcaron",          0x0164, -1 },
  { US"Tcedilla",        0x0162, -1 },
  { US"Tcommaaccent",    0x021a, +3 },
  { US"Thorn",           0x00de, -1 },
  { US"U",               0x0055, -1 },
  { US"Uacute",          0x00da, -1 },
  { US"Ubreve",          0x016c, -1 },
  { US"Ucircumflex",     0x00db, -1 },
  { US"Udieresis",       0x00dc, -1 },
  { US"Ugrave",          0x00d9, -1 },
  { US"Uhungarumlaut",   0x0170, -1 },
  { US"Umacron",         0x016a, -1 },
  { US"Uogonek",         0x0172, -1 },
  { US"Uring",           0x016e, -1 },
  { US"Utilde",          0x0168, -1 },
  { US"V",               0x0056, -1 },
  { US"W",               0x0057, -1 },
  { US"Wcircumflex",     0x0174, -1 },
  { US"X",               0x0058, -1 },
  { US"Y",               0x0059, -1 },
  { US"Yacute",          0x00dd, -1 },
  { US"Ycircumflex",     0x0176, -1 },
  { US"Ydieresis",       0x0178, -1 },
  { US"Z",               0x005a, -1 },
  { US"Zacute",          0x0179, -1 },
  { US"Zcaron",          0x017d, -1 },
  { US"Zdotaccent",      0x017b, -1 },
  { US"a",               0x0061, -1 },
  { US"aacute",          0x00e1, -1 },
  { US"abreve",          0x0103, -1 },
  { US"acircumflex",     0x00e2, -1 },
  { US"acute",           0x00b4, -1 },
  { US"adieresis",       0x00e4, -1 },
  { US"ae",              0x00e6, -1 },
  { US"agrave",          0x00e0, -1 },
  { US"amacron",         0x0101, -1 },
  { US"ampersand",       0x0026, -1 },
  { US"aogonek",         0x0105, -1 },
  { US"aring",           0x00e5, -1 },
  { US"asciicircum",     0x005e, -1 },
  { US"asciitilde",      0x007e, -1 },
  { US"asterisk",        0x002a, -1 },
  { US"at",              0x0040, -1 },
  { US"atilde",          0x00e3, -1 },
  { US"b",               0x0062, -1 },
  { US"backslash",       0x005c, -1 },
  { US"bar",             0x007c, -1 },
  { US"braceleft",       0x007b, -1 },
  { US"braceright",      0x007d, -1 },
  { US"bracketleft",     0x005b, -1 },
  { US"bracketright",    0x005d, -1 },
  { US"breve",           0x0306, +4 },
  { US"brokenbar",       0x00a6, -1 },
  { US"bullet",          0x00b7, -1 },
  { US"c",               0x0063, -1 },
  { US"cacute",          0x0107, -1 },
  { US"caron",           0x030c, +5 },
  { US"ccaron",          0x010d, -1 },
  { US"ccedilla",        0x00e7, -1 },
  { US"ccircumflex",     0x0109, -1 },
  { US"cdotaccent",      0x010b, -1 },
  { US"cedilla",         0x00b8, -1 },
  { US"cent",            0x00a2, -1 },
  { US"circumflex",      0x0302, +6 },
  { US"colon",           0x003a, -1 },
  { US"comma",           0x002c, -1 },
  { US"commaaccent",     0x0326, +7 },
  { US"copyright",       0x00a9, -1 },
  { US"currency",        0x00a4, -1 },
  { US"d",               0x0064, -1 },
  { US"dagger",          0x2020, +8 },
  { US"daggerdbl",       0x2021, +9 },
  { US"dcaron",          0x010f, -1 },
  { US"dcroat",          0x0111, -1 },
  { US"degree",          0x00b0, -1 },
  { US"dieresis",        0x00a8, -1 },
  { US"divide",          0x00f7, -1 },
  { US"dollar",          0x0024, -1 },
  { US"dotaccent",       0x0307, 10 },
  { US"dotlessi",        0x0131, -1 },
  { US"e",               0x0065, -1 },
  { US"eacute",          0x00e9, -1 },
  { US"ebreve",          0x0115, -1 },
  { US"ecaron",          0x011b, -1 },
  { US"ecircumflex",     0x00ea, -1 },
  { US"edieresis",       0x00eb, -1 },
  { US"edotaccent",      0x0117, -1 },
  { US"egrave",          0x00e8, -1 },
  { US"eight",           0x0038, -1 },
  { US"ellipsis",        0x2026, 11 },
  { US"emacron",         0x0113, -1 },
  { US"emdash",          0x2014, 12 },
  { US"endash",          0x2013, 13 },
  { US"eng",             0x014b, -1 },
  { US"eogonek",         0x0119, -1 },
  { US"equal",           0x003d, -1 },
  { US"eth",             0x00f0, -1 },
  { US"exclam",          0x0021, -1 },
  { US"exclamdown",      0x00a1, -1 },
  { US"f",               0x0066, -1 },
  { US"fi",              0xfb01, 14 },
  { US"five",            0x0035, -1 },
  { US"fl",              0xfb02, 15 },
  { US"florin",          0x0192, 16 },
  { US"four",            0x0034, -1 },
  { US"fraction",        0x2044, 17 },
  { US"g",               0x0067, -1 },
  { US"gbreve",          0x011f, -1 },
  { US"gcircumflex",     0x011d, -1 },
  { US"gcommaaccent",    0x0123, -1 },
  { US"gdotaccent",      0x0121, -1 },
  { US"germandbls",      0x00df, -1 },
  { US"grave",           0x0060, -1 },
  { US"greater",         0x003e, -1 },
  { US"greaterequal",    0x2265, 18 },
  { US"guillemotleft",   0x00ab, -1 },
  { US"guillemotright",  0x00bb, -1 },
  { US"guilsinglleft",   0x2039, 19 },
  { US"guilsinglright",  0x203a, 20 },
  { US"h",               0x0068, -1 },
  { US"hbar",            0x0127, -1 },
  { US"hcircumflex",     0x0125, -1 },
  { US"hungarumlaut",    0x030b, 21 },
  /* 002d is "hyphen-minus"; Unicode also has separate codes for hyphen and
  for minus. We use the latter below. */
  { US"hyphen",          0x002d, -1 },
  { US"i",               0x0069, -1 },
  { US"iacute",          0x00ed, -1 },
  { US"ibreve",          0x012d, -1 },
  { US"icircumflex",     0x00ee, -1 },
  { US"idieresis",       0x00ef, -1 },
  { US"igrave",          0x00ec, -1 },
  { US"ij",              0x0133, -1 },
  { US"imacron",         0x012b, -1 },
  { US"infinity",        0x221e, 43 },
  { US"iogonek",         0x012f, -1 },
  { US"itilde",          0x0129, -1 },
  { US"j",               0x006a, -1 },
  { US"jcircumflex",     0x0135, -1 },
  { US"k",               0x006b, -1 },
  { US"kcommaaccent",    0x0137, -1 },
  { US"kgreenlandic",    0x0138, -1 },
  { US"l",               0x006c, -1 },
  { US"lacute",          0x013a, -1 },
  { US"lcaron",          0x013e, -1 },
  { US"lcommaaccent",    0x013c, -1 },
  { US"ldot",            0x0140, -1 },
  { US"less",            0x003c, -1 },
  { US"lessequal",       0x2264, 22 },
  { US"logicalnot",      0x00ac, -1 },
  { US"longs",           0x017f, -1 },
  { US"lozenge",         0x25ca, 23 },
  { US"lslash",          0x0142, -1 },
  { US"m",               0x006d, -1 },
  { US"macron",          0x00af, -1 },
  { US"minus",           0x2212, 24 },
  { US"mu",              0x00b5, -1 },
  { US"multiply",        0x00d7, -1 },
  { US"n",               0x006e, -1 },
  { US"nacute",          0x0144, -1 },
  { US"napostrophe",     0x0149, -1 },
  { US"ncaron",          0x0148, -1 },
  { US"ncommaaccent",    0x0146, -1 },
  { US"nine",            0x0039, -1 },
  { US"notequal",        0x2260, 25 },
  { US"ntilde",          0x00f1, -1 },
  { US"numbersign",      0x0023, -1 },
  { US"o",               0x006f, -1 },
  { US"oacute",          0x00f3, -1 },
  { US"obreve",          0x014f, -1 },
  { US"ocircumflex",     0x00f4, -1 },
  { US"odieresis",       0x00f6, -1 },
  { US"oe",              0x0153, -1 },
  { US"ogonek",          0x0328, 26 },
  { US"ograve",          0x00f2, -1 },
  { US"ohungarumlaut",   0x0151, -1 },
  { US"omacron",         0x014d, -1 },
  { US"one",             0x0031, -1 },
  { US"onehalf",         0x00bd, -1 },
  { US"onequarter",      0x00bc, -1 },
  { US"onesuperior",     0x00b9, -1 },
  { US"ordfeminine",     0x00aa, -1 },
  { US"ordmasculine",    0x00ba, -1 },
  { US"oslash",          0x00f8, -1 },
  { US"otilde",          0x00f5, -1 },
  { US"p",               0x0070, -1 },
  { US"paragraph",       0x00b6, -1 },
  { US"parenleft",       0x0028, -1 },
  { US"parenright",      0x0029, -1 },
  { US"partialdiff",     0x2202, 27 },
  { US"percent",         0x0025, -1 },
  { US"period",          0x002e, -1 },
  { US"periodcentered",  0x2027, 28 },
  { US"perthousand",     0x2031, 29 },
  { US"plus",            0x002b, -1 },
  { US"plusminus",       0x00b1, -1 },
  { US"q",               0x0071, -1 },
  { US"question",        0x003f, -1 },
  { US"questiondown",    0x00bf, -1 },
  { US"quotedbl",        0x0022, -1 },
  { US"quotedblbase",    0x201e, 30 },
  { US"quotedblleft",    0x201c, 31 },
  { US"quotedblright",   0x201d, 32 },
  { US"quoteleft",       0x2018, 33 },
  { US"quoteright",      0x2019, 34 },
  { US"quotesinglbase",  0x201a, 35 },
  { US"quotesingle",     0x0027, -1 },
  { US"r",               0x0072, -1 },
  { US"racute",          0x0155, -1 },
  { US"radical",         0x221a, 36 },
  { US"rcaron",          0x0159, -1 },
  { US"rcommaaccent",    0x0157, -1 },
  { US"registered",      0x00ae, -1 },
  { US"ring",            0x030a, 37 },
  { US"s",               0x0073, -1 },
  { US"sacute",          0x015b, -1 },
  { US"scaron",          0x0161, -1 },
  { US"scedilla",        0x015f, -1 },
  { US"scircumflex",     0x015d, -1 },
  { US"scommaaccent",    0x0219, 38 },
  { US"section",         0x00a7, -1 },
  { US"semicolon",       0x003b, -1 },
  { US"seven",           0x0037, -1 },
  { US"six",             0x0036, -1 },
  { US"slash",           0x002f, -1 },
  { US"space",           0x0020, -1 },
  { US"sterling",        0x00a3, -1 },
  { US"summation",       0x2211, 39 },
  { US"t",               0x0074, -1 },
  { US"tbar",            0x0167, -1 },
  { US"tcaron",          0x0165, -1 },
  { US"tcedilla",        0x0163, -1 },
  { US"tcommaaccent",    0x021b, 40 },
  { US"thorn",           0x00fe, -1 },
  { US"three",           0x0033, -1 },
  { US"threequarters",   0x00be, -1 },
  { US"threesuperior",   0x00b3, -1 },
  { US"tilde",           0x0303, 41 },
  { US"trademark",       0x2122, 42 },
  { US"two",             0x0032, -1 },
  { US"twosuperior",     0x00b2, -1 },
  { US"u",               0x0075, -1 },
  { US"uacute",          0x00fa, -1 },
  { US"ubreve",          0x016d, -1 },
  { US"ucircumflex",     0x00fb, -1 },
  { US"udieresis",       0x00fc, -1 },
  { US"ugrave",          0x00f9, -1 },
  { US"uhungarumlaut",   0x0171, -1 },
  { US"umacron",         0x016b, -1 },
  { US"underscore",      0x005f, -1 },
  { US"uogonek",         0x0173, -1 },
  { US"uring",           0x016f, -1 },
  { US"utilde",          0x0169, -1 },
  { US"v",               0x0076, -1 },
  { US"w",               0x0077, -1 },
  { US"wcircumflex",     0x0175, -1 },
  { US"x",               0x0078, -1 },
  { US"y",               0x0079, -1 },
  { US"yacute",          0x00fd, -1 },
  { US"ycircumflex",     0x0177, -1 },
  { US"ydieresis",       0x00ff, -1 },
  { US"yen",             0x00a5, -1 },
  { US"z",               0x007a, -1 },
  { US"zacute",          0x017a, -1 },
  { US"zcaron",          0x017e, -1 },
  { US"zdotaccent",      0x017c, -1 },
  { US"zero",            0x0030, -1 }
};

static int an2ucount = sizeof(an2ulist)/sizeof(an2uencod);



/*************************************************
*     Convert character name to Unicode value    *
*************************************************/

/*
Arguments:
  cname    the character name
  fname    the font name (for warning)
  warn     TRUE if warning wanted for not found
  mcptr    if not NULL, where to put the special encoding value

Returns:   a Unicode code point, or -1 if not found
*/

static int
an2u(uschar *cname, uschar *fname, BOOL warn, int *mcptr)
{
int top = an2ucount;
int bot = 0;
if (mcptr != NULL) *mcptr = -1;

while (top > bot)
  {
  int mid = (top + bot)/2;
  an2uencod *an2u = an2ulist + mid;
  int c = Ustrcmp(cname, an2u->name);
  if (c == 0)
    {
    if (mcptr != NULL) *mcptr = an2u->poffset;
    return an2u->code;
    }
  if (c > 0) bot = mid + 1; else top = mid;
  }

if (warn) error_moan(ERR122, fname, cname, US" not found");  /* Hard */
return -1;
}



/*************************************************
*        Kern table sorting comparison           *
*************************************************/

/* This is the auxiliary routine used for comparing kern table entries when
sorting them.

Arguments:
  a          pointer to kerntable structure
  b          pointer to kerntable structure

Returns      difference between their "pair" values
*/

static int
kern_table_cmp(const void *a, const void *b)
{
kerntablestr *ka = (kerntablestr *)a;
kerntablestr *kb = (kerntablestr *)b;
return (int)(ka->pair - kb->pair);
}


/*************************************************
*         UTR table sorting comparison           *
*************************************************/

/* This is the auxiliary routine used for comparing UTR table entries when
sorting them.

Arguments:
  a          pointer to utrtable structure
  b          pointer to utrtable structure

Returns      difference between their "pair" values
*/

static int
utr_table_cmp(const void *a, const void *b)
{
utrtablestr *ka = (utrtablestr *)a;
utrtablestr *kb = (utrtablestr *)b;
return (int)(ka->unicode - kb->unicode);
}



/*************************************************
*          Number reader for AFM files           *
*************************************************/

/*
Arguments:
  value       where to return the value
  p           character pointer

Returns:      new value of p
*/

static uschar *
read_number(int *value, uschar *p)
{
int n = 0;
int sign = 1;
while (*p != 0 && *p == ' ') p++;
if (*p == '-') { sign = -1; p++; }
while (isdigit(*p)) n = n * 10 + *p++ - '0';
*value = n * sign;
return p;
}



/*************************************************
*         Find AFM or other file for a font      *
*************************************************/

/* This is an externally-called function, also called from below.

Arguments:
  psname     the font's PostScript name
  ext        ".afm", ".utr", ".pfa", etc., or ""
  fextras    list of extra directories to search
  fdefault   the default directory
  filename   where to return the successful file name
  mandatory  if TRUE, give hard error on failure

Returns:   the opened file or NULL (when not mandatory)
*/

FILE *
font_finddata(uschar *psname, const char *ext, uschar *fextras,
  uschar *fdefault, uschar *filename, BOOL mandatory)
{
FILE *f = NULL;

/* First look in any additional directories. */

if (fextras != NULL)
  {
  uschar *pp = fextras;
  while (*pp != 0)
    {
    int len;
    uschar *ep = Ustrchr(pp, ':');
    if (ep == NULL) ep = Ustrchr(pp, 0);

    /* Using sprintf() for the whole string causes a compiler warning if
    -Wformat-overflow is set. */

    len = ep - pp;
    (void)memcpy(filename, pp, len);
    sprintf(CS (filename + len), "/%s%s", psname, ext);
    f = Ufopen(filename, "r");
    if (f != NULL || *ep == 0) break;
    pp = ep + 1;
    }
  }

/* Try the default directory if not yet found. */

if (f == NULL)
  {
  sprintf(CS filename, "%s/%s%s", fdefault, psname, ext);
  f = Ufopen(filename, "r");
  if (mandatory && f == NULL)
    {
    const char *s = (*ext == 0)? "Data" : ext;
    if (fextras == NULL)
      error_moan(ERR59, s, psname, fdefault);            /* Hard error */
    else
      error_moan(ERR127, s, psname, fextras, fdefault);  /* Hard error */
    }
  }

return f;
}



/*************************************************
*  Load width, kern, & Unicode tables for a font *
*************************************************/

/* This is an externally-callable function. It looks for a mandatory AFM file,
containing character widths and kerning information, and also checks for an
optional UTR file if the font is not standardly-encoded.

Argument:   the font id (offset in font_List)
Returns:    nothing
*/

void
font_loadtables(int fontid)
{
FILE *f;
int kerncount = 0;
int finalcount = 0;
int *widths, *r2ladjusts, *heights;
kerntablestr *kerntable;
fontstr *fs = &(font_List[fontid]);
uschar *pp;
uschar filename[256];
uschar line[256];

DEBUG(("Loading AFM for %s\n", fs->psname));

f = font_finddata(fs->psname, ".afm", font_data_extra, font_data_default,
  filename, TRUE);   /* Hard error if not found */
widths = fs->widths = malloc(LOWCHARLIMIT * sizeof(int));
memset(widths, 0, LOWCHARLIMIT * sizeof(int));
r2ladjusts = fs->r2ladjusts = malloc(LOWCHARLIMIT * sizeof(int));
memset(r2ladjusts, 0, LOWCHARLIMIT * sizeof(int));
heights = fs->heights = NULL;
fs->kerncount = 0;

/* Process the AFM file. First find the start of the metrics; on the way, check
for the standard encoding scheme and for fixed pitch. */

for (;;)
  {
  if (Ufgets(line, sizeof(line), f) == NULL)
    error_moan(ERR122, filename, "no metric data found", "");  /* Hard */
  if (memcmp(line, "EncodingScheme AdobeStandardEncoding", 36) == 0)
    fs->stdencoding = TRUE;
  if (memcmp(line, "IsFixedPitch true", 17) == 0)
    fs->fixedpitch = TRUE;
  if (memcmp(line, "StartCharMetrics", 16) == 0) break;
  }

/* Process the metric lines for each character */

for (;;)
  {
  uschar *ppb;
  int width, code;
  int poffset = -1;
  int r2ladjust = 0;

  if (Ufgets(line, sizeof(line), f) == NULL)
    error_moan(ERR122, filename, "unexpected end of metric data", "");  /* Hard */
  if (memcmp(line, "EndCharMetrics", 14) == 0) break;

  if (memcmp(line, "C ", 2) != 0)
    error_moan(ERR122, filename, "unrecognized metric data line: ", line); /* Hard */

  pp = line + 2;
  while (memcmp(pp, "WX", 2) != 0) pp++;
  pp = read_number(&width, pp+2);

  /* Look for a bounding box, but use a new pointer, because N comes first. If
  a bounding box is found, compute a value by which to adjust the printing
  position of this character when printing right-to-left. This is used for the
  last character of every string, instead of the stringwidth character.

        |          |--------|
        |          | char   |
        |          |  glyph |
   ^    |<-- x0 -->|--------|     x0 is the side bearing (LH bbox value)
   ^--->|<-- x1 ------------>     x1 is the right hand bbox value
   ^    ^
   ^    ^
   ^    Original print point
   ^
   New print point is x0 + x1 to the left of the old. If it were just x1,
   the edge of the character would abut the original point; instead we add
   an additional same sized bearing on the other side. */

  ppb = pp;
  while (*ppb != 0 && Ustrncmp(ppb, "B ", 2) != 0) ppb++;
  if (*ppb != 0)
    {
    int x0, x1;
    ppb = read_number(&x0, ppb+2);   /* x-left */
    ppb = read_number(&x1, ppb);     /* y-bottom */
    ppb = read_number(&x1, ppb);     /* x-right */
    r2ladjust = x1 + x0;
    }

  /* If this is a StandardEncoding font, scan the list of characters so as to
  get the Unicode value for this character. */

  if (fs->stdencoding)
    {
    uschar *cname;
    while (memcmp(pp, "N ", 2) != 0) pp++;
    cname = (pp += 2);
    while (*pp != ' ') pp++;
    *pp = 0;
    code = an2u(cname, fs->psname, TRUE, &poffset);
    if (code < 0) continue;  /* Don't try to store anything! */
    }

  /* For other fonts, just use the character number directly. If there are
  unencoded characters, ignore them. These fonts include the PMW-Music font,
  which has some characters with vertical height movements. */

  else
    {
    (void)read_number(&code, line+1);
    if (code < 0) continue;
    while (*pp != 0 && memcmp(pp, "WY", 2) != 0) pp++;
    if (*pp != 0)
      {
      int height;
      pp += 2;
      (void)read_number(&height, pp);
      if (heights == NULL)
        {
        heights = fs->heights = malloc(256 * sizeof(int));
        memset(heights, 0, 256 * sizeof(int));
        }
      heights[code] = height;
      }
    }

  /* Remember that this font has certain characters */

  if (code == CHAR_FI) fs->hasfi = TRUE;

  /* Now put the widths in an appropriate place. */

  if (code < LOWCHARLIMIT)
    {
    widths[code] = width;
    r2ladjusts[code] = r2ladjust;
    }
  else
    {
    tree_node *tc = malloc(sizeof(tree_node));
    tc->name = malloc(8);
    tc->name[misc_ord2utf8(code, tc->name)] = 0;
    tc->val[0] = poffset;
    tc->val[1] = width;
    tc->val[2] = r2ladjust;
    (void)Tree_InsertNode(&(fs->high_tree), tc);
    }
  }

/* Process kerning data (if any); when this is done, we are finished with the
AFM file. */

for (;;)
  {
  if (Ufgets(line, sizeof(line), f) == NULL) goto ENDKERN;
  if (memcmp(line, "StartKernPairs", 14) == 0) break;
  }

/* Find size of kern table, and get space for it. In the past, some of Adobe's
AFM files had a habit of containing a large number of kern pairs with
zero amount of kern. We leave these out of the table and adjust the count for
searching, but don't bother to free up the unused store (it isn't a vast
amount). */

pp = line + 14;
while (*pp != 0 && *pp == ' ') pp++;
(void)read_number(&kerncount, pp);
fs->kerns = kerntable = malloc(kerncount*sizeof(kerntablestr));

finalcount = 0;
while (kerncount--)
  {
  uschar *x;
  int sign = 1;
  int value;
  int a = -1;
  int b = -1;

  if (Ufgets(line, sizeof(line), f) == NULL)
    error_moan(ERR122, filename, "unexpected end of kerning data");  /* Hard */
  if (memcmp(line, "EndKernPairs", 12) == 0) break;

  /* Skip blank lines */

  if (Ustrlen(line) <= 1)
    {
    kerncount++;
    continue;
    }

  /* Process each kern */

  pp = line + 4;

  x = pp;
  while (*pp != 0 && *pp != ' ') pp++;
  *pp++ = 0;
  a = an2u(x, fs->psname, FALSE, NULL);

  while (*pp != 0 && *pp == ' ') pp++;
  x = pp;
  while (*pp != 0 && *pp != ' ') pp++;
  *pp++ = 0;
  b = an2u(x, fs->psname, FALSE, NULL);

  /* Read the kern value only if we have found the characters; otherwise ignore
  the kern */

  if (a >= 0 && b >= 0)
    {
    kerntable[finalcount].pair = (a << 16) + b;
    while (*pp != 0 && *pp == ' ') pp++;
    if (*pp == '-') { sign = -1; pp++; }
    (void)read_number(&value, pp);
    if (value != 0) kerntable[finalcount++].kwidth = value*sign;
    }
  }

/* Adjust the count and sort the table into ascending order */

fs->kerncount = finalcount;  /* true count */
qsort(kerntable, fs->kerncount, sizeof(kerntablestr), kern_table_cmp);

/* Finished with the AFM file */

ENDKERN:
(void)fclose(f);


/* If this font does not have standard encoding, see if there is a .utr file
that contains Unicode translations. */

if (!fs->stdencoding &&
    (f = font_finddata(fs->psname, ".utr", font_data_extra, font_data_default,
      filename, FALSE)) != NULL)
  {
  int ucount = 0;
  int lineno = 0;
  utrtablestr utable[MAX_UTRANSLATE];

  DEBUG(("Loading UTR for %s\n", fs->psname));

  while (Ufgets(line, sizeof(line), f) != NULL)
    {
    uschar *epp;

    lineno++;
    pp = line;
    while (isspace(*pp)) pp++;
    if (*pp == 0 || *pp == '#') continue;

    if (ucount >= MAX_UTRANSLATE)
      {
      error_moan(ERR147, filename, MAX_UTRANSLATE);
      break;
      }

    if (Ustrncmp(pp, "U+", 2) == 0) pp += 2;
    utable[ucount].unicode = (unsigned int)Ustrtoul(pp, &epp, 16);
    if (epp == pp)
      {
      error_moan(ERR148, "Unicode", lineno, filename, line);
      continue;
      }
    pp = epp;
    utable[ucount].pscode = (unsigned int)Ustrtoul(pp, &epp, 0);
    if (epp == pp)
      {
      error_moan(ERR148, "font", lineno, filename, line);
      continue;
      }
    if (utable[ucount].pscode >= 256)
      {
      error_moan(ERR149, lineno, filename, line);
      continue;
      }

    ucount++;
    }

  (void)fclose(f);

  /* Sort the data, check for duplicates, and remember with the font. */

  if (ucount > 0)
    {
    int i;
    qsort(utable, ucount, sizeof(utrtablestr), utr_table_cmp);
    for (i = 1; i < ucount; i++)
      {
      if (utable[i].unicode == utable[i-1].unicode)
        {
        error_moan(ERR7, utable[i].unicode, filename);
        while(i < ucount - 1 && utable[i].unicode == utable[i+1].unicode) i++;
        }
      }
    fs->utr = store_Xget(ucount * sizeof(utrtablestr));
    memcpy(fs->utr, utable, ucount * sizeof(utrtablestr));
    fs->utrcount = ucount;
    }
  }


/* Early checking debugging code; retained in the source in case it is ever
needed again. */

#ifdef NEVER
debug_printf("FONT %s\n", fs->psname);
  {
  int i;

  for (i = 0; i < LOWCHARLIMIT; i++)
    {
    if (fs->widths[i] != 0) debug_printf("%04x %5d\n", i, fs->widths[i]);
    }

  for (i = LOWCHARLIMIT; i < 0xffff; i++)
    {
    tree_node *t;
    uschar key[4];
    key[0] = (i >> 8) & 255;
    key[1] = i & 255;
    key[2] = 0;
    t = tree_search(fs->high_tree, key);
    if (t != NULL) debug_printf("%04x %5d %5d %5d\n", i, t->data.val[0],
      t->data.val[1], t->data.val[2]);
    }

  debug_printf("KERNS %d\n", fs->kerncount);
  for (i = 0; i < fs->kerncount; i++)
    {
    kerntablestr *k = &(fs->kerns[i]);
    int a = (k->pair >> 16) & 0xffff;
    int b = (k->pair) & 0xffff;
    debug_printf("%04x %04x %5d\n", a, b, k->kwidth);
    }
  }
#endif
}



/*************************************************
*         Add a font to the base list            *
*************************************************/

/* This function is called from font_init() below, to add a single basic
PostScript font to the font_List vector.

Argument:  Name of PostScript font
Returns:   nothing
*/

static void
font_addbasefont(uschar *psfont)
{
fontstr *fs = &(font_List[font_basecount]);
if (font_basecount >= MAX_FONTS) { error_moan(ERR26, MAX_FONTS); return; }
fs->psname = malloc(Ustrlen(psfont)+1);
Ustrcpy(fs->psname, psfont);
fs->widths = NULL;
fs->high_tree = NULL;
fs->heights = NULL;
fs->kerns = NULL;
fs->kerncount = -1;
fs->stdencoding = fs->fixedpitch = fs->hasfi = FALSE;
fs->include = FALSE;
font_loadtables(font_basecount++);
}


/*************************************************
*        Set up the base list of fonts           *
*************************************************/

/* Called at start-up to set up the default fonts

Arguments:  none
Returns:    nothing
*/

void
font_init(void)
{
font_addbasefont(US"Times-Roman");
font_addbasefont(US"Times-Italic");
font_addbasefont(US"Times-Bold");
font_addbasefont(US"Times-BoldItalic");
font_addbasefont(US"Symbol");
font_addbasefont(US"PMW-Music");
}



/*************************************************
*              Search for given font             *
*************************************************/

/* This function searches the font list by the name of the font.

Argument:  the font name (e.g. "Times-Roman")
Returns:   the font id (offset in font_List) or -1 if not found
*/

int
font_search(uschar *name)
{
int i;
for (i = 0; i < font_count; i++)
  if (Ustrcmp(name, font_List[i].psname) == 0) return i;
return -1;
}


/*************************************************
*      Manually translate Unicode code point     *
*************************************************/

/* Called for non-standardly encoded fonts. If there is no translation table,
or if the character is absent from the table, return the code point unchanged.

Arguments:
  c           the Unicode code point
  fs          the font structure

Returns:      possibly a different code point
*/

int
font_utranslate(int c, fontstr *fs)
{
int top, bot, mid;

if (fs->utrcount == 0) return c;

bot = 0;
top = fs->utrcount;

while (bot < top)
  {
  mid = (bot + top)/2;
  if (c == fs->utr[mid].unicode) return fs->utr[mid].pscode;
  if (c > fs->utr[mid].unicode) bot = mid + 1;
    else top = mid;
  }

return c;   /* Not found */
}



/*************************************************
*         Find the width of a string             *
*************************************************/

/* The "height" (distance current point moves vertically) is placed in
font_stringheight. For ordinary text fonts, this is only non-zero when there's
a rotation, but some characters in the music font move vertically. If
font_xstretch is set, it is an absolute amount of space to add to each space
character. This is used when printing justified headings and footings.

Arguments:
  s            the string
  font         the font number
  pointsize    the font's point size

Returns:       the string width
*/

int
font_stringwidth(uschar *s, int font, int pointsize)
{
int hwidth = 0;
int vheight = 0;
int spacecount = 0;
int c;
int lastc = -1;

#ifdef SUPPORT_B2PF
uschar b2pf_stack_buffer[B2PF_STACKBSIZE];
#endif

int fontid = font_table[font];
fontstr *fs = &(font_List[fontid]);
int *widths = fs->widths;
int *heights = fs->heights;
tree_node *high_tree = fs->high_tree;
kerntablestr *ktable = fs->kerns;
int kerncount = fs->kerncount;
BOOL stdencoding = fs->stdencoding;

DEBUG(("font_stringwidth %d %d \"%s\"\n", fontid, pointsize, s));

/* If we have B2PF support, process this string if its font is set up for it.
For the moment, just use the on-stack output buffer. If ever there's a
complaint, we could retry the process using a larger heap buffer. */

#ifdef SUPPORT_B2PF
if (font_b2pf_contexts[font] != NULL)
  {
  size_t ilen = Ustrlen(s);
  size_t used, eoffset;
  int rc;

  /* Reduce output size by 1 to allow for a terminating zero to be added. */

  rc = b2pf_format_string(font_b2pf_contexts[font], (void *)s, ilen,
    (void *)b2pf_stack_buffer, B2PF_STACKBSIZE - 1, &used,
    font_b2pf_options[fontid], &eoffset);

  if (rc != B2PF_SUCCESS)
    {
    size_t buffused;
    char buffer[128];
    (void)b2pf_get_error_message(rc, buffer, sizeof(buffer), &buffused, 0);
    buffer[buffused] = 0;
    error_moan(ERR156, s, buffer);  /* Hard */
    }

  b2pf_stack_buffer[used] = 0;
  s = b2pf_stack_buffer;
  }
#endif

/* Now scan the string. For non-standardly encoded fonts there may be a .utr
translation from Unicode to the font's encoding. When kerning we have to note
the kern values. */

while (*s != 0)
  {
  GETCHARINC(c, s);
  if (c == ' ') spacecount++;
  if (!stdencoding) c = font_utranslate(c, fs);

  /* Deal with characters >= 256 */

  if (c >= 256)
    {
    if (!stdencoding)
      hwidth += widths[UNKNOWN_CHAR_N];

    else if (c < LOWCHARLIMIT)
      hwidth += widths[c];

    else
      {
      tree_node *t;
      uschar utf[8];
      utf[misc_ord2utf8(c, utf)] = 0;
      t = Tree_Search(high_tree, utf);
      if (t != NULL) hwidth += t->val[1];
        else hwidth += widths[UNKNOWN_CHAR_S];
      }
    }

  /* Character is < 256. Heights apply only to low-numbered characters. */

  else
    {
    hwidth += widths[c];
    if (heights != NULL) vheight += heights[c];
    }

  /* Deal with kerning */

  if (main_kerning && ktable != NULL &&
      lastc >= 0 && lastc <= 0xffff && c <= 0xffff)
    {
    int top, bot, mid;
    usint pair = (usint)((usint)lastc << 16) | c;

    bot = 0;
    top = kerncount;

    while (top > bot)
      {
      kerntablestr *k;
      mid = (top + bot)/2;
      k = &(ktable[mid]);
      if (pair == k->pair)
        {
        hwidth += k->kwidth;
        break;
        }
      if (pair > k->pair) bot = mid + 1; else top = mid;
      }
    }

  lastc = c;
  }

/* Scale to font size, then adjust for rotation */

hwidth = mac_muldiv(hwidth, pointsize, 1000);
vheight = mac_muldiv(vheight, pointsize, 1000);

font_stringheight =
  mac_muldiv(hwidth, font_transform[1], 65536) +
  mac_muldiv(vheight, font_transform[3], 65536);

DEBUG(("font_stringwidth() %d %d\n",
       mac_muldiv(hwidth, font_transform[0], 65536) +
       mac_muldiv(vheight, font_transform[2], 65536),
       font_stringheight));

return font_xstretch * spacecount +
       mac_muldiv(hwidth, font_transform[0], 65536) +
       mac_muldiv(vheight, font_transform[2], 65536);
}




/*************************************************
*            Read a font word (+ number)         *
*************************************************/

/*
Argument:  TRUE to not give an error if the word is unrecognised
Returns:   font_rm, font_it, etc, or -1 on error
*/

int
font_fontword(BOOL soft)
{
next_word();
if (Ustrcmp(read_word, "roman") == 0) return font_rm;
if (Ustrcmp(read_word, "italic") == 0) return font_it;
if (Ustrcmp(read_word, "bold") == 0) return font_bf;
if (Ustrcmp(read_word, "bolditalic") == 0) return font_bi;
if (Ustrcmp(read_word, "symbol") == 0) return font_sy;
if (Ustrcmp(read_word, "music") == 0) return font_mu;
if (Ustrcmp(read_word, "bigmusic") == 0) return font_mf;
if (Ustrcmp(read_word, "extra") == 0)
  {
  int x;
  if (!read_expect_integer(&x, FALSE, FALSE)) return -1;
  if (x >= 1 && x <= MaxExtraFont) return font_xx + x - 1;
  error_moan(ERR26, MaxExtraFont);
  }
else if (!soft)
  error_moan(ERR10, "\"roman\", \"italic\", \"bold\", \"bolditalic\", or \"extra\"");
return -1;
}



/*************************************************
*         Set up a font rotation                 *
*************************************************/

/* The effect of this function is to modify the font_transform matrix by the
given rotation.

Argument:  rotation in millidegrees
Returns:   nothing
*/

void
font_rotate(int rotate)
{
int newmatrix[4];
double sr = sin(((double)rotate)*atan(1.0)/45000.0);
double cr = cos(((double)rotate)*atan(1.0)/45000.0);

font_sinr = (int)(sr * 1000.0);
font_cosr = (int)(cr * 1000.0);

newmatrix[0] =
  (int)(((double)font_transform[0])*cr - ((double)font_transform[1])*sr);
newmatrix[1] =
  (int)(((double)font_transform[0])*sr + ((double)font_transform[1])*cr);
newmatrix[2] =
  (int)(((double)font_transform[2])*cr - ((double)font_transform[3])*sr);
newmatrix[3] =
  (int)(((double)font_transform[2])*sr + ((double)font_transform[3])*cr);

memcpy(font_transform, newmatrix, 4*sizeof(int));
}



/*************************************************
*           Reset font transformation            *
*************************************************/

/* Historical relic: the transform matrix operates in the units that RISC OS
uses for its font transformations. These were chosen so that the RISC OS screen
display code could use the matrix directly. There seems no reason to change
this choice gratuitously. The first four matrix values have 16-bit fractions;
the other two are in millipoints.

Arguments:  none
Returns:    nothing
*/

void font_reset(void)
{
font_sinr = 0;      /* No rotation => angle is zero */
font_cosr = 1000;   /* Fixed point 1.0 */
font_transform[0] = font_transform[3] = 65536;
font_transform[1] = font_transform[2] = 0;
font_transform[4] = font_transform[5] = 0;
}

/* End of font.c */
