1 /* $Id$ */
2 /*
3 * Copyright (c) 1994-1996 Sam Leffler
4 * Copyright (c) 1994-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 * Read and render a PCF font. This code is VERY distantly
29 * related to the X11R5 code found in the file pcfread.c (the
30 * original copyright is at the bottom of this file).
31 *
32 * This code is specifically written to handle just enough
33 * of the format to image text for outgoing facsimile.
34 */
35 #include <unistd.h>
36 #include "PCFFont.h"
37 #include "tiffio.h"
38
39 #define LSBFirst 0
40 #define MSBFirst 1
41
42 struct PCFTableRec { // on-disk table of contents
43 u_long type;
44 u_long format;
45 u_long size;
46 u_long offset;
47 };
48
49 struct charInfo {
50 short lsb, rsb; // left+right side bearing from file
51 short ascent, descent;// ascent+descent from file
52 u_short cw; // character width from file
53 u_char* bits; // pointer to glyph bitmap
54 };
55
56 #define PCF_FILE_VERSION (('p'<<24)|('c'<<16)|('f'<<8)|1)
57 #define PCF_FORMAT_MASK 0xffffff00
58
59 #define PCF_DEFAULT_FORMAT 0x00000000
60 #define PCF_COMPRESSED_METRICS 0x00000100
61 #define PCF_ACCEL_W_INKBOUNDS 0x00000100
62
63 #define PCF_METRICS (1<<2) // font metric information
64 #define PCF_BITMAPS (1<<3) // glyph bitmaps
65 #define PCF_BDF_ENCODINGS (1<<5) // BDF-based encoding
66 #define PCF_BDF_ACCELERATORS (1<<8) // BDF-derived accelerator information
67
68 #define PCF_GLYPH_PAD_MASK (3<<0)
69 #define PCF_BYTE_MASK (1<<2)
70 #define PCF_BIT_MASK (1<<3)
71 #define PCF_SCAN_UNIT_MASK (3<<4)
72
73 #define CONFIG_BIT MSBFirst // required bit order
74 #define CONFIG_BYTE MSBFirst // required byte order
75 #define CONFIG_GLYPH 2 // glyphs must be word-aligned
76 #define CONFIG_GLYPH_IX 1 // index for word-aligned glyphs
77
isFormat(u_long f) const78 inline bool PCFFont::isFormat(u_long f) const
79 { return ((format&PCF_FORMAT_MASK) == f); }
byteOrder() const80 inline u_int PCFFont::byteOrder() const
81 { return ((format&PCF_BYTE_MASK) ? MSBFirst : LSBFirst); }
bitOrder() const82 inline u_int PCFFont::bitOrder() const
83 { return ((format&PCF_BIT_MASK) ? MSBFirst : LSBFirst); }
glyphPadIndex() const84 inline u_int PCFFont::glyphPadIndex() const
85 { return ((format) & PCF_GLYPH_PAD_MASK); }
glyphPad() const86 inline u_int PCFFont::glyphPad() const
87 { return (1<<glyphPadIndex()); }
scanUnit() const88 inline u_int PCFFont::scanUnit() const
89 { return (1<<((format) & PCF_SCAN_UNIT_MASK)); }
90
PCFFont()91 PCFFont::PCFFont()
92 {
93 file = NULL;
94 filename = NULL;
95 encoding = NULL;
96 bitmaps = NULL;
97 metrics = NULL;
98 toc = NULL;
99 cdef = NULL;
100 { union { int32 i; char c[4]; } u; u.i = 1; isBigEndian = u.c[0] == 0; }
101 }
102
~PCFFont()103 PCFFont::~PCFFont()
104 {
105 cleanup();
106 }
107
108 void
cleanup()109 PCFFont::cleanup()
110 {
111 if (file != NULL)
112 fclose(file), file = NULL;
113 ready = false;
114 delete toc, toc = NULL;
115 delete encoding, encoding = NULL;
116 delete bitmaps, bitmaps = NULL;
117 delete metrics, metrics = NULL;
118 cdef = NULL;
119 }
120
121 /*
122 * Read only the useful bits from a PCF font file:
123 * font metrics, font bitmaps, font encoding, and
124 * some of the accelerator info.
125 */
126 bool
read(const char * name)127 PCFFont::read(const char* name)
128 {
129 cleanup();
130 filename = name; // for error diagnostics
131 file = fopen(filename, "r");
132 if (file == NULL) {
133 error("Can not open file");
134 return (false);
135 }
136 if (!readTOC())
137 return (false);
138 if (seekToTable(PCF_METRICS)) {
139 format = getLSB32();
140 if (isFormat(PCF_DEFAULT_FORMAT))
141 numGlyphs = getINT32();
142 else if (isFormat(PCF_COMPRESSED_METRICS))
143 numGlyphs = getINT16();
144 else {
145 error("Bad font metric format 0x%lx", format);
146 return (false);
147 }
148 metrics = new charInfo[numGlyphs];
149 if (!metrics) {
150 error("No space for font metric information");
151 return (false);
152 }
153 for (u_int i = 0; i < numGlyphs; i++) {
154 if (isFormat(PCF_DEFAULT_FORMAT))
155 getMetric(metrics[i]);
156 else
157 getCompressedMetric(metrics[i]);
158 }
159 } else {
160 error("Can not seek to font metric information");
161 return (false);
162 }
163
164 if (seekToTable(PCF_BITMAPS)) {
165 format = getLSB32();
166 if (!isFormat(PCF_DEFAULT_FORMAT)) {
167 error("Bad bitmap data format 0x%lx", format);
168 return (false);
169 }
170 u_long nbitmaps = getINT32();
171 u_long* offsets = new u_long[nbitmaps];
172 if (!offsets) {
173 error("No space for bitmap offsets array");
174 return (false);
175 }
176 for (u_int i = 0; i < nbitmaps; i++)
177 offsets[i] = getINT32();
178 u_long bitmapSizes[4];
179 bitmapSizes[0] = getINT32();
180 bitmapSizes[1] = getINT32();
181 bitmapSizes[2] = getINT32();
182 bitmapSizes[3] = getINT32();
183 u_long sizebitmaps = bitmapSizes[glyphPadIndex()];
184 bitmaps = new u_char[sizebitmaps];
185 if (!bitmaps) {
186 error("No space for bitmap data array");
187 delete offsets;
188 return (false);
189 }
190 if (fread(bitmaps, (u_int) sizebitmaps, 1, file) != 1) {
191 error("Error reading bitmap data");
192 delete offsets;
193 return (false);
194 }
195 if (bitOrder() != CONFIG_BIT)
196 TIFFReverseBits(bitmaps, sizebitmaps);
197 if (byteOrder() != bitOrder()) {
198 switch (scanUnit()) {
199 case 2:
200 TIFFSwabArrayOfShort((uint16*) bitmaps, sizebitmaps/2);
201 break;
202 case 4:
203 TIFFSwabArrayOfLong((uint32*) bitmaps, sizebitmaps/4);
204 break;
205 default:
206 error("Unknown scan unit format %d\n", scanUnit());
207 break;
208 }
209 }
210 if (!isBigEndian) // NB: rasterizer requires BE byte order
211 TIFFSwabArrayOfShort((u_short*) bitmaps, sizebitmaps/2);
212 if (glyphPad() != CONFIG_GLYPH) {
213 u_long sizepadbitmaps = bitmapSizes[CONFIG_GLYPH_IX];
214 u_char* padbitmaps = new u_char[sizepadbitmaps];
215 if (!padbitmaps) {
216 error("No space for padded bitmap data array");
217 delete offsets;
218 return (false);
219 }
220 int newoff = 0;
221 for (u_int i = 0; i < nbitmaps; i++) {
222 off_t old = offsets[i];
223 offsets[i] = newoff;
224 const charInfo& metric = metrics[i];
225 newoff += repadBitmap(bitmaps + old, padbitmaps + newoff,
226 glyphPad(), CONFIG_GLYPH,
227 metric.rsb - metric.lsb,
228 metric.ascent + metric.descent);
229 }
230 delete bitmaps;
231 bitmaps = padbitmaps;
232 }
233 for (u_int j = 0; j < nbitmaps; j++) {
234 metrics[j].bits = bitmaps + offsets[j];
235 if ((unsigned long) metrics[j].bits & 1) {
236 error("Internal error, bitmap data not word-aligned");
237 delete offsets;
238 return (false);
239 }
240 }
241 delete offsets;
242 } else {
243 error("Can not seek to bitmap data");
244 return (false);
245 }
246
247 if (seekToTable(PCF_BDF_ENCODINGS)) {
248 format = getLSB32();
249 if (!isFormat(PCF_DEFAULT_FORMAT)) {
250 error("Bad encodings format 0x%lx", format);
251 return (false);
252 }
253 firstCol = getINT16();
254 lastCol = getINT16();
255 u_short firstRow = getINT16();
256 u_short lastRow = getINT16();
257 u_short defaultCh = getINT16();
258
259 u_int nencoding = (lastCol-firstCol+1) * (lastRow-firstRow+1);
260 encoding = new charInfo*[nencoding];
261 if (!encoding) {
262 error("No space for character encoding vector");
263 return (false);
264 }
265 for (u_int i = 0; i < nencoding; i++) {
266 int encodingOffset = getINT16();
267 encoding[i] = (encodingOffset == 0xffff) ?
268 0 : metrics + encodingOffset;
269 }
270 if (defaultCh != (u_short)-1) {
271 int r = defaultCh >> 8;
272 int c = defaultCh & 0xff;
273 if (firstRow <= r && r <= lastRow && firstCol <= c && c <= lastCol) {
274 int cols = lastCol - firstCol + 1;
275 r = r - firstRow;
276 c = c - firstCol;
277 cdef = encoding[r * cols + c];
278 }
279 }
280 } else {
281 error("Can not seek to encoding data");
282 return (false);
283 }
284
285 if (seekToTable(PCF_BDF_ACCELERATORS)) {
286 format = getLSB32();
287 if (!isFormat(PCF_DEFAULT_FORMAT) &&
288 !isFormat(PCF_ACCEL_W_INKBOUNDS)) {
289 error("Bad BDF accelerator format 0x%lx", format);
290 return (false);
291 }
292 fseek(file, 8, SEEK_CUR); // skip a bunch of junk
293 fontAscent = (short) getINT32();
294 fontDescent = (short) getINT32();
295 // more stuff...
296 } else {
297 error("Can not seek to BDF accelerator information");
298 return (false);
299 }
300 fclose(file), file = NULL;
301 filename = NULL;
302 return (ready = true);
303 }
304
305 u_long
getLSB32()306 PCFFont::getLSB32()
307 {
308 u_long c = getc(file);
309 c |= getc(file) << 8;
310 c |= getc(file) << 16;
311 c |= getc(file) << 24;
312 return (c);
313 }
314
315 u_long
getINT32()316 PCFFont::getINT32()
317 {
318 u_long c;
319 if (byteOrder() == MSBFirst) {
320 c = getc(file) << 24;
321 c |= getc(file) << 16;
322 c |= getc(file) << 8;
323 c |= getc(file);
324 } else {
325 c = getc(file);
326 c |= getc(file) << 8;
327 c |= getc(file) << 16;
328 c |= getc(file) << 24;
329 }
330 return (c);
331 }
332
333 int
getINT16()334 PCFFont::getINT16()
335 {
336 int c;
337 if (byteOrder() == MSBFirst) {
338 c = getc(file) << 8;
339 c |= getc(file);
340 } else {
341 c = getc(file);
342 c |= getc(file) << 8;
343 }
344 return (c);
345 }
346
getINT8()347 int PCFFont::getINT8() { return getc(file); }
348
349 /*
350 * PCF supports two formats for metrics, both the regular
351 * jumbo size, and 'lite' metrics, which are useful
352 * for most fonts which have even vaguely reasonable
353 * metrics
354 */
355 void
getMetric(charInfo & metric)356 PCFFont::getMetric(charInfo& metric)
357 {
358 metric.lsb = getINT16();
359 metric.rsb = getINT16();
360 metric.cw = getINT16();
361 metric.ascent = getINT16();
362 metric.descent = getINT16();
363 (void) getINT16(); // attributes
364 }
365
366 void
getCompressedMetric(charInfo & metric)367 PCFFont::getCompressedMetric(charInfo& metric)
368 {
369 metric.lsb = getINT8() - 0x80;
370 metric.rsb = getINT8() - 0x80;
371 metric.cw = getINT8() - 0x80;
372 metric.ascent = getINT8() - 0x80;
373 metric.descent = getINT8() - 0x80;
374 }
375
376 /*
377 * Position to the begining of the specified table.
378 */
379 bool
seekToTable(u_long type)380 PCFFont::seekToTable(u_long type)
381 {
382 for (u_int i = 0; i < tocSize; i++)
383 if (toc[i].type == type) {
384 if (fseek(file, toc[i].offset, SEEK_SET) == -1) {
385 error("Can not seek; fseek failed");
386 return (false);
387 }
388 format = toc[i].format;
389 return (true);
390 }
391 error("Can not seek; no such entry in the TOC");
392 return (false);
393 }
394
395 /*
396 * Read the table-of-contents for the font file.
397 */
398 bool
readTOC()399 PCFFont::readTOC()
400 {
401 u_long version = getLSB32();
402 if (version != PCF_FILE_VERSION) {
403 error("Cannot read TOC; bad version number %lu", version);
404 return (false);
405 }
406 tocSize = getLSB32();
407 toc = new PCFTableRec[tocSize];
408 if (!toc) {
409 error("Cannot read TOC; no space for %lu records", tocSize);
410 return (false);
411 }
412 for (u_int i = 0; i < tocSize; i++) {
413 toc[i].type = getLSB32();
414 toc[i].format = getLSB32();
415 toc[i].size = getLSB32();
416 toc[i].offset = getLSB32();
417 }
418 return (true);
419 }
420
421 int
repadBitmap(u_char * src,u_char * dst,u_long spad,u_long dpad,int w,int h)422 PCFFont::repadBitmap(u_char* src, u_char* dst, u_long spad, u_long dpad, int w, int h)
423 {
424 int srcWidthBytes;
425 switch (spad) {
426 case 1: srcWidthBytes = (w+7)>>3; break;
427 case 2: srcWidthBytes = ((w+15)>>4)<<1; break;
428 case 4: srcWidthBytes = ((w+31)>>5)<<2; break;
429 case 8: srcWidthBytes = ((w+63)>>6)<<3; break;
430 default: return 0;
431 }
432 int dstWidthBytes;
433 switch (dpad) {
434 case 1: dstWidthBytes = (w+7)>>3; break;
435 case 2: dstWidthBytes = ((w+15)>>4)<<1; break;
436 case 4: dstWidthBytes = ((w+31)>>5)<<2; break;
437 case 8: dstWidthBytes = ((w+63)>>6)<<3; break;
438 default: return 0;
439 }
440 w = srcWidthBytes;
441 if (w > dstWidthBytes)
442 w = dstWidthBytes;
443 for (int row = 0; row < h; row++) {
444 int col;
445 for (col = 0; col < w; col++)
446 *dst++ = *src++;
447 while (col < dstWidthBytes) {
448 *dst++ = '\0';
449 col++;
450 }
451 src += srcWidthBytes - w;
452 }
453 return (dstWidthBytes * h);
454 }
455
456 u_int
charWidth(u_int c) const457 PCFFont::charWidth(u_int c) const
458 {
459 if (ready) {
460 charInfo* ci = (firstCol <= c && c <= lastCol) ?
461 encoding[c - firstCol] : cdef;
462 return (ci ? ci->cw : 0);
463 } else
464 return (0);
465 }
466
467 void
strWidth(const char * text,u_int & sw,u_int & sh) const468 PCFFont::strWidth(const char* text, u_int &sw, u_int& sh) const
469 {
470 sh = fontHeight();
471 sw = 0;
472 if (ready) {
473 for (const char* cp = text; *cp; cp++) {
474 u_int g = *cp;
475 charInfo* ci = (firstCol <= g && g <= lastCol) ?
476 encoding[g - firstCol] : cdef;
477 if (ci)
478 sw += ci->cw;
479 }
480 }
481 }
482
483 /* merge Left/Right bits from a word in a glyph bitmap */
484 #define MERGEL(r,g,dx,dm) \
485 (r) = ((r) &~ (dm)) | (((g)>>dx) & (dm))
486 #define MERGER(r,g,dx,dm) \
487 (r) = ((r) &~ (dm)) | (((g)<<(dx)) & (dm))
488 /*
489 * Image text into a raster. The raster origin (0,0) is assumed to
490 * be in the upper left. Text is imaged from top-to-bottom and
491 * left-to-right. The height of the rendered text is returned.
492 */
493 u_int
imageText(const char * text,u_short * raster,u_int w,u_int h,u_int lm,u_int rm,u_int tm,u_int bm) const494 PCFFont::imageText(const char* text,
495 u_short* raster, u_int w, u_int h,
496 u_int lm, u_int rm, u_int tm, u_int bm) const
497 {
498 if (!ready)
499 return (0);
500 u_int rowwords = howmany(w,16);
501 u_int y = tm + fontAscent;
502 u_int x = lm;
503 /*
504 * The rasterize assumes words have a big-endian
505 * byte order. For now (rather than fix it) we
506 * byte swap the data coming in and going out.
507 */
508 if (!isBigEndian) // XXX
509 TIFFSwabArrayOfShort((u_short*) raster, h*rowwords);
510 for (const char* cp = text; *cp; cp++) {
511 u_int g = (u_char)*cp;
512 charInfo* ci = (firstCol <= g && g <= lastCol) ?
513 encoding[g - firstCol] : cdef;
514 if (!ci)
515 continue;
516 if (x + ci->cw > w - rm) { // no space on line, move down
517 if (y+fontHeight() >= h-bm)
518 break; // raster completely full
519 y += fontHeight();
520 x = lm;
521 }
522 /*
523 * Blit glyph bitmap into raster. The work done here is
524 * not designed for speed. We break the work into two cases;
525 * where the destination location in the raster is word-aligned
526 * and where it's not word-aligned. Glyph bitmaps are assumed
527 * to be word-padded and to have the bits ``left adjusted''
528 * within words. Note that we handle glyphs that are at
529 * most 47 bits wide; this should be sufficient for our needs.
530 */
531 u_short cw = ci->rsb - ci->lsb; // bitmap glyph width
532 u_short cwords = cw>>4; // full words in glyph
533 if (cwords > 2) // skip too wide glyph
534 continue;
535 int cx = x + ci->lsb; // left edge of glyph
536 int ch = ci->ascent + ci->descent; // glyph height
537 u_short* rp = raster + (y-ci->ascent)*rowwords + (cx>>4);
538 u_short* gp = (u_short*) ci->bits;
539 u_short dx0 = cx&15; // raster word offset
540 u_short rowdelta = rowwords - cwords; // raster row adjust factor
541 u_short cbits = cw&15; // partial glyph word
542 if (dx0 != 0) { // hard case, raster unaligned
543 u_short dm0 = 0xffff>>dx0;
544 u_short dx1 = 16-dx0;
545 u_short dm1 = ~dm0;
546 u_short dm2, dm3;
547 if (cbits > dx1) { // spills into 2nd word
548 dm2 = dm0;
549 dm3 = ~((1<<dx1)-1);
550 } else { // fits entirely into 1st word
551 dm2 = dm0 &~ ((1<<(dx1-cbits))-1);
552 dm3 = 0;
553 }
554 for (short r = 0; r < ch; r++) {
555 switch (cwords) { // merge complete glyph words
556 case 2: MERGEL(rp[0], gp[0], dx0, dm0);
557 MERGER(rp[1], gp[0], dx1, dm1);
558 rp++, gp++;
559 case 1: MERGEL(rp[0], gp[0], dx0, dm0);
560 MERGER(rp[1], gp[0], dx1, dm1);
561 rp++, gp++;
562 }
563 if (cbits) {
564 MERGEL(rp[0], gp[0], dx0, dm2);
565 MERGER(rp[1], gp[0], dx1, dm3);
566 gp++;
567 }
568 rp += rowdelta;
569 }
570 } else { // raster word-aligned
571 u_short dm = 0xffff<<(16-cbits);
572 for (short r = 0; r < ch; r++) {
573 switch (cwords) {
574 case 2: *rp++ = *gp++;
575 case 1: *rp++ = *gp++;
576 }
577 if (cbits)
578 MERGEL(*rp, *gp++, 0, dm);
579 rp += rowdelta;
580 }
581 }
582 x += ci->cw;
583 }
584 if (!isBigEndian) // XXX
585 TIFFSwabArrayOfShort((u_short*) raster, h*rowwords);
586 return (y+fontDescent+bm);
587 }
588 #undef MERGEL
589 #undef MERGER
590
591 #include <ctype.h>
592
593 void
print(FILE * fd) const594 PCFFont::print(FILE* fd) const
595 {
596 if (ready) {
597 fprintf(fd, "Font Ascent: %d Descent: %d\n", fontAscent, fontDescent);
598 fprintf(fd, "FirstCol: %u LastCol: %u\n", firstCol, lastCol);
599 fprintf(fd, "%lu glyphs:\n", numGlyphs);
600 for (u_int c = firstCol; c <= lastCol; c++) {
601 charInfo* ci = encoding[c - firstCol];
602 if (!ci)
603 continue;
604 if (isprint(c))
605 fprintf(fd,
606 "'%c': lsb %2d rsb %2d cw %2d ascent %2d descent %d\n",
607 c, ci->lsb, ci->rsb, ci->cw, ci->ascent, ci->descent);
608 else
609 fprintf(fd,
610 "%3d: lsb %2d rsb %2d cw %2d ascent %2d descent %d\n",
611 c, ci->lsb, ci->rsb, ci->cw, ci->ascent, ci->descent);
612 }
613 }
614 }
615
616 #include "Str.h"
617
618 extern void vlogError(const char* fmt, va_list ap);
619
620 void
error(const char * fmt0...)621 PCFFont::error(const char* fmt0 ...)
622 {
623 va_list ap;
624 va_start(ap, fmt0);
625 fxStr fmt = fxStr::format("PCFFont: %s: %s",
626 filename ? filename : "<unknown file>", fmt0);
627 vlogError(fmt, ap);
628 va_end(ap);
629 }
630
631 /*
632 * $XConsortium: pcfread.c,v 1.10 92/05/12 18:07:47 gildea Exp $
633 *
634 * Copyright 1990 Massachusetts Institute of Technology
635 *
636 * Permission to use, copy, modify, distribute, and sell this software and its
637 * documentation for any purpose is hereby granted without fee, provided that
638 * the above copyright notice appear in all copies and that both that
639 * copyright notice and this permission notice appear in supporting
640 * documentation, and that the name of M.I.T. not be used in advertising or
641 * publicity pertaining to distribution of the software without specific,
642 * written prior permission. M.I.T. makes no representations about the
643 * suitability of this software for any purpose. It is provided "as is"
644 * without express or implied warranty.
645 *
646 * M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
647 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL M.I.T.
648 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
649 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
650 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
651 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
652 *
653 * Author: Keith Packard, MIT X Consortium
654 */
655