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 <glibmm/i18n.h>
12 #include <glibmm/markup.h>
13
14 #include "font-selector.h"
15
16 #include "libnrtype/font-lister.h"
17 #include "libnrtype/font-instance.h"
18
19 // For updating from selection
20 #include "inkscape.h"
21 #include "desktop.h"
22 #include "object/sp-text.h"
23
24 namespace Inkscape {
25 namespace UI {
26 namespace Widget {
27
FontSelector(bool with_size,bool with_variations)28 FontSelector::FontSelector (bool with_size, bool with_variations)
29 : Gtk::Grid ()
30 , family_frame (_("Font family"))
31 , style_frame (C_("Font selector", "Style"))
32 , size_label (_("Font size"))
33 , size_combobox (true) // With entry
34 , signal_block (false)
35 , font_size (18)
36 {
37
38 Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance();
39
40 // Font family
41 family_treecolumn.pack_start (family_cell, false);
42 family_treecolumn.set_fixed_width (200);
43 family_treecolumn.add_attribute (family_cell, "text", 0);
44 family_treecolumn.set_cell_data_func (family_cell, &font_lister_cell_data_func);
45
46 family_treeview.set_row_separator_func (&font_lister_separator_func);
47 family_treeview.set_model (font_lister->get_font_list());
48 family_treeview.set_name ("FontSelector: Family");
49 family_treeview.set_headers_visible (false);
50 family_treeview.append_column (family_treecolumn);
51
52 family_scroll.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
53 family_scroll.add (family_treeview);
54
55 family_frame.set_hexpand (true);
56 family_frame.set_vexpand (true);
57 family_frame.add (family_scroll);
58
59 // Style
60 style_treecolumn.pack_start (style_cell, false);
61 style_treecolumn.add_attribute (style_cell, "text", 0);
62 style_treecolumn.set_cell_data_func (style_cell, sigc::mem_fun(*this, &FontSelector::style_cell_data_func));
63 style_treecolumn.set_title ("Face");
64 style_treecolumn.set_resizable (true);
65
66 style_treeview.set_model (font_lister->get_style_list());
67 style_treeview.set_name ("FontSelectorStyle");
68 style_treeview.append_column ("CSS", font_lister->FontStyleList.cssStyle);
69 style_treeview.append_column (style_treecolumn);
70
71 style_treeview.get_column(0)->set_resizable (true);
72
73 style_scroll.set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
74 style_scroll.add (style_treeview);
75
76 style_frame.set_hexpand (true);
77 style_frame.set_vexpand (true);
78 style_frame.add (style_scroll);
79
80 // Size
81 size_combobox.set_name ("FontSelectorSize");
82 set_sizes();
83 size_combobox.set_active_text( "18" );
84
85 // Font Variations
86 font_variations.set_vexpand (true);
87 font_variations_scroll.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
88 font_variations_scroll.add (font_variations);
89
90 // Grid
91 set_name ("FontSelectorGrid");
92 set_row_spacing(4);
93 set_column_spacing(4);
94 // Add extra columns to the "family frame" to change space distribution
95 // by prioritizing font family over styles
96 const int extra = 4;
97 attach (family_frame, 0, 0, 1 + extra, 2);
98 attach (style_frame, 1 + extra, 0, 2, 1);
99 if (with_size) { // Glyph panel does not use size.
100 attach (size_label, 1 + extra, 1, 1, 1);
101 attach (size_combobox, 2 + extra, 1, 1, 1);
102 }
103 if (with_variations) { // Glyphs panel does not use variations.
104 attach (font_variations_scroll, 0, 2, 3 + extra, 1);
105 }
106
107 // Add signals
108 family_treeview.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FontSelector::on_family_changed));
109 style_treeview.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FontSelector::on_style_changed));
110 size_combobox.signal_changed().connect(sigc::mem_fun(*this, &FontSelector::on_size_changed));
111 font_variations.connectChanged(sigc::mem_fun(*this, &FontSelector::on_variations_changed));
112
113 show_all_children();
114
115 // Initialize font family lists. (May already be done.) Should be done on document change.
116 font_lister->update_font_list(SP_ACTIVE_DESKTOP->getDocument());
117 }
118
119 void
set_sizes()120 FontSelector::set_sizes ()
121 {
122 size_combobox.remove_all();
123
124 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
125 int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT);
126
127 int sizes[] = {
128 4, 6, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22, 24, 28,
129 32, 36, 40, 48, 56, 64, 72, 144
130 };
131
132 // Array must be same length as SPCSSUnit in style-internal.h
133 // PX PT PC MM CM IN EM EX %
134 double ratios[] = {1, 1, 1, 10, 4, 40, 100, 16, 8, 0.16};
135
136 for (int i : sizes)
137 {
138 double size = i/ratios[unit];
139 size_combobox.append( Glib::ustring::format(size) );
140 }
141 }
142
143 void
set_fontsize_tooltip()144 FontSelector::set_fontsize_tooltip()
145 {
146 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
147 int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT);
148 Glib::ustring tooltip = Glib::ustring::format(_("Font size"), " (", sp_style_get_css_unit_string(unit), ")");
149 size_combobox.set_tooltip_text (tooltip);
150 }
151
152 // Update GUI.
153 // We keep a private copy of the style list as the font-family in widget is only temporary
154 // until the "Apply" button is set so the style list can be different from that in
155 // FontLister.
156 void
update_font()157 FontSelector::update_font ()
158 {
159 signal_block = true;
160
161 Inkscape::FontLister *font_lister = Inkscape::FontLister::get_instance();
162 Gtk::TreePath path;
163 Glib::ustring family = font_lister->get_font_family();
164 Glib::ustring style = font_lister->get_font_style();
165
166 // Set font family
167 try {
168 path = font_lister->get_row_for_font (family);
169 } catch (...) {
170 std::cerr << "FontSelector::update_font: Couldn't find row for font-family: "
171 << family << std::endl;
172 path.clear();
173 path.push_back(0);
174 }
175
176 Gtk::TreePath currentPath;
177 Gtk::TreeViewColumn *currentColumn;
178 family_treeview.get_cursor(currentPath, currentColumn);
179 if (currentPath.empty() || !font_lister->is_path_for_font(currentPath, family)) {
180 family_treeview.set_cursor (path);
181 family_treeview.scroll_to_row (path);
182 }
183
184 // Get font-lister style list for selected family
185 Gtk::TreeModel::Row row = *(family_treeview.get_model()->get_iter (path));
186 GList *styles;
187 row.get_value(1, styles);
188
189 // Copy font-lister style list to private list store, searching for match.
190 Gtk::TreeModel::iterator match;
191 FontLister::FontStyleListClass FontStyleList;
192 Glib::RefPtr<Gtk::ListStore> local_style_list_store = Gtk::ListStore::create(FontStyleList);
193 for ( ; styles; styles = styles->next ) {
194 Gtk::TreeModel::iterator treeModelIter = local_style_list_store->append();
195 (*treeModelIter)[FontStyleList.cssStyle] = ((StyleNames *)styles->data)->CssName;
196 (*treeModelIter)[FontStyleList.displayStyle] = ((StyleNames *)styles->data)->DisplayName;
197 if (style == ((StyleNames*)styles->data)->CssName) {
198 match = treeModelIter;
199 }
200 }
201
202 // Attach store to tree view and select row.
203 style_treeview.set_model (local_style_list_store);
204 if (match) {
205 style_treeview.get_selection()->select (match);
206 }
207
208 Glib::ustring fontspec = font_lister->get_fontspec();
209 update_variations(fontspec);
210
211 signal_block = false;
212 }
213
214 void
update_size(double size)215 FontSelector::update_size (double size)
216 {
217 signal_block = true;
218
219 // Set font size
220 std::stringstream ss;
221 ss << size;
222 size_combobox.get_entry()->set_text( ss.str() );
223 font_size = size; // Store value
224 set_fontsize_tooltip();
225
226 signal_block = false;
227 }
228
229
230 // If use_variations is true (default), we get variation values from variations widget otherwise we
231 // get values from CSS widget (we need to be able to keep the two widgets synchronized both ways).
232 Glib::ustring
get_fontspec(bool use_variations)233 FontSelector::get_fontspec(bool use_variations) {
234
235 // Build new fontspec from GUI settings
236 Glib::ustring family = "Sans"; // Default...family list may not have been constructed.
237 Gtk::TreeModel::iterator iter = family_treeview.get_selection()->get_selected();
238 if (iter) {
239 (*iter).get_value(0, family);
240 }
241
242 Glib::ustring style = "Normal";
243 iter = style_treeview.get_selection()->get_selected();
244 if (iter) {
245 (*iter).get_value(0, style);
246 }
247
248 if (family.empty()) {
249 std::cerr << "FontSelector::get_fontspec: empty family!" << std::endl;
250 }
251
252 if (style.empty()) {
253 std::cerr << "FontSelector::get_fontspec: empty style!" << std::endl;
254 }
255
256 Glib::ustring fontspec = family + ", ";
257
258 if (use_variations) {
259 // Clip any font_variation data in 'style' as we'll replace it.
260 auto pos = style.find('@');
261 if (pos != Glib::ustring::npos) {
262 style.erase (pos, style.length()-1);
263 }
264
265 Glib::ustring variations = font_variations.get_pango_string();
266
267 if (variations.empty()) {
268 fontspec += style;
269 } else {
270 fontspec += variations;
271 }
272 } else {
273 fontspec += style;
274 }
275
276 return fontspec;
277 }
278
279 void
style_cell_data_func(Gtk::CellRenderer * renderer,Gtk::TreeIter const & iter)280 FontSelector::style_cell_data_func (Gtk::CellRenderer *renderer, Gtk::TreeIter const &iter)
281 {
282 Glib::ustring family = "Sans"; // Default...family list may not have been constructed.
283 Gtk::TreeModel::iterator iter_family = family_treeview.get_selection()->get_selected();
284 if (iter_family) {
285 (*iter_family).get_value(0, family);
286 }
287
288 Glib::ustring style = "Normal";
289 (*iter).get_value(1, style);
290
291 Glib::ustring style_escaped = Glib::Markup::escape_text( style );
292 Glib::ustring font_desc = Glib::Markup::escape_text( family + ", " + style );
293 Glib::ustring markup;
294
295 markup = "<span font='" + font_desc + "'>" + style_escaped + "</span>";
296
297 // std::cout << " markup: " << markup << " (" << name << ")" << std::endl;
298
299 renderer->set_property("markup", markup);
300 }
301
302
303 // Callbacks
304
305 // Need to update style list
306 void
on_family_changed()307 FontSelector::on_family_changed() {
308
309 if (signal_block) return;
310 signal_block = true;
311
312 Glib::RefPtr<Gtk::TreeModel> model;
313 Gtk::TreeModel::iterator iter = family_treeview.get_selection()->get_selected(model);
314
315 if (!iter) {
316 // This can happen just after the family list is recreated.
317 signal_block = false;
318 return;
319 }
320
321 Inkscape::FontLister *fontlister = Inkscape::FontLister::get_instance();
322 fontlister->ensureRowStyles(model, iter);
323
324 Gtk::TreeModel::Row row = *iter;
325
326 // Get family name
327 Glib::ustring family;
328 row.get_value(0, family);
329
330 // Get style list (TO DO: Get rid of GList)
331 GList *styles;
332 row.get_value(1, styles);
333
334 // Find best style match for selected family with current style (e.g. of selected text).
335 Glib::ustring style = fontlister->get_font_style();
336 Glib::ustring best = fontlister->get_best_style_match (family, style);
337
338 // Create are own store of styles for selected font-family (the font-family selected
339 // in the dialog may not be the same as stored in the font-lister class until the
340 // "Apply" button is triggered).
341 Gtk::TreeModel::iterator it_best;
342 FontLister::FontStyleListClass FontStyleList;
343 Glib::RefPtr<Gtk::ListStore> local_style_list_store = Gtk::ListStore::create(FontStyleList);
344
345 // Build list and find best match.
346 for ( ; styles; styles = styles->next ) {
347 Gtk::TreeModel::iterator treeModelIter = local_style_list_store->append();
348 (*treeModelIter)[FontStyleList.cssStyle] = ((StyleNames *)styles->data)->CssName;
349 (*treeModelIter)[FontStyleList.displayStyle] = ((StyleNames *)styles->data)->DisplayName;
350 if (best == ((StyleNames*)styles->data)->CssName) {
351 it_best = treeModelIter;
352 }
353 }
354
355 // Attach store to tree view and select row.
356 style_treeview.set_model (local_style_list_store);
357 if (it_best) {
358 style_treeview.get_selection()->select (it_best);
359 }
360
361 signal_block = false;
362
363 // Let world know
364 changed_emit();
365 }
366
367 void
on_style_changed()368 FontSelector::on_style_changed() {
369 if (signal_block) return;
370
371 // Update variations widget if new style selected from style widget.
372 signal_block = true;
373 Glib::ustring fontspec = get_fontspec( false );
374 update_variations(fontspec);
375 signal_block = false;
376
377 // Let world know
378 changed_emit();
379 }
380
381 void
on_size_changed()382 FontSelector::on_size_changed() {
383
384 if (signal_block) return;
385
386 double size;
387 Glib::ustring input = size_combobox.get_active_text();
388 try {
389 size = std::stod (input);
390 }
391 catch (std::invalid_argument) {
392 std::cerr << "FontSelector::on_size_changed: Invalid input: " << input << std::endl;
393 size = -1;
394 }
395
396 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
397 // Arbitrary: Text and Font preview freezes with huge font sizes.
398 int max_size = prefs->getInt("/dialogs/textandfont/maxFontSize", 10000);
399
400 if (size <= 0) {
401 return;
402 }
403 if (size > max_size)
404 size = max_size;
405
406 if (fabs(font_size - size) > 0.001) {
407 font_size = size;
408 // Let world know
409 changed_emit();
410 }
411 }
412
413 void
on_variations_changed()414 FontSelector::on_variations_changed() {
415
416 if (signal_block) return;
417
418 // Let world know
419 changed_emit();
420 }
421
422 void
changed_emit()423 FontSelector::changed_emit() {
424 signal_block = true;
425 signal_changed.emit (get_fontspec());
426 signal_block = false;
427 }
428
update_variations(const Glib::ustring & fontspec)429 void FontSelector::update_variations(const Glib::ustring& fontspec) {
430 font_variations.update(fontspec);
431
432 // Check if there are any variations available; if not, don't expand font_variations_scroll
433 bool hasContent = font_variations.variations_present();
434 font_variations_scroll.set_vexpand(hasContent);
435 }
436
437 } // namespace Widget
438 } // namespace UI
439 } // namespace Inkscape
440
441 /*
442 Local Variables:
443 mode:c++
444 c-file-style:"stroustrup"
445 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
446 indent-tabs-mode:nil
447 fill-column:99
448 End:
449 */
450 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
451