1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3  * @file
4  * SVG Fonts dialog - implementation.
5  */
6 /* Authors:
7  *   Felipe C. da S. Sanches <juca@members.fsf.org>
8  *   Jon A. Cruz <jon@joncruz.org>
9  *   Abhishek Sharma
10  *
11  * Copyright (C) 2008 Authors
12  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
13  */
14 
15 #include <message-stack.h>
16 #include <sstream>
17 
18 #include <gtkmm/scale.h>
19 #include <gtkmm/notebook.h>
20 #include <gtkmm/imagemenuitem.h>
21 #include <glibmm/stringutils.h>
22 #include <glibmm/i18n.h>
23 
24 #include "desktop.h"
25 #include "document-undo.h"
26 #include "selection.h"
27 #include "svg-fonts-dialog.h"
28 #include "verbs.h"
29 
30 #include "display/nr-svgfonts.h"
31 #include "include/gtkmm_version.h"
32 #include "object/sp-defs.h"
33 #include "object/sp-font-face.h"
34 #include "object/sp-font.h"
35 #include "object/sp-glyph-kerning.h"
36 #include "object/sp-glyph.h"
37 #include "object/sp-missing-glyph.h"
38 #include "svg/svg.h"
39 #include "xml/repr.h"
40 
SvgFontDrawingArea()41 SvgFontDrawingArea::SvgFontDrawingArea():
42     _x(0),
43     _y(0),
44     _svgfont(nullptr),
45     _text()
46 {
47 }
48 
set_svgfont(SvgFont * svgfont)49 void SvgFontDrawingArea::set_svgfont(SvgFont* svgfont){
50     _svgfont = svgfont;
51 }
52 
set_text(Glib::ustring text)53 void SvgFontDrawingArea::set_text(Glib::ustring text){
54     _text = text;
55     redraw();
56 }
57 
set_size(int x,int y)58 void SvgFontDrawingArea::set_size(int x, int y){
59     _x = x;
60     _y = y;
61     ((Gtk::Widget*) this)->set_size_request(_x, _y);
62 }
63 
redraw()64 void SvgFontDrawingArea::redraw(){
65     ((Gtk::Widget*) this)->queue_draw();
66 }
67 
on_draw(const Cairo::RefPtr<Cairo::Context> & cr)68 bool SvgFontDrawingArea::on_draw(const Cairo::RefPtr<Cairo::Context> &cr) {
69   if (_svgfont){
70     cr->set_font_face( Cairo::RefPtr<Cairo::FontFace>(new Cairo::FontFace(_svgfont->get_font_face(), false /* does not have reference */)) );
71     cr->set_font_size (_y-20);
72     cr->move_to (10, 10);
73     cr->show_text (_text.c_str());
74 
75     // Draw some lines to show line area.
76     cr->set_source_rgb( 0.5, 0.5, 0.5 );
77     cr->move_to ( 0, 10);
78     cr->line_to (_x, 10);
79     cr->stroke();
80     cr->move_to ( 0, _y-10);
81     cr->line_to (_x, _y-10);
82     cr->stroke();
83   }
84   return true;
85 }
86 
87 namespace Inkscape {
88 namespace UI {
89 namespace Dialog {
90 
91 /*
92 Gtk::Box* SvgFontsDialog::AttrEntry(gchar* lbl, const SPAttr attr){
93     Gtk::Box* hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
94     hbox->add(* Gtk::manage(new Gtk::Label(lbl)) );
95     Gtk::Entry* entry = Gtk::manage(new Gtk::Entry());
96     hbox->add(* entry );
97     hbox->show_all();
98 
99     entry->signal_changed().connect(sigc::mem_fun(*this, &SvgFontsDialog::on_attr_changed));
100     return hbox;
101 }
102 */
103 
AttrEntry(SvgFontsDialog * d,gchar * lbl,Glib::ustring tooltip,const SPAttr attr)104 SvgFontsDialog::AttrEntry::AttrEntry(SvgFontsDialog* d, gchar* lbl, Glib::ustring tooltip, const SPAttr attr)
105 : Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)
106 {
107     this->dialog = d;
108     this->attr = attr;
109     entry.set_tooltip_text(tooltip);
110     auto label = new Gtk::Label(lbl);
111     this->pack_start(*Gtk::manage(label), false, false, 4);
112     this->pack_end(entry, true, true);
113     this->show_all();
114 
115     entry.signal_changed().connect(sigc::mem_fun(*this, &SvgFontsDialog::AttrEntry::on_attr_changed));
116 }
117 
set_text(char * t)118 void SvgFontsDialog::AttrEntry::set_text(char* t){
119     if (!t) return;
120     entry.set_text(t);
121 }
122 
123 // 'font-family' has a problem as it is also a presentation attribute for <text>
on_attr_changed()124 void SvgFontsDialog::AttrEntry::on_attr_changed(){
125 
126     SPObject* o = nullptr;
127     for (auto& node: dialog->get_selected_spfont()->children) {
128         switch(this->attr){
129             case SPAttr::FONT_FAMILY:
130                 if (SP_IS_FONTFACE(&node)){
131                     o = &node;
132                     continue;
133                 }
134                 break;
135             default:
136                 o = nullptr;
137         }
138     }
139 
140     const gchar* name = (const gchar*)sp_attribute_name(this->attr);
141     if(name && o) {
142         o->setAttribute((const gchar*) name, this->entry.get_text());
143         o->parent->requestModified(SP_OBJECT_MODIFIED_FLAG);
144 
145         Glib::ustring undokey = "svgfonts:";
146         undokey += name;
147         DocumentUndo::maybeDone(o->document, undokey.c_str(), SP_VERB_DIALOG_SVG_FONTS,
148                                 _("Set SVG Font attribute"));
149     }
150 
151 }
152 
AttrSpin(SvgFontsDialog * d,gchar * lbl,Glib::ustring tooltip,const SPAttr attr)153 SvgFontsDialog::AttrSpin::AttrSpin(SvgFontsDialog* d, gchar* lbl, Glib::ustring tooltip, const SPAttr attr)
154 : Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)
155 {
156     this->dialog = d;
157     this->attr = attr;
158     spin.set_tooltip_text(tooltip);
159     auto label = new Gtk::Label(lbl);
160     this->set_border_width(2);
161     this->set_spacing(6);
162     this->pack_start(*Gtk::manage(label), false, false);
163     this->pack_end(spin, true, true);
164     this->show_all();
165     spin.set_range(0, 4096);
166     spin.set_increments(16, 0);
167     spin.signal_value_changed().connect(sigc::mem_fun(*this, &SvgFontsDialog::AttrSpin::on_attr_changed));
168 }
169 
set_range(double low,double high)170 void SvgFontsDialog::AttrSpin::set_range(double low, double high){
171     spin.set_range(low, high);
172 }
173 
set_value(double v)174 void SvgFontsDialog::AttrSpin::set_value(double v){
175     spin.set_value(v);
176 }
177 
on_attr_changed()178 void SvgFontsDialog::AttrSpin::on_attr_changed(){
179 
180     SPObject* o = nullptr;
181     switch (this->attr) {
182 
183         // <font> attributes
184         case SPAttr::HORIZ_ORIGIN_X:
185         case SPAttr::HORIZ_ORIGIN_Y:
186         case SPAttr::HORIZ_ADV_X:
187         case SPAttr::VERT_ORIGIN_X:
188         case SPAttr::VERT_ORIGIN_Y:
189         case SPAttr::VERT_ADV_Y:
190             o = this->dialog->get_selected_spfont();
191             break;
192 
193         // <font-face> attributes
194         case SPAttr::UNITS_PER_EM:
195         case SPAttr::ASCENT:
196         case SPAttr::DESCENT:
197         case SPAttr::CAP_HEIGHT:
198         case SPAttr::X_HEIGHT:
199             for (auto& node: dialog->get_selected_spfont()->children){
200                 if (SP_IS_FONTFACE(&node)){
201                     o = &node;
202                     continue;
203                 }
204             }
205             break;
206 
207         default:
208             o = nullptr;
209     }
210 
211     const gchar* name = (const gchar*)sp_attribute_name(this->attr);
212     if(name && o) {
213         std::ostringstream temp;
214         temp << this->spin.get_value();
215         o->setAttribute(name, temp.str());
216         o->parent->requestModified(SP_OBJECT_MODIFIED_FLAG);
217 
218         Glib::ustring undokey = "svgfonts:";
219         undokey += name;
220         DocumentUndo::maybeDone(o->document, undokey.c_str(), SP_VERB_DIALOG_SVG_FONTS,
221                                 _("Set SVG Font attribute"));
222     }
223 
224 }
225 
AttrCombo(gchar * lbl,const SPAttr)226 Gtk::Box* SvgFontsDialog::AttrCombo(gchar* lbl, const SPAttr /*attr*/){
227     Gtk::Box* hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
228     hbox->add(* Gtk::manage(new Gtk::Label(lbl)) );
229     hbox->add(* Gtk::manage(new Gtk::ComboBox()) );
230     hbox->show_all();
231     return hbox;
232 }
233 
234 /*
235 Gtk::Box* SvgFontsDialog::AttrSpin(gchar* lbl){
236     Gtk::Box* hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
237     hbox->add(* Gtk::manage(new Gtk::Label(lbl)) );
238     hbox->add(* Gtk::manage(new Inkscape::UI::Widget::SpinBox()) );
239     hbox->show_all();
240     return hbox;
241 }*/
242 
243 /*** SvgFontsDialog ***/
244 
245 GlyphComboBox::GlyphComboBox()= default;
246 
update(SPFont * spfont)247 void GlyphComboBox::update(SPFont* spfont){
248     if (!spfont) return;
249 
250     this->remove_all();
251 
252     for (auto& node: spfont->children) {
253         if (SP_IS_GLYPH(&node)){
254             this->append((static_cast<SPGlyph*>(&node))->unicode);
255         }
256     }
257 }
258 
on_kerning_value_changed()259 void SvgFontsDialog::on_kerning_value_changed(){
260     if (!get_selected_kerning_pair()) {
261         return;
262     }
263 
264     SPDocument* document = this->getDesktop()->getDocument();
265 
266     //TODO: I am unsure whether this is the correct way of calling SPDocumentUndo::maybe_done
267     Glib::ustring undokey = "svgfonts:hkern:k:";
268     undokey += this->kerning_pair->u1->attribute_string();
269     undokey += ":";
270     undokey += this->kerning_pair->u2->attribute_string();
271 
272     //slider values increase from right to left so that they match the kerning pair preview
273 
274     //XML Tree being directly used here while it shouldn't be.
275     this->kerning_pair->setAttribute("k", Glib::Ascii::dtostr(get_selected_spfont()->horiz_adv_x - kerning_slider->get_value()));
276     DocumentUndo::maybeDone(document, undokey.c_str(), SP_VERB_DIALOG_SVG_FONTS, _("Adjust kerning value"));
277 
278     //populate_kerning_pairs_box();
279     kerning_preview.redraw();
280     _font_da.redraw();
281 }
282 
glyphs_list_button_release(GdkEventButton * event)283 void SvgFontsDialog::glyphs_list_button_release(GdkEventButton* event)
284 {
285     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
286         _GlyphsContextMenu.popup_at_pointer(reinterpret_cast<GdkEvent *>(event));
287     }
288 }
289 
kerning_pairs_list_button_release(GdkEventButton * event)290 void SvgFontsDialog::kerning_pairs_list_button_release(GdkEventButton* event)
291 {
292     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
293         _KerningPairsContextMenu.popup_at_pointer(reinterpret_cast<GdkEvent *>(event));
294     }
295 }
296 
fonts_list_button_release(GdkEventButton * event)297 void SvgFontsDialog::fonts_list_button_release(GdkEventButton* event)
298 {
299     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
300         _FontsContextMenu.popup_at_pointer(reinterpret_cast<GdkEvent *>(event));
301     }
302 }
303 
create_glyphs_popup_menu(Gtk::Widget & parent,sigc::slot<void> rem)304 void SvgFontsDialog::create_glyphs_popup_menu(Gtk::Widget& parent, sigc::slot<void> rem)
305 {
306     auto mi = Gtk::manage(new Gtk::MenuItem(_("_Remove"), true));
307     _GlyphsContextMenu.append(*mi);
308     mi->signal_activate().connect(rem);
309     mi->show();
310     _GlyphsContextMenu.accelerate(parent);
311 }
312 
create_kerning_pairs_popup_menu(Gtk::Widget & parent,sigc::slot<void> rem)313 void SvgFontsDialog::create_kerning_pairs_popup_menu(Gtk::Widget& parent, sigc::slot<void> rem)
314 {
315     auto mi = Gtk::manage(new Gtk::MenuItem(_("_Remove"), true));
316     _KerningPairsContextMenu.append(*mi);
317     mi->signal_activate().connect(rem);
318     mi->show();
319     _KerningPairsContextMenu.accelerate(parent);
320 }
321 
create_fonts_popup_menu(Gtk::Widget & parent,sigc::slot<void> rem)322 void SvgFontsDialog::create_fonts_popup_menu(Gtk::Widget& parent, sigc::slot<void> rem)
323 {
324     auto mi = Gtk::manage(new Gtk::MenuItem(_("_Remove"), true));
325     _FontsContextMenu.append(*mi);
326     mi->signal_activate().connect(rem);
327     mi->show();
328     _FontsContextMenu.accelerate(parent);
329 }
330 
update_sensitiveness()331 void SvgFontsDialog::update_sensitiveness(){
332     if (get_selected_spfont()){
333         global_vbox.set_sensitive(true);
334         glyphs_vbox.set_sensitive(true);
335         kerning_vbox.set_sensitive(true);
336     } else {
337         global_vbox.set_sensitive(false);
338         glyphs_vbox.set_sensitive(false);
339         kerning_vbox.set_sensitive(false);
340     }
341 }
342 
343 /* Add all fonts in the document to the combobox. */
update_fonts()344 void SvgFontsDialog::update_fonts()
345 {
346     SPDesktop* desktop = this->getDesktop();
347     SPDocument* document = desktop->getDocument();
348     std::vector<SPObject *> fonts = document->getResourceList( "font" );
349 
350     _model->clear();
351     for (auto font : fonts) {
352         Gtk::TreeModel::Row row = *_model->append();
353         SPFont* f = SP_FONT(font);
354         row[_columns.spfont] = f;
355         row[_columns.svgfont] = new SvgFont(f);
356         const gchar* lbl = f->label();
357         const gchar* id = f->getId();
358         row[_columns.label] = lbl ? lbl : (id ? id : "font");
359     }
360 
361     update_sensitiveness();
362 }
363 
on_preview_text_changed()364 void SvgFontsDialog::on_preview_text_changed(){
365     _font_da.set_text(_preview_entry.get_text());
366 }
367 
on_kerning_pair_selection_changed()368 void SvgFontsDialog::on_kerning_pair_selection_changed(){
369     SPGlyphKerning* kern = get_selected_kerning_pair();
370     if (!kern) {
371         kerning_preview.set_text("");
372         return;
373     }
374     Glib::ustring str;
375     str += kern->u1->sample_glyph();
376     str += kern->u2->sample_glyph();
377 
378     kerning_preview.set_text(str);
379     this->kerning_pair = kern;
380 
381     //slider values increase from right to left so that they match the kerning pair preview
382     kerning_slider->set_value(get_selected_spfont()->horiz_adv_x - kern->k);
383 }
384 
update_global_settings_tab()385 void SvgFontsDialog::update_global_settings_tab(){
386     SPFont* font = get_selected_spfont();
387     if (!font) return;
388 
389     _horiz_adv_x_spin->set_value(font->horiz_adv_x);
390     _horiz_origin_x_spin->set_value(font->horiz_origin_x);
391     _horiz_origin_y_spin->set_value(font->horiz_origin_y);
392 
393     for (auto& obj: font->children) {
394         if (SP_IS_FONTFACE(&obj)){
395             _familyname_entry->set_text((SP_FONTFACE(&obj))->font_family);
396             _units_per_em_spin->set_value((SP_FONTFACE(&obj))->units_per_em);
397             _ascent_spin->set_value((SP_FONTFACE(&obj))->ascent);
398             _descent_spin->set_value((SP_FONTFACE(&obj))->descent);
399             _x_height_spin->set_value((SP_FONTFACE(&obj))->x_height);
400             _cap_height_spin->set_value((SP_FONTFACE(&obj))->cap_height);
401         }
402     }
403 }
404 
on_font_selection_changed()405 void SvgFontsDialog::on_font_selection_changed(){
406     SPFont* spfont = this->get_selected_spfont();
407     if (!spfont) return;
408 
409     SvgFont* svgfont = this->get_selected_svgfont();
410     first_glyph.update(spfont);
411     second_glyph.update(spfont);
412     kerning_preview.set_svgfont(svgfont);
413     _font_da.set_svgfont(svgfont);
414     _font_da.redraw();
415 
416     kerning_slider->set_range(0, spfont->horiz_adv_x);
417     kerning_slider->set_draw_value(false);
418     kerning_slider->set_value(0);
419 
420     update_global_settings_tab();
421     populate_glyphs_box();
422     populate_kerning_pairs_box();
423     update_sensitiveness();
424 }
425 
get_selected_kerning_pair()426 SPGlyphKerning* SvgFontsDialog::get_selected_kerning_pair()
427 {
428     Gtk::TreeModel::iterator i = _KerningPairsList.get_selection()->get_selected();
429     if(i)
430         return (*i)[_KerningPairsListColumns.spnode];
431     return nullptr;
432 }
433 
get_selected_svgfont()434 SvgFont* SvgFontsDialog::get_selected_svgfont()
435 {
436     Gtk::TreeModel::iterator i = _FontsList.get_selection()->get_selected();
437     if(i)
438         return (*i)[_columns.svgfont];
439     return nullptr;
440 }
441 
get_selected_spfont()442 SPFont* SvgFontsDialog::get_selected_spfont()
443 {
444     Gtk::TreeModel::iterator i = _FontsList.get_selection()->get_selected();
445     if(i)
446         return (*i)[_columns.spfont];
447     return nullptr;
448 }
449 
get_selected_glyph()450 SPGlyph* SvgFontsDialog::get_selected_glyph()
451 {
452     Gtk::TreeModel::iterator i = _GlyphsList.get_selection()->get_selected();
453     if(i)
454         return (*i)[_GlyphsListColumns.glyph_node];
455     return nullptr;
456 }
457 
global_settings_tab()458 Gtk::Box* SvgFontsDialog::global_settings_tab(){
459     _font_label          = new Gtk::Label(Glib::ustring("<b>") + _("Font Attributes") + "</b>", Gtk::ALIGN_START, Gtk::ALIGN_CENTER);
460     _horiz_adv_x_spin    = new AttrSpin( this, (gchar*) _("Horiz. Advance X:"), _("Default glyph width for horizontal text"), SPAttr::HORIZ_ADV_X);
461     _horiz_origin_x_spin = new AttrSpin( this, (gchar*) _("Horiz. Origin X:"), _("Default X-coordinate of the origin of a glyph (for horizontal text)"), SPAttr::HORIZ_ORIGIN_X);
462     _horiz_origin_y_spin = new AttrSpin( this, (gchar*) _("Horiz. Origin Y:"), _("Default Y-coordinate of the origin of a glyph (for horizontal text)"), SPAttr::HORIZ_ORIGIN_Y);
463     _font_face_label     = new Gtk::Label(Glib::ustring("<b>") + _("Font Face Attributes") + "</b>", Gtk::ALIGN_START, Gtk::ALIGN_CENTER);
464     _familyname_entry    = new AttrEntry(this, (gchar*) _("Family Name:"), _("Name of the font as it appears in font selectors and css font-family properties"), SPAttr::FONT_FAMILY);
465     _units_per_em_spin   = new AttrSpin( this, (gchar*) _("Units per em:"), _("Display units per <italic>em</italic> (nominally width of 'M' character)"), SPAttr::UNITS_PER_EM);
466     _ascent_spin         = new AttrSpin( this, (gchar*) _("Ascent:"),      _("Amount of space taken up by ascenders like the tall line on the letter 'h'"), SPAttr::ASCENT);
467     _descent_spin        = new AttrSpin( this, (gchar*) _("Descent:"),     _("Amount of space taken up by descenders like the tail on the letter 'g'"), SPAttr::DESCENT);
468     _cap_height_spin     = new AttrSpin( this, (gchar*) _("Cap Height:"),  _("The height of a capital letter above the baseline like the letter 'H' or 'I'"), SPAttr::CAP_HEIGHT);
469     _x_height_spin       = new AttrSpin( this, (gchar*) _("x Height:"),    _("The height of a lower-case letter above the baseline like the letter 'x'"), SPAttr::X_HEIGHT);
470 
471     //_descent_spin->set_range(-4096,0);
472     _font_label->set_use_markup();
473     _font_face_label->set_use_markup();
474 
475     global_vbox.set_border_width(2);
476     global_vbox.pack_start(*_font_label);
477     global_vbox.pack_start(*_horiz_adv_x_spin);
478     global_vbox.pack_start(*_horiz_origin_x_spin);
479     global_vbox.pack_start(*_horiz_origin_y_spin);
480     global_vbox.pack_start(*_font_face_label);
481     global_vbox.pack_start(*_familyname_entry);
482     global_vbox.pack_start(*_units_per_em_spin);
483     global_vbox.pack_start(*_ascent_spin);
484     global_vbox.pack_start(*_descent_spin);
485     global_vbox.pack_start(*_cap_height_spin);
486     global_vbox.pack_start(*_x_height_spin);
487 
488 /*    global_vbox->add(*AttrCombo((gchar*) _("Style:"), SPAttr::FONT_STYLE));
489     global_vbox->add(*AttrCombo((gchar*) _("Variant:"), SPAttr::FONT_VARIANT));
490     global_vbox->add(*AttrCombo((gchar*) _("Weight:"), SPAttr::FONT_WEIGHT));
491 */
492 
493     return &global_vbox;
494 }
495 
496 void
populate_glyphs_box()497 SvgFontsDialog::populate_glyphs_box()
498 {
499     if (!_GlyphsListStore) return;
500     _GlyphsListStore->clear();
501 
502     SPFont* spfont = this->get_selected_spfont();
503     _glyphs_observer.set(spfont);
504 
505     for (auto& node: spfont->children) {
506         if (SP_IS_GLYPH(&node)){
507             Gtk::TreeModel::Row row = *(_GlyphsListStore->append());
508             row[_GlyphsListColumns.glyph_node] =  static_cast<SPGlyph*>(&node);
509             row[_GlyphsListColumns.glyph_name] = (static_cast<SPGlyph*>(&node))->glyph_name;
510             row[_GlyphsListColumns.unicode]    = (static_cast<SPGlyph*>(&node))->unicode;
511             row[_GlyphsListColumns.advance]    = (static_cast<SPGlyph*>(&node))->horiz_adv_x;
512         }
513     }
514 }
515 
516 void
populate_kerning_pairs_box()517 SvgFontsDialog::populate_kerning_pairs_box()
518 {
519     if (!_KerningPairsListStore) return;
520     _KerningPairsListStore->clear();
521 
522     SPFont* spfont = this->get_selected_spfont();
523 
524     for (auto& node: spfont->children) {
525         if (SP_IS_HKERN(&node)){
526             Gtk::TreeModel::Row row = *(_KerningPairsListStore->append());
527             row[_KerningPairsListColumns.first_glyph] = (static_cast<SPGlyphKerning*>(&node))->u1->attribute_string().c_str();
528             row[_KerningPairsListColumns.second_glyph] = (static_cast<SPGlyphKerning*>(&node))->u2->attribute_string().c_str();
529             row[_KerningPairsListColumns.kerning_value] = (static_cast<SPGlyphKerning*>(&node))->k;
530             row[_KerningPairsListColumns.spnode] = static_cast<SPGlyphKerning*>(&node);
531         }
532     }
533 }
534 
new_glyph(SPDocument * document,SPFont * font,const int count)535 SPGlyph *new_glyph(SPDocument* document, SPFont *font, const int count)
536 {
537     g_return_val_if_fail(font != nullptr, NULL);
538     Inkscape::XML::Document *xml_doc = document->getReprDoc();
539 
540     // create a new glyph
541     Inkscape::XML::Node *repr;
542     repr = xml_doc->createElement("svg:glyph");
543 
544     std::ostringstream os;
545     os << _("glyph") << " " << count;
546     repr->setAttribute("glyph-name", os.str());
547 
548     // Append the new glyph node to the current font
549     font->getRepr()->appendChild(repr);
550     Inkscape::GC::release(repr);
551 
552     // get corresponding object
553     SPGlyph *g = SP_GLYPH( document->getObjectByRepr(repr) );
554 
555     g_assert(g != nullptr);
556     g_assert(SP_IS_GLYPH(g));
557 
558     return g;
559 }
560 
update_glyphs()561 void SvgFontsDialog::update_glyphs(){
562     SPFont* font = get_selected_spfont();
563     if (!font) return;
564     populate_glyphs_box();
565     populate_kerning_pairs_box();
566     first_glyph.update(font);
567     second_glyph.update(font);
568     get_selected_svgfont()->refresh();
569     _font_da.redraw();
570 }
571 
add_glyph()572 void SvgFontsDialog::add_glyph(){
573     const int count = _GlyphsListStore->children().size();
574     SPDocument* doc = this->getDesktop()->getDocument();
575     /* SPGlyph* glyph =*/ new_glyph(doc, get_selected_spfont(), count+1);
576 
577     DocumentUndo::done(doc, SP_VERB_DIALOG_SVG_FONTS, _("Add glyph"));
578 
579     update_glyphs();
580 }
581 
582 Geom::PathVector
flip_coordinate_system(Geom::PathVector pathv)583 SvgFontsDialog::flip_coordinate_system(Geom::PathVector pathv){
584     double units_per_em = 1024;
585     for (auto& obj: get_selected_spfont()->children) {
586         if (SP_IS_FONTFACE(&obj)){
587             //XML Tree being directly used here while it shouldn't be.
588             sp_repr_get_double(obj.getRepr(), "units-per-em", &units_per_em);
589         }
590     }
591     double baseline_offset = units_per_em - get_selected_spfont()->horiz_origin_y;
592     //This matrix flips y-axis and places the origin at baseline
593     Geom::Affine m(Geom::Coord(1),Geom::Coord(0),Geom::Coord(0),Geom::Coord(-1),Geom::Coord(0),Geom::Coord(baseline_offset));
594     return pathv*m;
595 }
596 
set_glyph_description_from_selected_path()597 void SvgFontsDialog::set_glyph_description_from_selected_path(){
598     SPDesktop* desktop = this->getDesktop();
599     if (!desktop) {
600         g_warning("SvgFontsDialog: No active desktop");
601         return;
602     }
603 
604     Inkscape::MessageStack *msgStack = desktop->getMessageStack();
605     SPDocument* doc = desktop->getDocument();
606     Inkscape::Selection* sel = desktop->getSelection();
607     if (sel->isEmpty()){
608         char *msg = _("Select a <b>path</b> to define the curves of a glyph");
609         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
610         return;
611     }
612 
613     Inkscape::XML::Node* node = sel->xmlNodes().front();
614     if (!node) return;//TODO: should this be an assert?
615     if (!node->matchAttributeName("d") || !node->attribute("d")){
616         char *msg = _("The selected object does not have a <b>path</b> description.");
617         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
618         return;
619     } //TODO: //Is there a better way to tell it to to the user?
620 
621     SPGlyph* glyph = get_selected_glyph();
622     if (!glyph){
623         char *msg = _("No glyph selected in the SVGFonts dialog.");
624         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
625         return;
626     }
627 
628     Geom::PathVector pathv = sp_svg_read_pathv(node->attribute("d"));
629 
630 	//XML Tree being directly used here while it shouldn't be.
631     glyph->setAttribute("d", sp_svg_write_path(flip_coordinate_system(pathv)));
632     DocumentUndo::done(doc, SP_VERB_DIALOG_SVG_FONTS, _("Set glyph curves"));
633 
634     update_glyphs();
635 }
636 
missing_glyph_description_from_selected_path()637 void SvgFontsDialog::missing_glyph_description_from_selected_path(){
638     SPDesktop* desktop = this->getDesktop();
639     if (!desktop) {
640         g_warning("SvgFontsDialog: No active desktop");
641         return;
642     }
643 
644     Inkscape::MessageStack *msgStack = desktop->getMessageStack();
645     SPDocument* doc = desktop->getDocument();
646     Inkscape::Selection* sel = desktop->getSelection();
647     if (sel->isEmpty()){
648         char *msg = _("Select a <b>path</b> to define the curves of a glyph");
649         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
650         return;
651     }
652 
653     Inkscape::XML::Node* node = sel->xmlNodes().front();
654     if (!node) return;//TODO: should this be an assert?
655     if (!node->matchAttributeName("d") || !node->attribute("d")){
656         char *msg = _("The selected object does not have a <b>path</b> description.");
657         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
658         return;
659     } //TODO: //Is there a better way to tell it to to the user?
660 
661     Geom::PathVector pathv = sp_svg_read_pathv(node->attribute("d"));
662 
663     for (auto& obj: get_selected_spfont()->children) {
664         if (SP_IS_MISSING_GLYPH(&obj)){
665 
666             //XML Tree being directly used here while it shouldn't be.
667             obj.setAttribute("d", sp_svg_write_path(flip_coordinate_system(pathv)));
668             DocumentUndo::done(doc, SP_VERB_DIALOG_SVG_FONTS, _("Set glyph curves"));
669         }
670     }
671 
672     update_glyphs();
673 }
674 
reset_missing_glyph_description()675 void SvgFontsDialog::reset_missing_glyph_description(){
676     SPDesktop* desktop = this->getDesktop();
677     if (!desktop) {
678         g_warning("SvgFontsDialog: No active desktop");
679         return;
680     }
681 
682     SPDocument* doc = desktop->getDocument();
683     for (auto& obj: get_selected_spfont()->children) {
684         if (SP_IS_MISSING_GLYPH(&obj)){
685             //XML Tree being directly used here while it shouldn't be.
686             obj.setAttribute("d", "M0,0h1000v1024h-1000z");
687             DocumentUndo::done(doc, SP_VERB_DIALOG_SVG_FONTS, _("Reset missing-glyph"));
688         }
689     }
690 
691     update_glyphs();
692 }
693 
glyph_name_edit(const Glib::ustring &,const Glib::ustring & str)694 void SvgFontsDialog::glyph_name_edit(const Glib::ustring&, const Glib::ustring& str){
695     Gtk::TreeModel::iterator i = _GlyphsList.get_selection()->get_selected();
696     if (!i) return;
697 
698     SPGlyph* glyph = (*i)[_GlyphsListColumns.glyph_node];
699     //XML Tree being directly used here while it shouldn't be.
700     glyph->setAttribute("glyph-name", str);
701 
702     SPDocument* doc = this->getDesktop()->getDocument();
703     DocumentUndo::done(doc, SP_VERB_DIALOG_SVG_FONTS, _("Edit glyph name"));
704 
705     update_glyphs();
706 }
707 
glyph_unicode_edit(const Glib::ustring &,const Glib::ustring & str)708 void SvgFontsDialog::glyph_unicode_edit(const Glib::ustring&, const Glib::ustring& str){
709     Gtk::TreeModel::iterator i = _GlyphsList.get_selection()->get_selected();
710     if (!i) return;
711 
712     SPGlyph* glyph = (*i)[_GlyphsListColumns.glyph_node];
713     //XML Tree being directly used here while it shouldn't be.
714     glyph->setAttribute("unicode", str);
715 
716     SPDocument* doc = this->getDesktop()->getDocument();
717     DocumentUndo::done(doc, SP_VERB_DIALOG_SVG_FONTS, _("Set glyph unicode"));
718 
719     update_glyphs();
720 }
721 
glyph_advance_edit(const Glib::ustring &,const Glib::ustring & str)722 void SvgFontsDialog::glyph_advance_edit(const Glib::ustring&, const Glib::ustring& str){
723     Gtk::TreeModel::iterator i = _GlyphsList.get_selection()->get_selected();
724     if (!i) return;
725 
726     SPGlyph* glyph = (*i)[_GlyphsListColumns.glyph_node];
727     //XML Tree being directly used here while it shouldn't be.
728     std::istringstream is(str.raw());
729     double value;
730     // Check if input valid
731     if ((is >> value)) {
732         glyph->setAttribute("horiz-adv-x", str);
733         SPDocument* doc = this->getDesktop()->getDocument();
734         DocumentUndo::done(doc, SP_VERB_DIALOG_SVG_FONTS, _("Set glyph advance"));
735 
736         update_glyphs();
737     } else {
738         std::cerr << "SvgFontDialog::glyph_advance_edit: Error in input: " << str << std::endl;
739     }
740 }
741 
remove_selected_font()742 void SvgFontsDialog::remove_selected_font(){
743     SPFont* font = get_selected_spfont();
744     if (!font) return;
745 
746     //XML Tree being directly used here while it shouldn't be.
747     sp_repr_unparent(font->getRepr());
748     SPDocument* doc = this->getDesktop()->getDocument();
749     DocumentUndo::done(doc, SP_VERB_DIALOG_SVG_FONTS, _("Remove font"));
750 
751     update_fonts();
752 }
753 
remove_selected_glyph()754 void SvgFontsDialog::remove_selected_glyph(){
755     if(!_GlyphsList.get_selection()) return;
756 
757     Gtk::TreeModel::iterator i = _GlyphsList.get_selection()->get_selected();
758     if(!i) return;
759 
760     SPGlyph* glyph = (*i)[_GlyphsListColumns.glyph_node];
761 
762 	//XML Tree being directly used here while it shouldn't be.
763     sp_repr_unparent(glyph->getRepr());
764 
765     SPDocument* doc = this->getDesktop()->getDocument();
766     DocumentUndo::done(doc, SP_VERB_DIALOG_SVG_FONTS, _("Remove glyph"));
767 
768     update_glyphs();
769 }
770 
remove_selected_kerning_pair()771 void SvgFontsDialog::remove_selected_kerning_pair(){
772     if(!_KerningPairsList.get_selection()) return;
773 
774     Gtk::TreeModel::iterator i = _KerningPairsList.get_selection()->get_selected();
775     if(!i) return;
776 
777     SPGlyphKerning* pair = (*i)[_KerningPairsListColumns.spnode];
778 
779 	//XML Tree being directly used here while it shouldn't be.
780     sp_repr_unparent(pair->getRepr());
781 
782     SPDocument* doc = this->getDesktop()->getDocument();
783     DocumentUndo::done(doc, SP_VERB_DIALOG_SVG_FONTS, _("Remove kerning pair"));
784 
785     update_glyphs();
786 }
787 
glyphs_tab()788 Gtk::Box* SvgFontsDialog::glyphs_tab(){
789     _GlyphsList.signal_button_release_event().connect_notify(sigc::mem_fun(*this, &SvgFontsDialog::glyphs_list_button_release));
790     create_glyphs_popup_menu(_GlyphsList, sigc::mem_fun(*this, &SvgFontsDialog::remove_selected_glyph));
791 
792     Gtk::Box* missing_glyph_hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4));
793     Gtk::Label* missing_glyph_label = Gtk::manage(new Gtk::Label(_("Missing Glyph:")));
794     missing_glyph_hbox->set_hexpand(false);
795     missing_glyph_hbox->pack_start(*missing_glyph_label, false,false);
796     missing_glyph_hbox->pack_start(missing_glyph_button, false,false);
797     missing_glyph_hbox->pack_start(missing_glyph_reset_button, false,false);
798 
799     missing_glyph_button.set_label(_("From selection..."));
800     missing_glyph_button.signal_clicked().connect(sigc::mem_fun(*this, &SvgFontsDialog::missing_glyph_description_from_selected_path));
801     missing_glyph_reset_button.set_label(_("Reset"));
802     missing_glyph_reset_button.signal_clicked().connect(sigc::mem_fun(*this, &SvgFontsDialog::reset_missing_glyph_description));
803 
804     glyphs_vbox.set_border_width(4);
805     glyphs_vbox.set_spacing(4);
806     glyphs_vbox.pack_start(*missing_glyph_hbox, false,false);
807 
808     glyphs_vbox.add(_GlyphsListScroller);
809     _GlyphsListScroller.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
810     _GlyphsListScroller.set_size_request(-1, 290);
811     _GlyphsListScroller.add(_GlyphsList);
812     _GlyphsListStore = Gtk::ListStore::create(_GlyphsListColumns);
813     _GlyphsList.set_model(_GlyphsListStore);
814     _GlyphsList.append_column_editable(_("Glyph name"),      _GlyphsListColumns.glyph_name);
815     _GlyphsList.append_column_editable(_("Matching string"), _GlyphsListColumns.unicode);
816     _GlyphsList.append_column_numeric_editable(_("Advance"), _GlyphsListColumns.advance, "%.2f");
817     Gtk::Box* hb = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4));
818     add_glyph_button.set_label(_("Add Glyph"));
819     add_glyph_button.signal_clicked().connect(sigc::mem_fun(*this, &SvgFontsDialog::add_glyph));
820 
821     hb->pack_start(add_glyph_button, false,false);
822     hb->pack_start(glyph_from_path_button, false,false);
823 
824     glyphs_vbox.pack_start(*hb, false, false);
825     glyph_from_path_button.set_label(_("Get curves from selection..."));
826     glyph_from_path_button.signal_clicked().connect(sigc::mem_fun(*this, &SvgFontsDialog::set_glyph_description_from_selected_path));
827 
828     static_cast<Gtk::CellRendererText*>( _GlyphsList.get_column_cell_renderer(0))->signal_edited().connect(
829         sigc::mem_fun(*this, &SvgFontsDialog::glyph_name_edit));
830 
831     static_cast<Gtk::CellRendererText*>( _GlyphsList.get_column_cell_renderer(1))->signal_edited().connect(
832         sigc::mem_fun(*this, &SvgFontsDialog::glyph_unicode_edit));
833 
834     static_cast<Gtk::CellRendererText*>( _GlyphsList.get_column_cell_renderer(2))->signal_edited().connect(
835         sigc::mem_fun(*this, &SvgFontsDialog::glyph_advance_edit));
836 
837     _glyphs_observer.signal_changed().connect(sigc::mem_fun(*this, &SvgFontsDialog::update_glyphs));
838 
839     return &glyphs_vbox;
840 }
841 
add_kerning_pair()842 void SvgFontsDialog::add_kerning_pair(){
843     if (first_glyph.get_active_text() == "" ||
844         second_glyph.get_active_text() == "") return;
845 
846     //look for this kerning pair on the currently selected font
847     this->kerning_pair = nullptr;
848     for (auto& node: get_selected_spfont()->children) {
849         //TODO: It is not really correct to get only the first byte of each string.
850         //TODO: We should also support vertical kerning
851         if (SP_IS_HKERN(&node) && (static_cast<SPGlyphKerning*>(&node))->u1->contains((gchar) first_glyph.get_active_text().c_str()[0])
852             && (static_cast<SPGlyphKerning*>(&node))->u2->contains((gchar) second_glyph.get_active_text().c_str()[0]) ){
853             this->kerning_pair = static_cast<SPGlyphKerning*>(&node);
854             continue;
855         }
856     }
857 
858     if (this->kerning_pair) return; //We already have this kerning pair
859 
860     SPDocument* document = this->getDesktop()->getDocument();
861     Inkscape::XML::Document *xml_doc = document->getReprDoc();
862 
863     // create a new hkern node
864     Inkscape::XML::Node *repr = xml_doc->createElement("svg:hkern");
865 
866     repr->setAttribute("u1", first_glyph.get_active_text());
867     repr->setAttribute("u2", second_glyph.get_active_text());
868     repr->setAttribute("k", "0");
869 
870     // Append the new hkern node to the current font
871     get_selected_spfont()->getRepr()->appendChild(repr);
872     Inkscape::GC::release(repr);
873 
874     // get corresponding object
875     this->kerning_pair = SP_HKERN( document->getObjectByRepr(repr) );
876 
877     DocumentUndo::done(document, SP_VERB_DIALOG_SVG_FONTS, _("Add kerning pair"));
878 }
879 
kerning_tab()880 Gtk::Box* SvgFontsDialog::kerning_tab(){
881     _KerningPairsList.signal_button_release_event().connect_notify(sigc::mem_fun(*this, &SvgFontsDialog::kerning_pairs_list_button_release));
882     create_kerning_pairs_popup_menu(_KerningPairsList, sigc::mem_fun(*this, &SvgFontsDialog::remove_selected_kerning_pair));
883 
884 //Kerning Setup:
885     kerning_vbox.set_border_width(4);
886     kerning_vbox.set_spacing(4);
887     // kerning_vbox.add(*Gtk::manage(new Gtk::Label(_("Kerning Setup"))));
888     Gtk::Box* kerning_selector = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
889     kerning_selector->pack_start(*Gtk::manage(new Gtk::Label(_("1st Glyph:"))), false, false);
890     kerning_selector->pack_start(first_glyph, true, true, 4);
891     kerning_selector->pack_start(*Gtk::manage(new Gtk::Label(_("2nd Glyph:"))), false, false);
892     kerning_selector->pack_start(second_glyph, true, true, 4);
893     kerning_selector->pack_start(add_kernpair_button, true, true);
894     add_kernpair_button.set_label(_("Add pair"));
895     add_kernpair_button.signal_clicked().connect(sigc::mem_fun(*this, &SvgFontsDialog::add_kerning_pair));
896     _KerningPairsList.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &SvgFontsDialog::on_kerning_pair_selection_changed));
897     kerning_slider->signal_value_changed().connect(sigc::mem_fun(*this, &SvgFontsDialog::on_kerning_value_changed));
898 
899     kerning_vbox.pack_start(*kerning_selector, false,false);
900 
901     kerning_vbox.pack_start(_KerningPairsListScroller, true,true);
902     _KerningPairsListScroller.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
903     _KerningPairsListScroller.add(_KerningPairsList);
904     _KerningPairsListStore = Gtk::ListStore::create(_KerningPairsListColumns);
905     _KerningPairsList.set_model(_KerningPairsListStore);
906     _KerningPairsList.append_column(_("First Unicode range"), _KerningPairsListColumns.first_glyph);
907     _KerningPairsList.append_column(_("Second Unicode range"), _KerningPairsListColumns.second_glyph);
908 //    _KerningPairsList.append_column_numeric_editable(_("Kerning Value"), _KerningPairsListColumns.kerning_value, "%f");
909 
910     kerning_vbox.pack_start((Gtk::Widget&) kerning_preview, false,false);
911 
912     // kerning_slider has a big handle. Extra padding added
913     Gtk::Box* kerning_amount_hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 8));
914     kerning_vbox.pack_start(*kerning_amount_hbox, false,false);
915     kerning_amount_hbox->pack_start(*Gtk::manage(new Gtk::Label(_("Kerning Value:"))), false,false);
916     kerning_amount_hbox->pack_start(*kerning_slider, true,true);
917 
918     kerning_preview.set_size(300 + 20, 150 + 20);
919     _font_da.set_size(300 + 50 + 20, 60 + 20);
920 
921     return &kerning_vbox;
922 }
923 
new_font(SPDocument * document)924 SPFont *new_font(SPDocument *document)
925 {
926     g_return_val_if_fail(document != nullptr, NULL);
927 
928     SPDefs *defs = document->getDefs();
929 
930     Inkscape::XML::Document *xml_doc = document->getReprDoc();
931 
932     // create a new font
933     Inkscape::XML::Node *repr = xml_doc->createElement("svg:font");
934 
935     //By default, set the horizontal advance to 1024 units
936     repr->setAttribute("horiz-adv-x", "1024");
937 
938     // Append the new font node to defs
939     defs->getRepr()->appendChild(repr);
940 
941     //create a missing glyph
942     Inkscape::XML::Node *fontface;
943     fontface = xml_doc->createElement("svg:font-face");
944     fontface->setAttribute("units-per-em", "1024");
945     repr->appendChild(fontface);
946 
947     //create a missing glyph
948     Inkscape::XML::Node *mg;
949     mg = xml_doc->createElement("svg:missing-glyph");
950     mg->setAttribute("d", "M0,0h1000v1024h-1000z");
951     repr->appendChild(mg);
952 
953     // get corresponding object
954     SPFont *f = SP_FONT( document->getObjectByRepr(repr) );
955 
956     g_assert(f != nullptr);
957     g_assert(SP_IS_FONT(f));
958     Inkscape::GC::release(mg);
959     Inkscape::GC::release(repr);
960     return f;
961 }
962 
set_font_family(SPFont * font,char * str)963 void set_font_family(SPFont* font, char* str){
964     if (!font) return;
965     for (auto& obj: font->children) {
966         if (SP_IS_FONTFACE(&obj)){
967             //XML Tree being directly used here while it shouldn't be.
968             obj.setAttribute("font-family", str);
969         }
970     }
971 
972     DocumentUndo::done(font->document, SP_VERB_DIALOG_SVG_FONTS, _("Set font family"));
973 }
974 
add_font()975 void SvgFontsDialog::add_font(){
976     SPDocument* doc = this->getDesktop()->getDocument();
977     SPFont* font = new_font(doc);
978 
979     const int count = _model->children().size();
980     std::ostringstream os, os2;
981     os << _("font") << " " << count;
982     font->setLabel(os.str().c_str());
983 
984     os2 << "SVGFont " << count;
985     for (auto& obj: font->children) {
986         if (SP_IS_FONTFACE(&obj)){
987             //XML Tree being directly used here while it shouldn't be.
988             obj.setAttribute("font-family", os2.str());
989         }
990     }
991 
992     update_fonts();
993 //    select_font(font);
994 
995     DocumentUndo::done(doc, SP_VERB_DIALOG_SVG_FONTS, _("Add font"));
996 }
997 
SvgFontsDialog()998 SvgFontsDialog::SvgFontsDialog()
999  : DialogBase("/dialogs/svgfonts", SP_VERB_DIALOG_SVG_FONTS)
1000  , _add(_("_New"), true)
1001  , _font_settings(Gtk::ORIENTATION_VERTICAL)
1002  , global_vbox(Gtk::ORIENTATION_VERTICAL)
1003  , glyphs_vbox(Gtk::ORIENTATION_VERTICAL)
1004  , kerning_vbox(Gtk::ORIENTATION_VERTICAL)
1005 {
1006     kerning_slider = Gtk::manage(new Gtk::Scale(Gtk::ORIENTATION_HORIZONTAL));
1007     _add.signal_clicked().connect(sigc::mem_fun(*this, &SvgFontsDialog::add_font));
1008 
1009     Gtk::Box* hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
1010     Gtk::Box* vbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
1011 
1012     vbox->pack_start(_FontsList);
1013     vbox->pack_start(_add, false, false);
1014     hbox->add(*vbox);
1015     hbox->add(_font_settings);
1016     add(*hbox);
1017 
1018     // List of SVGFonts declared in a document:
1019     _model = Gtk::ListStore::create(_columns);
1020     _FontsList.set_model(_model);
1021     _FontsList.append_column_editable(_("_Fonts"), _columns.label);
1022     _FontsList.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &SvgFontsDialog::on_font_selection_changed));
1023 
1024     Gtk::Notebook *tabs = Gtk::manage(new Gtk::Notebook());
1025     tabs->set_scrollable();
1026 
1027     tabs->append_page(*global_settings_tab(), _("_Global Settings"), true);
1028     tabs->append_page(*glyphs_tab(), _("_Glyphs"), true);
1029     tabs->append_page(*kerning_tab(), _("_Kerning"), true);
1030 
1031     _font_settings.add(*tabs);
1032 
1033     // Text Preview:
1034     _preview_entry.signal_changed().connect(sigc::mem_fun(*this, &SvgFontsDialog::on_preview_text_changed));
1035     pack_start((Gtk::Widget&) _font_da, false, false);
1036     _preview_entry.set_text(_("Sample Text"));
1037     _font_da.set_text(_("Sample Text"));
1038 
1039     Gtk::Box* preview_entry_hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4));
1040     pack_start(*preview_entry_hbox, false, false); // Non-latin characters may need more height.
1041     preview_entry_hbox->pack_start(*Gtk::manage(new Gtk::Label(_("Preview Text:"))), false, false);
1042     preview_entry_hbox->pack_start(_preview_entry, true, true);
1043 
1044     _FontsList.signal_button_release_event().connect_notify(sigc::mem_fun(*this, &SvgFontsDialog::fonts_list_button_release));
1045     create_fonts_popup_menu(_FontsList, sigc::mem_fun(*this, &SvgFontsDialog::remove_selected_font));
1046 
1047     show_all();
1048 }
1049 
1050 SvgFontsDialog::~SvgFontsDialog()= default;
1051 
update()1052 void SvgFontsDialog::update()
1053 {
1054     if (!_app) {
1055         std::cerr << "SvgFontsDialog::update(): _app is null" << std::endl;
1056         return;
1057     }
1058 
1059     SPDesktop *desktop = getDesktop();
1060 
1061     if (!desktop) {
1062         return;
1063     }
1064 
1065     _defs_observer_connection.disconnect();
1066 
1067     _defs_observer.set(desktop->getDocument()->getDefs());
1068     _defs_observer_connection =
1069         _defs_observer.signal_changed().connect(sigc::mem_fun(*this, &SvgFontsDialog::update_fonts));
1070 
1071     update_fonts();
1072 }
1073 
1074 } // namespace Dialog
1075 } // namespace UI
1076 } // namespace Inkscape
1077 
1078 /*
1079   Local Variables:
1080   mode:c++
1081   c-file-style:"stroustrup"
1082   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1083   indent-tabs-mode:nil
1084   fill-column:99
1085   End:
1086 */
1087 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
1088