blob: 9d7ee54506ad6237545d0301575085c6acd92f0f [file] [log] [blame]
/*
* psffontop.c - aeb@cwi.nl, 990921
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include "xmalloc.h"
#include "nls.h"
#include "psf.h"
#include "psffontop.h"
#include "utf8.h"
#include "paths.h"
extern char *progname;
static void
addpair(struct unicode_list *up, unsigned int uc) {
struct unicode_list *ul;
struct unicode_seq *us;
ul = xmalloc(sizeof(struct unicode_list));
us = xmalloc(sizeof(struct unicode_seq));
us->uc = uc;
us->prev = us;
us->next = NULL;
ul->seq = us;
ul->prev = up->prev;
ul->prev->next = ul;
ul->next = NULL;
up->prev = ul;
}
static void
addseq(struct unicode_list *up, unsigned int uc) {
struct unicode_seq *us;
struct unicode_seq *usl;
struct unicode_list *ul = up->prev;
usl = ul->seq;
while (usl->next) usl = usl->next;
us = xmalloc(sizeof(struct unicode_seq));
us->uc = uc;
us->prev = usl;
us->next = NULL;
usl->next = us;
//ul->seq->prev = us;
}
static unsigned int
assemble_int(unsigned char *ip) {
return (ip[0] + (ip[1]<<8) + (ip[2]<<16) + (ip[3]<<24));
}
static void
store_int_le(unsigned char *ip, int num) {
ip[0] = (num & 0xff);
ip[1] = ((num >> 8) & 0xff);
ip[2] = ((num >> 16) & 0xff);
ip[3] = ((num >> 24) & 0xff);
}
static unsigned int
assemble_ucs2(char **inptr, int cnt) {
unsigned int u1, u2;
if (cnt < 2) {
char *u = _("%s: short ucs2 unicode table\n");
fprintf(stderr, u, progname);
exit(EX_DATAERR);
}
u1 = (unsigned char) *(*inptr)++;
u2 = (unsigned char) *(*inptr)++;
return (u1 | (u2 << 8));
}
/* called with cnt > 0 and **inptr not 0xff or 0xfe */
static unsigned int
assemble_utf8(char **inptr, int cnt) {
int err;
unsigned long uc;
char *u;
uc = from_utf8(inptr, cnt, &err);
if (err) {
switch (err) {
case UTF8_SHORT:
u = _("%s: short utf8 unicode table\n");
break;
case UTF8_BAD:
u = _("%s: bad utf8\n");
break;
default:
u = _("%s: unknown utf8 error\n");
}
fprintf(stderr, u, progname);
exit(EX_DATAERR);
}
return uc;
}
static void
clear_uni_entry(struct unicode_list *up) {
up->next = NULL;
up->seq = NULL;
up->prev = up;
}
/*
* Read description of a single font position.
*/
static void
get_uni_entry(char **inptr, char **endptr, struct unicode_list *up, int utf8) {
unsigned char uc;
unicode unichar;
int inseq = 0;
up->next = NULL;
up->seq = NULL;
up->prev = up;
while(1) {
if (*endptr == *inptr) {
char *u = _("%s: short unicode table\n");
fprintf(stderr, u, progname);
exit(EX_DATAERR);
}
if (utf8) {
uc = *(*inptr)++;
if (uc == PSF2_SEPARATOR)
break;
if (uc == PSF2_STARTSEQ) {
inseq = 1;
continue;
}
--(*inptr);
unichar = assemble_utf8(inptr, *endptr - *inptr);
} else {
unichar = assemble_ucs2(inptr, *endptr - *inptr);
if (unichar == PSF1_SEPARATOR)
break;
if (unichar == PSF1_STARTSEQ) {
inseq = 1;
continue;
}
}
if (inseq < 2)
addpair(up, unichar);
else
addseq(up, unichar);
if (inseq)
inseq++;
}
}
/*
* Read a psf font and return >= 0 on success and -1 on failure.
* Failure means that the font was not psf (but has been read).
* > 0 means that the Unicode table contains sequences.
*
* The font is read either from file (when FONT is non-NULL)
* or from memory (namely from *ALLBUFP of size *ALLSZP).
* In the former case, if ALLBUFP is non-NULL, a pointer to
* the entire fontfile contents (possibly read from pipe)
* is returned in *ALLBUFP, and the size in ALLSZP, where this
* buffer was allocated using malloc().
* In FONTBUFP, FONTSZP the subinterval of ALLBUFP containing
* the font data is given.
* The font width is stored in FONTWIDTHP.
* The number of glyphs is stored in FONTLENP.
* The unicodetable is stored in UCLISTHEADSP (when non-NULL), with
* fontpositions counted from FONTPOS0 (so that calling this several
* times can achieve font merging).
*/
extern char *progname;
int
readpsffont(FILE *fontf, char **allbufp, int *allszp,
char **fontbufp, int *fontszp,
int *fontwidthp, int *fontlenp, int fontpos0,
struct unicode_list **uclistheadsp) {
char *inputbuf = NULL;
size_t inputbuflth = 0;
size_t inputlth, fontlen, fontwidth, charsize, hastable, ftoffset, utf8;
size_t i, k, n;
/*
* We used to look at the length of the input file
* with stat(); now that we accept compressed files,
* just read the entire file.
*/
if (fontf) {
inputbuflth = MAXFONTSIZE/4; /* random */
inputbuf = xmalloc(inputbuflth);
n = 0;
while(1) {
if (n >= inputbuflth) {
inputbuflth *= 2;
inputbuf = xrealloc(inputbuf, inputbuflth);
}
n += fread(inputbuf+n, 1, inputbuflth-n, fontf);
if (ferror(fontf)) {
char *u = _("%s: Error reading input font");
fprintf(stderr, u, progname);
exit(EX_DATAERR);
}
if (feof(fontf))
break;
}
if (allbufp)
*allbufp = inputbuf;
if (allszp)
*allszp = n;
inputlth = n;
} else {
if (!allbufp || !allszp) {
char *u = _("%s: Bad call of readpsffont\n");
fprintf(stderr, u, progname);
exit(EX_SOFTWARE);
}
inputbuf = *allbufp;
inputbuflth = inputlth = n = *allszp;
}
if (inputlth >= sizeof(struct psf1_header) &&
PSF1_MAGIC_OK((unsigned char *)inputbuf)) {
struct psf1_header *psfhdr;
psfhdr = (struct psf1_header *) &inputbuf[0];
if (psfhdr->mode > PSF1_MAXMODE) {
char *u = _("%s: Unsupported psf file mode (%d)\n");
fprintf(stderr, u, progname, psfhdr->mode);
exit(EX_DATAERR);
}
fontlen = ((psfhdr->mode & PSF1_MODE512) ? 512 : 256);
charsize = psfhdr->charsize;
hastable = (psfhdr->mode & (PSF1_MODEHASTAB|PSF1_MODEHASSEQ));
ftoffset = sizeof(struct psf1_header);
fontwidth = 8;
utf8 = 0;
} else if (inputlth >= sizeof(struct psf2_header) &&
PSF2_MAGIC_OK((unsigned char *)inputbuf)) {
struct psf2_header psfhdr;
int flags;
memcpy(&psfhdr, inputbuf, sizeof(struct psf2_header));
if (psfhdr.version > PSF2_MAXVERSION) {
char *u = _("%s: Unsupported psf version (%d)\n");
fprintf(stderr, u, progname, psfhdr.version);
exit(EX_DATAERR);
}
fontlen = assemble_int((unsigned char *) &psfhdr.length);
charsize = assemble_int((unsigned char *) &psfhdr.charsize);
flags = assemble_int((unsigned char *) &psfhdr.flags);
hastable = (flags & PSF2_HAS_UNICODE_TABLE);
ftoffset = assemble_int((unsigned char *) &psfhdr.headersize);
fontwidth = assemble_int((unsigned char *) &psfhdr.width);
utf8 = 1;
} else
return -1; /* not psf */
/* tests required - we divide by these */
if (fontlen == 0) {
char *u = _("%s: zero input font length?\n");
fprintf(stderr, u, progname);
exit(EX_DATAERR);
}
if (charsize == 0) {
char *u = _("%s: zero input character size?\n");
fprintf(stderr, u, progname);
exit(EX_DATAERR);
}
i = ftoffset + fontlen * charsize;
if (i > inputlth || (!hastable && i != inputlth)) {
char *u = _("%s: Input file: bad input length (%d)\n");
fprintf(stderr, u, progname, inputlth);
exit(EX_DATAERR);
}
if (fontbufp && allbufp)
*fontbufp = *allbufp + ftoffset;
if (fontszp)
*fontszp = fontlen * charsize;
if (fontlenp)
*fontlenp = fontlen;
if (fontwidthp)
*fontwidthp = fontwidth;
if (!uclistheadsp)
return 0; /* got font, don't need unicode_list */
*uclistheadsp = xrealloc(*uclistheadsp,
(fontpos0+fontlen)*sizeof(struct unicode_list));
if (hastable) {
char *inptr, *endptr;
inptr = inputbuf + ftoffset + fontlen * charsize;
endptr = inputbuf + inputlth;
for (i=0; i<fontlen; i++) {
k = fontpos0 + i;
get_uni_entry(&inptr, &endptr,
&(*uclistheadsp)[k], utf8);
}
if (inptr != endptr) {
char *u = _("%s: Input file: trailing garbage\n");
fprintf(stderr, u, progname);
exit(EX_DATAERR);
}
} else {
for (i=0; i<fontlen; i++) {
k = fontpos0 + i;
clear_uni_entry(&(*uclistheadsp)[k]);
}
}
return 0; /* got psf font */
}
static int
has_sequences(struct unicode_list *uclistheads, int fontlen) {
struct unicode_list *ul;
struct unicode_seq *us;
int i;
for (i=0; i<fontlen; i++) {
ul = uclistheads[i].next;
while(ul) {
us = ul->seq;
if (us && us->next)
return 1;
ul = ul->next;
}
}
return 0;
}
void
appendunicode(FILE *fp, unsigned int uc, int utf8) {
int n = 6;
unsigned char out[6];
if (uc & ~0x7fffffff) {
fprintf(stderr, _("appendunicode: illegal unicode %u\n"), uc);
exit(1);
}
if (!utf8) {
out[--n] = ((uc >> 8) & 0xff);
out[--n] = (uc & 0xff);
} else if (uc < 0x80) {
out[--n] = uc;
} else {
int mask = 0x3f;
while (uc & ~mask) {
out[--n] = 0x80 + (uc & 0x3f);
uc >>= 6;
mask >>= 1;
}
out[--n] = ((uc + ~mask + ~mask) & 0xff);
}
if (fwrite(out+n, 6-n, 1, fp) != 1) {
perror("appendunimap");
exit(1);
}
if (debug) {
printf ("(");
if (!utf8)
printf ("U+");
while (n < 6) printf ("%02x ", out[n++]);
printf (")");
}
}
void
appendseparator(FILE *fp, int seq, int utf8) {
int n;
if (utf8) {
unsigned char u = (seq ? PSF2_STARTSEQ : PSF2_SEPARATOR);
n = fwrite(&u, sizeof(u), 1, fp);
} else {
unsigned short u = (seq ? PSF1_STARTSEQ : PSF1_SEPARATOR);
n = fwrite(&u, sizeof(u), 1, fp);
}
if (n != 1) {
perror("appendseparator");
exit(1);
}
}
void
writepsffontheader(FILE *ofil, int width, int height, int fontlen,
int *psftype, int flags) {
int bytewidth, charsize, ret;
bytewidth = (width+7)/8;
charsize = bytewidth * height;
if ((fontlen != 256 && fontlen != 512) || width != 8)
*psftype = 2;
if (*psftype == 2) {
struct psf2_header h;
int flags2 = 0;
if (flags & WPSFH_HASTAB)
flags2 |= PSF2_HAS_UNICODE_TABLE;
h.magic[0] = PSF2_MAGIC0;
h.magic[1] = PSF2_MAGIC1;
h.magic[2] = PSF2_MAGIC2;
h.magic[3] = PSF2_MAGIC3;
store_int_le((unsigned char *) &h.version, 0);
store_int_le((unsigned char *) &h.headersize, sizeof(h));
store_int_le((unsigned char *) &h.flags, flags2);
store_int_le((unsigned char *) &h.length, fontlen);
store_int_le((unsigned char *) &h.charsize, charsize);
store_int_le((unsigned char *) &h.width, width);
store_int_le((unsigned char *) &h.height, height);
ret = fwrite(&h, sizeof(h), 1, ofil);
} else {
struct psf1_header h;
h.magic[0] = PSF1_MAGIC0;
h.magic[1] = PSF1_MAGIC1;
h.mode = 0;
if (fontlen == 512)
h.mode |= PSF1_MODE512;
if (flags & WPSFH_HASSEQ)
h.mode |= PSF1_MODEHASSEQ;
else if (flags & WPSFH_HASTAB)
h.mode |= PSF1_MODEHASTAB;
h.charsize = charsize;
ret = fwrite(&h, sizeof(h), 1, ofil);
}
if (ret != 1) {
fprintf(stderr, _("Cannot write font file header"));
exit(EX_IOERR);
}
}
int
writepsffont(FILE *ofil, char *fontbuf, int width, int height, size_t fontlen,
int psftype, struct unicode_list *uclistheads) {
int bytewidth, charsize, flags, utf8;
size_t i;
bytewidth = (width+7)/8;
charsize = bytewidth * height;
flags = 0;
if (uclistheads) {
flags |= WPSFH_HASTAB;
if (has_sequences(uclistheads, fontlen))
flags |= WPSFH_HASSEQ;
}
writepsffontheader(ofil, width, height, fontlen, &psftype, flags);
utf8 = (psftype == 2);
if ((fwrite(fontbuf, charsize, fontlen, ofil)) != fontlen) {
fprintf(stderr, _("Cannot write font file"));
exit(EX_IOERR);
}
/* unimaps: -1 => do nothing: caller will append map */
if (uclistheads != NULL && uclistheads != (struct unicode_list*)-1) {
struct unicode_list *ul;
struct unicode_seq *us;
for (i=0; i<fontlen; i++) {
ul = uclistheads[i].next;
while(ul) {
us = ul->seq;
if (us && us->next)
appendseparator(ofil, 1, utf8);
while(us) {
appendunicode(ofil, us->uc, utf8);
us = us->next;
}
ul = ul->next;
}
appendseparator(ofil, 0, utf8);
}
}
return utf8;
}