/* * fileio.c: Read font-bitmaps, write GIF-, PPM-, ASCII- & VTX-files * * $Id: fileio.c,v 1.6 1997/10/21 00:22:27 mb Exp mb $ * * Copyright (c) 1995-97 Martin Buck * Read COPYING for more information * */ #include #include #include #include #include #include #include #ifdef PNG_SUPPORT #include #include #endif #include "vtx_assert.h" #include "safe_malloc.h" #include "vtxdecode.h" #ifdef GIF_SUPPORT #include "gifwrite.h" #endif #include "vtxtools.h" #include "misc.h" #include "fileio.h" #define VTXFONT_EXT ".vtxfont" typedef enum { FP_ARG, FP_ENV, FP_DEFAULT } fp_src_t; char *vtx_fontpath; static const int vtx_img_red[8] = { 0, 255, 0, 255, 0, 255, 0, 255 }; static const int vtx_img_green[8] = { 0, 0, 255, 255, 0, 0, 255, 255 }; static const int vtx_img_blue[8] = { 0, 0, 0, 0, 255, 255, 255, 255 }; static const vtxpage_t *vtxpage; static const vtxbmfont_t *vtxfont; static int reveal; /* Try to load font from given directory */ static vtxbmfont_t * read_font_dir(unsigned int xsize, unsigned int ysize, const char *fontdir) { DIR *dir; FILE *fontfile; struct dirent *de; struct stat st; char buf[11], *filename; int len; vtxbmfont_t *vtxbmfont = NULL; if (!(dir = opendir(fontdir))) return NULL; while (!vtxbmfont && (de = readdir(dir))) { filename = smalloc(strlen(fontdir) + 1 + strlen(de->d_name) + 1); sprintf(filename, "%s/%s", fontdir, de->d_name); if (!stat(filename, &st) && (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) && (len = strlen(filename)) >= sizeof(VTXFONT_EXT) - 1 && !strcmp(filename + len - sizeof(VTXFONT_EXT) + 1, VTXFONT_EXT) && (fontfile = fopen(filename ,"r")) && fread(buf, sizeof(buf), 1, fontfile) == 1 && !memcmp("VTXFONTV1", buf, 9) && xsize == buf[9] && ysize == buf[10]) { int bmlen; vtxbmfont = smalloc(sizeof(vtxbmfont_t)); vtxbmfont->xsize = xsize; vtxbmfont->ysize = ysize; vtxbmfont->bpr = ((xsize - 1) / BITS_PER_BYTE) + 1; vtxbmfont->bpc = vtxbmfont->bpr * ysize; bmlen = 256 * ysize * vtxbmfont->bpr * sizeof(byte_t); vtxbmfont->bitmap = smalloc(bmlen); if (fread(vtxbmfont->bitmap, bmlen, 1, fontfile) != 1) { free(vtxbmfont->bitmap); free(vtxbmfont); vtxbmfont = NULL; } if (fontfile) fclose(fontfile); } free(filename); } closedir(dir); return vtxbmfont; } /* Loop through all directories in path given by -P, $VTX_FONTPATH and VTX_LIBDIR and * try to load font. */ vtxbmfont_t * read_font(unsigned int xsize, unsigned int ysize) { fp_src_t fp_src; for (fp_src = FP_ARG; fp_src <= FP_DEFAULT; fp_src++) { char *path = NULL, *dir; vtxbmfont_t *tmpfont; switch (fp_src) { case FP_ARG: path = vtx_fontpath; break; case FP_ENV: path = getenv("VTX_FONTPATH"); break; case FP_DEFAULT: path = VTX_LIBDIR; break; } if (path) { path = sstrdup(path); dir = strtok(path, ":"); while (dir) { if ((tmpfont = read_font_dir(xsize, ysize, dir))) return tmpfont; dir = strtok(NULL, ":"); } free(path); } } return NULL; } /* This function converts a vtx-page to a virtual bitmap (to save memory the * whole bitmap actually is never created) and returns the colors of single pixels */ static unsigned int vtx_getpixel(unsigned int x, unsigned int y) { /* There's no reason to define SLOW_GETPIXEL -- it's just here so that I still can understand * what's going on if I look at this function in a few weeks :-) * The fast version is a carefully hand-optimized version which avoids such complex * calculations as multiplications and divisions because they are obnoxiously slow on * my i386 :-( This makes this function more than twice as fast on my box which is really * noticable, because it's called very often when writing GIFs/PPMs. * BEWARE: The fast version only works when reading the bitmap from left to right starting * at column 0. */ #ifndef SLOW_GETPIXEL static unsigned int lastx = 999999; static unsigned int offsx, offsy, chroffs, attr, bmoffs, sepoffs, seporig, bitmap, tmp_offs; /* GETPIXEL_calcbmoffs and GETPIXEL_readbitmap should be inline-functions, but for * portability's sake I used macros. Normal functions wouldn't be a good idea because we're * concerned about speed here. * GETPIXEL_calcbmoffs calculates the offset of the current line of the current character in * the font-bitmap, taking the VTX_DOUBLE-attribute into account; GETPIXEL_readbitmap reads a * single byte from the font-bitmap, taking the VTX_HIDDEN-attribute into account. */ #define GETPIXEL_calcbmoffs \ if (!(attr & VTX_DOUBLE)) { \ tmp_offs = offsy * vtxfont->bpr; \ bmoffs = vtxfont->bpc * vtxpage->chr[chroffs] + tmp_offs; \ sepoffs = seporig + tmp_offs; \ } else if (attr & VTX_DOUBLE1) { \ tmp_offs = (offsy / 2) * vtxfont->bpr; \ bmoffs = \ vtxfont->bpc * (vtxpage->chr[chroffs >= VTX_PAGESIZE - 40 ? chroffs : chroffs + 40]) \ + tmp_offs; \ sepoffs = seporig + tmp_offs; \ } else { \ tmp_offs = ((offsy + vtxfont->ysize) / 2) * vtxfont->bpr; \ bmoffs = vtxfont->bpc * vtxpage->chr[chroffs] + tmp_offs; \ sepoffs = seporig + tmp_offs; \ } #define GETPIXEL_readbitmap \ if ((attr & VTX_HIDDEN) && !reveal) { \ bitmap = 0; \ } else { \ bitmap = vtxfont->bitmap[bmoffs]; \ if (attr & VTX_GRSEP) { \ bitmap &= ~vtxfont->bitmap[sepoffs]; \ } \ } if (++lastx != x) { /* This gets executed whenver we start reading a new line */ lastx = x; offsx = x % vtxfont->xsize; assert(offsx == 0); offsy = y % vtxfont->ysize; chroffs = (x / vtxfont->xsize) + (y / vtxfont->ysize) * 40; attr = vtxpage->attrib[chroffs]; seporig = 255 * vtxfont->bpc; GETPIXEL_calcbmoffs; GETPIXEL_readbitmap; } else { if (++offsx >= vtxfont->xsize) { /* This gets executed when we reach a new character in a given line */ offsx = 0; chroffs++; attr = vtxpage->attrib[chroffs]; GETPIXEL_calcbmoffs; GETPIXEL_readbitmap; } else if (offsx % BITS_PER_BYTE == 0) { /* This gets executed every 8 bits when we have shifted out a whole byte from the font- * bitmap */ bmoffs++; sepoffs++; GETPIXEL_readbitmap; } } /* Return the fore- or background color depending on whether or not the current bit in * the font-bitmap is set */ bitmap <<= 1; if (bitmap & (0x80 << 1)) { return attr & VTX_COLMASK; } else { return (attr >> 3) & VTX_COLMASK; } #undef GETPIXEL_calcbmoffs #undef GETPIXEL_readbitmap #else /* This is the old version. It's obsolete and incomplete: it ignores VTX_HIDDEN and VTX_DOUBLE */ int offsx, offsy, chroffs, chr, attr, bmoffs; offsx = x % vtxfont->xsize; offsy = y % vtxfont->ysize; chroffs = (x / vtxfont->xsize) + (y / vtxfont->ysize) * 40; attr = vtxpage->attrib[chroffs]; if (!(attr & VTX_DOUBLE)) { chr = vtxpage->chr[chroffs]; bmoffs = vtxfont->bpc * chr + offsy * vtxfont->bpr + offsx / BITS_PER_BYTE; } else if (attr & VTX_DOUBLE1) { chr = vtxpage->chr[chroffs >= VTX_PAGESIZE - 40 ? chroffs : chroffs + 40]; bmoffs = vtxfont->bpc * chr + (offsy / 2) * vtxfont->bpr + offsx / BITS_PER_BYTE; } else { chr = vtxpage->chr[chroffs]; bmoffs = vtxfont->bpc * chr + ((offsy + vtxfont->ysize) / 2) * vtxfont->bpr + offsx / BITS_PER_BYTE; } if ((vtxfont->bitmap[bmoffs] & (0x80 >> (offsx % BITS_PER_BYTE))) && (!(attr & VTX_HIDDEN) || reveal)) { return (attr & VTX_COLMASK); } else { return ((attr >> 3) & VTX_COLMASK); } #endif } #ifdef GIF_SUPPORT void export_gif(FILE *file, const vtxpage_t *page, const vtxbmfont_t *font, int interlace, int show_hidden) { vtxpage = page; vtxfont = font; reveal = show_hidden; GIFEncode(file, font->xsize * 40, font->ysize * 24, interlace, 0, -1, 3, vtx_img_red, vtx_img_green, vtx_img_blue, vtx_getpixel); } #endif void export_ppm(FILE *file, const vtxpage_t *page, const vtxbmfont_t *font, int show_hidden) { unsigned int width, height, row, col, pixel, rowpos; unsigned char *rowdata; vtxpage = page; vtxfont = font; reveal = show_hidden; width = font->xsize * 40; height = font->ysize * 24; fprintf(file, "P6\n%d %d\n255\n", width, height); rowdata = smalloc(width * 3 * sizeof(char)); for (row = 0; row < height; row++) { rowpos = 0; for (col = 0; col < width; col++) { pixel = vtx_getpixel(col, row); rowdata[rowpos++] = vtx_img_red[pixel]; rowdata[rowpos++] = vtx_img_green[pixel]; rowdata[rowpos++] = vtx_img_blue[pixel]; } fwrite(rowdata, rowpos, 1, file); } free(rowdata); } #ifdef PNG_SUPPORT static char **vtx_png_msg_list = NULL; static void vtx_png_add_msg(const char *msg1, const char *msg2) { static int next_entry; if (!vtx_png_msg_list) { next_entry = 0; } vtx_png_msg_list = srealloc(vtx_png_msg_list, (next_entry + 2) * sizeof(*vtx_png_msg_list)); vtx_png_msg_list[next_entry] = sstrdup(msg1); vtx_png_msg_list[next_entry] = sstrapp(vtx_png_msg_list[next_entry], msg2); vtx_png_msg_list[++next_entry] = NULL; } static void vtx_png_warning(png_structp png_ptr, png_const_charp warning_msg) { vtx_png_add_msg("PNG warning: ", warning_msg); } static void vtx_png_error(png_structp png_ptr, png_const_charp error_msg) { if (!strcasecmp(error_msg, "write error")) { vtx_png_add_msg("PNG write error: ", strerror(errno)); } else { vtx_png_add_msg("PNG error: ", error_msg); } longjmp(png_ptr->jmpbuf, 1); } int export_png(FILE *file, const vtxpage_t *page, const vtxbmfont_t *font, int interlace, int show_hidden, char ***msg_list) { unsigned int color, width, height, row, column; volatile int error = 0; png_structp png_ptr = NULL; png_infop info_ptr = NULL; volatile png_bytep pic_data = NULL, row_data = NULL; volatile png_bytepp row_ptr = NULL; vtxpage = page; vtxfont = font; reveal = show_hidden; /* Remove old messages */ if (vtx_png_msg_list) { char **msg; for (msg = vtx_png_msg_list; *msg; msg++) { free(*msg); } vtx_png_msg_list = NULL; } if (!(png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, vtx_png_error, vtx_png_warning)) || !(info_ptr = png_create_info_struct(png_ptr))) { if (!vtx_png_msg_list) { vtx_png_add_msg("PNG error: ", "Initialization failed"); } error = -1; goto exit; } info_ptr->palette = NULL; if (setjmp(png_ptr->jmpbuf)) { error = -1; goto exit; } png_init_io(png_ptr, file); width = info_ptr->width = font->xsize * 40; height = info_ptr->height = font->ysize * 24; info_ptr->bit_depth = 4; info_ptr->color_type = PNG_COLOR_TYPE_PALETTE; info_ptr->interlace_type = !!interlace; info_ptr->valid |= PNG_INFO_PLTE; info_ptr->palette = smalloc(8 * sizeof(png_color)); info_ptr->num_palette = 8; for (color = 0; color < 8; color++) { info_ptr->palette[color].red = vtx_img_red[color]; info_ptr->palette[color].green = vtx_img_green[color]; info_ptr->palette[color].blue = vtx_img_blue[color]; } png_write_info(png_ptr, info_ptr); if (interlace) { int offset = 0; row_ptr = smalloc(sizeof(png_bytep) * height); pic_data = smalloc(sizeof(png_byte) * height * width / 2 + (width & 1)); for (row = 0; row < height; row++) { row_ptr[row] = &pic_data[offset]; for (column = 0; column < (width & ~1); column += 2) { pic_data[offset] = vtx_getpixel(column, row) << 4; pic_data[offset++] |= vtx_getpixel(column + 1, row); } if (width & 1) { pic_data[offset++] = vtx_getpixel(column, row) << 4; } } png_write_image(png_ptr, row_ptr); } else { row_data = smalloc(sizeof(png_byte) * width); png_set_packing(png_ptr); for (row = 0; row < height; row++) { for (column = 0; column < width; column++) { row_data[column] = vtx_getpixel(column, row); } png_write_row(png_ptr, row_data); } } png_write_end(png_ptr, info_ptr); exit: if (pic_data) { free(pic_data); } if (row_ptr) { free(row_ptr); } if (row_data) { free(row_data); } if (info_ptr && info_ptr->palette) { free(info_ptr->palette); } png_destroy_write_struct(&png_ptr, &info_ptr); *msg_list = vtx_png_msg_list; return error; } #endif void export_ascii(FILE *file, const vtxpage_t *page, int show_hidden) { int pos; for (pos = 0; pos < VTX_PAGESIZE; pos++) { if (!(page->attrib[pos] & VTX_HIDDEN) || show_hidden) { fputc(vtx2iso_table[page->chr[pos]], file); } else { fputc(' ', file); } if (pos % 40 == 39) fputc('\n', file); } } void save_vtx(FILE *file, const byte_t *buffer, const vtx_pageinfo_t *info, int virtual) { fputs("VTXV4", file); fputc(info->pagenum & 0xff, file); fputc(info->pagenum / 0x100, file); fputc(info->hour & 0xff, file); fputc(info->minute & 0xff, file); fputc(info->charset & 0xff, file); fputc(info->delete << 7 | info->headline << 6 | info->subtitle << 5 | info->supp_header << 4 | info->update << 3 | info->inter_seq << 2 | info->dis_disp << 1 | info->serial << 0, file); fputc(info->notfound << 7 | info->pblf << 6 | info->hamming << 5 | (!!virtual) << 4 | 0 << 3, file); fwrite(buffer, 1, virtual ? VTX_VIRTUALSIZE : VTX_PAGESIZE, file); } int load_vtx(FILE *file, byte_t *buffer, vtx_pageinfo_t *info, int *virtual) { byte_t tmp_buffer[VTX_VIRTUALSIZE]; vtx_pageinfo_t tmp_inf; unsigned char tmpstr[256]; struct stat st; int pos, tmpbits, is_virtual = FALSE, is_sevenbit = FALSE; memset(tmp_buffer, ' ', sizeof(tmp_buffer)); if (fscanf(file, "VTXV%c", tmpstr) != 1) { if (fstat(fileno(file), &st) < 0) { return LOADERR; /* The stupid INtv format doesn't use a signature, so we have to use the file-length instead */ } else if (st.st_size != 1008) { return LOADEMAGIC; } memset(&tmp_inf, 0, sizeof(tmp_inf)); /* Read ITV-file */ rewind(file); for (pos = 0; pos <= 23; pos++) { fseek(file, 2, SEEK_CUR); fread(tmp_buffer + pos * 40, 40, 1, file); } /* The first 8 bytes in the INtv-format usually contain garbage (or data I don't understand) */ memset(tmp_buffer, vtx_mkparity(' '), 8 * sizeof(byte_t)); for (pos = 0; pos <= 2; pos++) { tmpstr[pos] = tmp_buffer[8 + pos]; vtx_chkparity(&tmpstr[pos]); } tmpstr[3] = '\0'; sscanf(tmpstr, "%3x", &tmp_inf.pagenum); if (!vtx_chkpgnum(tmp_inf.pagenum, TRUE)) { tmp_inf.pagenum = 0; } if (virtual) { *virtual = FALSE; } } else { if (tmpstr[0] != '2' && tmpstr[0] != '3' && tmpstr[0] != '4') { return LOADEVERSION; } tmp_inf.pagenum = fgetc(file) + 0x100 * fgetc(file); /* Read VTX-file */ tmp_inf.hour = fgetc(file); tmp_inf.minute = fgetc(file); tmp_inf.charset = fgetc(file); tmpbits = fgetc(file); tmp_inf.delete = !!(tmpbits & 0x80); tmp_inf.headline = !!(tmpbits & 0x40); tmp_inf.subtitle = !!(tmpbits & 0x20); tmp_inf.supp_header = !!(tmpbits & 0x10); tmp_inf.update = !!(tmpbits & 8); tmp_inf.inter_seq = !!(tmpbits & 4); tmp_inf.dis_disp = !!(tmpbits & 2); tmp_inf.serial = (tmpbits & 1); tmpbits = fgetc(file); tmp_inf.notfound = !!(tmpbits & 0x80); tmp_inf.pblf = !!(tmpbits & 0x40); tmp_inf.hamming = !!(tmpbits & 0x20); if (tmpstr[0] == '3') { is_virtual = TRUE; } else if (tmpstr[0] == '4') { is_virtual = !!(tmpbits & 0x10); is_sevenbit = !!(tmpbits & 8); } fread(tmp_buffer, is_virtual ? VTX_VIRTUALSIZE : VTX_PAGESIZE, 1, file); if (virtual) { *virtual = is_virtual; } } if (feof(file)) { return LOADECORRUPT; } if (ferror(file)) { return LOADERR; } if (buffer) { /* If file was saved in seven-bit mode, fake parity bit. * FIXME: Should we add parity to the virtual part too? */ if (is_sevenbit) { for (pos = 0; pos < VTX_PAGESIZE; pos++) { tmp_buffer[pos] = vtx_mkparity(tmp_buffer[pos]); } } memcpy(buffer, tmp_buffer, is_virtual ? VTX_VIRTUALSIZE : VTX_PAGESIZE); memset(buffer, vtx_mkparity(7), 8); } if (info) { *info = tmp_inf; } return LOADOK; }