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