1 /*	$Id$ */
2 /*
3  * Copyright (c) 1993-1996 Sam Leffler
4  * Copyright (c) 1993-1996 Silicon Graphics, Inc.
5  * HylaFAX is a trademark of Silicon Graphics
6  *
7  * Permission to use, copy, modify, distribute, and sell this software and
8  * its documentation for any purpose is hereby granted without fee, provided
9  * that (i) the above copyright notices and this permission notice appear in
10  * all copies of the software and related documentation, and (ii) the names of
11  * Sam Leffler and Silicon Graphics may not be used in any advertising or
12  * publicity relating to the software without the specific, prior written
13  * permission of Sam Leffler and Silicon Graphics.
14  *
15  * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
16  * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
17  * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
18  *
19  * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
20  * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
21  * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
22  * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
23  * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
24  * OF THIS SOFTWARE.
25  */
26 
27 /*
28  * Text to PostScript conversion and formatting support.
29  *
30  * This class takes ASCII text and produces PostScript
31  * doing formatting using the font metric information
32  * for a single font.  Multi-column output, landscape and
33  * portrait page orientation, and various other controls
34  * are supported.  Page headers a la the old enscript
35  * program from Adobe can also be added.  This code is
36  * distantly related to the lptops program by Nelson Beebe.
37  */
38 #include "config.h"
39 #include "Array.h"
40 #include "Dictionary.h"
41 #include "PageSize.h"
42 #include "TextFormat.h"
43 #include "Sys.h"
44 
45 #include <ctype.h>
46 #include <errno.h>
47 #if HAS_MMAP
48 #include <sys/mman.h>
49 #endif
50 
51 #include "NLS.h"
52 
53 #define LUNIT 	(72*20)		// local coord system is .05 scale
54 #define	ICVT(x) ((TextCoord)((x)*LUNIT))	// scale inches to local coordinates
55 #define	CVTI(x)	(float(x)/LUNIT)	// convert coords to inches
56 
fxmax(TextCoord a,TextCoord b)57 inline TextCoord fxmax(TextCoord a, TextCoord b)
58     { return (a > b) ? a : b; }
59 
60 #define COLFRAC		35	// 1/fraction of col width for margin
61 
fxDECLARE_PrimArray(OfftArray,off_t)62 fxDECLARE_PrimArray(OfftArray, off_t)
63 fxIMPLEMENT_PrimArray(OfftArray, off_t)
64 fxDECLARE_StrKeyDictionary(FontDict, TextFont*)
65 fxIMPLEMENT_StrKeyPtrValueDictionary(FontDict, TextFont*)
66 
67 TextFormat::TextFormat()
68 {
69     output = NULL;
70     tf = NULL;
71     pageOff = new OfftArray;
72 
73     firstPageNum = 1;		// starting page number
74     column = 1;			// current text column # (1..numcol)
75     pageNum = 1;		// current page number
76     workStarted = false;
77 
78     fonts = new FontDict;
79     curFont = addFont("Roman", "Courier");
80 
81     TextFormat::setupConfig();	// NB: virtual
82 }
83 
~TextFormat()84 TextFormat::~TextFormat()
85 {
86     for (FontDictIter iter(*fonts); iter.notDone(); iter++)
87 	delete iter.value();
88     delete fonts;
89     if (tf != NULL)
90 	fclose(tf);
91     tf = NULL;
92 }
93 
94 void
warning(const char * fmt...) const95 TextFormat::warning(const char* fmt ...) const
96 {
97     fputs(NLS::TEXT("Warning, "), stderr);
98     va_list ap;
99     va_start(ap, fmt);
100     vfprintf(stderr, fmt, ap);
101     va_end(ap);
102     fputs(".\n", stderr);
103 }
104 
105 void
error(const char * fmt...) const106 TextFormat::error(const char* fmt ...) const
107 {
108     va_list ap;
109     va_start(ap, fmt);
110     vfprintf(stderr, fmt, ap);
111     va_end(ap);
112     fputs(".\n", stderr);
113 }
114 
115 void
fatal(const char * fmt...) const116 TextFormat::fatal(const char* fmt ...) const
117 {
118     va_list ap;
119     va_start(ap, fmt);
120     vfprintf(stderr, fmt, ap);
121     va_end(ap);
122     fputs(".\n", stderr);
123     exit(1);
124 }
125 
126 TextFont*
addFont(const char * name,const char * family)127 TextFormat::addFont(const char* name, const char* family)
128 {
129     TextFont* f = new TextFont(family);
130     (*fonts)[name] = f;
131     if (workStarted) {
132 	fxStr emsg;
133 	if (!f->readMetrics(pointSize, useISO8859, emsg))
134 	    error(NLS::TEXT("Font %s: %s"), f->getFamily(), (const char*) emsg);
135     }
136     return (f);
137 }
138 
139 const TextFont*
getFont(const char * name) const140 TextFormat::getFont(const char* name) const
141 {
142     return (*fonts)[name];
143 }
144 
setFont(TextFont * f)145 void TextFormat::setFont(TextFont* f)		{ curFont = f; }
setFont(const char * name)146 void TextFormat::setFont(const char* name)		{ curFont = (*fonts)[name]; }
setFontPath(const char * path)147 void TextFormat::setFontPath(const char* path)	{ TextFont::fontPath = path; }
148 
setOutputFile(FILE * f)149 void TextFormat::setOutputFile(FILE* f)		{ output = f; }
setNumberOfColumns(u_int n)150 void TextFormat::setNumberOfColumns(u_int n)	{ numcol = n; }
setPageHeaders(bool b)151 void TextFormat::setPageHeaders(bool b)		{ headers = b; }
setISO8859(bool b)152 void TextFormat::setISO8859(bool b)		{ useISO8859 = b; }
setLineWrapping(bool b)153 void TextFormat::setLineWrapping(bool b)		{ wrapLines = b; }
setOutlineMargin(TextCoord o)154 void TextFormat::setOutlineMargin(TextCoord o)	{ outline = o; }
setTextPointSize(TextCoord p)155 void TextFormat::setTextPointSize(TextCoord p)	{ pointSize = p; }
setPageOrientation(u_int o)156 void TextFormat::setPageOrientation(u_int o)	{ landscape = (o == LANDSCAPE); }
setPageCollation(u_int c)157 void TextFormat::setPageCollation(u_int c)		{ reverse = (c == REVERSE); }
setTextLineHeight(TextCoord h)158 void TextFormat::setTextLineHeight(TextCoord h)	{ lineHeight = h; }
setTitle(const char * cp)159 void TextFormat::setTitle(const char* cp)		{ title = cp; }
setFilename(const char * cp)160 void TextFormat::setFilename(const char* cp)	{ curFile = cp; }
161 
162 void
setGaudyHeaders(bool b)163 TextFormat::setGaudyHeaders(bool b)
164 {
165     if (gaudy = b)
166 	headers = true;
167 }
168 
169 bool
setTextFont(const char * name)170 TextFormat::setTextFont(const char* name)
171 {
172     if (TextFont::findFont(name)) {
173 	(*fonts)["Roman"]->family = name;
174 	return (true);
175     } else
176 	return (false);
177 }
178 
179 
180 /*
181  * Parse margin syntax: l=#,r=#,t=#,b=#
182  */
183 bool
setPageMargins(const char * s)184 TextFormat::setPageMargins(const char* s)
185 {
186     for (const char* cp = s; cp && cp[0];) {
187 	if (cp[1] != '=')
188 	    return (false);
189 	TextCoord v = inch(&cp[2]);
190 	switch (tolower(cp[0])) {
191 	case 'b': bm = v; break;
192 	case 'l': lm = v; break;
193 	case 'r': rm = v; break;
194 	case 't': tm = v; break;
195 	default:
196 	    return (false);
197 	}
198 	cp = strchr(cp, ',');
199 	if (cp)
200 	    cp++;
201     }
202     return (true);
203 }
204 
205 void
setPageMargins(TextCoord l,TextCoord r,TextCoord b,TextCoord t)206 TextFormat::setPageMargins(TextCoord l, TextCoord r, TextCoord b, TextCoord t)
207 {
208     lm = l;
209     rm = r;
210     bm = b;
211     tm = t;
212 }
213 
214 bool
setPageSize(const char * name)215 TextFormat::setPageSize(const char* name)
216 {
217     PageSizeInfo* info = PageSizeInfo::getPageSizeByName(name);
218     if (info) {
219 	setPageWidth(info->width() / 25.4);
220 	setPageHeight(info->height() / 25.4);
221 	delete info;
222 	return (true);
223     } else
224 	return (false);
225 }
226 
setPageWidth(float pw)227 void TextFormat::setPageWidth(float pw)		{ physPageWidth = pw; }
setPageHeight(float ph)228 void TextFormat::setPageHeight(float ph)		{ physPageHeight = ph; }
229 
230 void
setModTimeAndDate(time_t t)231 TextFormat::setModTimeAndDate(time_t t)
232 {
233     struct tm* tm = localtime(&t);
234     char buf[30];
235     strftime(buf, sizeof (buf), "%X", tm); modTime = buf;
236     strftime(buf, sizeof (buf), "%D", tm); modDate = buf;
237 }
setModTime(const char * cp)238 void TextFormat::setModTime(const char* cp)	{ modTime = cp; }
setModDate(const char * cp)239 void TextFormat::setModDate(const char* cp)	{ modDate = cp; }
240 
241 void
beginFormatting(FILE * o)242 TextFormat::beginFormatting(FILE* o)
243 {
244     output = o;
245     pageHeight = ICVT(physPageHeight);
246     pageWidth = ICVT(physPageWidth);
247 
248     /*
249      * Open the file w+ so that we can reread the temp file.
250      */
251     tf = Sys::tmpfile();
252     if (tf == NULL)
253 	fatal(NLS::TEXT("Cannot open temporary file: %s"), strerror(errno));
254 
255     numcol = fxmax(1,numcol);
256     if (pointSize == -1)
257 	pointSize = inch(numcol > 1 ? "7bp" : "10bp");
258     else
259 	pointSize = fxmax(inch("3bp"), pointSize);
260     if (pointSize > inch("18bp"))
261 	warning(NLS::TEXT("point size is unusually large (>18pt)"));
262     // read font metrics
263     for (FontDictIter iter(*fonts); iter.notDone(); iter++) {
264 	fxStr emsg;
265 	TextFont* f = iter.value();
266 	if (!f->readMetrics(pointSize, useISO8859, emsg))
267 	    error(NLS::TEXT("Font %s: %s"), f->getFamily(), (const char*) emsg);
268     }
269     outline = fxmax(0L,outline);
270     curFont = (*fonts)["Roman"];
271     tabWidth = tabStop * curFont->charwidth(' ');
272 
273     if (landscape) {
274 	TextCoord t = pageWidth;
275 	pageWidth = pageHeight;
276 	pageHeight = t;
277     }
278     if (lm+rm >= pageWidth)
279 	fatal(NLS::TEXT("Margin values too large for page; lm %lu rm %lu page width %lu"),
280 	    lm, rm, pageWidth);
281     if (tm+bm >= pageHeight)
282 	fatal(NLS::TEXT("Margin values too large for page; tm %lu bm %lu page height %lu"),
283 	    tm, bm, pageHeight);
284 
285     col_width = (pageWidth - (lm + rm))/numcol;
286     if (numcol > 1 || outline)
287 	col_margin = col_width/COLFRAC;
288     else
289 	col_margin = 0;
290     /*
291      * TeX's baseline skip is 12pt for
292      * 10pt type, we preserve that ratio.
293      */
294     if (lineHeight <= 0)
295 	lineHeight = (pointSize * 12L) / 10L;
296     workStarted = true;
297 }
298 
299 void
endFormatting(void)300 TextFormat::endFormatting(void)
301 {
302     emitPrologue();
303     /*
304      * Now rewind temporary file and write
305      * pages to stdout in appropriate order.
306      */
307     if (reverse) {
308 	rewind(tf);
309 	off_t last = (*pageOff)[pageOff->length()-1];
310 	for (int k = pageNum-firstPageNum; k >= 0; --k) {
311 	    /* copy remainder in reverse order */
312 	    off_t next = (off_t) ftell(stdout);
313 	    Copy_Block((*pageOff)[k],last-1);
314 	    last = (*pageOff)[k];
315 	    (*pageOff)[k] = next;
316 	}
317     } else {
318 	off_t last = ftell(tf);
319 	rewind(tf);
320 	Copy_Block(0L, last-1);
321     }
322     if (fclose(tf))
323 	fatal(NLS::TEXT("Close failure on temporary file: %s"), strerror(errno));
324     tf = NULL;
325     emitTrailer();
326     fflush(output);
327     workStarted = false;
328 }
329 
330 /* copy bytes b1..b2 to stdout */
331 void
Copy_Block(off_t b1,off_t b2)332 TextFormat::Copy_Block(off_t b1, off_t b2)
333 {
334     char buf[16*1024];
335     for (off_t k = b1; k <= b2; k += sizeof (buf)) {
336 	size_t cc = (size_t) fxmin(sizeof (buf), (size_t) (b2-k+1));
337 	fseek(tf, (long) k, SEEK_SET);		// position to desired block
338 	if (fread(buf, 1, (size_t) cc, tf) != cc)
339 	    fatal(NLS::TEXT("Read error during reverse collation: %s"), strerror(errno));
340 	if (fwrite(buf, 1, (size_t) cc, output) != cc)
341 	    fatal(NLS::TEXT("Output write error: %s"), strerror(errno));
342     }
343 }
344 
345 static const char* ISOprologue2 = "\
346 /reencodeISO{\n\
347   dup length dict begin\n\
348     {1 index /FID ne {def}{pop pop} ifelse} forall\n\
349     /Encoding ISOLatin1Encoding def\n\
350     currentdict\n\
351   end\n\
352 }def\n\
353 /findISO{\n\
354   dup /FontType known{\n\
355     dup /FontType get 3 ne\n\
356     1 index /CharStrings known{\n\
357       1 index /CharStrings get /Thorn known\n\
358     }{false}ifelse\n\
359     and\n\
360   }{false}ifelse\n\
361 }def\n\
362 ";
363 
364 /*
365  * Yech, instead of a single string that we fputs, we
366  * break things up into smaller chunks to satisfy braindead
367  * compilers...
368  */
369 void
putISOPrologue(void)370 TextFormat::putISOPrologue(void)
371 {
372     fputs("/ISOLatin1Encoding where{pop save true}{false}ifelse\n", output);
373     fputs("/ISOLatin1Encoding[\n", output);
374     fputs(" /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n", output);
375     fputs(" /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n", output);
376     fputs(" /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n", output);
377     fputs(" /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n", output);
378     fputs(" /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n", output);
379     fputs(" /.notdef /.notdef /space /exclam /quotedbl /numbersign\n", output);
380     fputs(" /dollar /percent /ampersand /quoteright /parenleft\n", output);
381     fputs(" /parenright /asterisk /plus /comma /minus /period\n", output);
382     fputs(" /slash /zero /one /two /three /four /five /six /seven\n", output);
383     fputs(" /eight /nine /colon /semicolon /less /equal /greater\n", output);
384     fputs(" /question /at /A /B /C /D /E /F /G /H /I /J /K /L /M\n", output);
385     fputs(" /N /O /P /Q /R /S /T /U /V /W /X /Y /Z /bracketleft\n", output);
386     fputs(" /backslash /bracketright /asciicircum /underscore\n", output);
387     fputs(" /quoteleft /a /b /c /d /e /f /g /h /i /j /k /l /m\n", output);
388     fputs(" /n /o /p /q /r /s /t /u /v /w /x /y /z /braceleft\n", output);
389     fputs(" /bar /braceright /asciitilde /guilsinglright /fraction\n", output);
390     fputs(" /florin /quotesingle /quotedblleft /guilsinglleft /fi\n", output);
391     fputs(" /fl /endash /dagger /daggerdbl /bullet /quotesinglbase\n", output);
392     fputs(" /quotedblbase /quotedblright /ellipsis /trademark\n", output);
393     fputs(" /perthousand /grave /scaron /circumflex /Scaron /tilde\n", output);
394     fputs(" /breve /zcaron /dotaccent /dotlessi /Zcaron /ring\n", output);
395     fputs(" /hungarumlaut /ogonek /caron /emdash /space /exclamdown\n", output);
396     fputs(" /cent /sterling /currency /yen /brokenbar /section\n", output);
397     fputs(" /dieresis /copyright /ordfeminine /guillemotleft\n", output);
398     fputs(" /logicalnot /hyphen /registered /macron /degree\n", output);
399     fputs(" /plusminus /twosuperior /threesuperior /acute /mu\n", output);
400     fputs(" /paragraph /periodcentered /cedilla /onesuperior\n", output);
401     fputs(" /ordmasculine /guillemotright /onequarter /onehalf\n", output);
402     fputs(" /threequarters /questiondown /Agrave /Aacute\n", output);
403     fputs(" /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla\n", output);
404     fputs(" /Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute\n", output);
405     fputs(" /Icircumflex /Idieresis /Eth /Ntilde /Ograve /Oacute\n", output);
406     fputs(" /Ocircumflex /Otilde /Odieresis /multiply /Oslash\n", output);
407     fputs(" /Ugrave /Uacute /Ucircumflex /Udieresis /Yacute /Thorn\n", output);
408     fputs(" /germandbls /agrave /aacute /acircumflex /atilde\n", output);
409     fputs(" /adieresis /aring /ae /ccedilla /egrave /eacute\n", output);
410     fputs(" /ecircumflex /edieresis /igrave /iacute /icircumflex\n", output);
411     fputs(" /idieresis /eth /ntilde /ograve /oacute /ocircumflex\n", output);
412     fputs(" /otilde /odieresis /divide /oslash /ugrave /uacute\n", output);
413     fputs(" /ucircumflex /udieresis /yacute /thorn /ydieresis\n", output);
414     fputs("]def{restore}if\n", output);
415     fputs(ISOprologue2, output);
416 }
417 
418 static const char* defPrologue = "\
419 /Cols %u def\n\
420 /PageWidth %.2f def\n\
421 /PageHeight %.2f def\n\
422 /LH %u def\n\
423 /B{gsave}def\n\
424 /LN{show}def\n\
425 /EL{grestore 0 -%d rmoveto}def\n\
426 /M{0 rmoveto}def\n\
427 /O{gsave show grestore}def\n\
428 /LandScape{90 rotate 0 -%ld translate}def\n\
429 /U{%d mul}def\n\
430 /UP{U 72 div}def\n\
431 /S{show grestore 0 -%d rmoveto}def\n\
432 ";
433 
434 static const char* headerPrologue1 = "\
435 /InitGaudyHeaders{\n\
436   /HeaderY exch def /BarLength exch def\n\
437   /ftD /Times-Bold findfont 12 UP scalefont def\n\
438   /ftF /Times-Roman findfont 14 UP scalefont def\n\
439   /ftP /Helvetica-Bold findfont 30 UP scalefont def\n\
440   /fillbox{ % w h x y => -\n\
441     moveto 1 index 0 rlineto 0 exch rlineto neg 0 rlineto\n\
442     closepath fill\n\
443   }def\n\
444   /LB{ % x y w h (label) font labelColor boxColor labelPtSize => -\n\
445     gsave\n\
446     /pts exch UP def /charcolor exch def /boxcolor exch def\n\
447     /font exch def /label exch def\n\
448     /h exch def /w exch def\n\
449     /y exch def /x exch def\n\
450     boxcolor setgray w h x y fillbox\n\
451     /lines label length def\n\
452     /ly y h add h lines pts mul sub 2 div sub pts .85 mul sub def\n\
453     font setfont charcolor setgray\n\
454     label {\n\
455       dup stringwidth pop\n\
456       2 div x w 2 div add exch sub ly moveto\n\
457       show\n\
458       /ly ly pts sub def\n\
459     } forall\n\
460     grestore\n\
461   }def\n\
462   /Header{ % (file) [(date)] (page) => -\n\
463     /Page exch def /Date exch def /File exch def\n\
464     .25 U HeaderY U BarLength .1 sub U .25 U [File] ftF .97 0 14 LB\n\
465     .25 U HeaderY .25 add U BarLength .1 sub U .25 U [()] ftF 1 0 14 LB\n\
466     .25 U HeaderY U 1 U .5 U Date ftD .7 0 12 LB\n\
467     BarLength .75 sub U HeaderY U 1 U .5 U [Page] ftP .7 1 30 LB\n\
468     1 1 Cols 1 sub{\n\
469       BarLength Cols div mul .19 add U HeaderY U moveto 0 -10 U rlineto stroke\n\
470     }for\n\
471   }def\n\
472 }def\n\
473 ";
474 static const char* headerPrologue2 = "\
475 /InitNormalHeaders{\n\
476   /HeaderY exch def /BarLength exch def\n\
477   /ftF /Times-Roman findfont 14 UP scalefont def\n\
478   /ftP /Helvetica-Bold findfont 14 UP scalefont def\n\
479   /LB{ % x y w h (label) font labelColor labelPtSize => -\n\
480     gsave\n\
481     /pts exch UP def /charcolor exch def\n\
482     /font exch def /label exch def\n\
483     /h exch def /w exch def\n\
484     /y exch def /x exch def\n\
485     /ly y h add h pts sub 2 div sub pts .85 mul sub def\n\
486     font setfont charcolor setgray\n\
487     label stringwidth pop 2 div x w 2 div add exch sub ly moveto\n\
488     label show\n\
489     grestore\n\
490   }def\n\
491   /Header{ % (file) [(date)] (page) => -\n\
492     /Page exch def pop /File exch def\n\
493     .25 U HeaderY U BarLength 2 div U .5 U File ftF 0 14 LB\n\
494     BarLength .75 sub U HeaderY U 1 U .5 U Page ftP 0 14 LB\n\
495     1 1 Cols 1 sub{\n\
496       BarLength Cols div mul .19 add U HeaderY U moveto 0 -10 U rlineto stroke\n\
497     }for\n\
498   }def\n\
499 }def\n\
500 /InitNullHeaders{/Header{3{pop}repeat}def pop pop}def\n\
501 ";
502 
503 /*
504  * Emit the DSC header comments and prologue.
505  */
506 void
emitPrologue(void)507 TextFormat::emitPrologue(void)
508 {
509     fputs("%!PS-Adobe-3.0\n", output);
510     fprintf(output, "%%%%Creator: HylaFAX TextFormat Class\n");
511     fprintf(output, "%%%%Title: %s\n", (const char*) title);
512     time_t t = Sys::now();
513     fprintf(output, "%%%%CreationDate: %s", ctime(&t));
514     char* cp = getlogin();
515     fprintf(output, "%%%%For: %s\n", cp ? cp : "");
516     fputs("%%Origin: 0 0\n", output);
517     fprintf(output, "%%%%BoundingBox: 0 0 %.0f %.0f\n",
518 	physPageWidth*72, physPageHeight*72);
519     fputs("%%Pages: (atend)\n", output);
520     fprintf(output, "%%%%PageOrder: %s\n",
521 	reverse ? "Descend" : "Ascend");
522     fprintf(output, "%%%%Orientation: %s\n",
523 	landscape ? "Landscape" : "Portrait");
524     fprintf(output, "%%%%DocumentNeededResources: font");
525     FontDictIter iter;
526     for (iter = *fonts; iter.notDone(); iter++) {
527 	TextFont* f = iter.value();
528 	fprintf(output, " %s", f->getFamily());
529     }
530     fputc('\n', output);
531     if (gaudy) {
532 	fputs("%%+ font Times-Bold\n", output);
533 	fputs("%%+ font Times-Roman\n", output);
534 	fputs("%%+ font Helvetica-Bold\n", output);
535     }
536     emitClientComments(output);
537     fprintf(output, "%%%%EndComments\n");
538 
539     fprintf(output, "%%%%BeginProlog\n");
540     fputs("/$printdict 50 dict def $printdict begin\n", output);
541     if (useISO8859)
542 	putISOPrologue();
543     fprintf(output, defPrologue
544 	, numcol
545 	, CVTI(pageWidth - (lm+rm))
546 	, CVTI(pageHeight - (tm+bm))
547 	, lineHeight
548 	, lineHeight
549 	, pageHeight
550 	, LUNIT
551 	, lineHeight
552     );
553     fputs(headerPrologue1, output);
554     fputs(headerPrologue2, output);
555     fprintf(output, "%.2f %.2f Init%sHeaders\n"
556 	, CVTI(pageWidth - (lm+rm))
557 	, CVTI(pageHeight - tm)
558 	, (gaudy ? "Gaudy" : headers ? "Normal" : "Null")
559     );
560     for (iter = *fonts; iter.notDone(); iter++)
561 	iter.value()->defFont(output, pointSize, useISO8859);
562     emitClientPrologue(output);
563     fputs("end\n", output);
564     fputs("%%EndProlog\n", output);
565 }
emitClientComments(FILE *)566 void TextFormat::emitClientComments(FILE*) {}
emitClientPrologue(FILE *)567 void TextFormat::emitClientPrologue(FILE*) {}
568 
569 /*
570  * Emit the DSC trailer comments.
571  */
572 void
emitTrailer(void)573 TextFormat::emitTrailer(void)
574 {
575     fputs("%%Trailer\n", output);
576     fprintf(output, "%%%%Pages: %d\n", pageNum - firstPageNum);
577     fputs("%%EOF\n", output);
578 }
579 
580 void
newPage(void)581 TextFormat::newPage(void)
582 {
583     x = lm;					// x starts at left margin
584     right_x = col_width - col_margin/2;		// right x, 0-relative
585     y = pageHeight - tm - lineHeight;		// y at page top
586     level = 0;					// string paren level reset
587     column = 1;
588     boc = true;
589     bop = true;
590 }
591 
592 void
newCol(void)593 TextFormat::newCol(void)
594 {
595     x += col_width;				// x, shifted
596     right_x += col_width;			// right x, shifted
597     y = pageHeight - tm - lineHeight;		// y at page top
598     level = 0;
599     column++;
600     boc = true;
601 }
602 
603 static void
putString(FILE * fd,const char * val)604 putString(FILE* fd, const char* val)
605 {
606     fputc('(', fd);
607     for (; *val; val++) {
608 	u_int c = *val & 0xff;
609 	if ((c & 0200) == 0) {
610 	    if (c == '(' || c == ')' || c == '\\')
611 		fputc('\\', fd);
612 	    fputc(c, fd);
613 	} else
614 	    fprintf(fd, "\\%03o", c);
615     }
616     fputc(')', fd);
617 }
618 
619 void
beginCol(void)620 TextFormat::beginCol(void)
621 {
622     if (column == 1) {				// new page
623 	if (reverse)  {
624 	    u_int k = pageNum-firstPageNum;
625 	    off_t off = (off_t) ftell(tf);
626 	    if (k < pageOff->length())
627 		(*pageOff)[k] = off;
628 	    else
629 		pageOff->append(off);
630 	}
631 	fprintf(tf,"%%%%Page: \"%d\" %d\n", pageNum-firstPageNum+1, pageNum);
632 	fputs("save $printdict begin\n", tf);
633 	fprintf(tf, ".05 dup scale\n");
634 	curFont->setfont(tf);
635 	if (landscape)
636 	    fputs("LandScape\n", tf);
637 	putString(tf, curFile);
638 	fputc('[', tf);
639 	putString(tf, modDate);
640 	putString(tf, modTime);
641 	fputc(']', tf);
642 	fprintf(tf, "(%d)Header\n", pageNum);
643     }
644     fprintf(tf, "%ld %ld moveto\n",x,y);
645 }
646 
647 void
beginLine(void)648 TextFormat::beginLine(void)
649 {
650     if (boc)
651 	beginCol(), boc = false, bop = false;
652     fputc('B', tf);
653 }
654 
655 void
beginText(void)656 TextFormat::beginText(void)
657 {
658     fputc('(', tf);
659     level++;
660 }
661 
662 void
hrMove(TextCoord x)663 TextFormat::hrMove(TextCoord x)
664 {
665     fprintf(tf, " %ld M ", x);
666     xoff += x;
667 }
668 
669 void
closeStrings(const char * cmd)670 TextFormat::closeStrings(const char* cmd)
671 {
672     int l = level;
673     for (; level > 0; level--)
674 	fputc(')', tf);
675     if (l > 0)
676 	fputs(cmd, tf);
677 }
678 
679 void
beginFile(void)680 TextFormat::beginFile(void)
681 {
682     newPage();				// each file starts on a new page
683 
684     bol = true;				// force line start
685     bot = true;				// force text start
686     xoff = col_width * (column-1);
687 }
688 
689 void
endFile(void)690 TextFormat::endFile(void)
691 {
692     if (!bot)
693 	endTextLine();
694     if (!bol)
695 	endLine();
696     if (!bop) {
697 	column = numcol;			// force page end action
698 	endTextCol();
699     }
700     if (reverse) {
701 	/*
702 	 * Normally, beginCol sets the pageOff entry.  Since this is
703 	 * the last output for this file, we must set it here manually.
704 	 * If more files are printed, this entry will be overwritten (with
705 	 * the same value) at the next call to beginCol.
706 	 */
707 	off_t off = (off_t) ftell(tf);
708 	pageOff->append(off);
709     }
710 }
711 
712 void
formatFile(const char * name)713 TextFormat::formatFile(const char* name)
714 {
715     FILE* fp = fopen(name, "r");
716     if (fp != NULL) {
717 	curFile = name;
718 	formatFile(fp);
719 	fclose(fp);
720     } else
721 	error(NLS::TEXT("%s: Cannot open file: %s"), name, strerror(errno));
722 }
723 
724 void
formatFile(FILE * fp)725 TextFormat::formatFile(FILE* fp)
726 {
727 #if HAS_MMAP
728     struct stat sb;
729     Sys::fstat(fileno(fp), sb);
730     char* addr = (char*)
731 	mmap(NULL, (size_t) sb.st_size, PROT_READ, MAP_SHARED, fileno(fp), 0);
732     if (addr == (char*) -1) {		// revert to file reads
733 #endif
734 	int c;
735 	while ((c = getc(fp)) == '\f')	// discard initial form feeds
736 	    ;
737 	ungetc(c, fp);
738 	beginFile();
739 	format(fp);
740 	endFile();
741 #if HAS_MMAP
742     } else {
743 	const char* cp = addr;
744 	const char* ep = cp + sb.st_size;
745 	while (cp < ep && *cp == '\f')	// discard initial form feeds
746 	    cp++;
747 	beginFile();
748 	format(cp, ep-cp);
749 	endFile();
750 	munmap(addr, (size_t) sb.st_size);
751     }
752 #endif
753 }
754 
755 void
format(FILE * fp)756 TextFormat::format(FILE* fp)
757 {
758     int c;
759     while ((c = getc(fp)) != EOF) {
760 	switch (c) {
761 	case '\0':			// discard nulls
762 	    break;
763 	case '\f':			// form feed
764 	    if (!bop) {
765 		endTextCol();
766 		bol = bot = true;
767 	    }
768 	    break;
769 	case '\n':			// line break
770 	    if (bol)
771 		beginLine();
772 	    if (bot)
773 		beginText();
774 	    endTextLine();
775 	    break;
776 	case '\r':			// check for overstriking
777 	    if ((c = getc(fp)) == '\n') {
778 		ungetc(c,fp);		// collapse \r\n => \n
779 		break;
780 	    }
781 	    closeStrings("O\n");	// do overstriking
782 	    bot = true;			// start new string
783 	    break;
784 	default:
785 	    TextCoord hm;
786 	    if (c == '\t' || c == ' ') {
787 		/*
788 		 * Coalesce white space into one relative motion.
789 		 * The offset calculation below is to insure that
790 		 * 0 means the start of the line (no matter what
791 		 * column we're in).
792 		 */
793 		hm = 0;
794 		int cc = c;
795 		TextCoord off = xoff - col_width*(column-1);
796 		do {
797 		    if (cc == '\t')
798 			hm += tabWidth - (off+hm) % tabWidth;
799 		    else
800 			hm += curFont->charwidth(' ');
801 		} while ((cc = getc(fp)) == '\t' || cc == ' ');
802 		if (cc != EOF)
803 		    ungetc(cc, fp);
804 		/*
805 		 * If the motion is one space's worth, either
806 		 * a single blank or a tab that causes a blank's
807 		 * worth of horizontal motion, then force it
808 		 * to be treated as a blank below.
809 		 */
810 		if (hm == curFont->charwidth(' '))
811 		    c = ' ';
812 		else
813 		    c = '\t';
814 	    } else
815 		hm = curFont->charwidth(c);
816 	    if (xoff + hm > right_x) {
817 		if (!wrapLines)		// discard line overflow
818 		    break;
819 		if (c == '\t')		// adjust white space motion
820 		    hm -= right_x - xoff;
821 		endTextLine();
822 	    }
823 	    if (bol)
824 		beginLine(), bol = false;
825 	    if (c == '\t') {		// close open PS string and do motion
826 		if (hm > 0) {
827 		    closeStrings("LN");
828 		    bot = true;		// force new string
829 		    hrMove(hm);
830 		}
831 	    } else {			// append to open PS string
832 		if (bot)
833 		    beginText(), bot = false;
834 		if (040 <= c && c <= 0176) {
835 		    if (c == '(' || c == ')' || c == '\\')
836 			fputc('\\',tf);
837 		    fputc(c,tf);
838 		} else
839 		    fprintf(tf, "\\%03o", c & 0xff);
840 		xoff += hm;
841 	    }
842 	    break;
843 	}
844     }
845 }
846 
847 void
format(const char * cp,u_int cc)848 TextFormat::format(const char* cp, u_int cc)
849 {
850     const char* ep = cp+cc;
851     while (cp < ep) {
852 	int c = *cp++ & 0xff;
853 	switch (c) {
854 	case '\0':			// discard nulls
855 	    break;
856 	case '\f':			// form feed
857 	    if (!bop) {
858 		endTextCol();
859 		bol = bot = true;
860 	    }
861 	    break;
862 	case '\n':			// line break
863 	    if (bol)
864 		beginLine();
865 	    if (bot)
866 		beginText();
867 	    endTextLine();
868 	    break;
869 	case '\r':			// check for overstriking
870 	    if (cp < ep && *cp == '\n')
871 		break;			// collapse \r\n => \n
872 	    cp++;			// count character
873 	    closeStrings("O\n");	// do overstriking
874 	    bot = true;			// start new string
875 	    break;
876 	default:
877 	    TextCoord hm;
878 	    if (c == '\t' || c == ' ') {
879 		/*
880 		 * Coalesce white space into one relative motion.
881 		 * The offset calculation below is to insure that
882 		 * 0 means the start of the line (no matter what
883 		 * column we're in).
884 		 */
885 		hm = 0;
886 		int cc = c;
887 		TextCoord off = xoff - col_width*(column-1);
888 		do {
889 		    if (cc == '\t')
890 			hm += tabWidth - (off+hm) % tabWidth;
891 		    else
892 			hm += curFont->charwidth(' ');
893 		} while (cp < ep && ((cc = *cp++) == '\t' || cc == ' '));
894 		if (cc != '\t' && cc != ' ')
895 		    cp--;
896 		/*
897 		 * If the motion is one space's worth, either
898 		 * a single blank or a tab that causes a blank's
899 		 * worth of horizontal motion, then force it
900 		 * to be treated as a blank below.
901 		 */
902 		if (hm == curFont->charwidth(' '))
903 		    c = ' ';
904 		else
905 		    c = '\t';
906 	    } else
907 		hm = curFont->charwidth(c);
908 	    if (xoff + hm > right_x) {
909 		if (!wrapLines)		// discard line overflow
910 		    break;
911 		if (c == '\t')		// adjust white space motion
912 		    hm -= right_x - xoff;
913 		endTextLine();
914 	    }
915 	    if (bol)
916 		beginLine(), bol = false;
917 	    if (c == '\t') {		// close open PS string and do motion
918 		if (hm > 0) {
919 		    closeStrings("LN");
920 		    fprintf(tf, " %ld M ", hm);
921 		    bot = true;		// force new string
922 		}
923 	    } else {			// append to open PS string
924 		if (bot)
925 		    beginText(), bot = false;
926 		if (040 <= c && c <= 0176) {
927 		    if (c == '(' || c == ')' || c == '\\')
928 			fputc('\\',tf);
929 		    fputc(c,tf);
930 		} else
931 		    fprintf(tf, "\\%03o", c & 0xff);
932 	    }
933 	    xoff += hm;
934 	    break;
935 	}
936     }
937 }
938 
939 static const char* outlineCol = "\n\
940 gsave\
941     %ld setlinewidth\
942     newpath %ld %ld moveto\
943     %ld %ld rlineto\
944     %ld %ld rlineto\
945     %ld %ld rlineto\
946     closepath stroke \
947 grestore\n\
948 ";
949 
950 void
endCol(void)951 TextFormat::endCol(void)
952 {
953     if (outline > 0) {
954 	fprintf(tf, outlineCol, outline,
955 	    x - col_margin, bm,		col_width, 0, 0,
956 	    pageHeight-bm-tm,		-col_width, 0);
957     }
958     if (column == numcol) {		// columns filled, start new page
959 	pageNum++;
960 	fputs("showpage\nend restore\n", tf);
961 	flush();
962 	newPage();
963     } else
964 	newCol();
965 }
966 
967 void
endLine(void)968 TextFormat::endLine(void)
969 {
970     fputs("EL\n", tf);
971     if ((y -= lineHeight) < bm)
972 	endCol();
973     xoff = col_width * (column-1);
974 }
975 
976 void
endTextCol(void)977 TextFormat::endTextCol(void)
978 {
979     closeStrings("LN");
980     fputc('\n', tf);
981     endCol();
982 }
983 
984 void
endTextLine(void)985 TextFormat::endTextLine(void)
986 {
987     closeStrings("S\n");
988     if ((y -= lineHeight) < bm)
989 	endCol();
990     xoff = col_width * (column-1);
991     bol = bot = true;
992 }
993 
994 void
reserveVSpace(TextCoord vs)995 TextFormat::reserveVSpace(TextCoord vs)
996 {
997     if (y - vs < bm)
998 	endCol();
999 }
1000 
1001 void
flush(void)1002 TextFormat::flush(void)
1003 {
1004     fflush(tf);
1005     if (ferror(tf) && errno == ENOSPC)
1006 	fatal(NLS::TEXT("Output write error: %s"), strerror(errno));
1007 }
1008 
1009 /*
1010  * Convert a value of the form:
1011  * 	#.##bp		big point (1in = 72bp)
1012  * 	#.##cc		cicero (1cc = 12dd)
1013  * 	#.##cm		centimeter
1014  * 	#.##dd		didot point (1157dd = 1238pt)
1015  * 	#.##in		inch
1016  * 	#.##mm		millimeter (10mm = 1cm)
1017  * 	#.##pc		pica (1pc = 12pt)
1018  * 	#.##pt		point (72.27pt = 1in)
1019  * 	#.##sp		scaled point (65536sp = 1pt)
1020  * to inches, returning it as the function value.  The case of
1021  * the dimension name is ignored.  No space is permitted between
1022  * the number and the dimension.
1023  */
1024 TextCoord
inch(const char * s)1025 TextFormat::inch(const char* s)
1026 {
1027     char* cp;
1028     double v = strtod(s, &cp);
1029     if (cp == NULL)
1030 	return (ICVT(0));			// XXX
1031     if (strncasecmp(cp,"in",2) == 0)		// inches
1032 	;
1033     else if (strncasecmp(cp,"cm",2) == 0)	// centimeters
1034 	v /= 2.54;
1035     else if (strncasecmp(cp,"pt",2) == 0)	// points
1036 	v /= 72.27;
1037     else if (strncasecmp(cp,"cc",2) == 0)	// cicero
1038 	v *= 12.0 * (1238.0 / 1157.0) / 72.27;
1039     else if (strncasecmp(cp,"dd",2) == 0)	// didot points
1040 	v *= (1238.0 / 1157.0) / 72.27;
1041     else if (strncasecmp(cp,"mm",2) == 0)	// millimeters
1042 	v /= 25.4;
1043     else if (strncasecmp(cp,"pc",2) == 0)	// pica
1044 	v *= 12.0 / 72.27;
1045     else if (strncasecmp(cp,"sp",2) == 0)	// scaled points
1046 	v /= (65536.0 * 72.27);
1047     else					// big points
1048 	v /= 72.0;
1049     return ICVT(v);
1050 }
1051 
1052 /*
1053  * Configuration file support.
1054  */
1055 void
setupConfig()1056 TextFormat::setupConfig()
1057 {
1058     gaudy	= false;	// emit gaudy headers
1059     landscape	= false;	// horizontal landscape mode output
1060     useISO8859	= true;		// use the ISO 8859-1 character encoding
1061     reverse	= false;	// page reversal flag
1062     wrapLines	= true;		// wrap/truncate lines
1063     headers	= true;		// emit page headers
1064 
1065     pointSize = -1;		// font point size in big points
1066     lm = inch("0.25in");	// left margin
1067     rm = inch("0.25in");	// right margin
1068     tm = inch("0.85in");	// top margin
1069     bm = inch("0.5in");		// bottom margin
1070     lineHeight = 0;		// inter-line spacing
1071     numcol = 1;			// number of text columns
1072     col_margin = 0L;		// inter-column margin
1073     outline = 0L;		// page and column outline linewidth
1074     tabStop = 8;		// 8-column tab stop
1075     setPageSize("default");	// default system page dimensions
1076 }
1077 
1078 void
resetConfig()1079 TextFormat::resetConfig()
1080 {
1081     setupConfig();
1082 }
1083 
configError(const char * ...)1084 void TextFormat::configError(const char* ...) {}
configTrace(const char * ...)1085 void TextFormat::configTrace(const char* ...) {}
1086 
1087 #undef streq
1088 #define	streq(a,b)	(strcasecmp(a,b)==0)
1089 
1090 bool
setConfigItem(const char * tag,const char * value)1091 TextFormat::setConfigItem(const char* tag, const char* value)
1092 {
1093     if (streq(tag, "columns"))
1094 	setNumberOfColumns(getNumber(value));
1095     else if (streq(tag, "pageheaders"))
1096 	setPageHeaders(getBoolean(value));
1097     else if (streq(tag, "linewrap"))
1098 	setLineWrapping(getBoolean(value));
1099     else if (streq(tag, "iso8859"))
1100 	setISO8859(getBoolean(value));
1101     else if (streq(tag, "textfont"))
1102 	setTextFont(value);
1103     else if (streq(tag, "gaudyheaders"))
1104 	setGaudyHeaders(getBoolean(value));
1105     else if (streq(tag, "pagemargins"))
1106 	setPageMargins(value);
1107     else if (streq(tag, "outlinemargin"))
1108 	setOutlineMargin(inch(value));
1109     else if (streq(tag, "textpointsize"))
1110 	setTextPointSize(inch(value));
1111     else if (streq(tag, "orientation"))
1112 	setPageOrientation(streq(value, "landscape") ? LANDSCAPE : PORTRAIT);
1113     else if (streq(tag, "pagesize"))
1114 	setPageSize(value);
1115     else if (streq(tag, "pagewidth"))
1116 	setPageWidth(atof(value));
1117     else if (streq(tag, "pageheight"))
1118 	setPageHeight(atof(value));
1119     else if (streq(tag, "pagecollation"))
1120 	setPageCollation(streq(value, "forward") ? FORWARD : REVERSE);
1121     else if (streq(tag, "textlineheight"))
1122 	setTextLineHeight(inch(value));
1123     else if (streq(tag, "tabstop"))
1124 	tabStop = getNumber(value);
1125     else if (streq(tag, "fontmap"))		// XXX
1126 	TextFont::fontMap = value;
1127     else if (streq(tag, "fontpath"))
1128 	setFontPath(value);
1129     else
1130 	return (false);
1131     return (true);
1132 }
1133 
1134 #define	NCHARS	(sizeof (widths) / sizeof (widths[0]))
1135 
1136 fxStr TextFont::fontMap = _PATH_FONTMAP;
1137 fxStr TextFont::fontPath = _PATH_AFM;
1138 u_int TextFont::fontID = 0;
1139 bool  TextFont::fontMapsLoaded = false;
1140 fxStrDict TextFont::fontMapDict;
1141 
TextFont(const char * cp)1142 TextFont::TextFont(const char* cp) : family(cp)
1143 {
1144     showproc = fxStr::format("s%u", fontID);
1145     setproc = fxStr::format("sf%u", fontID);
1146     fontID++;
1147 }
~TextFont()1148 TextFont::~TextFont() {}
1149 
1150 void
error(const char * fmt...)1151 TextFont::error(const char* fmt ...)
1152 {
1153     va_list ap;
1154     va_start(ap, fmt);
1155     vfprintf(stderr, fmt, ap);
1156     va_end(ap);
1157     fputs(".\n", stderr);
1158 }
1159 
1160 void
loadFontMap(const char * fontMapFile)1161 TextFont::loadFontMap(const char* fontMapFile)
1162 {
1163     FILE* fd = Sys::fopen(fontMapFile, "r");
1164 
1165     if (fd != NULL && fontMapFile[0] == '/') {
1166 	char buf[1024];
1167 	while (fgets(buf, sizeof(buf), fd) != NULL) {
1168 	    size_t len = strcspn(buf, "%\n");
1169 	    if (len == strlen(buf)) {
1170 		error(NLS::TEXT("Warning: %s - line too long."), (const char*)fontMapFile);
1171 		break;
1172 	    }
1173 	    if (len == 0) continue;
1174 	    if (*buf != '/') continue;
1175 	    *(buf + len) = '\0';
1176 	    char* key = buf + 1;
1177 	    char* tmp = buf + strcspn(buf, ") \t");
1178 	    *tmp++ = '\0';
1179 	    tmp += strspn(tmp, " \t");
1180 	    *(tmp + strcspn(tmp, ") \t;")) = '\0';
1181 	    fxStr val = tmp;
1182 	    if (val[0] == '/') {
1183 		//alias
1184 		fontMapDict[key] = val;
1185 	    } else {
1186 		//real file
1187 		val.remove(0);
1188 		fontMapDict[key] = val;
1189 	    }
1190 	}
1191 	fclose(fd);
1192     }
1193 }
1194 
1195 void
loadFontMaps(void)1196 TextFont::loadFontMaps(void)
1197 {
1198     fxStr path = fontMap;
1199     u_int index = path.next(0, ':');
1200     while (index > 0) {
1201 
1202 	/* Newer versions of Ghostscript use "Fontmap.GS"
1203 	 * whereas older ones omit the ".GS" extension.
1204 	 * If the Fontmap.GS file isn't available default
1205 	 * to the older convention. faxsetup may also generate
1206 	 * a Fontmap.HylaFAX file if the Ghostscript files
1207 	 * are not found or incomplete at setup time.
1208 	 */
1209 	loadFontMap(path.head(index) | "/" | "Fontmap");
1210 	loadFontMap(path.head(index) | "/" | "Fontmap.GS");
1211 	loadFontMap(path.head(index) | "/" | "Fontmap.HylaFAX");
1212 
1213 	path.remove(0, index);
1214 	if (path.length() > 0) path.remove(0, 1);
1215 	index = path.next(0, ':');
1216     }
1217     TextFont::fontMapsLoaded = true;
1218 }
1219 
1220 bool
findAFMFile(const char * name,fxStr & filename,fxStr & emsg,bool noExtension)1221 TextFont::findAFMFile(const char* name, fxStr& filename, fxStr& emsg, bool noExtension)
1222 {
1223     struct stat junk;
1224     fxStr val = name;
1225     int pos = val.next(0, '.');
1226     val.remove(pos, val.length() - pos);
1227     val.append(".afm");
1228     //move through dirs looking for font
1229     fxStr fpath = fontPath;
1230     u_int index = fpath.next(0, ':');
1231     if (index == 0) {
1232 	emsg = NLS::TEXT("Empty FontPath, no possibility to find font file");
1233 	return false;
1234     }
1235     while (index > 0) {
1236 	filename = fpath.head(index) | "/" | val;
1237 	fpath.remove(0, index);
1238 	if (fpath.length() > 0) fpath.remove(0, 1);
1239 	if (stat(filename, &junk) == 0) return true;
1240 	if (noExtension) {
1241 	    filename.resize(filename.length()-4);	// strip ``.afm''
1242 	    if (stat(filename, &junk) == 0) return true;
1243 	}
1244 	index = fpath.next(0, ':');
1245     }
1246     emsg = fxStr::format(NLS::TEXT("Font metrics file not found: %s"), (const char *)val);
1247     return false;
1248 }
1249 
1250 bool
decodeFontName(const char * name,fxStr & filename,fxStr & emsg)1251 TextFont::decodeFontName(const char* name, fxStr& filename, fxStr& emsg)
1252 {
1253     fxStr key = name;
1254     int aliascount = maxaliases;
1255 
1256     if (!TextFont::fontMapsLoaded);
1257 	loadFontMaps();
1258     while (((const char*)fontMapDict[key])[0] == '/' && aliascount-- > 0) {
1259 	// Alias
1260 	key = fontMapDict[key];
1261 	key.remove(0);
1262     }
1263 
1264     if (aliascount >= 0 && fontMapDict.find(key))
1265 	return findAFMFile(fontMapDict[key], filename, emsg);
1266 
1267     //decoding using fontmap has failed
1268     //now try plain filename with/without afm extension
1269     return findAFMFile(name, filename, emsg, true);
1270 }
1271 
1272 bool
findFont(const char * name)1273 TextFont::findFont(const char* name)
1274 {
1275     fxStr myname, emsg;
1276     return decodeFontName(name, myname, emsg);
1277 }
1278 
1279 static const char* defISOFont = "\
1280 /%s{/%s findfont\
1281   findISO{reencodeISO /%s-ISO exch definefont}if\
1282   %d UP scalefont setfont\
1283 }def\n\
1284 ";
1285 static const char* defRegularFont = "\
1286 /%s{/%s findfont %d UP scalefont setfont}def\n\
1287 ";
1288 
1289 void
defFont(FILE * fd,TextCoord ps,bool useISO8859) const1290 TextFont::defFont(FILE* fd, TextCoord ps, bool useISO8859) const
1291 {
1292     if (useISO8859) {
1293 	fprintf(fd, defISOFont, (const char*) setproc,
1294 	    (const char*) family, (const char*) family, ps/20, ps/20);
1295     } else {
1296 	fprintf(fd, defRegularFont, (const char*) setproc,
1297 	    (const char*) family, ps/20);
1298     }
1299     fprintf(fd, "/%s{%s show}def\n",
1300 	(const char*) showproc, (const char*) setproc);
1301 }
1302 
1303 void
setfont(FILE * fd) const1304 TextFont::setfont(FILE* fd)	const
1305 {
1306     fprintf(fd, " %s ", (const char*) setproc);
1307 }
1308 
1309 TextCoord
show(FILE * fd,const char * val,int len) const1310 TextFont::show(FILE* fd, const char* val, int len) const
1311 {
1312     TextCoord hm = 0;
1313     if (len > 0) {
1314 	fprintf(fd, "(");
1315 	do {
1316 	    u_int c = *val++ & 0xff;
1317 	    if ((c & 0200) == 0) {
1318 		if (c == '(' || c == ')' || c == '\\')
1319 		    fputc('\\', fd);
1320 		fputc(c, fd);
1321 	    } else
1322 		fprintf(fd, "\\%03o", c);
1323 	    hm += widths[c];		// Leif Erlingsson <leif@lege.com>
1324 	} while (--len);
1325 	fprintf(fd, ")%s ", (const char*) showproc);
1326     }
1327     return (hm);
1328 }
1329 
1330 TextCoord
show(FILE * fd,const fxStr & s) const1331 TextFont::show(FILE* fd, const fxStr& s) const
1332 {
1333     return show(fd, s, s.length());
1334 }
1335 
1336 TextCoord
strwidth(const char * cp) const1337 TextFont::strwidth(const char* cp) const
1338 {
1339     TextCoord w = 0;
1340     while (*cp)
1341 	w += widths[(unsigned) (*cp++ & 0xff)];	// Leif Erlingsson <leif@lege.com>
1342     return w;
1343 }
1344 
1345 void
loadFixedMetrics(TextCoord w)1346 TextFont::loadFixedMetrics(TextCoord w)
1347 {
1348     for (u_int i = 0; i < NCHARS; i++)
1349 	widths[i] = w;
1350 }
1351 
1352 bool
getAFMLine(FILE * fp,char * buf,int bsize)1353 TextFont::getAFMLine(FILE* fp, char* buf, int bsize)
1354 {
1355     if (fgets(buf, bsize, fp) == NULL)
1356 	return (false);
1357     char* cp = strchr(buf, '\n');
1358     if (cp == NULL) {			// line too long, skip it
1359 	int c;
1360 	while ((c = getc(fp)) != '\n')	// skip to end of line
1361 	    if (c == EOF)
1362 		return (false);
1363 	cp = buf;			// force line to be skipped
1364     }
1365     *cp = '\0';
1366     return (true);
1367 }
1368 
1369 FILE*
openAFMFile(fxStr & fontpath)1370 TextFont::openAFMFile(fxStr& fontpath)
1371 {
1372     fxStr emsg;
1373     if (!decodeFontName(family, fontpath, emsg)) {
1374 	fprintf(stderr,emsg);
1375 	return NULL;
1376     }
1377     return Sys::fopen(fontpath, "r");
1378 }
1379 
1380 bool
readMetrics(TextCoord ps,bool useISO8859,fxStr & emsg)1381 TextFont::readMetrics(TextCoord ps, bool useISO8859, fxStr& emsg)
1382 {
1383     fxStr file;
1384     FILE *fp = openAFMFile(file);
1385     if (fp == NULL) {
1386 	emsg = fxStr::format(
1387 	    NLS::TEXT("%s: Can not open font metrics file; using fixed widths"),
1388 	    (const char*) file);
1389 	loadFixedMetrics(625*ps/1000L);		// NB: use fixed width metrics
1390 	return (false);
1391     }
1392     /*
1393      * Since many ISO-encoded fonts don't include metrics for
1394      * the higher-order characters we cheat here and use a
1395      * default metric for those glyphs that were unspecified.
1396      */
1397     loadFixedMetrics(useISO8859 ? 625*ps/1000L : 0);
1398 
1399     char buf[1024];
1400     u_int lineno = 0;
1401     do {
1402 	if (!getAFMLine(fp, buf, sizeof (buf))) {
1403 	    emsg = fxStr::format(
1404 		NLS::TEXT("%s: No glyph metric table located; using fixed widths"),
1405 		(const char*) file);
1406 	    fclose(fp);
1407 	    /*
1408 	     * Next line added by Leif Erlingsson <leif@lege.com> because
1409 	     * otherwise, if the metrics-file has no glyph metric table
1410 	     * and useISO8859 == False, the FixedMetrics will be all 0's!
1411 	     * (I don't know if this does or does not cause any problem.)
1412 	     */
1413 	    loadFixedMetrics(625*ps/1000L);	// NB: use fixed width metrics
1414 	    return (false);
1415 	}
1416 	lineno++;
1417     } while (strncmp(buf, "StartCharMetrics", 16));
1418     while (getAFMLine(fp, buf, sizeof (buf)) && strcmp(buf, "EndCharMetrics")) {
1419 	lineno++;
1420 	int ix, w;
1421 	/* read the glyph position and width */
1422 	if (sscanf(buf, "C %d ; WX %d ;", &ix, &w) != 2) {
1423 	    emsg = fxStr::format(NLS::TEXT("%s, line %u: format error"),
1424 		(const char*) file, lineno);
1425 	    fclose(fp);
1426 	    return (false);
1427 	}
1428 	if (ix == -1)			// end of unencoded glyphs
1429 	    break;
1430 	/*
1431 	 * Next if-clause added by Leif Erlingsson <leif@lege.com> because
1432 	 * experience has shown most glyph metric table's to be useless
1433 	 * for obtaining character widths of iso8859-1 characters > 127.
1434 	 * The Adobe Helvetica-Oblique metrics-file created Tue Apr 1
1435 	 * 12:54:09 PST 1986 caused bad spacing for eight-bit iso-8859-1
1436 	 * characters, for example.
1437 	 */
1438 	if (ix > 127)
1439 	    w = 625;			// distrust metrics-file for char > 127
1440 	if ((unsigned)ix < NCHARS)
1441 	    widths[ix] = w*ps/1000L;
1442     }
1443     fclose(fp);
1444     return (true);
1445 }
1446