From 8ec691057f900e615b4b7aba66acd8b2276f68a8 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Tue, 27 Jun 2023 18:57:13 +0100 Subject: [PATCH] FileManager: Add "Font" tab to File Properties window We now keep the MappedFile around when inspecting a font, so that we can use that font to display a preview. --- .../Applications/FileManager/CMakeLists.txt | 2 + .../FileManager/PropertiesWindow.cpp | 102 +++++++++++++++ .../FileManager/PropertiesWindow.h | 1 + .../FileManager/PropertiesWindowFontTab.gml | 123 ++++++++++++++++++ 4 files changed, 228 insertions(+) create mode 100644 Userland/Applications/FileManager/PropertiesWindowFontTab.gml diff --git a/Userland/Applications/FileManager/CMakeLists.txt b/Userland/Applications/FileManager/CMakeLists.txt index de1807bba833e7..6c4dbd7081bf6c 100644 --- a/Userland/Applications/FileManager/CMakeLists.txt +++ b/Userland/Applications/FileManager/CMakeLists.txt @@ -8,6 +8,7 @@ serenity_component( compile_gml(FileManagerWindow.gml FileManagerWindowGML.h file_manager_window_gml) compile_gml(FileOperationProgress.gml FileOperationProgressGML.h file_operation_progress_gml) compile_gml(PropertiesWindowAudioTab.gml PropertiesWindowAudioTabGML.h properties_window_audio_tab_gml) +compile_gml(PropertiesWindowFontTab.gml PropertiesWindowFontTabGML.h properties_window_font_tab_gml) compile_gml(PropertiesWindowGeneralTab.gml PropertiesWindowGeneralTabGML.h properties_window_general_tab_gml) compile_gml(PropertiesWindowImageTab.gml PropertiesWindowImageTabGML.h properties_window_image_tab_gml) @@ -24,6 +25,7 @@ set(GENERATED_SOURCES FileManagerWindowGML.h FileOperationProgressGML.h PropertiesWindowAudioTabGML.h + PropertiesWindowFontTabGML.h PropertiesWindowGeneralTabGML.h PropertiesWindowImageTabGML.h ) diff --git a/Userland/Applications/FileManager/PropertiesWindow.cpp b/Userland/Applications/FileManager/PropertiesWindow.cpp index 3b42801f1b5697..5deacfd584fc51 100644 --- a/Userland/Applications/FileManager/PropertiesWindow.cpp +++ b/Userland/Applications/FileManager/PropertiesWindow.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -27,6 +28,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include #include #include @@ -218,6 +225,9 @@ ErrorOr PropertiesWindow::create_file_type_specific_tabs(GUI::TabWidget& t if (mime_type.starts_with("audio/"sv)) return create_audio_tab(tab_widget, move(mapped_file)); + if (mime_type.starts_with("font/"sv) || m_path.ends_with(".font"sv)) + return create_font_tab(tab_widget, move(mapped_file), mime_type); + if (mime_type.starts_with("image/"sv)) return create_image_tab(tab_widget, move(mapped_file), mime_type); @@ -262,6 +272,98 @@ ErrorOr PropertiesWindow::create_audio_tab(GUI::TabWidget& tab_widget, Non return {}; } +struct FontInfo { + enum class Format { + BitmapFont, + OpenType, + TrueType, + WOFF, + WOFF2, + }; + Format format; + NonnullRefPtr typeface; +}; +static ErrorOr load_font(StringView path, StringView mime_type, NonnullRefPtr mapped_file) +{ + if (path.ends_with(".font"sv)) { + auto font = TRY(Gfx::BitmapFont::try_load_from_mapped_file(mapped_file)); + auto typeface = TRY(try_make_ref_counted(font->family(), font->variant())); + typeface->add_bitmap_font(move(font)); + return FontInfo { FontInfo::Format::BitmapFont, move(typeface) }; + } + + if (mime_type == "font/otf" || mime_type == "font/ttf") { + auto font = TRY(OpenType::Font::try_load_from_externally_owned_memory(mapped_file->bytes())); + auto typeface = TRY(try_make_ref_counted(font->family(), font->variant())); + typeface->set_vector_font(move(font)); + return FontInfo { + mime_type == "font/otf" ? FontInfo::Format::OpenType : FontInfo::Format::TrueType, + move(typeface) + }; + } + + if (mime_type == "font/woff" || mime_type == "font/woff2") { + auto font = TRY(WOFF::Font::try_load_from_externally_owned_memory(mapped_file->bytes())); + auto typeface = TRY(try_make_ref_counted(font->family(), font->variant())); + typeface->set_vector_font(move(font)); + return FontInfo { + mime_type == "font/woff" ? FontInfo::Format::WOFF : FontInfo::Format::WOFF2, + move(typeface) + }; + } + + return Error::from_string_view("Unrecognized font format."sv); +} + +ErrorOr PropertiesWindow::create_font_tab(GUI::TabWidget& tab_widget, NonnullRefPtr mapped_file, StringView mime_type) +{ + auto font_info_or_error = load_font(m_path, mime_type, mapped_file); + if (font_info_or_error.is_error()) { + warnln("Failed to open '{}': {}", m_path, font_info_or_error.release_error()); + return {}; + } + auto font_info = font_info_or_error.release_value(); + auto& typeface = font_info.typeface; + + auto tab = TRY(tab_widget.try_add_tab("Font"_short_string)); + TRY(tab->load_from_gml(properties_window_font_tab_gml)); + + String format_name; + switch (font_info.format) { + case FontInfo::Format::BitmapFont: + format_name = TRY("Bitmap Font"_string); + break; + case FontInfo::Format::OpenType: + format_name = TRY("OpenType"_string); + break; + case FontInfo::Format::TrueType: + format_name = TRY("TrueType"_string); + break; + case FontInfo::Format::WOFF: + format_name = TRY("WOFF"_string); + break; + case FontInfo::Format::WOFF2: + format_name = TRY("WOFF2"_string); + break; + } + tab->find_descendant_of_type_named("font_format")->set_text(format_name); + tab->find_descendant_of_type_named("font_family")->set_text(TRY(String::from_deprecated_string(typeface->family()))); + tab->find_descendant_of_type_named("font_fixed_width")->set_text(typeface->is_fixed_width() ? "Yes"_short_string : "No"_short_string); + tab->find_descendant_of_type_named("font_width")->set_text(TRY(String::from_utf8(Gfx::width_to_name(static_cast(typeface->width()))))); + + auto nearest_weight_class_name = [](unsigned weight) { + if (weight > 925) + return Gfx::weight_to_name(Gfx::FontWeight::ExtraBlack); + unsigned weight_class = clamp(round_to(weight / 100.0) * 100, Gfx::FontWeight::Thin, Gfx::FontWeight::Black); + return Gfx::weight_to_name(weight_class); + }; + auto weight = typeface->weight(); + tab->find_descendant_of_type_named("font_weight")->set_text(TRY(String::formatted("{} ({})", weight, nearest_weight_class_name(weight)))); + tab->find_descendant_of_type_named("font_slope")->set_text(TRY(String::from_utf8(Gfx::slope_to_name(typeface->slope())))); + + return {}; +} + ErrorOr PropertiesWindow::create_image_tab(GUI::TabWidget& tab_widget, NonnullRefPtr mapped_file, StringView mime_type) { auto image_decoder = Gfx::ImageDecoder::try_create_for_raw_bytes(mapped_file->bytes(), mime_type); diff --git a/Userland/Applications/FileManager/PropertiesWindow.h b/Userland/Applications/FileManager/PropertiesWindow.h index 768236359aab58..fc429631e96fcd 100644 --- a/Userland/Applications/FileManager/PropertiesWindow.h +++ b/Userland/Applications/FileManager/PropertiesWindow.h @@ -31,6 +31,7 @@ class PropertiesWindow final : public GUI::Window { ErrorOr create_general_tab(GUI::TabWidget&, bool disable_rename); ErrorOr create_file_type_specific_tabs(GUI::TabWidget&); ErrorOr create_audio_tab(GUI::TabWidget&, NonnullRefPtr); + ErrorOr create_font_tab(GUI::TabWidget&, NonnullRefPtr, StringView mime_type); ErrorOr create_image_tab(GUI::TabWidget&, NonnullRefPtr, StringView mime_type); struct PermissionMasks { diff --git a/Userland/Applications/FileManager/PropertiesWindowFontTab.gml b/Userland/Applications/FileManager/PropertiesWindowFontTab.gml new file mode 100644 index 00000000000000..1c3c2293c36f47 --- /dev/null +++ b/Userland/Applications/FileManager/PropertiesWindowFontTab.gml @@ -0,0 +1,123 @@ +@GUI::Widget { + layout: @GUI::VerticalBoxLayout { + margins: [8] + spacing: 12 + } + + @GUI::GroupBox { + title: "Font" + preferred_height: "shrink" + layout: @GUI::VerticalBoxLayout { + margins: [12, 8, 0] + spacing: 2 + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout { + spacing: 12 + } + + @GUI::Label { + text: "Format:" + text_alignment: "TopLeft" + fixed_width: 80 + } + + @GUI::Label { + name: "font_format" + text: "TrueType" + text_alignment: "TopLeft" + } + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout { + spacing: 12 + } + + @GUI::Label { + text: "Family:" + text_alignment: "TopLeft" + fixed_width: 80 + } + + @GUI::Label { + name: "font_family" + text: "SerenitySans" + text_alignment: "TopLeft" + } + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout { + spacing: 12 + } + + @GUI::Label { + text: "Fixed width:" + text_alignment: "TopLeft" + fixed_width: 80 + } + + @GUI::Label { + name: "font_fixed_width" + text: "No" + text_alignment: "TopLeft" + } + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout { + spacing: 12 + } + + @GUI::Label { + text: "Width:" + text_alignment: "TopLeft" + fixed_width: 80 + } + + @GUI::Label { + name: "font_width" + text: "Normal" + text_alignment: "TopLeft" + } + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout { + spacing: 12 + } + + @GUI::Label { + text: "Weight:" + text_alignment: "TopLeft" + fixed_width: 80 + } + + @GUI::Label { + name: "font_weight" + text: "500 (Medium)" + text_alignment: "TopLeft" + } + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout { + spacing: 12 + } + + @GUI::Label { + text: "Slope:" + text_alignment: "TopLeft" + fixed_width: 80 + } + + @GUI::Label { + name: "font_slope" + text: "Regular" + text_alignment: "TopLeft" + } + } + } +}