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