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