mame/src/emu/rendfont.cpp

1696 lines
48 KiB
C++

// 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 <algorithm>
#include <cstddef>
#include <cstring>
#include <iterator>
#include <limits>
#define VERBOSE 0
#define LOG(...) do { if (VERBOSE) osd_printf_verbose(__VA_ARGS__); } while (0)
namespace {
template <typename Iterator>
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<u8 *>(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)
{
str.c_str(); // force NUL-termination - we depend on it later
std::size_t const len(str.length());
std::vector<char> 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<char>::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<u64>)(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<u8 const *>(&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.c_str());
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<u64>)(std::numeric_limits<u32>::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<std::vector<char>::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<u32>::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<u8> chartable(std::size_t(numchars) * bdc_table_entry::size(), 0);
// allocate a temp buffer to compress into
std::vector<u8> 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<u64>)(std::numeric_limits<u32>::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<u32>::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;
}
}
}
}