LibGfx: Introduce provisional font interface

Old font functionality has been moved into BitmapFont
and an abstract Font interface has been introduced to
faciliate further development of TTF font integration.
This commit is contained in:
Stephan Unverwerth 2020-12-31 14:01:59 +01:00 committed by Andreas Kling
parent ac50bc79e4
commit bb27b212de
Notes: sideshowbarker 2024-07-19 00:17:45 +09:00
13 changed files with 542 additions and 416 deletions

View File

@ -38,11 +38,11 @@
#include <LibGUI/SpinBox.h>
#include <LibGUI/TextBox.h>
#include <LibGUI/Window.h>
#include <LibGfx/Font.h>
#include <LibGfx/BitmapFont.h>
#include <LibGfx/Palette.h>
#include <stdlib.h>
FontEditorWidget::FontEditorWidget(const String& path, RefPtr<Gfx::Font>&& edited_font)
FontEditorWidget::FontEditorWidget(const String& path, RefPtr<Gfx::BitmapFont>&& edited_font)
: m_edited_font(move(edited_font))
, m_path(path)
{

View File

@ -28,6 +28,7 @@
#include <AK/Function.h>
#include <LibGUI/Widget.h>
#include <LibGfx/BitmapFont.h>
class GlyphEditorWidget;
class GlyphMapWidget;
@ -45,8 +46,8 @@ public:
const String& path() { return m_path; }
private:
FontEditorWidget(const String& path, RefPtr<Gfx::Font>&&);
RefPtr<Gfx::Font> m_edited_font;
FontEditorWidget(const String& path, RefPtr<Gfx::BitmapFont>&&);
RefPtr<Gfx::BitmapFont> m_edited_font;
RefPtr<GlyphMapWidget> m_glyph_map_widget;
RefPtr<GlyphEditorWidget> m_glyph_editor_widget;

View File

@ -26,10 +26,10 @@
#include "GlyphEditorWidget.h"
#include <LibGUI/Painter.h>
#include <LibGfx/Font.h>
#include <LibGfx/BitmapFont.h>
#include <LibGfx/Palette.h>
GlyphEditorWidget::GlyphEditorWidget(Gfx::Font& mutable_font)
GlyphEditorWidget::GlyphEditorWidget(Gfx::BitmapFont& mutable_font)
: m_font(mutable_font)
{
set_relative_rect({ 0, 0, preferred_width(), preferred_height() });

View File

@ -28,6 +28,7 @@
#include <AK/Function.h>
#include <LibGUI/Frame.h>
#include <LibGfx/BitmapFont.h>
class GlyphEditorWidget final : public GUI::Frame {
C_OBJECT(GlyphEditorWidget)
@ -40,20 +41,20 @@ public:
int preferred_width() const;
int preferred_height() const;
Gfx::Font& font() { return *m_font; }
const Gfx::Font& font() const { return *m_font; }
Gfx::BitmapFont& font() { return *m_font; }
const Gfx::BitmapFont& font() const { return *m_font; }
Function<void(u8)> on_glyph_altered;
private:
GlyphEditorWidget(Gfx::Font&);
GlyphEditorWidget(Gfx::BitmapFont&);
virtual void paint_event(GUI::PaintEvent&) override;
virtual void mousedown_event(GUI::MouseEvent&) override;
virtual void mousemove_event(GUI::MouseEvent&) override;
void draw_at_mouse(const GUI::MouseEvent&);
RefPtr<Gfx::Font> m_font;
RefPtr<Gfx::BitmapFont> m_font;
int m_glyph { 0 };
int m_scale { 10 };
};

View File

@ -26,10 +26,10 @@
#include "GlyphMapWidget.h"
#include <LibGUI/Painter.h>
#include <LibGfx/Font.h>
#include <LibGfx/BitmapFont.h>
#include <LibGfx/Palette.h>
GlyphMapWidget::GlyphMapWidget(Gfx::Font& mutable_font)
GlyphMapWidget::GlyphMapWidget(Gfx::BitmapFont& mutable_font)
: m_font(mutable_font)
{
m_glyph_count = mutable_font.glyph_count();

View File

@ -29,6 +29,7 @@
#include <AK/Function.h>
#include <AK/StdLibExtras.h>
#include <LibGUI/Frame.h>
#include <LibGfx/BitmapFont.h>
class GlyphMapWidget final : public GUI::Frame {
C_OBJECT(GlyphMapWidget)
@ -44,22 +45,22 @@ public:
int preferred_width() const;
int preferred_height() const;
Gfx::Font& font() { return *m_font; }
const Gfx::Font& font() const { return *m_font; }
Gfx::BitmapFont& font() { return *m_font; }
const Gfx::BitmapFont& font() const { return *m_font; }
void update_glyph(int);
Function<void(int)> on_glyph_selected;
private:
explicit GlyphMapWidget(Gfx::Font&);
explicit GlyphMapWidget(Gfx::BitmapFont&);
virtual void paint_event(GUI::PaintEvent&) override;
virtual void mousedown_event(GUI::MouseEvent&) override;
virtual void keydown_event(GUI::KeyEvent&) override;
Gfx::IntRect get_outer_rect(int glyph) const;
RefPtr<Gfx::Font> m_font;
RefPtr<Gfx::BitmapFont> m_font;
int m_glyph_count;
int m_columns { 32 };
int m_horizontal_spacing { 2 };

View File

@ -36,7 +36,7 @@
#include <LibGUI/MessageBox.h>
#include <LibGUI/Window.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/Font.h>
#include <LibGfx/BitmapFont.h>
#include <LibGfx/FontDatabase.h>
#include <LibGfx/Point.h>
#include <stdio.h>
@ -60,12 +60,12 @@ int main(int argc, char** argv)
args_parser.add_positional_argument(path, "The font file for editing.", "file", Core::ArgsParser::Required::No);
args_parser.parse(argc, argv);
RefPtr<Gfx::Font> edited_font;
RefPtr<Gfx::BitmapFont> edited_font;
if (path == nullptr) {
path = "/tmp/saved.font";
edited_font = Gfx::FontDatabase::default_font().clone();
edited_font = static_ptr_cast<Gfx::BitmapFont>(Gfx::FontDatabase::default_font().clone());
} else {
edited_font = Gfx::Font::load_from_file(path)->clone();
edited_font = static_ptr_cast<Gfx::BitmapFont>(Gfx::Font::load_from_file(path)->clone());
if (!edited_font) {
String message = String::formatted("Couldn't load font: {}\n", path);
GUI::MessageBox::show(nullptr, message, "Font Editor", GUI::MessageBox::Type::Error);
@ -78,7 +78,7 @@ int main(int argc, char** argv)
auto window = GUI::Window::construct();
window->set_icon(app_icon.bitmap_for_size(16));
auto set_edited_font = [&](const String& path, RefPtr<Gfx::Font>&& font, Gfx::IntPoint point) {
auto set_edited_font = [&](const String& path, RefPtr<Gfx::BitmapFont>&& font, Gfx::IntPoint point) {
// Convert 256 char font to 384 char font.
if (font->type() == Gfx::FontTypes::Default)
font->set_type(Gfx::FontTypes::LatinExtendedA);
@ -97,7 +97,7 @@ int main(int argc, char** argv)
if (!open_path.has_value())
return;
RefPtr<Gfx::Font> new_font = Gfx::Font::load_from_file(open_path.value())->clone();
RefPtr<Gfx::BitmapFont> new_font = static_ptr_cast<Gfx::BitmapFont>(Gfx::Font::load_from_file(open_path.value())->clone());
if (!new_font) {
String message = String::formatted("Couldn't load font: {}\n", open_path.value());
GUI::MessageBox::show(window, message, "Font Editor", GUI::MessageBox::Type::Error);

View File

@ -0,0 +1,325 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "BitmapFont.h"
#include "Bitmap.h"
#include "Emoji.h"
#include <AK/MappedFile.h>
#include <AK/StdLibExtras.h>
#include <AK/StringBuilder.h>
#include <AK/Utf32View.h>
#include <AK/Utf8View.h>
#include <AK/Vector.h>
#include <AK/kmalloc.h>
#include <LibCore/FileStream.h>
#include <LibGfx/FontDatabase.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
namespace Gfx {
struct [[gnu::packed]] FontFileHeader {
char magic[4];
u8 glyph_width;
u8 glyph_height;
u8 type;
u8 is_variable_width;
u8 glyph_spacing;
u8 baseline;
u8 mean_line;
u8 presentation_size;
u16 weight;
char name[32];
char family[32];
};
NonnullRefPtr<Font> BitmapFont::clone() const
{
size_t bytes_per_glyph = sizeof(u32) * glyph_height();
auto* new_rows = static_cast<unsigned*>(malloc(bytes_per_glyph * m_glyph_count));
memcpy(new_rows, m_rows, bytes_per_glyph * m_glyph_count);
auto* new_widths = static_cast<u8*>(malloc(m_glyph_count));
if (m_glyph_widths)
memcpy(new_widths, m_glyph_widths, m_glyph_count);
else
memset(new_widths, m_glyph_width, m_glyph_count);
return adopt(*new BitmapFont(m_name, m_family, new_rows, new_widths, m_fixed_width, m_glyph_width, m_glyph_height, m_glyph_spacing, m_type, m_baseline, m_mean_line, m_presentation_size, m_weight, true));
}
NonnullRefPtr<BitmapFont> BitmapFont::create(u8 glyph_height, u8 glyph_width, bool fixed, FontTypes type)
{
size_t bytes_per_glyph = sizeof(u32) * glyph_height;
size_t count = glyph_count_by_type(type);
auto* new_rows = static_cast<unsigned*>(malloc(bytes_per_glyph * count));
memset(new_rows, 0, bytes_per_glyph * count);
auto* new_widths = static_cast<u8*>(malloc(count));
memset(new_widths, glyph_width, count);
return adopt(*new BitmapFont("Untitled", "Untitled", new_rows, new_widths, fixed, glyph_width, glyph_height, 1, type, 0, 0, 0, 400, true));
}
BitmapFont::BitmapFont(String name, String family, unsigned* rows, u8* widths, bool is_fixed_width, u8 glyph_width, u8 glyph_height, u8 glyph_spacing, FontTypes type, u8 baseline, u8 mean_line, u8 presentation_size, u16 weight, bool owns_arrays)
: m_name(name)
, m_family(family)
, m_type(type)
, m_rows(rows)
, m_glyph_widths(widths)
, m_glyph_width(glyph_width)
, m_glyph_height(glyph_height)
, m_min_glyph_width(glyph_width)
, m_max_glyph_width(glyph_width)
, m_glyph_spacing(glyph_spacing)
, m_baseline(baseline)
, m_mean_line(mean_line)
, m_presentation_size(presentation_size)
, m_weight(weight)
, m_fixed_width(is_fixed_width)
, m_owns_arrays(owns_arrays)
{
update_x_height();
m_glyph_count = glyph_count_by_type(m_type);
if (!m_fixed_width) {
u8 maximum = 0;
u8 minimum = 255;
for (size_t i = 0; i < m_glyph_count; ++i) {
minimum = min(minimum, m_glyph_widths[i]);
maximum = max(maximum, m_glyph_widths[i]);
}
m_min_glyph_width = minimum;
m_max_glyph_width = maximum;
}
}
BitmapFont::~BitmapFont()
{
if (m_owns_arrays) {
free(m_glyph_widths);
free(m_rows);
}
}
RefPtr<BitmapFont> BitmapFont::load_from_memory(const u8* data)
{
auto& header = *reinterpret_cast<const FontFileHeader*>(data);
if (memcmp(header.magic, "!Fnt", 4)) {
dbgprintf("header.magic != '!Fnt', instead it's '%c%c%c%c'\n", header.magic[0], header.magic[1], header.magic[2], header.magic[3]);
return nullptr;
}
if (header.name[sizeof(header.name) - 1] != '\0') {
dbgprintf("Font name not fully null-terminated\n");
return nullptr;
}
if (header.family[sizeof(header.family) - 1] != '\0') {
dbgprintf("Font family not fully null-terminated\n");
return nullptr;
}
FontTypes type;
if (header.type == 0)
type = FontTypes::Default;
else if (header.type == 1)
type = FontTypes::LatinExtendedA;
else
ASSERT_NOT_REACHED();
size_t count = glyph_count_by_type(type);
size_t bytes_per_glyph = sizeof(unsigned) * header.glyph_height;
auto* rows = const_cast<unsigned*>((const unsigned*)(data + sizeof(FontFileHeader)));
u8* widths = nullptr;
if (header.is_variable_width)
widths = (u8*)(rows) + count * bytes_per_glyph;
return adopt(*new BitmapFont(String(header.name), String(header.family), rows, widths, !header.is_variable_width, header.glyph_width, header.glyph_height, header.glyph_spacing, type, header.baseline, header.mean_line, header.presentation_size, header.weight));
}
size_t BitmapFont::glyph_count_by_type(FontTypes type)
{
if (type == FontTypes::Default)
return 256;
if (type == FontTypes::LatinExtendedA)
return 384;
dbg() << "Unknown font type:" << type;
ASSERT_NOT_REACHED();
}
RefPtr<BitmapFont> BitmapFont::load_from_file(const StringView& path)
{
MappedFile mapped_file(path);
if (!mapped_file.is_valid())
return nullptr;
auto font = load_from_memory((const u8*)mapped_file.data());
if (!font)
return nullptr;
font->m_mapped_file = move(mapped_file);
return font;
}
bool BitmapFont::write_to_file(const StringView& path)
{
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 = m_type;
header.baseline = m_baseline;
header.mean_line = m_mean_line;
header.is_variable_width = !m_fixed_width;
header.glyph_spacing = m_glyph_spacing;
header.presentation_size = m_presentation_size;
header.weight = m_weight;
memcpy(header.name, m_name.characters(), min(m_name.length(), sizeof(header.name) - 1));
memcpy(header.family, m_family.characters(), min(m_family.length(), sizeof(header.family) - 1));
size_t bytes_per_glyph = sizeof(unsigned) * m_glyph_height;
size_t count = glyph_count_by_type(m_type);
auto stream_result = Core::OutputFileStream::open_buffered(path);
if (stream_result.is_error())
return false;
auto& stream = stream_result.value();
stream << ReadonlyBytes { &header, sizeof(header) };
stream << ReadonlyBytes { m_rows, count * bytes_per_glyph };
stream << ReadonlyBytes { m_glyph_widths, count };
stream.flush();
if (stream.handle_any_error())
return false;
return true;
}
GlyphBitmap BitmapFont::glyph_bitmap(u32 code_point) const
{
return GlyphBitmap(&m_rows[code_point * m_glyph_height], { glyph_width(code_point), m_glyph_height });
}
int BitmapFont::glyph_or_emoji_width(u32 code_point) const
{
if (code_point < m_glyph_count)
return glyph_width(code_point);
if (m_fixed_width)
return m_glyph_width;
auto* emoji = Emoji::emoji_for_code_point(code_point);
if (emoji == nullptr)
return glyph_width('?');
return emoji->size().width();
}
int BitmapFont::width(const StringView& string) const
{
Utf8View utf8 { string };
return width(utf8);
}
int BitmapFont::width(const Utf8View& utf8) const
{
bool first = true;
int width = 0;
for (u32 code_point : utf8) {
if (!first)
width += glyph_spacing();
first = false;
width += glyph_or_emoji_width(code_point);
}
return width;
}
int BitmapFont::width(const Utf32View& view) const
{
if (view.length() == 0)
return 0;
int width = (view.length() - 1) * glyph_spacing();
for (size_t i = 0; i < view.length(); ++i)
width += glyph_or_emoji_width(view.code_points()[i]);
return width;
}
void BitmapFont::set_type(FontTypes type)
{
if (type == m_type)
return;
if (type == FontTypes::Default)
return;
size_t new_glyph_count = glyph_count_by_type(type);
if (new_glyph_count <= m_glyph_count) {
m_glyph_count = new_glyph_count;
return;
}
int item_count_to_copy = min(m_glyph_count, new_glyph_count);
size_t bytes_per_glyph = sizeof(u32) * glyph_height();
auto* new_rows = static_cast<unsigned*>(kmalloc(bytes_per_glyph * new_glyph_count));
memset(new_rows, (unsigned)0, bytes_per_glyph * new_glyph_count);
memcpy(new_rows, m_rows, bytes_per_glyph * item_count_to_copy);
auto* new_widths = static_cast<u8*>(kmalloc(new_glyph_count));
memset(new_widths, (u8)0, new_glyph_count);
memcpy(new_widths, m_glyph_widths, item_count_to_copy);
kfree(m_rows);
kfree(m_glyph_widths);
m_type = type;
m_glyph_count = new_glyph_count;
m_rows = new_rows;
m_glyph_widths = new_widths;
}
String BitmapFont::qualified_name() const
{
return String::formatted("{} {} {}", family(), presentation_size(), weight());
}
const Font& BitmapFont::bold_variant() const
{
if (m_bold_variant)
return *m_bold_variant;
m_bold_variant = Gfx::FontDatabase::the().get(m_family, m_presentation_size, 700);
if (!m_bold_variant)
m_bold_variant = this;
return *m_bold_variant;
}
}

View File

@ -0,0 +1,150 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/MappedFile.h>
#include <AK/RefCounted.h>
#include <AK/RefPtr.h>
#include <AK/String.h>
#include <AK/Types.h>
#include <LibGfx/Font.h>
#include <LibGfx/Size.h>
namespace Gfx {
enum FontTypes {
Default = 0,
LatinExtendedA = 1
};
class BitmapFont : public Font {
public:
NonnullRefPtr<Font> clone() const;
static NonnullRefPtr<BitmapFont> create(u8 glyph_height, u8 glyph_width, bool fixed, FontTypes type);
static RefPtr<BitmapFont> load_from_file(const StringView& path);
bool write_to_file(const StringView& path);
~BitmapFont();
u8 presentation_size() const { return m_presentation_size; }
void set_presentation_size(u8 size) { m_presentation_size = size; }
u16 weight() const { return m_weight; }
void set_weight(u16 weight) { m_weight = weight; }
GlyphBitmap glyph_bitmap(u32 code_point) const;
u8 glyph_width(size_t ch) const { return m_fixed_width ? m_glyph_width : m_glyph_widths[ch]; }
int glyph_or_emoji_width(u32 code_point) const;
u8 glyph_height() const { return m_glyph_height; }
int x_height() const { return m_x_height; }
u8 min_glyph_width() const { return m_min_glyph_width; }
u8 max_glyph_width() const { return m_max_glyph_width; }
u8 glyph_fixed_width() const { return m_glyph_width; }
u8 baseline() const { return m_baseline; }
void set_baseline(u8 baseline)
{
m_baseline = baseline;
update_x_height();
}
u8 mean_line() const { return m_mean_line; }
void set_mean_line(u8 mean_line)
{
m_mean_line = mean_line;
update_x_height();
}
int width(const StringView&) const;
int width(const Utf8View&) const;
int width(const Utf32View&) const;
const String& name() const { return m_name; }
void set_name(String name) { m_name = move(name); }
bool is_fixed_width() const { return m_fixed_width; }
void set_fixed_width(bool b) { m_fixed_width = b; }
u8 glyph_spacing() const { return m_glyph_spacing; }
void set_glyph_spacing(u8 spacing) { m_glyph_spacing = spacing; }
void set_glyph_width(size_t ch, u8 width)
{
ASSERT(m_glyph_widths);
m_glyph_widths[ch] = width;
}
int glyph_count() const { return m_glyph_count; }
FontTypes type() { return m_type; }
void set_type(FontTypes type);
const String& family() const { return m_family; }
void set_family(String family) { m_family = move(family); }
String qualified_name() const;
const Font& bold_variant() const;
private:
BitmapFont(String name, String family, unsigned* rows, u8* widths, bool is_fixed_width, u8 glyph_width, u8 glyph_height, u8 glyph_spacing, FontTypes type, u8 baseline, u8 mean_line, u8 presentation_size, u16 weight, bool owns_arrays = false);
static RefPtr<BitmapFont> load_from_memory(const u8*);
static size_t glyph_count_by_type(FontTypes type);
void update_x_height() { m_x_height = m_baseline - m_mean_line; };
String m_name;
String m_family;
FontTypes m_type;
size_t m_glyph_count { 256 };
unsigned* m_rows { nullptr };
u8* m_glyph_widths { nullptr };
MappedFile m_mapped_file;
u8 m_glyph_width { 0 };
u8 m_glyph_height { 0 };
u8 m_x_height { 0 };
u8 m_min_glyph_width { 0 };
u8 m_max_glyph_width { 0 };
u8 m_glyph_spacing { 0 };
u8 m_baseline { 0 };
u8 m_mean_line { 0 };
u8 m_presentation_size { 0 };
u16 m_weight { 0 };
bool m_fixed_width { false };
bool m_owns_arrays { false };
mutable RefPtr<Gfx::Font> m_bold_variant;
};
}

View File

@ -1,6 +1,7 @@
set(SOURCES
AffineTransform.cpp
Bitmap.cpp
BitmapFont.cpp
BMPLoader.cpp
BMPWriter.cpp
CharacterBitmap.cpp

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@gmx.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -24,302 +24,17 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "Font.h"
#include "Bitmap.h"
#include "Emoji.h"
#include <AK/MappedFile.h>
#include <AK/StdLibExtras.h>
#include <AK/StringBuilder.h>
#include <AK/Utf32View.h>
#include <AK/Utf8View.h>
#include <AK/Vector.h>
#include <AK/kmalloc.h>
#include <LibCore/FileStream.h>
#include <LibGfx/FontDatabase.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <LibGfx/BitmapFont.h>
#include <LibGfx/Font.h>
namespace Gfx {
struct [[gnu::packed]] FontFileHeader {
char magic[4];
u8 glyph_width;
u8 glyph_height;
u8 type;
u8 is_variable_width;
u8 glyph_spacing;
u8 baseline;
u8 mean_line;
u8 presentation_size;
u16 weight;
char name[32];
char family[32];
};
NonnullRefPtr<Font> Font::clone() const
{
size_t bytes_per_glyph = sizeof(u32) * glyph_height();
auto* new_rows = static_cast<unsigned*>(malloc(bytes_per_glyph * m_glyph_count));
memcpy(new_rows, m_rows, bytes_per_glyph * m_glyph_count);
auto* new_widths = static_cast<u8*>(malloc(m_glyph_count));
if (m_glyph_widths)
memcpy(new_widths, m_glyph_widths, m_glyph_count);
else
memset(new_widths, m_glyph_width, m_glyph_count);
return adopt(*new Font(m_name, m_family, new_rows, new_widths, m_fixed_width, m_glyph_width, m_glyph_height, m_glyph_spacing, m_type, m_baseline, m_mean_line, m_presentation_size, m_weight, true));
}
NonnullRefPtr<Font> Font::create(u8 glyph_height, u8 glyph_width, bool fixed, FontTypes type)
{
size_t bytes_per_glyph = sizeof(u32) * glyph_height;
size_t count = glyph_count_by_type(type);
auto* new_rows = static_cast<unsigned*>(malloc(bytes_per_glyph * count));
memset(new_rows, 0, bytes_per_glyph * count);
auto* new_widths = static_cast<u8*>(malloc(count));
memset(new_widths, glyph_width, count);
return adopt(*new Font("Untitled", "Untitled", new_rows, new_widths, fixed, glyph_width, glyph_height, 1, type, 0, 0, 0, 400, true));
}
Font::Font(String name, String family, unsigned* rows, u8* widths, bool is_fixed_width, u8 glyph_width, u8 glyph_height, u8 glyph_spacing, FontTypes type, u8 baseline, u8 mean_line, u8 presentation_size, u16 weight, bool owns_arrays)
: m_name(name)
, m_family(family)
, m_type(type)
, m_rows(rows)
, m_glyph_widths(widths)
, m_glyph_width(glyph_width)
, m_glyph_height(glyph_height)
, m_min_glyph_width(glyph_width)
, m_max_glyph_width(glyph_width)
, m_glyph_spacing(glyph_spacing)
, m_baseline(baseline)
, m_mean_line(mean_line)
, m_presentation_size(presentation_size)
, m_weight(weight)
, m_fixed_width(is_fixed_width)
, m_owns_arrays(owns_arrays)
{
update_x_height();
m_glyph_count = glyph_count_by_type(m_type);
if (!m_fixed_width) {
u8 maximum = 0;
u8 minimum = 255;
for (size_t i = 0; i < m_glyph_count; ++i) {
minimum = min(minimum, m_glyph_widths[i]);
maximum = max(maximum, m_glyph_widths[i]);
}
m_min_glyph_width = minimum;
m_max_glyph_width = maximum;
}
}
Font::~Font()
{
if (m_owns_arrays) {
free(m_glyph_widths);
free(m_rows);
}
}
RefPtr<Font> Font::load_from_memory(const u8* data)
{
auto& header = *reinterpret_cast<const FontFileHeader*>(data);
if (memcmp(header.magic, "!Fnt", 4)) {
dbgprintf("header.magic != '!Fnt', instead it's '%c%c%c%c'\n", header.magic[0], header.magic[1], header.magic[2], header.magic[3]);
return nullptr;
}
if (header.name[sizeof(header.name) - 1] != '\0') {
dbgprintf("Font name not fully null-terminated\n");
return nullptr;
}
if (header.family[sizeof(header.family) - 1] != '\0') {
dbgprintf("Font family not fully null-terminated\n");
return nullptr;
}
FontTypes type;
if (header.type == 0)
type = FontTypes::Default;
else if (header.type == 1)
type = FontTypes::LatinExtendedA;
else
ASSERT_NOT_REACHED();
size_t count = glyph_count_by_type(type);
size_t bytes_per_glyph = sizeof(unsigned) * header.glyph_height;
auto* rows = const_cast<unsigned*>((const unsigned*)(data + sizeof(FontFileHeader)));
u8* widths = nullptr;
if (header.is_variable_width)
widths = (u8*)(rows) + count * bytes_per_glyph;
return adopt(*new Font(String(header.name), String(header.family), rows, widths, !header.is_variable_width, header.glyph_width, header.glyph_height, header.glyph_spacing, type, header.baseline, header.mean_line, header.presentation_size, header.weight));
}
size_t Font::glyph_count_by_type(FontTypes type)
{
if (type == FontTypes::Default)
return 256;
if (type == FontTypes::LatinExtendedA)
return 384;
dbg() << "Unknown font type:" << type;
ASSERT_NOT_REACHED();
}
RefPtr<Font> Font::load_from_file(const StringView& path)
{
MappedFile mapped_file(path);
if (!mapped_file.is_valid())
return nullptr;
auto font = load_from_memory((const u8*)mapped_file.data());
if (!font)
return nullptr;
font->m_mapped_file = move(mapped_file);
return font;
}
bool Font::write_to_file(const StringView& path)
{
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 = m_type;
header.baseline = m_baseline;
header.mean_line = m_mean_line;
header.is_variable_width = !m_fixed_width;
header.glyph_spacing = m_glyph_spacing;
header.presentation_size = m_presentation_size;
header.weight = m_weight;
memcpy(header.name, m_name.characters(), min(m_name.length(), sizeof(header.name) - 1));
memcpy(header.family, m_family.characters(), min(m_family.length(), sizeof(header.family) - 1));
size_t bytes_per_glyph = sizeof(unsigned) * m_glyph_height;
size_t count = glyph_count_by_type(m_type);
auto stream_result = Core::OutputFileStream::open_buffered(path);
if (stream_result.is_error())
return false;
auto& stream = stream_result.value();
stream << ReadonlyBytes { &header, sizeof(header) };
stream << ReadonlyBytes { m_rows, count * bytes_per_glyph };
stream << ReadonlyBytes { m_glyph_widths, count };
stream.flush();
if (stream.handle_any_error())
return false;
return true;
}
GlyphBitmap Font::glyph_bitmap(u32 code_point) const
{
return GlyphBitmap(&m_rows[code_point * m_glyph_height], { glyph_width(code_point), m_glyph_height });
}
int Font::glyph_or_emoji_width(u32 code_point) const
{
if (code_point < m_glyph_count)
return glyph_width(code_point);
if (m_fixed_width)
return m_glyph_width;
auto* emoji = Emoji::emoji_for_code_point(code_point);
if (emoji == nullptr)
return glyph_width('?');
return emoji->size().width();
}
int Font::width(const StringView& string) const
{
Utf8View utf8 { string };
return width(utf8);
}
int Font::width(const Utf8View& utf8) const
{
bool first = true;
int width = 0;
for (u32 code_point : utf8) {
if (!first)
width += glyph_spacing();
first = false;
width += glyph_or_emoji_width(code_point);
if (path.ends_with(".font")) {
return BitmapFont::load_from_file(path);
}
return width;
return {};
}
int Font::width(const Utf32View& view) const
{
if (view.length() == 0)
return 0;
int width = (view.length() - 1) * glyph_spacing();
for (size_t i = 0; i < view.length(); ++i)
width += glyph_or_emoji_width(view.code_points()[i]);
return width;
}
void Font::set_type(FontTypes type)
{
if (type == m_type)
return;
if (type == FontTypes::Default)
return;
size_t new_glyph_count = glyph_count_by_type(type);
if (new_glyph_count <= m_glyph_count) {
m_glyph_count = new_glyph_count;
return;
}
int item_count_to_copy = min(m_glyph_count, new_glyph_count);
size_t bytes_per_glyph = sizeof(u32) * glyph_height();
auto* new_rows = static_cast<unsigned*>(kmalloc(bytes_per_glyph * new_glyph_count));
memset(new_rows, (unsigned)0, bytes_per_glyph * new_glyph_count);
memcpy(new_rows, m_rows, bytes_per_glyph * item_count_to_copy);
auto* new_widths = static_cast<u8*>(kmalloc(new_glyph_count));
memset(new_widths, (u8)0, new_glyph_count);
memcpy(new_widths, m_glyph_widths, item_count_to_copy);
kfree(m_rows);
kfree(m_glyph_widths);
m_type = type;
m_glyph_count = new_glyph_count;
m_rows = new_rows;
m_glyph_widths = new_widths;
}
String Font::qualified_name() const
{
return String::formatted("{} {} {}", family(), presentation_size(), weight());
}
const Font& Font::bold_variant() const
{
if (m_bold_variant)
return *m_bold_variant;
m_bold_variant = Gfx::FontDatabase::the().get(m_family, m_presentation_size, 700);
if (!m_bold_variant)
m_bold_variant = this;
return *m_bold_variant;
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@gmx.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -35,14 +35,9 @@
namespace Gfx {
enum FontTypes {
Default = 0,
LatinExtendedA = 1
};
// FIXME: Make a MutableGlyphBitmap buddy class for FontEditor instead?
class GlyphBitmap {
friend class Font;
friend class BitmapFont;
public:
const unsigned* rows() const { return m_rows; }
@ -75,108 +70,45 @@ private:
class Font : public RefCounted<Font> {
public:
NonnullRefPtr<Font> clone() const;
static NonnullRefPtr<Font> create(u8 glyph_height, u8 glyph_width, bool fixed, FontTypes type);
static RefPtr<Font> load_from_file(const StringView& path);
bool write_to_file(const StringView& path);
~Font();
virtual NonnullRefPtr<Font> clone() const = 0;
virtual ~Font() {};
u8 presentation_size() const { return m_presentation_size; }
void set_presentation_size(u8 size) { m_presentation_size = size; }
virtual u8 presentation_size() const = 0;
u16 weight() const { return m_weight; }
void set_weight(u16 weight) { m_weight = weight; }
virtual u16 weight() const = 0;
virtual GlyphBitmap glyph_bitmap(u32 code_point) const = 0;
GlyphBitmap glyph_bitmap(u32 code_point) const;
virtual u8 glyph_width(size_t ch) const = 0;
virtual int glyph_or_emoji_width(u32 code_point) const = 0;
virtual u8 glyph_height() const = 0;
virtual int x_height() const = 0;
u8 glyph_width(size_t ch) const { return m_fixed_width ? m_glyph_width : m_glyph_widths[ch]; }
int glyph_or_emoji_width(u32 code_point) const;
u8 glyph_height() const { return m_glyph_height; }
int x_height() const { return m_x_height; }
virtual u8 min_glyph_width() const = 0;
virtual u8 max_glyph_width() const = 0;
virtual u8 glyph_fixed_width() const = 0;
u8 min_glyph_width() const { return m_min_glyph_width; }
u8 max_glyph_width() const { return m_max_glyph_width; }
u8 glyph_fixed_width() const { return m_glyph_width; }
virtual u8 baseline() const = 0;
virtual u8 mean_line() const = 0;
u8 baseline() const { return m_baseline; }
void set_baseline(u8 baseline)
{
m_baseline = baseline;
update_x_height();
}
virtual int width(const StringView&) const = 0;
virtual int width(const Utf8View&) const = 0;
virtual int width(const Utf32View&) const = 0;
u8 mean_line() const { return m_mean_line; }
void set_mean_line(u8 mean_line)
{
m_mean_line = mean_line;
update_x_height();
}
virtual const String& name() const = 0;
int width(const StringView&) const;
int width(const Utf8View&) const;
int width(const Utf32View&) const;
virtual bool is_fixed_width() const = 0;
const String& name() const { return m_name; }
void set_name(String name) { m_name = move(name); }
virtual u8 glyph_spacing() const = 0;
bool is_fixed_width() const { return m_fixed_width; }
void set_fixed_width(bool b) { m_fixed_width = b; }
virtual int glyph_count() const = 0;
u8 glyph_spacing() const { return m_glyph_spacing; }
void set_glyph_spacing(u8 spacing) { m_glyph_spacing = spacing; }
virtual const String& family() const = 0;
void set_glyph_width(size_t ch, u8 width)
{
ASSERT(m_glyph_widths);
m_glyph_widths[ch] = width;
}
virtual String qualified_name() const = 0;
int glyph_count() const { return m_glyph_count; }
FontTypes type() { return m_type; }
void set_type(FontTypes type);
const String& family() const { return m_family; }
void set_family(String family) { m_family = move(family); }
String qualified_name() const;
const Font& bold_variant() const;
private:
Font(String name, String family, unsigned* rows, u8* widths, bool is_fixed_width, u8 glyph_width, u8 glyph_height, u8 glyph_spacing, FontTypes type, u8 baseline, u8 mean_line, u8 presentation_size, u16 weight, bool owns_arrays = false);
static RefPtr<Font> load_from_memory(const u8*);
static size_t glyph_count_by_type(FontTypes type);
void update_x_height() { m_x_height = m_baseline - m_mean_line; };
String m_name;
String m_family;
FontTypes m_type;
size_t m_glyph_count { 256 };
unsigned* m_rows { nullptr };
u8* m_glyph_widths { nullptr };
MappedFile m_mapped_file;
u8 m_glyph_width { 0 };
u8 m_glyph_height { 0 };
u8 m_x_height { 0 };
u8 m_min_glyph_width { 0 };
u8 m_max_glyph_width { 0 };
u8 m_glyph_spacing { 0 };
u8 m_baseline { 0 };
u8 m_mean_line { 0 };
u8 m_presentation_size { 0 };
u16 m_weight { 0 };
bool m_fixed_width { false };
bool m_owns_arrays { false };
mutable RefPtr<Gfx::Font> m_bold_variant;
virtual const Font& bold_variant() const = 0;
};
}

View File

@ -24,7 +24,7 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <LibGfx/Font.h>
#include <LibGfx/BitmapFont.h>
#include <LibGfx/FontDatabase.h>
#include <stdio.h>
#include <stdlib.h>
@ -72,7 +72,7 @@ static void test_clone()
{
u8 glyph_height = 1;
u8 glyph_width = 1;
auto font = Gfx::Font::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default);
auto font = Gfx::BitmapFont::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default);
auto new_font = font->clone();
assert(!new_font->name().is_null());
@ -85,7 +85,7 @@ static void test_set_name()
{
u8 glyph_height = 1;
u8 glyph_width = 1;
auto font = Gfx::Font::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default);
auto font = Gfx::BitmapFont::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default);
const char* name = "my newly created font";
font->set_name(name);
@ -98,7 +98,7 @@ static void test_set_type()
{
u8 glyph_height = 1;
u8 glyph_width = 1;
auto font = Gfx::Font::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default);
auto font = Gfx::BitmapFont::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default);
auto type = Gfx::FontTypes::Default;
font->set_type(type);
@ -110,7 +110,7 @@ static void test_width()
{
u8 glyph_height = 1;
u8 glyph_width = 1;
auto font = Gfx::Font::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default);
auto font = Gfx::BitmapFont::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default);
assert(font->width("A") == glyph_width);
}
@ -119,7 +119,7 @@ static void test_glyph_or_emoji_width()
{
u8 glyph_height = 1;
u8 glyph_width = 1;
auto font = Gfx::Font::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default);
auto font = Gfx::BitmapFont::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default);
font->set_type(Gfx::FontTypes::Default);
assert(font->glyph_or_emoji_width(0));
@ -127,7 +127,7 @@ static void test_glyph_or_emoji_width()
static void test_load_from_file()
{
auto font = Gfx::Font::load_from_file("/res/fonts/PebbletonBold14.font");
auto font = Gfx::BitmapFont::load_from_file("/res/fonts/PebbletonBold14.font");
assert(!font->name().is_null());
}
@ -135,7 +135,7 @@ static void test_write_to_file()
{
u8 glyph_height = 1;
u8 glyph_width = 1;
auto font = Gfx::Font::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default);
auto font = Gfx::BitmapFont::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default);
char path[] = "/tmp/new.font.XXXXXX";
assert(mkstemp(path) != -1);