blob: db5d25cb4ecba32f1fc172ade6fce4f2d45da58e [file] [log] [blame]
/*@z37.c:Font Service:Declarations@*******************************************/
/* */
/* THE LOUT DOCUMENT FORMATTING SYSTEM (VERSION 3.24) */
/* COPYRIGHT (C) 1991, 2000 Jeffrey H. Kingston */
/* */
/* Jeffrey H. Kingston (jeff@cs.usyd.edu.au) */
/* Basser Department of Computer Science */
/* The University of Sydney 2006 */
/* AUSTRALIA */
/* */
/* This program is free software; you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation; either Version 2, or (at your option) */
/* any later version. */
/* */
/* This program 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 General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program; if not, write to the Free Software */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA */
/* */
/* FILE: z37.c */
/* MODULE: Font Service */
/* EXTERNS: FontInit(), FontDefine(), FontChange(), FontWordSize(), */
/* FontSize(), FontHalfXHeight(), FontEncoding(), */
/* FontMapping(), FontFamilyAndFace(), FontNeeded() */
/* */
/* This module implements fonts, using encoding vectors and Adobe font */
/* metrics files (.AFM files, version 2). */
/* */
/*****************************************************************************/
#include "externs.h"
#define DEFAULT_XHEIGHT 500 /* the default XHeight if font has none */
#define NO_FONT 0 /* the not-a-font font number */
#define SZ_DFT 1000 /* default lout size is 50p */
#define INIT_FINFO_SIZE 100 /* initial number of sized fonts set aside */
/*****************************************************************************/
/* */
/* These definitions have been moved to "externs.h" since z24.c needs them: */
/* */
/* struct metrics { */
/* SHORT_LENGTH up; */
/* SHORT_LENGTH down; */
/* SHORT_LENGTH left; */
/* SHORT_LENGTH right; */
/* SHORT_LENGTH last_adjust; */
/* }; */
/* */
/* typedef struc composite_rec { */
/* FULL_CHAR char_code; */
/* SHORT_LENGTH x_offset; */
/* SHORT_LENGTH y_offset; */
/* } COMPOSITE; */
/* */
/* typedef struct font_rec { */
/* struct metrics *size_table; metrics of sized fonts */
/* FULL_CHAR *lig_table; ligatures */
/* unsigned short *composite; non-zero means composite */
/* COMPOSITE *cmp_table; composites to build */
/* int cmp_top; length of cmp_table */
/* OBJECT font_table; record of sized fonts */
/* OBJECT original_face; face object of font */
/* SHORT_LENGTH underline_pos; position of underline */
/* SHORT_LENGTH underline_thick; thickness of underline */
/* unsigned short *kern_table; first kerning chars */
/* FULL_CHAR *kern_chars; second kerning chars */
/* unsigned char *kern_value; points into kern_lengths */
/* SHORT_LENGTH *kern_sizes; sizes of kernings */
/* } FONT_INFO; */
/* */
/*****************************************************************************/
/*****************************************************************************/
/* */
/* Private data structures of this module */
/* */
/* +++++++++++++++++++++++++++ */
/* + + */
/* root -> + ACAT + */
/* + + */
/* + + */
/* +++++++++++++++++++++++++++ */
/* | font families... */
/* | */
/* +-----+-----------------------------------------------+ ... */
/* | | */
/* | | */
/* +++++++++++++++++++++++++++ */
/* + + */
/* family -> + WORD + */
/* + string (family name) + */
/* + + */
/* +++++++++++++++++++++++++++ */
/* | faces of this family... */
/* | */
/* +-----+-----------------------------------------------+ ... */
/* | | */
/* | | */
/* +++++++++++++++++++++++++++++++++ */
/* + + */
/* face -> + WORD + */
/* + string (face name) + */
/* + font_recoded + */
/* + font_mapping + */
/* + font_page + */
/* + + */
/* +++++++++++++++++++++++++++++++++ */
/* | size records... */
/* | */
/* +----------+---------+--------------------+-----------------------+ */
/* | | | | */
/* | | | | */
/* +++++++++++++++++++ +++++++++++++++++++ +++++++++++++++++++++ */
/* + + + + + + */
/* + WORD + + WORD + + WORD + */
/* + string (font + + string (AFM + + string (short + */
/* + name) + + file name) + + font name) + */
/* + + + + + font_num + */
/* +++++++++++++++++++ +++++++++++++++++++ + font_size + */
/* | + font_xheight2 + */
/* | + font_recoded + */
/* ++++++++++++++++++++ + font_mapping + */
/* + + + font_spacewidth + */
/* (optional) + WORD + + + */
/* + string (extra + +++++++++++++++++++++ */
/* + AFM file name) + */
/* + + */
/* ++++++++++++++++++++ */
/* */
/*****************************************************************************/
int font_curr_page; /* current page number */
FONT_INFO *finfo; /* all the font table info */
static int finfo_size; /* current finfo array size */
static OBJECT font_root; /* root of tree of fonts */
static OBJECT font_used; /* fonts used on this page */
static FONT_NUM font_count; /* number of sized fonts */
static int font_seqnum; /* unique number for a font */
static OBJECT FontDefSym; /* symtab entry for @FontDef */
static OBJECT fd_tag; /* @FontDef @Tag entry */
static OBJECT fd_family; /* @FontDef @Family entry */
static OBJECT fd_face; /* @FontDef @Face entry */
static OBJECT fd_name; /* @FontDef @Name entry */
static OBJECT fd_metrics; /* @FontDef @Metrics entry */
static OBJECT fd_extra_metrics; /* @FontDef @ExtraMetrics */
static OBJECT fd_mapping; /* @FontDef @Mapping entry */
static OBJECT fd_recode; /* @FontDef @Recode entry */
/*@::FontInit(), FontDebug()@*************************************************/
/* */
/* FontInit() */
/* */
/* Initialise this module. */
/* */
/*****************************************************************************/
static OBJECT load(FULL_CHAR *name, unsigned dtype, OBJECT encl, BOOLEAN compulsory)
{ OBJECT res;
res = InsertSym(name, dtype, no_fpos, DEFAULT_PREC, FALSE, FALSE, 0, encl,
MakeWord(WORD, STR_EMPTY, no_fpos));
if( dtype == NPAR ) visible(res) = TRUE;
if( compulsory )
{ has_compulsory(encl)++;
is_compulsory(res) = TRUE;
}
return res;
}
void FontInit(void)
{
debug0(DFT, D, "FontInit()");
font_curr_page = 1;
font_count = 0;
New(font_root, ACAT);
New(font_used, ACAT);
font_seqnum = 0;
finfo = (FONT_INFO *) malloc(INIT_FINFO_SIZE * sizeof(FONT_INFO));
finfo_size = INIT_FINFO_SIZE;
ifdebug(DMA, D,
DebugRegisterUsage(MEM_FONTS, 1, INIT_FINFO_SIZE * sizeof(FONT_INFO)));
/* set up FontDefSym */
FontDefSym = load(KW_FONTDEF, LOCAL, StartSym, FALSE);
fd_tag = load(KW_TAG, NPAR, FontDefSym, TRUE);
fd_family = load(KW_FAMILY, NPAR, FontDefSym, TRUE);
fd_face = load(KW_FACE, NPAR, FontDefSym, TRUE);
fd_name = load(KW_NAME, NPAR, FontDefSym, TRUE);
fd_metrics = load(KW_METRICS, NPAR, FontDefSym, TRUE);
fd_extra_metrics = load(KW_EXTRA_METRICS, NPAR, FontDefSym, FALSE);
fd_mapping = load(KW_MAPPING, NPAR, FontDefSym, TRUE);
fd_recode = load(KW_RECODE, NPAR, FontDefSym, FALSE);
debug0(DFT, D, "FontInit returning.");
}
/*****************************************************************************/
/* */
/* FontDebug() */
/* */
/* Print out font tree (not currectly used). */
/* */
/*****************************************************************************/
#if DEBUG_ON
static void FontDebug(void)
{ OBJECT family, face, link, flink, zlink, z; int i;
assert(font_root!=nilobj && type(font_root)==ACAT, "FontDebug: font_root!");
for( link = Down(font_root); link != font_root; link = NextDown(link) )
{ Child(family, link);
assert( is_word(type(family)), "FontDebug: family!" );
debug1(DFS, D, "family %s:", string(family));
for( flink = Down(family); flink != family; flink = NextDown(flink) )
{ Child(face, flink);
assert( is_word(type(face)), "FontDebug: face!" );
debug1(DFS, D, " face %s:", string(face));
for( zlink = Down(face); zlink != face; zlink = NextDown(zlink) )
{ Child(z, zlink);
if( is_word(type(z)) )
{ debug2(DFS, D, " %s%s", string(z), Down(z) != z ? " child" : "");
}
else
{ debug1(DFS, D, " %s", Image(type(z)));
}
}
}
}
for( i = 1; i <= font_count; i++ )
fprintf(stderr, " finfo[%d].font_table = %s\n", i,
EchoObject(finfo[i].font_table));
} /* end FontDebug */
/*****************************************************************************/
/* */
/* DebugKernTable(fnum) */
/* */
/* Print debug output of kern table for font fnum. */
/* */
/*****************************************************************************/
static void DebugKernTable(FONT_NUM fnum)
{ int i, j;
unsigned short *kt = finfo[fnum].kern_table;
FULL_CHAR *kc = finfo[fnum].kern_chars;
unsigned char *kv = finfo[fnum].kern_value;
SHORT_LENGTH *ks = finfo[fnum].kern_sizes;
debug1(DFT, DD, "DebugKernTable(%d)", fnum);
for( i = 0; i < MAX_CHARS; i++ )
{ if( kt[i] != 0 )
{ debug1(DFT, DD, "kt[%d]:", i);
for( j = kt[i]; kc[j] != '\0'; j++ )
{ debug3(DFT, DD, "KPX %c %c %d", i, kc[j], ks[kv[j]]);
}
}
}
debug1(DFT, DD, "DebugKernTable(%d) returning", fnum);
} /* DebugKernTable */
#endif
/*****************************************************************************/
/* */
/* ReadCharMetrics(face, fixed_pitch, xheight2,lig,ligtop,fnum,fnt,lnum,fp) */
/* */
/* Read a sequence of character metrics lines. The font record is */
/* face, its ligatures are lig[0..ligtop], font number fnum, metrics fnt. */
/* The line number is lnum; input is to be read from file fp. */
/* */
/*****************************************************************************/
static void ReadCharMetrics(OBJECT face, BOOLEAN fixed_pitch, int xheight2,
FULL_CHAR *lig, int *ligtop, FILE_NUM fnum, struct metrics *fnt,
int *lnum, FILE *fp)
{ FULL_CHAR buff[MAX_BUFF], command[MAX_BUFF], ch, ligchar;
int i, wx, llx, lly, urx, ury;
float fl_wx, fl_llx, fl_lly, fl_urx, fl_ury;
BOOLEAN wxfound, bfound;
OBJECT AFMfilename;
Child(AFMfilename, NextDown(Down(face)));
while( StringFGets(buff, MAX_BUFF, fp) != NULL &&
!StringBeginsWith(buff, AsciiToFull("EndCharMetrics")) &&
!StringBeginsWith(buff, AsciiToFull("EndExtraCharMetrics")) )
{
/* read one line containing metric info for one character */
debug1(DFT, DD, " ReadCharMetrics: %s", buff);
(*lnum)++; ch = '\0';
wxfound = bfound = FALSE;
i = 0; while( buff[i] == ' ' ) i++;
while( buff[i] != '\n' )
{
debug2(DFT, DDD, " ch = %d, &buff[i] = %s", ch, &buff[i]);
sscanf( (char *) &buff[i], "%s", command);
if( StringEqual(command, "N") )
{ sscanf( (char *) &buff[i], "N %s", command);
ch = MapCharEncoding(command, font_mapping(face));
}
else if( StringEqual(command, "WX") )
{ sscanf( (char *) &buff[i], "WX %f", &fl_wx);
wx = fl_wx;
wxfound = TRUE;
}
else if( StringEqual(command, "B") )
{ sscanf( (char *) &buff[i], "B %f %f %f %f",
&fl_llx, &fl_lly, &fl_urx, &fl_ury);
llx = fl_llx;
lly = fl_lly;
urx = fl_urx;
ury = fl_ury;
bfound = TRUE;
}
else if( StringEqual(command, "L") &&
BackEnd->uses_font_metrics && ch != '\0' )
{ if( lig[ch] == 1 ) lig[ch] = (*ligtop) - MAX_CHARS;
lig[(*ligtop)++] = ch;
i++; /* skip L */
while( buff[i] == ' ' ) i++;
while( buff[i] != ';' && buff[i] != '\n' )
{ sscanf( (char *) &buff[i], "%s", command);
ligchar = MapCharEncoding(command, font_mapping(face));
if( ligchar != '\0' ) lig[(*ligtop)++] = ligchar;
else
{ Error(37, 1, "ignoring unencoded ligature character %s in font file %s (line %d)",
WARN, &fpos(AFMfilename), command, FileName(fnum), *lnum);
lig[ch] = 1;
}
if( *ligtop > 2*MAX_CHARS - 5 )
Error(37, 2, "too many ligature characters in font file %s (line %d)",
FATAL, &fpos(AFMfilename), FileName(fnum), *lnum);
while( buff[i] != ' ' && buff[i] != ';' ) i++;
while( buff[i] == ' ' ) i++;
}
lig[(*ligtop)++] = '\0';
}
while( buff[i] != ';' && buff[i] != '\n' ) i++;
if( buff[i] == ';' )
{ i++; while( buff[i] == ' ' ) i++;
}
}
if( ch > '\0' )
{
if( !wxfound )
{ Error(37, 3, "WX missing in font file %s (line %d)",
FATAL, &fpos(AFMfilename), FileName(fnum), *lnum);
}
if( !bfound )
{ Error(37, 4, "B missing in font file %s (line %d)",
FATAL, &fpos(AFMfilename), FileName(fnum), *lnum);
}
if( lig[ch] == 1 ) lig[ch] = 0; /* set to known if unknown */
else if( lig[ch] > 1 ) /* add '\0' to end of ligs */
lig[(*ligtop)++] = '\0';
if( BackEnd->uses_font_metrics )
{
fnt[ch].left = llx;
fnt[ch].down = lly - xheight2;
fnt[ch].right = wx;
fnt[ch].up = ury - xheight2;
fnt[ch].last_adjust = (urx==0 || wx==0 || fixed_pitch) ? 0 : urx - wx;
}
else
{
fnt[ch].left = 0;
fnt[ch].down = - PlainCharHeight / 2;
fnt[ch].right = PlainCharWidth;
fnt[ch].up = PlainCharHeight / 2;
fnt[ch].last_adjust = 0;
}
debug6(DFT, DDD, " fnt[%c] = (%d,%d,%d,%d,%d)",ch, fnt[ch].left,
fnt[ch].down, fnt[ch].right, fnt[ch].up, fnt[ch].last_adjust);
}
}
} /* end ReadCharMetrics */
/*****************************************************************************/
/* */
/* ReadCompositeMetrics(face, Extrafilename, extra_fnum, lnum, composite, */
/* cmp, cmptop, fp) */
/* */
/* Read a sequence of composite metrics lines. The font record is face. */
/* The line number is lnum; input is to be read from file fp. */
/* */
/*****************************************************************************/
static void ReadCompositeMetrics(OBJECT face, OBJECT Extrafilename,
FILE_NUM extra_fnum, int *lnum, unsigned short composite[],
COMPOSITE cmp[], int *cmptop, FILE *fp)
{ char *status;
FULL_CHAR buff[MAX_BUFF], composite_name[100], name[100];
int composite_num, x_offset, y_offset, i, count;
FULL_CHAR composite_code, code;
/* build composites */
while( (status = StringFGets(buff, MAX_BUFF, fp)) != (char *) NULL
&& StringBeginsWith(buff, AsciiToFull("CC")) )
{
(*lnum)++;
debug1(DFT, D, " composite: %s", buff);
/* read CC <charname> <number_of_pieces> ; and move i to after it */
if( sscanf((char *)buff, "CC %s %d ", composite_name, &composite_num) != 2 )
Error(37, 5, "syntax error in extra font file %s (line %d)",
FATAL, &fpos(Extrafilename), FileName(extra_fnum), *lnum);
for( i = 0; buff[i] != ';' && buff[i] != '\n' && buff[i] != '\0'; i++ );
if( buff[i] != ';' )
Error(37, 5, "syntax error in extra font file %s (line %d)",
FATAL, &fpos(Extrafilename), FileName(extra_fnum), *lnum);
i++;
/* add entry for this character to composite */
composite_code = MapCharEncoding(composite_name,font_mapping(face));
if( composite_code == (FULL_CHAR) '\0' )
Error(37, 6, "unknown character name %s in font file %s (line %d)",
FATAL, &fpos(Extrafilename), FileName(extra_fnum), *lnum);
composite[composite_code] = *cmptop;
for( count = 0; count < composite_num; count++ )
{
/* read one PCC <charname> <xoffset> <yoffset> ; and move i to after it */
if( sscanf((char *)&buff[i]," PCC %s %d %d",name,&x_offset,&y_offset)!=3 )
Error(37, 5, "syntax error in extra font file %s (line %d)",
FATAL, &fpos(Extrafilename), FileName(extra_fnum), *lnum);
for( ; buff[i] != ';' && buff[i] != '\n' && buff[i] != '\0'; i++ );
if( buff[i] != ';' )
Error(37, 5, "syntax error in extra font file %s (line %d)",
FATAL, &fpos(Extrafilename), FileName(extra_fnum), *lnum);
i++;
/* load this piece into cmp */
if( *cmptop >= MAX_CHARS )
Error(37, 7, "too many composites in file %s (at line %d)",
FATAL, &fpos(Extrafilename), FileName(extra_fnum), *lnum);
code = MapCharEncoding(name, font_mapping(face));
cmp[*cmptop].char_code = code;
cmp[*cmptop].x_offset = x_offset;
cmp[*cmptop].y_offset = y_offset;
(*cmptop)++;
}
/* add null terminating component */
if( *cmptop >= MAX_CHARS )
Error(37, 8, "too many composites in file %s (at line %d)",
FATAL, &fpos(Extrafilename), FileName(extra_fnum), *lnum);
cmp[*cmptop].char_code = (FULL_CHAR) '\0';
(*cmptop)++;
}
if( status == (char *) NULL ||
!StringBeginsWith(buff, AsciiToFull("EndBuildComposites")) )
Error(37, 9, "missing EndBuildComposites in extra font file %s (line %d)",
FATAL, &fpos(Extrafilename), FileName(extra_fnum), *lnum);
} /* end ReadCompositeMetrics */
/*@::FontRead()@**************************************************************/
/* */
/* static OBJECT FontRead(FULL_CHAR *family_name, *face_name, OBJECT err) */
/* */
/* Search the font databases for a font with this family and face name. */
/* If found, read the font and update this module's data structures, then */
/* return the face object. */
/* */
/* If an error occurs, use fpos(err) for reporting its location if nothing */
/* better suggests itself. */
/* */
/*****************************************************************************/
static OBJECT FontRead(FULL_CHAR *family_name, FULL_CHAR *face_name, OBJECT err)
{
OBJECT cs, link, db, fontdef_obj, y, ylink;
FULL_CHAR tag[100], seq[100];
FILE_NUM dfnum; long dfpos, cont; int dlnum;
BOOLEAN font_name_found;
OBJECT family, face, font_name, AFMfilename, Extrafilename, LCMfilename;
OBJECT recode, first_size;
FULL_CHAR buff[MAX_BUFF], command[MAX_BUFF], ch;
char *status;
int xheight2, i, lnum, ligtop, cmptop;
float fl_xheight2, fl_under_pos, fl_under_thick;
int under_pos, under_thick;
BOOLEAN upfound, utfound, xhfound;
BOOLEAN fixed_pitch = FALSE;
FILE_NUM fnum, extra_fnum; FILE *fp, *extra_fp;
struct metrics *fnt;
FULL_CHAR *lig; unsigned short *composite; COMPOSITE *cmp;
unsigned short *kt; FULL_CHAR *kc; unsigned char *kv; SHORT_LENGTH *ks;
debug2(DFT, D, "FontRead(%s, %s)", family_name, face_name);
/***************************************************************************/
/* */
/* Get the @FontDef object with tag family_name-face_name from databases */
/* */
/***************************************************************************/
/* if no databases available, fatal error */
cs = cross_sym(FontDefSym);
if( cs == nilobj )
{ Error(37, 10, "unable to set font %s %s (no font databases loaded)",
FATAL, no_fpos, family_name, face_name);
}
/* search the databases for @FontDef @Tag { family-face } */
sprintf( (char *) tag, "%s-%s", family_name, face_name);
for( link = NextUp(Up(cs)); link != cs; link = NextUp(link) )
{ Parent(db, link);
if( DbRetrieve(db, FALSE, FontDefSym,tag,seq,&dfnum,&dfpos,&dlnum,&cont) )
break;
}
/* if not found, return nilobj */
if( link == cs )
{ debug0(DFT, D, "FontRead returning nilobj (not in any database)");
return nilobj;
}
/* found it; read @FontDef object from database file */
SwitchScope(nilobj);
fontdef_obj = ReadFromFile(dfnum, dfpos, dlnum);
UnSwitchScope(nilobj);
if( fontdef_obj == nilobj )
Error(37, 11, "cannot read %s for %s", INTERN, no_fpos, KW_FONTDEF, tag);
/***************************************************************************/
/* */
/* Extract the attributes of fontdef_obj, and check that they are OK. */
/* */
/***************************************************************************/
/* extract the various attributes */
family = face = font_name = AFMfilename = nilobj;
Extrafilename = LCMfilename = recode = nilobj;
for( ylink=Down(fontdef_obj); ylink != fontdef_obj; ylink=NextDown(ylink) )
{ Child(y, ylink);
assert( type(y) == PAR, "FontRead: type(y) != PAR!" );
if( actual(y) == fd_tag )
{
/* do nothing with this one */
}
else if( actual(y) == fd_family )
{ Child(family, Down(y));
if( !is_word(type(family)) || !StringEqual(string(family), family_name) )
Error(37, 12, "font family name %s incompatible with %s value %s",
FATAL, &fpos(fontdef_obj), string(family), KW_TAG, tag);
}
else if( actual(y) == fd_face )
{ Child(face, Down(y));
if( !is_word(type(face)) || !StringEqual(string(face), face_name) )
Error(37, 13, "font face name %s incompatible with %s value %s",
FATAL, &fpos(fontdef_obj), string(face), KW_TAG, tag);
}
else if( actual(y) == fd_name )
{ Child(font_name, Down(y));
font_name = ReplaceWithTidy(font_name, TRUE);
if( !is_word(type(font_name)) )
Error(37, 14, "illegal font name (quotes needed?)",
FATAL, &fpos(font_name));
}
else if( actual(y) == fd_metrics )
{ Child(AFMfilename, Down(y));
AFMfilename = ReplaceWithTidy(AFMfilename, TRUE);
if( !is_word(type(AFMfilename)) )
Error(37, 15, "illegal font metrics file name (quotes needed?)",
FATAL, &fpos(AFMfilename));
}
else if( actual(y) == fd_extra_metrics )
{ Child(Extrafilename, Down(y));
Extrafilename = ReplaceWithTidy(Extrafilename, TRUE);
if( !is_word(type(Extrafilename)) )
Error(37, 16, "illegal font extra metrics file name (quotes needed?)",
FATAL, &fpos(Extrafilename));
}
else if( actual(y) == fd_mapping )
{ Child(LCMfilename, Down(y));
LCMfilename = ReplaceWithTidy(LCMfilename, TRUE);
if( !is_word(type(LCMfilename)) )
Error(37, 17, "illegal mapping file name (quotes needed?)",
FATAL, &fpos(LCMfilename));
}
else if( actual(y) == fd_recode )
{ Child(recode, Down(y));
recode = ReplaceWithTidy(recode, TRUE);
if( !is_word(type(recode)) )
Error(37, 18, "illegal value of %s", FATAL, &fpos(recode),
SymName(fd_recode));
}
else
{ assert(FALSE, "FontRead: cannot identify component of FontDef")
}
}
/* check that all the compulsory ones were found */
/* a warning message will have already been given if not */
if( family == nilobj || face == nilobj || font_name == nilobj ||
AFMfilename == nilobj || LCMfilename == nilobj )
{
debug0(DFT, D, "FontRead returning nilobj (missing compulsory)");
return nilobj;
}
/***************************************************************************/
/* */
/* Update font tree to have this family, face and first_size. */
/* */
/***************************************************************************/
/* insert family into font tree if not already present */
for( link = Down(font_root); link != font_root; link = NextDown(link) )
{ Child(y, link);
if( StringEqual(string(y), string(family)) )
{ family = y;
break;
}
}
if( link == font_root )
MoveLink(Up(family), font_root, PARENT);
/* insert face into family, or error if already present */
for( link = Down(family); link != family; link = NextDown(link) )
{ Child(y, link);
if( StringEqual(string(y), string(face)) )
{ Error(37, 19, "font %s %s already defined, at%s", WARN, &fpos(face),
string(family), string(face), EchoFilePos(&fpos(y)));
debug0(DFT, D, "FontRead returning: font already defined");
DisposeObject(fontdef_obj);
return y;
}
}
MoveLink(Up(face), family, PARENT);
/* PostScript name and AFM file name are first two children of face */
Link(face, font_name);
Link(face, AFMfilename);
/* AFM file name has extra file name as optional child */
if( Extrafilename != nilobj )
Link(AFMfilename, Extrafilename);
/* load character mapping file */
if( recode != nilobj && StringEqual(string(recode), AsciiToFull("No")) )
{ font_recoded(face) = FALSE;
font_mapping(face) = MapLoad(LCMfilename, FALSE);
}
else if( recode == nilobj || StringEqual(string(recode), AsciiToFull("Yes")) )
{ font_recoded(face) = TRUE;
font_mapping(face) = MapLoad(LCMfilename, TRUE);
}
else Error(37, 20, "expecting either Yes or No here", FATAL, &fpos(recode));
/* say that this font is currently unused on any page */
font_page(face) = 0;
/* get a new number for this (default) font size */
if( ++font_count >= finfo_size )
{ if( font_count > MAX_FONT )
Error(37, 21, "too many different fonts and sizes (maximum is %d)",
FATAL, &fpos(err),MAX_FONT);
ifdebug(DMA, D,
DebugRegisterUsage(MEM_FONTS, -1, -finfo_size * sizeof(FONT_INFO)));
finfo_size *= 2;
ifdebug(DMA, D,
DebugRegisterUsage(MEM_FONTS, 1, finfo_size * sizeof(FONT_INFO)));
finfo = (FONT_INFO *) realloc(finfo, finfo_size * sizeof(FONT_INFO));
if( finfo == (FONT_INFO *) NULL )
Error(37, 22, "run out of memory when increasing font table size",
FATAL, &fpos(err));
}
/* build the first size record, and initialize it with what we know now */
first_size = MakeWordTwo(WORD, AsciiToFull("fnt"), StringInt(++font_seqnum),
no_fpos);
Link(face, first_size);
font_num(first_size) = font_count;
font_size(first_size) = BackEnd->uses_font_metrics ? SZ_DFT : PlainCharHeight;
font_recoded(first_size) = font_recoded(face);
font_mapping(first_size) = font_mapping(face);
font_num(face) = font_num(first_size); /* Uwe's suggestion, helps PDF */
/* leaves font_xheight2 and font_spacewidth still to do */
/***************************************************************************/
/* */
/* Read the Adobe font metrics file, and record what's in it. */
/* */
/***************************************************************************/
/* open the Adobe font metrics (AFM) file of the font */
debug0(DFS, D, " calling DefineFile from FontRead");
fnum = DefineFile(string(AFMfilename), STR_EMPTY, &fpos(AFMfilename),
FONT_FILE, FONT_PATH);
fp = OpenFile(fnum, FALSE, FALSE);
if( fp == NULL )
Error(37, 23, "cannot open font file %s", FATAL, &fpos(AFMfilename),
FileName(fnum));
/* check that the AFM file begins, as it should, with "StartFontMetrics" */
if( StringFGets(buff, MAX_BUFF, fp) == NULL ||
sscanf( (char *) buff, "%s", command) != 1 ||
!StringEqual(command, "StartFontMetrics") )
{ debug1(DFT, DD, "first line of AFM file:%s", buff);
debug1(DFT, DD, "command:%s", command);
Error(37, 24, "font file %s does not begin with StartFontMetrics",
FATAL, &fpos(AFMfilename), FileName(fnum));
}
/* initialise font metrics table for the new font */
ifdebug(DMA, D,
DebugRegisterUsage(MEM_FONTS, 1, MAX_CHARS * sizeof(struct metrics)));
fnt = (struct metrics *) malloc(MAX_CHARS * sizeof(struct metrics));
if( fnt == (struct metrics *) NULL )
Error(37, 25, "run out of memory while reading font file %s",
FATAL, &fpos(err), FileName(fnum));
ifdebug(DMA, D,
DebugRegisterUsage(MEM_FONTS, 0, 2*MAX_CHARS*sizeof(FULL_CHAR)));
/* initialise ligature table for the new font */
lig = (FULL_CHAR *) malloc(2*MAX_CHARS*sizeof(FULL_CHAR));
if( lig == (FULL_CHAR *) NULL )
Error(37, 25, "run out of memory while reading font file %s",
FATAL, &fpos(err), FileName(fnum));
for( i = 0; i < MAX_CHARS; i++ ) lig[i] = 1; /* i.e. char unknown */
ligtop = MAX_CHARS+2; /* must avoid ligtop - MAX_CHARS == 0 or 1 */
/* initialise composites table for the new font */
composite = (unsigned short *) malloc(MAX_CHARS * sizeof(unsigned short));
if( composite == (unsigned short *) NULL )
Error(37, 25, "run out of memory while reading font file %s",
FATAL, &fpos(err), FileName(fnum));
cmp = (COMPOSITE *) malloc(MAX_CHARS * sizeof(COMPOSITE));
if( cmp == (COMPOSITE *) NULL )
Error(37, 25, "run out of memory while reading font file %s",
FATAL, &fpos(err), FileName(fnum));
for( i = 0; i < MAX_CHARS; i++ ) composite[i] = 0; /* i.e. not composite */
cmptop = 1; /* must avoid cmptop == 0 */
/* initialise kerning table for the new font */
ifdebug(DMA, D,
DebugRegisterUsage(MEM_FONTS, 0, MAX_CHARS * sizeof(unsigned short)));
kt = (unsigned short *) malloc(MAX_CHARS * sizeof(unsigned short));
if( kt == (unsigned short *) NULL )
Error(37, 25, "run out of memory while reading font file %s",
FATAL, &fpos(err), FileName(fnum));
for( i = 0; i < MAX_CHARS; i++ ) kt[i] = 0; /* i.e. no kerns */
ks = (SHORT_LENGTH *) NULL; /* i.e. no kern sizes */
/* read font metrics file fp */
xhfound = upfound = utfound = FALSE;
xheight2 = under_thick = under_pos = 0;
kc = (FULL_CHAR *) NULL;
kv = (unsigned char *) NULL;
ks = (SHORT_LENGTH *) NULL;
font_name_found = FALSE; lnum = 1;
while( (status = StringFGets(buff, MAX_BUFF, fp)) != (char *) NULL &&
!(buff[0] == 'E' && StringEqual(buff, AsciiToFull("EndFontMetrics\n"))) )
{
lnum++;
sscanf( (char *) buff, "%s", command);
switch( command[0] )
{
case 'U':
if( StringEqual(command, AsciiToFull("UnderlinePosition")) )
{ if( upfound )
{ Error(37, 26, "UnderlinePosition found twice in font file (line %d)",
FATAL, &fpos(AFMfilename), lnum);
}
sscanf( (char *) buff, "UnderlinePosition %f", &fl_under_pos);
under_pos = fl_under_pos;
upfound = TRUE;
}
else if( StringEqual(command, AsciiToFull("UnderlineThickness")) )
{ if( utfound )
{ Error(37, 27, "UnderlineThickness found twice in font file (line %d)",
FATAL, &fpos(AFMfilename), lnum);
}
sscanf( (char *) buff, "UnderlineThickness %f", &fl_under_thick);
under_thick = fl_under_thick;
utfound = TRUE;
}
break;
case 'X':
if( StringEqual(command, AsciiToFull("XHeight")) )
{ if( xhfound )
{ Error(37, 28, "XHeight found twice in font file (line %d)",
FATAL, &fpos(AFMfilename), lnum);
}
sscanf( (char *) buff, "XHeight %f", &fl_xheight2);
xheight2 = fl_xheight2 / 2;
xhfound = TRUE;
}
break;
case 'F':
if( StringEqual(command, AsciiToFull("FontName")) )
{ if( font_name_found )
{ Error(37, 29, "FontName found twice in font file %s (line %d)",
FATAL, &fpos(AFMfilename), FileName(fnum), lnum);
}
sscanf( (char *) buff, "FontName %s", command);
if( StringEqual(command, STR_EMPTY) )
{ Error(37, 30, "FontName empty in font file %s (line %d)",
FATAL, &fpos(AFMfilename), FileName(fnum), lnum);
}
Child(y, Down(face));
if( !StringEqual(command, string(y)) )
Error(37, 31, "FontName in font file (%s) and %s (%s) disagree",
WARN, &fpos(AFMfilename), command, KW_FONTDEF, string(y));
font_name_found = TRUE;
}
break;
case 'I':
if( StringEqual(command, AsciiToFull("IsFixedPitch")) )
{
sscanf( (char *) buff, "IsFixedPitch %s", command);
if( StringEqual(command, AsciiToFull("true")) )
{ fixed_pitch = TRUE;
}
}
break;
case 'S':
if( StringEqual(command, AsciiToFull("StartCharMetrics")) )
{
if( !font_name_found )
Error(37, 32, "FontName missing in file %s",
FATAL, &fpos(AFMfilename), FileName(fnum));
if( !xhfound ) xheight2 = DEFAULT_XHEIGHT / 2;
ReadCharMetrics(face, fixed_pitch, xheight2, lig, &ligtop,
fnum, fnt, &lnum, fp);
}
else if( BackEnd->uses_font_metrics && Kern &&
StringEqual(command, AsciiToFull("StartKernPairs")) )
{ FULL_CHAR ch1, ch2, last_ch1;
FULL_CHAR name1[30], name2[30];
int kc_top, ks_top, pos, num_pairs, ksize; float fl_ksize;
if( sscanf( (char *) buff, "StartKernPairs %d", &num_pairs) != 1 )
Error(37, 33, "syntax error on StartKernPairs line in font file %s (line %d)",
FATAL, &fpos(AFMfilename), FileName(fnum), lnum);
kc_top = 1; ks_top = 1;
ifdebug(DMA, D,
DebugRegisterUsage(MEM_FONTS, 0, 2*num_pairs * sizeof(FULL_CHAR)));
kc = (FULL_CHAR *) malloc(2 * num_pairs * sizeof(FULL_CHAR));
ifdebug(DMA, D, DebugRegisterUsage(MEM_FONTS, 0,
2 * num_pairs * sizeof(unsigned char)));
kv = (unsigned char *) malloc(2 * num_pairs * sizeof(unsigned char));
ifdebug(DMA, D, DebugRegisterUsage(MEM_FONTS, 0,
num_pairs * sizeof(SHORT_LENGTH)));
ks = (SHORT_LENGTH *) malloc(num_pairs * sizeof(SHORT_LENGTH));
last_ch1 = '\0';
while( StringFGets(buff, MAX_BUFF, fp) == (char *) buff &&
!StringBeginsWith(buff, AsciiToFull("EndKernPairs")) )
{
debug1(DFT, DD, "FontRead reading %s", buff);
lnum++;
if( StringBeginsWith(buff, AsciiToFull("KPX")) )
{
/* get the two character names and kern size from buff */
if( sscanf((char *)buff, "KPX %s %s %f",name1,name2,&fl_ksize)!=3 )
Error(37, 34, "syntax error in font file %s (line %d): %s",
FATAL, &fpos(AFMfilename), FileName(fnum), lnum, buff);
/* ignore size 0 kern pairs (they are frequent, why?) */
ksize = fl_ksize;
if( ksize == 0 ) continue;
/* check that both characters are encoded */
ch1 = MapCharEncoding(name1, font_mapping(face));
if( ch1 == '\0' )
{
continue;
}
ch2 = MapCharEncoding(name2, font_mapping(face));
if( ch2 == '\0' )
{
continue;
}
/* check that ch1 is contiguous with previous occurrences */
if( ch1 != last_ch1 && kt[ch1] != 0 )
{ Error(37, 35, "non-contiguous kerning pair %s %s in font file %s (line %d)",
WARN, &fpos(AFMfilename), name1, name2, FileName(fnum), lnum);
continue;
}
last_ch1 = ch1;
/* if ch1 never seen before, make new entry in kt[] and kc[] */
if( kt[ch1] == 0 )
{ debug2(DFT, DD, " kt[%d] = %d", ch1, kc_top);
kt[ch1] = kc_top;
kc[kc_top] = (FULL_CHAR) '\0';
kv[kc_top] = 0;
kc_top++;
}
/* find kerning size in ks[] or else add it to the end */
for( pos = 1; pos < ks_top; pos++ )
{ if( ks[pos] == ksize ) break;
}
if( pos == ks_top )
{ if( ks_top == num_pairs )
Error(37, 36, "too many kerning pairs in font file %s (line %d)",
FATAL, &fpos(AFMfilename), FileName(fnum), lnum);
debug2(DFT, DD, " ks[%d] = %d", pos, ksize);
ks[pos] = ksize;
ks_top++;
}
/* insert ch2 into the kc entries (sorted decreasing) for ch1 */
for( i = kc_top-1; i >= kt[ch1] && kc[i] < ch2; i-- )
{ kc[i+1] = kc[i];
kv[i+1] = kv[i];
}
if( i >= kt[ch1] && kc[i] == ch2 )
Error(37, 37, "kerning pair %s %s appears twice in font file %s (line %d)",
FATAL, &fpos(AFMfilename), name1, name2, FileName(fnum), lnum);
kc[i+1] = ch2;
kv[i+1] = pos;
kc_top++;
}
}
ks[0] = ks_top;
}
break;
default:
break;
}
}
/* make sure we terminated the font metrics file gracefully */
if( status == (char *) NULL )
Error(37, 38, "EndFontMetrics missing from font file %s",
FATAL, &fpos(AFMfilename), FileName(fnum));
fclose(fp);
fp = (FILE *) NULL;
/* complete the initialization of first_size */
font_xheight2(first_size) =
BackEnd->uses_font_metrics ? xheight2 : PlainCharHeight / 4;
ch = MapCharEncoding(STR_PS_SPACENAME, font_mapping(first_size));
font_spacewidth(first_size) = ch == '\0' ? 0 : fnt[ch].right;
/***************************************************************************/
/* */
/* Read the optional Extra font metrics file, and record what's in it. */
/* */
/***************************************************************************/
if( Extrafilename != nilobj )
{ debug0(DFS, D, " calling DefineFile from FontRead (extra_filename)");
extra_fnum = DefineFile(string(Extrafilename), STR_EMPTY,
&fpos(Extrafilename), FONT_FILE, FONT_PATH);
extra_fp = OpenFile(extra_fnum, FALSE, FALSE);
if( extra_fp == NULL )
Error(37, 39, "cannot open extra font file %s", FATAL,
&fpos(Extrafilename), FileName(extra_fnum));
lnum = 0;
while( StringFGets(buff, MAX_BUFF, extra_fp) != (char *) NULL )
{
debug1(DFT, D, " Extra: %s", buff);
lnum++;
sscanf( (char *) buff, "%s", command);
if( command[0] == 'S' )
{
if( StringEqual(command, AsciiToFull("StartExtraCharMetrics")) )
{
/* get extra character metrics, just like the others */
debug0(DFT, D, " StartExtraCharMetrics calling ReadCharMetrics");
ReadCharMetrics(face, fixed_pitch, xheight2, lig, &ligtop,
extra_fnum, fnt, &lnum, extra_fp);
}
else if( StringEqual(command, AsciiToFull("StartBuildComposites")) )
{
/* build composites */
debug0(DFT, D, " StartBuildComposites");
ReadCompositeMetrics(face, Extrafilename, extra_fnum, &lnum,
composite, cmp, &cmptop, extra_fp);
}
}
}
fclose(extra_fp);
extra_fp = (FILE *) NULL;
}
/***************************************************************************/
/* */
/* Set finfo[fontcount] and exit. */
/* */
/***************************************************************************/
finfo[font_count].font_table = first_size;
finfo[font_count].original_face = face;
finfo[font_count].underline_pos = xheight2 - under_pos;
finfo[font_count].underline_thick = under_thick;
finfo[font_count].size_table = fnt;
finfo[font_count].lig_table = lig;
finfo[font_count].composite = composite;
finfo[font_count].cmp_table = cmp;
finfo[font_count].cmp_top = cmptop;
finfo[font_count].kern_table = kt;
finfo[font_count].kern_chars = kc;
finfo[font_count].kern_value = kv;
finfo[font_count].kern_sizes = ks;
ifdebug(DFT, DD, DebugKernTable(font_count));
debug4(DFT, D, "FontRead returning: %d, name %s, fs %d, xh2 %d",
font_count, string(first_size), font_size(first_size), xheight2);
return face;
} /* end FontRead */
/*@::FontChange()@************************************************************/
/* */
/* FontChange(style, x) */
/* */
/* Returns an internal font number which is the current font changed */
/* according to word object x. e.g. if current font is Roman 12p and x is */
/* "-3p", then FontChange returns the internal font number of Roman 9p. */
/* */
/* FontChange permits empty and null objects within x; these have no */
/* effect. */
/* */
/*****************************************************************************/
void FontChange(STYLE *style, OBJECT x)
{ /* register */ int i;
OBJECT requested_family, requested_face, requested_size;
OBJECT par[3], family, face, fsize, y, link, new, old, tmpf;
GAP gp; SHORT_LENGTH flen; int num, c; unsigned inc;
struct metrics *newfnt, *oldfnt;
FULL_CHAR *lig;
int cmptop;
COMPOSITE *oldcmp, *newcmp;
SHORT_LENGTH *oldks, *newks; int klen;
debug2(DFT, D, "FontChange( %s, %s )", EchoStyle(style), EchoObject(x));
assert( font(*style) <= font_count, "FontChange: font_count!");
ifdebug(DFT, DD, FontDebug());
/***************************************************************************/
/* */
/* Analyse x, doing any small-caps style changes immediately, and putting */
/* all the other words of x into par[0 .. num-1] for further analysis. */
/* */
/***************************************************************************/
num = 0;
if( type(x) == NULL_CLOS )
{ /* acceptable, but do nothing */
}
else if( is_word(type(x)) )
{
if( StringEqual(string(x), STR_SMALL_CAPS_ON) )
small_caps(*style) = SMALL_CAPS_ON;
else if( StringEqual(string(x), STR_SMALL_CAPS_OFF) )
small_caps(*style) = SMALL_CAPS_OFF;
else if( !StringEqual(string(x), STR_EMPTY) )
par[num++] = x;
}
else if( type(x) == ACAT )
{ for( link = Down(x); link != x; link = NextDown(link) )
{ Child(y, link);
debug1(DFT, DDD, " pars examining y = %s", EchoObject(y));
if( type(y) == GAP_OBJ || type(y) == NULL_CLOS ) continue;
if( is_word(type(y)) )
{
if( StringEqual(string(y), STR_SMALL_CAPS_ON) )
small_caps(*style) = SMALL_CAPS_ON;
else if( StringEqual(string(y), STR_SMALL_CAPS_OFF) )
small_caps(*style) = SMALL_CAPS_OFF;
else if( !StringEqual(string(y), STR_EMPTY) )
{
if( num >= 3 )
{ Error(37, 40, "error in left parameter of %s",
WARN, &fpos(x), KW_FONT);
debug0(DFT, D, "FontChange returning: ACAT children");
return;
}
par[num++] = y;
}
}
else
{ Error(37, 41, "error in left parameter of %s",
WARN, &fpos(x), KW_FONT);
debug0(DFT, D, "FontChange returning: ACAT children");
return;
}
}
}
else
{ Error(37, 42, "error in left parameter of %s", WARN, &fpos(x), KW_FONT);
debug0(DFT, D, "FontChange returning: wrong type");
return;
}
debug1(DFT, DDD, " found pars, num = %d", num);
if( num == 0 )
{ debug1(DFT, D, "FontChange returning %s", EchoStyle(style));
return;
}
/***************************************************************************/
/* */
/* Extract size, family, and face changes (if any) from par[0 .. num-1]. */
/* */
/***************************************************************************/
/* extract fsize parameter, if any */
assert( num >= 1 && num <= 3, "FontChange: num!" );
requested_size = nilobj;
for( i = 0; i < num; i++ )
{
c = string(par[i])[0];
if( c == CH_INCGAP || c == CH_DECGAP || decimaldigit(c) )
{
/* extract fsize, shuffle the rest down */
requested_size = par[i];
for( i = i + 1; i < num; i++ )
par[i-1] = par[i];
num--;
}
}
/* what remains must be family and face */
switch( num )
{
case 0:
requested_family = requested_face = nilobj;
break;
case 1:
requested_family = nilobj;
requested_face = par[0];
break;
case 2:
requested_family = par[0];
requested_face = par[1];
break;
default:
Error(37, 43, "error in left parameter of %s", WARN, &fpos(x), KW_FONT);
debug0(DFT, D, "FontChange returning: too many parameters");
return;
break;
}
/* check for initial font case: must have family, face, and size */
if( font(*style) == NO_FONT && (requested_size == nilobj ||
requested_family == nilobj || requested_face == nilobj) )
Error(37, 44, "initial font must have family, face and size",
FATAL, &fpos(x));
/***************************************************************************/
/* */
/* Either find the family and face already existing, or load them. */
/* */
/***************************************************************************/
/* get font family */
family = nilobj;
if( requested_family != nilobj )
{
/* search for this family */
for( link = Down(font_root); link != font_root; link = NextDown(link) )
{ Child(y, link);
if( StringEqual(string(requested_family), string(y)) ) break;
}
if( link != font_root )
family = y;
}
else
{
/* preserve current family */
assert( Up(finfo[font(*style)].font_table)!=finfo[font(*style)].font_table,
"FontChange: Up(finfo[font(*style)].font_table) !" );
Parent(tmpf, Up(finfo[font(*style)].font_table));
assert( is_word(type(tmpf)), "FontChange: type(tmpf)!" );
assert( Up(tmpf) != tmpf, "FontChange: Up(tmpf)!" );
Parent(family, Up(tmpf));
assert( is_word(type(family)), "FontChange: type(family)!" );
}
/* get font face, if have family */
face = nilobj;
if( family != nilobj )
{
if( requested_face != nilobj )
{
/* search for this face in family */
for( link = Down(family); link != family; link = NextDown(link) )
{ Child(y, link);
if( StringEqual(string(requested_face), string(y)) ) break;
}
if( link != family )
face = y;
}
else
{
/* preserve current face */
Parent(face, Up(finfo[font(*style)].font_table));
assert( is_word(type(face)), "FontChange: type(face)!" );
assert( Up(face) != face, "FontChange: Up(face)!" );
}
}
if( face == nilobj )
{
/* face not loaded, try the font databases */
assert( family != nilobj || requested_family != nilobj, "FontChange fr!" );
assert( requested_face != nilobj, "FontChange requested_face!");
if( family != nilobj )
requested_family = family;
face = FontRead(string(requested_family), string(requested_face), x);
if( face == nilobj )
{
/* missing face name error; check whether a family name was intended */
for( link = Down(font_root); link != font_root; link = NextDown(link) )
{ Child(y, link);
if( StringEqual(string(y), string(requested_face)) ) break;
}
if( link != font_root )
Error(37, 45, "font family name %s must be followed by a face name",
WARN, &fpos(requested_face), string(requested_face));
else
Error(37, 46, "there is no font with family name %s and face name %s",
WARN, &fpos(requested_face), string(requested_family),
string(requested_face));
debug0(DFT, D, "FontChange returning (unable to set face)");
return;
}
}
assert( Down(face) != face, "FontChange: no children!" );
assert( NextDown(Down(face)) != face, "FontChange: 1 child!" );
assert( NextDown(NextDown(Down(face))) != face, "FontChange: 2 children!" );
/***************************************************************************/
/* */
/* Now have family and face; search for size and return it if found. */
/* */
/***************************************************************************/
/* get font size as integer flen */
if( requested_size == nilobj )
flen = font_size(finfo[font(*style)].font_table);
else
{ GetGap(requested_size, style, &gp, &inc);
if( mode(gp) != EDGE_MODE || units(gp) != FIXED_UNIT )
{ Error(37, 47, "syntax error in font size %s; ignoring it",
WARN, &fpos(requested_size), string(requested_size));
flen = font_size(finfo[font(*style)].font_table);
}
else if( inc == GAP_ABS )
flen = width(gp);
else if( font(*style) == NO_FONT )
{ Error(37, 48, "no current font on which to base size change %s",
FATAL, &fpos(requested_size), string(requested_size));
}
else if( inc == GAP_INC )
flen = font_size(finfo[font(*style)].font_table) + width(gp);
else if( inc == GAP_DEC )
flen = font_size(finfo[font(*style)].font_table) - width(gp);
else Error(37, 49, "FontChange: %d", INTERN, &fpos(x), inc);
}
if( flen <= 0 )
{ Error(37, 50, "%s %s ignored (result is not positive)",
WARN, &fpos(requested_size), string(requested_size), KW_FONT);
debug0(DFT, D,"FontChange returning (non-positive size)");
return;
}
/* search fonts of face for desired size; return if already present */
if( !(BackEnd->uses_font_metrics) ) flen = PlainCharHeight;
for( link=NextDown(NextDown(Down(face))); link!=face; link = NextDown(link) )
{ Child(fsize, link);
if( font_size(fsize) == flen )
{ font(*style) = font_num(fsize);
SetGap(space_gap(*style), nobreak(space_gap(*style)), FALSE, TRUE,
FIXED_UNIT, EDGE_MODE, font_spacewidth(fsize));
debug2(DFT, D,"FontChange returning (old) %d (XHeight2 = %d)",
font(*style), font_xheight2(finfo[font(*style)].font_table));
return;
}
}
/***************************************************************************/
/* */
/* No suitable size right now, so scale the original size and exit. */
/* */
/***************************************************************************/
/* get a new number for this new size */
if( ++font_count >= finfo_size )
{ if( font_count > MAX_FONT )
Error(37, 51, "too many different fonts and sizes (max is %d)",
FATAL, &fpos(x), MAX_FONT);
ifdebug(DMA, D, DebugRegisterUsage(MEM_FONTS, -1,
-finfo_size * sizeof(FONT_INFO)));
finfo_size *= 2;
ifdebug(DMA, D, DebugRegisterUsage(MEM_FONTS, 1,
finfo_size * sizeof(FONT_INFO)));
finfo = (FONT_INFO *) realloc(finfo, finfo_size * sizeof(FONT_INFO));
if( finfo == (FONT_INFO *) NULL )
Error(37, 52, "run out of memory when increasing font table size",
FATAL, &fpos(x));
}
/* create a new sized font record */
Child(old, NextDown(NextDown(Down(face))));
assert( is_word(type(old)), "FontChange: old!" );
new = MakeWord(WORD, string(old), no_fpos);
Link(face, new);
font_num(new) = font_count;
font_size(new) = BackEnd->uses_font_metrics ? flen : font_size(old);
font_xheight2(new) = font_xheight2(old) * font_size(new) / font_size(old);
font_recoded(new) = font_recoded(old);
font_mapping(new) = font_mapping(old);
font_spacewidth(new) = font_spacewidth(old) * font_size(new)/font_size(old);
finfo[font_count].font_table = new;
finfo[font_count].original_face = face;
finfo[font_count].underline_pos =
(finfo[font_num(old)].underline_pos * font_size(new)) / font_size(old);
finfo[font_count].underline_thick =
(finfo[font_num(old)].underline_thick * font_size(new)) / font_size(old);
ifdebug(DMA, D, DebugRegisterUsage(MEM_FONTS, 1,
MAX_CHARS * sizeof(struct metrics)));
finfo[font_count].size_table =
(struct metrics *) malloc(MAX_CHARS * sizeof(struct metrics));
if( finfo[font_count].size_table == (struct metrics *) NULL )
Error(37, 53, "run out of memory when changing font or font size",
FATAL, &fpos(x));
finfo[font_count].lig_table = lig = finfo[font_num(old)].lig_table;
/* scale old font to new size */
newfnt = finfo[font_num(new)].size_table;
oldfnt = finfo[font_num(old)].size_table;
for( i = 0; i < MAX_CHARS; i++ ) if( lig[i] != 1 )
{ newfnt[i].left = (oldfnt[i].left * font_size(new)) / font_size(old);
newfnt[i].right = (oldfnt[i].right * font_size(new)) / font_size(old);
newfnt[i].down = (oldfnt[i].down * font_size(new)) / font_size(old);
newfnt[i].up = (oldfnt[i].up * font_size(new)) / font_size(old);
newfnt[i].last_adjust = (oldfnt[i].last_adjust * font_size(new)) / font_size(old);
}
/* copy and scale composite table */
finfo[font_count].composite = finfo[font_num(old)].composite;
finfo[font_count].cmp_top = cmptop = finfo[font_num(old)].cmp_top;
oldcmp = finfo[font_num(old)].cmp_table;
newcmp = (COMPOSITE *) malloc(cmptop*sizeof(COMPOSITE));
if( newcmp == (COMPOSITE *) NULL )
Error(37, 54, "run out of memory when changing font or font size",
FATAL, &fpos(x));
for( i = 1; i < cmptop; i++ ) /* NB position 0 is unused */
{ newcmp[i].char_code = oldcmp[i].char_code;
if( newcmp[i].char_code != (FULL_CHAR) '\0' )
{ newcmp[i].x_offset = (oldcmp[i].x_offset*font_size(new)) / font_size(old);
newcmp[i].y_offset = (oldcmp[i].y_offset*font_size(new)) / font_size(old);
debug5(DFT, D, "FontChange scales composite %d from (%d, %d) to (%d, %d)",
(int) newcmp[i].char_code, oldcmp[i].x_offset, oldcmp[i].y_offset,
newcmp[i].x_offset, newcmp[i].y_offset);
}
}
finfo[font_count].cmp_table = newcmp;
/* copy and scale kerning tables */
finfo[font_count].kern_table = finfo[font_num(old)].kern_table;
finfo[font_count].kern_chars = finfo[font_num(old)].kern_chars;
finfo[font_count].kern_value = finfo[font_num(old)].kern_value;
oldks = finfo[font_num(old)].kern_sizes;
if( oldks != (SHORT_LENGTH *) NULL )
{ klen = oldks[0];
ifdebug(DMA, D, DebugRegisterUsage(MEM_FONTS, 0, klen * sizeof(SHORT_LENGTH)));
finfo[font_count].kern_sizes = newks =
(SHORT_LENGTH *) malloc(klen * sizeof(SHORT_LENGTH));
if( newks == (SHORT_LENGTH *) NULL )
Error(37, 55, "run out of memory when changing font or font size",
FATAL, &fpos(x));
newks[0] = klen;
for( i = 1; i < klen; i++ )
newks[i] = (oldks[i] * font_size(new)) / font_size(old);
}
else finfo[font_count].kern_sizes = (SHORT_LENGTH *) NULL;
/* return new font number and exit */
font(*style) = font_count;
SetGap(space_gap(*style), nobreak(space_gap(*style)), FALSE, TRUE,
FIXED_UNIT, EDGE_MODE, font_spacewidth(new));
debug2(DFT, D,"FontChange returning (scaled) %d (XHeight2 = %d)",
font(*style), font_xheight2(finfo[font(*style)].font_table));
/* FontDebug(); */
} /* end FontChange */
/*****************************************************************************/
/* */
/* KernLength(fnum, ch1, ch2, res) */
/* */
/* Set res to the kern length between ch1 and ch2 in font fnum, or 0 if */
/* none. Actually we first convert ch1 and ch2 to corresponding unaccented */
/* characters, because metrics files don't seem to contain kerning pairs */
/* for accented characters. */
/* */
/*****************************************************************************/
#define KernLength(fnum, mp, ch1, ch2, res) \
{ int ua_ch1 = mp[ch1]; \
int ua_ch2 = mp[ch2]; \
int i = finfo[fnum].kern_table[ua_ch1], j; \
if( i == 0 ) res = 0; \
else \
{ FULL_CHAR *kc = finfo[fnum].kern_chars; \
for( j = i; kc[j] > ua_ch2; j++ ); \
res = (kc[j] == ua_ch2) ? \
finfo[fnum].kern_sizes[finfo[fnum].kern_value[j]] : 0; \
} \
} /* end KernLength */
/*@::FontWordSize()@**********************************************************/
/* */
/* FontWordSize(x) */
/* */
/* Calculate the horizontal and vertical size of WORD or QWORD x, including */
/* the effect of ligature sequences but not replacing them with ligatures. */
/* */
/*****************************************************************************/
void FontWordSize(OBJECT x)
{ FULL_CHAR *p, *q, *a, *b, *lig, *unacc, *acc; OBJECT tmp;
FULL_CHAR buff[MAX_BUFF]; MAPPING m;
int r, u, d, ksize; struct metrics *fnt;
debug2(DFT, D, "FontWordSize( %s ), font = %d", string(x), word_font(x));
assert( is_word(type(x)), "FontWordSize: !is_word(type(x))!" );
p = string(x);
q = buff;
if( *p )
{ if( word_font(x) < 1 || word_font(x) > font_count )
Error(37, 56, "no current font at word %s", FATAL, &fpos(x), string(x));
if( word_colour(x) == 0 && BackEnd->colour_avail )
Error(37, 57, "no current colour at word %s", FATAL, &fpos(x), string(x));
if( word_language(x) == 0 )
Error(37, 58, "no current language at word %s", FATAL,&fpos(x),string(x));
fnt = finfo[word_font(x)].size_table;
lig = finfo[word_font(x)].lig_table;
m = font_mapping(finfo[word_font(x)].font_table);
unacc = MapTable[m]->map[MAP_UNACCENTED];
acc = MapTable[m]->map[MAP_ACCENT];
d = u = r = 0;
do
{
/* check for missing glyph (lig[] == 1) or ligatures (lig[] > 1) */
debug2(DFT, D, " examining `%c' lig = %d", *p, lig[*p]);
if( lig[*q = *p++] )
{
if( lig[*q] == 1 )
{ tmp = MakeWord(QWORD, STR_SPACE, &fpos(x));
string(tmp)[0] = *q;
/* bug fix: unaccented version exists if unacc differs from self */
if( unacc[*q] != *q )
{
/* *** this is acceptable now, let this char through
Error(37, 59, "accent dropped from character %s (it has no glyph in font %s)",
WARN, &fpos(x),
StringQuotedWord(tmp), FontFamilyAndFace(word_font(x)));
*(p-1) = *q = unacc[*q];
*** */
debug2(DFT, D, " unacc[%c] = `%c'", *q, unacc[*q]);
fnt[*q].up = fnt[unacc[*q]].up;
fnt[*q].down = fnt[unacc[*q]].down;
fnt[*q].left = fnt[unacc[*q]].left;
fnt[*q].right = fnt[unacc[*q]].right;
fnt[*q].last_adjust = fnt[unacc[*q]].last_adjust;
lig[*q] = 0;
}
else
{
debug1(DFT, D, " unacc[%c] = 0, replacing by space", *q);
Error(37, 60, "character %s replaced by space (it has no glyph in font %s)",
WARN, &fpos(x),
StringQuotedWord(tmp), FontFamilyAndFace(word_font(x)));
*(p-1) = *q = CH_SPACE;
}
Dispose(tmp);
}
else
{
debug1(DFT, D, " processing ligature beginning at %c", *q);
a = &lig[ lig[*(p-1)] + MAX_CHARS ];
while( *a++ == *(p-1) )
{ b = p;
while( *a == *b && *(a+1) != '\0' && *b != '\0' ) a++, b++;
if( *(a+1) == '\0' )
{ *q = *a;
p = b;
break;
}
else
{ while( *++a );
a++;
}
}
}
}
/* accumulate size of *q */
if( fnt[*q].up > u ) u = fnt[*q].up;
if( fnt[*q].down < d ) d = fnt[*q].down;
r += fnt[*q++].right;
} while( *p );
*q = '\0';
/* adjust for last character */
r += fnt[*(q-1)].last_adjust;
/* add kern lengths to r */
for( p = buff, q = p+1; *q; p++, q++ )
{ KernLength(word_font(x), unacc, *p, *q, ksize);
debugcond3(DFT, D, ksize != 0, " KernLength(fnum, %c, %c) = %d",
*p, *q, ksize);
r += ksize;
}
/* set sizes of x */
back(x, COLM) = 0;
fwd(x, COLM) = r;
back(x, ROWM) = u;
fwd(x, ROWM) = -d;
}
else back(x, COLM) = fwd(x, COLM) = back(x, ROWM) = fwd(x, ROWM) = 0;
debug4(DFT, D, "FontWordSize returning %hd %hd %hd %hd",
back(x, COLM), fwd(x, COLM), back(x, ROWM), fwd(x, ROWM));
} /* end FontWordSize */
/*@::FontSize(), FontHalfXHeight(), FontEncoding(), FontName()@***************/
/* */
/* FULL_LENGTH FontSize(fnum, x) */
/* */
/* Return the size of this font. x is for error messages only. */
/* */
/*****************************************************************************/
FULL_LENGTH FontSize(FONT_NUM fnum, OBJECT x)
{ debug1(DFT, D, "FontSize( %d )", fnum);
assert( fnum <= font_count, "FontSize!" );
if( fnum <= 0 )
Error(37, 61, "no current font at this point", FATAL, &fpos(x));
debug1(DFT, D, "FontSize returning %d", font_size(finfo[fnum].font_table));
return font_size(finfo[fnum].font_table);
} /* end FontSize */
/*****************************************************************************/
/* */
/* FULL_LENGTH FontHalfXHeight(fnum) */
/* */
/* Return the xheight2 value of this font. */
/* */
/*****************************************************************************/
FULL_LENGTH FontHalfXHeight(FONT_NUM fnum)
{ debug1(DFT, DD, "FontHalfXHeight( %d )", fnum);
assert( fnum <= font_count, "FontHalfXHeight!" );
debug1(DFT, DD, "FontHalfXHeight returning %d",
font_xheight2(finfo[fnum].font_table));
return font_xheight2(finfo[fnum].font_table);
} /* end FontHalfXHeight */
/*****************************************************************************/
/* */
/* MAPPING FontMapping(fnum, xfpos) */
/* */
/* Return the character mapping of this font, to use for small caps, etc. */
/* xfpos is the file position for error messages. */
/* */
/*****************************************************************************/
MAPPING FontMapping(FONT_NUM fnum, FILE_POS *xfpos)
{ debug1(DFT, DD, "FontMapping( %d )", fnum);
assert( fnum <= font_count, "FontMapping!" );
if( fnum <= 0 )
Error(37, 62, "no current font at this point", FATAL, xfpos);
debug1(DFT, DD, "FontMapping returning %d",
font_mapping(finfo[fnum].font_table));
return font_mapping(finfo[fnum].font_table);
} /* end FontMapping */
/*****************************************************************************/
/* */
/* FULL_CHAR *FontName(fnum) */
/* */
/* Return the short PostScript name of this font. */
/* */
/*****************************************************************************/
FULL_CHAR *FontName(FONT_NUM fnum)
{ debug1(DFT, D, "FontName( %d )", fnum);
assert( fnum <= font_count, "FontName!" );
debug1(DFT, D, "FontName returning %s", string(finfo[fnum].font_table));
return string(finfo[fnum].font_table);
} /* end FontName */
/*@::FontFamily(), FontFace@**************************************************/
/* */
/* FULL_CHAR *FontFamilyAndFace(fnum) */
/* */
/* Return a static string of the current font family and face. */
/* */
/*****************************************************************************/
FULL_CHAR *FontFamily(FONT_NUM fnum)
{ OBJECT face, family;
debug1(DFT, D, "FontFamily( %d )", fnum);
assert( fnum <= font_count, "FontFamiliy!" );
Parent(face, Up(finfo[fnum].font_table));
Parent(family, Up(face));
debug1(DFT, D, "FontFamily returning %s", string(family));
return string(family);
} /* end FontFamilyAndFace */
FULL_CHAR *FontFace(FONT_NUM fnum)
{ OBJECT face, family;
debug1(DFT, D, "FontFacec( %d )", fnum);
assert( fnum <= font_count, "FontFamiliy!" );
Parent(face, Up(finfo[fnum].font_table));
Parent(family, Up(face));
debug1(DFT, D, "FontFace returning %s", string(face));
return string(face);
} /* end FontFamilyAndFace */
/*@::FontFamilyAndFace(), FontPrintAll()@*************************************/
/* */
/* FULL_CHAR *FontFamilyAndFace(fnum) */
/* */
/* Return a static string of the current font family and face. */
/* */
/*****************************************************************************/
FULL_CHAR *FontFamilyAndFace(FONT_NUM fnum)
{ OBJECT face, family; static FULL_CHAR buff[80];
debug1(DFT, D, "FontFamilyAndFace( %d )", fnum);
assert( fnum <= font_count, "FontName!" );
Parent(face, Up(finfo[fnum].font_table));
Parent(family, Up(face));
if( StringLength(string(family)) + StringLength(string(face)) + 1 > 80 )
Error(37, 63, "family and face names %s %s are too long",
FATAL, no_fpos, string(family), string(face));
StringCopy(buff, string(family));
StringCat(buff, STR_SPACE);
StringCat(buff, string(face));
debug1(DFT, D, "FontName returning %s", buff);
return buff;
} /* end FontFamilyAndFace */
/*****************************************************************************/
/* */
/* FontPrintAll(fp) */
/* */
/* Print all font encoding commands on output file fp */
/* */
/*****************************************************************************/
void FontPrintAll(FILE *fp)
{ OBJECT family, face, first_size, ps_name, link, flink;
assert(font_root!=nilobj && type(font_root)==ACAT, "FontDebug: font_root!");
debug0(DFT, DD, "FontPrintAll(fp)");
for( link = Down(font_root); link != font_root; link = NextDown(link) )
{ Child(family, link);
assert( is_word(type(family)), "FontPrintAll: family!" );
for( flink = Down(family); flink != family; flink = NextDown(flink) )
{ Child(face, flink);
assert( is_word(type(face)), "FontPrintAll: face!" );
assert( Down(face) != face && NextDown(Down(face)) != face &&
NextDown(NextDown(Down(face))) != face, "FontDebug: Down(face)!");
Child(ps_name, Down(face));
assert( is_word(type(ps_name)), "FontPrintAll: ps_name!" );
Child(first_size, NextDown(NextDown(Down(face))));
assert( is_word(type(first_size)), "FontPrintAll: first_size!" );
if( font_recoded(face) )
{ fprintf(fp, "/%s%s %s /%s LoutRecode\n",
string(ps_name), string(first_size),
MapEncodingName(font_mapping(face)), string(ps_name));
fprintf(fp, "/%s { /%s%s LoutFont } def\n", string(first_size),
string(ps_name), string(first_size));
}
else fprintf(fp, "/%s { /%s LoutFont } def\n", string(first_size),
string(ps_name));
}
}
fputs("\n", fp);
debug0(DFT, DD, "FontPrintAll returning.");
} /* end FontPrintAll */
/*@@**************************************************************************/
/* */
/* FontPrintPageSetup(fp) */
/* */
/* Print all font encoding commands needed for the current page onto fp. */
/* */
/*****************************************************************************/
void FontPrintPageSetup(FILE *fp)
{ OBJECT face, first_size, ps_name, link;
assert(font_root!=nilobj && type(font_root)==ACAT, "FontDebug: font_root!");
assert(font_used!=nilobj && type(font_used)==ACAT, "FontDebug: font_used!");
debug0(DFT, DD, "FontPrintPageSetup(fp)");
for( link = Down(font_used); link != font_used; link = NextDown(link) )
{
Child(face, link);
assert( is_word(type(face)), "FontPrintPageSetup: face!" );
assert( Down(face) != face, "FontDebug: Down(face)!");
/* print font encoding command */
Child(first_size, NextDown(NextDown(Down(face))));
assert( is_word(type(first_size)), "FontPrintPageSetup: first_size!" );
Child(ps_name, Down(face));
assert( is_word(type(ps_name)), "FontPrintPageSetup: ps_name!" );
BackEnd->PrintPageSetupForFont(face, font_curr_page,
string(ps_name), string(first_size));
}
debug0(DFT, DD, "FontPrintPageSetup returning.");
} /* end FontPrintPageSetup */
/*@@**************************************************************************/
/* */
/* FontPrintPageResources(fp) */
/* */
/* Print all page resources (i.e. fonts needed or supplied) onto fp. */
/* */
/*****************************************************************************/
void FontPrintPageResources(FILE *fp)
{ OBJECT face, ps_name, link, pface, pname, plink;
BOOLEAN first;
assert(font_root!=nilobj && type(font_root)==ACAT, "FontDebug: font_root!");
assert(font_used!=nilobj && type(font_used)==ACAT, "FontDebug: font_used!");
debug0(DFT, DD, "FontPrintPageResources(fp)");
first = TRUE;
for( link = Down(font_used); link != font_used; link = NextDown(link) )
{
Child(face, link);
assert( is_word(type(face)), "FontPrintPageResources: face!" );
assert( Down(face) != face, "FontDebug: Down(face)!");
Child(ps_name, Down(face));
assert( is_word(type(ps_name)), "FontPrintPageResources: ps_name!" );
/* make sure this ps_name has not been printed before (ugly, I know). */
/* Repeats arise when the font appears twice in the database under */
/* different family-face names, perhaps because of sysnonyms like */
/* Italic and Slope, or perhaps because of different encoding vectors */
for( plink = Down(font_used); plink != link; plink = NextDown(plink) )
{
Child(pface, plink);
Child(pname, Down(pface));
if( StringEqual(string(pname), string(ps_name)) )
break;
}
if( plink == link )
{
/* not seen before, so print it */
BackEnd->PrintPageResourceForFont(string(ps_name), first);
first = FALSE;
}
}
debug0(DFT, DD, "FontPrintPageResources returning.");
} /* end FontPrintPageResources */
/*@@**************************************************************************/
/* */
/* FontAdvanceCurrentPage() */
/* */
/* Advance the current page. */
/* */
/*****************************************************************************/
void FontAdvanceCurrentPage(void)
{ debug0(DFT, DD, "FontAdvanceCurrentPage()");
while( Down(font_used) != font_used ) DeleteLink(Down(font_used));
font_curr_page++;
debug0(DFT, DD, "FontAdvanceCurrentPage() returning.");
} /* end FontAdvanceCurrentPage */
/*@::FontPageUsed()@**********************************************************/
/* */
/* OBJECT FontPageUsed(face) */
/* */
/* Declares that font face is used on the current page. */
/* */
/*****************************************************************************/
void FontPageUsed(OBJECT face)
{ debug1(DFT, DD, "FontPageUsed(%d)", font_num(face));
assert( font_page(face) < font_curr_page, "FontPageUsed!" );
Link(font_used, face);
font_page(face) = font_curr_page;
debug0(DFT, DD, "FontPageUsed returning");
} /* end FontPageUsed */
/*@::FontNeeded()@************************************************************/
/* */
/* OBJECT FontNeeded(fp) */
/* */
/* Writes font needed resources onto file out_fp. Returns TRUE if none. */
/* Now that we are using a database, every font that is actually loaded */
/* is really needed. */
/* */
/*****************************************************************************/
BOOLEAN FontNeeded(FILE *fp)
{ BOOLEAN first_need = TRUE;
OBJECT link, flink, family, face, ps_name;
for( link = Down(font_root); link != font_root; link = NextDown(link) )
{ Child(family, link);
for( flink = Down(family); flink != family; flink = NextDown(flink) )
{ Child(face, flink);
Child(ps_name, Down(face));
assert( is_word(type(ps_name)), "FontPrintPageResources: ps_name!" );
fprintf(fp, "%s font %s\n",
first_need ? "%%DocumentNeededResources:" : "%%+", string(ps_name));
first_need = FALSE;
}
}
return first_need;
} /* end FontNeeded */