Support font files.

This only works with the userspace build of SharedGraphics so far.
It's also very slow at loading fonts, but that's easy to fix.

Let's put fonts in /res/fonts/.
This commit is contained in:
Andreas Kling 2019-02-02 23:13:12 +01:00
parent 655753c557
commit 7f91aec25c
Notes: sideshowbarker 2024-07-19 15:53:54 +09:00
7 changed files with 170 additions and 10 deletions

View file

@ -16,6 +16,11 @@ public:
m_buffer[m_offset++] = value & 0xffu;
}
void operator<<(char value)
{
m_buffer[m_offset++] = (byte)value;
}
void operator<<(word value)
{
m_buffer[m_offset++] = value & 0xffu;
@ -43,9 +48,20 @@ public:
m_buffer[m_offset++] = value[i];
}
void operator<<(const ByteBuffer& value)
{
for (size_t i = 0; i < value.size(); ++i)
m_buffer[m_offset++] = value[i];
}
bool at_end() const
{
return m_offset == m_buffer.size();
}
void fill_to_end(byte ch)
{
while (m_offset < m_buffer.size())
while (!at_end())
m_buffer[m_offset++] = ch;
}

Binary file not shown.

View file

@ -1,18 +1,28 @@
#include "FontEditor.h"
#include <SharedGraphics/Painter.h>
#include <LibGUI/GButton.h>
#include <LibGUI/GLabel.h>
FontEditorWidget::FontEditorWidget(GWidget* parent)
: GWidget(parent)
{
auto mutable_font = Font::default_font().clone();
m_edited_font = Font::load_from_file("/saved.font");
if (!m_edited_font)
m_edited_font = Font::default_font().clone();
m_glyph_map_widget = new GlyphMapWidget(*mutable_font, this);
m_glyph_map_widget = new GlyphMapWidget(*m_edited_font, this);
m_glyph_map_widget->move_to({ 90, 5 });
m_glyph_editor_widget = new GlyphEditorWidget(*mutable_font, this);
m_glyph_editor_widget = new GlyphEditorWidget(*m_edited_font, this);
m_glyph_editor_widget->move_to({ 5, 5 });
auto* save_button = new GButton(this);
save_button->set_caption("Save");
save_button->set_relative_rect({ 5, 135, 140, 20 });
save_button->on_click = [this] (GButton&) {
m_edited_font->write_to_file("/saved.font");
};
auto* label = new GLabel(this);
label->set_relative_rect({ 5, 110, 140, 20 });

View file

@ -12,6 +12,8 @@ public:
virtual ~FontEditorWidget() override;
private:
RetainPtr<Font> m_edited_font;
GlyphMapWidget* m_glyph_map_widget { nullptr };
GlyphEditorWidget* m_glyph_editor_widget { nullptr };
};

View file

