/src/serenity/Userland/Libraries/LibGfx/Font/BitmapFont.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> |
3 | | * Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include "BitmapFont.h" |
9 | | #include "Emoji.h" |
10 | | #include <AK/BuiltinWrappers.h> |
11 | | #include <AK/Utf32View.h> |
12 | | #include <AK/Utf8View.h> |
13 | | #include <LibCore/File.h> |
14 | | #include <LibCore/Resource.h> |
15 | | #include <LibCore/System.h> |
16 | | #include <LibGfx/Font/FontDatabase.h> |
17 | | #include <LibGfx/Font/FontStyleMapping.h> |
18 | | #include <LibGfx/Painter.h> |
19 | | #include <string.h> |
20 | | |
21 | | namespace Gfx { |
22 | | |
23 | | struct [[gnu::packed]] FontFileHeader { |
24 | | char magic[4]; |
25 | | u8 glyph_width; |
26 | | u8 glyph_height; |
27 | | u16 range_mask_size; |
28 | | u8 is_variable_width; |
29 | | u8 glyph_spacing; |
30 | | u8 baseline; |
31 | | u8 mean_line; |
32 | | u8 presentation_size; |
33 | | u16 weight; |
34 | | u8 slope; |
35 | | char name[32]; |
36 | | char family[32]; |
37 | | }; |
38 | | |
39 | | static_assert(AssertSize<FontFileHeader, 80>()); |
40 | | |
41 | | static constexpr size_t s_max_glyph_count = 0x110000; |
42 | | static constexpr size_t s_max_range_mask_size = s_max_glyph_count / (256 * 8); |
43 | | |
44 | | } |
45 | | |
46 | | template<> |
47 | | class AK::Traits<Gfx::FontFileHeader> : public DefaultTraits<Gfx::FontFileHeader> { |
48 | | public: |
49 | 0 | static constexpr bool is_trivially_serializable() { return true; } |
50 | | }; |
51 | | |
52 | | namespace Gfx { |
53 | | |
54 | | NonnullRefPtr<Font> BitmapFont::clone() const |
55 | 0 | { |
56 | 0 | return MUST(try_clone()); |
57 | 0 | } |
58 | | |
59 | | ErrorOr<NonnullRefPtr<Font>> BitmapFont::try_clone() const |
60 | 0 | { |
61 | 0 | auto new_range_mask = TRY(Core::System::allocate(m_range_mask.size(), 1)); |
62 | 0 | m_range_mask.copy_to(new_range_mask); |
63 | 0 | size_t bytes_per_glyph = sizeof(u32) * glyph_height(); |
64 | 0 | auto new_rows = TRY(Core::System::allocate(m_glyph_count, bytes_per_glyph)); |
65 | 0 | m_rows.copy_to(new_rows); |
66 | 0 | auto new_widths = TRY(Core::System::allocate(m_glyph_count, 1)); |
67 | 0 | m_glyph_widths.copy_to(new_widths); |
68 | 0 | return TRY(adopt_nonnull_ref_or_enomem(new (nothrow) BitmapFont(m_name, m_family, new_rows, new_widths, m_fixed_width, m_glyph_width, m_glyph_height, m_glyph_spacing, new_range_mask, m_baseline, m_mean_line, m_presentation_size, m_weight, m_slope, true))); |
69 | 0 | } |
70 | | |
71 | | ErrorOr<NonnullRefPtr<BitmapFont>> BitmapFont::create(u8 glyph_height, u8 glyph_width, bool fixed, size_t glyph_count) |
72 | 0 | { |
73 | 0 | glyph_count += 256 - (glyph_count % 256); |
74 | 0 | glyph_count = min(glyph_count, s_max_glyph_count); |
75 | 0 | size_t glyphs_per_range = 8 * 256; |
76 | 0 | u16 range_mask_size = ceil_div(glyph_count, glyphs_per_range); |
77 | 0 | auto new_range_mask = TRY(Core::System::allocate(range_mask_size, 1)); |
78 | 0 | for (size_t i = 0; i < glyph_count; i += 256) { |
79 | 0 | new_range_mask[i / 256 / 8] |= 1 << (i / 256 % 8); |
80 | 0 | } |
81 | 0 | size_t bytes_per_glyph = sizeof(u32) * glyph_height; |
82 | 0 | auto new_rows = TRY(Core::System::allocate(glyph_count, bytes_per_glyph)); |
83 | 0 | auto new_widths = TRY(Core::System::allocate(glyph_count, 1)); |
84 | 0 | return adopt_nonnull_ref_or_enomem(new (nothrow) BitmapFont("Untitled"_string, "Untitled"_string, new_rows, new_widths, fixed, glyph_width, glyph_height, 1, new_range_mask, 0, 0, 0, 400, 0, true)); |
85 | 0 | } |
86 | | |
87 | | ErrorOr<NonnullRefPtr<BitmapFont>> BitmapFont::unmasked_character_set() const |
88 | 0 | { |
89 | 0 | auto new_range_mask = TRY(Core::System::allocate(s_max_range_mask_size, 1)); |
90 | 0 | constexpr u8 max_bits { 0b1111'1111 }; |
91 | 0 | memset(new_range_mask.data(), max_bits, s_max_range_mask_size); |
92 | 0 | size_t bytes_per_glyph = sizeof(u32) * glyph_height(); |
93 | 0 | auto new_rows = TRY(Core::System::allocate(s_max_glyph_count, bytes_per_glyph)); |
94 | 0 | auto new_widths = TRY(Core::System::allocate(s_max_glyph_count, 1)); |
95 | 0 | for (size_t code_point = 0; code_point < s_max_glyph_count; ++code_point) { |
96 | 0 | auto index = glyph_index(code_point); |
97 | 0 | if (index.has_value()) { |
98 | 0 | new_widths[code_point] = m_glyph_widths[index.value()]; |
99 | 0 | memcpy(&new_rows[code_point * bytes_per_glyph], &m_rows[index.value() * bytes_per_glyph], bytes_per_glyph); |
100 | 0 | } |
101 | 0 | } |
102 | 0 | return adopt_nonnull_ref_or_enomem(new (nothrow) BitmapFont(m_name, m_family, new_rows, new_widths, m_fixed_width, m_glyph_width, m_glyph_height, m_glyph_spacing, new_range_mask, m_baseline, m_mean_line, m_presentation_size, m_weight, m_slope, true)); |
103 | 0 | } |
104 | | |
105 | | ErrorOr<NonnullRefPtr<BitmapFont>> BitmapFont::masked_character_set() const |
106 | 0 | { |
107 | 0 | auto new_range_mask = TRY(Core::System::allocate(s_max_range_mask_size, 1)); |
108 | 0 | u16 new_range_mask_size { 0 }; |
109 | 0 | for (size_t i = 0; i < m_glyph_count; ++i) { |
110 | 0 | if (m_glyph_widths[i] > 0) { |
111 | 0 | new_range_mask[i / 256 / 8] |= 1 << (i / 256 % 8); |
112 | 0 | if (i / 256 / 8 + 1 > new_range_mask_size) |
113 | 0 | new_range_mask_size = i / 256 / 8 + 1; |
114 | 0 | } |
115 | 0 | } |
116 | 0 | size_t new_glyph_count { 0 }; |
117 | 0 | for (size_t i = 0; i < new_range_mask_size; ++i) { |
118 | 0 | new_glyph_count += 256 * popcount(new_range_mask[i]); |
119 | 0 | } |
120 | 0 | size_t bytes_per_glyph = sizeof(u32) * m_glyph_height; |
121 | 0 | auto new_rows = TRY(Core::System::allocate(new_glyph_count, bytes_per_glyph)); |
122 | 0 | auto new_widths = TRY(Core::System::allocate(new_glyph_count, 1)); |
123 | 0 | for (size_t i = 0, j = 0; i < m_glyph_count; ++i) { |
124 | 0 | if (!(new_range_mask[i / 256 / 8] & 1 << (i / 256 % 8))) { |
125 | 0 | j++; |
126 | 0 | i += 255; |
127 | 0 | continue; |
128 | 0 | } |
129 | 0 | new_widths[i - j * 256] = m_glyph_widths[i]; |
130 | 0 | memcpy(&new_rows[(i - j * 256) * bytes_per_glyph], &m_rows[i * bytes_per_glyph], bytes_per_glyph); |
131 | 0 | } |
132 | | // Now that we're done working with the range-mask memory, reduce its reported size down to what it should be. |
133 | 0 | new_range_mask = { new_range_mask.data(), new_range_mask_size }; |
134 | 0 | return adopt_nonnull_ref_or_enomem(new (nothrow) BitmapFont(m_name, m_family, new_rows, new_widths, m_fixed_width, m_glyph_width, m_glyph_height, m_glyph_spacing, new_range_mask, m_baseline, m_mean_line, m_presentation_size, m_weight, m_slope, true)); |
135 | 0 | } |
136 | | |
137 | | BitmapFont::BitmapFont(String name, String family, Bytes rows, Span<u8> widths, bool is_fixed_width, u8 glyph_width, u8 glyph_height, u8 glyph_spacing, Bytes range_mask, u8 baseline, u8 mean_line, u8 presentation_size, u16 weight, u8 slope, bool owns_arrays) |
138 | 0 | : m_name(move(name)) |
139 | 0 | , m_family(move(family)) |
140 | 0 | , m_range_mask(range_mask) |
141 | 0 | , m_rows(rows) |
142 | 0 | , m_glyph_widths(widths) |
143 | 0 | , m_glyph_width(glyph_width) |
144 | 0 | , m_glyph_height(glyph_height) |
145 | 0 | , m_min_glyph_width(glyph_width) |
146 | 0 | , m_max_glyph_width(glyph_width) |
147 | 0 | , m_glyph_spacing(glyph_spacing) |
148 | 0 | , m_baseline(baseline) |
149 | 0 | , m_mean_line(mean_line) |
150 | 0 | , m_presentation_size(presentation_size) |
151 | 0 | , m_weight(weight) |
152 | 0 | , m_slope(slope) |
153 | 0 | , m_fixed_width(is_fixed_width) |
154 | 0 | , m_owns_arrays(owns_arrays) |
155 | 0 | { |
156 | 0 | update_x_height(); |
157 | |
|
158 | 0 | for (size_t i = 0, index = 0; i < m_range_mask.size(); ++i) { |
159 | 0 | for (size_t j = 0; j < 8; ++j) { |
160 | 0 | if (m_range_mask[i] & (1 << j)) { |
161 | 0 | m_glyph_count += 256; |
162 | 0 | m_range_indices.append(index++); |
163 | 0 | } else { |
164 | 0 | m_range_indices.append({}); |
165 | 0 | } |
166 | 0 | } |
167 | 0 | } |
168 | |
|
169 | 0 | if (!m_fixed_width) { |
170 | 0 | u8 maximum = 0; |
171 | 0 | u8 minimum = 255; |
172 | 0 | for (size_t i = 0; i < m_glyph_count; ++i) { |
173 | 0 | minimum = min(minimum, m_glyph_widths[i]); |
174 | 0 | maximum = max(maximum, m_glyph_widths[i]); |
175 | 0 | } |
176 | 0 | m_min_glyph_width = minimum; |
177 | 0 | m_max_glyph_width = max(maximum, m_glyph_width); |
178 | 0 | } |
179 | 0 | } |
180 | | |
181 | | BitmapFont::~BitmapFont() |
182 | 0 | { |
183 | 0 | if (m_owns_arrays) { |
184 | 0 | free(m_glyph_widths.data()); |
185 | 0 | free(m_rows.data()); |
186 | 0 | free(m_range_mask.data()); |
187 | 0 | } |
188 | 0 | } |
189 | | |
190 | | ErrorOr<NonnullRefPtr<BitmapFont>> BitmapFont::try_load_from_stream(FixedMemoryStream& stream) |
191 | 0 | { |
192 | 0 | auto& header = *TRY(stream.read_in_place<FontFileHeader const>()); |
193 | 0 | if (memcmp(header.magic, "!Fnt", 4)) |
194 | 0 | return Error::from_string_literal("Gfx::BitmapFont::load_from_memory: Incompatible header"); |
195 | 0 | if (header.name[sizeof(header.name) - 1] != '\0') |
196 | 0 | return Error::from_string_literal("Gfx::BitmapFont::load_from_memory: Nonnull-terminated name"); |
197 | 0 | if (header.family[sizeof(header.family) - 1] != '\0') |
198 | 0 | return Error::from_string_literal("Gfx::BitmapFont::load_from_memory: Nonnull-terminated family"); |
199 | | |
200 | 0 | size_t bytes_per_glyph = sizeof(u32) * header.glyph_height; |
201 | 0 | size_t glyph_count { 0 }; |
202 | | |
203 | | // FIXME: These ReadonlyFoo -> Foo casts are awkward, and only needed because BitmapFont is |
204 | | // sometimes editable and sometimes not. Splitting it into editable/non-editable classes |
205 | | // would make this a lot cleaner. |
206 | 0 | ReadonlyBytes readonly_range_mask = TRY(stream.read_in_place<u8 const>(header.range_mask_size)); |
207 | 0 | Bytes range_mask { const_cast<u8*>(readonly_range_mask.data()), readonly_range_mask.size() }; |
208 | 0 | for (size_t i = 0; i < header.range_mask_size; ++i) |
209 | 0 | glyph_count += 256 * popcount(range_mask[i]); |
210 | |
|
211 | 0 | ReadonlyBytes readonly_rows = TRY(stream.read_in_place<u8 const>(glyph_count * bytes_per_glyph)); |
212 | 0 | Bytes rows { const_cast<u8*>(readonly_rows.data()), readonly_rows.size() }; |
213 | |
|
214 | 0 | ReadonlySpan<u8> readonly_widths = TRY(stream.read_in_place<u8 const>(glyph_count)); |
215 | 0 | Span<u8> widths { const_cast<u8*>(readonly_widths.data()), readonly_widths.size() }; |
216 | |
|
217 | 0 | if (!stream.is_eof()) |
218 | 0 | return Error::from_string_literal("Gfx::BitmapFont::load_from_memory: Trailing data in file"); |
219 | | |
220 | 0 | auto name = TRY(String::from_utf8(ReadonlyBytes { header.name, strlen(header.name) })); |
221 | 0 | auto family = TRY(String::from_utf8(ReadonlyBytes { header.family, strlen(header.family) })); |
222 | 0 | auto font = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) BitmapFont(move(name), move(family), rows, widths, !header.is_variable_width, header.glyph_width, header.glyph_height, header.glyph_spacing, range_mask, header.baseline, header.mean_line, header.presentation_size, header.weight, header.slope))); |
223 | 0 | return font; |
224 | 0 | } |
225 | | |
226 | | ErrorOr<NonnullRefPtr<BitmapFont>> BitmapFont::try_load_from_resource(NonnullRefPtr<Core::Resource> resource) |
227 | 0 | { |
228 | 0 | auto stream = resource->stream(); |
229 | 0 | auto font = TRY(try_load_from_stream(stream)); |
230 | 0 | font->m_owned_data = move(resource); |
231 | 0 | return font; |
232 | 0 | } |
233 | | |
234 | | ErrorOr<NonnullRefPtr<BitmapFont>> BitmapFont::try_load_from_mapped_file(NonnullOwnPtr<Core::MappedFile> mapped_file) |
235 | 0 | { |
236 | 0 | auto font = TRY(try_load_from_stream(*mapped_file)); |
237 | 0 | font->m_owned_data = move(mapped_file); |
238 | 0 | return font; |
239 | 0 | } |
240 | | |
241 | | NonnullRefPtr<BitmapFont> BitmapFont::load_from_uri(StringView uri) |
242 | 0 | { |
243 | 0 | return MUST(try_load_from_uri(uri)); |
244 | 0 | } |
245 | | |
246 | | ErrorOr<NonnullRefPtr<BitmapFont>> BitmapFont::try_load_from_uri(StringView uri) |
247 | 0 | { |
248 | 0 | return try_load_from_resource(TRY(Core::Resource::load_from_uri(uri))); |
249 | 0 | } |
250 | | |
251 | | ErrorOr<void> BitmapFont::write_to_file(ByteString const& path) |
252 | 0 | { |
253 | 0 | auto stream = TRY(Core::File::open(path, Core::File::OpenMode::Write)); |
254 | 0 | TRY(write_to_file(move(stream))); |
255 | |
|
256 | 0 | return {}; |
257 | 0 | } |
258 | | |
259 | | ErrorOr<void> BitmapFont::write_to_file(NonnullOwnPtr<Core::File> file) |
260 | 0 | { |
261 | 0 | FontFileHeader header; |
262 | 0 | memset(&header, 0, sizeof(FontFileHeader)); |
263 | 0 | memcpy(header.magic, "!Fnt", 4); |
264 | 0 | header.glyph_width = m_glyph_width; |
265 | 0 | header.glyph_height = m_glyph_height; |
266 | 0 | header.range_mask_size = m_range_mask.size(); |
267 | 0 | header.baseline = m_baseline; |
268 | 0 | header.mean_line = m_mean_line; |
269 | 0 | header.is_variable_width = !m_fixed_width; |
270 | 0 | header.glyph_spacing = m_glyph_spacing; |
271 | 0 | header.presentation_size = m_presentation_size; |
272 | 0 | header.weight = m_weight; |
273 | 0 | header.slope = m_slope; |
274 | 0 | memcpy(header.name, m_name.bytes().data(), min(m_name.bytes().size(), sizeof(header.name) - 1)); |
275 | 0 | memcpy(header.family, m_family.bytes().data(), min(m_family.bytes().size(), sizeof(header.family) - 1)); |
276 | |
|
277 | 0 | TRY(file->write_until_depleted({ &header, sizeof(header) })); |
278 | 0 | TRY(file->write_until_depleted(m_range_mask)); |
279 | 0 | TRY(file->write_until_depleted(m_rows)); |
280 | 0 | TRY(file->write_until_depleted(m_glyph_widths)); |
281 | |
|
282 | 0 | return {}; |
283 | 0 | } |
284 | | |
285 | | Optional<Glyph> BitmapFont::glyph(u32 code_point) const |
286 | 0 | { |
287 | 0 | auto maybe_index = glyph_index(code_point); |
288 | 0 | if (!maybe_index.has_value()) |
289 | 0 | return {}; |
290 | 0 | auto index = maybe_index.release_value(); |
291 | 0 | auto width = m_glyph_widths[index]; |
292 | 0 | auto glyph_byte_count = m_glyph_height * GlyphBitmap::bytes_per_row(); |
293 | 0 | return Glyph( |
294 | 0 | GlyphBitmap(m_rows.slice(index * glyph_byte_count, glyph_byte_count), { width, m_glyph_height }), |
295 | 0 | 0, |
296 | 0 | width, |
297 | 0 | m_glyph_height); |
298 | 0 | } |
299 | | |
300 | | Glyph BitmapFont::raw_glyph(u32 code_point) const |
301 | 0 | { |
302 | 0 | auto width = m_glyph_widths[code_point]; |
303 | 0 | auto glyph_byte_count = m_glyph_height * GlyphBitmap::bytes_per_row(); |
304 | 0 | return Glyph( |
305 | 0 | GlyphBitmap(m_rows.slice(code_point * glyph_byte_count, glyph_byte_count), { width, m_glyph_height }), |
306 | 0 | 0, |
307 | 0 | width, |
308 | 0 | m_glyph_height); |
309 | 0 | } |
310 | | |
311 | | Optional<size_t> BitmapFont::glyph_index(u32 code_point) const |
312 | 0 | { |
313 | 0 | auto index = code_point / 256; |
314 | 0 | if (index >= m_range_indices.size()) |
315 | 0 | return {}; |
316 | 0 | if (!m_range_indices[index].has_value()) |
317 | 0 | return {}; |
318 | 0 | return m_range_indices[index].value() * 256 + code_point % 256; |
319 | 0 | } |
320 | | |
321 | | bool BitmapFont::contains_glyph(u32 code_point) const |
322 | 0 | { |
323 | 0 | auto index = glyph_index(code_point); |
324 | 0 | return index.has_value() && m_glyph_widths[index.value()] > 0; |
325 | 0 | } |
326 | | |
327 | | float BitmapFont::glyph_width(u32 code_point) const |
328 | 0 | { |
329 | 0 | if (is_ascii(code_point) && !is_ascii_printable(code_point)) |
330 | 0 | return 0; |
331 | 0 | auto index = glyph_index(code_point); |
332 | 0 | return m_fixed_width || !index.has_value() ? m_glyph_width : m_glyph_widths[index.value()]; |
333 | 0 | } |
334 | | |
335 | | template<typename CodePointIterator> |
336 | | static float glyph_or_emoji_width_impl(BitmapFont const& font, CodePointIterator& it) |
337 | 0 | { |
338 | 0 | if (auto const* emoji = Emoji::emoji_for_code_point_iterator(it)) |
339 | 0 | return font.pixel_size() * emoji->width() / emoji->height(); |
340 | | |
341 | 0 | if (font.is_fixed_width()) |
342 | 0 | return font.glyph_fixed_width(); |
343 | | |
344 | 0 | return font.glyph_width(*it); |
345 | 0 | } Unexecuted instantiation: BitmapFont.cpp:float Gfx::glyph_or_emoji_width_impl<AK::Utf8CodePointIterator>(Gfx::BitmapFont const&, AK::Utf8CodePointIterator&) Unexecuted instantiation: BitmapFont.cpp:float Gfx::glyph_or_emoji_width_impl<AK::Utf32CodePointIterator>(Gfx::BitmapFont const&, AK::Utf32CodePointIterator&) |
346 | | |
347 | | float BitmapFont::glyph_or_emoji_width(Utf8CodePointIterator& it) const |
348 | 0 | { |
349 | 0 | return glyph_or_emoji_width_impl(*this, it); |
350 | 0 | } |
351 | | |
352 | | float BitmapFont::glyph_or_emoji_width(Utf32CodePointIterator& it) const |
353 | 0 | { |
354 | 0 | return glyph_or_emoji_width_impl(*this, it); |
355 | 0 | } |
356 | | |
357 | | int BitmapFont::width_rounded_up(StringView view) const |
358 | 0 | { |
359 | 0 | return static_cast<int>(ceilf(width(view))); |
360 | 0 | } |
361 | | |
362 | 0 | float BitmapFont::width(StringView view) const { return unicode_view_width(Utf8View(view)); } |
363 | 0 | float BitmapFont::width(Utf8View const& view) const { return unicode_view_width(view); } |
364 | 0 | float BitmapFont::width(Utf32View const& view) const { return unicode_view_width(view); } |
365 | | |
366 | | template<typename T> |
367 | | ALWAYS_INLINE int BitmapFont::unicode_view_width(T const& view) const |
368 | 0 | { |
369 | 0 | if (view.is_empty()) |
370 | 0 | return 0; |
371 | 0 | bool first = true; |
372 | 0 | int width = 0; |
373 | 0 | int longest_width = 0; |
374 | |
|
375 | 0 | for (auto it = view.begin(); it != view.end(); ++it) { |
376 | 0 | auto code_point = *it; |
377 | |
|
378 | 0 | if (code_point == '\n' || code_point == '\r') { |
379 | 0 | first = true; |
380 | 0 | longest_width = max(width, longest_width); |
381 | 0 | width = 0; |
382 | 0 | continue; |
383 | 0 | } |
384 | 0 | if (!first) |
385 | 0 | width += glyph_spacing(); |
386 | 0 | first = false; |
387 | |
|
388 | 0 | width += glyph_or_emoji_width(it); |
389 | 0 | } |
390 | |
|
391 | 0 | longest_width = max(width, longest_width); |
392 | 0 | return longest_width; |
393 | 0 | } Unexecuted instantiation: int Gfx::BitmapFont::unicode_view_width<AK::Utf8View>(AK::Utf8View const&) const Unexecuted instantiation: int Gfx::BitmapFont::unicode_view_width<AK::Utf32View>(AK::Utf32View const&) const |
394 | | |
395 | | String BitmapFont::qualified_name() const |
396 | 0 | { |
397 | 0 | return MUST(String::formatted("{} {} {} {}", family(), presentation_size(), weight(), slope())); |
398 | 0 | } |
399 | | |
400 | | String BitmapFont::variant() const |
401 | 0 | { |
402 | 0 | StringBuilder builder; |
403 | 0 | builder.append(weight_to_name(weight())); |
404 | 0 | if (slope() != 0) { |
405 | 0 | if (builder.string_view() == "Regular"sv) |
406 | 0 | builder.clear(); |
407 | 0 | else |
408 | 0 | builder.append(' '); |
409 | 0 | builder.append(slope_to_name(slope())); |
410 | 0 | } |
411 | 0 | return MUST(builder.to_string()); |
412 | 0 | } |
413 | | |
414 | | NonnullRefPtr<Font> BitmapFont::with_size(float point_size) const |
415 | 0 | { |
416 | 0 | auto scaled_font = Gfx::FontDatabase::the().get(family(), point_size, weight(), width(), slope(), AllowInexactSizeMatch::Yes); |
417 | 0 | VERIFY(scaled_font); // The inexact lookup should, at the very least, return `this` font. |
418 | | |
419 | 0 | return *scaled_font; |
420 | 0 | } |
421 | | |
422 | | Font const& Font::bold_variant() const |
423 | 0 | { |
424 | 0 | if (m_bold_variant) |
425 | 0 | return *m_bold_variant; |
426 | 0 | m_bold_variant = Gfx::FontDatabase::the().get(family(), presentation_size(), 700, Gfx::FontWidth::Normal, 0); |
427 | 0 | if (!m_bold_variant) |
428 | 0 | m_bold_variant = this; |
429 | 0 | return *m_bold_variant; |
430 | 0 | } |
431 | | |
432 | | FontPixelMetrics BitmapFont::pixel_metrics() const |
433 | 0 | { |
434 | 0 | return FontPixelMetrics { |
435 | 0 | .size = (float)pixel_size(), |
436 | 0 | .x_height = (float)x_height(), |
437 | 0 | .advance_of_ascii_zero = (float)glyph_width('0'), |
438 | 0 | .glyph_spacing = (float)glyph_spacing(), |
439 | 0 | .ascent = (float)m_baseline, |
440 | 0 | .descent = (float)(m_glyph_height - m_baseline), |
441 | 0 | .line_gap = Gfx::Painter::LINE_SPACING, |
442 | 0 | }; |
443 | 0 | } |
444 | | |
445 | | } |