/*************************************************************************** rendfont.c Rendering system font management. Copyright Nicola Salmoria and the MAME Team. Visit http://mamedev.org for licensing and usage restrictions. ***************************************************************************/ #include "rendfont.h" #include "rendutil.h" #include "zlib.h" #include "uismall.fh" /*************************************************************************** CONSTANTS ***************************************************************************/ #define FONT_FORMAT_TEXT 1 #define FONT_FORMAT_CACHED 2 #define CACHED_CHAR_SIZE 12 #define CACHED_HEADER_SIZE 16 #define CACHED_BDF_HASH_SIZE 1024 /*************************************************************************** TYPE DEFINITIONS ***************************************************************************/ /* a render_font contains information about a single character in a font */ typedef struct _render_font_char render_font_char; struct _render_font_char { INT32 width; /* width from this character to the next */ INT32 xoffs, yoffs; /* X and Y offset from baseline to top,left of bitmap */ INT32 bmwidth, bmheight; /* width and height of bitmap */ const char * rawdata; /* pointer to the raw data for this one */ bitmap_t * bitmap; /* pointer to the bitmap containing the raw data */ render_texture * texture; /* pointer to a texture for rendering and sizing */ }; /* a render_font contains information about a font */ /*typedef struct _render_font render_font; -- defined in rendfont.h */ struct _render_font { int format; /* format of font data */ int height; /* height of the font, from ascent to descent */ int yoffs; /* y offset from baseline to descent */ float scale; /* 1 / height precomputed */ render_font_char * chars[256]; /* array of character subtables */ const char * rawdata; /* pointer to the raw data for the font */ UINT64 rawsize; /* size of the raw font data */ }; /*************************************************************************** FUNCTION PROTOTYPES ***************************************************************************/ static void render_font_char_expand(render_font *font, render_font_char *ch); static int render_font_load_cached_bdf(render_font *font, const char *filename); static int render_font_load_bdf(render_font *font); static int render_font_load_cached(render_font *font, mame_file *file, UINT32 hash); static int render_font_save_cached(render_font *font, const char *filename, UINT32 hash); /*************************************************************************** INLINE FUNCTIONS ***************************************************************************/ /*------------------------------------------------- next_line - return a pointer to the start of the next line -------------------------------------------------*/ INLINE const char *next_line(const char *ptr) { /* scan forward until we hit the end or a carriage return */ while (*ptr != 13 && *ptr != 10 && *ptr != 0) ptr++; /* if we hit the end, return NULL */ if (*ptr == 0) return NULL; /* eat the trailing linefeed if present */ if (*++ptr == 10) ptr++; return ptr; } /*------------------------------------------------- get_char - return a pointer to a character in a font, expanding if necessary -------------------------------------------------*/ INLINE render_font_char *get_char(render_font *font, unicode_char chnum) { static render_font_char dummy_char; render_font_char *chtable; render_font_char *ch; /* grab the table; if none, return the dummy character */ chtable = font->chars[chnum / 256]; if (chtable == NULL) return &dummy_char; /* if the character isn't generated yet, do it now */ ch = &chtable[chnum % 256]; if (ch->bitmap == NULL) render_font_char_expand(font, ch); /* return the resulting character */ return ch; } /*************************************************************************** RENDER FONTS ***************************************************************************/ /*------------------------------------------------- render_font_alloc - allocate a new font and load the BDF file -------------------------------------------------*/ render_font *render_font_alloc(const char *filename) { file_error filerr; mame_file *ramfile; render_font *font; /* allocate and clear memory */ font = alloc_clear_or_die(render_font); /* attempt to load the cached version of the font first */ if (filename != NULL && render_font_load_cached_bdf(font, filename) == 0) return font; /* if we failed, clean up and realloc */ render_font_free(font); font = alloc_clear_or_die(render_font); /* load the raw data instead */ filerr = mame_fopen_ram(font_uismall, sizeof(font_uismall), OPEN_FLAG_READ, &ramfile); if (filerr == FILERR_NONE) { render_font_load_cached(font, ramfile, 0); mame_fclose(ramfile); } return font; } /*------------------------------------------------- render_font_free - free an allocated font and all of its owned subobjects -------------------------------------------------*/ void render_font_free(render_font *font) { int tablenum; /* free all the subtables */ for (tablenum = 0; tablenum < 256; tablenum++) if (font->chars[tablenum] != NULL) { int charnum; /* loop over characters */ for (charnum = 0; charnum < 256; charnum++) { render_font_char *ch = &font->chars[tablenum][charnum]; if (ch->texture != NULL) render_texture_free(ch->texture); if (ch->bitmap != NULL) bitmap_free(ch->bitmap); } /* free the subtable itself */ free(font->chars[tablenum]); } /* free the raw data and the size itself */ if (font->rawdata != NULL) free((void *)font->rawdata); free(font); } /*------------------------------------------------- render_font_char_expand - expand the raw data for a character into a bitmap -------------------------------------------------*/ static void render_font_char_expand(render_font *font, render_font_char *ch) { const char *ptr = ch->rawdata; UINT8 accum = 0, accumbit = 7; int x, y; /* punt if nothing there */ if (ch->bmwidth == 0 || ch->bmheight == 0 || ch->rawdata == NULL) return; /* allocate a new bitmap of the size we need */ ch->bitmap = bitmap_alloc(ch->bmwidth, font->height, BITMAP_FORMAT_ARGB32); bitmap_fill(ch->bitmap, NULL, 0); /* extract the data */ for (y = 0; y < ch->bmheight; y++) { int desty = y + font->height + font->yoffs - ch->yoffs - ch->bmheight; UINT32 *dest = (desty >= 0 && desty < font->height) ? BITMAP_ADDR32(ch->bitmap, desty, 0) : NULL; /* text format */ if (font->format == FONT_FORMAT_TEXT) { /* loop over bytes */ for (x = 0; x < ch->bmwidth; x += 4) { int bits = -1; /* scan for the next hex digit */ while (*ptr != 13 && bits == -1) { if (*ptr >= '0' && *ptr <= '9') bits = *ptr++ - '0'; else if (*ptr >= 'A' && *ptr <= 'F') bits = *ptr++ - 'A' + 10; else if (*ptr >= 'a' && *ptr <= 'f') bits = *ptr++ - 'a' + 10; else ptr++; } /* expand the four bits */ if (dest != NULL) { *dest++ = (bits & 8) ? MAKE_ARGB(0xff,0xff,0xff,0xff) : MAKE_ARGB(0x00,0xff,0xff,0xff); *dest++ = (bits & 4) ? MAKE_ARGB(0xff,0xff,0xff,0xff) : MAKE_ARGB(0x00,0xff,0xff,0xff); *dest++ = (bits & 2) ? MAKE_ARGB(0xff,0xff,0xff,0xff) : MAKE_ARGB(0x00,0xff,0xff,0xff); *dest++ = (bits & 1) ? MAKE_ARGB(0xff,0xff,0xff,0xff) : MAKE_ARGB(0x00,0xff,0xff,0xff); } } /* advance to the next line */ ptr = next_line(ptr); } /* cached format */ else if (font->format == FONT_FORMAT_CACHED) { for (x = 0; x < ch->bmwidth; x++) { if (accumbit == 7) accum = *ptr++; if (dest != NULL) *dest++ = (accum & (1 << accumbit)) ? MAKE_ARGB(0xff,0xff,0xff,0xff) : MAKE_ARGB(0x00,0xff,0xff,0xff); accumbit = (accumbit - 1) & 7; } } } /* wrap a texture around the bitmap */ ch->texture = render_texture_alloc(render_texture_hq_scale, NULL); render_texture_set_bitmap(ch->texture, ch->bitmap, NULL, TEXFORMAT_ARGB32, NULL); } /*------------------------------------------------- render_font_get_pixel_height - return the height of the font in pixels -------------------------------------------------*/ INT32 render_font_get_pixel_height(render_font *font) { return font->height; } /*------------------------------------------------- render_font_get_char_texture_and_bounds - return the texture for a character and compute the bounds of the final bitmap -------------------------------------------------*/ render_texture *render_font_get_char_texture_and_bounds(render_font *font, float height, float aspect, unicode_char chnum, render_bounds *bounds) { render_font_char *ch = get_char(font, chnum); float scale = font->scale * height; /* on entry, assume x0,y0 are the top,left coordinate of the cell and add */ /* the character bounding box to that position */ bounds->x0 += (float)ch->xoffs * scale * aspect; /* compute x1,y1 from there based on the bitmap size */ bounds->x1 = bounds->x0 + (float)ch->bmwidth * scale * aspect; bounds->y1 = bounds->y0 + (float)font->height * scale; /* return the texture */ return ch->texture; } /*------------------------------------------------- render_font_draw_string_to_bitmap - draw a string to a bitmap -------------------------------------------------*/ void render_font_get_scaled_bitmap_and_bounds(render_font *font, bitmap_t *dest, float height, float aspect, unicode_char chnum, rectangle *bounds) { render_font_char *ch = get_char(font, chnum); float scale = font->scale * height; INT32 origwidth, origheight; /* on entry, assume x0,y0 are the top,left coordinate of the cell and add */ /* the character bounding box to that position */ bounds->min_x = (float)ch->xoffs * scale * aspect; bounds->min_y = 0; /* compute x1,y1 from there based on the bitmap size */ bounds->max_x = bounds->min_x + (float)ch->bmwidth * scale * aspect; bounds->max_y = bounds->min_y + (float)font->height * scale; /* if the bitmap isn't big enough, bail */ if (dest->width < bounds->max_x - bounds->min_x || dest->height < bounds->max_y - bounds->min_y) return; /* scale the font */ origwidth = dest->width; origheight = dest->height; dest->width = bounds->max_x - bounds->min_x; dest->height = bounds->max_y - bounds->min_y; render_texture_hq_scale(dest, ch->bitmap, NULL, NULL); dest->width = origwidth; dest->height = origheight; } /*------------------------------------------------- render_font_char_width - return the width of a character at the given height -------------------------------------------------*/ float render_font_get_char_width(render_font *font, float height, float aspect, unicode_char ch) { return (float)get_char(font, ch)->width * font->scale * height * aspect; } /*------------------------------------------------- render_font_string_width - return the width of a string at the given height -------------------------------------------------*/ float render_font_get_string_width(render_font *font, float height, float aspect, const char *string) { const unsigned char *ptr; int totwidth = 0; /* loop over the string and accumulate widths */ for (ptr = (const unsigned char *)string; *ptr != 0; ptr++) totwidth += get_char(font, *ptr)->width; /* scale the final result based on height */ return (float)totwidth * font->scale * height * aspect; } /*------------------------------------------------- render_font_get_utf8string_width - return the width of a UTF8-encoded string at the given height -------------------------------------------------*/ float render_font_get_utf8string_width(render_font *font, float height, float aspect, const char *utf8string) { int length = strlen(utf8string); unicode_char uchar; int totwidth = 0; int count = 0; int offset; /* loop over the string and accumulate widths */ for (offset = 0; offset < length; offset += count) { count = uchar_from_utf8(&uchar, utf8string + offset, length - offset); if (count == -1) break; if (uchar < 0x10000) totwidth += get_char(font, uchar)->width; } /* scale the final result based on height */ return (float)totwidth * font->scale * height * aspect; } /*------------------------------------------------- render_font_load_cached_bdf - attempt to load a cached version of the BDF font 'filename'; if that fails, fall back on the regular BDF loader and create a new cached version -------------------------------------------------*/ static int render_font_load_cached_bdf(render_font *font, const char *filename) { file_error filerr; char *cachedname = NULL; char *data = NULL; mame_file *cachefile; mame_file *file; int result = 1; UINT32 bytes; UINT32 hash; /* first try to open the BDF itself */ filerr = mame_fopen(SEARCHPATH_FONT, filename, OPEN_FLAG_READ, &file); if (filerr != FILERR_NONE) return 1; /* determine the file size and allocate memory */ font->rawsize = mame_fsize(file); data = alloc_array_clear_or_die(char, font->rawsize + 1); /* read and hash the first chunk */ bytes = mame_fread(file, data, MIN(CACHED_BDF_HASH_SIZE, font->rawsize)); if (bytes != MIN(CACHED_BDF_HASH_SIZE, font->rawsize)) goto error; hash = crc32(0, (const UINT8*)data, bytes) ^ (UINT32)font->rawsize; /* create the cached filename */ cachedname = mame_strdup(filename); if (cachedname == NULL) goto error; /* change the 'F' to a 'C' on the extension */ cachedname[strlen(cachedname) - 1] -= 3; /* attempt to load a cached version of the font */ filerr = mame_fopen(SEARCHPATH_FONT, cachedname, OPEN_FLAG_READ, &cachefile); if (filerr == FILERR_NONE) { result = render_font_load_cached(font, cachefile, hash); mame_fclose(cachefile); } if (result != 0) { /* if that failed, read the rest of the font and parse it */ if (bytes < font->rawsize) { UINT32 read = mame_fread(file, data + bytes, font->rawsize - bytes); if (read != font->rawsize - bytes) goto error; } /* NULL-terminate the data and attach it to the font */ data[font->rawsize] = 0; font->rawdata = data; /* load the BDF */ result = render_font_load_bdf(font); /* if we loaded okay, create a cached one */ if (result == 0) render_font_save_cached(font, cachedname, hash); } else free(data); /* close the file */ free(cachedname); mame_fclose(file); return result; error: /* close the file */ if (cachedname != NULL) free(cachedname); if (data != NULL) free(data); mame_fclose(file); return 1; } /*------------------------------------------------- render_font_load_bdf - parse and load a BDF font -------------------------------------------------*/ static int render_font_load_bdf(render_font *font) { const char *ptr; int charcount = 0; /* set the format to text */ font->format = FONT_FORMAT_TEXT; /* first find the FONTBOUNDINGBOX tag */ for (ptr = font->rawdata; ptr != NULL; ptr = next_line(ptr)) { int dummy1, dummy2; /* we only care about a tiny few fields */ if (strncmp(ptr, "FONTBOUNDINGBOX ", 16) == 0) { if (sscanf(ptr + 16, "%d %d %d %d", &dummy1, &font->height, &dummy2, &font->yoffs) != 4) return 1; break; } } /* compute the scale factor */ font->scale = 1.0f / (float)font->height; /* now scan for characters */ for ( ; ptr != NULL; ptr = next_line(ptr)) { /* stop at ENDFONT */ if (strncmp(ptr, "ENDFONT", 7) == 0) break; /* once we hit a STARTCHAR, parse until the end */ if (strncmp(ptr, "STARTCHAR ", 10) == 0) { int bmwidth = -1, bmheight = -1, xoffs = -1, yoffs = -1; const char *rawdata = NULL; int charnum = -1; int width = -1; int dummy1; /* scan for interesting per-character tags */ for ( ; ptr != NULL; ptr = next_line(ptr)) { /* ENCODING tells us which character */ if (strncmp(ptr, "ENCODING ", 9) == 0) { if (sscanf(ptr + 9, "%d", &charnum) != 1) return 1; } /* DWIDTH tells us the width to the next character */ else if (strncmp(ptr, "DWIDTH ", 7) == 0) { if (sscanf(ptr + 7, "%d %d", &width, &dummy1) != 2) return 1; } /* BBX tells us the height/width of the bitmap and the offsets */ else if (strncmp(ptr, "BBX ", 4) == 0) { if (sscanf(ptr + 4, "%d %d %d %d", &bmwidth, &bmheight, &xoffs, &yoffs) != 4) return 1; } /* BITMAP is the start of the data */ else if (strncmp(ptr, "BITMAP", 6) == 0) { /* stash the raw pointer and scan for the end of the character */ for (rawdata = ptr = next_line(ptr); ptr != NULL && strncmp(ptr, "ENDCHAR", 7) != 0; ptr = next_line(ptr)) ; break; } } /* if we have everything, allocate a new character */ if (charnum >= 0 && charnum < 65536 && rawdata != NULL && bmwidth >= 0 && bmheight >= 0) { render_font_char *ch; /* if we don't have a subtable yet, make one */ if (font->chars[charnum / 256] == NULL) font->chars[charnum / 256] = alloc_array_clear_or_die(render_font_char, 256); /* fill in the entry */ ch = &font->chars[charnum / 256][charnum % 256]; ch->width = width; ch->bmwidth = bmwidth; ch->bmheight = bmheight; ch->xoffs = xoffs; ch->yoffs = yoffs; ch->rawdata = rawdata; } /* some progress for big fonts */ if (++charcount % 256 == 0) mame_printf_warning("Loading BDF font... (%d characters loaded)\n", charcount); } } return 0; } /*------------------------------------------------- render_font_load_cached - load a font in cached format -------------------------------------------------*/ static int render_font_load_cached(render_font *font, mame_file *file, UINT32 hash) { UINT8 header[CACHED_HEADER_SIZE]; UINT64 offset, filesize; UINT8 *data = NULL; UINT32 bytes_read; int numchars; int chindex; /* get the file size */ filesize = mame_fsize(file); /* first read the header */ bytes_read = mame_fread(file, header, CACHED_HEADER_SIZE); if (bytes_read != CACHED_HEADER_SIZE) goto error; /* validate the header */ if (header[0] != 'f' || header[1] != 'o' || header[2] != 'n' || header[3] != 't') goto error; if (header[4] != (UINT8)(hash >> 24) || header[5] != (UINT8)(hash >> 16) || header[6] != (UINT8)(hash >> 8) || header[7] != (UINT8)hash) goto error; font->height = (header[8] << 8) | header[9]; font->scale = 1.0f / (float)font->height; font->yoffs = (INT16)((header[10] << 8) | header[11]); numchars = (header[12] << 24) | (header[13] << 16) | (header[14] << 8) | header[15]; if (filesize - CACHED_HEADER_SIZE < numchars * CACHED_CHAR_SIZE) goto error; /* now read the rest of the data */ data = alloc_array_or_die(UINT8, filesize - CACHED_HEADER_SIZE); bytes_read = mame_fread(file, data, filesize - CACHED_HEADER_SIZE); if (bytes_read != filesize - CACHED_HEADER_SIZE) goto error; /* extract the data from the data */ offset = numchars * CACHED_CHAR_SIZE; for (chindex = 0; chindex < numchars; chindex++) { const UINT8 *info = &data[chindex * CACHED_CHAR_SIZE]; int chnum = (info[0] << 8) | info[1]; render_font_char *ch; /* if we don't have a subtable yet, make one */ if (font->chars[chnum / 256] == NULL) font->chars[chnum / 256] = alloc_array_clear_or_die(render_font_char, 256); /* fill in the entry */ ch = &font->chars[chnum / 256][chnum % 256]; ch->width = (info[2] << 8) | info[3]; ch->xoffs = (INT16)((info[4] << 8) | info[5]); ch->yoffs = (INT16)((info[6] << 8) | info[7]); ch->bmwidth = (info[8] << 8) | info[9]; ch->bmheight = (info[10] << 8) | info[11]; ch->rawdata = (char *)data + offset; /* advance the offset past the character */ offset += (ch->bmwidth * ch->bmheight + 7) / 8; if (offset > filesize - CACHED_HEADER_SIZE) goto error; } /* reuse the chartable as a temporary buffer */ font->format = FONT_FORMAT_CACHED; font->rawdata = (char *)data; return 0; error: if (data != NULL) free(data); return 1; } /*------------------------------------------------- render_font_save_cached - save a font in cached format -------------------------------------------------*/ static int render_font_save_cached(render_font *font, const char *filename, UINT32 hash) { file_error filerr; render_font_char *ch; UINT32 bytes_written; UINT8 *tempbuffer; UINT8 *chartable; mame_file *file; int numchars; UINT8 *dest; int tableindex; int chnum; mame_printf_warning("Generating cached BDF font...\n"); /* attempt to open the file */ filerr = mame_fopen(SEARCHPATH_FONT, filename, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &file); if (filerr != FILERR_NONE) return 1; /* determine the number of characters */ numchars = 0; for (chnum = 0; chnum < 65536; chnum++) { render_font_char *chtable = font->chars[chnum / 256]; if (chtable != NULL) { ch = &chtable[chnum % 256]; if (ch->width > 0) numchars++; } } /* allocate an array to hold the character data */ chartable = alloc_array_clear_or_die(UINT8, numchars * CACHED_CHAR_SIZE); /* allocate a temp buffer to compress into */ tempbuffer = alloc_array_or_die(UINT8, 65536); /* write the header */ dest = tempbuffer; *dest++ = 'f'; *dest++ = 'o'; *dest++ = 'n'; *dest++ = 't'; *dest++ = hash >> 24; *dest++ = hash >> 16; *dest++ = hash >> 8; *dest++ = hash & 0xff; *dest++ = font->height >> 8; *dest++ = font->height & 0xff; *dest++ = font->yoffs >> 8; *dest++ = font->yoffs & 0xff; *dest++ = numchars >> 24; *dest++ = numchars >> 16; *dest++ = numchars >> 8; *dest++ = numchars & 0xff; assert(dest - tempbuffer == CACHED_HEADER_SIZE); bytes_written = mame_fwrite(file, tempbuffer, dest - tempbuffer); if (bytes_written != dest - tempbuffer) goto error; /* write the empty table to the beginning of the file */ bytes_written = mame_fwrite(file, chartable, numchars * CACHED_CHAR_SIZE); if (bytes_written != numchars * CACHED_CHAR_SIZE) goto error; /* loop over all characters */ tableindex = 0; for (chnum = 0; chnum < 65536; chnum++) { ch = get_char(font, chnum); if (ch != NULL && ch->width > 0) { UINT8 accum, accbit; int x, y; /* write out a bit-compressed bitmap if we have one */ if (ch->bitmap != NULL) { /* write the data to the tempbuffer */ dest = tempbuffer; accum = 0; accbit = 7; /* bit-encode the character data */ for (y = 0; y < ch->bmheight; y++) { int desty = y + font->height + font->yoffs - ch->yoffs - ch->bmheight; const UINT32 *src = (desty >= 0 && desty < font->height) ? BITMAP_ADDR32(ch->bitmap, desty, 0) : NULL; for (x = 0; x < ch->bmwidth; x++) { if (src != NULL && RGB_ALPHA(src[x]) != 0) accum |= 1 << accbit; if (accbit-- == 0) { *dest++ = accum; accum = 0; accbit = 7; } } } /* flush any extra */ if (accbit != 7) *dest++ = accum; /* write the data */ bytes_written = mame_fwrite(file, tempbuffer, dest - tempbuffer); if (bytes_written != dest - tempbuffer) goto error; /* free the bitmap and texture */ if (ch->texture != NULL) render_texture_free(ch->texture); ch->texture = NULL; bitmap_free(ch->bitmap); ch->bitmap = NULL; } /* compute the table entry */ dest = &chartable[tableindex++ * CACHED_CHAR_SIZE]; *dest++ = chnum >> 8; *dest++ = chnum & 0xff; *dest++ = ch->width >> 8; *dest++ = ch->width & 0xff; *dest++ = ch->xoffs >> 8; *dest++ = ch->xoffs & 0xff; *dest++ = ch->yoffs >> 8; *dest++ = ch->yoffs & 0xff; *dest++ = ch->bmwidth >> 8; *dest++ = ch->bmwidth & 0xff; *dest++ = ch->bmheight >> 8; *dest++ = ch->bmheight & 0xff; } } /* seek back to the beginning and rewrite the table */ mame_fseek(file, CACHED_HEADER_SIZE, SEEK_SET); bytes_written = mame_fwrite(file, chartable, numchars * CACHED_CHAR_SIZE); if (bytes_written != numchars * CACHED_CHAR_SIZE) goto error; /* all done */ mame_fclose(file); free(tempbuffer); free(chartable); return 0; error: mame_fclose(file); osd_rmfile(filename); free(tempbuffer); free(chartable); return 1; }