Inkscape
Vector Graphics Editor
font-selector.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Author:
4 * Tavmjong Bah <tavmjong@free.fr>
5 *
6 * Copyright (C) 2018 Tavmong Bah
7 *
8 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
9 */
10
11#include "font-selector.h"
12
13#include <iostream>
14#include <stdexcept>
15#include <string>
16#include <vector>
17#include <glibmm/i18n.h>
18#include <glibmm/markup.h>
19#include <glibmm/value.h>
20#include <gdkmm/contentprovider.h>
21#include <gtkmm/dragsource.h>
22#include <sigc++/functors/mem_fun.h>
23
27// For updating from selection
28#include "inkscape.h"
29#include "desktop.h"
30#include "object/sp-text.h"
31#include "ui/controller.h"
33
34namespace Inkscape::UI::Widget {
35
36FontSelector::FontSelector (bool with_size, bool with_variations)
37 : Gtk::Grid ()
38 , family_frame (_("Font family"))
39 , style_frame (C_("Font selector", "Style"))
40 , size_label (_("Font size"))
41 , size_combobox (true) // With entry
42 , signal_block (false)
43 , font_size (18)
44{
46 Glib::RefPtr<Gtk::TreeModel> model = font_lister->get_font_list();
47
48 // Font family
49 family_treecolumn.pack_start (family_cell, false);
50 int total = model->children().size();
51 int height = 30;
52 if (total > 1000) {
53 height = 30000/total;
54 g_warning("You have a huge number of font families (%d), "
55 "and Cairo is limiting the size of widgets you can draw.\n"
56 "Your preview cell height is capped to %d.",
57 total, height);
58 // hope we dont need a forced height because now pango line height
59 // not add data outside parent rendered expanding it so no naturall cells become over 30 height
60 family_cell.set_fixed_size(-1, height);
61 } else {
62#if !PANGO_VERSION_CHECK(1,50,0)
63 family_cell.set_fixed_size(-1, height);
64#endif
65 }
66 family_treecolumn.set_fixed_width (120); // limit minimal width to keep entire dialog narrow; column can still grow
67 family_treecolumn.add_attribute (family_cell, "text", 0);
69 family_treeview.set_row_separator_func (&font_lister_separator_func);
70 family_treeview.set_model(model);
71 family_treeview.set_name ("FontSelector: Family");
72 family_treeview.set_headers_visible (false);
73 family_treeview.append_column (family_treecolumn);
74
75 family_scroll.set_policy (Gtk::PolicyType::NEVER, Gtk::PolicyType::AUTOMATIC);
77
78 family_frame.set_hexpand (true);
79 family_frame.set_vexpand (true);
80 family_frame.set_child(family_scroll);
81
82 // Style
83 style_treecolumn.pack_start (style_cell, false);
84 style_treecolumn.add_attribute (style_cell, "text", 0);
85 style_treecolumn.set_cell_data_func (style_cell, sigc::mem_fun(*this, &FontSelector::style_cell_data_func));
86 style_treecolumn.set_title ("Face");
87 style_treecolumn.set_resizable (true);
88
89 style_treeview.set_model (font_lister->get_style_list());
90 style_treeview.set_name ("FontSelectorStyle");
91 style_treeview.append_column ("CSS", font_lister->font_style_list.cssStyle);
92 style_treeview.append_column (style_treecolumn);
93
94 style_treeview.get_column(0)->set_resizable (true);
95
96 style_scroll.set_policy (Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
98
99 style_frame.set_hexpand (true);
100 style_frame.set_vexpand (true);
101 style_frame.set_child(style_scroll);
102
103 // Size
104 size_combobox.set_name ("FontSelectorSize");
105 if (auto entry = size_combobox.get_entry()) {
106 // limit min size of the entry box to 6 chars, so it doesn't inflate entire dialog!
107 entry->set_width_chars(6);
108 }
109 set_sizes();
110 size_combobox.set_active_text( "18" );
111
112 // Font Variations
113 font_variations.set_vexpand (true);
114 font_variations_scroll.set_policy (Gtk::PolicyType::NEVER, Gtk::PolicyType::AUTOMATIC);
116
117 // Grid
118 set_name ("FontSelectorGrid");
119 set_row_spacing(4);
120 set_column_spacing(4);
121 // Add extra columns to the "family frame" to change space distribution
122 // by prioritizing font family over styles
123 const int extra = 4;
124 attach (family_frame, 0, 0, 1 + extra, 2);
125 attach (style_frame, 1 + extra, 0, 2, 1);
126 if (with_size) { // Glyph panel does not use size.
127 attach (size_label, 1 + extra, 1, 1, 1);
128 attach (size_combobox, 2 + extra, 1, 1, 1);
129 }
130 if (with_variations) { // Glyphs panel does not use variations.
131 attach (font_variations_scroll, 0, 2, 3 + extra, 1);
132 }
133
134 // For drag and drop.
136 .prepare = sigc::mem_fun(*this, &FontSelector::on_drag_prepare),
137 .begin = sigc::mem_fun(*this, &FontSelector::on_drag_begin )
138 });
139
140 // Add signals
141 family_treeview.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FontSelector::on_family_changed));
142 style_treeview.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FontSelector::on_style_changed));
143 size_combobox.signal_changed().connect(sigc::mem_fun(*this, &FontSelector::on_size_changed));
145 family_treeview.signal_realize().connect(sigc::mem_fun(*this, &FontSelector::on_realize_list));
146
147 font_variations_scroll.set_vexpand(false);
148
149 // Initialize font family lists. (May already be done.) Should be done on document change.
150 font_lister->update_font_list(SP_ACTIVE_DESKTOP->getDocument());
151}
152
155 g_idle_add(FontSelector::set_cell_markup, this);
156}
157
159{
160 FontSelector *self = static_cast<FontSelector *>(data);
161 self->family_treeview.set_visible(false);
162 self->family_treecolumn.set_cell_data_func (self->family_cell, &font_lister_cell_data_func_markup);
163 self->family_treeview.set_visible(true);
164 return false;
165}
166
168{
169 style_frame.set_visible(false);
170 size_label.set_visible(false);
171 size_combobox.set_visible(false);
172 font_variations_scroll.set_visible(false);
173 font_variations_scroll.set_vexpand(false);
174}
175
176// TODO: Dropping doesnʼt seem to be implemented anywhere
177void FontSelector::on_drag_begin(Gtk::DragSource &source,
178 Glib::RefPtr<Gdk::Drag> const &drag)
179{
180 // Get the current collection.
181 Glib::RefPtr<Gtk::TreeSelection> selection = family_treeview.get_selection();
182 Gtk::TreeModel::iterator iter = selection->get_selected();
183 Gtk::TreePath path(iter);
184 auto paintable = family_treeview.create_row_drag_icon(path);
185 source.set_icon(paintable, 0, 0);
186}
187
188Glib::RefPtr<Gdk::ContentProvider> FontSelector::on_drag_prepare(Gtk::DragSource const &/*source*/,
189 double /*x*/, double /*y*/)
190{
191 // std::cout << "FontSelector::on_drag_data_get()" << std::endl;
193
194 // std::cout << font_lister->get_font_family_row() << ", " << font_lister->get_treeview_selection() << std::endl;
195
196 Glib::ustring family_name = font_lister->get_dragging_family();
197 // std::cout << "Family: " << family_name << std::endl;
198
199 Glib::Value<Glib::ustring> value;
200 value.init(G_TYPE_STRING);
201 value.set(family_name);
202 return Gdk::ContentProvider::create(value);
203}
204
205void
207{
208 size_combobox.remove_all();
209
211 int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT);
212
213 int sizes[] = {
214 4, 6, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22, 24, 28,
215 32, 36, 40, 48, 56, 64, 72, 144
216 };
217
218 // Array must be same length as SPCSSUnit in style-internal.h
219 // PX PT PC MM CM IN EM EX %
220 double ratios[] = {1, 1, 1, 10, 4, 40, 100, 16, 8, 0.16};
221
222 for (int i : sizes)
223 {
224 double size = i/ratios[unit];
226 }
227}
228
229void
231{
233 int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT);
234 Glib::ustring tooltip = Inkscape::ustring::format_classic(_("Font size"), " (", sp_style_get_css_unit_string(unit), ")");
235 size_combobox.set_tooltip_text (tooltip);
236}
237
238// Update GUI.
239// We keep a private copy of the style list as the font-family in widget is only temporary
240// until the "Apply" button is set so the style list can be different from that in
241// FontLister.
243{
244 signal_block = true;
245
246 auto font_lister = Inkscape::FontLister::get_instance();
247 Gtk::TreePath path;
248 Glib::ustring family = font_lister->get_font_family();
249 Glib::ustring style = font_lister->get_font_style();
250
251 // Set font family
252 try {
253 path = font_lister->get_row_for_font(family).get_iter();
254 } catch (FontLister::Exception) {
255 std::cerr << "FontSelector::update_font: Couldn't find row for font-family: "
256 << family.raw() << std::endl;
257 path.clear();
258 path.push_back(0);
259 }
260
261 Gtk::TreePath currentPath;
262 Gtk::TreeViewColumn *currentColumn;
263 family_treeview.get_cursor(currentPath, currentColumn);
264 if (currentPath.empty() || !font_lister->is_path_for_font(currentPath, family)) {
265 family_treeview.set_cursor(path);
266 family_treeview.scroll_to_row(path);
267 }
268
269 // Get font-lister style list for selected family
270 auto const row = *family_treeview.get_model()->get_iter(path);
271 auto styles = row.get_value(font_lister->font_list.styles);
272
273 // Copy font-lister style list to private list store, searching for match.
274 Gtk::TreeModel::iterator match;
275 auto local_style_list_store = Gtk::ListStore::create(font_lister->font_style_list);
276 for (auto const &s : *styles) {
277 auto srow = *local_style_list_store->append();
278 srow[font_lister->font_style_list.cssStyle] = s.css_name;
279 srow[font_lister->font_style_list.displayStyle] = s.display_name;
280 if (style == s.css_name) {
281 match = srow.get_iter();
282 }
283 }
284
285 // Attach store to tree view and select row.
286 style_treeview.set_model(local_style_list_store);
287 if (match) {
288 style_treeview.get_selection()->select(match);
289 }
290
291 Glib::ustring fontspec = font_lister->get_fontspec();
292 update_variations(fontspec);
293
294 signal_block = false;
295}
296
297void
299{
300 signal_block = true;
301
302 // Set font size
303 std::stringstream ss;
304 ss << size;
305 size_combobox.get_entry()->set_text( ss.str() );
306 font_size = size; // Store value
308
309 signal_block = false;
310}
311
313{
314 family_treeview.unset_model();
315}
316
318{
320 Glib::RefPtr<Gtk::TreeModel> model = font_lister->get_font_list();
321 family_treeview.set_model(model);
322}
323
324// If use_variations is true (default), we get variation values from variations widget otherwise we
325// get values from CSS widget (we need to be able to keep the two widgets synchronized both ways).
326Glib::ustring
327FontSelector::get_fontspec(bool use_variations) {
328
329 // Build new fontspec from GUI settings
330 Glib::ustring family = "Sans"; // Default...family list may not have been constructed.
331 Gtk::TreeModel::iterator iter = family_treeview.get_selection()->get_selected();
332 if (iter) {
333 (*iter).get_value(0, family);
334 }
335
336 Glib::ustring style = "Normal";
337 iter = style_treeview.get_selection()->get_selected();
338 if (iter) {
339 (*iter).get_value(0, style);
340 }
341
342 if (family.empty()) {
343 std::cerr << "FontSelector::get_fontspec: empty family!" << std::endl;
344 }
345
346 if (style.empty()) {
347 std::cerr << "FontSelector::get_fontspec: empty style!" << std::endl;
348 }
349
350 Glib::ustring fontspec = family + ", ";
351
352 if (use_variations) {
353 // Clip any font_variation data in 'style' as we'll replace it.
354 auto pos = style.find('@');
355 if (pos != Glib::ustring::npos) {
356 style.erase (pos, style.length()-1);
357 }
358
359 Glib::ustring variations = font_variations.get_pango_string();
360
361 if (variations.empty()) {
362 fontspec += style;
363 } else {
364 fontspec += variations;
365 }
366 } else {
367 fontspec += style;
368 }
369
370 return fontspec;
371}
372
373void
374FontSelector::style_cell_data_func(Gtk::CellRenderer * const renderer,
375 Gtk::TreeModel::const_iterator const &iter)
376{
377 Glib::ustring family = "Sans"; // Default...family list may not have been constructed.
378 auto const iter_family = family_treeview.get_selection()->get_selected();
379 if (iter_family) {
380 (*iter_family).get_value(0, family);
381 }
382
383 Glib::ustring style = "Normal";
384 (*iter).get_value(1, style);
385
386 Glib::ustring style_escaped = Glib::Markup::escape_text( style );
387 Glib::ustring font_desc = Glib::Markup::escape_text( family + ", " + style );
388 Glib::ustring markup;
389
390 markup = "<span font='" + font_desc + "'>" + style_escaped + "</span>";
391
392 // std::cout << " markup: " << markup << " (" << name << ")" << std::endl;
393
394 renderer->set_property("markup", markup);
395}
396
397// Callbacks
398
399// Need to update style list
400void
402
403 if (signal_block) return;
404 signal_block = true;
405
406 Glib::RefPtr<Gtk::TreeModel> model;
407 Gtk::TreeModel::iterator iter = family_treeview.get_selection()->get_selected(model);
408
409 if (!iter) {
410 // This can happen just after the family list is recreated.
411 signal_block = false;
412 return;
413 }
414
415 auto fontlister = Inkscape::FontLister::get_instance();
416 fontlister->ensureRowStyles(iter);
417
418 Gtk::TreeModel::Row row = *iter;
419
420 // Get family name
421 Glib::ustring family;
422 row.get_value(0, family);
423
424 fontlister->set_dragging_family(family);
425
426 // Get style list.
427 auto styles = row.get_value(fontlister->font_list.styles);
428
429 // Find best style match for selected family with current style (e.g. of selected text).
430 Glib::ustring style = fontlister->get_font_style();
431 Glib::ustring best = fontlister->get_best_style_match (family, style);
432
433 // Create are own store of styles for selected font-family (the font-family selected
434 // in the dialog may not be the same as stored in the font-lister class until the
435 // "Apply" button is triggered).
436 Gtk::TreeModel::iterator it_best;
437 FontLister::FontStyleListClass FontStyleList;
438 Glib::RefPtr<Gtk::ListStore> local_style_list_store = Gtk::ListStore::create(FontStyleList);
439
440 // Build list and find best match.
441 for (auto const &s : *styles) {
442 auto srow = *local_style_list_store->append();
443 srow[FontStyleList.cssStyle] = s.css_name;
444 srow[FontStyleList.displayStyle] = s.display_name;
445 if (best == s.css_name) {
446 it_best = srow.get_iter();
447 }
448 }
449
450 // Attach store to tree view and select row.
451 style_treeview.set_model (local_style_list_store);
452 if (it_best) {
453 style_treeview.get_selection()->select (it_best);
454 }
455
456 signal_block = false;
457
458 // Let world know
459 changed_emit();
460}
461
462void
464 if (signal_block) return;
465
466 // Update variations widget if new style selected from style widget.
467 signal_block = true;
468 Glib::ustring fontspec = get_fontspec( false );
469 update_variations(fontspec);
470 signal_block = false;
471
472 // Let world know
473 changed_emit();
474}
475
476void
478
479 if (signal_block) return;
480
481 double size;
482 Glib::ustring input = size_combobox.get_active_text();
483 try {
484 size = std::stod (input);
485 }
486 catch (std::invalid_argument) {
487 std::cerr << "FontSelector::on_size_changed: Invalid input: " << input.raw() << std::endl;
488 size = -1;
489 }
490
492 // Arbitrary: Text and Font preview freezes with huge font sizes.
493 int max_size = prefs->getInt("/dialogs/textandfont/maxFontSize", 10000);
494
495 if (size <= 0) {
496 return;
497 }
498 if (size > max_size)
499 size = max_size;
500
501 if (fabs(font_size - size) > 0.001) {
502 font_size = size;
503 // Let world know
504 changed_emit();
505 }
506}
507
508void
510
511 if (signal_block) return;
512
513 // Let world know
514 changed_emit();
515}
516
517void
519 signal_block = true;
521 if (initial) {
522 initial = false;
523 family_treecolumn.unset_cell_data_func (family_cell);
525 g_idle_add(FontSelector::set_cell_markup, this);
526 }
527 signal_block = false;
528}
529
530void FontSelector::update_variations(const Glib::ustring& fontspec) {
531 font_variations.update(fontspec);
532
533 // Check if there are any variations available; if not, don't expand font_variations_scroll
534 bool hasContent = font_variations.variations_present();
535 font_variations_scroll.set_vexpand(hasContent);
536}
537
538} // namespace Inkscape::UI::Widget
539
540/*
541 Local Variables:
542 mode:c++
543 c-file-style:"stroustrup"
544 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
545 indent-tabs-mode:nil
546 fill-column:99
547 End:
548*/
549// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
This class enumerates fonts using libnrtype into reusable data stores and allows for random access to...
Definition: font-lister.h:84
Glib::ustring const & get_dragging_family() const
Definition: font-lister.h:257
Glib::RefPtr< Gtk::ListStore > const & get_style_list() const
Definition: font-lister.h:162
void update_font_list(SPDocument *document)
Updates font list to include fonts in document.
static Inkscape::FontLister * get_instance()
FontStyleListClass font_style_list
Definition: font-lister.h:143
Glib::RefPtr< Gtk::ListStore > const & get_font_list() const
Definition: font-lister.h:157
Preference storage class.
Definition: preferences.h:63
static Preferences * get()
Access the singleton Preferences object.
Definition: preferences.h:599
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
Definition: preferences.h:386
A container of widgets for selecting font faces.
Definition: font-selector.h:74
void on_drag_begin(Gtk::DragSource &source, Glib::RefPtr< Gdk::Drag > const &drag)
void update_font()
Update GUI based on fontspec.
FontSelector(bool with_size=true, bool with_variations=true)
Constructor.
Gtk::ScrolledWindow font_variations_scroll
static gboolean set_cell_markup(gpointer)
Gtk::ScrolledWindow family_scroll
Definition: font-selector.h:87
Gtk::ScrolledWindow style_scroll
Definition: font-selector.h:94
Gtk::TreeViewColumn style_treecolumn
Definition: font-selector.h:96
Glib::ustring get_fontspec(bool use_variations=true)
Get fontspec based on current settings.
void style_cell_data_func(Gtk::CellRenderer *renderer, Gtk::TreeModel::const_iterator const &iter)
Glib::RefPtr< Gdk::ContentProvider > on_drag_prepare(Gtk::DragSource const &source, double x, double y)
Gtk::TreeViewColumn family_treecolumn
Definition: font-selector.h:89
Gtk::CellRendererText family_cell
Definition: font-selector.h:90
Gtk::CellRendererText style_cell
Definition: font-selector.h:97
sigc::signal< void(Glib::ustring)> signal_changed
void update_variations(const Glib::ustring &fontspec)
void update(const Glib::ustring &font_spec)
Update GUI.
sigc::connection connectChanged(sigc::slot< void()> slot)
Let others know that user has changed GUI settings.
Utilities to more easily use Gtk::EventController & subclasses like Gesture.
Editable view implementation.
TODO: insert short description here.
The data describing a single loaded font.
bool font_lister_separator_func(Glib::RefPtr< Gtk::TreeModel > const &, Gtk::TreeModel::const_iterator const &iter)
void font_lister_cell_data_func_markup(Gtk::CellRenderer *const renderer, Gtk::TreeModel::const_iterator const &iter)
void font_lister_cell_data_func(Gtk::CellRenderer *, Gtk::TreeModel::const_iterator const &)
Font selection widgets.
The routines here create and manage a font selector widget with three parts, one each for font-family...
Definition: desktop.h:51
Gtk::DragSource & add_drag_source(Gtk::Widget &widget, AddDragSourceArgs &&args, Gtk::PropagationPhase const phase, When const when)
Create a drag source for the given widget.
Definition: controller.cpp:147
Custom widgets.
Definition: desktop.h:127
static constexpr int height
Glib::ustring format_classic(T const &... args)
int size
unsigned char * data
Gtk::TreeModelColumn< Glib::ustring > cssStyle
Column containing the styles in CSS/Pango format.
Definition: font-lister.h:134
Gtk::TreeModelColumn< Glib::ustring > displayStyle
Column containing the styles as Font designer used.
Definition: font-lister.h:131
@ SP_CSS_UNIT_PT
gchar const * sp_style_get_css_unit_string(int unit)
Definition: style.cpp:1303
std::unique_ptr< Toolbar >(* create)(SPDesktop *desktop)
Definition: toolbars.cpp:63