// license:BSD-3-Clause // copyright-holders:Aaron Giles, Vas Crabb /*************************************************************************** rendfont.c Rendering system font management. ***************************************************************************/ #include "emu.h" #include "rendfont.h" #include "emuopts.h" #include "coreutil.h" #include "osdepend.h" #include "uismall.fh" #include "ui/uicmd14.fh" #include "ui/cmddata.h" #include #include #include #include #include #define VERBOSE 0 #define LOG(...) do { if (VERBOSE) osd_printf_verbose(__VA_ARGS__); } while (0) namespace { template class bdf_helper { public: bdf_helper(Iterator const &begin, Iterator const &end) : m_keyword_begin(begin) , m_keyword_end(begin) , m_value_begin(begin) , m_value_end(begin) , m_line_end(begin) , m_end(end) { next_line(); } bool at_end() const { return m_end == m_keyword_begin; } void next_line() { m_keyword_begin = m_line_end; while ((m_end != m_keyword_begin) && (('\r' == *m_keyword_begin) || ('\n' == *m_keyword_begin))) ++m_keyword_begin; m_keyword_end = m_keyword_begin; while ((m_end != m_keyword_end) && (' ' != *m_keyword_end) && ('\t' != *m_keyword_end) && ('\r' != *m_keyword_end) && ('\n' != *m_keyword_end)) ++m_keyword_end; m_value_begin = m_keyword_end; while ((m_end != m_value_begin) && ((' ' == *m_value_begin) || ('\t' == *m_value_begin)) && ('\r' != *m_value_begin) && ('\n' != *m_value_begin)) ++m_value_begin; m_value_end = m_line_end = m_value_begin; while ((m_end != m_line_end) && ('\r' != *m_line_end) && ('\n' != *m_line_end)) { if ((' ' != *m_line_end) && ('\t' != *m_line_end)) m_value_end = ++m_line_end; else ++m_line_end; } } bool is_keyword(char const *keyword) const { Iterator pos(m_keyword_begin); while (true) { if (m_keyword_end == pos) { return '\0' == *keyword; } else if (('\0' == *keyword) || (*pos != *keyword)) { return false; } else { ++pos; ++keyword; } } } Iterator const &keyword_begin() const { return m_keyword_begin; } Iterator const &keyword_end() const { return m_keyword_end; } auto keyword_length() const { return std::distance(m_keyword_begin, m_keyword_end); } Iterator const &value_begin() const { return m_value_begin; } Iterator const &value_end() const { return m_value_end; } auto value_length() const { return std::distance(m_value_begin, m_value_end); } private: Iterator m_keyword_begin; Iterator m_keyword_end; Iterator m_value_begin; Iterator m_value_end; Iterator m_line_end; Iterator const m_end; }; class bdc_header { public: static constexpr unsigned MAJVERSION = 1; static constexpr unsigned MINVERSION = 0; bool read(emu_file &f) { return f.read(m_data, sizeof(m_data)) == sizeof(m_data); } bool write(emu_file &f) { return f.write(m_data, sizeof(m_data)) == sizeof(m_data); } bool check_magic() const { return !std::memcmp(MAGIC, m_data + OFFS_MAGIC, OFFS_MAJVERSION - OFFS_MAGIC); } unsigned get_major_version() const { return m_data[OFFS_MAJVERSION]; } unsigned get_minor_version() const { return m_data[OFFS_MINVERSION]; } u64 get_original_length() const { return (u64(m_data[OFFS_ORIGLENGTH + 0]) << (7 * 8)) | (u64(m_data[OFFS_ORIGLENGTH + 1]) << (6 * 8)) | (u64(m_data[OFFS_ORIGLENGTH + 2]) << (5 * 8)) | (u64(m_data[OFFS_ORIGLENGTH + 3]) << (4 * 8)) | (u64(m_data[OFFS_ORIGLENGTH + 4]) << (3 * 8)) | (u64(m_data[OFFS_ORIGLENGTH + 5]) << (2 * 8)) | (u64(m_data[OFFS_ORIGLENGTH + 6]) << (1 * 8)) | (u64(m_data[OFFS_ORIGLENGTH + 7]) << (0 * 8)); } u32 get_original_hash() const { return (u32(m_data[OFFS_ORIGHASH + 0]) << (3 * 8)) | (u32(m_data[OFFS_ORIGHASH + 1]) << (2 * 8)) | (u32(m_data[OFFS_ORIGHASH + 2]) << (1 * 8)) | (u32(m_data[OFFS_ORIGHASH + 3]) << (0 * 8)); } u32 get_glyph_count() const { return (u32(m_data[OFFS_GLYPHCOUNT + 0]) << (3 * 8)) | (u32(m_data[OFFS_GLYPHCOUNT + 1]) << (2 * 8)) | (u32(m_data[OFFS_GLYPHCOUNT + 2]) << (1 * 8)) | (u32(m_data[OFFS_GLYPHCOUNT + 3]) << (0 * 8)); } u16 get_height() const { return (u16(m_data[OFFS_HEIGHT + 0]) << (1 * 8)) | (u16(m_data[OFFS_HEIGHT + 1]) << (0 * 8)); } s16 get_y_offset() const { return (u16(m_data[OFFS_YOFFSET + 0]) << (1 * 8)) | (u16(m_data[OFFS_YOFFSET + 1]) << (0 * 8)); } s32 get_default_character() const { return (u32(m_data[OFFS_DEFCHAR + 0]) << (3 * 8)) | (u32(m_data[OFFS_DEFCHAR + 1]) << (2 * 8)) | (u32(m_data[OFFS_DEFCHAR + 2]) << (1 * 8)) | (u32(m_data[OFFS_DEFCHAR + 3]) << (0 * 8)); } void set_magic() { std::memcpy(m_data + OFFS_MAGIC, MAGIC, OFFS_MAJVERSION - OFFS_MAGIC); } void set_version() { m_data[OFFS_MAJVERSION] = MAJVERSION; m_data[OFFS_MINVERSION] = MINVERSION; } void set_original_length(u64 value) { m_data[OFFS_ORIGLENGTH + 0] = u8((value >> (7 * 8)) & 0x00ff); m_data[OFFS_ORIGLENGTH + 1] = u8((value >> (6 * 8)) & 0x00ff); m_data[OFFS_ORIGLENGTH + 2] = u8((value >> (5 * 8)) & 0x00ff); m_data[OFFS_ORIGLENGTH + 3] = u8((value >> (4 * 8)) & 0x00ff); m_data[OFFS_ORIGLENGTH + 4] = u8((value >> (3 * 8)) & 0x00ff); m_data[OFFS_ORIGLENGTH + 5] = u8((value >> (2 * 8)) & 0x00ff); m_data[OFFS_ORIGLENGTH + 6] = u8((value >> (1 * 8)) & 0x00ff); m_data[OFFS_ORIGLENGTH + 7] = u8((value >> (0 * 8)) & 0x00ff); } void set_original_hash(u32 value) { m_data[OFFS_ORIGHASH + 0] = u8((value >> (3 * 8)) & 0x00ff); m_data[OFFS_ORIGHASH + 1] = u8((value >> (2 * 8)) & 0x00ff); m_data[OFFS_ORIGHASH + 2] = u8((value >> (1 * 8)) & 0x00ff); m_data[OFFS_ORIGHASH + 3] = u8((value >> (0 * 8)) & 0x00ff); } void set_glyph_count(u32 value) { m_data[OFFS_GLYPHCOUNT + 0] = u8((value >> (3 * 8)) & 0x00ff); m_data[OFFS_GLYPHCOUNT + 1] = u8((value >> (2 * 8)) & 0x00ff); m_data[OFFS_GLYPHCOUNT + 2] = u8((value >> (1 * 8)) & 0x00ff); m_data[OFFS_GLYPHCOUNT + 3] = u8((value >> (0 * 8)) & 0x00ff); } void set_height(u16 value) { m_data[OFFS_HEIGHT + 0] = u8((value >> (1 * 8)) & 0x00ff); m_data[OFFS_HEIGHT + 1] = u8((value >> (0 * 8)) & 0x00ff); } void set_y_offset(s16 value) { m_data[OFFS_YOFFSET + 0] = u8((value >> (1 * 8)) & 0x00ff); m_data[OFFS_YOFFSET + 1] = u8((value >> (0 * 8)) & 0x00ff); } void set_default_character(s32 value) { m_data[OFFS_DEFCHAR + 0] = u8((value >> (3 * 8)) & 0x00ff); m_data[OFFS_DEFCHAR + 1] = u8((value >> (2 * 8)) & 0x00ff); m_data[OFFS_DEFCHAR + 2] = u8((value >> (1 * 8)) & 0x00ff); m_data[OFFS_DEFCHAR + 3] = u8((value >> (0 * 8)) & 0x00ff); } private: static constexpr std::size_t OFFS_MAGIC = 0x00; // 0x06 bytes static constexpr std::size_t OFFS_MAJVERSION = 0x06; // 0x01 bytes (binary integer) static constexpr std::size_t OFFS_MINVERSION = 0x07; // 0x01 bytes (binary integer) static constexpr std::size_t OFFS_ORIGLENGTH = 0x08; // 0x08 bytes (big-endian binary integer) static constexpr std::size_t OFFS_ORIGHASH = 0x10; // 0x04 bytes static constexpr std::size_t OFFS_GLYPHCOUNT = 0x14; // 0x04 bytes (big-endian binary integer) static constexpr std::size_t OFFS_HEIGHT = 0x18; // 0x02 bytes (big-endian binary integer) static constexpr std::size_t OFFS_YOFFSET = 0x1a; // 0x02 bytes (big-endian binary integer) static constexpr std::size_t OFFS_DEFCHAR = 0x1c; // 0x04 bytes (big-endian binary integer) static constexpr std::size_t OFFS_END = 0x20; static u8 const MAGIC[OFFS_MAJVERSION - OFFS_MAGIC]; u8 m_data[OFFS_END]; }; u8 const bdc_header::MAGIC[OFFS_MAJVERSION - OFFS_MAGIC] = { 'b', 'd', 'c', 'f', 'n', 't' }; class bdc_table_entry { public: bdc_table_entry(void *bytes) : m_ptr(reinterpret_cast(bytes)) { } bdc_table_entry(bdc_table_entry const &that) = default; bdc_table_entry(bdc_table_entry &&that) = default; bdc_table_entry get_next() const { return bdc_table_entry(m_ptr + OFFS_END); } u32 get_encoding() const { return (u32(m_ptr[OFFS_ENCODING + 0]) << (3 * 8)) | (u32(m_ptr[OFFS_ENCODING + 1]) << (2 * 8)) | (u32(m_ptr[OFFS_ENCODING + 2]) << (1 * 8)) | (u32(m_ptr[OFFS_ENCODING + 3]) << (0 * 8)); } u16 get_x_advance() const { return (u16(m_ptr[OFFS_XADVANCE + 0]) << (1 * 8)) | (u16(m_ptr[OFFS_XADVANCE + 1]) << (0 * 8)); } s16 get_bb_x_offset() const { return (u16(m_ptr[OFFS_BBXOFFSET + 0]) << (1 * 8)) | (u16(m_ptr[OFFS_BBXOFFSET + 1]) << (0 * 8)); } s16 get_bb_y_offset() const { return (u16(m_ptr[OFFS_BBYOFFSET + 0]) << (1 * 8)) | (u16(m_ptr[OFFS_BBYOFFSET + 1]) << (0 * 8)); } u16 get_bb_width() const { return (u16(m_ptr[OFFS_BBWIDTH + 0]) << (1 * 8)) | (u16(m_ptr[OFFS_BBWIDTH + 1]) << (0 * 8)); } u16 get_bb_height() const { return (u16(m_ptr[OFFS_BBHEIGHT + 0]) << (1 * 8)) | (u16(m_ptr[OFFS_BBHEIGHT + 1]) << (0 * 8)); } void set_encoding(u32 value) { m_ptr[OFFS_ENCODING + 0] = u8((value >> (3 * 8)) & 0x00ff); m_ptr[OFFS_ENCODING + 1] = u8((value >> (2 * 8)) & 0x00ff); m_ptr[OFFS_ENCODING + 2] = u8((value >> (1 * 8)) & 0x00ff); m_ptr[OFFS_ENCODING + 3] = u8((value >> (0 * 8)) & 0x00ff); } void set_x_advance(u16 value) { m_ptr[OFFS_XADVANCE + 0] = u8((value >> (1 * 8)) & 0x00ff); m_ptr[OFFS_XADVANCE + 1] = u8((value >> (0 * 8)) & 0x00ff); } void set_bb_x_offset(s16 value) { m_ptr[OFFS_BBXOFFSET + 0] = u8((value >> (1 * 8)) & 0x00ff); m_ptr[OFFS_BBXOFFSET + 1] = u8((value >> (0 * 8)) & 0x00ff); } void set_bb_y_offset(s16 value) { m_ptr[OFFS_BBYOFFSET + 0] = u8((value >> (1 * 8)) & 0x00ff); m_ptr[OFFS_BBYOFFSET + 1] = u8((value >> (0 * 8)) & 0x00ff); } void set_bb_width(u16 value) { m_ptr[OFFS_BBWIDTH + 0] = u8((value >> (1 * 8)) & 0x00ff); m_ptr[OFFS_BBWIDTH + 1] = u8((value >> (0 * 8)) & 0x00ff); } void set_bb_height(u16 value) { m_ptr[OFFS_BBHEIGHT + 0] = u8((value >> (1 * 8)) & 0x00ff); m_ptr[OFFS_BBHEIGHT + 1] = u8((value >> (0 * 8)) & 0x00ff); } bdc_table_entry &operator=(bdc_table_entry const &that) = default; bdc_table_entry &operator=(bdc_table_entry &&that) = default; static std::size_t size() { return OFFS_END; } private: static constexpr std::size_t OFFS_ENCODING = 0x00; // 0x04 bytes (big-endian binary integer) static constexpr std::size_t OFFS_XADVANCE = 0x04; // 0x02 bytes (big-endian binary integer) // two bytes reserved static constexpr std::size_t OFFS_BBXOFFSET = 0x08; // 0x02 bytes (big-endian binary integer) static constexpr std::size_t OFFS_BBYOFFSET = 0x0a; // 0x02 bytes (big-endian binary integer) static constexpr std::size_t OFFS_BBWIDTH = 0x0c; // 0x02 bytes (big-endian binary integer) static constexpr std::size_t OFFS_BBHEIGHT = 0x0e; // 0x02 bytes (big-endian binary integer) static constexpr std::size_t OFFS_END = 0x10; u8 *m_ptr; }; } // anonymous namespace void convert_command_glyph(std::string &str) { (void)str.c_str(); // force NUL-termination - we depend on it later std::size_t const len(str.length()); std::vector buf(2 * (len + 1)); std::size_t j(0); for (std::size_t i = 0; len > i; ) { // decode UTF-8 char32_t uchar; int const codelen(uchar_from_utf8(&uchar, &str[i], len - i)); if (0 >= codelen) break; i += codelen; // check for three metacharacters fix_command_t const *fixcmd(nullptr); switch (uchar) { case COMMAND_CONVERT_TEXT: for (fix_strings_t *fixtext = convert_text; fixtext->glyph_code; ++fixtext) { if (!fixtext->glyph_str_len) fixtext->glyph_str_len = std::strlen(fixtext->glyph_str); if (!std::strncmp(fixtext->glyph_str, &str[i], fixtext->glyph_str_len)) { uchar = fixtext->glyph_code + COMMAND_UNICODE; i += strlen(fixtext->glyph_str); break; } } break; case COMMAND_DEFAULT_TEXT: fixcmd = default_text; break; case COMMAND_EXPAND_TEXT: fixcmd = expand_text; break; } // this substitutes a single character if (fixcmd) { if (str[i] == uchar) { ++i; } else { while (fixcmd->glyph_code && (fixcmd->glyph_char != str[i])) ++fixcmd; if (fixcmd->glyph_code) { uchar = COMMAND_UNICODE + fixcmd->glyph_code; ++i; } } } // copy character to output int const outlen(utf8_from_uchar(&buf[j], buf.size() - j, uchar)); if (0 >= outlen) break; j += outlen; } str.assign(&buf[0], j); } const u64 render_font::CACHED_BDF_HASH_SIZE; //************************************************************************** // 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 nullptr if (*ptr == 0) return nullptr; // 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::glyph &render_font::get_char(char32_t chnum) { static glyph dummy_glyph; unsigned const page(chnum / 256); if (page >= ARRAY_LENGTH(m_glyphs)) { if ((0 <= m_defchar) && (chnum != m_defchar)) return get_char(m_defchar); else return dummy_glyph; } else if (!m_glyphs[page]) { //mamep: make table for command glyph if ((m_format == format::OSD) || ((chnum >= COMMAND_UNICODE) && (chnum < COMMAND_UNICODE + MAX_GLYPH_FONT))) m_glyphs[page] = new glyph[256]; else if ((0 <= m_defchar) && (chnum != m_defchar)) return get_char(m_defchar); else return dummy_glyph; } // if the character isn't generated yet, do it now glyph &gl = m_glyphs[page][chnum % 256]; if (!gl.bitmap.valid()) { //mamep: command glyph support if (m_height_cmd && chnum >= COMMAND_UNICODE && chnum < COMMAND_UNICODE + MAX_GLYPH_FONT) { glyph &glyph_ch = m_glyphs_cmd[page][chnum % 256]; float scale = float(m_height) / float(m_height_cmd); if (m_format == format::OSD) scale *= 0.90f; if (!glyph_ch.bitmap.valid()) char_expand(chnum, glyph_ch); //mamep: for color glyph gl.color = glyph_ch.color; gl.width = int(glyph_ch.width * scale + 0.5f); gl.xoffs = int(glyph_ch.xoffs * scale + 0.5f); gl.yoffs = int(glyph_ch.yoffs * scale + 0.5f); gl.bmwidth = int(glyph_ch.bmwidth * scale + 0.5f); gl.bmheight = int(glyph_ch.bmheight * scale + 0.5f); gl.bitmap.allocate(gl.bmwidth, gl.bmheight); rectangle clip( 0, glyph_ch.bitmap.width() - 1, 0, glyph_ch.bitmap.height() - 1); render_texture::hq_scale(gl.bitmap, glyph_ch.bitmap, clip, nullptr); /* wrap a texture around the bitmap */ gl.texture = m_manager.texture_alloc(render_texture::hq_scale); gl.texture->set_bitmap(gl.bitmap, gl.bitmap.cliprect(), TEXFORMAT_ARGB32); } else { char_expand(chnum, gl); } } return gl; } //************************************************************************** // RENDER FONT //************************************************************************** //------------------------------------------------- // render_font - constructor //------------------------------------------------- render_font::render_font(render_manager &manager, const char *filename) : m_manager(manager) , m_format(format::UNKNOWN) , m_height(0) , m_yoffs(0) , m_defchar(-1) , m_scale(1.0f) , m_rawsize(0) , m_osdfont() , m_height_cmd(0) , m_yoffs_cmd(0) { memset(m_glyphs, 0, sizeof(m_glyphs)); memset(m_glyphs_cmd, 0, sizeof(m_glyphs_cmd)); // if this is an OSD font, we're done if (filename) { m_osdfont = manager.machine().osd().font_alloc(); if (m_osdfont && m_osdfont->open(manager.machine().options().font_path(), filename, m_height)) { m_scale = 1.0f / float(m_height); m_format = format::OSD; //mamep: allocate command glyph font render_font_command_glyph(); return; } m_osdfont.reset(); } // if the filename is 'default' default to 'ui.bdf' for backwards compatibility if (filename && !core_stricmp(filename, "default")) filename = "ui.bdf"; // attempt to load an external BDF font first if (filename && load_cached_bdf(filename)) { //mamep: allocate command glyph font render_font_command_glyph(); return; } // load the compiled in data instead emu_file ramfile(OPEN_FLAG_READ); osd_file::error const filerr(ramfile.open_ram(font_uismall, sizeof(font_uismall))); if (osd_file::error::NONE == filerr) load_cached(ramfile, 0, 0); render_font_command_glyph(); } //------------------------------------------------- // ~render_font - destructor //------------------------------------------------- render_font::~render_font() { // free all the subtables for (auto & elem : m_glyphs) if (elem) { for (unsigned int charnum = 0; charnum < 256; charnum++) { glyph &gl = elem[charnum]; m_manager.texture_free(gl.texture); } delete[] elem; } for (auto & elem : m_glyphs_cmd) if (elem) { for (unsigned int charnum = 0; charnum < 256; charnum++) { glyph &gl = elem[charnum]; m_manager.texture_free(gl.texture); } delete[] elem; } } //------------------------------------------------- // char_expand - expand the raw data for a // character into a bitmap //------------------------------------------------- void render_font::char_expand(char32_t chnum, glyph &gl) { LOG("render_font::char_expand: expanding character %u\n", unsigned(chnum)); rgb_t const fgcol(gl.color ? gl.color : rgb_t(0xff, 0xff, 0xff, 0xff)); rgb_t const bgcol(0x00, 0xff, 0xff, 0xff); bool const is_cmd((chnum >= COMMAND_UNICODE) && (chnum < COMMAND_UNICODE + MAX_GLYPH_FONT)); if (is_cmd) { // abort if nothing there if (gl.bmwidth == 0 || gl.bmheight == 0 || gl.rawdata == nullptr) return; // allocate a new bitmap of the size we need gl.bitmap.allocate(gl.bmwidth, m_height_cmd); gl.bitmap.fill(0); // extract the data const char *ptr = gl.rawdata; u8 accum = 0, accumbit = 7; for (int y = 0; y < gl.bmheight; y++) { int desty = y + m_height_cmd + m_yoffs_cmd - gl.yoffs - gl.bmheight; u32 *dest = (desty >= 0 && desty < m_height_cmd) ? &gl.bitmap.pix32(desty, 0) : nullptr; { for (int x = 0; x < gl.bmwidth; x++) { if (accumbit == 7) accum = *ptr++; if (dest != nullptr) *dest++ = (accum & (1 << accumbit)) ? fgcol : bgcol; accumbit = (accumbit - 1) & 7; } } } } else if (m_format == format::OSD) { // if we're an OSD font, query the info if (0 > gl.bmwidth) { // we set bmwidth to -1 if we've previously queried and failed LOG("render_font::char_expand: previously failed to get bitmap from OSD font\n"); return; } if (!m_osdfont->get_bitmap(chnum, gl.bitmap, gl.width, gl.xoffs, gl.yoffs)) { // attempt to get the font bitmap failed - set bmwidth to -1 LOG("render_font::char_expand: get bitmap from OSD font failed\n"); gl.bitmap.reset(); gl.bmwidth = -1; return; } else { // populate the bmwidth/bmheight fields LOG("render_font::char_expand: got %dx%d bitmap from OSD font\n", gl.bitmap.width(), gl.bitmap.height()); gl.bmwidth = gl.bitmap.width(); gl.bmheight = gl.bitmap.height(); } } else if (!gl.bmwidth || !gl.bmheight || !gl.rawdata) { // abort if nothing there LOG("render_font::char_expand: empty bitmap bounds or no raw data\n"); return; } else { // other formats need to parse their data LOG("render_font::char_expand: building bitmap from raw data\n"); // allocate a new bitmap of the size we need gl.bitmap.allocate(gl.bmwidth, m_height); gl.bitmap.fill(0); // extract the data const char *ptr = gl.rawdata; u8 accum(0), accumbit(7); for (int y = 0; y < gl.bmheight; ++y) { int const desty(y + m_height + m_yoffs - gl.yoffs - gl.bmheight); u32 *dest(((0 <= desty) && (m_height > desty)) ? &gl.bitmap.pix32(desty) : nullptr); if (m_format == format::TEXT) { if (dest) { for (int x = 0; gl.bmwidth > x; ) { // scan for the next hex digit int bits = -1; while (('\r' != *ptr) && ('\n' != *ptr) && (0 > bits)) { 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 *dest++ = (bits & 8) ? fgcol : bgcol; if (gl.bmwidth > ++x) *dest++ = (bits & 4) ? fgcol : bgcol; if (gl.bmwidth > ++x) *dest++ = (bits & 2) ? fgcol : bgcol; if (gl.bmwidth > ++x) *dest++ = (bits & 1) ? fgcol : bgcol; ++x; } } // advance to the next line ptr = next_line(ptr); } else if (m_format == format::CACHED) { for (int x = 0; x < gl.bmwidth; x++) { if (accumbit == 7) accum = *ptr++; if (dest != nullptr) *dest++ = (accum & (1 << accumbit)) ? fgcol : bgcol; accumbit = (accumbit - 1) & 7; } } } } // wrap a texture around the bitmap gl.texture = m_manager.texture_alloc(render_texture::hq_scale); gl.texture->set_bitmap(gl.bitmap, gl.bitmap.cliprect(), TEXFORMAT_ARGB32); } //------------------------------------------------- // 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(float height, float aspect, char32_t chnum, render_bounds &bounds) { glyph &gl = get_char(chnum); // on entry, assume x0,y0 are the top,left coordinate of the cell and add // the character bounding box to that position float scale = m_scale * height; bounds.x0 += float(gl.xoffs) * scale * aspect; // compute x1,y1 from there based on the bitmap size bounds.x1 = bounds.x0 + float(gl.bmwidth) * scale * aspect; bounds.y1 = bounds.y0 + float(m_height) * scale; // return the texture return gl.texture; } //------------------------------------------------- // get_scaled_bitmap_and_bounds - return a // scaled bitmap and bounding rect for a char //------------------------------------------------- void render_font::get_scaled_bitmap_and_bounds(bitmap_argb32 &dest, float height, float aspect, char32_t chnum, rectangle &bounds) { glyph &gl = get_char(chnum); // on entry, assume x0,y0 are the top,left coordinate of the cell and add // the character bounding box to that position float scale = m_scale * height; bounds.min_x = float(gl.xoffs) * scale * aspect; bounds.min_y = 0; // compute x1,y1 from there based on the bitmap size bounds.set_width(float(gl.bmwidth) * scale * aspect); bounds.set_height(float(m_height) * scale); // if the bitmap isn't big enough, bail if (dest.width() < bounds.width() || dest.height() < bounds.height()) return; // if no texture, fill the target if (gl.texture == nullptr) { dest.fill(0); return; } // scale the font bitmap_argb32 tempbitmap(&dest.pix(0), bounds.width(), bounds.height(), dest.rowpixels()); render_texture::hq_scale(tempbitmap, gl.bitmap, gl.bitmap.cliprect(), nullptr); } //------------------------------------------------- // char_width - return the width of a character // at the given height //------------------------------------------------- float render_font::char_width(float height, float aspect, char32_t ch) { return float(get_char(ch).width) * m_scale * height * aspect; } //------------------------------------------------- // string_width - return the width of a string // at the given height //------------------------------------------------- float render_font::string_width(float height, float aspect, const char *string) { // loop over the string and accumulate widths int totwidth = 0; const char *ends = string + strlen(string); const char *s = string; char32_t schar; // loop over characters while (*s != 0) { int scharcount = uchar_from_utf8(&schar, s, ends - s); totwidth += get_char(schar).width; s += scharcount; } // scale the final result based on height return float(totwidth) * m_scale * height * aspect; } //------------------------------------------------- // utf8string_width - return the width of a // UTF8-encoded string at the given height //------------------------------------------------- float render_font::utf8string_width(float height, float aspect, const char *utf8string) { std::size_t const length = std::strlen(utf8string); // loop over the string and accumulate widths int count; s32 totwidth = 0; for (std::size_t offset = 0U; offset < length; offset += unsigned(count)) { char32_t uchar; count = uchar_from_utf8(&uchar, utf8string + offset, length - offset); if (count < 0) break; totwidth += get_char(uchar).width; } // scale the final result based on height return float(totwidth) * m_scale * height * aspect; } //------------------------------------------------- // 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 //------------------------------------------------- bool render_font::load_cached_bdf(const char *filename) { osd_file::error filerr; u32 chunk; u64 bytes; // first try to open the BDF itself emu_file file(m_manager.machine().options().font_path(), OPEN_FLAG_READ); filerr = file.open(filename); if (filerr != osd_file::error::NONE) return false; // determine the file size and allocate memory try { m_rawsize = file.size(); std::vector::size_type const sz(m_rawsize + 1); if (u64(sz) != (m_rawsize + 1)) return false; m_rawdata.resize(sz); } catch (...) { return false; } // read the first chunk and hash it chunk = u32((std::min)(CACHED_BDF_HASH_SIZE, m_rawsize)); bytes = file.read(&m_rawdata[0], chunk); if (bytes != chunk) { m_rawdata.clear(); return false; } u32 const hash(core_crc32(0, reinterpret_cast(&m_rawdata[0]), bytes)); // create the cached filename, changing the 'F' to a 'C' on the extension std::string cachedname(filename); if ((4U < cachedname.length()) && !core_stricmp(&cachedname[cachedname.length() - 4], ".bdf")) cachedname.erase(cachedname.length() - 4); cachedname.append(".bdc"); // attempt to open the cached version of the font { emu_file cachefile(m_manager.machine().options().font_path(), OPEN_FLAG_READ); filerr = cachefile.open(cachedname); if (filerr == osd_file::error::NONE) { // if we have a cached version, load it bool const result = load_cached(cachefile, m_rawsize, hash); // if that worked, we're done if (result) return true; } } // read in the rest of the font and NUL-terminate it while (bytes < m_rawsize) { chunk = u32((std::min)(std::numeric_limits::max(), m_rawsize - bytes)); u32 const read(file.read(&m_rawdata[bytes], chunk)); bytes += read; if (read != chunk) { m_rawdata.clear(); return false; } } m_rawdata[m_rawsize] = '\0'; // load the BDF bool const result = load_bdf(); // if we loaded okay, create a cached one if (result) save_cached(cachedname.c_str(), m_rawsize, hash); else m_rawdata.clear(); // close the file return result; } //------------------------------------------------- // load_bdf - parse and load a BDF font //------------------------------------------------- bool render_font::load_bdf() { // set the format to text m_format = format::TEXT; bdf_helper::const_iterator> helper(std::cbegin(m_rawdata), std::cend(m_rawdata)); // the first thing we want to see is the STARTFONT declaration, failing that we can't do much for ( ; !helper.is_keyword("STARTFONT"); helper.next_line()) { if (helper.at_end()) { osd_printf_error("render_font::load_bdf: expected STARTFONT\n"); return false; } } // parse out the global information we need bool have_bbox(false); bool have_properties(false); bool have_defchar(false); for (helper.next_line(); !helper.is_keyword("CHARS"); helper.next_line()) { if (helper.at_end()) { // font with no characters is useless osd_printf_error("render_font::load_bdf: no glyph section found\n"); return false; } else if (helper.is_keyword("FONTBOUNDINGBOX")) { // check for duplicate bounding box if (have_bbox) { osd_printf_error("render_font::load_bdf: found additional bounding box \"%.*s\"\n", int(helper.value_length()), &*helper.value_begin()); return false; } have_bbox = true; // parse bounding box and check that it's at least half sane int width, xoffs; if (4 == sscanf(&*helper.value_begin(), "%d %d %d %d", &width, &m_height, &xoffs, &m_yoffs)) { LOG("render_font::load_bdf: got bounding box %dx%d %d,%d\n", width, m_height, xoffs, m_yoffs); if ((0 >= m_height) || (0 >= width)) { osd_printf_error("render_font::load_bdf: bounding box is invalid\n"); return false; } } else { osd_printf_error("render_font::load_bdf: failed to parse bounding box \"%.*s\"\n", int(helper.value_length()), &*helper.value_begin()); return false; } } else if (helper.is_keyword("STARTPROPERTIES")) { // check for duplicated properties section if (have_properties) { osd_printf_error("render_font::load_bdf: found additional properties\n"); return false; } have_properties = true; // get property count for sanity check int propcount; if (1 != sscanf(&*helper.value_begin(), "%d", &propcount)) { osd_printf_error("render_font::load_bdf: failed to parse property count \"%.*s\"\n", int(helper.value_length()), &*helper.value_begin()); return false; } int actual(0); for (helper.next_line(); !helper.is_keyword("ENDPROPERTIES"); helper.next_line()) { ++actual; if (helper.at_end()) { // unterminated properties section osd_printf_error("render_font::load_bdf: end of properties not found\n"); return false; } else if (helper.is_keyword("DEFAULT_CHAR")) { // check for duplicate default character if (have_defchar) { osd_printf_error("render_font::load_bdf: found additional default character \"%.*s\"\n", int(helper.value_length()), &*helper.value_begin()); return false; } have_defchar = true; // parse default character if (1 == sscanf(&*helper.value_begin(), "%d", &m_defchar)) { LOG("render_font::load_bdf: got default character 0x%x\n", m_defchar); } else { osd_printf_error("render_font::load_bdf: failed to parse default character \"%.*s\"\n", int(helper.value_length()), &*helper.value_begin()); return false; } } } // sanity check on number of properties if (actual != propcount) { osd_printf_error("render_font::load_bdf: incorrect number of properties %d\n", actual); return false; } } } // compute the scale factor if (!have_bbox) { osd_printf_error("render_font::load_bdf: no bounding box found\n"); return false; } m_scale = 1.0f / float(m_height); // get expected character count int expected; if (1 == sscanf(&*helper.value_begin(), "%d", &expected)) { LOG("render_font::load_bdf: got character count %d\n", expected); } else { osd_printf_error("render_font::load_bdf: failed to parse character count \"%.*s\"\n", int(helper.value_length()), &*helper.value_begin()); return false; } // now scan for characters auto const nothex([] (char ch) { return (('0' > ch) || ('9' < ch)) && (('A' > ch) || ('Z' < ch)) && (('a' > ch) || ('z' < ch)); }); int charcount = 0; for (helper.next_line(); !helper.is_keyword("ENDFONT"); helper.next_line()) { if (helper.at_end()) { // unterminated font osd_printf_error("render_font::load_bdf: end of font not found\n"); return false; } else if (helper.is_keyword("STARTCHAR")) { // required glyph properties bool have_encoding(false); bool have_advance(false); bool have_bbounds(false); int encoding(-1); int xadvance(-1); int bbw(-1), bbh(-1), bbxoff(-1), bbyoff(-1); // stuff for the bitmap data bool in_bitmap(false); int bitmap_rows(0); char const *bitmap_data(nullptr); // parse a glyph for (helper.next_line(); !helper.is_keyword("ENDCHAR"); helper.next_line()) { if (helper.at_end()) { // unterminated glyph osd_printf_error("render_font::load_bdf: end of glyph not found\n"); return false; } else if (in_bitmap) { // quick sanity check if ((2 * ((bbw + 7) / 8)) != helper.keyword_length()) { osd_printf_error("render_font::load_bdf: incorrect length for bitmap line \"%.*s\"\n", int(helper.keyword_length()), &*helper.keyword_begin()); return false; } else if (std::find_if(helper.keyword_begin(), helper.keyword_end(), nothex) != helper.keyword_end()) { osd_printf_error("render_font::load_bdf: found invalid character in bitmap line \"%.*s\"\n", int(helper.keyword_length()), &*helper.keyword_begin()); return false; } // track number of rows if (1 == ++bitmap_rows) bitmap_data = &*helper.keyword_begin(); } else if (helper.is_keyword("ENCODING")) { // check for duplicate glyph encoding if (have_encoding) { osd_printf_error("render_font::load_bdf: found additional glyph encoding \"%.*s\"\n", int(helper.value_length()), &*helper.value_begin()); return false; } have_encoding = true; // need to support Adobe Standard Encoding "123" and non-standard glyph index "-1 123" std::string const value(helper.value_begin(), helper.value_end()); int aux; int const cnt(sscanf(value.c_str(), "%d %d", &encoding, &aux)); if ((2 == cnt) && (-1 == encoding) && (0 <= aux)) { encoding = aux; } else if ((1 != cnt) || (0 > encoding)) { osd_printf_error("render_font::load_bdf: failed to parse glyph encoding \"%.*s\"\n", int(helper.value_length()), &*helper.value_begin()); return false; } LOG("render_font::load_bdf: got glyph encoding %d\n", encoding); } else if (helper.is_keyword("DWIDTH")) { // check for duplicate advance if (have_advance) { osd_printf_error("render_font::load_bdf: found additional pixel width \"%.*s\"\n", int(helper.value_length()), &*helper.value_begin()); return false; } have_advance = true; // completely ignore vertical advance int yadvance; if (2 == sscanf(&*helper.value_begin(), "%d %d", &xadvance, &yadvance)) { LOG("render_font::load_bdf: got pixel width %d,%d\n", xadvance, yadvance); } else { osd_printf_error("render_font::load_bdf: failed to parse pixel width \"%.*s\"\n", int(helper.value_length()), &*helper.value_begin()); return false; } } else if (helper.is_keyword("BBX")) { // check for duplicate black pixel box if (have_bbounds) { osd_printf_error("render_font::load_bdf: found additional pixel width \"%.*s\"\n", int(helper.value_length()), &*helper.value_begin()); return false; } have_bbounds = true; // extract position/size of black pixel area if (4 == sscanf(&*helper.value_begin(), "%d %d %d %d", &bbw, &bbh, &bbxoff, &bbyoff)) { LOG("render_font::load_bdf: got black pixel box %dx%d %d,%d\n", bbw, bbh, bbxoff, bbyoff); if ((0 > bbw) || (0 > bbh)) { osd_printf_error("render_font::load_bdf: black pixel box is invalid\n"); return false; } } else { osd_printf_error("render_font::load_bdf: failed to parse black pixel box \"%.*s\"\n", int(helper.value_length()), &*helper.value_begin()); return false; } } else if (helper.is_keyword("BITMAP")) { // this is the bitmap - we need to already have properties before we get here if (!have_advance) { osd_printf_error("render_font::load_bdf: glyph has no pixel width\n"); return false; } else if (!have_bbounds) { osd_printf_error("render_font::load_bdf: glyph has no black pixel box\n"); return false; } in_bitmap = true; } } // now check that we have what we need if (!in_bitmap) { osd_printf_error("render_font::load_bdf: glyph has no bitmap\n"); return false; } else if (bitmap_rows != bbh) { osd_printf_error("render_font::load_bdf: incorrect number of bitmap lines %d\n", bitmap_rows); return false; } // some kinds of characters will screw us up if (0 > xadvance) { LOG("render_font::load_bdf: ignoring character with negative x advance\n"); } else if ((256 * ARRAY_LENGTH(m_glyphs)) <= encoding) { LOG("render_font::load_bdf: ignoring character with encoding outside range\n"); } else { // if we don't have a subtable yet, make one if (!m_glyphs[encoding / 256]) { try { m_glyphs[encoding / 256] = new glyph[256]; } catch (...) { osd_printf_error("render_font::load_bdf: allocation failed\n"); return false; } } // fill in the entry glyph &gl = m_glyphs[encoding / 256][encoding % 256]; gl.width = xadvance; gl.bmwidth = bbw; gl.bmheight = bbh; gl.xoffs = bbxoff; gl.yoffs = bbyoff; gl.rawdata = bitmap_data; } // some progress for big fonts if (0 == (++charcount % 256)) osd_printf_warning("Loading BDF font... (%d characters loaded)\n", charcount); } } // check number of characters if (expected != charcount) { osd_printf_error("render_font::load_bdf: incorrect character count %d\n", charcount); return false; } // should have bailed by now if something went wrong return true; } //------------------------------------------------- // load_cached - load a font in cached format //------------------------------------------------- bool render_font::load_cached(emu_file &file, u64 length, u32 hash) { // get the file size, read the header, and check that it looks good u64 const filesize(file.size()); bdc_header header; if (!header.read(file)) { osd_printf_warning("render_font::load_cached: error reading BDC header\n"); return false; } else if (!header.check_magic() || (bdc_header::MAJVERSION != header.get_major_version()) || (bdc_header::MINVERSION != header.get_minor_version())) { LOG("render_font::load_cached: incompatible BDC file\n"); return false; } else if (length && ((header.get_original_length() != length) || (header.get_original_hash() != hash))) { LOG("render_font::load_cached: BDC file does not match original BDF file\n"); return false; } // get global properties from the header m_height = header.get_height(); m_scale = 1.0f / float(m_height); m_yoffs = header.get_y_offset(); m_defchar = header.get_default_character(); u32 const numchars(header.get_glyph_count()); if ((file.tell() + (u64(numchars) * bdc_table_entry::size())) > filesize) { LOG("render_font::load_cached: BDC file is too small to hold glyph table\n"); return false; } // now read the rest of the data u64 const remaining(filesize - file.tell()); try { m_rawdata.resize(std::size_t(remaining)); } catch (...) { osd_printf_error("render_font::load_cached: allocation error\n"); } for (u64 bytes_read = 0; remaining > bytes_read; ) { u32 const chunk((std::min)(u64(std::numeric_limits::max()), remaining)); if (file.read(&m_rawdata[bytes_read], chunk) != chunk) { osd_printf_error("render_font::load_cached: error reading BDC data\n"); m_rawdata.clear(); return false; } bytes_read += chunk; } // extract the data from the data std::size_t offset(std::size_t(numchars) * bdc_table_entry::size()); bdc_table_entry entry(m_rawdata.empty() ? nullptr : &m_rawdata[0]); for (unsigned chindex = 0; chindex < numchars; chindex++, entry = entry.get_next()) { // if we don't have a subtable yet, make one int const chnum(entry.get_encoding()); LOG("render_font::load_cached: loading character %d\n", chnum); if (!m_glyphs[chnum / 256]) { try { m_glyphs[chnum / 256] = new glyph[256]; } catch (...) { osd_printf_error("render_font::load_cached: allocation error\n"); m_rawdata.clear(); return false; } } // fill in the entry glyph &gl = m_glyphs[chnum / 256][chnum % 256]; gl.width = entry.get_x_advance(); gl.xoffs = entry.get_bb_x_offset(); gl.yoffs = entry.get_bb_y_offset(); gl.bmwidth = entry.get_bb_width(); gl.bmheight = entry.get_bb_height(); gl.rawdata = &m_rawdata[offset]; // advance the offset past the character offset += (gl.bmwidth * gl.bmheight + 7) / 8; if (m_rawdata.size() < offset) { osd_printf_verbose("render_font::load_cached: BDC file too small to hold all glyphs\n"); m_rawdata.clear(); return false; } } // got everything m_format = format::CACHED; return true; } //------------------------------------------------- // save_cached - save a font in cached format //------------------------------------------------- bool render_font::save_cached(const char *filename, u64 length, u32 hash) { osd_printf_warning("Generating cached BDF font...\n"); // attempt to open the file emu_file file(m_manager.machine().options().font_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE); osd_file::error const filerr = file.open(filename); if (osd_file::error::NONE != filerr) return false; // count glyphs unsigned numchars = 0; for (glyph const *const page : m_glyphs) { for (unsigned chnum = 0; page && (256 > chnum); ++chnum) { if (0 < page[chnum].width) ++numchars; } } LOG("render_font::save_cached: %u glyphs with positive advance to save\n", numchars); try { u32 bytes_written; { LOG("render_font::save_cached: writing header\n"); bdc_header hdr; hdr.set_magic(); hdr.set_version(); hdr.set_original_length(length); hdr.set_original_hash(hash); hdr.set_glyph_count(numchars); hdr.set_height(m_height); hdr.set_y_offset(m_yoffs); hdr.set_default_character(m_defchar); if (!hdr.write(file)) throw emu_fatalerror("Error writing cached file"); } u64 const table_offs(file.tell()); // allocate an array to hold the character data std::vector chartable(std::size_t(numchars) * bdc_table_entry::size(), 0); // allocate a temp buffer to compress into std::vector tempbuffer(65536); // write the empty table to the beginning of the file bytes_written = file.write(&chartable[0], chartable.size()); if (bytes_written != chartable.size()) throw emu_fatalerror("Error writing cached file"); // loop over all characters bdc_table_entry table_entry(chartable.empty() ? nullptr : &chartable[0]); for (unsigned chnum = 0; chnum < (256 * ARRAY_LENGTH(m_glyphs)); chnum++) { if (m_glyphs[chnum / 256] && (0 < m_glyphs[chnum / 256][chnum % 256].width)) { LOG("render_font::save_cached: writing glyph %u\n", chnum); glyph &gl(get_char(chnum)); // write out a bit-compressed bitmap if we have one if (gl.bitmap.valid()) { // write the data to the tempbuffer u8 *dest = &tempbuffer[0]; u8 accum = 0; u8 accbit = 7; // bit-encode the character data for (int y = 0; y < gl.bmheight; y++) { int desty = y + m_height + m_yoffs - gl.yoffs - gl.bmheight; const u32 *src = (desty >= 0 && desty < m_height) ? &gl.bitmap.pix32(desty) : nullptr; for (int x = 0; x < gl.bmwidth; x++) { if (src != nullptr && rgb_t(src[x]).a() != 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 = file.write(&tempbuffer[0], dest - &tempbuffer[0]); if (bytes_written != dest - &tempbuffer[0]) throw emu_fatalerror("Error writing cached file"); // free the bitmap and texture m_manager.texture_free(gl.texture); gl.bitmap.reset(); gl.texture = nullptr; } // compute the table entry table_entry.set_encoding(chnum); table_entry.set_x_advance(gl.width); table_entry.set_bb_x_offset(gl.xoffs); table_entry.set_bb_y_offset(gl.yoffs); table_entry.set_bb_width(gl.bmwidth); table_entry.set_bb_height(gl.bmheight); table_entry = table_entry.get_next(); } } // seek back to the beginning and rewrite the table if (!chartable.empty()) { LOG("render_font::save_cached: writing character table\n"); file.seek(table_offs, SEEK_SET); u8 const *bytes(&chartable[0]); for (u64 remaining = chartable.size(); remaining; ) { u32 const chunk((std::min)(std::numeric_limits::max(), remaining)); bytes_written = file.write(bytes, chunk); if (chunk != bytes_written) throw emu_fatalerror("Error writing cached file"); bytes += chunk; remaining -= chunk; } } // no trouble? return true; } catch (...) { file.remove_on_close(); return false; } } void render_font::render_font_command_glyph() { // FIXME: this is copy/pasta from the BDC loading, and it shouldn't be injected into every font emu_file file(OPEN_FLAG_READ); if (file.open_ram(font_uicmd14, sizeof(font_uicmd14)) == osd_file::error::NONE) { // get the file size, read the header, and check that it looks good u64 const filesize(file.size()); bdc_header header; if (!header.read(file)) { osd_printf_warning("render_font::render_font_command_glyph: error reading BDC header\n"); return; } else if (!header.check_magic() || (bdc_header::MAJVERSION != header.get_major_version()) || (bdc_header::MINVERSION != header.get_minor_version())) { LOG("render_font::render_font_command_glyph: incompatible BDC file\n"); return; } // get global properties from the header m_height_cmd = header.get_height(); m_yoffs_cmd = header.get_y_offset(); u32 const numchars(header.get_glyph_count()); if ((file.tell() + (u64(numchars) * bdc_table_entry::size())) > filesize) { LOG("render_font::render_font_command_glyph: BDC file is too small to hold glyph table\n"); return; } // now read the rest of the data u64 const remaining(filesize - file.tell()); try { m_rawdata_cmd.resize(std::size_t(remaining)); } catch (...) { osd_printf_error("render_font::render_font_command_glyph: allocation error\n"); } for (u64 bytes_read = 0; remaining > bytes_read; ) { u32 const chunk((std::min)(u64(std::numeric_limits::max()), remaining)); if (file.read(&m_rawdata_cmd[bytes_read], chunk) != chunk) { osd_printf_error("render_font::render_font_command_glyph: error reading BDC data\n"); m_rawdata_cmd.clear(); return; } bytes_read += chunk; } // extract the data from the data std::size_t offset(std::size_t(numchars) * bdc_table_entry::size()); bdc_table_entry entry(m_rawdata_cmd.empty() ? nullptr : &m_rawdata_cmd[0]); for (unsigned chindex = 0; chindex < numchars; chindex++, entry = entry.get_next()) { // if we don't have a subtable yet, make one int const chnum(entry.get_encoding()); LOG("render_font::render_font_command_glyph: loading character %d\n", chnum); if (!m_glyphs_cmd[chnum / 256]) { try { m_glyphs_cmd[chnum / 256] = new glyph[256]; } catch (...) { osd_printf_error("render_font::render_font_command_glyph: allocation error\n"); m_rawdata_cmd.clear(); return; } } // fill in the entry glyph &gl = m_glyphs_cmd[chnum / 256][chnum % 256]; gl.width = entry.get_x_advance(); gl.xoffs = entry.get_bb_x_offset(); gl.yoffs = entry.get_bb_y_offset(); gl.bmwidth = entry.get_bb_width(); gl.bmheight = entry.get_bb_height(); gl.rawdata = &m_rawdata_cmd[offset]; // advance the offset past the character offset += (gl.bmwidth * gl.bmheight + 7) / 8; if (m_rawdata_cmd.size() < offset) { osd_printf_verbose("render_font::render_font_command_glyph: BDC file too small to hold all glyphs\n"); m_rawdata_cmd.clear(); return; } } } }