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 "Bitmap.h"
29#include "Emoji.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 <stdlib.h>
39#include <string.h>
40#include <sys/mman.h>
41#include <unistd.h>
42
43namespace Gfx {
44
45struct [[gnu::packed]] FontFileHeader
46{
47 char magic[4];
48 u8 glyph_width;
49 u8 glyph_height;
50 u8 type;
51 u8 is_variable_width;
52 u8 glyph_spacing;
53 u8 unused[5];
54 char name[64];
55};
56
57Font& Font::default_font()
58{
59 static Font* s_default_font;
60 static const char* default_font_path = "/res/fonts/Katica10.font";
61 if (!s_default_font) {
62 s_default_font = Font::load_from_file(default_font_path).leak_ref();
63 ASSERT(s_default_font);
64 }
65 return *s_default_font;
66}
67
68Font& Font::default_fixed_width_font()
69{
70 static Font* s_default_fixed_width_font;
71 static const char* default_fixed_width_font_path = "/res/fonts/CsillaThin7x10.font";
72 if (!s_default_fixed_width_font) {
73 s_default_fixed_width_font = Font::load_from_file(default_fixed_width_font_path).leak_ref();
74 ASSERT(s_default_fixed_width_font);
75 }
76 return *s_default_fixed_width_font;
77}
78
79Font& Font::default_bold_fixed_width_font()
80{
81 static Font* font;
82 static const char* default_bold_fixed_width_font_path = "/res/fonts/CsillaBold7x10.font";
83 if (!font) {
84 font = Font::load_from_file(default_bold_fixed_width_font_path).leak_ref();
85 ASSERT(font);
86 }
87 return *font;
88}
89
90Font& Font::default_bold_font()
91{
92 static Font* s_default_bold_font;
93 static const char* default_bold_font_path = "/res/fonts/KaticaBold10.font";
94 if (!s_default_bold_font) {
95 s_default_bold_font = Font::load_from_file(default_bold_font_path).leak_ref();
96 ASSERT(s_default_bold_font);
97 }
98 return *s_default_bold_font;
99}
100
101NonnullRefPtr<Font> Font::clone() const
102{
103 size_t bytes_per_glyph = sizeof(u32) * glyph_height();
104 // FIXME: This is leaked!
105 auto* new_rows = static_cast<unsigned*>(kmalloc(bytes_per_glyph * 256));
106 memcpy(new_rows, m_rows, bytes_per_glyph * 256);
107 auto* new_widths = static_cast<u8*>(kmalloc(256));
108 if (m_glyph_widths)
109 memcpy(new_widths, m_glyph_widths, 256);
110 else
111 memset(new_widths, m_glyph_width, 256);
112 return adopt(*new Font(m_name, new_rows, new_widths, m_fixed_width, m_glyph_width, m_glyph_height, m_glyph_spacing));
113}
114
115NonnullRefPtr<Font> Font::create(u8 glyph_height, u8 glyph_width, bool fixed)
116{
117 size_t bytes_per_glyph = sizeof(u32) * glyph_height;
118 // FIXME: This is leaked!
119 auto* new_rows = static_cast<unsigned*>(malloc(bytes_per_glyph * 256));
120 memset(new_rows, 0, bytes_per_glyph * 256);
121 auto* new_widths = static_cast<u8*>(malloc(256));
122 memset(new_widths, glyph_width, 256);
123 return adopt(*new Font("Untitled", new_rows, new_widths, fixed, glyph_width, glyph_height, 1));
124}
125
126Font::Font(const StringView& name, unsigned* rows, u8* widths, bool is_fixed_width, u8 glyph_width, u8 glyph_height, u8 glyph_spacing)
127 : m_name(name)
128 , m_rows(rows)
129 , m_glyph_widths(widths)
130 , m_glyph_width(glyph_width)
131 , m_glyph_height(glyph_height)
132 , m_min_glyph_width(glyph_width)
133 , m_max_glyph_width(glyph_width)
134 , m_glyph_spacing(glyph_spacing)
135 , m_fixed_width(is_fixed_width)
136{
137 if (!m_fixed_width) {
138 u8 maximum = 0;
139 u8 minimum = 255;
140 for (int i = 0; i < 256; ++i) {
141 minimum = min(minimum, m_glyph_widths[i]);
142 maximum = max(maximum, m_glyph_widths[i]);
143 }
144 m_min_glyph_width = minimum;
145 m_max_glyph_width = maximum;
146 }
147}
148
149Font::~Font()
150{
151}
152
153RefPtr<Font> Font::load_from_memory(const u8* data)
154{
155 auto& header = *reinterpret_cast<const FontFileHeader*>(data);
156 if (memcmp(header.magic, "!Fnt", 4)) {
157 dbgprintf("header.magic != '!Fnt', instead it's '%c%c%c%c'\n", header.magic[0], header.magic[1], header.magic[2], header.magic[3]);
158 return nullptr;
159 }
160 if (header.name[63] != '\0') {
161 dbgprintf("Font name not fully null-terminated\n");
162 return nullptr;
163 }
164
165 size_t bytes_per_glyph = sizeof(unsigned) * header.glyph_height;
166
167 auto* rows = const_cast<unsigned*>((const unsigned*)(data + sizeof(FontFileHeader)));
168 u8* widths = nullptr;
169 if (header.is_variable_width)
170 widths = (u8*)(rows) + 256 * bytes_per_glyph;
171 return adopt(*new Font(String(header.name), rows, widths, !header.is_variable_width, header.glyph_width, header.glyph_height, header.glyph_spacing));
172}
173
174RefPtr<Font> Font::load_from_file(const StringView& path)
175{
176 MappedFile mapped_file(path);
177 if (!mapped_file.is_valid())
178 return nullptr;
179
180 auto font = load_from_memory((const u8*)mapped_file.data());
181 if (!font)
182 return nullptr;
183
184 font->m_mapped_file = move(mapped_file);
185 return font;
186}
187
188bool Font::write_to_file(const StringView& path)
189{
190 int fd = creat_with_path_length(path.characters_without_null_termination(), path.length(), 0644);
191 if (fd < 0) {
192 perror("open");
193 return false;
194 }
195
196 FontFileHeader header;
197 memset(&header, 0, sizeof(FontFileHeader));
198 memcpy(header.magic, "!Fnt", 4);
199 header.glyph_width = m_glyph_width;
200 header.glyph_height = m_glyph_height;
201 header.type = 0;
202 header.is_variable_width = !m_fixed_width;
203 header.glyph_spacing = m_glyph_spacing;
204 memcpy(header.name, m_name.characters(), min(m_name.length(), (size_t)63));
205
206 size_t bytes_per_glyph = sizeof(unsigned) * m_glyph_height;
207
208 auto buffer = ByteBuffer::create_uninitialized(sizeof(FontFileHeader) + (256 * bytes_per_glyph) + 256);
209 BufferStream stream(buffer);
210
211 stream << ByteBuffer::wrap(&header, sizeof(FontFileHeader));
212 stream << ByteBuffer::wrap(m_rows, (256 * bytes_per_glyph));
213 stream << ByteBuffer::wrap(m_glyph_widths, 256);
214
215 ASSERT(stream.at_end());
216 ssize_t nwritten = write(fd, buffer.data(), buffer.size());
217 ASSERT(nwritten == (ssize_t)buffer.size());
218 int rc = close(fd);
219 ASSERT(rc == 0);
220 return true;
221}
222
223int Font::glyph_or_emoji_width(u32 codepoint) const
224{
225 if (codepoint < 256)
226 return glyph_width((char)codepoint);
227
228 if (m_fixed_width)
229 return m_glyph_width;
230
231 auto* emoji = Emoji::emoji_for_codepoint(codepoint);
232 if (emoji == nullptr)
233 return glyph_width('?');
234 return emoji->size().width();
235}
236
237int Font::width(const StringView& string) const
238{
239 Utf8View utf8 { string };
240 return width(utf8);
241}
242
243int Font::width(const Utf8View& utf8) const
244{
245 bool first = true;
246 int width = 0;
247
248 for (u32 codepoint : utf8) {
249 if (!first)
250 width += glyph_spacing();
251 first = false;
252 width += glyph_or_emoji_width(codepoint);
253 }
254
255 return width;
256}
257
258}