Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "Font.h"
28#include "Emoji.h"
29#include "Bitmap.h"
30#include <AK/BufferStream.h>
31#include <AK/MappedFile.h>
32#include <AK/StdLibExtras.h>
33#include <AK/Utf8View.h>
34#include <AK/kmalloc.h>
35#include <errno.h>
36#include <fcntl.h>
37#include <stdio.h>
38#include <sys/mman.h>
39#include <unistd.h>
40#include <stdlib.h>
41
42namespace Gfx {
43
44struct [[gnu::packed]] FontFileHeader
45{
46 char magic[4];
47 u8 glyph_width;
48 u8 glyph_height;
49 u8 type;
50 u8 is_variable_width;
51 u8 glyph_spacing;
52 u8 unused[5];
53 char name[64];
54};
55
56Font& Font::default_font()
57{
58 static Font* s_default_font;
59 static const char* default_font_path = "/res/fonts/Katica10.font";
60 if (!s_default_font) {
61 s_default_font = Font::load_from_file(default_font_path).leak_ref();
62 ASSERT(s_default_font);
63 }
64 return *s_default_font;
65}
66
67Font& Font::default_fixed_width_font()
68{
69 static Font* s_default_fixed_width_font;
70 static const char* default_fixed_width_font_path = "/res/fonts/CsillaThin7x10.font";
71 if (!s_default_fixed_width_font) {
72 s_default_fixed_width_font = Font::load_from_file(default_fixed_width_font_path).leak_ref();
73 ASSERT(s_default_fixed_width_font);
74 }
75 return *s_default_fixed_width_font;
76}
77
78Font& Font::default_bold_fixed_width_font()
79{
80 static Font* font;
81 static const char* default_bold_fixed_width_font_path = "/res/fonts/CsillaBold7x10.font";
82 if (!font) {
83 font = Font::load_from_file(default_bold_fixed_width_font_path).leak_ref();
84 ASSERT(font);
85 }
86 return *font;
87}
88
89Font& Font::default_bold_font()
90{
91 static Font* s_default_bold_font;
92 static const char* default_bold_font_path = "/res/fonts/KaticaBold10.font";
93 if (!s_default_bold_font) {
94 s_default_bold_font = Font::load_from_file(default_bold_font_path).leak_ref();
95 ASSERT(s_default_bold_font);
96 }
97 return *s_default_bold_font;
98}
99
100NonnullRefPtr<Font> Font::clone() const
101{
102 size_t bytes_per_glyph = sizeof(u32) * glyph_height();
103 // FIXME: This is leaked!
104 auto* new_rows = static_cast<unsigned*>(kmalloc(bytes_per_glyph * 256));
105 memcpy(new_rows, m_rows, bytes_per_glyph * 256);
106 auto* new_widths = static_cast<u8*>(kmalloc(256));
107 if (m_glyph_widths)
108 memcpy(new_widths, m_glyph_widths, 256);
109 else
110 memset(new_widths, m_glyph_width, 256);
111 return adopt(*new Font(m_name, new_rows, new_widths, m_fixed_width, m_glyph_width, m_glyph_height, m_glyph_spacing));
112}
113
114NonnullRefPtr<Font> Font::create(u8 glyph_height, u8 glyph_width, bool fixed)
115{
116 size_t bytes_per_glyph = sizeof(u32) * glyph_height;
117 // FIXME: This is leaked!
118 auto* new_rows = static_cast<unsigned*>(malloc(bytes_per_glyph * 256));
119 memset(new_rows, 0, bytes_per_glyph * 256);
120 auto* new_widths = static_cast<u8*>(malloc(256));
121 memset(new_widths, glyph_width, 256);
122 return adopt(*new Font("Untitled", new_rows, new_widths, fixed, glyph_width, glyph_height, 1));
123}
124
125Font::Font(const StringView& name, unsigned* rows, u8* widths, bool is_fixed_width, u8 glyph_width, u8 glyph_height, u8 glyph_spacing)
126 : m_name(name)
127 , m_rows(rows)
128 , m_glyph_widths(widths)
129 , m_glyph_width(glyph_width)
130 , m_glyph_height(glyph_height)
131 , m_min_glyph_width(glyph_width)
132 , m_max_glyph_width(glyph_width)
133 , m_glyph_spacing(glyph_spacing)
134 , m_fixed_width(is_fixed_width)
135{
136 if (!m_fixed_width) {
137 u8 maximum = 0;
138 u8 minimum = 255;
139 for (int i = 0; i < 256; ++i) {
140 minimum = min(minimum, m_glyph_widths[i]);
141 maximum = max(maximum, m_glyph_widths[i]);
142 }
143 m_min_glyph_width = minimum;
144 m_max_glyph_width = maximum;
145 }
146}
147
148Font::~Font()
149{
150}
151
152RefPtr<Font> Font::load_from_memory(const u8* data)
153{
154 auto& header = *reinterpret_cast<const FontFileHeader*>(data);
155 if (memcmp(header.magic, "!Fnt", 4)) {
156 dbgprintf("header.magic != '!Fnt', instead it's '%c%c%c%c'\n", header.magic[0], header.magic[1], header.magic[2], header.magic[3]);
157 return nullptr;
158 }
159 if (header.name[63] != '\0') {
160 dbgprintf("Font name not fully null-terminated\n");
161 return nullptr;
162 }
163
164 size_t bytes_per_glyph = sizeof(unsigned) * header.glyph_height;
165
166 auto* rows = const_cast<unsigned*>((const unsigned*)(data + sizeof(FontFileHeader)));
167 u8* widths = nullptr;
168 if (header.is_variable_width)
169 widths = (u8*)(rows) + 256 * bytes_per_glyph;
170 return adopt(*new Font(String(header.name), rows, widths, !header.is_variable_width, header.glyph_width, header.glyph_height, header.glyph_spacing));
171}
172
173RefPtr<Font> Font::load_from_file(const StringView& path)
174{
175 MappedFile mapped_file(path);
176 if (!mapped_file.is_valid())
177 return nullptr;
178
179 auto font = load_from_memory((const u8*)mapped_file.data());
180 font->m_mapped_file = move(mapped_file);
181 return font;
182}
183
184bool Font::write_to_file(const StringView& path)
185{
186 int fd = creat_with_path_length(path.characters_without_null_termination(), path.length(), 0644);
187 if (fd < 0) {
188 perror("open");
189 return false;
190 }
191
192 FontFileHeader header;
193 memset(&header, 0, sizeof(FontFileHeader));
194 memcpy(header.magic, "!Fnt", 4);
195 header.glyph_width = m_glyph_width;
196 header.glyph_height = m_glyph_height;
197 header.type = 0;
198 header.is_variable_width = !m_fixed_width;
199 header.glyph_spacing = m_glyph_spacing;
200 memcpy(header.name, m_name.characters(), min(m_name.length(), (size_t)63));
201
202 size_t bytes_per_glyph = sizeof(unsigned) * m_glyph_height;
203
204 auto buffer = ByteBuffer::create_uninitialized(sizeof(FontFileHeader) + (256 * bytes_per_glyph) + 256);
205 BufferStream stream(buffer);
206
207 stream << ByteBuffer::wrap(&header, sizeof(FontFileHeader));
208 stream << ByteBuffer::wrap(m_rows, (256 * bytes_per_glyph));
209 stream << ByteBuffer::wrap(m_glyph_widths, 256);
210
211 ASSERT(stream.at_end());
212 ssize_t nwritten = write(fd, buffer.data(), buffer.size());
213 ASSERT(nwritten == (ssize_t)buffer.size());
214 int rc = close(fd);
215 ASSERT(rc == 0);
216 return true;
217}
218
219int Font::glyph_or_emoji_width(u32 codepoint) const
220{
221 if (codepoint < 256)
222 return glyph_width((char)codepoint);
223
224 if (m_fixed_width)
225 return m_glyph_width;
226
227 auto* emoji = Emoji::emoji_for_codepoint(codepoint);
228 if (emoji == nullptr)
229 return glyph_width('?');
230 return emoji->size().width();
231}
232
233int Font::width(const StringView& string) const
234{
235 Utf8View utf8 { string };
236 return width(utf8);
237}
238
239int Font::width(const Utf8View& utf8) const
240{
241 bool first = true;
242 int width = 0;
243
244 for (u32 codepoint : utf8) {
245 if (!first)
246 width += glyph_spacing();
247 first = false;
248 width += glyph_or_emoji_width(codepoint);
249 }
250
251 return width;
252}
253
254}