/* psftools: Manipulate console fonts in the .PSF format Copyright (C) 2000, 2005, 2024 John Elliott This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* Convert a vfont file to a Windows .FNT font. Note that to get this * font displayable by Windows, it has to be added to a program's * resources or converted to a .FON, which is a black art. */ #include "cnvshell.h" #include "psflib.h" #include "mswfnt.h" static char *copyright = "Copyright is unknown"; static char *font_name = "ConVFONT"; static char fnbuf[80]; static char cpybuf[200]; static VFONT vfonti; /* Input file */ static VFONT vfont; /* Source file */ static PSF_MAPPING *codepage = NULL; static MSW_FONTINFO fnt_head; /* Target file */ static unsigned nchars; /* No. of characters (256 or 512) */ static psf_byte charset = 0; /* ANSI charset */ static psf_byte bold = 0; static psf_byte italic = 0; /* Bold/italic */ static unsigned chartable_len; static unsigned head_len; /* Lengths of various bits */ static unsigned bitmap_len; static unsigned bitmap_width; static unsigned font_ver = 0x200; /* Use 2.0 format by default. */ /* Current Windows versions can't use the * 1.0 format, and 3.0 (real mode) can't * use the 3.0 format */ static unsigned char defchar = 0x2E; static unsigned char brkchar = 0x20; static int pointsize = -1; static int vertres = 48; static int horzres = 48; static int ascent = -1; static int firstchar = 0; static int lastchar = -1; static int fixwidth = 0; /* 0 for variable-width, else fixed width */ static int avgwidth = 0; /* Width of 'X' */ static psf_dword max_w, max_h, basex = 0, basey = 0; static psf_dword total_height = 0, total_width = 0; /* Write character table */ static int msw_chartable_write(FILE *fp) { unsigned n; psf_dword cellw, cellh, charx; psf_dword char_offset = head_len + chartable_len; /* In a 1.x font, the character table is omitted for a fixed-pitch * font */ if (font_ver == 0x100 && fixwidth) return 0; if (font_ver == 0x100) { charx = 0; for (n = firstchar; n <= lastchar + 1; n++) { vfont_get_cellsize(&vfont, n, &cellw, &cellh); if (write_word(fp, charx)) return -1; charx += cellw; } if (charx > 65535) { fprintf(stderr, "Character widths total to %ld, more than 65536\n", charx); return -1; } else if (charx > 32767) { fprintf(stderr, "Warning: Character widths total to %ld, more than 32767\n", charx); } return 0; } /* lastchar + 1 for the 'absolute-space' character */ for (n = firstchar; n <= lastchar + 1; n++) { vfont_get_cellsize(&vfont, n, &cellw, &cellh); if (write_word(fp, cellw)) return -1; if (write_word(fp, (msw_word)char_offset)) return -1; if (font_ver == 0x300 && write_word(fp, (msw_word)(char_offset >> 16))) return -1; char_offset += (bitmap_width * max_h); } if (font_ver == 0x0200 && char_offset > 65535) { fprintf(stderr, "Total character bitmap size is %ld, more than 65536.\nSuggest using --fontver=3\n", char_offset); return -1; } else if (font_ver == 0x0200 && char_offset > 32767) { fprintf(stderr, "Warning: Total character bitmap size is %ld, more than 32767.\nSuggest using --fontver=3\n", char_offset); } return 0; } /* Write glyphs table */ static int msw_glyphs_write(FILE *fp) { unsigned n, m, x, px, vx; signed y, yoff, xoff; psf_byte v, mask, b; psf_dword cellw, cellh; psf_dword charw, charh; VFONT_DISPATCH *curchar; if (font_ver == 0x100) { if (write_byte(fp, 0)) return -1; /* dfReserved */ for (m = 0; m < max_h; m++) { vx = x = 0; v = 0; mask = 0x80; for (n = firstchar; n <= lastchar; n++) { vfont_get_cellsize(&vfont, n, &cellw, &cellh); vfont_get_charsize(&vfont, n, &charw, &charh); curchar = &vfont.vf_dispatch[n]; /* Gap at top? */ yoff = basey - curchar->vfd_up; if (yoff < 0) yoff = 0; /* Gap at left? */ if (curchar->vfd_left < 0) xoff = -curchar->vfd_left; else xoff = 0; for (x = 0; x < cellw; x++) { if (x < xoff || x > (xoff+charw) || m < yoff || m > (yoff+cellh)) { b = 0; } else vfont_get_pixel(&vfont, n, x - xoff, m - yoff, &b); if (b) { v |= mask; } if (mask == 1) { if (write_byte(fp, v)) return -1; mask = 0x80; v = 0; ++vx; } else mask = mask >> 1; } } /* End loop over chars. Pad to bitmap_width */ while (vx < bitmap_width) { if (write_byte(fp, v)) return -1; v = 0; ++vx; } } } else { /* fprintf(stderr, "bitmap_width=%d max_w=%ld max_h=%ld\n", bitmap_width, max_w, max_h); */ for (n = firstchar; n <= lastchar; n++) { curchar = &vfont.vf_dispatch[n]; vfont_get_charsize(&vfont, n, &charw, &charh); vfont_get_cellsize(&vfont, n, &cellw, &cellh); yoff = basey - curchar->vfd_up; /* Gap at top? */ if (yoff < 0) yoff = 0; if (curchar->vfd_left < 0) xoff = -curchar->vfd_left; else xoff = 0; /* fprintf(stderr, "%02x: up=%d down=%d left=%d right=%d yoff=%d charh=%d @ %08x\n", n, curchar->vfd_up, curchar->vfd_down, curchar->vfd_left, curchar->vfd_right, yoff, charh, ftell(fp)); */ for (vx = 0; vx < bitmap_width; vx++) { for (y = 0; y < max_h; y++) { /* In top or bottom gap? */ if (y < yoff || y > (yoff+charh)) { if (write_byte(fp, 0x00)) return -1; } else { /* In a data line */ mask = 0x80; v = 0; for (x = 0; x < 8; x++) { px = vx * 8 + x; if (px < xoff || px - xoff >= charw) { b = 0; /* fputc('_', stderr); */ } else { vfont_get_pixel(&vfont, n, px - xoff, y - yoff, &b); /* fputc(b ? '#' : '-', stderr); */ } if (b) v |= mask; mask = mask >> 1; } if (write_byte(fp, v)) return -1; /* fputc('\n', stderr); */ } } } } /* Now write the blank character at the end */ for (x = 0; x < bitmap_width; x++) for (y = 0; y < max_h; y++) if (write_byte(fp, 0x00)) return -1; } return 0; } /* Write font name */ static int msw_name_write(FILE *fp) { if (fputs(font_name, fp)==EOF) return -1; return write_byte(fp, 0); /* Terminating zero */ } /* Set up the font header */ static void setup_fnthead(MSW_FONTINFO *head) { head->dfVersion = font_ver; if (font_ver == 0x300) head_len += 30; head->dfSize = head_len + chartable_len + bitmap_len + 1 +strlen(font_name); strncpy(head->dfCopyright, copyright, 60); head->dfCopyright[59] = 0; head->dfType = 0; /* Raster font */ head->dfPoints = pointsize; /* Point size := pixel height */ head->dfVertRes = vertres; /* this is based on CGA aspect ratio! */ head->dfHorizRes = horzres; /* this isn't based on anything */ head->dfAscent = ascent; head->dfInternalLeading = 0; head->dfExternalLeading = 0; head->dfItalic = italic; head->dfUnderline = 0; head->dfStrikeOut = 0; head->dfWeight = bold ? 700 : 400; head->dfCharSet = charset; head->dfPixWidth = fixwidth; head->dfPixHeight = max_h; head->dfPitchAndFamily = (fixwidth ? (3 << 4) : 0); /* FF_MODERN */ head->dfAvgWidth = avgwidth; head->dfMaxWidth = max_w; head->dfFirstChar = firstchar; head->dfLastChar = lastchar; head->dfDefaultChar = defchar; head->dfBreakChar = brkchar; /* Width in bytes, rounded up to nearest word */ head->dfWidthBytes = bitmap_width; head->dfDevice = 0; head->dfFace = head_len + chartable_len + bitmap_len; head->dfBitsPointer = 0; head->dfBitsOffset = head_len + chartable_len; head->dfReserved = 0; head->dfFlags = (fixwidth ? 1 : 2); /* Fixed width */ head->dfAspace = 0; head->dfBspace = 8; head->dfCspace = 0; head->dfColorPointer = 0; } /* Public interface to this program */ char *cnv_progname = "VFONT2FNT"; char *cnv_help(void) { static char helpbuf[2048]; sprintf(helpbuf, "Syntax: %s vfontfile fntfile { options }\n\n", cnv_progname); strcat (helpbuf, "Options: \n" " --ascent - Set ascent\n" " --bold - Mark font as bold\n" " --brkchar - Break character\n" " --copy=copyright-string - Set copyright string\n" " --codepage=cp - Extract the named " "codepage from the source\n" " file.\n" " --charset=ch - Set character set\n" " ch can be ANSI, OEM, SYMBOL or a number\n" " --defchar - Default character\n" " --first - First character to include\n" " --fontname=name - Set font name\n" " --fontver=1, 2 or 3 - Font format version\n" " --horzres - Set horizontal resolution\n" " --italic - Mark font as italic\n" " --last - Last character to include\n" " --pointsize - Set font point size\n" " --vertres - Set vertical resolution\n"); return helpbuf; } static char errbuf[150]; char *cnv_set_option(int ddash, char *variable, char *value) { if (!stricmp(variable, "copy" )) { copyright = cpybuf; strncpy(cpybuf, value, sizeof(cpybuf)); cpybuf[sizeof (cpybuf)-1] = 0; } else if (!stricmp(variable, "charset")) { if (!stricmp(value, "ansi" )) charset = 0; else if (!stricmp(value, "symbol")) charset = 2; else if (!stricmp(value, "oem" )) charset = 0xFF; else charset = atoi(value); } else if (!stricmp(variable, "codepage")) { codepage = psf_find_mapping(value); if (codepage == NULL) return "Code page name not recognised."; } else if (!stricmp(variable, "fontname")) { strncpy(fnbuf, value, sizeof(fnbuf)); fnbuf[sizeof(fnbuf) - 1] = 0; font_name = fnbuf; } else if (!stricmp(variable, "ascent")) ascent=atoi(value); else if (!stricmp(variable, "horzres")) horzres=atoi(value); else if (!stricmp(variable, "vertres")) vertres=atoi(value); else if (!stricmp(variable, "pointsize")) pointsize=atoi(value); else if (!stricmp(variable, "firstchar")) firstchar=atoi(value); else if (!stricmp(variable, "first")) firstchar=atoi(value); else if (!stricmp(variable, "lastchar")) lastchar=atoi(value); else if (!stricmp(variable, "last")) lastchar=atoi(value); else if (!stricmp(variable, "defchar")) defchar=atoi(value); else if (!stricmp(variable, "brkchar")) brkchar=atoi(value); else if (!stricmp(variable, "bold")) bold = 1; else if (!stricmp(variable, "italic")) italic = 1; else if (!stricmp(variable, "fontver")) { if (!strcmp(value, "1")) font_ver = 0x100; else if (!strcmp(value, "2")) font_ver = 0x200; else if (!strcmp(value, "3")) font_ver = 0x300; else return "--fontver option must be 1, 2 or 3"; } else { /* Brute force avoidance of buffer overflows */ if (strlen(variable) > 120) variable[120] = 0; sprintf(errbuf, "Unrecognised option: %s", variable); } return NULL; } char *cnv_execute(FILE *fpin, FILE *fpout) { int rv; psf_dword nc, glyph, w; psf_dword cellw, cellh; vfont_new(&vfont); vfont_new(&vfonti); if (codepage) { rv = vfont_read(&vfonti, fpin); if (!rv) rv = vfont_create(&vfont, vfonti.vf_length, vfonti.vf_size, 0); if (!rv) { psf_dword addr = 0; for (nc = 0; nc < vfonti.vf_length; nc++) { if (!vfont_unicode_lookupmap(&vfonti, codepage, nc, &glyph, NULL)) { VFONT_DISPATCH *src = &vfonti.vf_dispatch[glyph]; VFONT_DISPATCH *dest = &vfont.vf_dispatch[nc]; dest->vfd_nbytes = src->vfd_nbytes; dest->vfd_up = src->vfd_up; dest->vfd_down = src->vfd_down; dest->vfd_left = src->vfd_left; dest->vfd_right = src->vfd_right; dest->vfd_width = src->vfd_width; dest->vfd_addr = addr; memcpy(vfont.vf_bitmap + dest->vfd_addr, vfonti.vf_bitmap + src->vfd_addr, src->vfd_nbytes); addr += src->vfd_nbytes; } } vfont_delete(&vfonti); } } else { rv = vfont_read(&vfont, fpin); } if (rv) return psf_error_string(rv); nchars = vfont.vf_length; if (lastchar < 0) lastchar = nchars - 1; vfont_get_max_cellsize(&vfont, &max_w, &max_h, &basex, &basey); vfont_get_max_charsize(&vfont, NULL, &max_h); fixwidth = -1; avgwidth = -1; if (max_h > 64) { fprintf(stderr, "Warning: Character height of %ld pixels exceeds 64\n", max_h); } for (nc = firstchar; nc <= lastchar; nc++) { vfont_get_charsize(&vfont, nc, &w, NULL); if (fixwidth == -1) { fixwidth = w; } else if (fixwidth != w) { fixwidth = 0; } if (nc == 'X') avgwidth = w; } if (fixwidth) { avgwidth = fixwidth; } /* OK, we have the font loaded. */ head_len = 0x76; /* Length of font header for v1 / v2 */ for (nc = firstchar; nc <= lastchar; nc++) { vfont_get_cellsize(&vfont, nc, &cellw, &cellh); total_width += cellw; total_height += cellh; } /* fprintf(stderr, "fixwidth = %d avgwidth=%d max_w = %ld\n" "total_width=%ld total_height=%ld\n", fixwidth, avgwidth, max_w, total_width, total_height); */ switch(font_ver) { case 0x100: if (fixwidth) chartable_len = 0; else chartable_len = (lastchar + 2 - firstchar) * 2; bitmap_width = (total_width + 7) / 8; break; /* 4 bytes/char in table. */ case 0x200: chartable_len = (lastchar + 2 - firstchar) * 4; bitmap_width = (max_w + 7) / 8; if (bitmap_width & 1) ++bitmap_width; break; /* 6 bytes/char in table. */ case 0x300: chartable_len = (lastchar + 2 - firstchar) * 6; bitmap_width = (max_w + 7) / 8; if (bitmap_width & 1) ++bitmap_width; break; } if (pointsize < 0) pointsize = max_h; if (ascent < 0) ascent = basey; if (font_ver < 0x200) bitmap_len = max_h * bitmap_width; else bitmap_len = max_h * bitmap_width * (nchars + 1); msw_fontinfo_new(&fnt_head); setup_fnthead(&fnt_head); if (msw_fontinfo_write(&fnt_head, fpout) || msw_chartable_write(fpout) || msw_glyphs_write(fpout) || msw_name_write(fpout)) { vfont_delete(&vfont); return "Error writing output file"; } vfont_delete(&vfont); return NULL; }