diff --git a/share/icons/hicolor/symbolic/actions/sort-by-family-symbolic.svg b/share/icons/hicolor/symbolic/actions/sort-by-family-symbolic.svg
new file mode 100644
index 0000000000000000000000000000000000000000..7441b9e6f1c14c8f0aab7b0e1eb1daf2f5b12c07
--- /dev/null
+++ b/share/icons/hicolor/symbolic/actions/sort-by-family-symbolic.svg
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/share/ui/font-list.glade b/share/ui/font-list.glade
index ec38dcf4ffe1f90a857ff6c51fd64e4c23e7798f..013963ec5441c3294fc4d1fcc4b5a487b1b7fc2c 100644
--- a/share/ui/font-list.glade
+++ b/share/ui/font-list.glade
@@ -1,6 +1,6 @@
-
+
+
diff --git a/share/ui/style.css b/share/ui/style.css
index 202b0e34329084bf706912cf5f9dbf4f48feebc7..133657f4a47480a63cf8149129aa5dfeca244be1 100644
--- a/share/ui/style.css
+++ b/share/ui/style.css
@@ -1787,6 +1787,37 @@ padding:0 1px;
border: none;
}
+#FontList indent {
+ /* Set indent to align listview items with and without leaf nodes (i.e. with and without expander icon) */
+ -gtk-icon-size: 16px;
+}
+
#FontList .font-list {
min-height: 8em;
}
+
+/* Allow scrollwindow to become smaller than it normally is to let font variations take up as little space as they need */
+#FontList .collapse-scrollbar scrollbar slider {
+ min-height: 0;
+}
+
+.compact-menu-button box {
+ border-spacing: 0;
+}
+
+/* Style used by font grid items */
+.round-rect-shade {
+ border-radius: 3px;
+ background: mix(@theme_base_color, @theme_fg_color, 0.04);
+ color: @theme_fg_color;
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.30);
+}
+#FontGrid > child {
+ padding: 4px;
+ border-radius: 6px;
+}
+#FontGrid > child box.item-box {
+ padding: 2px 0;
+ margin: 0;
+ border-spacing: 0;
+}
diff --git a/src/ui/dialog/inkscape-preferences.cpp b/src/ui/dialog/inkscape-preferences.cpp
index e5f5e3ecf05ddffc01409e227381d7eace160325..b485fe27ffddc804a618516c9d08e5fbce364b7e 100644
--- a/src/ui/dialog/inkscape-preferences.cpp
+++ b/src/ui/dialog/inkscape-preferences.cpp
@@ -2133,9 +2133,9 @@ void InkscapePreferences::initPageUI()
_page_windows.add_group_header( _("Text and Font dialog"));
std::vector lister = {
{ _("List fonts and styles"), 0, _("List fonts and styles separately"), true },
- { _("Unified font browser (experimental)"), 1, _("Show all font styles in a single list") }
+ { _("Unified font browser"), 1, _("Show all font styles in a single list or tree view") }
};
- _page_windows.add_line(true, _("Font selector"), *Gtk::make_managed(lister, "/options/font/browser"), "", "", false, reset_icon());
+ _page_windows.add_line(true, _("Font selector"), *Gtk::make_managed(lister, "/options/font/browser"), "", "", true, reset_icon());
_page_windows.add_group_header( _("Miscellaneous"));
diff --git a/src/ui/dialog/text-edit.cpp b/src/ui/dialog/text-edit.cpp
index dfd780ffc9099fd522205597fc43b16b6c6166e4..7f41d81a8d8c6c2283dfc1dfeeff972e4ee18751 100644
--- a/src/ui/dialog/text-edit.cpp
+++ b/src/ui/dialog/text-edit.cpp
@@ -98,7 +98,7 @@ TextEdit::TextEdit()
, _redo{"doc.redo"}
{
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
- _use_browser = prefs->getInt("/options/font/browser", 0) != 0;
+ _use_browser = prefs->getInt("/options/font/browser", 1) != 0;
font_list = _use_browser ?
Inkscape::UI::Widget::FontList::create_font_list("/font-selector") :
diff --git a/src/ui/widget/font-list.cpp b/src/ui/widget/font-list.cpp
index ad45df8d120b73d78af34c00952230f7e0382bc1..cfebd3de16e28dd3fd6b6c56728eee28dd5b791a 100644
--- a/src/ui/widget/font-list.cpp
+++ b/src/ui/widget/font-list.cpp
@@ -2,10 +2,15 @@
#include "font-list.h"
+#include
+#include
#include
#include
#include
#include
+#include
+#include
+#include
#include
#include
#include
@@ -14,71 +19,26 @@
#include
#include
#include
-#include
+#include
+#include
#include "preferences.h"
#include "svg/css-ostringstream.h"
#include "ui/builder-utils.h"
#include "ui/icon-loader.h"
+#include "ui/text_filter.h"
+#include "ui/dialog/xml-tree.h"
+#include "ui/widget/drop-down-list.h"
#include "ui/widget/generic/popover-menu.h"
#include "util/font-collections.h"
using Inkscape::UI::create_builder;
-namespace Inkscape::UI::Widget {
-
-std::unique_ptr FontList::create_font_list(Glib::ustring path) {
- return std::make_unique(path);
-}
-
-struct FontListColumnModel : public Gtk::TreeModelColumnRecord {
- // font metadata for installed fonts only
- Gtk::TreeModelColumn font;
- // font's class
- // Gtk::TreeModelColumn font_class;
- Gtk::TreeModelColumn injected;
- // fontspec for fonts that are not installed, but used in a document
- Gtk::TreeModelColumn alt_fontspec;
- // icon to show next to a font name (if any)
- Gtk::TreeModelColumn icon_name;
-
- FontListColumnModel() {
- add(alt_fontspec);
- add(injected);
- add(icon_name);
- add(font);
- }
-};
-
-FontListColumnModel g_column_model; // model for font list
-
-// list of font sizes for a slider; combo box has its own list
-static std::array g_font_sizes = {
- 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20, 24, 28, 32, 36,
- 44, 56, 64, 72, 80, 96, 112, 128, 144, 160, 192, 224, 256,
- 300, 350, 400, 450, 500, 550, 600, 700, 800
-};
-
-static int index_to_font_size(int index) {
- if (index < 0) {
- return g_font_sizes.front();
- }
- else if (index >= g_font_sizes.size()) {
- return g_font_sizes.back();
- }
- else {
- return g_font_sizes[index];
- }
-}
-
-static int font_size_to_index(double size) {
- auto it = std::lower_bound(begin(g_font_sizes), end(g_font_sizes), static_cast(size));
- return std::distance(begin(g_font_sizes), it);
-}
+namespace {
// construct font name from Pango face and family;
// return font name as it is recorded in the font itself, as far as Pango allows it
-Glib::ustring get_full_name(const Inkscape::FontInfo& font_info) {
+Glib::ustring get_full_name(const FontInfo& font_info) {
return get_full_font_name(font_info.ff, font_info.face);
}
@@ -93,160 +53,486 @@ Glib::ustring get_alt_name(const Glib::ustring& fontspec) {
return fontspec; // use font spec verbatim
}
-class CellFontRenderer : public Gtk::CellRendererText {
+Glib::ustring get_font_icon(const FontInfo& font, bool missing_font = false) {
+ if (missing_font) {
+ return "missing-element-symbolic";
+ }
+ else if (font.variable_font) {
+ return ""; // add icon for variable fonts?
+ }
+ else if (font.synthetic) {
+ return "generic-font-symbolic";
+ }
+ return {};
+}
+
+// Gio models require Glib object-based elements,
+// we use this class to keep track of all fonts.
+class FontElement : public Glib::Object {
+ enum class Type {
+ Font, // all fonts when not sorting by family
+ Family, // when sorting by family, this is a font that represents a family (a node in a tree)
+ Style // when sorting by family, this is one of the "style" fonts in a family (a leaf in a tree)
+ } _type;
public:
- CellFontRenderer() { }
+ static Glib::RefPtr create_font(const FontInfo& font) {
+ return Glib::make_refptr_for_instance(new FontElement({}, font, {}, Type::Font));
+ }
+
+ static Glib::RefPtr create_style(const FontInfo& font) {
+ return Glib::make_refptr_for_instance(new FontElement({}, font, {}, Type::Style));
+ }
+
+ static Glib::RefPtr create_family(const FontInfo& font, std::vector family) {
+ return Glib::make_refptr_for_instance(new FontElement(std::move(family), font, {}, Type::Family));
+ }
+
+ static Glib::RefPtr create_injected_font(const FontInfo& font, Glib::ustring alt_spec, bool is_missing) {
+ auto element = Glib::make_refptr_for_instance(new FontElement({}, font, std::move(alt_spec), Type::Font));
+ element->_missing_font = is_missing;
+ element->_injected = true;
+ return element;
+ }
+
+ static Glib::RefPtr create_placeholder() {
+ auto element = Glib::make_refptr_for_instance(new FontElement({}, {}, {}, Type::Font));
+ element->_placeholder = true;
+ return element;
+ }
+
+ Glib::ustring icon_name() const {
+ return get_font_icon(_font, _missing_font);
+ }
+ Glib::ustring icon_tooltip() const {
+ if (_missing_font) {
+ // this font is not installed / not available
+ return _("This font is missing");
+ }
+ else if (_font.synthetic) {
+ // this is an alias for some fallback font (ex: 'Serif', 'Sans') and/or faux style
+ return _("This is an alias or synthetic font");
+ }
+ return {};
+ }
+ const FontInfo& font() const {
+ return _font;
+ }
+ const Glib::ustring& get_alt_spec() const {
+ return _alt_fontspec;
+ }
+ bool is_present() const {
+ // true if this font is installed
+ return _font.ff != nullptr;
+ }
+ bool is_family() const {
+ return _type == Type::Family;
+ }
+ bool is_injected() const {
+ return _injected;
+ }
+ void clear_injected() {
+ _injected = false;
+ _placeholder = true;
+ }
+ bool is_placeholder() const {
+ return _placeholder;
+ }
+ const std::vector& family() const {
+ return _family;
+ }
+ // get markup for a full font name
+ Glib::ustring get_full_name_markup() const {
+ auto name = get_font_name(Type::Font);
+ return Glib::ustring::compose("%1", name);
+ }
+ // get markup for a font name
+ Glib::ustring get_name_markup() const {
+ auto name = get_font_name(_type);
+ return Glib::ustring::compose("%1", name);
+ }
+ Glib::ustring get_name_tooltip() const {
+ return get_font_name(_type);
+ }
+ // get markup for a font badge - number of styles in a family
+ Glib::ustring get_badge_markup() const {
+ if ( _family.size() > 1) {
+ // count
+ return Glib::ustring::compose(" %1 ", _family.size());
+ }
+ return {};
+ }
+ // get markup to render font preview
+ Glib::ustring get_sample_markup(int font_size_percent, Glib::ustring sample_text) {
+ // if no sample text given, then render font name
+ auto text = Glib::Markup::escape_text(sample_text.empty() ? get_font_name(_type == Type::Family ? _type : Type::Font) : sample_text);
+
+ auto& alt = _alt_fontspec;
+ auto font_desc = Glib::Markup::escape_text(
+ is_present() ? get_font_description(_font.ff, _font.face).to_string() : (alt.empty() ? "sans-serif" : alt));
+ auto alpha = _missing_font ? "60%" : "100%";
+ return Glib::ustring::format(
+ "", text, "");
+ }
+
+private:
+ FontElement(std::vector family, const FontInfo& font, Glib::ustring alt, Type type):
+ _font(font), _family(std::move(family)), _type(type), _alt_fontspec(std::move(alt)) {
+ }
- ~CellFontRenderer() override = default;
+ Glib::ustring get_font_name(Type type) const {
+ auto present = is_present();
+ Glib::ustring name;
+ Glib::RefPtr empty;
- Gtk::Widget* _tree = nullptr;
- bool _show_font_name = true;
- int _font_size = 200; // size in %, where 100 is normal UI font size
- Glib::ustring _sample_text; // text to render (font preview)
- Glib::ustring _name;
+ switch (type) {
+ case Type::Font:
+ // full font name: family + style
+ name = Glib::Markup::escape_text(present ? get_full_font_name(_font.ff, _font.face) : get_alt_name(_alt_fontspec));
+ break;
+ case Type::Family:
+ // font family name only
+ name = Glib::Markup::escape_text(present ? get_full_font_name(_font.ff, empty) : get_alt_name(_alt_fontspec));
+ break;
+ case Type::Style:
+ // font style only
+ name = Glib::Markup::escape_text(_font.face->get_name());
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ return name;
+ }
- void snapshot_vfunc(Glib::RefPtr const &snapshot, Gtk::Widget &widget, Gdk::Rectangle const &background_area, Gdk::Rectangle const &cell_area, Gtk::CellRendererState flags) override;
+ FontInfo _font;
+ std::vector _family;
+ // empty element to keep space in a store
+ bool _placeholder = false;
+ // if font is not present in a system, then show "missing font" icon
+ bool _missing_font = false;
+ // injected element - always show it first, regardless of filters or sorting order
+ bool _injected = false;
+ // this is a fontspec of the missing font
+ Glib::ustring _alt_fontspec;
};
-CellFontRenderer& get_renderer(Gtk::CellRenderer& renderer) {
- return dynamic_cast(renderer);
+// This function constructs a widget to show font info in a list view.
+// List view is capable of being transformed into a tree-like display too.
+void on_set_up_listitem(const Glib::RefPtr& list_item) {
+ // Each ListItem contains a TreeExpander, which contains a box.
+ auto expander = Gtk::make_managed();
+ auto vbox = Gtk::make_managed(Gtk::Orientation::VERTICAL, 1);
+ auto upper = Gtk::make_managed(Gtk::Orientation::HORIZONTAL, 3);
+ auto lower = Gtk::make_managed(Gtk::Orientation::HORIZONTAL, 4);
+ vbox->set_margin_top(2);
+ vbox->set_overflow(Gtk::Overflow::HIDDEN);
+ auto sample = Gtk::make_managed();
+ sample->set_ellipsize(Pango::EllipsizeMode::END);
+ sample->set_halign(Gtk::Align::START);
+ sample->set_margin_start(2); // extra space for fonts extend past the bbox
+ auto name = Gtk::make_managed();
+ name->set_ellipsize(Pango::EllipsizeMode::END);
+ name->set_halign(Gtk::Align::START);
+ name->set_margin_start(4); // indent more than label due to optical alignment
+ lower->append(*name);
+ auto badge = Gtk::make_managed();
+ badge->set_halign(Gtk::Align::CENTER);
+ badge->add_css_class("tag-box");
+ lower->append(*badge);
+ vbox->append(*upper);
+ vbox->append(*lower);
+ auto icon = Gtk::make_managed();
+ icon->set_pixel_size(16);
+ icon->set_valign(Gtk::Align::CENTER);
+ upper->append(*icon);
+ upper->append(*sample);
+ expander->set_child(*vbox);
+ list_item->set_child(*expander);
+ list_item->set_activatable();
+ expander->set_indent_for_icon();
+ expander->set_indent_for_depth();
+ // expander can be collapsed with an action, but it cannot be executed in setup function:
+ // expander->activate_action("listitem.collapse");
}
-Glib::ustring get_font_name(Gtk::TreeIter& iter) {
- if (!iter) return Glib::ustring();
-
- const Inkscape::FontInfo& font = (*iter)[g_column_model.font];
- auto present = !!font.ff;
- Glib::ustring&& alt = (*iter)[g_column_model.alt_fontspec];
- auto name = Glib::Markup::escape_text(present ? get_full_name(font) : get_alt_name(alt));
- return name;
+void on_bind_listitem(int sample_font_size, bool show_name, const Glib::ustring& sample_text, const Glib::RefPtr& list_item) {
+ auto row = std::dynamic_pointer_cast(list_item->get_item());
+ if (!row) return;
+ // if only leaves in the tree can be selected:
+ // list_item->set_selectable(!row->is_expandable());
+ auto element = std::dynamic_pointer_cast(row->get_item());
+ if (!element) return;
+ auto expander = dynamic_cast(list_item->get_child());
+ if (!expander) return;
+
+ expander->set_list_row(row);
+
+ auto vbox = dynamic_cast(expander->get_child());
+ auto& upper = dynamic_cast(*vbox->get_first_child());
+ auto& lower = dynamic_cast(*upper.get_next_sibling());
+ auto& icon = dynamic_cast(*upper.get_first_child());
+ auto& sample = dynamic_cast(*icon.get_next_sibling());
+ auto& name = dynamic_cast(*lower.get_first_child());
+ auto& badge = dynamic_cast(*name.get_next_sibling());
+
+ sample.set_markup(element->get_sample_markup(sample_font_size, sample_text));
+ if (show_name) {
+ name.set_markup(element->get_name_markup());
+ badge.set_markup(element->get_badge_markup());
+ }
+ name.set_visible(show_name);
+ badge.set_visible(show_name);
+ icon.set_from_icon_name(element->icon_name());
+ icon.set_tooltip_text(element->icon_tooltip());
+ icon.set_visible(!element->icon_name().empty());
}
-void get_cell_data_func(Gtk::CellRenderer* cell_renderer, Gtk::TreeModel::ConstRow row) {
- auto& renderer = get_renderer(*cell_renderer);
+// helper function that constructs tree model
+Glib::RefPtr> create_element_model(const Glib::RefPtr& item = {}) {
+ auto element = std::dynamic_pointer_cast(item);
+ if (!element || element && element->family().size() < 2) {
+ // An item without children, i.e. a leaf in the tree.
+ return {};
+ }
- const Inkscape::FontInfo& font = row[g_column_model.font];
- // auto font_class = row[g_column_model.font_class];
- auto present = !!font.ff;
- Glib::ustring&& icon_name = row[g_column_model.icon_name];
- Glib::ustring&& alt = row[g_column_model.alt_fontspec];
+ auto result = Gio::ListStore::create();
+ for (auto& f : element->family()) {
+ result->append(FontElement::create_style(f));
+ }
- auto name = Glib::Markup::escape_text(present ? get_full_name(font) : get_alt_name(alt));
- // if no sample text given, then render font name
- auto text = Glib::Markup::escape_text(renderer._sample_text.empty() ? name : renderer._sample_text);
+ // no visible children?
+ if (result->get_n_items() == 0) {
+ return {};
+ }
- auto font_desc = Glib::Markup::escape_text(
- present ? Inkscape::get_font_description(font.ff, font.face).to_string() : (alt.empty() ? "sans-serif" : alt));
- auto markup = Glib::ustring::format("", text, "");
+ return result;
+}
+
+// Grid view items have a simple shape: just two labels,
+// top one for a font preview and bottom one for a font name.
+// Then we style a box hosting them to make items distinct.
+void on_set_up_griditem(const Glib::RefPtr& list_item) {
+ auto box = Gtk::make_managed(Gtk::Orientation::VERTICAL, 1);
+ auto sample = Gtk::make_managed();
+ auto name = Gtk::make_managed();
+ sample->set_halign(Gtk::Align::CENTER);
+ sample->set_valign(Gtk::Align::CENTER);
+ sample->set_expand();
+ name->set_halign(Gtk::Align::CENTER);
+ name->set_hexpand();
+ name->set_margin_start(1);
+ name->set_margin_end(1);
+ name->set_ellipsize(Pango::EllipsizeMode::END);
+ box->add_css_class("item-box");
+ box->add_css_class("round-rect-shade");
+ box->append(*sample);
+ box->append(*name);
+ list_item->set_child(*box);
+}
- if (renderer._show_font_name) {
- renderer._name = name;
+void on_bind_griditem(int sample_font_size, bool show_name, const Glib::ustring& sample_text, const Glib::RefPtr& list_item) {
+ auto element = std::dynamic_pointer_cast(list_item->get_item());
+ if (!element) return;
+ auto box = dynamic_cast(list_item->get_child());
+ if (!box) return;
+ auto label = dynamic_cast(box->get_first_child());
+ auto name = dynamic_cast(label->get_next_sibling());
+
+ label->set_markup(element->get_sample_markup(sample_font_size, sample_text.empty() ? "Aa" : sample_text));
+ if (show_name) {
+ name->set_markup(element->get_full_name_markup());
}
+ name->set_visible(show_name);
+ box->set_tooltip_text(element->get_name_tooltip());
+}
- renderer.set_property("markup", markup);
+void refilter(Glib::RefPtr& filter) {
+ Gtk::Filter* f = filter.get();
+ gtk_filter_changed(f->gobj(), GtkFilterChange::GTK_FILTER_CHANGE_DIFFERENT);
}
-void CellFontRenderer::snapshot_vfunc(Glib::RefPtr const &snapshot, Gtk::Widget &widget, Gdk::Rectangle const &background_area, Gdk::Rectangle const &cell_area, Gtk::CellRendererState flags) {
- Gdk::Rectangle bgnd(background_area);
- Gdk::Rectangle area(cell_area);
- auto margin = 0; // extra space for icon?
- bgnd.set_x(bgnd.get_x() + margin);
- area.set_x(area.get_x() + margin);
- const auto name_font_size = 10; // attempt to select text size
- const auto bottom = area.get_y() + area.get_height(); // bottom where the info font name will be placed
- Glib::RefPtr layout;
- int text_height = 0;
-
- if (_show_font_name) {
- layout = _tree->create_pango_layout(_name);
- Pango::FontDescription font("Noto"); // wide range of character support
- font.set_weight(Pango::Weight::NORMAL);
- font.set_size(name_font_size * PANGO_SCALE);
- layout->set_font_description(font);
- int text_width = 0;
- // get the text dimensions
- layout->get_pixel_size(text_width, text_height);
- // shrink area to prevent overlap
- area.set_height(area.get_height() - text_height);
- }
-
- CellRendererText::snapshot_vfunc(snapshot, widget, bgnd, area, flags);
-
- if (_show_font_name) {
- auto context = _tree->get_style_context();
- Gtk::StateFlags sflags = _tree->get_state_flags();
- if ((bool)(flags & Gtk::CellRendererState::SELECTED)) {
- sflags |= Gtk::StateFlags::SELECTED;
- }
- context->set_state(sflags);
- Gdk::RGBA fg = context->get_color();
- snapshot->save();
- snapshot->translate({(float)(area.get_x() + 2), (float)bottom - text_height});
- snapshot->append_layout(layout, fg);
- snapshot->restore();
- }
+} // namespace
+
+namespace Inkscape::UI::Widget {
+
+std::unique_ptr FontList::create_font_list(Glib::ustring path) {
+ return std::make_unique(path);
}
-const char* get_sort_icon(Inkscape::FontOrder order) {
- const char* icon = nullptr;
+// list of font sizes for a slider; combo box has its own list
+static std::array g_font_sizes = {
+ 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20, 24, 28, 32, 36,
+ 44, 56, 64, 72, 80, 96, 112, 128, 144, 160, 192, 224, 256,
+ 300, 350, 400, 450, 500, 550, 600, 700, 800
+};
- switch (order) {
- case Inkscape::FontOrder::by_name:
- icon = "sort-alphabetically-symbolic";
- break;
+static int index_to_font_size(int index) {
+ if (index < 0) {
+ return g_font_sizes.front();
+ }
+ else if (index >= g_font_sizes.size()) {
+ return g_font_sizes.back();
+ }
+ else {
+ return g_font_sizes[index];
+ }
+}
- case Inkscape::FontOrder::by_weight:
- icon = "sort-by-weight-symbolic";
- break;
+static int font_size_to_index(double size) {
+ auto it = std::lower_bound(begin(g_font_sizes), end(g_font_sizes), static_cast(size));
+ return std::distance(begin(g_font_sizes), it);
+}
- case Inkscape::FontOrder::by_width:
- icon = "sort-by-width-symbolic";
- break;
+const char* get_sort_icon(FontOrder order) {
+ const char* icon = nullptr;
- default:
- g_warning("Missing case in get_sort_icon");
- break;
+ switch (order) {
+ case FontOrder::ByFamily:
+ icon = "sort-by-family-symbolic";
+ break;
+ case FontOrder::ByName:
+ icon = "sort-alphabetically-symbolic";
+ break;
+ case FontOrder::ByWeight:
+ icon = "sort-by-weight-symbolic";
+ break;
+ case FontOrder::ByWidth:
+ icon = "sort-by-width-symbolic";
+ break;
+ default:
+ g_warning("Missing case in get_sort_icon");
+ break;
}
return icon;
}
-void set_grid_cell_size(Gtk::CellRendererText* renderer, int font_size_percent) {
- // TODO: use pango layout to calc sizes
- int size = 20 * font_size_percent / 100;
- renderer->set_fixed_size(size * 4 / 3, size);
-};
-
FontList::FontList(Glib::ustring preferences_path) :
_prefs(std::move(preferences_path)),
_builder(create_builder("font-list.glade")),
_main_grid(get_widget(_builder, "main-grid")),
_tag_list(get_widget(_builder, "categories")),
- _font_list(get_widget(_builder, "font-list")),
- _font_grid(get_widget(_builder, "font-grid")),
+ _font_list(get_widget(_builder, "font-list")),
+ _font_grid(get_widget(_builder, "font-grid")),
_font_size(get_widget(_builder, "font-size")),
_font_size_scale(get_widget(_builder, "font-size-scale")),
+ _preview_size_scale(get_widget(_builder, "preview-font-size")),
+ _grid_size_scale(get_widget(_builder, "grid-font-size")),
+ _grid_sample_entry(get_widget(_builder, "grid-sample")),
+ _list_sample_entry(get_widget(_builder, "sample-text")),
_tag_box(get_widget(_builder, "tag-box")),
_info_box(get_widget(_builder, "info-box")),
_progress_box(get_widget(_builder, "progress-box")),
_search(get_widget(_builder, "font-search")),
- _font_tags(Inkscape::FontTags::get())
+ _var_axes(get_widget(_builder, "var-axes")),
+ _font_tags(FontTags::get())
{
- _cell_renderer = std::make_unique();
- auto font_renderer = static_cast(_cell_renderer.get());
- font_renderer->_tree = &_font_list;
-
- _cell_icon_renderer = std::make_unique();
- auto ico = static_cast(_cell_icon_renderer.get());
- ico->set_fixed_size(16, 16);
+ _font_store = Gio::ListStore::create();
+
+ // common filtering action for placeholder and injected font:
+ // - placeholders: always hidden
+ // - injected fonts: always visible
+#define HANDLE_SPECIAL_FONT \
+ auto font = std::dynamic_pointer_cast(item); \
+ if (!font || font->is_placeholder()) return false; \
+ if (font->is_injected()) return true;
+
+ // filter for:
+ // - grouping fonts by family
+ _family_filter = Gtk::BoolFilter::create(Gtk::ClosureExpression::create([this](auto& item) {
+ HANDLE_SPECIAL_FONT
+
+ // if grouping by family is on filter out individual font styles leaving only "Regular" font
+ if (_order == FontOrder::ByFamily && !font->is_family()) return false;
+
+ return true;
+ }));
+
+ // filter for:
+ // - font collections
+ // - font categories
+ _font_filter = Gtk::BoolFilter::create(Gtk::ClosureExpression::create([this](auto& item){
+ HANDLE_SPECIAL_FONT
+
+ // apply categories, if any
+ auto& active_categories = _font_tags.get_selected_tags();
+ if (!active_categories.empty()) {
+ bool filter_in = false;
+ auto&& set = _font_tags.get_font_tags(font->font().face);
+ for (auto&& ftag : active_categories) {
+ if (set.contains(ftag.tag)) {
+ filter_in = true;
+ break;
+ }
+ }
+ if (!filter_in) return false;
+ }
- _grid_renderer = std::make_unique();
- auto grid_renderer = static_cast(_grid_renderer.get());
- grid_renderer->_show_font_name = false;
+ // check for selected font collections, if any
+ auto fc = FontCollections::get();
+ auto& font_collections = fc->get_selected_collections();
+ if (!font_collections.empty()) {
+ bool filter_in = false;
+ for (auto& col : font_collections) {
+ if (fc->is_font_in_collection(col, font->font().ff->get_name())) {
+ filter_in = true;
+ break;
+ }
+ }
+ if (!filter_in) return false;
+ }
- _font_list_store = Gtk::ListStore::create(g_column_model);
+ return true; // filter in
+ }));
+
+ // filter for matching search text
+ _text_filter = Gtk::BoolFilter::create(Gtk::ClosureExpression::create([this](auto& item) {
+ HANDLE_SPECIAL_FONT
+
+ if (_search_term.empty()) return true;
+
+ // TODO: vary name based on font '_type'?
+ auto text = get_full_name(font->font()).lowercase();
+ return text.find(_search_term) != Glib::ustring::npos;
+ }));
+
+#undef HANDLE_SPECIAL_FONT
+
+ // set up tree view
+ {
+ // cascade filters; currently family filter needs to be first to find the "Regular" font
+ auto filtered1 = Gtk::FilterListModel::create(_font_store, _family_filter);
+ auto filtered2 = Gtk::FilterListModel::create(filtered1, _font_filter);
+ auto filtered3 = Gtk::FilterListModel::create(filtered2, _text_filter);
+ auto tree_model = Gtk::TreeListModel::create(filtered3, [](auto& item){ return create_element_model(item); }, false, false);
+ _list_selection = Gtk::SingleSelection::create(tree_model);
+ // we need autoselect off, so collapsing nodes does not move selection
+ _list_selection->set_autoselect(false);
+ // manually unselecting current font doesn't seem to serve any purpose, so it is disabled
+ _list_selection->set_can_unselect(false);
+ auto factory = Gtk::SignalListItemFactory::create();
+ factory->signal_setup().connect([](auto& item){ on_set_up_listitem(item); });
+ factory->signal_bind().connect([this](auto& item){ on_bind_listitem(_sample_font_size, _show_font_names, _sample_text, item); });
+ _font_list.set_show_separators();
+ _font_list.set_model(_list_selection);
+ _font_list.set_factory(factory);
+ }
+ // set up grid view
+ {
+ // cascade filters; no family filter for a grid, there's no collapsing there
+ auto filtered1 = Gtk::FilterListModel::create(_font_store, _font_filter);
+ auto filtered2 = Gtk::FilterListModel::create(filtered1, _text_filter);
+ _grid_selection = Gtk::SingleSelection::create(filtered2);
+ _grid_selection->set_can_unselect(false);
+ _font_grid.set_model(_grid_selection);
+ auto factory = Gtk::SignalListItemFactory::create();
+ factory->signal_setup().connect([](auto& item){ on_set_up_griditem(item); });
+ factory->signal_bind().connect([this](auto& item){ on_bind_griditem(_grid_font_size, _show_font_names, _grid_sample_text, item); });
+ _font_grid.set_factory(factory);
+ }
- get_widget(_builder, "variants").append(_font_variations);
+ _var_axes.set_visible(false);
+ _var_axes.set_child(_font_variations);
_font_variations.get_size_group(0)->add_widget(get_widget(_builder, "font-size-label"));
_font_variations.get_size_group(1)->add_widget(_font_size);
_font_variations.connectChanged([this]{
@@ -262,35 +548,40 @@ FontList::FontList(Glib::ustring preferences_path) :
set_margin_top(5);
set_margin_bottom(0);
- auto options = &get_widget(_builder, "btn-options");
- auto options_grid = &get_widget(_builder, "options-grid");
- options->signal_toggled().connect([=](){
- options_grid->set_visible(options->get_active());
- });
+ auto prefs = Preferences::get();
- std::pair sorting[3] = {
- {N_("Sort alphabetically"), Inkscape::FontOrder::by_name},
- {N_("Light to heavy"), Inkscape::FontOrder::by_weight},
- {N_("Condensed to expanded"), Inkscape::FontOrder::by_width}
+ std::pair sorting[4] = {
+ {N_("Group by family"), FontOrder::ByFamily},
+ {N_("Sort alphabetically"), FontOrder::ByName},
+ {N_("Light to heavy"), FontOrder::ByWeight},
+ {N_("Condensed to expanded"), FontOrder::ByWidth}
};
auto sort_menu = Gtk::make_managed(Gtk::PositionType::BOTTOM);
- for (auto&& el : sorting) {
+ for (auto&[label, order] : sorting) {
auto item = Gtk::make_managed();
+ if (order == FontOrder::ByFamily) {
+ _sort_by_family = item;
+ }
auto hbox = Gtk::make_managed();
- hbox->append(*Gtk::manage(sp_get_icon_image(get_sort_icon(el.second), Gtk::IconSize::NORMAL)));
- hbox->append(*Gtk::make_managed(_(el.first)));
+ hbox->append(*Gtk::manage(sp_get_icon_image(get_sort_icon(order), Gtk::IconSize::NORMAL)));
+ hbox->append(*Gtk::make_managed(_(label)));
hbox->set_spacing(4);
item->set_child(*hbox);
- item->signal_activate().connect([=, this] { sort_fonts(el.second); });
+ item->signal_activate().connect([=, this] {
+ _order = order;
+ set_sort_icon();
+ sort_fonts(order);
+ prefs->setInt(_prefs + "/font-order", static_cast(_order));
+ });
sort_menu->append(*item);
}
get_widget(_builder, "btn-sort").set_popover(*sort_menu);
- get_widget(_builder, "id-reset-filter").signal_clicked().connect([this](){
+ get_widget(_builder, "id-reset-filter").signal_clicked().connect([this] {
bool modified = false;
if (_font_tags.deselect_all()) {
modified = true;
}
- auto fc = Inkscape::FontCollections::get();
+ auto fc = FontCollections::get();
if (!fc->get_selected_collections().empty()) {
fc->clear_selected_collections();
modified = true;
@@ -309,8 +600,8 @@ FontList::FontList(Glib::ustring preferences_path) :
_current_font_instance = FontFactory::get().FaceFromPangoString(spec.c_str());
Glib::ustring name;
- if (auto iter = get_selected_font()) {
- const auto& font = iter->get_value(g_column_model.font);
+ if (auto element = std::dynamic_pointer_cast(get_selected_font())) {
+ const auto& font = element->font();
name = get_full_name(font);
}
_charmap.set_font(_current_font_instance.get(), name);
@@ -332,77 +623,72 @@ FontList::FontList(Glib::ustring preferences_path) :
_signal_insert_text.emit(text);
});
- _search.signal_changed().connect([this]{ filter(); });
-
- auto set_row_height = [font_renderer, this](int font_size_percent) {
- font_renderer->_font_size = font_size_percent;
- // TODO: use pango layout to calc sizes
- int hh = (font_renderer->_show_font_name ? 10 : 0) + 18 * font_renderer->_font_size / 100;
- font_renderer->set_fixed_size(-1, hh);
- // resize rows
- _font_list.set_fixed_height_mode(false);
- _font_list.set_fixed_height_mode();
- };
- auto set_grid_size = [=](int font_size_percent) {
- grid_renderer->_font_size = font_size_percent;
- set_grid_cell_size(grid_renderer, font_size_percent);
- };
-
- auto prefs = Inkscape::Preferences::get();
+ _search.signal_changed().connect([this] {
+ apply_filters_keep_selection(true);
+ });
- font_renderer->_font_size = prefs->getIntLimited(_prefs + "/preview-size", 200, 100, 800);
- auto size = &get_widget(_builder, "preview-font-size");
- size->set_format_value_func([](double val){
+ _sample_font_size = prefs->getIntLimited(_prefs + "/preview-size", _sample_font_size, 100, 800);
+ _preview_size_scale.set_format_value_func([](double val){
return Glib::ustring::format(std::fixed, std::setprecision(0), val) + "%";
});
- size->set_value(font_renderer->_font_size);
- size->signal_value_changed().connect([=, this]{
- auto font_size = size->get_value();
- set_row_height(font_size);
- set_grid_size(font_size);
- prefs->setInt(_prefs + "/preview-size", font_size);
- // resize
- filter();
+ _preview_size_scale.set_value(_sample_font_size);
+ _preview_size_scale.signal_value_changed().connect([=, this]{
+ _sample_font_size = _preview_size_scale.get_value();
+ prefs->setInt(_prefs + "/preview-size", _sample_font_size);
+ rebuild_ui();
+ });
+ _grid_font_size = prefs->getIntLimited(_prefs + "/grid-size", _grid_font_size, 100, 800);
+ _grid_size_scale.set_format_value_func([](double val){
+ return Glib::ustring::format(std::fixed, std::setprecision(0), val) + "%";
+ });
+ _grid_size_scale.set_value(_grid_font_size);
+ _grid_size_scale.signal_value_changed().connect([=, this]{
+ _grid_font_size = _grid_size_scale.get_value();
+ prefs->setInt(_prefs + "/grid-size", _grid_font_size);
+ rebuild_ui();
+ });
+
+ auto to_top = prefs->getBool(_prefs + "/font-size-top", false);
+ set_font_size_layout(to_top);
+ auto size_top = &get_widget(_builder, "font-size-top");
+ size_top->set_active(to_top);
+ get_widget(_builder, "font-size-bottom").set_active(!to_top);
+ size_top->signal_toggled().connect([=, this] {
+ bool top = size_top->get_active();
+ set_font_size_layout(top);
+ prefs->setBool(_prefs + "/font-size-top", top);
});
auto show_names = &get_widget(_builder, "show-font-name");
auto set_show_names = [=, this](bool show) {
- font_renderer->_show_font_name = show;
+ _show_font_names = show;
prefs->setBool(_prefs + "/show-font-names", show);
- set_row_height(font_renderer->_font_size);
- _font_list.set_grid_lines(show ? Gtk::TreeView::GridLines::HORIZONTAL : Gtk::TreeView::GridLines::NONE);
- // resize
- filter();
+ rebuild_ui();
};
auto show = prefs->getBool(_prefs + "/show-font-names", true);
set_show_names(show);
show_names->set_active(show);
- show_names->signal_toggled().connect([=](){
+ show_names->signal_toggled().connect([=] {
bool show = show_names->get_active();
set_show_names(show);
});
// sample text to show for each font; empty to show font name
- auto sample = &get_widget(_builder, "sample-text");
- auto sample_text = prefs->getString(_prefs + "/sample-text");
- sample->set_text(sample_text);
- font_renderer->_sample_text = sample_text;
- sample->signal_changed().connect([=, this](){
- auto text = sample->get_text();
- font_renderer->_sample_text = text;
- prefs->setString(_prefs + "/sample-text", text);
- _font_list.queue_draw();
+ _sample_text = prefs->getString(_prefs + "/sample-text");
+ _list_sample_entry.set_text(_sample_text);
+ _list_sample_entry.signal_changed().connect([=, this] {
+ _sample_text = _list_sample_entry.get_text();
+ prefs->setString(_prefs + "/sample-text", _sample_text);
+ rebuild_ui();
});
+
// sample text for grid
- auto grid_sample = &get_widget(_builder, "grid-sample");
- auto sample_grid_text = prefs->getString(_prefs + "/grid-text", "Aa");
- grid_sample->set_text(sample_grid_text);
- grid_renderer->_sample_text = sample_grid_text;
- grid_sample->signal_changed().connect([=, this](){
- auto text = grid_sample->get_text();
- grid_renderer->_sample_text = text.empty() ? "?" : text;
- prefs->setString(_prefs + "/grid-text", text);
- _font_grid.queue_draw();
+ _grid_sample_text = prefs->getString(_prefs + "/grid-text", "Aa");
+ _grid_sample_entry.set_text(_grid_sample_text);
+ _grid_sample_entry.signal_changed().connect([=, this] {
+ _grid_sample_text = _grid_sample_entry.get_text();
+ prefs->setString(_prefs + "/grid-text", _grid_sample_text);
+ rebuild_ui();
});
// Populate samples submenu from stringlist
@@ -435,106 +721,68 @@ FontList::FontList(Glib::ustring preferences_path) :
// Hook up action used by samples submenu
auto action_group = Gio::SimpleActionGroup::create();
- action_group->add_action_with_parameter("set-sample", Glib::Variant::variant_type(), [=] (Glib::VariantBase const ¶m) {
+ action_group->add_action_with_parameter("set-sample", Glib::Variant::variant_type(), [=, this] (Glib::VariantBase const ¶m) {
auto param_str = Glib::VariantBase::cast_dynamic>(param).get();
- sample->set_text(param_str);
+ _list_sample_entry.set_text(param_str);
});
insert_action_group("win", action_group);
- _text_column.set_sizing(Gtk::TreeViewColumn::Sizing::FIXED);
- _text_column.pack_start(*_cell_icon_renderer, false);
- _text_column.add_attribute(ico->property_icon_name(), g_column_model.icon_name);
- _cell_renderer->property_ellipsize() = Pango::EllipsizeMode::END;
- _text_column.pack_start(*_cell_renderer, true);
- _text_column.set_fixed_width(100); // limit minimal width to keep entire dialog narrow; column can still grow
- _text_column.set_cell_data_func(*_cell_renderer, [=](Gtk::CellRenderer* r, const Gtk::TreeModel::const_iterator& it) {
- Gtk::TreeModel::ConstRow row = *it;
- get_cell_data_func(r, row);
- });
- _text_column.set_expand();
- _font_list.append_column(_text_column);
-
- _font_list.set_fixed_height_mode();
- set_row_height(font_renderer->_font_size);
- //todo: restore grid size and view?
- _font_list.set_search_column(-1); // disable, we have a separate search/filter
- _font_list.set_enable_search(false);
- _font_list.set_model(_font_list_store);
-
- _font_grid.pack_start(*_grid_renderer);
- grid_renderer->set_fixed_height_from_font(-1);
- set_grid_size(grid_renderer->_font_size);
- // grid_renderer->_sample_text = "Aa";
- _font_grid.set_cell_data_func(*grid_renderer, [=](const Gtk::TreeModel::const_iterator& it) {
- Gtk::TreeModel::ConstRow row = *it;
- get_cell_data_func(grid_renderer, row);
- });
- // show font name in a grid tooltip
- _font_grid.signal_query_tooltip().connect([this](int x, int y, bool kbd, const Glib::RefPtr& tt){
- Gtk::TreeModel::iterator iter;
- Glib::ustring name;
- if (_font_grid.get_tooltip_context_iter(x, y, kbd, iter)) {
- const auto& font = iter->get_value(g_column_model.font);
- name = get_font_name(iter);
- tt->set_text(name);
- }
- return !name.empty();
- }, true);
- _font_grid.property_has_tooltip() = true;
-
auto font_selected = [this](const FontInfo& font) {
if (_update.pending()) return;
auto scoped = _update.block();
auto vars = font.variations;
if (vars.empty() && font.variable_font) {
- vars = Inkscape::get_inkscape_fontspec(font.ff, font.face, font.variations);
+ vars = get_inkscape_fontspec(font.ff, font.face, font.variations);
}
_font_variations.update(vars);
+ _var_axes.set_visible(_font_variations.variations_present());
_signal_changed.emit();
};
- _selection_changed = _font_grid.signal_selection_changed().connect([font_selected, this]{
- auto sel = _font_grid.get_selected_items();
- if (sel.size() == 1) {
- auto it = _font_list_store->get_iter(sel.front());
- const Inkscape::FontInfo& font = (*it)[g_column_model.font];
- font_selected(font);
+ _list_selection->signal_selection_changed().connect([font_selected, this](auto index, auto n) {
+ if (auto element = std::dynamic_pointer_cast(get_selected_font())) {
+ font_selected(element->font());
}
});
+ _font_list.signal_activate().connect([this](auto index) {
+ if (_update.pending()) return;
- auto show_grid = &get_widget(_builder, "view-grid");
- auto show_list = &get_widget(_builder, "view-list");
- auto set_list_view_mode = [prefs, this](bool show_list) {
- auto& list = get_widget(_builder, "list");
- auto& grid = get_widget(_builder, "grid");
- // TODO: sync selection between font widgets
- if (show_list) {
- grid.set_visible(false);
- _font_grid.unset_model();
- list.set_visible();
+ if (auto element = std::dynamic_pointer_cast(get_nth_font(index))) {
+ auto scoped = _update.block();
+ _signal_apply.emit();
}
- else {
- list.set_visible(false);
- _font_grid.set_model(_font_list_store);
- grid.set_visible();
+ });
+
+ _grid_selection->signal_selection_changed().connect([font_selected, this](auto index, auto n) {
+ if (auto element = std::dynamic_pointer_cast(get_selected_font())) {
+ font_selected(element->font());
}
- _view_mode_list = show_list;
- get_widget(_builder, "grid-sample").set_visible(!show_list);
- prefs->setBool(_prefs + "/list-view-mode", show_list);
- };
+ });
+ _font_grid.signal_activate().connect([this](auto index) {
+ if (_update.pending()) return;
+
+ if (auto element = std::dynamic_pointer_cast(get_nth_font(index))) {
+ auto scoped = _update.block();
+ _signal_apply.emit();
+ }
+ });
+
+ // set up list/tree view vs grid view mode switching
auto list_mode = prefs->getBool(_prefs + "/list-view-mode", true);
+ switch_view_mode(list_mode);
+ auto show_grid = &get_widget(_builder, "view-grid");
+ auto show_list = &get_widget(_builder, "view-list");
if (list_mode) show_list->set_active(); else show_grid->set_active();
- set_list_view_mode(list_mode);
- show_list->signal_toggled().connect([=] { set_list_view_mode(true); });
- show_grid->signal_toggled().connect([=] { set_list_view_mode(false); });
+ show_list->signal_toggled().connect([show_list, this] {
+ switch_view_mode(show_list->get_active());
+ });
- _fonts.clear();
_initializing = 0;
_info_box.set_visible(false);
_progress_box.show();
- auto prepare_tags = [this](){
+ auto prepare_tags = [this]{
// prepare dynamic tags
for (auto&& f : _fonts) {
auto kind = f.family_kind >> 8;
@@ -565,10 +813,13 @@ FontList::FontList(Glib::ustring preferences_path) :
_font_stream = FontDiscovery::get().connect_to_fonts([=, this](const FontDiscovery::MessageType& msg){
if (auto r = Async::Msg::get_result(msg)) {
- _fonts = **r;
+ _font_families = **r;
+ _fonts.clear();
+ for (auto&& family : _font_families) {
+ _fonts.insert(_fonts.end(), family.begin(), family.end());
+ }
sort_fonts(_order);
prepare_tags();
- filter();
}
else if (auto p = Async::Msg::get_progress(msg)) {
// show progress
@@ -579,12 +830,14 @@ FontList::FontList(Glib::ustring preferences_path) :
progress.set_text(std::get(*p));
auto&& family = std::get>(*p);
_fonts.insert(_fonts.end(), family.begin(), family.end());
+ if (!family.empty()) {
+ _font_families.push_back(std::move(family));
+ }
auto delta = _fonts.size() - _initializing;
// refresh fonts; at first more frequently, every new 100, but then more slowly, as it gets costly
if (delta > 500 || (_fonts.size() < 500 && delta > 100)) {
_initializing = _fonts.size();
sort_fonts(_order);
- filter();
}
}
else if (Async::Msg::is_finished(msg)) {
@@ -596,7 +849,7 @@ FontList::FontList(Glib::ustring preferences_path) :
_font_size_scale.get_adjustment()->set_lower(0);
_font_size_scale.get_adjustment()->set_upper(g_font_sizes.size() - 1);
- _font_size_scale.signal_value_changed().connect([this](){
+ _font_size_scale.signal_value_changed().connect([this] {
if (_update.pending()) return;
auto scoped = _update.block();
@@ -605,7 +858,7 @@ FontList::FontList(Glib::ustring preferences_path) :
_signal_changed.emit();
});
- _font_size.signal_changed().connect([this](){
+ _font_size.signal_changed().connect([this] {
if (_update.pending()) return;
auto scoped = _update.block();
@@ -621,199 +874,269 @@ FontList::FontList(Glib::ustring preferences_path) :
_font_size.set_active_text("10");
_font_size.get_entry()->set_max_width_chars(6);
- sort_fonts(Inkscape::FontOrder::by_name);
-
- _font_list.get_selection()->signal_changed().connect([=, this](){
- if (auto iter = _font_list.get_selection()->get_selected()) {
- const Inkscape::FontInfo& font = (*iter)[g_column_model.font];
- font_selected(font);
- }
- }, false);
-
- // double-click:
- _font_list.signal_row_activated().connect([this](const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn*){
- if (!_update.pending()) {
- auto scoped = _update.block();
- _signal_apply.emit();
- }
- });
+ // restore sorting
+ _order = static_cast(prefs->getIntLimited(_prefs + "/font-order", static_cast(_order), static_cast(FontOrder::_First), static_cast(FontOrder::_Last)));
+ set_sort_icon();
+ sort_fonts(_order);
_font_tags.get_signal_tag_changed().connect([this](const FontTag* ftag, bool selected){
sync_font_tag(ftag, selected);
});
auto& filter_popover = get_widget(_builder, "filter-popover");
- filter_popover.signal_show().connect([this](){
+ filter_popover.signal_show().connect([this] {
// update tag checkboxes
add_categories();
update_filterbar();
}, false);
- _font_collections_update = Inkscape::FontCollections::get()->connect_update([this]() {
+ _font_collections_update = FontCollections::get()->connect_update([this] {
add_categories();
update_filterbar();
- filter();
+ apply_filters_keep_selection();
});
- _font_collections_selection = Inkscape::FontCollections::get()->connect_selection_update([this](){
+ _font_collections_selection = FontCollections::get()->connect_selection_update([this] {
add_categories();
update_filterbar();
- filter();
+ apply_filters_keep_selection();
});
}
-void FontList::sort_fonts(Inkscape::FontOrder order) {
- Inkscape::sort_fonts(_fonts, order, true);
+void FontList::set_sort_icon() {
+ auto order = _order;
+ if (order == FontOrder::ByFamily && !_list_visible) {
+ order = FontOrder::ByName;
+ }
+ // this option only applies to a list/tree view
+ _sort_by_family->set_visible(_list_visible);
if (const char* icon = get_sort_icon(order)) {
auto& button = get_widget(_builder, "btn-sort");
button.set_icon_name(icon);
}
-
- filter();
}
-bool FontList::select_font(const Glib::ustring& fontspec) {
- bool found = false;
+void FontList::sort_fonts(FontOrder order) {
+ Inkscape::sort_fonts(_fonts, order, true);
+
+ sort_font_families(_font_families, true);
- _font_list_store->foreach([&](const Gtk::TreeModel::Path& path, const Gtk::TreeModel::iterator& iter){
- const auto& row = *iter;
- const auto& font = row.get_value(g_column_model.font);
- if (!font.ff) {
- auto spec = row.get_value(g_column_model.alt_fontspec);
+ rebuild_store();
+}
+
+int FontList::find_font(const Glib::ustring& fontspec, int from, int count) const {
+ auto& selection = _list_visible ? _list_selection : _grid_selection;
+ auto total = selection->get_n_items();
+ auto n = count > 0 ? std::min(count, static_cast(total)) : total;
+ for (int i = from; i < n; ++i) {
+ auto element = std::dynamic_pointer_cast(get_nth_font(i));
+ if (!element) continue;
+
+ if (element->is_present()) {
+ auto& font = element->font();
+ auto spec = get_inkscape_fontspec(font.ff, font.face, font.variations);
if (spec == fontspec) {
- _font_list.get_selection()->select(row.get_iter());
- _font_grid.select_path(path);
- scroll_to_row(path);
- found = true;
- return true; // stop
+ return i;
}
}
else {
- const auto& font = row.get_value(g_column_model.font);
- auto spec = Inkscape::get_inkscape_fontspec(font.ff, font.face, font.variations);
+ auto& spec = element->get_alt_spec();
if (spec == fontspec) {
- _font_list.get_selection()->select(row.get_iter());
- _font_grid.select_path(path);
- scroll_to_row(path);
- found = true;
- return true; // stop
+ return i;
}
}
- return false; // continue
- });
+ }
+ return -1;
+}
- return found;
+void FontList::switch_view_mode(bool show_list) {
+ // get current selection to sync selection between font widgets
+ auto fontspec = get_fontspec();
+ _list_visible = show_list;
+ auto& list = get_widget(_builder, "list");
+ auto& grid = get_widget(_builder, "grid");
+ if (show_list) {
+ grid.set_visible(false);
+ _font_grid.set_model({});
+ _font_list.set_model(_list_selection);
+ list.set_visible();
+ }
+ else {
+ list.set_visible(false);
+ _font_list.set_model({});
+ _font_grid.set_model(_grid_selection);
+ grid.set_visible();
+ }
+ // update sort icon: grid view does not support grouping by family
+ set_sort_icon();
+ // update widgets in an option popup
+ get_widget(_builder, "sample-menu-btn").set_sensitive(show_list);
+ _list_sample_entry.set_visible(show_list);
+ _preview_size_scale.set_visible(show_list);
+ _grid_sample_entry.set_visible(!show_list);
+ _grid_size_scale.set_visible(!show_list);
+ Preferences::get()->setBool(_prefs + "/list-view-mode", show_list);
+ // try to reselect the same font in a new view
+ select_font(fontspec);
}
-void FontList::filter() {
+bool FontList::select_font(const Glib::ustring& fontspec) {
auto scoped = _update.block();
- // todo: save selection
- Inkscape::FontInfo selected;
- auto it = _font_list.get_selection()->get_selected();
- if (it) {
- selected = it->get_value(g_column_model.font);
- }
-
- // Not used: extra search terms; use collections instead
- // auto& oblique = get_widget(_builder, "id-oblique");
- // auto& monospaced = get_widget(_builder, "id-monospaced");
- // auto& others = get_widget(_builder, "id-other");
- Show params;
- // Not used
- // params.monospaced = monospaced.get_active();
- // params.oblique = oblique.get_active();
- // params.others = others.get_active();
- populate_font_store(_search.get_text(), params);
- if (!_current_fspec.empty()) {
- add_font(_current_fspec, false);
- }
+ bool found = false;
- if (it) {
- // reselect if that font is still available
- //TODO
+ auto pos = find_font(fontspec);
+ if (pos >= 0) {
+ found = true;
+ scroll_to_row(pos);
}
-}
-Glib::ustring get_font_icon(const FontInfo& font, bool missing_font) {
- if (missing_font) {
- return "missing-element-symbolic";
- }
- else if (font.variable_font) {
- return ""; // TODO: add icon for variable fonts?
- }
- else if (font.synthetic) {
- return "generic-font-symbolic";
+ if (!found && _list_visible && _order == FontOrder::ByFamily) {
+ // if some fonts in a tree are collapsed, their leaves are not visible to the selection model;
+ // we need to check all styles too:
+
+ int pos = -1;
+ for (auto&& fam : _font_families) {
+ for (auto& font : fam) {
+ auto spec = get_inkscape_fontspec(font.ff, font.face, font.variations);
+ if (spec == fontspec) {
+ // find "family" node in the tree
+ auto& regular = get_family_font(fam);
+ spec = get_inkscape_fontspec(regular.ff, regular.face, regular.variations);
+ pos = find_font(spec);
+ break;
+ }
+ }
+ if (pos >= 0) {
+ if (auto row = std::dynamic_pointer_cast(_list_selection->get_object(pos))) {
+ // expand family row to populate styles
+ row->set_expanded();
+ pos = find_font(fontspec, pos, pos + 1 + fam.size());
+ if (pos >= 0) {
+ found = true;
+ scroll_to_row(pos);
+ }
+ }
+ break;
+ }
+ }
}
- return Glib::ustring();
+
+ return found;
}
-// add fonts to the font store taking filtring params into account
-void FontList::populate_font_store(Glib::ustring text, const Show& params) {
- auto filter = text.lowercase();
+void FontList::rebuild_store() {
+ // recreate content of the font store
- auto active_categories = _font_tags.get_selected_tags();
- auto apply_categories = !active_categories.empty();
+ auto scoped = _update.block();
- auto fc = Inkscape::FontCollections::get();
- auto font_collections = fc->get_selected_collections();
- auto apply_font_collections = !font_collections.empty();
+ // save selection
+ auto fontspec = get_fontspec();
_font_list.set_visible(false); // hide tree view temporarily to speed up rebuild
_font_grid.set_visible(false);
- _font_list_store->freeze_notify();
- _font_list_store->clear();
- _extra_fonts = 0;
+ _font_list.set_model(nullptr);
+ _font_grid.set_model(nullptr);
- for (auto&& f : _fonts) {
- bool filter_in = false;
+ populate_font_store(_order == FontOrder::ByFamily);
- if (!text.empty()) {
- auto name1 = get_full_name(f);
- if (name1.lowercase().find(filter) == Glib::ustring::npos) continue;
- }
+ if (!_current_fspec.empty()) {
+ add_font(_current_fspec, false);
+ }
- if (apply_categories || apply_font_collections) {
- if (apply_categories) {
- auto&& set = _font_tags.get_font_tags(f.face);
- for (auto&& ftag : active_categories) {
- if (set.contains(ftag.tag)) {
- filter_in = true;
- break;
- }
- }
- }
+ apply_filters();
- if (!filter_in && apply_font_collections) {
- for (auto& col : font_collections) {
- if (fc->is_font_in_collection(col, f.ff->get_name())) {
- filter_in = true;
- break;
- }
- }
- }
+ _font_list.set_visible();
+ _font_grid.set_visible();
+ rebuild_ui();
+
+ // reselect if that font is still available
+ select_font(fontspec);
+}
- if (!filter_in) continue;
+void FontList::apply_filters_keep_selection(bool text_only) {
+ // save selection
+ auto fontspec = get_fontspec();
+
+ if (auto placeholder = std::dynamic_pointer_cast(_font_store->get_object(0))) {
+ if (placeholder->is_injected()) {
+ // now is the time to remove injected fonts; they will be reinserted as needed
+ placeholder->clear_injected();
+ //
+ // text_only = false;
}
+ }
+
+ apply_filters(!text_only);
+
+ // restore
+ select_font(fontspec);
+}
+
+void FontList::apply_filters(bool all_filters) {
+ auto scoped = _update.block();
- Gtk::TreeModel::iterator treeModelIter = _font_list_store->append();
- auto& row = *treeModelIter;
- row[g_column_model.font] = f;
- row[g_column_model.alt_fontspec] = Glib::ustring();
- row[g_column_model.icon_name] = get_font_icon(f, false);
+ if (all_filters) {
+ refilter(_family_filter);
+ refilter(_font_filter);
}
- _font_list_store->thaw_notify();
- _font_list.set_visible(); // restore visibility
- _font_grid.set_visible();
+ _search_term = _search.get_text().lowercase();
+ refilter(_text_filter);
update_font_count();
}
+void FontList::rebuild_ui() {
+ // force the UI to be recreated when all items are impacted
+
+ _font_list.set_model(nullptr);
+ _font_grid.set_model(nullptr);
+
+ if (_list_visible) {
+ _font_list.set_model(_list_selection);
+ }
+ else {
+ _font_grid.set_model(_grid_selection);
+ }
+}
+
+// add fonts to the font store replacing its content
+void FontList::populate_font_store(bool by_family) {
+ _font_store->freeze_notify();
+ _font_store->remove_all();
+
+ // reserve first spot; it will be used for "missing" or "injected" fonts, as needed
+ _font_store->append(FontElement::create_placeholder());
+
+ if (by_family) {
+ for (auto&& fam : _font_families) {
+ auto& regular = get_family_font(fam);
+ for (auto& font : fam) {
+ if (font == regular) {
+ _font_store->append(FontElement::create_family(regular, fam));
+ }
+ else {
+ _font_store->append(FontElement::create_font(font));
+ }
+ }
+ }
+ }
+ else {
+ for (auto&& font : _fonts) {
+ _font_store->append(FontElement::create_font(font));
+ }
+ }
+
+ _font_store->thaw_notify();
+}
+
void FontList::update_font_count() {
auto& font_count = get_widget(_builder, "font-count");
- auto count = _font_list_store->children().size();
- auto total = _fonts.size();
+ // total number of fonts; subtract one, there's a placeholder or "missing" font entry always present
+ auto total = _font_store->get_n_items() - 1;
+ // use grid selection model, as it always reflects current number of fonts (list view can be partially collapsed)
+ auto count = _grid_selection->get_n_items();
// count could be larger than total if we insert "missing" font(s)
auto label = count >= total ? C_("N-of-fonts", "All fonts") : Glib::ustring::format(count, ' ', C_("N-of-fonts", "of"), ' ', total, ' ', C_("N-of-fonts", "fonts"));
font_count.set_text(label);
@@ -828,31 +1151,43 @@ double FontList::get_fontsize() const {
return _current_fsize;
}
-Gtk::TreeModel::iterator FontList::get_selected_font() const {
- if (_view_mode_list) {
- return _font_list.get_selection()->get_selected();
+Glib::RefPtr FontList::get_nth_font(int index) const {
+ if (_list_visible) {
+ if (auto row = std::dynamic_pointer_cast(_list_selection->get_object(index))) {
+ return row->get_item();
+ }
}
else {
- auto sel = _font_grid.get_selected_items();
- if (sel.size() == 1) return _font_list_store->get_iter(sel.front());
+ return _grid_selection->get_object(index);
}
+
+ return {};
+}
+
+Glib::RefPtr FontList::get_selected_font() const {
+ if (_list_visible) {
+ if (auto row = std::dynamic_pointer_cast(_list_selection->get_selected_item())) {
+ return row->get_item();
+ }
+ }
+ else {
+ return _grid_selection->get_selected_item();
+ }
+
return {};
}
Glib::ustring FontList::get_fontspec() const {
- if (auto iter = get_selected_font()) { //_font_list.get_selection()->get_selected()) {
- const auto& font = iter->get_value(g_column_model.font);
- // auto font_class = iter->get_value(g_column_model.font_class);
+ if (auto element = std::dynamic_pointer_cast(get_selected_font())) {
+ auto& font = element->font();
if (font.ff) {
auto variations = _font_variations.get_pango_string(true);
- auto fspec = Inkscape::get_inkscape_fontspec(font.ff, font.face, variations);
- return fspec;
+ return Inkscape::get_inkscape_fontspec(font.ff, font.face, variations);
}
else {
// missing fonts don't have known variation that we could tweak,
// so ignore _font_variations UI and simply return alt_fontspec
- auto&& name = iter->get_value(g_column_model.alt_fontspec);
- return name;
+ return element->get_alt_spec();
}
}
return "sans-serif"; // no selection
@@ -864,6 +1199,7 @@ void FontList::set_current_font(const Glib::ustring& family, const Glib::ustring
auto scoped = _update.block();
auto fontspec = Inkscape::get_fontspec(family, face);
+
if (fontspec == _current_fspec) {
auto fspec = get_fontspec_without_variants(fontspec);
select_font(fspec);
@@ -871,8 +1207,9 @@ void FontList::set_current_font(const Glib::ustring& family, const Glib::ustring
}
_current_fspec = fontspec;
- if (!fontspec.empty()) {
+ if (!fontspec.empty() && fontspec != get_fontspec()) {
_font_variations.update(fontspec);
+ _var_axes.set_visible(_font_variations.variations_present());
add_font(fontspec, true);
}
}
@@ -890,11 +1227,13 @@ void FontList::set_current_size(double size) {
}
void FontList::add_font(const Glib::ustring& fontspec, bool select) {
+ auto scoped = _update.block();
+
bool found = select_font(fontspec); // found in the tree view?
if (found) return;
- auto it = std::find_if(begin(_fonts), end(_fonts), [&](const FontInfo& f){
- return Inkscape::get_inkscape_fontspec(f.ff, f.face, f.variations) == fontspec;
+ auto it = std::ranges::find_if(_fonts, [&](const FontInfo& f){
+ return get_inkscape_fontspec(f.ff, f.face, f.variations) == fontspec;
});
// fonts with variations will not be found, we need to remove " @ axis=value" part
@@ -903,7 +1242,7 @@ void FontList::add_font(const Glib::ustring& fontspec, bool select) {
if (it == end(_fonts) && fspec != fontspec) {
// try to match existing font
it = std::find_if(begin(_fonts), end(_fonts), [&](const FontInfo& f){
- return Inkscape::get_inkscape_fontspec(f.ff, f.face, f.variations) == fspec;
+ return get_inkscape_fontspec(f.ff, f.face, f.variations) == fspec;
});
if (it != end(_fonts)) {
bool found = select_font(fspec); // found in the tree view?
@@ -911,36 +1250,25 @@ void FontList::add_font(const Glib::ustring& fontspec, bool select) {
}
}
+ Glib::RefPtr insert;
+
if (it != end(_fonts)) {
// font found in the "all fonts" vector, but
- // this font is filtered out; add it temporarily to the tree list
- Gtk::TreeModel::iterator iter = _font_list_store->prepend();
- auto& row = *iter;
- row[g_column_model.font] = *it;
- row[g_column_model.injected] = true;
- row[g_column_model.alt_fontspec] = Glib::ustring();
- row[g_column_model.icon_name] = get_font_icon(*it, false);
-
- if (select) {
- _font_list.get_selection()->select(row.get_iter());
- auto path = _font_list_store->get_path(iter);
- scroll_to_row(path);
- }
-
- ++_extra_fonts; // extra font entry inserted
+ // this font is filtered out; boost it temporarily to let it pass filtering
+ insert = FontElement::create_injected_font(*it, {}, false);
}
else {
bool missing_font = true;
- Inkscape::FontInfo subst;
+ FontInfo subst;
auto desc = Pango::FontDescription(fontspec);
auto vars = desc.get_variations();
if (!vars.empty()) {
- // font with variantions; check if we have matching family
+ // font with variations; check if we have matching family
subst.variations = vars;
auto family = desc.get_family();
- it = std::find_if(begin(_fonts), end(_fonts), [&](const FontInfo& f){
+ it = std::ranges::find_if(_fonts, [&](const FontInfo& f){
return f.ff->get_name() == family;
});
if (it != end(_fonts)) {
@@ -949,32 +1277,15 @@ void FontList::add_font(const Glib::ustring& fontspec, bool select) {
}
}
- auto&& children = _font_list_store->children();
- Gtk::TreeModel::iterator iter;
- if (!children.empty() && children[0][g_column_model.injected]) {
- // reuse "injected font" entry
- iter = children[0].get_iter();
- }
- else {
- // font not found; insert a placeholder to show injected font: font is used in a document,
- // but either not available in the system (it's missing) or it is a variant of the variable font
- iter = _font_list_store->prepend();
- }
- auto& row = *iter;
- row[g_column_model.font] = subst;
- row[g_column_model.injected] = true;
- row[g_column_model.alt_fontspec] = fontspec;// TODO fname?
- row[g_column_model.icon_name] = get_font_icon(subst, missing_font);
-
- if (select) {
- _font_list.get_selection()->select(iter);
- auto path = _font_list_store->get_path(iter);
- scroll_to_row(path);
- }
-
- ++_extra_fonts; // extra font entry for a missing font added
+ // set "missing" font entry
+ insert = FontElement::create_injected_font(subst, fontspec.raw(), missing_font);
}
- update_font_count();
+
+ // change placeholder font entry; it's a first one in a store
+ std::vector> v(1, insert);
+ _font_store->splice(0, 1, v);
+ apply_filters();
+ scroll_to_row(0);
}
Gtk::Box* FontList::create_pill_box(const Glib::ustring& display_name, const Glib::ustring& tag, bool tags) {
@@ -988,15 +1299,15 @@ Gtk::Box* FontList::create_pill_box(const Glib::ustring& display_name, const Gli
close->set_image_from_icon_name("close-button-symbolic");
close->set_valign(Gtk::Align::CENTER);
if (tags) {
- close->signal_clicked().connect([=, this](){
+ close->signal_clicked().connect([=, this] {
// remove category from current filter
update_categories(tag, false);
});
}
else {
- close->signal_clicked().connect([=](){
+ close->signal_clicked().connect([=] {
// remove collection from current filter
- Inkscape::FontCollections::get()->update_selected_collections(tag);
+ FontCollections::get()->update_selected_collections(tag);
});
}
box->get_style_context()->add_class("tag-box");
@@ -1018,7 +1329,7 @@ void FontList::update_filterbar() {
_tag_box.append(*pill);
}
- for (auto&& collection : Inkscape::FontCollections::get()->get_selected_collections()) {
+ for (auto&& collection : FontCollections::get()->get_selected_collections()) {
auto pill = create_pill_box(collection, collection, false);
_tag_box.append(*pill);
}
@@ -1035,7 +1346,7 @@ void FontList::update_categories(const std::string& tag, bool select) {
update_filterbar();
// apply filter
- filter();
+ apply_filters();
}
void FontList::add_categories() {
@@ -1058,7 +1369,7 @@ void FontList::add_categories() {
label.set_markup("" + tag.display_name + "");
btn->set_child(label);
btn->set_active(_font_tags.is_tag_selected(tag.tag));
- btn->signal_toggled().connect([=, this](){
+ btn->signal_toggled().connect([=, this]{
// toggle font category
update_categories(tag.tag, btn->get_active());
});
@@ -1076,9 +1387,12 @@ void FontList::add_categories() {
add_row(sep);
}
for (auto& col : font_collections) {
- auto btn = Gtk::make_managed(col);
+ auto btn = Gtk::make_managed();
+ auto& label = *Gtk::make_managed();
+ label.set_text(col);
+ btn->set_child(label);
btn->set_active(fc->is_collection_selected(col));
- btn->signal_toggled().connect([=](){
+ btn->signal_toggled().connect([=]{
// toggle font system collection
fc->update_selected_collections(col);
});
@@ -1095,20 +1409,44 @@ void FontList::sync_font_tag(const FontTag* ftag, bool selected) {
//todo as needed
}
-void FontList::scroll_to_row(Gtk::TreePath path) {
- if (_view_mode_list) {
- // delay scroll request to let widget layout complete (due to hiding or showing variable font widgets);
- // keep track of connection so we can disconnect in a destructor if it is still pending at that point
- _scroll = Glib::signal_timeout().connect([=, this](){
- _font_list.scroll_to_row(path);
- return false; // <- false means disconnect
- }, 50, Glib::PRIORITY_LOW);
- // fudge factor of 50ms; ideally wait for layout pass to complete before scrolling to the row
+void FontList::scroll_to_row(int index) {
+ auto flags = Gtk::ListScrollFlags::SELECT;
+
+ if (_list_visible) {
+ _font_list.scroll_to(index, flags);
}
else {
- // scroll grid
- //todo
+ _font_grid.scroll_to(index, flags);
}
}
+// place "Font size" box at the top (true) or at the bottom (false) of the dialog
+void FontList::set_font_size_layout(bool top) {
+ auto& size = get_widget(_builder, "size-box");
+ auto layout1 = std::dynamic_pointer_cast(_main_grid.get_layout_manager()->get_layout_child(size));
+ auto& variants = get_widget(_builder, "variants");
+ auto layout2 = std::dynamic_pointer_cast(_main_grid.get_layout_manager()->get_layout_child(variants));
+ auto& separator = get_widget(_builder, "btm-separator");
+ if (top) {
+ layout1->set_row(3);
+ layout2->set_row(4);
+ separator.set_visible(false);
+ }
+ else {
+ layout1->set_row(10);
+ layout2->set_row(11);
+ separator.set_visible();
+ }
+}
+
+void FontList::on_map() {
+ Box::on_map();
+
+ // grow scrollwindow to accommodate up to 4 and a half axes; beyond that - scroll
+ static auto four = _font_variations.measure_height(4);
+ static auto five = _font_variations.measure_height(5);
+ // fractional size to expose fifth axis and let users know there's more content there
+ _var_axes.set_max_content_height((four + five) / 2);
+}
+
} // namespaces
diff --git a/src/ui/widget/font-list.h b/src/ui/widget/font-list.h
index b85177ea3918d34a4df5918ece9a58851febb92d..1ca83ed4c38b1b50f886f165165826b0f23d71e9 100644
--- a/src/ui/widget/font-list.h
+++ b/src/ui/widget/font-list.h
@@ -2,25 +2,30 @@
/** @file
* @brief Font browser and selector
*
- * Copyright (C) 2022-2023 Michael Kowalski
+ * Copyright (C) 2022-2025 Michael Kowalski
*/
#ifndef INKSCAPE_UI_WIDGET_FONT_LIST_H
#define INKSCAPE_UI_WIDGET_FONT_LIST_H
+#include
#include
#include
#include
-#include
+#include
#include
-#include
+#include
#include
+#include
+#include
#include "character-viewer.h"
#include "ui/widget/font-variations.h"
#include "util/font-discovery.h"
#include "util/font-tags.h"
#include "font-selector-interface.h"
+#include "generic/popover-menu.h"
+#include "ui/text_filter.h"
namespace Inkscape::UI::Widget {
@@ -52,58 +57,71 @@ public:
void unset_model() override {};
private:
- void sort_fonts(Inkscape::FontOrder order);
- void filter();
- struct Show {
- bool monospaced;
- bool oblique;
- bool others;
- };
- void populate_font_store(Glib::ustring text, const Show& params);
+ void on_map() override;
+ void sort_fonts(FontOrder order);
+ void set_sort_icon();
+ void apply_filters(bool all_filters = true);
+ void rebuild_ui();
+ void rebuild_store();
+ void populate_font_store(bool by_family);
+ void apply_filters_keep_selection(bool text_only = false);
void add_font(const Glib::ustring& fontspec, bool select);
bool select_font(const Glib::ustring& fontspec);
void update_font_count();
void add_categories();
- void toggle_category();
void update_categories(const std::string& tag, bool select);
void update_filterbar();
Gtk::Box* create_pill_box(const Glib::ustring& display_name, const Glib::ustring& tag, bool tags);
void sync_font_tag(const FontTag* ftag, bool selected);
- void scroll_to_row(Gtk::TreePath path);
- Gtk::TreeModel::iterator get_selected_font() const;
+ void scroll_to_row(int index);
+ void set_font_size_layout(bool top);
+ Glib::RefPtr get_selected_font() const;
+ Glib::RefPtr get_nth_font(int index) const;
+ int find_font(const Glib::ustring& fontspec, int from = 0, int count = -1) const;
+ void switch_view_mode(bool show_list);
sigc::signal _signal_changed;
sigc::signal _signal_apply;
sigc::signal _signal_insert_text;
Glib::RefPtr _builder;
Gtk::Grid& _main_grid;
- Gtk::TreeView& _font_list;
- Gtk::TreeViewColumn _text_column;
- Gtk::IconView& _font_grid;
+ Gtk::GridView& _font_grid;
+ Gtk::ListView& _font_list;
Gtk::SearchEntry2& _search;
- sigc::scoped_connection _selection_changed;
- Glib::RefPtr _font_list_store;
Gtk::Box& _tag_box;
Gtk::Box& _info_box;
Gtk::Box& _progress_box;
+ Gtk::Entry& _grid_sample_entry;
+ Gtk::Entry& _list_sample_entry;
+ Gtk::Scale& _preview_size_scale;
+ Gtk::Scale& _grid_size_scale;
+ Gtk::ScrolledWindow& _var_axes;
+ Gtk::ListBox& _tag_list;
+ Inkscape::FontTags& _font_tags;
std::vector _fonts;
- Inkscape::FontOrder _order = Inkscape::FontOrder::by_name;
+ std::vector> _font_families;
+ Glib::RefPtr _font_store;
+ Glib::RefPtr _text_filter;
+ Glib::RefPtr _font_filter;
+ Glib::RefPtr _family_filter;
+ Glib::RefPtr _list_selection;
+ Glib::RefPtr _grid_selection;
+ bool _list_visible = true;
+ FontOrder _order = FontOrder::ByFamily;
Glib::ustring _filter;
Gtk::ComboBoxText& _font_size;
Gtk::Scale& _font_size_scale;
- std::unique_ptr _cell_renderer;
- std::unique_ptr _cell_icon_renderer;
- std::unique_ptr _grid_renderer;
Glib::ustring _current_fspec;
double _current_fsize = 0.0;
+ bool _show_font_names = true;
+ Glib::ustring _sample_text;
+ Glib::ustring _grid_sample_text;
+ int _sample_font_size = 200;
+ int _grid_font_size = 300;
+ Glib::ustring _search_term;
OperationBlocker _update;
- int _extra_fonts = 0;
- Gtk::ListBox& _tag_list;
- Inkscape::FontTags& _font_tags;
FontVariations _font_variations;
- sigc::scoped_connection _scroll;
Glib::ustring _prefs;
- bool _view_mode_list = true;
sigc::scoped_connection _font_stream;
std::size_t _initializing = 0;
sigc::scoped_connection _font_collections_update;
@@ -111,6 +129,7 @@ private:
Gtk::Popover _charmap_popover;
CharacterViewer _charmap;
std::shared_ptr _current_font_instance; // for charmap only
+ PopoverMenuItem* _sort_by_family = nullptr;
};
} // namespaces
diff --git a/src/ui/widget/font-variations.cpp b/src/ui/widget/font-variations.cpp
index d63100b630f8737499cd44b37ebc72fabf9b1508..c8b5e0480b79fe125a93feb5693cc7eb5be28a5b 100644
--- a/src/ui/widget/font-variations.cpp
+++ b/src/ui/widget/font-variations.cpp
@@ -48,6 +48,10 @@ std::pair get_axis_name(const std::string& tag, co
// mainly from https://fonts.google.com/knowledge/using_type/introducing_parametric_axes
// CC BY-SA 4.0
// Standard axes guide for reference: https://variationsguide.typenetwork.com
+ // Other references:
+ // - https://fonts.google.com/variablefonts#axis-definitions
+ // - https://canary.grida.co/docs/reference/open-type-variable-axes
+
static std::map> map = {
// TRANSLATORS: “Grade” (GRAD in CSS) is an axis that can be used to alter stroke thicknesses (or other forms)
// without affecting the type's overall width, inter-letter spacing, or kerning — unlike altering weight.
@@ -97,8 +101,46 @@ std::pair get_axis_name(const std::string& tag, co
{"WONK", std::make_pair(C_("Variable font axis", "Wonky"), _("Binary switch used to control substitution of “wonky” forms"))},
// Element shape
{"ESHP", std::make_pair(C_("Variable font axis", "Element shape"), _("Selection of the base element glyphs are composed of"))},
+ // Element shape
+ {"ELSH", std::make_pair(C_("Variable font axis", "Element shape"), _("Controls element shape characteristics"))},
+ // Element grid
+ {"ELGR", std::make_pair(C_("Variable font axis", "Element grid"), _("Controls how many elements are used per one grid unit"))},
// Element grid
{"EGRD", std::make_pair(C_("Variable font axis", "Element grid"), _("Controls how many elements are used per one grid unit"))},
+ // Proposed axis "height"
+ {"HGHT", std::make_pair(C_("Variable font axis", "Height"), _("Controls the font file’s height parameter"))},
+ // Non-standard Y-axis stem thickness
+ {"YAXS", std::make_pair(C_("Variable font axis", "Y-Axis"), _("Controls stem thickness in vertical direction"))},
+ // Vertical Element Alignment
+ {"YELA", std::make_pair(C_("Variable font axis", "Vertical align"), _("Controls vertical element alignment"))},
+ // Corner roundness
+ {"ROND", std::make_pair(C_("Variable font axis", "Roundness"), _("Controls corner roundness"))},
+ // Bleed
+ {"BLED", std::make_pair(C_("Variable font axis", "Bleed"), _("Controls ink bleed effect"))},
+ // Scanlines
+ {"SCAN", std::make_pair(C_("Variable font axis", "Scanlines"), _("Controls scanline effect"))},
+ // Morph
+ {"MORF", std::make_pair(C_("Variable font axis", "Morph"), _("Controls morphing characteristics"))},
+ // Extrusion
+ {"EDPT", std::make_pair(C_("Variable font axis", "Extrusion depth"), _("Controls depth of extrusion"))},
+ // Edge highlight
+ {"EHLT", std::make_pair(C_("Variable font axis", "Edge highlight"), _("Controls edge highlighting"))},
+ // Hyper expansion
+ {"HEXP", std::make_pair(C_("Variable font axis", "Hyper expansion"), _("Controls hyper expansion characteristics"))},
+ // Bounce
+ {"BNCE", std::make_pair(C_("Variable font axis", "Bounce"), _("Controls bounce/spring effect"))},
+ // Informal
+ {"INFM", std::make_pair(C_("Variable font axis", "Informality"), _("Controls informality characteristics"))},
+ // Spacing
+ {"SPAC", std::make_pair(C_("Variable font axis", "Spacing"), _("Controls character spacing"))},
+ // Negative space
+ {"NEGA", std::make_pair(C_("Variable font axis", "Negative space"), _("Controls negative spacing"))},
+ // X-rotation
+ {"XROT", std::make_pair(C_("Variable font axis", "X rotation"), _("Controls character 3D horizontal rotation"))},
+ // Y-rotation
+ {"YROT", std::make_pair(C_("Variable font axis", "Y rotation"), _("Controls character 3D vertical rotation"))},
+ // Sharpness
+ {"SHRP", std::make_pair(C_("Variable font axis", "Sharpness"), _("Controls sharpness characteristics"))},
// TRANSLATORS: “Optical Size”
// Optical sizes in a variable font are different versions of a typeface optimized for use at singular specific sizes,
// such as 14 pt or 144 pt. Small (or body) optical sizes tend to have less stroke contrast, more open and wider spacing,
@@ -371,6 +413,21 @@ Glib::RefPtr FontVariations::get_size_group(int index) {
}
}
+int FontVariations::measure_height(int axis_count) {
+ std::map axes;
+ for (int i = 0; i < axis_count; ++i) {
+ auto name = std::to_string(i);
+ OTVarAxis axis;
+ axis.tag = name;
+ axes[name] = axis;
+ }
+ build_ui(axes);
+ int min=0,nat=0,b1,b2;
+ measure(Gtk::Orientation::VERTICAL, 9999, min, nat, b1, b2);
+ build_ui({});
+ return nat;
+}
+
} // namespace Widget
} // namespace UI
} // namespace Inkscape
diff --git a/src/ui/widget/font-variations.h b/src/ui/widget/font-variations.h
index f58c20f1313d9da5fdb7bc3fd7894f4195a82116..36b2b881e5b61490fe9114ae47e295308961eddc 100644
--- a/src/ui/widget/font-variations.h
+++ b/src/ui/widget/font-variations.h
@@ -102,6 +102,9 @@ public:
// provide access to label and spin button size groups
Glib::RefPtr get_size_group(int index);
+ // construct temp UI with N axes and report its height
+ int measure_height(int axis_count);
+
private:
void build_ui(const std::map& axes);
diff --git a/src/util/font-discovery.cpp b/src/util/font-discovery.cpp
index e48145f5154c99bc5095877c65b91e3b7bc89d12..6dfe20203fd4106305fdeca13ae22d251a5c4161 100644
--- a/src/util/font-discovery.cpp
+++ b/src/util/font-discovery.cpp
@@ -133,17 +133,18 @@ void sort_fonts_by_name(std::vector& fonts, bool sans_first) {
// sort fonts in requested order, in-place
void sort_fonts(std::vector& fonts, FontOrder order, bool sans_first) {
switch (order) {
- case FontOrder::by_name:
+ case FontOrder::ByName:
+ case FontOrder::ByFamily:
sort_fonts_by_name(fonts, sans_first);
break;
- case FontOrder::by_weight:
+ case FontOrder::ByWeight:
// there are many repetitions for weight, due to font substitutions, so sort by name first
sort_fonts_by_name(fonts, sans_first);
std::stable_sort(begin(fonts), end(fonts), [](const FontInfo& a, const FontInfo& b) { return a.weight < b.weight; });
break;
- case FontOrder::by_width:
+ case FontOrder::ByWidth:
sort_fonts_by_name(fonts, sans_first);
std::stable_sort(begin(fonts), end(fonts), [](const FontInfo& a, const FontInfo& b) { return a.width < b.width; });
break;
@@ -154,6 +155,44 @@ void sort_fonts(std::vector& fonts, FontOrder order, bool sans_first)
}
}
+const FontInfo& get_family_font(const std::vector& family) {
+ assert(!family.empty());
+ auto it = std::ranges::find_if(family, [](auto& fam) {
+ return fam.face &&
+ (fam.face->get_name().raw().find("Regular") != std::string::npos ||
+ fam.face->get_name().raw().find("Normal") != std::string::npos);
+ });
+ if (it != end(family)) {
+ return *it;
+ }
+ return family.front();
+}
+
+FontInfo& get_family_font(std::vector& family) {
+ return const_cast(get_family_font(const_cast&>(family)));
+}
+
+void sort_font_families(std::vector>& fonts, bool sans_first) {
+ std::sort(begin(fonts), end(fonts), [=](const auto& a, const auto& b) {
+ auto& f1 = get_family_font(a);
+ auto& f2 = get_family_font(b);
+
+ auto na = f1.ff->get_name();
+ auto nb = f2.ff->get_name();
+ if (sans_first) {
+ bool sans_a = f1.synthetic && f1.ff->get_name() == "Sans";
+ bool sans_b = f2.synthetic && f2.ff->get_name() == "Sans";
+ if (sans_a != sans_b) {
+ return sans_a;
+ }
+ }
+ // lexicographical order:
+ return na < nb;
+ // alphabetical order:
+ //return na.raw() < nb.raw();
+ });
+}
+
Glib::ustring get_fontspec(const Glib::ustring& family, const Glib::ustring& face, const Glib::ustring& variations) {
if (variations.empty()) {
return face.empty() ? family : family + ", " + face;
@@ -203,35 +242,37 @@ enum FontCacheFlags : int {
Synthetic = 0x08,
};
-void save_font_cache(const std::vector& fonts) {
+void save_font_cache(const std::vector>& fonts) {
auto keyfile = Glib::KeyFile::create();
keyfile->set_double(cache_header, "version", cache_version);
Glib::ustring weight("weight");
Glib::ustring width("width");
- Glib::ustring family("family");
+ Glib::ustring ffamily("family");
Glib::ustring fontflags("flags");
- for (auto&& font : fonts) {
- auto desc = get_font_description(font.ff, font.face);
- auto group = desc.to_string();
- int flags = FontCacheFlags::Normal;
- if (font.monospaced) {
- flags |= FontCacheFlags::Monospace;
- }
- if (font.oblique) {
- flags |= FontCacheFlags::Oblique;
- }
- if (font.variable_font) {
- flags |= FontCacheFlags::Variable;
- }
- if (font.synthetic) {
- flags |= FontCacheFlags::Synthetic;
+ for (auto&& family : fonts) {
+ for (auto&& font : family) {
+ auto desc = get_font_description(font.ff, font.face);
+ auto group = desc.to_string();
+ int flags = FontCacheFlags::Normal;
+ if (font.monospaced) {
+ flags |= FontCacheFlags::Monospace;
+ }
+ if (font.oblique) {
+ flags |= FontCacheFlags::Oblique;
+ }
+ if (font.variable_font) {
+ flags |= FontCacheFlags::Variable;
+ }
+ if (font.synthetic) {
+ flags |= FontCacheFlags::Synthetic;
+ }
+ keyfile->set_double(group, weight, font.weight);
+ keyfile->set_double(group, width, font.width);
+ keyfile->set_integer(group, ffamily, font.family_kind);
+ keyfile->set_integer(group, fontflags, flags);
}
- keyfile->set_double(group, weight, font.weight);
- keyfile->set_double(group, width, font.width);
- keyfile->set_integer(group, family, font.family_kind);
- keyfile->set_integer(group, fontflags, flags);
}
std::string filename = Glib::build_filename(Inkscape::IO::Resource::profile_path(), font_cache);
@@ -298,8 +339,8 @@ std::vector get_all_fonts() {
return fonts;
}
-std::shared_ptr> get_all_fonts(Async::Progress>& progress) {
- auto result = std::make_shared>();
+std::shared_ptr>> get_all_fonts(Async::Progress>& progress) {
+ auto result = std::make_shared>>();
auto& fonts = *result;
auto cache = load_cached_font_info();
@@ -385,10 +426,12 @@ std::shared_ptr> get_all_fonts(Async::Progress
#include
#include
-#include
#include
#include
#include
@@ -25,14 +24,23 @@ struct FontInfo {
bool oblique = false; // italic or oblique font
bool variable_font = false; // this is variable font
bool synthetic = false; // this is an alias, like "Sans" or "Monospace"
+
+ bool operator == (const FontInfo&) const = default;
};
-enum class FontOrder { by_name, by_weight, by_width };
+enum class FontOrder {
+ _First = 0,
+ ByName = 0,
+ ByWeight,
+ ByWidth,
+ ByFamily,
+ _Last = ByFamily
+};
class FontDiscovery : public Util::EnableSingleton>
{
public:
- using FontsPayload = std::shared_ptr>;
+ using FontsPayload = std::shared_ptr>>;
using MessageType = Async::Msg::Message>;
sigc::scoped_connection connect_to_fonts(std::function