@ -2,6 +2,15 @@
#include "Peanut8x10.h"
#include "Liza8x10.h"
#include <AK/kmalloc.h>
#include <AK/BufferStream.h>
#include <AK/StdLibExtras.h>
#ifdef USERLAND
#include <LibC/unistd.h>
#include <LibC/stdio.h>
#include <LibC/fcntl.h>
#include <LibC/errno.h>
#endif
#define DEFAULT_FONT_NAME Liza8x10
@ -29,8 +38,15 @@ void Font::initialize()
Font& Font::default_font()
{
if (!s_default_font)
s_default_font = adopt(*new Font(DEFAULT_FONT_NAME::glyphs, DEFAULT_FONT_NAME::glyph_width, DEFAULT_FONT_NAME::glyph_height, DEFAULT_FONT_NAME::first_glyph, DEFAULT_FONT_NAME::last_glyph)).leak_ref();
if (!s_default_font) {
#ifdef USERLAND
s_default_font = Font::load_from_file("/res/fonts/Liza8x10.font").leak_ref();
ASSERT(s_default_font);
#else
s_default_font = adopt(*new Font(DEFAULT_FONT_NAME::name, DEFAULT_FONT_NAME::glyphs, DEFAULT_FONT_NAME::glyph_width, DEFAULT_FONT_NAME::glyph_height, DEFAULT_FONT_NAME::first_glyph, DEFAULT_FONT_NAME::last_glyph)).leak_ref();
#endif
}
return *s_default_font;
}
@ -47,11 +63,12 @@ RetainPtr<Font> Font::clone() const
memset(new_glyphs[i], ' ', bytes_per_glyph);
}
}
return adopt(*new Font(new_glyphs, m_glyph_width, m_glyph_height, 0, 255));
return adopt(*new Font(m_name, new_glyphs, m_glyph_width, m_glyph_height, 0, 255));
}
Font::Font(const char* const* glyphs, byte glyph_width, byte glyph_height, byte first_glyph, byte last_glyph)
: m_glyphs(glyphs)
Font::Font(const String& name, const char* const* glyphs, byte glyph_width, byte glyph_height, byte first_glyph, byte last_glyph)
: m_name(name)
, m_glyphs(glyphs)
, m_glyph_width(glyph_width)
, m_glyph_height(glyph_height)
, m_first_glyph(first_glyph)
@ -73,3 +90,108 @@ Font::Font(const char* const* glyphs, byte glyph_width, byte glyph_height, byte
Font::~Font()
{
}
#ifdef USERLAND
struct FontFileHeader {
char magic[4];
byte glyph_width;
byte glyph_height;
byte type;
byte unused[7];
char name[64];
} PACKED;
RetainPtr<Font> Font::load_from_file(const String& path)
{
int fd = open(path.characters(), O_RDONLY, 0644);
if (fd < 0) {
dbgprintf("open(%s) got fd=%d, failed: %s\n", path.characters(), fd, strerror(errno));
perror("open");
return nullptr;
}
FontFileHeader header;
ssize_t nread = read(fd, &header, sizeof(FontFileHeader));
if (nread != sizeof(FontFileHeader)) {
dbgprintf("nread != sizeof(FontFileHeader)=%u\n", sizeof(FontFileHeader));
return nullptr;
}
if (memcmp(header.magic, "!Fnt", 4)) {
dbgprintf("header.magic != '!Fnt'\n");
return nullptr;
}
if (header.name[63] != '\0') {
dbgprintf("Font name not fully null-terminated\n");
return nullptr;
}
char** new_glyphs = static_cast<char**>(kmalloc(sizeof(char*) * 256));
for (unsigned glyph_index = 0; glyph_index < 256; ++glyph_index) {
new_glyphs[glyph_index] = static_cast<char*>(kmalloc(header.glyph_width * header.glyph_height));
char* bitptr = new_glyphs[glyph_index];
for (unsigned y = 0; y < header.glyph_height; ++y) {
unsigned pattern;
ssize_t nread = read(fd, &pattern, sizeof(unsigned));
if (nread != sizeof(unsigned)) {
// FIXME: free() things and return nullptr.
ASSERT_NOT_REACHED();
}
for (unsigned x = 0; x < header.glyph_width; ++x) {
if (pattern & (1u << x)) {
*(bitptr++) = '#';
} else {
*(bitptr++) = ' ';
}
}
}
}
return adopt(*new Font(String(header.name), new_glyphs, header.glyph_width, header.glyph_height, 0, 255));
}
bool Font::write_to_file(const String& path)
{
int fd = open(path.characters(), O_WRONLY | O_CREAT, 0644);
if (fd < 0) {
perror("open");
return false;
}
FontFileHeader header;
memset(&header, 0, sizeof(FontFileHeader));
memcpy(header.magic, "!Fnt", 4);
header.glyph_width = m_glyph_width;
header.glyph_height = m_glyph_height;
header.type = 0;
memcpy(header.name, m_name.characters(), min(m_name.length(), 63u));
size_t bytes_per_glyph = sizeof(unsigned) * m_glyph_height;
auto buffer = ByteBuffer::create_uninitialized(sizeof(FontFileHeader) + (256 * bytes_per_glyph));
BufferStream stream(buffer);
stream << ByteBuffer::wrap((byte*)&header, sizeof(FontFileHeader));
for (unsigned glyph_index = 0; glyph_index < 256; ++glyph_index) {
auto* glyph_bits = (byte*)m_glyphs[glyph_index];
for (unsigned y = 0; y < m_glyph_height; ++y) {
unsigned pattern = 0;
for (unsigned x = 0; x < m_glyph_width; ++x) {
if (glyph_bits[y * m_glyph_width + x] == '#') {
pattern |= 1 << x;
}
}
stream << pattern;
}
}
ASSERT(stream.at_end());
ssize_t nwritten = write(fd, buffer.pointer(), buffer.size());
ASSERT(nwritten == (ssize_t)buffer.size());
int rc = close(fd);
ASSERT(rc == 0);
return true;
}
#endif

View file

@ -3,6 +3,7 @@
#include "CharacterBitmap.h"
#include <AK/Retainable.h>
#include <AK/RetainPtr.h>
#include <AK/AKString.h>
#include <AK/Types.h>
class Font : public Retainable<Font> {
@ -11,6 +12,11 @@ public:
RetainPtr<Font> clone() const;
#ifdef USERLAND
static RetainPtr<Font> load_from_file(const String& path);
bool write_to_file(const String& path);
#endif
~Font();
const CharacterBitmap& glyph_bitmap(char ch) const { return *m_bitmaps[(byte)ch]; }
@ -21,7 +27,9 @@ public:
static void initialize();
private:
Font(const char* const* glyphs, byte glyph_width, byte glyph_height, byte first_glyph, byte last_glyph);
Font(const String& name, const char* const* glyphs, byte glyph_width, byte glyph_height, byte first_glyph, byte last_glyph);
String m_name;
const char* const* m_glyphs { nullptr };
mutable RetainPtr<CharacterBitmap> m_bitmaps[256];

View file

@ -2,6 +2,8 @@
namespace Liza8x10 {
static constexpr char name[64] = "Old Liza";
static constexpr char first_glyph = '!';
static constexpr char last_glyph = '~';
static constexpr byte glyph_width = 8;