/* psftools: Manipulate console fonts in the .PSF format Copyright (C) 2005,2021 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. */ #include "cnvshell.h" #include "psflib.h" #include #ifndef PATH_MAX # ifdef CPM # define PATH_MAX 20 # else # define PATH_MAX 256 # endif #endif typedef struct range { int first; int last; struct range *next; } RANGE; typedef struct subset { int applies; RANGE *range; } SUBSET; /* Perform various operations on a vfont (or a subset of a vfont) */ static char helpbuf[2048]; static PSF_MAPPING *codepage = NULL; static PSF_MAPPING *setcodepage = NULL; static int first = -1; static int last = -1; static int height = -1; static int width = -1; static int strip = 0; static SUBSET bold; static SUBSET centre; static SUBSET dblheight; static SUBSET flipx; static SUBSET inverse; static SUBSET repeat; static SUBSET scale; static SUBSET thin; static int vfont1 = 0, vfont2 = 0; static int big_endian = -1; /* When writing v1, use big-endian? */ static int ucs = 0; static char permfile[PATH_MAX]; static char linebuf[20]; static psf_dword *permutation; static VFONT vfonti, vfontp, vfonto; /* Program name */ char *cnv_progname = "VFONTXFORM"; int within_subset(int idx, SUBSET *s) { RANGE *r; if (!s->applies) return 0; if (!s->range) return 1; for (r = s->range; r != NULL; r = r->next) { if (idx >= r->first && idx <= r->last) return 1; } return 0; } char *parse_subset(char *value, SUBSET *s) { char *p, *q; RANGE *r; s->applies = 1; if (!value[0]) /* No range applies */ { s->range = NULL; return NULL; } /* Parse a string formed [nnn|nnn-nnn]{,[nnn|nnn-nnn]}* */ do { if (!isdigit(value[0])) { return "A character range must be formed nnn " "or nnn-nnn or nnn,nnn-nnn..."; } r = malloc(sizeof(RANGE)); if (!r) return "Out of memory"; p = strchr(value, ','); /* p->the part after the next comma */ if (p) { *p = 0; p++; } /* Now see if we have nnn or nnn-nnn */ q = strchr(value, '-'); if (q) { r->first = atoi(value); r->last = atoi(q+1); } else { r->first = r->last = atoi(value); } /* Add the new range to the subset */ r->next = s->range; s->range = r; /* And go to the bit after the comma */ value = p; } while (p); return NULL; } /* ddash = 1 if option started with a double-dash; else 0 */ /* Return NULL if OK, else error string */ char *cnv_set_option(int ddash, char *variable, char *value) { if (!stricmp(variable, "first")) { first = atoi(value); return NULL; } if (!stricmp(variable, "last")) { last = atoi(value); return NULL; } if (!stricmp(variable, "height")) { height = atoi(value); return NULL; } if (!stricmp(variable, "width")) { width = atoi(value); return NULL; } if (!stricmp(variable, "flip")) { return parse_subset(value, &flipx); } if (!stricmp(variable, "bold")) { return parse_subset(value, &bold); } if (!stricmp(variable, "thin")) { return parse_subset(value, &thin); } if (!stricmp(variable, "double")) { return parse_subset(value, &dblheight); } if (!stricmp(variable, "inverse")) { return parse_subset(value, &inverse);} if (!stricmp(variable, "repeat")) { return parse_subset(value, &repeat);} /* if (!stricmp(variable, "scale")) { return parse_subset(value, &scale);} if (!stricmp(variable, "centre")) { return parse_subset(value, ¢re);} if (!stricmp(variable, "center")) { return parse_subset(value, ¢re);} */ if (!stricmp(variable, "strip")) { strip = 1; return NULL; } if (!stricmp(variable, "256")) { first = 0; last = 255; return NULL; } if (!stricmp(variable, "v1")) { vfont1 = 1; return NULL; } if (!stricmp(variable, "v2")) { vfont2 = 1; return NULL; } if (!stricmp(variable, "be") || !stricmp(variable, "big-endian")) { big_endian = 1; return NULL; } if (!stricmp(variable, "le") || !stricmp(variable, "little-endian")) { big_endian = 0; return NULL; } if (!stricmp(variable, "permute")) { strncpy(permfile, value, PATH_MAX - 1); permfile[PATH_MAX - 1] = 0; return NULL; } if (!stricmp(variable, "codepage")) { codepage = psf_find_mapping(value); if (codepage == NULL) return "Code page name not recognised."; return NULL; } if (!stricmp(variable, "setcodepage")) { setcodepage = psf_find_mapping(value); if (setcodepage == NULL) return "Code page name not recognised."; return NULL; } if (strlen(variable) > 2000) variable[2000] = 0; sprintf(helpbuf, "Unknown option: %s\n", variable); return helpbuf; } /* Return help string */ char *cnv_help(void) { sprintf(helpbuf, "Syntax: %s psffile psffile { options }\n\n", cnv_progname); strcat (helpbuf, "Options: \n" " --first=n Start with character n\n" " --last=n Finish with character n\n" " --256 Equivalent to --first=0 --last=255\n" " --v1 Force output to VFONT v1 format\n" " --v2 Force output to VFONT v2 format\n" " --big-endian Output a big-endian file (v1 only).\n" " --little-endian Output a little-endian file (v1 only).\n" " --bold{=range} Make the characters bold\n" /* " --centre{=range} Centre character in cell when changing size\n" */ " --double{=range} Double character height\n" " --flip{=range} Mirror characters horizontally\n" " --inverse{=range} Swap ink/paper cells\n" " --repeat{=range} Repeat edge rows/columns when increasing character size\n" /* " --scale{=range} Scale characters when changing size\n"*/ " --thin{=range} Make the characters thin\n" " --strip Remove any Unicode directory\n" " --permute=f Shuffle characters as described in file f\n" " --codepage=x Extract the specified codepage\n" " --setcodepage=x Replace the Unicode directory with the specified codepage\n" "\n" "Ranges, if present, are formed r,r,r... where each r is either nnn or nnn-nnn\n"); return helpbuf; } /* Load a file describing permutations */ int load_transfile(char *transfile) { int from, to; FILE *fp; char *s, *result; int c; if (!strcmp(transfile, "-")) fp = stdin; else fp = fopen(transfile, "r"); if (!fp) { perror(transfile); return -1; } do { result = fgets(linebuf, 20, fp); if (feof(fp) || result == NULL) break; /* If line was longer than 20 chars, swallow the rest */ s = strchr(linebuf, '\n'); if (!s) { do { c = fgetc(fp); } while (c != EOF && c != '\n'); if (c == EOF) break; } s = strchr(linebuf, ';'); if (s) *s = 0; s = strchr(linebuf, '#'); if (s) *s = 0; if (sscanf(linebuf, "%d,%d", &from, &to) == 2 && to < vfonti.vf_length && to >= 0) { permutation[to] = from; } } while (!feof(fp)); if (!strcmp(transfile, "-")) fclose(fp); return 0; } #define MIN(a,b) (a < b) ? a : b psf_dword sizechar(int ndest, int nsrc, int *width, int *height) { psf_dword srcwidth, srcheight; vfont_get_cellsize(&vfonti, nsrc, &srcwidth, &srcheight); if (within_subset(ndest, &dblheight)) srcheight *= 2; if (within_subset(ndest, &bold)) srcwidth += 1; if (width) *width = srcwidth; if (height) *height = srcheight; /* Bitmap size required */ return ((srcwidth + 7) / 8) * srcheight; } void copychar(int ndest, int nsrc) { /* Rewritten to use psf_get_pixel, psf_set_pixel rather than fumbling around * with the bitmaps. */ int xsrc, ysrc, xdest, ydest; int dx, dy; psf_byte pix, pixl, pixr; psf_dword srcwidth, srcheight; double xscale, yscale; psf_unicode_dirent *ude; int destwidth, destheight; sizechar(ndest, nsrc, &destwidth, &destheight); /* Dispatch structure only has vfd_addr and vfd_nbytes populated. Default * all the other character definitions in the destination file from the * source file. */ vfonto.vf_dispatch[ndest].vfd_up = vfonti.vf_dispatch[nsrc].vfd_up; vfonto.vf_dispatch[ndest].vfd_down = vfonti.vf_dispatch[nsrc].vfd_down; vfonto.vf_dispatch[ndest].vfd_left = vfonti.vf_dispatch[nsrc].vfd_left; vfonto.vf_dispatch[ndest].vfd_right = vfonti.vf_dispatch[nsrc].vfd_right; vfonto.vf_dispatch[ndest].vfd_width = vfonti.vf_dispatch[nsrc].vfd_width; /* 1. Copy the source bitmap to the destination bitmap. * dblheight makes the source pretend to be twice as high as it is */ xscale = 1.0; yscale = 1.0; dx = 0; dy = 0; vfont_get_cellsize(&vfonti, nsrc, &srcwidth, &srcheight); if (within_subset(ndest, &dblheight)) { srcheight *= 2; vfonto.vf_dispatch[ndest].vfd_up *= 2; vfonto.vf_dispatch[ndest].vfd_down *= 2; } if (within_subset(ndest, &bold)) { vfonto.vf_dispatch[ndest].vfd_right++; vfonto.vf_dispatch[ndest].vfd_width++; } if (within_subset(ndest, &scale)) { xscale = ((double)srcwidth) / ((double)destwidth); yscale = ((double)srcheight) / ((double)destheight); } if (within_subset(ndest, ¢re)) { dx = ((int)srcwidth - (int)srcwidth) / 2; dy = ((int)srcheight - (int)destheight) / 2; } for (ydest = 0; ydest < destheight; ydest++) { for (xdest = 0; xdest < destwidth; xdest++) { if (within_subset(ndest, &scale)) { xsrc = (int)((xscale * xdest)); ysrc = (int)((yscale * ydest)); } else { xsrc = xdest + dx; ysrc = ydest + dy; } if (within_subset(ndest, &dblheight)) ysrc /= 2; if (within_subset(ndest, &flipx)) xsrc = (srcwidth - 1) - xsrc; /* If we are repeating the last row and column out to the edge, then translate * out-of-range coords to the edge. */ if (within_subset(ndest, &repeat)) { if (xsrc < 0) xsrc = 0; if (xsrc >= srcwidth) xsrc = srcwidth - 1; if (ysrc < 0) ysrc = 0; if (ysrc >= srcheight) ysrc = srcheight - 1; } /* If still out of range, return blank pixel */ if (ysrc < 0 || ysrc >= srcheight || xsrc < 0 || xsrc >= srcwidth) { pix = 0; } else vfont_get_pixel(&vfonti, nsrc, xsrc, ysrc, &pix); vfont_set_pixel(&vfonto, ndest, xdest, ydest, pix); /* Now apply special effects: Bold. Bold is done by * setting this pixel to black if the source pixel one * to the left is black. */ if (within_subset(ndest, &bold)) { int bxsrc = xsrc - 1; if (bxsrc < 0 && within_subset(ndest, &repeat)) bxsrc = 0; if (bxsrc >= 0) { vfont_get_pixel(&vfonti, nsrc, bxsrc, ysrc, &pixl); if (pixl) vfont_set_pixel(&vfonto, ndest, xdest, ydest, pixl); } } /* Thin. Thin checks three pixels: If this pixel is * black, the one to the left is white, and the one * to the right is black, then make this pixel white */ if (within_subset(ndest, &thin)) { int tlsrc = xsrc - 1; int trsrc = xsrc + 1; if (within_subset(ndest, &repeat)) { if (tlsrc < 0) tlsrc = 0; if (trsrc >= srcwidth) trsrc = srcwidth - 1; } if (tlsrc >= 0) vfont_get_pixel(&vfonti, nsrc, tlsrc, ysrc, &pixl); else pixl = 0; if (trsrc < srcwidth) vfont_get_pixel(&vfonti, nsrc, trsrc, ysrc, &pixr); else pixr = 0; if (pixl == 0 && pix != 0 && pixr != 0) { vfont_set_pixel(&vfonto, ndest, xdest, ydest, 0); } } if (within_subset(ndest, &inverse)) { vfont_get_pixel(&vfonto, ndest, xdest, ydest, &pix); vfont_set_pixel(&vfonto, ndest, xdest, ydest, !pix); } } } if (setcodepage) { if (ndest < setcodepage->psfm_count) vfont_unicode_addmap(&vfonto, ndest, setcodepage, ndest); } else if (ucs) for (ude = vfonti.vf_dir.psf_dirents_used[nsrc]; ude != NULL; ude = ude->psfu_next) { vfont_unicode_add(&vfonto, ndest, ude->psfu_token); } } char *cnv_execute(FILE *infile, FILE *outfile) { int rv; psf_dword glyph, sch, dch, f, l, destsize; psf_byte *src, *dest; if (strip && setcodepage) { return "Cannot have both --strip and --setcodepage options"; } if (big_endian > 0 && vfont2) { return "A version 2 vfont cannot be big-endian"; } vfont_new(&vfonti); vfont_new(&vfonto); vfont_new(&vfontp); rv = vfont_read(&vfonti, infile); if (rv != PSF_E_OK) return psf_error_string(rv); if (permfile[0]) { permutation = calloc(vfonti.vf_length, sizeof(psf_dword)); if (permutation == NULL) { return "No memory for buffer required by --permute option"; } for (sch = 0; sch < vfonti.vf_length; sch++) permutation[sch] = sch; if (load_transfile(permfile)) return "Could not load permutation table file."; rv = vfont_create(&vfontp, vfonti.vf_length, vfonti.vf_size, 0); if (rv) return psf_error_string(rv); } if (codepage && !vfont_is_unicode(&vfonti)) { return "Cannot extract by codepage; source file has no Unicode table"; } f = (first >= 0) ? first : 0; l = (last >= 0) ? last : (vfonti.vf_length - 1); if (codepage && l >= 256) l = 255; if (l >= vfonti.vf_length) l = vfonti.vf_length; ucs = 0; if (codepage || setcodepage || vfont_is_unicode(&vfonti)) ucs = 1; if (strip) ucs = 0; /* Do permutation as an initial pass */ if (permutation) { psf_dword required; /* Firstly: Get the size of all characters */ for (required = 0, dch = 0; dch < vfonti.vf_length; dch++) { sch = permutation[dch]; if (sch > vfonti.vf_length) continue; vfontp.vf_dispatch[dch].vfd_addr = required; vfontp.vf_dispatch[dch].vfd_nbytes = vfonti.vf_dispatch[sch].vfd_nbytes; required += vfonti.vf_dispatch[sch].vfd_nbytes; } rv = vfont_realloc(&vfontp, required); if (rv) return psf_error_string(rv); /* Secondly: Populate vfontp with the permuted characters */ for (dch = 0; dch < vfonti.vf_length; dch++) { VFONT_DISPATCH *srcd, *destd; sch = permutation[dch]; if (sch > vfonti.vf_length) continue; srcd = &vfonti.vf_dispatch[sch]; destd = &vfontp.vf_dispatch[dch]; /* Copy all fields except vfd_addr and vfd_nbytes */ destd->vfd_up = srcd->vfd_up; destd->vfd_down = srcd->vfd_down; destd->vfd_left = srcd->vfd_left; destd->vfd_right = srcd->vfd_right; destd->vfd_width = srcd->vfd_width; src = &vfonti.vf_bitmap[srcd->vfd_addr]; dest = &vfontp.vf_bitmap[destd->vfd_addr]; memcpy(dest, src, destd->vfd_nbytes); } /* Replace vfonti with vfontp */ vfont_delete(&vfonti); memcpy(&vfonti, &vfontp, sizeof(VFONT)); memset(&vfontp, 0, sizeof(VFONT)); } /* Calculate destination size required */ destsize = 0; for (sch = f; sch <= l; sch++) { if (codepage) { if (sch < 256 && !vfont_unicode_lookupmap(&vfonti, codepage, sch, &glyph, NULL)) { destsize += sizechar(sch - f, glyph, NULL, NULL); } else { destsize += sizechar(sch - f, sch, NULL, NULL); } } else { destsize += sizechar(sch - f, sch, NULL, NULL); } } /* Create the destination font */ rv = vfont_create(&vfonto, (l - f + 1), destsize, ucs); if (rv) return psf_error_string(rv); destsize = 0; /* And now copy the characters one by one into it */ for (sch = f; sch <= l; sch++) { if (codepage) { if (sch < 256 && !vfont_unicode_lookupmap(&vfonti, codepage, sch, &glyph, NULL)) { vfonto.vf_dispatch[sch - f].vfd_addr = destsize; vfonto.vf_dispatch[sch - f].vfd_nbytes = sizechar(sch - f, glyph, NULL, NULL); copychar(sch - f, glyph); destsize += vfonto.vf_dispatch[sch - f].vfd_nbytes; } else { if (sch < 256 && !psf_unicode_banned(codepage->psfm_tokens[sch][0])) fprintf(stderr, "Warning: U+%04lx not found in font\n", codepage->psfm_tokens[sch][0]); vfonto.vf_dispatch[sch - f].vfd_addr = destsize; vfonto.vf_dispatch[sch - f].vfd_nbytes = sizechar(sch - f, sch, NULL, NULL); dest = &vfonto.vf_bitmap[destsize]; memset(dest, 0, vfonto.vf_dispatch[sch - f].vfd_nbytes); destsize += vfonto.vf_dispatch[sch - f].vfd_nbytes; } } else { vfonto.vf_dispatch[sch - f].vfd_addr = destsize; vfonto.vf_dispatch[sch - f].vfd_nbytes = sizechar(sch - f, sch, NULL, NULL); copychar(sch - f, sch); destsize += vfonto.vf_dispatch[sch - f].vfd_nbytes; } } if (vfont1) { vfont_force_v1(&vfonto); if (big_endian >= 0) vfonto.vf_be = big_endian; } if (vfont2) { vfont_force_v2(&vfonto); } rv = vfont_write(&vfonto, outfile); vfont_delete(&vfonti); vfont_delete(&vfonto); if (permutation) free(permutation); if (rv) return psf_error_string(rv); return NULL; }