Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
svg-fonts-dialog.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
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 "svg-fonts-dialog.h"
16
17#include <algorithm>
18#include <exception>
19#include <iomanip>
20#include <sstream>
21#include <utility>
22#include <glibmm/i18n.h>
23#include <glibmm/markup.h>
24#include <glibmm/stringutils.h>
25#include <giomm/themedicon.h>
26#include <gtkmm/expander.h>
27#include <gtkmm/image.h>
28#include <gtkmm/liststore.h>
29#include <gtkmm/notebook.h>
30#include <gtkmm/scale.h>
31#include <gtkmm/snapshot.h>
32#include <gtkmm/togglebutton.h>
33#include <sigc++/functors/mem_fun.h>
34
35#include "desktop.h"
36#include "document.h"
37#include "document-undo.h"
38#include "layer-manager.h"
39#include "message-stack.h"
40#include "selection.h"
41#include "display/nr-svgfonts.h"
42#include "object/sp-defs.h"
43#include "object/sp-font-face.h"
44#include "object/sp-font.h"
46#include "object/sp-glyph.h"
47#include "object/sp-guide.h"
49#include "object/sp-path.h"
50#include "svg/svg.h"
52#include "ui/pack.h"
53#include "ui/util.h"
56#include "util/units.h"
57#include "xml/repr.h"
58
60{
61 set_name("SVGFontDrawingArea");
62 set_draw_func(sigc::mem_fun(*this, &SvgFontDrawingArea::draw_func));
63}
64
66 _svgfont = svgfont;
67}
68
69void SvgFontDrawingArea::set_text(Glib::ustring text){
70 _text = std::move(text);
71 redraw();
72}
73
75 _x = x;
76 _y = y;
77 set_size_request(_x, _y);
78}
79
81 queue_draw();
82}
83
84void SvgFontDrawingArea::draw_func(Cairo::RefPtr<Cairo::Context> const &cr,
85 int /*width*/, int /*height*/)
86{
87 if (!_svgfont) return;
88
89 cr->set_font_face( Cairo::RefPtr<Cairo::FontFace>(new Cairo::FontFace(_svgfont->get_font_face(), false /* does not have reference */)) );
90 cr->set_font_size (_y-20);
91 cr->move_to (10, 10);
92 auto const fg = get_color();
93 cr->set_source_rgb(fg.get_red(), fg.get_green(), fg.get_blue());
94 // crash on macos: https://gitlab.com/inkscape/inkscape/-/issues/266
95 try {
96 cr->show_text(_text.c_str());
97 } catch (std::exception const &ex) {
98 g_warning("Error drawing custom SVG font text: %s", ex.what());
99 }
100}
101
102namespace Inkscape::UI::Dialog {
103
105 Glib::RefPtr<Gtk::Snapshot> const &snapshot, Gtk::Widget& widget,
106 const Gdk::Rectangle& background_area, const Gdk::Rectangle& cell_area, Gtk::CellRendererState flags) {
107
108 if (!_font || !_tree) return;
109
110 auto const cr = snapshot->append_cairo(background_area);
111
112 cr->set_font_face(Cairo::RefPtr<Cairo::FontFace>(new Cairo::FontFace(_font->get_font_face(), false /* does not have reference */)));
113 cr->set_font_size(_font_size);
114 Glib::ustring glyph = _property_glyph.get_value();
115 Cairo::TextExtents ext;
116 cr->get_text_extents(glyph, ext);
117 cr->move_to(cell_area.get_x() + (cell_area.get_width() - ext.width) / 2, cell_area.get_y() + (cell_area.get_height() + ext.height) / 2);
118
119 auto const selected = (flags & Gtk::CellRendererState::SELECTED) != Gtk::CellRendererState{};
120 Gdk::RGBA fg;
121 if (!_tree->get_style_context()->lookup_color(selected ? "theme_selected_fg_color" : "theme_fg_color", fg)) {
122 // some themes don't have standard color names defined
123 fg = Gdk::RGBA{"#000"};
124 }
125 cr->set_source_rgb(fg.get_red(), fg.get_green(), fg.get_blue());
126
127 // crash on macos: https://gitlab.com/inkscape/inkscape/-/issues/266
128 try {
129 cr->show_text(glyph);
130 } catch (std::exception const &ex) {
131 g_warning("Error drawing custom SVG font glyphs: %s", ex.what());
132 }
133}
134
136 Glib::RefPtr<Gdk::Event const> const &event,
137 Gtk::Widget &widget, Glib::ustring const &path, Gdk::Rectangle const &background_area,
138 const Gdk::Rectangle& cell_area, Gtk::CellRendererState flags) {
139
140 Glib::ustring glyph = _property_glyph.get_value();
141 _signal_clicked.emit(event, glyph);
142 return false;
143}
144
145SvgFontsDialog::AttrEntry::AttrEntry(SvgFontsDialog* d, gchar* lbl, Glib::ustring tooltip, const SPAttr attr)
146{
147 this->dialog = d;
148 this->attr = attr;
149 entry.set_tooltip_text(tooltip);
150 _label = Gtk::make_managed<Gtk::Label>(lbl);
151 _label->set_visible(true);
152 _label->set_halign(Gtk::Align::START);
153 entry.signal_changed().connect(sigc::mem_fun(*this, &SvgFontsDialog::AttrEntry::on_attr_changed));
154}
155
157 if (!t) return;
158 entry.set_text(t);
159}
160
161// 'font-family' has a problem as it is also a presentation attribute for <text>
163 if (dialog->_update.pending()) return;
164
165 SPObject* o = nullptr;
166 for (auto& node: dialog->get_selected_spfont()->children) {
167 switch(this->attr){
169 if (is<SPFontFace>(&node)){
170 o = &node;
171 continue;
172 }
173 break;
174 default:
175 o = nullptr;
176 }
177 }
178
179 const gchar* name = (const gchar*)sp_attribute_name(this->attr);
180 if(name && o) {
181 o->setAttribute((const gchar*) name, this->entry.get_text());
182 o->parent->requestModified(SP_OBJECT_MODIFIED_FLAG);
183
184 Glib::ustring undokey = "svgfonts:";
185 undokey += name;
186 DocumentUndo::maybeDone(o->document, undokey.c_str(), _("Set SVG Font attribute"), "");
187 }
188
189}
190
191SvgFontsDialog::AttrSpin::AttrSpin(SvgFontsDialog* d, gchar* lbl, Glib::ustring tooltip, const SPAttr attr)
192{
193 this->dialog = d;
194 this->attr = attr;
195 spin.set_tooltip_text(tooltip);
196 spin.set_visible(true);
197 _label = Gtk::make_managed<Gtk::Label>(lbl);
198 _label->set_visible(true);
199 _label->set_halign(Gtk::Align::START);
200 spin.set_range(0, 4096);
201 spin.set_increments(10, 0);
202 spin.signal_value_changed().connect(sigc::mem_fun(*this, &SvgFontsDialog::AttrSpin::on_attr_changed));
203}
204
205void SvgFontsDialog::AttrSpin::set_range(double low, double high){
206 spin.set_range(low, high);
207}
208
210 spin.set_value(v);
211}
212
214 if (dialog->_update.pending()) return;
215
216 SPObject* o = nullptr;
217 switch (this->attr) {
218
219 // <font> attributes
226 o = this->dialog->get_selected_spfont();
227 break;
228
229 // <font-face> attributes
231 case SPAttr::ASCENT:
232 case SPAttr::DESCENT:
234 case SPAttr::X_HEIGHT:
235 for (auto& node: dialog->get_selected_spfont()->children){
236 if (is<SPFontFace>(&node)){
237 o = &node;
238 continue;
239 }
240 }
241 break;
242
243 default:
244 o = nullptr;
245 }
246
247 const gchar* name = (const gchar*)sp_attribute_name(this->attr);
248 if(name && o) {
249 std::ostringstream temp;
250 temp << this->spin.get_value();
251 o->setAttribute(name, temp.str());
252 o->parent->requestModified(SP_OBJECT_MODIFIED_FLAG);
253
254 Glib::ustring undokey = "svgfonts:";
255 undokey += name;
256 DocumentUndo::maybeDone(o->document, undokey.c_str(), _("Set SVG Font attribute"), "");
257 }
258
259}
260
261Gtk::Box* SvgFontsDialog::AttrCombo(gchar* lbl, const SPAttr /*attr*/){
262 auto const hbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
263 hbox->append(*Gtk::make_managed<Gtk::Label>(lbl));
264 hbox->append(*Gtk::make_managed<Gtk::ComboBox>());
265 return hbox;
266}
267
269 : _menu{std::make_unique<UI::Widget::PopoverMenu>(Gtk::PositionType::BOTTOM)}
270{
271 _label.set_width_chars(2);
272
273 auto const arrow = Gtk::make_managed<Gtk::Image>(Gio::ThemedIcon::create("pan-down-symbolic"));
274 arrow->add_css_class("arrow");
275
276 auto const box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 2);
277 box->append(_label);
278 box->append(*arrow);
279 set_child(*box);
280
281 set_popover(*_menu);
282}
283
285
286void GlyphMenuButton::update(SPFont const * const spfont)
287{
288 set_sensitive(false);
289 _label.set_label({});
290 _menu->remove_all();
291
292 if (!spfont) return;
293
294 auto const &font_nodes = spfont->children;
295 if (font_nodes.empty()) return;
296
297 // TODO: GTK4: probably nicer to use GtkGridView.
299 Glib::ustring active_label;
300
301 for (auto const &node: font_nodes) {
302 if (!is<SPGlyph>(&node)) continue;
303
304 Glib::ustring text = static_cast<SPGlyph const *>(&node)->unicode;
305 if (text.empty()) continue;
306
307 builder.add_item(text, {}, {}, true, false,
308 [=, this]{ _label.set_label(text); });
309 if (active_label.empty()) active_label = std::move(text);
310 }
311
312 set_sensitive(true);
313 _label.set_label(active_label);
314}
315
317{
318 return _label.get_label();
319}
320
323 return;
324 }
325
326 //TODO: I am unsure whether this is the correct way of calling SPDocumentUndo::maybe_done
327 Glib::ustring undokey = "svgfonts:hkern:k:";
328 undokey += this->kerning_pair->u1->attribute_string();
329 undokey += ":";
330 undokey += this->kerning_pair->u2->attribute_string();
331
332 //slider values increase from right to left so that they match the kerning pair preview
333
334 //XML Tree being directly used here while it shouldn't be.
335 this->kerning_pair->setAttribute("k", Glib::Ascii::dtostr(get_selected_spfont()->horiz_adv_x - kerning_slider->get_value()));
336 DocumentUndo::maybeDone(getDocument(), undokey.c_str(), _("Adjust kerning value"), "");
337
338 //populate_kerning_pairs_box();
341}
342
344 if (!font) return;
345
346 {
347 auto scoped(_update.block());
348 font->sort_glyphs();
349 }
351}
352
353// return U+<code> ... string
354Glib::ustring create_unicode_name(const Glib::ustring& unicode, int max_chars) {
355 std::ostringstream ost;
356 if (unicode.empty()) {
357 ost << "-";
358 }
359 else {
360 auto it = unicode.begin();
361 for (int i = 0; i < max_chars && it != unicode.end(); ++i) {
362 if (i > 0) {
363 ost << " ";
364 }
365 unsigned int code = *it++;
366 ost << "U+" << std::hex << std::uppercase << std::setw(6) << std::setfill('0') << code;
367 }
368 if (it != unicode.end()) {
369 ost << "..."; // there's more, but we skip them
370 }
371 }
372 return ost.str();
373}
374
375// synthetic name consists for unicode hex numbers derived from glyph's "unicode" attribute
376Glib::ustring get_glyph_synthetic_name(const SPGlyph& glyph) {
377 auto unicode_name = create_unicode_name(glyph.unicode, 3);
378 // U+<code> plus character
379 return unicode_name + " " + glyph.unicode;
380}
381
382// full name consists of user-defined name combined with synthetic one
383Glib::ustring get_glyph_full_name(const SPGlyph& glyph) {
384 auto name = get_glyph_synthetic_name(glyph);
385 if (!glyph.glyph_name.empty()) {
386 // unicode name first, followed by user name - for sorting layers
387 return name + " " + glyph.glyph_name;
388 }
389 else {
390 return name;
391 }
392}
393
394// look for a layer by its label; looking only in direct sublayers of 'root_layer'
395SPItem* find_layer(SPDesktop* desktop, SPObject* root_layer, const Glib::ustring& name) {
396 if (!desktop) return nullptr;
397
398 const auto& layers = desktop->layerManager();
399 auto root = root_layer == nullptr ? layers.currentRoot() : root_layer;
400 if (!root) return nullptr;
401
402 // check only direct child layers
403 auto it = std::find_if(root->children.begin(), root->children.end(), [&](SPObject& obj) {
404 return layers.isLayer(&obj) && obj.label() && strcmp(obj.label(), name.c_str()) == 0;
405 });
406 if (it != root->children.end()) {
407 return static_cast<SPItem*>(&*it);
408 }
409
410 return nullptr; // not found
411}
412
413std::vector<SPGroup*> get_direct_sublayers(SPObject* layer) {
414 std::vector<SPGroup*> layers;
415 if (!layer) return layers;
416
417 for (auto&& item : layer->children) {
418 if (auto l = LayerManager::asLayer(&item)) {
419 layers.push_back(l);
420 }
421 }
422
423 return layers;
424}
425
426void rename_glyph_layer(SPDesktop* desktop, SPItem* layer, const Glib::ustring& font, const Glib::ustring& name) {
427 if (!desktop || !layer || font.empty() || name.empty()) return;
428
429 auto parent_layer = find_layer(desktop, desktop->layerManager().currentRoot(), font);
430 if (!parent_layer) return;
431
432 // before renaming the layer find new place to move it into to keep sorted order intact
433 auto glyph_layers = get_direct_sublayers(parent_layer);
434
435 auto it = std::lower_bound(glyph_layers.rbegin(), glyph_layers.rend(), name, [&](auto&& layer, const Glib::ustring n) {
436 auto label = layer->label();
437 if (!label) return false;
438
439 Glib::ustring temp(label);
440 return std::lexicographical_compare(temp.begin(), temp.end(), n.begin(), n.end());
441 });
442 SPObject* after = nullptr;
443 if (it != glyph_layers.rend()) {
444 after = *it;
445 }
446
447 // SPItem changeOrder messes up inserting into first position, so dropping to Node level
448 if (layer != after && parent_layer->getRepr() && layer->getRepr()) {
449 parent_layer->getRepr()->changeOrder(layer->getRepr(), after ? after->getRepr() : nullptr);
450 }
451
452 desktop->layerManager().renameLayer(layer, name.c_str(), false);
453}
454
455SPItem* get_layer_for_glyph(SPDesktop* desktop, const Glib::ustring& font, const Glib::ustring& name) {
456 if (!desktop || name.empty() || font.empty()) return nullptr;
457
458 auto parent_layer = find_layer(desktop, desktop->layerManager().currentRoot(), font);
459 if (!parent_layer) return nullptr;
460
461 return find_layer(desktop, parent_layer, name);
462}
463
464SPItem* get_or_create_layer_for_glyph(SPDesktop* desktop, const Glib::ustring& font, const Glib::ustring& name) {
465 if (!desktop || name.empty() || font.empty()) return nullptr;
466
467 auto& layers = desktop->layerManager();
468 auto parent_layer = find_layer(desktop, layers.currentRoot(), font);
469 if (!parent_layer) {
470 // create a new layer for a font
471 parent_layer = static_cast<SPItem*>(create_layer(layers.currentRoot(), layers.currentRoot(), Inkscape::LayerRelativePosition::LPOS_CHILD));
472 if (!parent_layer) return nullptr;
473
474 layers.renameLayer(parent_layer, font.c_str(), false);
475 }
476
477 if (auto layer = find_layer(desktop, parent_layer, name)) {
478 return layer;
479 }
480
481 // find the right place for a new layer, so they appear sorted
482 auto glyph_layers = get_direct_sublayers(parent_layer);
483 // auto& glyph_layers = parent_layer->children;
484 auto it = std::lower_bound(glyph_layers.rbegin(), glyph_layers.rend(), name, [&](auto&& layer, const Glib::ustring n) {
485 auto label = layer->label();
486 if (!label) return false;
487
488 Glib::ustring temp(label);
489 return std::lexicographical_compare(temp.begin(), temp.end(), n.begin(), n.end());
490 });
491 SPObject* insert = parent_layer;
493 if (it != glyph_layers.rend()) {
494 insert = *it;
495 }
496 else {
497 // auto first = std::find_if(glyph_layers.begin(), glyph_layers.end(), [&](auto&& obj) {
498 // return layers.isLayer(&obj);
499 // });
500 if (!glyph_layers.empty()) {
501 insert = glyph_layers.front();
503 }
504 }
505
506 // create a new layer for a glyph
507 auto layer = create_layer(parent_layer, insert, pos);
508 if (!layer) return nullptr;
509
510 layers.renameLayer(layer, name.c_str(), false);
511
512 DocumentUndo::done(desktop->getDocument(), _("Add layer"), "");
513 return cast<SPItem>(layer);
514}
515
517 if (get_selected_spfont()){
518 _grid.set_sensitive(true);
519 glyphs_vbox.set_sensitive(true);
520 kerning_vbox.set_sensitive(true);
521 } else {
522 _grid.set_sensitive(false);
523 glyphs_vbox.set_sensitive(false);
524 kerning_vbox.set_sensitive(false);
525 }
526}
527
528Glib::ustring get_font_label(SPFont* font) {
529 if (!font) return Glib::ustring();
530
531 const gchar* label = font->label();
532 const gchar* id = font->getId();
533 return Glib::ustring(label ? label : (id ? id : "font"));
534};
535
540void SvgFontsDialog::update_fonts(bool document_replaced)
541{
542 std::vector<SPObject*> fonts;
543 if (auto document = getDocument()) {
544 fonts = document->getResourceList( "font" );
545 }
546
547 auto children = _model->children();
548 bool equal = false;
549 bool selected_font = false;
550
551 // compare model and resources
552 if (!document_replaced && children.size() == fonts.size()) {
553 equal = true; // assume they are the same
554 auto it = fonts.begin();
555 for (auto&& node : children) {
556 SPFont* sp_font = node[_columns.spfont];
557 if (it == fonts.end() || *it != sp_font) {
558 // difference detected; update model
559 equal = false;
560 break;
561 }
562 ++it;
563 }
564 }
565
566 // rebuild model if list of fonts is different
567 if (!equal) {
568 _model->clear();
569 for (auto font : fonts) {
570 Gtk::TreeModel::Row row = *_model->append();
571 auto f = cast<SPFont>(font);
572 row[_columns.spfont] = f;
573 row[_columns.svgfont] = new SvgFont(f);
574 row[_columns.label] = get_font_label(f);
575 }
576 if (!fonts.empty()) {
577 // select a font, this dialog is disabled without a font
578 auto selection = _FontsList.get_selection();
579 if (selection) {
580 selection->select(_model->get_iter("0"));
581 selected_font = true;
582 }
583 }
584 }
585 else {
586 // list of fonts is the same, but attributes may have changed
587 auto it = fonts.begin();
588 for (auto&& node : children) {
589 if (auto font = cast<SPFont>(*it++)) {
591 }
592 }
593 }
594
595 if (document_replaced && !selected_font) {
596 // replace fonts, they are stale
597 font_selected(nullptr, nullptr);
598 }
599 else {
601 }
602}
603
607
610 if (!kern) {
612 return;
613 }
614 Glib::ustring str;
615 str += kern->u1->sample_glyph();
616 str += kern->u2->sample_glyph();
617
619 this->kerning_pair = kern;
620
621 //slider values increase from right to left so that they match the kerning pair preview
622 kerning_slider->set_value(get_selected_spfont()->horiz_adv_x - kern->k);
623}
624
626 SPFont* font = get_selected_spfont();
627 if (!font) {
628 //TODO: perhaps reset all values when there's no font
629 _familyname_entry->set_text("");
630 return;
631 }
632
633 _horiz_adv_x_spin->set_value(font->horiz_adv_x);
634 _horiz_origin_x_spin->set_value(font->horiz_origin_x);
635 _horiz_origin_y_spin->set_value(font->horiz_origin_y);
636
637 for (auto& obj: font->children) {
638 if (is<SPFontFace>(&obj)){
639 _familyname_entry->set_text((cast<SPFontFace>(&obj))->font_family);
640 _units_per_em_spin->set_value((cast<SPFontFace>(&obj))->units_per_em);
641 _ascent_spin->set_value((cast<SPFontFace>(&obj))->ascent);
642 _descent_spin->set_value((cast<SPFontFace>(&obj))->descent);
643 _x_height_spin->set_value((cast<SPFontFace>(&obj))->x_height);
644 _cap_height_spin->set_value((cast<SPFontFace>(&obj))->cap_height);
645 }
646 }
647}
648
650 // in update
651 auto scoped(_update.block());
652
653 first_glyph.update(spfont);
654 second_glyph.update(spfont);
656 _font_da.set_svgfont(svgfont);
660
661 kerning_slider->set_range(0, spfont ? spfont->horiz_adv_x : 0);
662 kerning_slider->set_draw_value(false);
663 kerning_slider->set_value(0);
664
669}
670
672 SPFont* spfont = get_selected_spfont();
673 SvgFont* svgfont = get_selected_svgfont();
674 font_selected(svgfont, spfont);
675}
676
678{
679 Gtk::TreeModel::iterator i = _KerningPairsList.get_selection()->get_selected();
680 if(i)
681 return (*i)[_KerningPairsListColumns.spnode];
682 return nullptr;
683}
684
686{
687 Gtk::TreeModel::iterator i = _FontsList.get_selection()->get_selected();
688 if(i)
689 return (*i)[_columns.svgfont];
690 return nullptr;
691}
692
694{
695 Gtk::TreeModel::iterator i = _FontsList.get_selection()->get_selected();
696 if(i)
697 return (*i)[_columns.spfont];
698 return nullptr;
699}
700
701Gtk::TreeModel::iterator SvgFontsDialog::get_selected_glyph_iter() {
702 if (_GlyphsListScroller.get_visible()) {
703 if (auto selection = _GlyphsList.get_selection()) {
704 Gtk::TreeModel::iterator it = selection->get_selected();
705 return it;
706 }
707 }
708 else {
709 std::vector<Gtk::TreePath> selected = _glyphs_grid.get_selected_items();
710 if (selected.size() == 1) {
711 Gtk::ListStore::iterator it = _GlyphsListStore->get_iter(selected.front());
712 return it;
713 }
714 }
715 return Gtk::TreeModel::iterator();
716}
717
719{
720 if (auto it = get_selected_glyph_iter()) {
721 return (*it)[_GlyphsListColumns.glyph_node];
722 }
723 return nullptr;
724}
725
727 if (!glyph) return;
728
729 _GlyphsListStore->foreach_iter([=, this](Gtk::TreeModel::iterator const &it) {
730 if (it->get_value(_GlyphsListColumns.glyph_node) == glyph) {
731 if (auto selection = _GlyphsList.get_selection()) {
732 selection->select(it);
733 }
734 auto selected_item = _GlyphsListStore->get_path(it);
735 _glyphs_grid.select_path(selected_item);
736 return true; // stop
737 }
738 return false; // continue
739 });
740}
741
742SPGuide* get_guide(SPDocument& doc, const Glib::ustring& id) {
743 auto object = doc.getObjectById(id);
744 if (!object) return nullptr;
745
746 // get guide line
747 if (auto guide = cast<SPGuide>(object)) {
748 return guide;
749 }
750
751 // remove colliding object
752 object->deleteObject();
753 return nullptr;
754}
755
756SPGuide* create_guide(SPDocument& doc, double x0, double y0, double x1, double y1) {
757 return SPGuide::createSPGuide(&doc, Geom::Point(x0, y1), Geom::Point(x1, y1));
758}
759
760void set_up_typography_canvas(SPDocument* document, double em, double asc, double cap, double xheight, double des) {
761 if (!document || em <= 0) return;
762
763 // set size and viewbox
764 auto size = Inkscape::Util::Quantity(em, "px");
765 bool change_size = false;
766 document->setWidthAndHeight(size, size, change_size);
767 document->setViewBox(Geom::Rect::from_xywh(0, 0, em, em));
768
769 // baseline
770 double base = des;
771 double ascPos = base + asc;
772 double capPos = base + cap;
773 double xPos = base + xheight;
774 double desPos = base - des;
775
776 if (!document->is_yaxisdown()) {
777 base = size.quantity - des;
778 ascPos = base - asc;
779 capPos = base - cap;
780 xPos = base - xheight;
781 desPos = base + des;
782 }
783
784 // add/move guide lines
785 struct { double pos; const char* name; const char* id; } guides[5] = {
786 {ascPos, _("ascender"), "ink-font-guide-ascender"},
787 {capPos, _("caps"), "ink-font-guide-caps"},
788 {xPos, _("x-height"), "ink-font-guide-x-height"},
789 {base, _("baseline"), "ink-font-guide-baseline"},
790 {desPos, _("descender"), "ink-font-guide-descender"},
791 };
792
793 double left = 0;
794 double right = em;
795
796 for (auto&& g : guides) {
797 double y = em - g.pos;
798 auto guide = get_guide(*document, g.id);
799 if (guide) {
800 guide->set_locked(false, true);
801 guide->moveto(Geom::Point(left, y), true);
802 }
803 else {
804 guide = create_guide(*document, left, y, right, y);
805 guide->getRepr()->setAttributeOrRemoveIfEmpty("id", g.id);
806 }
807 guide->set_label(g.name, true);
808 guide->set_locked(true, true);
809 }
810
811 DocumentUndo::done(document, _("Set up typography canvas"), "");
812}
813
814const int MARGIN_SPACE = 4;
815
816Gtk::Box* SvgFontsDialog::global_settings_tab(){
817 _fonts_scroller.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::AUTOMATIC);
818 _fonts_scroller.set_child(_FontsList);
819 _fonts_scroller.set_hexpand();
820 _fonts_scroller.set_visible(true);
821
822 _header_box.set_column_spacing(MARGIN_SPACE);
823 _header_box.set_row_spacing(MARGIN_SPACE);
824 _header_box.attach(_fonts_scroller, 0, 0, 1, 3);
825 _header_box.attach(*Gtk::make_managed<Gtk::Label>(), 1, 0);
826 _header_box.attach(_font_add, 1, 1);
827 _header_box.attach(_font_remove, 1, 2);
828 _header_box.set_margin_bottom(MARGIN_SPACE);
829 _header_box.set_margin_end(MARGIN_SPACE);
830
831 _font_add.set_valign(Gtk::Align::CENTER);
832 _font_add.set_image_from_icon_name("list-add", Gtk::IconSize::NORMAL);
833 _font_add.signal_clicked().connect(sigc::mem_fun(*this, &SvgFontsDialog::add_font));
834
835 _font_remove.set_valign(Gtk::Align::CENTER);
836 _font_remove.set_halign(Gtk::Align::CENTER);
837 _font_remove.set_image_from_icon_name("list-remove", Gtk::IconSize::NORMAL);
838 _font_remove.signal_clicked().connect([this]{ remove_selected_font(); });
839
840 global_vbox.set_name("SVGFontsGlobalSettingsTab");
841 UI::pack_start(global_vbox, _header_box, false, false);
842
843 _font_label = Gtk::make_managed<Gtk::Label>(Glib::ustring::compose("<b>%1</b>", _("Font Attributes")), Gtk::Align::START, Gtk::Align::CENTER);
844 _horiz_adv_x_spin = std::make_unique <AttrSpin >(this, _("Horizontal advance X:"), _("Default glyph width for horizontal text" ), SPAttr::HORIZ_ADV_X );
845 _horiz_origin_x_spin = std::make_unique <AttrSpin >(this, _("Horizontal origin X:" ), _("Default X-coordinate of the origin of a glyph (for horizontal text)"), SPAttr::HORIZ_ORIGIN_X);
846 _horiz_origin_y_spin = std::make_unique <AttrSpin >(this, _("Horizontal origin Y:" ), _("Default Y-coordinate of the origin of a glyph (for horizontal text)"), SPAttr::HORIZ_ORIGIN_Y);
847 _font_face_label = Gtk::make_managed<Gtk::Label>(Glib::ustring::compose("<b>%1</b>", _("Font face attributes")), Gtk::Align::START, Gtk::Align::CENTER);
848 _familyname_entry = std::make_unique <AttrEntry >(this, _("Family name:" ), _("Name of the font as it appears in font selectors and css font-family properties"), SPAttr::FONT_FAMILY );
849 _units_per_em_spin = std::make_unique <AttrSpin >(this, _("Em-size:" ), _("Display units per <italic>em</italic> (nominally width of 'M' character)" ), SPAttr::UNITS_PER_EM);
850 _ascent_spin = std::make_unique <AttrSpin >(this, _("Ascender:" ), _("Amount of space taken up by ascenders like the tall line on the letter 'h'" ), SPAttr::ASCENT );
851 _cap_height_spin = std::make_unique <AttrSpin >(this, _("Caps height:" ), _("The height of a capital letter above the baseline like the letter 'H' or 'I'" ), SPAttr::CAP_HEIGHT );
852 _x_height_spin = std::make_unique <AttrSpin >(this, _("x-height:" ), _("The height of a lower-case letter above the baseline like the letter 'x'" ), SPAttr::X_HEIGHT );
853 _descent_spin = std::make_unique <AttrSpin >(this, _("Descender:" ), _("Amount of space taken up by descenders like the tail on the letter 'g'" ), SPAttr::DESCENT );
854
855 _font_label->set_use_markup();
856 _font_face_label->set_use_markup();
857
858 _grid.set_column_spacing(MARGIN_SPACE);
859 _grid.set_row_spacing(MARGIN_SPACE);
860 _grid.set_margin_start(MARGIN_SPACE);
861 _grid.set_margin_bottom(MARGIN_SPACE);
862 const int indent = 2 * MARGIN_SPACE;
863 int row = 0;
864
865 _grid.attach(*_font_label, 0, row++, 2);
866 for (auto const spin: {_horiz_adv_x_spin.get(),
867 _horiz_origin_x_spin.get(), _horiz_origin_y_spin.get()})
868 {
869 spin->get_label()->set_margin_start(indent);
870 _grid.attach(*spin->get_label(), 0, row);
871 _grid.attach(*spin->getSpin(), 1, row++);
872 }
873
874 _grid.attach(*_font_face_label, 0, row++, 2);
875 _familyname_entry->get_label()->set_margin_start(indent);
876 _familyname_entry->get_entry()->set_margin_end(MARGIN_SPACE);
877 _grid.attach(*_familyname_entry->get_label(), 0, row);
878 _grid.attach(*_familyname_entry->get_entry(), 1, row++, 2);
879
880 for (auto const &spin : {_units_per_em_spin.get(), _ascent_spin.get(), _cap_height_spin.get(),
881 _x_height_spin.get(), _descent_spin.get()}) {
882 spin->get_label()->set_margin_start(indent);
883 _grid.attach(*spin->get_label(), 0, row);
884 _grid.attach(*spin->getSpin(), 1, row++);
885 }
886
887 auto const setup = Gtk::make_managed<Gtk::Button>(_("Set up canvas"));
888 _grid.attach(*setup, 0, row++, 2);
889 setup->set_halign(Gtk::Align::START);
890 setup->signal_clicked().connect([this]{
891 // set up typography canvas
893 getDocument(),
894 _units_per_em_spin->getSpin()->get_value(),
895 _ascent_spin->getSpin()->get_value(),
896 _cap_height_spin->getSpin()->get_value(),
897 _x_height_spin->getSpin()->get_value(),
898 _descent_spin->getSpin()->get_value()
899 );
900 });
901
902 global_vbox.set_margin(2);
903 UI::pack_start(global_vbox, _grid, false, true);
904 return &global_vbox;
905}
906
907void SvgFontsDialog::set_glyph_row(Gtk::TreeRow &row, SPGlyph &glyph)
908{
909 auto unicode_name = create_unicode_name(glyph.unicode, 3);
910 row[_GlyphsListColumns.glyph_node] = &glyph;
911 row[_GlyphsListColumns.glyph_name] = glyph.glyph_name;
912 row[_GlyphsListColumns.unicode] = glyph.unicode;
913 row[_GlyphsListColumns.UplusCode] = unicode_name;
914 row[_GlyphsListColumns.advance] = glyph.horiz_adv_x;
915 row[_GlyphsListColumns.name_markup] = "<small>" + Glib::Markup::escape_text(get_glyph_synthetic_name(glyph)) + "</small>";
916}
917
918void
919SvgFontsDialog::populate_glyphs_box()
920{
921 if (!_GlyphsListStore) return;
922
923 _GlyphsListStore->freeze_notify();
924
925 // try to keep selected glyph
926 Gtk::TreeModel::Path selected_item;
927 if (auto selected = get_selected_glyph_iter()) {
928 selected_item = _GlyphsListStore->get_path(selected);
929 }
930 _GlyphsListStore->clear();
931
932 SPFont* spfont = get_selected_spfont();
933 _glyphs_observer.set(spfont);
934
935 if (spfont) {
936 for (auto& node: spfont->children) {
937 if (is<SPGlyph>(&node)) {
938 auto& glyph = static_cast<SPGlyph&>(node);
939 auto row = *_GlyphsListStore->append();
940 set_glyph_row(row, glyph);
941 }
942 }
943
944 if (!selected_item.empty()) {
945 if (auto selection = _GlyphsList.get_selection()) {
946 selection->select(selected_item);
947 _GlyphsList.scroll_to_row(selected_item);
948 }
949 _glyphs_grid.select_path(selected_item);
950 }
951 }
952
953 _GlyphsListStore->thaw_notify();
954}
955
956void
957SvgFontsDialog::populate_kerning_pairs_box()
958{
959 if (!_KerningPairsListStore) return;
960
961 _KerningPairsListStore->clear();
962
963 if (SPFont* spfont = get_selected_spfont()) {
964 for (auto& node: spfont->children) {
965 if (is<SPHkern>(&node)){
966 Gtk::TreeModel::Row row = *(_KerningPairsListStore->append());
967 row[_KerningPairsListColumns.first_glyph] = (static_cast<SPGlyphKerning*>(&node))->u1->attribute_string().c_str();
968 row[_KerningPairsListColumns.second_glyph] = (static_cast<SPGlyphKerning*>(&node))->u2->attribute_string().c_str();
969 row[_KerningPairsListColumns.kerning_value] = (static_cast<SPGlyphKerning*>(&node))->k;
970 row[_KerningPairsListColumns.spnode] = static_cast<SPGlyphKerning*>(&node);
971 }
972 }
973 }
974}
975
976// update existing glyph in the tree model
977void SvgFontsDialog::update_glyph(SPGlyph* glyph) {
978 if (_update.pending() || !glyph) return;
979
980 _GlyphsListStore->foreach_iter([&](const Gtk::TreeModel::iterator& it) {
981 if (it->get_value(_GlyphsListColumns.glyph_node) == glyph) {
982 Gtk::TreeRow row = *it;
983 set_glyph_row(row, *glyph);
984 return true; // stop
985 }
986 return false; // continue
987 });
988}
989
990void SvgFontsDialog::update_glyphs(SPGlyph* changed_glyph) {
991 if (_update.pending()) return;
992
993 SPFont* font = get_selected_spfont();
994 if (!font) return;
995
996 if (changed_glyph) {
997 update_glyph(changed_glyph);
998 }
999 else {
1000 populate_glyphs_box();
1001 }
1002
1003 populate_kerning_pairs_box();
1004 refresh_svgfont();
1005}
1006
1007void SvgFontsDialog::refresh_svgfont() {
1008 if (auto font = get_selected_svgfont()) {
1009 font->refresh();
1010 }
1011 _font_da.redraw();
1012}
1013
1014void SvgFontsDialog::add_glyph(){
1015 auto document = getDocument();
1016 if (!document) return;
1017 auto font = get_selected_spfont();
1018 if (!font) return;
1019
1020 auto glyphs = _GlyphsListStore->children();
1021 // initialize "unicode" field; if there are glyphs look for the last one and take next unicode
1022 gunichar unicode = ' ';
1023 if (!glyphs.empty()) {
1024 const auto& last = glyphs[glyphs.size() - 1];
1025 if (SPGlyph* last_glyph = last[_GlyphsListColumns.glyph_node]) {
1026 const Glib::ustring& code = last_glyph->unicode;
1027 if (!code.empty()) {
1028 auto value = code[0];
1029 // skip control chars 7f-9f
1030 if (value == 0x7e) value = 0x9f;
1031 // wrap around
1032 if (value == 0x10ffff) value = 0x1f;
1033 unicode = value + 1;
1034 }
1035 }
1036 }
1037 auto str = Glib::ustring(1, unicode);
1038
1039 // empty name to begin with
1040 SPGlyph* glyph = font->create_new_glyph("", str.c_str());
1041 DocumentUndo::done(document, _("Add glyph"), "");
1042
1043 // select newly added glyph
1044 set_selected_glyph(glyph);
1045}
1046
1047double get_font_units_per_em(const SPFont* font) {
1048 double units_per_em = 0.0;
1049 if (font) {
1050 for (auto& obj: font->children) {
1051 if (is<SPFontFace>(&obj)){
1052 //XML Tree being directly used here while it shouldn't be.
1053 units_per_em = obj.getRepr()->getAttributeDouble("units-per-em", units_per_em);
1054 break;
1055 }
1056 }
1057 }
1058 return units_per_em;
1059}
1060
1061Geom::PathVector flip_coordinate_system(Geom::PathVector pathv, const SPFont* font, double units_per_em) {
1062 if (!font) return pathv;
1063
1064 if (units_per_em <= 0) {
1065 g_warning("Units per em not defined, path will be misplaced.");
1066 }
1067
1068 double baseline_offset = units_per_em - font->horiz_origin_y;
1069 // This matrix flips y-axis and places the origin at baseline
1070 Geom::Affine m(1, 0, 0, -1, 0, baseline_offset);
1071 return pathv * m;
1072}
1073
1074void SvgFontsDialog::set_glyph_description_from_selected_path() {
1075 auto font = get_selected_spfont();
1076 if (!font) return;
1077
1078 auto selection = getSelection();
1079 if (!selection)
1080 return;
1081
1082 Inkscape::MessageStack *msgStack = getDesktop()->messageStack();
1083 if (selection->isEmpty()){
1084 char *msg = _("Select a <b>path</b> to define the curves of a glyph");
1085 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
1086 return;
1087 }
1088
1089 Inkscape::XML::Node* node = selection->xmlNodes().front();
1090 if (!node) return;//TODO: should this be an assert?
1091 if (!node->matchAttributeName("d") || !node->attribute("d")){
1092 char *msg = _("The selected object does not have a <b>path</b> description.");
1093 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
1094 return;
1095 } //TODO: //Is there a better way to tell it to to the user?
1096
1097 SPGlyph* glyph = get_selected_glyph();
1098 if (!glyph){
1099 char *msg = _("No glyph selected in the SVGFonts dialog.");
1100 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
1101 return;
1102 }
1103
1105
1106 auto units_per_em = get_font_units_per_em(font);
1107 //XML Tree being directly used here while it shouldn't be.
1108 glyph->setAttribute("d", sp_svg_write_path(flip_coordinate_system(pathv, font, units_per_em)));
1109 DocumentUndo::done(getDocument(), _("Set glyph curves"), "");
1110
1111 update_glyphs(glyph);
1112}
1113
1114void SvgFontsDialog::missing_glyph_description_from_selected_path(){
1115 auto font = get_selected_spfont();
1116 if (!font) return;
1117
1118 auto selection = getSelection();
1119 if (!selection)
1120 return;
1121
1122 Inkscape::MessageStack *msgStack = getDesktop()->messageStack();
1123 if (selection->isEmpty()){
1124 char *msg = _("Select a <b>path</b> to define the curves of a glyph");
1125 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
1126 return;
1127 }
1128
1129 Inkscape::XML::Node* node = selection->xmlNodes().front();
1130 if (!node) return;//TODO: should this be an assert?
1131 if (!node->matchAttributeName("d") || !node->attribute("d")){
1132 char *msg = _("The selected object does not have a <b>path</b> description.");
1133 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
1134 return;
1135 } //TODO: //Is there a better way to tell it to the user?
1136
1138
1139 auto units_per_em = get_font_units_per_em(font);
1140 for (auto& obj: font->children) {
1141 if (is<SPMissingGlyph>(&obj)){
1142 //XML Tree being directly used here while it shouldn't be.
1143 obj.setAttribute("d", sp_svg_write_path(flip_coordinate_system(pathv, font, units_per_em)));
1144 DocumentUndo::done(getDocument(), _("Set glyph curves"), "");
1145 }
1146 }
1147
1148 refresh_svgfont();
1149}
1150
1151void SvgFontsDialog::reset_missing_glyph_description(){
1152 for (auto& obj: get_selected_spfont()->children) {
1153 if (is<SPMissingGlyph>(&obj)){
1154 //XML Tree being directly used here while it shouldn't be.
1155 obj.setAttribute("d", "M0,0h1000v1024h-1000z");
1156 DocumentUndo::done(getDocument(), _("Reset missing-glyph"), "");
1157 }
1158 }
1159 refresh_svgfont();
1160}
1161
1162void change_glyph_attribute(SPDesktop* desktop, SPGlyph& glyph, std::function<void ()> change) {
1163 assert(glyph.parent);
1164
1165 auto name = get_glyph_full_name(glyph);
1166 auto font_label = glyph.parent->label();
1167 auto layer = get_layer_for_glyph(desktop, font_label, name);
1168
1169 change();
1170
1171 if (!layer) return;
1172
1173 name = get_glyph_full_name(glyph);
1174 font_label = glyph.parent->label();
1175 rename_glyph_layer(desktop, layer, font_label, name);
1176}
1177
1178void SvgFontsDialog::glyph_name_edit(const Glib::ustring&, const Glib::ustring& str){
1179 SPGlyph* glyph = get_selected_glyph();
1180 if (!glyph) return;
1181
1182 if (glyph->glyph_name == str) return; // no change
1183
1184 change_glyph_attribute(getDesktop(), *glyph, [=, this]{
1185 //XML Tree being directly used here while it shouldn't be.
1186 glyph->setAttribute("glyph-name", str);
1187
1188 DocumentUndo::done(getDocument(), _("Edit glyph name"), "");
1189 update_glyphs(glyph);
1190 });
1191}
1192
1193void SvgFontsDialog::glyph_unicode_edit(const Glib::ustring&, const Glib::ustring& str){
1194 SPGlyph* glyph = get_selected_glyph();
1195 if (!glyph) return;
1196
1197 if (glyph->unicode == str) return; // no change
1198
1199 change_glyph_attribute(getDesktop(), *glyph, [=, this]{
1200 // XML Tree being directly used here while it shouldn't be.
1201 glyph->setAttribute("unicode", str);
1202
1203 DocumentUndo::done(getDocument(), _("Set glyph unicode"), "");
1204 update_glyphs(glyph);
1205 });
1206}
1207
1208void SvgFontsDialog::glyph_advance_edit(const Glib::ustring&, const Glib::ustring& str){
1209 SPGlyph* glyph = get_selected_glyph();
1210 if (!glyph) return;
1211
1212 if (auto val = glyph->getAttribute("horiz-adv-x")) {
1213 if (str == val) return; // no change
1214 }
1215
1216 //XML Tree being directly used here while it shouldn't be.
1217 std::istringstream is(str.raw());
1218 double value;
1219 // Check if input valid
1220 if ((is >> value)) {
1221 glyph->setAttribute("horiz-adv-x", str);
1222 DocumentUndo::done(getDocument(), _("Set glyph advance"), "");
1223
1224 update_glyphs(glyph);
1225 } else {
1226 std::cerr << "SvgFontDialog::glyph_advance_edit: Error in input: " << str.raw() << std::endl;
1227 }
1228}
1229
1230void SvgFontsDialog::remove_selected_font(){
1231 SPFont* font = get_selected_spfont();
1232 if (!font) return;
1233
1234 //XML Tree being directly used here while it shouldn't be.
1235 sp_repr_unparent(font->getRepr());
1236 DocumentUndo::done(getDocument(), _("Remove font"), "");
1237
1238 update_fonts(false);
1239}
1240
1241void SvgFontsDialog::remove_selected_glyph(){
1242 SPGlyph* glyph = get_selected_glyph();
1243 if (!glyph) return;
1244
1245 //XML Tree being directly used here while it shouldn't be.
1246 sp_repr_unparent(glyph->getRepr());
1247 DocumentUndo::done(getDocument(), _("Remove glyph"), "");
1248
1249 update_glyphs();
1250}
1251
1252void SvgFontsDialog::remove_selected_kerning_pair() {
1253 SPGlyphKerning* pair = get_selected_kerning_pair();
1254 if (!pair) return;
1255
1256 //XML Tree being directly used here while it shouldn't be.
1257 sp_repr_unparent(pair->getRepr());
1258 DocumentUndo::done(getDocument(), _("Remove kerning pair"), "");
1259
1260 update_glyphs();
1261}
1262
1265 auto path = glyph.document->getReprDoc()->createElement("svg:path");
1266 // auto path = new SPPath();
1267 auto font = cast<SPFont>(glyph.parent);
1268 auto units_per_em = get_font_units_per_em(font);
1269 path->setAttribute("d", sp_svg_write_path(flip_coordinate_system(pathv, font, units_per_em)));
1270 return path;
1271}
1272
1273// switch to a glyph layer (and create this dedicated layer if necessary)
1274void SvgFontsDialog::edit_glyph(SPGlyph* glyph) {
1275 if (!glyph || !glyph->parent) return;
1276
1277 auto desktop = getDesktop();
1278 if (!desktop) return;
1279 auto document = getDocument();
1280 if (!document) return;
1281
1282 // glyph's full name to match layer name
1283 auto name = get_glyph_full_name(*glyph);
1284 if (name.empty()) return;
1285 // font's name to match parent layer name
1286 auto font_label = get_font_label(cast<SPFont>(glyph->parent));
1287 if (font_label.empty()) return;
1288
1289 auto layer = get_or_create_layer_for_glyph(desktop, font_label, name);
1290 if (!layer) return;
1291
1292 // is layer empty?
1293 if (!layer->hasChildren()) {
1294 // since layer is empty try to initialize it by copying font glyph into it
1295 auto path = create_path_from_glyph(*glyph);
1296 if (path) {
1297 // layer->attach(path, nullptr);
1298 layer->addChild(path);
1299 }
1300 }
1301
1302 auto& layers = desktop->layerManager();
1303 // set layer as "solo" - only one visible and unlocked
1304 if (layers.isLayer(layer) && layer != layers.currentRoot()) {
1305 layers.setCurrentLayer(layer, true);
1306 layers.toggleLayerSolo(layer, true);
1307 layers.toggleLockOtherLayers(layer, true);
1308 DocumentUndo::done(document, _("Toggle layer solo"), "");
1309 }
1310}
1311
1312void SvgFontsDialog::set_glyphs_view_mode(bool list) {
1313 if (list) {
1314 _glyphs_icon_scroller.set_visible(false);
1315 _GlyphsListScroller.set_visible(true);
1316 }
1317 else {
1318 _GlyphsListScroller.set_visible(false);
1319 _glyphs_icon_scroller.set_visible(true);
1320 }
1321}
1322
1323Gtk::Box* SvgFontsDialog::glyphs_tab() {
1324
1325 glyphs_vbox.set_name("SVGFontsGlyphsTab");
1326 glyphs_vbox.set_margin(4);
1327 glyphs_vbox.set_spacing(4);
1328
1329 auto const missing_glyph_button = Gtk::make_managed<Gtk::Button>(_("From selection"));
1330 missing_glyph_button->set_margin_top(MARGIN_SPACE);
1331 missing_glyph_button->signal_clicked().connect(sigc::mem_fun(*this, &SvgFontsDialog::missing_glyph_description_from_selected_path));
1332
1333 auto const missing_glyph_reset_button = Gtk::make_managed<Gtk::Button>(_("Reset"));
1334 missing_glyph_reset_button->set_margin_top(MARGIN_SPACE);
1335 missing_glyph_reset_button->signal_clicked().connect(sigc::mem_fun(*this, &SvgFontsDialog::reset_missing_glyph_description));
1336
1337 auto const missing_glyph_hbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 4);
1338 missing_glyph_hbox->set_hexpand(false);
1339 UI::pack_start(*missing_glyph_hbox, *missing_glyph_button, false,false);
1340 UI::pack_start(*missing_glyph_hbox, *missing_glyph_reset_button, false,false);
1341
1342 auto const missing_glyph = Gtk::make_managed<Gtk::Expander>();
1343 missing_glyph->set_label(_("Missing glyph"));
1344 missing_glyph->set_child(*missing_glyph_hbox);
1345 missing_glyph->set_valign(Gtk::Align::CENTER);
1346
1347 _GlyphsListScroller.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::ALWAYS);
1348 _GlyphsListScroller.set_child(_GlyphsList);
1349 fix_inner_scroll(_GlyphsListScroller);
1350 _GlyphsList.set_model(_GlyphsListStore);
1351 _GlyphsList.set_enable_search(false);
1352
1353 _glyph_renderer = Gtk::make_managed<SvgGlyphRenderer>();
1354 const int size = 20; // arbitrarily chosen to keep glyphs small but still legible
1355 _glyph_renderer->set_font_size(size * 9 / 10);
1356 _glyph_renderer->set_cell_size(size * 3 / 2, size);
1357 _glyph_renderer->set_tree(&_GlyphsList);
1358 _glyph_renderer->signal_clicked().connect([this](Glib::RefPtr<Gdk::Event const> const &/*event*/,
1359 Glib::ustring const &unicodes)
1360 {
1361 // set preview: show clicked glyph only
1362 _preview_entry.set_text(unicodes);
1363 });
1364 auto col_index = _GlyphsList.append_column(_("Glyph"), *_glyph_renderer) - 1;
1365 if (auto column = _GlyphsList.get_column(col_index)) {
1366 column->add_attribute(_glyph_renderer->property_glyph(), _GlyphsListColumns.unicode);
1367 }
1368 _GlyphsList.append_column_editable(_("Name"), _GlyphsListColumns.glyph_name);
1369 _GlyphsList.append_column_editable(_("Characters"), _GlyphsListColumns.unicode);
1370 _GlyphsList.append_column(_("Unicode"), _GlyphsListColumns.UplusCode);
1371 _GlyphsList.append_column_numeric_editable(_("Advance"), _GlyphsListColumns.advance, "%.2f");
1372 _GlyphsList.set_visible(true);
1373 _GlyphsList.signal_row_activated().connect([this](Gtk::TreeModel::Path const &path, Gtk::TreeViewColumn*) {
1374 edit_glyph(get_selected_glyph());
1375 });
1376
1377 auto const glyph_from_path_button = Gtk::make_managed<Gtk::Button>();
1378 auto box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 2);
1379 box->append(*Gtk::make_managed<Gtk::Image>(Gio::ThemedIcon::create("glyph-copy-from")));
1380 box->append(*Gtk::make_managed<Gtk::Label>(_("Get curves")));
1381 glyph_from_path_button->set_child(*box);
1382 glyph_from_path_button->set_tooltip_text(_("Get curves from selection to replace current glyph"));
1383 glyph_from_path_button->signal_clicked().connect(sigc::mem_fun(*this, &SvgFontsDialog::set_glyph_description_from_selected_path));
1384
1385 auto const edit = Gtk::make_managed<Gtk::Button>();
1386 box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 2);
1387 box->append(*Gtk::make_managed<Gtk::Image>(Gio::ThemedIcon::create("edit")));
1388 box->append(*Gtk::make_managed<Gtk::Label>(_("Edit")));
1389 edit->set_child(*box);
1390 edit->set_tooltip_text(_("Switch to a layer with the same name as current glyph"));
1391 edit->signal_clicked().connect([this]{ edit_glyph(get_selected_glyph()); });
1392
1393 auto const sort_glyphs_button = Gtk::make_managed<Gtk::Button>(_("Sort glyphs"));
1394 sort_glyphs_button->set_tooltip_text(_("Sort glyphs in Unicode order"));
1395 sort_glyphs_button->signal_clicked().connect([this]{ sort_glyphs(get_selected_spfont()); });
1396
1397 auto const add_glyph_button = Gtk::make_managed<Gtk::Button>();
1398 add_glyph_button->set_image_from_icon_name("list-add");
1399 add_glyph_button->set_tooltip_text(_("Add new glyph"));
1400 add_glyph_button->signal_clicked().connect(sigc::mem_fun(*this, &SvgFontsDialog::add_glyph));
1401
1402 auto const remove_glyph_button = Gtk::make_managed<Gtk::Button>();
1403 remove_glyph_button->set_image_from_icon_name("list-remove");
1404 remove_glyph_button->set_tooltip_text(_("Delete current glyph"));
1405 remove_glyph_button->signal_clicked().connect([this]{ remove_selected_glyph(); });
1406
1407 auto const hb = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 4);
1408 UI::pack_start(*hb, *glyph_from_path_button, false, false);
1409 UI::pack_start(*hb, *edit, false, false);
1410 UI::pack_start(*hb, *sort_glyphs_button, false, false);
1411 UI::pack_end(*hb, *remove_glyph_button, false, false);
1412 UI::pack_end(*hb, *add_glyph_button, false, false);
1413
1414 _glyph_cell_renderer = Gtk::make_managed<SvgGlyphRenderer>();
1415 _glyph_cell_renderer->set_tree(&_glyphs_grid);
1416 const int cell_width = 70;
1417 const int cell_height = 50;
1418 _glyph_cell_renderer->set_cell_size(cell_width, cell_height);
1419 _glyph_cell_renderer->set_font_size(cell_height * 8 / 10); // font size: 80% of height
1420 _glyphs_icon_scroller.set_child(_glyphs_grid);
1421 _glyphs_icon_scroller.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
1422 _glyphs_grid.set_name("GlyphsGrid");
1423 _glyphs_grid.set_model(_GlyphsListStore);
1424 _glyphs_grid.set_item_width(cell_width);
1425 _glyphs_grid.set_selection_mode(Gtk::SelectionMode::SINGLE);
1426 _glyphs_grid.set_margin(0);
1427 _glyphs_grid.set_item_padding(0);
1428 _glyphs_grid.set_row_spacing(0);
1429 _glyphs_grid.set_column_spacing(0);
1430 _glyphs_grid.set_columns(-1);
1431 _glyphs_grid.set_markup_column(_GlyphsListColumns.name_markup);
1432 _glyphs_grid.pack_start(*_glyph_cell_renderer);
1433 _glyphs_grid.add_attribute(*_glyph_cell_renderer, "glyph", _GlyphsListColumns.unicode);
1434 _glyphs_grid.set_visible(true);
1435 _glyphs_grid.signal_item_activated().connect([this](Gtk::TreeModel::Path const &path) {
1436 edit_glyph(get_selected_glyph());
1437 });
1438
1439 // keep selection in sync between the two views: list and grid
1440 _glyphs_grid.signal_selection_changed().connect([this]{
1441 if (_glyphs_icon_scroller.get_visible()) {
1442 if (auto selected = get_selected_glyph_iter()) {
1443 if (auto selection = _GlyphsList.get_selection()) {
1444 selection->select(selected);
1445 }
1446 }
1447 }
1448 });
1449 if (auto selection = _GlyphsList.get_selection()) {
1450 selection->signal_changed().connect([this]{
1451 if (_GlyphsListScroller.get_visible()) {
1452 if (auto selected = get_selected_glyph_iter()) {
1453 auto selected_item = _GlyphsListStore->get_path(selected);
1454 _glyphs_grid.select_path(selected_item);
1455 }
1456 }
1457 });
1458 }
1459
1460 // display mode switching buttons
1461 auto const hbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 4);
1462 auto const list = Gtk::make_managed<Gtk::ToggleButton>();
1463 list->set_icon_name("layout-list");
1464 list->set_tooltip_text(_("Glyph list view"));
1465 list->set_valign(Gtk::Align::START);
1466 list->signal_toggled().connect([this]{ set_glyphs_view_mode(true); });
1467 auto const grid = Gtk::make_managed<Gtk::ToggleButton>();
1468 grid->set_group(*list);
1469 grid->set_icon_name("layout-grid");
1470 grid->set_tooltip_text(_("Glyph grid view"));
1471 grid->set_valign(Gtk::Align::START);
1472 grid->signal_toggled().connect([this] { set_glyphs_view_mode(false); });
1473 UI::pack_start(*hbox, *missing_glyph);
1474 UI::pack_end(*hbox, *grid, false, false);
1475 UI::pack_end(*hbox, *list, false, false);
1476
1477 UI::pack_start(glyphs_vbox, *hb, false, false);
1478 UI::pack_start(glyphs_vbox, _GlyphsListScroller, true, true);
1479 UI::pack_start(glyphs_vbox, _glyphs_icon_scroller, true, true);
1480 UI::pack_start(glyphs_vbox, *hbox, false,false);
1481
1482 _GlyphsListScroller.set_visible(false);
1483 _glyphs_icon_scroller.set_visible(false);
1484 (_show_glyph_list ? list : grid)->set_active();
1485 set_glyphs_view_mode(_show_glyph_list);
1486
1487 for (auto&& col : _GlyphsList.get_columns()) {
1488 col->set_resizable();
1489 }
1490
1491 static_cast<Gtk::CellRendererText*>(_GlyphsList.get_column_cell_renderer(ColName))->signal_edited().connect(
1492 sigc::mem_fun(*this, &SvgFontsDialog::glyph_name_edit));
1493
1494 static_cast<Gtk::CellRendererText*>(_GlyphsList.get_column_cell_renderer(ColString))->signal_edited().connect(
1495 sigc::mem_fun(*this, &SvgFontsDialog::glyph_unicode_edit));
1496
1497 static_cast<Gtk::CellRendererText*>(_GlyphsList.get_column_cell_renderer(ColAdvance))->signal_edited().connect(
1498 sigc::mem_fun(*this, &SvgFontsDialog::glyph_advance_edit));
1499
1500 _glyphs_observer.signal_changed().connect([this]{ update_glyphs(); });
1501
1502 return &glyphs_vbox;
1503}
1504
1505void SvgFontsDialog::add_kerning_pair() {
1506 if (first_glyph.get_active_text() == "" ||
1507 second_glyph.get_active_text() == "") return;
1508 //look for this kerning pair on the currently selected font
1509 this->kerning_pair = nullptr;
1510 for (auto& node: get_selected_spfont()->children) {
1511 //TODO: It is not really correct to get only the first byte of each string.
1512 //TODO: We should also support vertical kerning
1513 if(is<SPHkern>(&node) &&
1514 (static_cast<SPGlyphKerning*>(&node))->u1->contains((gchar) first_glyph.get_active_text().c_str()[0]) &&
1515 (static_cast<SPGlyphKerning*>(&node))->u2->contains((gchar) second_glyph.get_active_text().c_str()[0])) {
1516 this->kerning_pair = static_cast<SPGlyphKerning*>(&node);
1517 return;
1518 }
1519 }
1520
1521
1522 Inkscape::XML::Document *xml_doc = getDocument()->getReprDoc();
1523
1524 // create a new hkern node
1525 Inkscape::XML::Node *repr = xml_doc->createElement("svg:hkern");
1526
1527 repr->setAttribute("u1", first_glyph.get_active_text());
1528 repr->setAttribute("u2", second_glyph.get_active_text());
1529 repr->setAttribute("k", "0");
1530
1531 // Append the new hkern node to the current font
1532 get_selected_spfont()->getRepr()->appendChild(repr);
1534
1535 // get corresponding object
1536 kerning_pair = cast<SPHkern>(getDocument()->getObjectByRepr(repr));
1537
1538 // select newly added pair
1539 if (auto selection = _KerningPairsList.get_selection()) {
1540 _KerningPairsListStore->foreach_iter([=, this](Gtk::TreeModel::iterator const &it) {
1541 if (it->get_value(_KerningPairsListColumns.spnode) == kerning_pair) {
1542 selection->select(it);
1543 return true; // stop
1544 }
1545 return false; // continue
1546 });
1547 }
1548
1549 DocumentUndo::done(getDocument(), _("Add kerning pair"), "");
1550}
1551
1552Gtk::Box* SvgFontsDialog::kerning_tab(){
1553 auto const add_kernpair_button = Gtk::make_managed<Gtk::Button>(_("Add pair"));
1554 add_kernpair_button->signal_clicked().connect(sigc::mem_fun(*this, &SvgFontsDialog::add_kerning_pair));
1555
1556 auto const remove_kernpair_button = Gtk::make_managed<Gtk::Button>(_("Remove pair"));
1557 remove_kernpair_button->signal_clicked().connect(sigc::mem_fun(*this, &SvgFontsDialog::remove_selected_kerning_pair));
1558
1559 auto const kerning_selector = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, MARGIN_SPACE);
1560 kerning_selector->append(*Gtk::make_managed<Gtk::Label>(_("Select glyphs:")));
1561 kerning_selector->append(first_glyph);
1562 kerning_selector->append(second_glyph);
1563 kerning_selector->append(*add_kernpair_button);
1564 kerning_selector->append(*remove_kernpair_button);
1565
1566 _KerningPairsList.set_model(_KerningPairsListStore);
1567 _KerningPairsList.append_column(_("First glyph"), _KerningPairsListColumns.first_glyph);
1568 _KerningPairsList.append_column(_("Second glyph"), _KerningPairsListColumns.second_glyph);
1569 _KerningPairsList.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &SvgFontsDialog::on_kerning_pair_selection_changed));
1570
1571 _KerningPairsListScroller.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::ALWAYS);
1572 _KerningPairsListScroller.set_child(_KerningPairsList);
1573
1574 kerning_slider->signal_value_changed().connect(sigc::mem_fun(*this, &SvgFontsDialog::on_kerning_value_changed));
1575
1576 // kerning_slider has a big handle. Extra padding added
1577 auto const kerning_amount_hbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
1578 UI::pack_start(*kerning_amount_hbox, *Gtk::make_managed<Gtk::Label>(_("Kerning value:")), false,false);
1579 UI::pack_start(*kerning_amount_hbox, *kerning_slider, true,true);
1580
1581 kerning_preview.set_size(-1, 150 + 20);
1582 _font_da.set_size(-1, 60 + 20);
1583
1584 kerning_vbox.set_name("SVGFontsKerningTab");
1585 kerning_vbox.set_margin(4);
1586 kerning_vbox.set_spacing(4);
1587 UI::pack_start(kerning_vbox, *kerning_selector, false,false);
1588 UI::pack_start(kerning_vbox, _KerningPairsListScroller, true,true);
1589 UI::pack_start(kerning_vbox, (Gtk::Widget&) kerning_preview, false,false);
1590 UI::pack_start(kerning_vbox, *kerning_amount_hbox, false,false);
1591 return &kerning_vbox;
1592}
1593
1595{
1596 g_return_val_if_fail(document != nullptr, NULL);
1597
1598 SPDefs *defs = document->getDefs();
1599
1600 Inkscape::XML::Document *xml_doc = document->getReprDoc();
1601
1602 // create a new font
1603 Inkscape::XML::Node *repr = xml_doc->createElement("svg:font");
1604
1605 //By default, set the horizontal advance to 1000 units
1606 repr->setAttribute("horiz-adv-x", "1000");
1607
1608 // Append the new font node to defs
1609 defs->getRepr()->appendChild(repr);
1610
1611 // add some default values
1612 Inkscape::XML::Node *fontface;
1613 fontface = xml_doc->createElement("svg:font-face");
1614 fontface->setAttribute("units-per-em", "1000");
1615 fontface->setAttribute("ascent", "750");
1616 fontface->setAttribute("cap-height", "600");
1617 fontface->setAttribute("x-height", "400");
1618 fontface->setAttribute("descent", "200");
1619 repr->appendChild(fontface);
1620
1621 //create a missing glyph
1623 mg = xml_doc->createElement("svg:missing-glyph");
1624 mg->setAttribute("d", "M0,0h1000v1000h-1000z");
1625 repr->appendChild(mg);
1626
1627 // get corresponding object
1628 auto f = cast<SPFont>( document->getObjectByRepr(repr) );
1629
1630 g_assert(f != nullptr);
1633 return f;
1634}
1635
1636void set_font_family(SPFont* font, char* str){
1637 if (!font) return;
1638 for (auto& obj: font->children) {
1639 if (is<SPFontFace>(&obj)){
1640 //XML Tree being directly used here while it shouldn't be.
1641 obj.setAttribute("font-family", str);
1642 }
1643 }
1644
1645 DocumentUndo::done(font->document, _("Set font family"), "");
1646}
1647
1648void SvgFontsDialog::add_font(){
1649 SPDocument* doc = this->getDesktop()->getDocument();
1650 SPFont* font = new_font(doc);
1651
1652 const int count = _model->children().size();
1653 std::ostringstream os, os2;
1654 os << _("font") << " " << count;
1655 font->setLabel(os.str().c_str());
1656
1657 os2 << "SVGFont " << count;
1658 for (auto& obj: font->children) {
1659 if (is<SPFontFace>(&obj)){
1660 //XML Tree being directly used here while it shouldn't be.
1661 obj.setAttribute("font-family", os2.str());
1662 }
1663 }
1664
1665 update_fonts(false);
1666 on_font_selection_changed();
1667
1668 DocumentUndo::done(doc, _("Add font"), "");
1669}
1670
1671SvgFontsDialog::SvgFontsDialog()
1672 : DialogBase("/dialogs/svgfonts", "SVGFonts")
1673 , global_vbox(Gtk::Orientation::VERTICAL)
1674 , glyphs_vbox(Gtk::Orientation::VERTICAL)
1675 , kerning_vbox(Gtk::Orientation::VERTICAL)
1676{
1677 kerning_slider = Gtk::make_managed<Gtk::Scale>(Gtk::Orientation::HORIZONTAL);
1678
1679 // kerning pairs store
1680 _KerningPairsListStore = Gtk::ListStore::create(_KerningPairsListColumns);
1681
1682 // list of glyphs in a current font; this store is reused if there are multiple fonts
1683 _GlyphsListStore = Gtk::ListStore::create(_GlyphsListColumns);
1684
1685 // List of SVGFonts declared in a document:
1686 _model = Gtk::ListStore::create(_columns);
1687 _FontsList.set_model(_model);
1688 _FontsList.set_enable_search(false);
1689 _FontsList.append_column_editable(_("_Fonts"), _columns.label);
1690 _FontsList.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &SvgFontsDialog::on_font_selection_changed));
1691 // connect to the cell renderer's edit signal; there's also model's row_changed, but it is less specific
1692 if (auto renderer = dynamic_cast<Gtk::CellRendererText*>(_FontsList.get_column_cell_renderer(0))) {
1693 // commit font names when user edits them
1694 renderer->signal_edited().connect([this](Glib::ustring const &path, Glib::ustring const &new_name) {
1695 if (auto it = _model->get_iter(path)) {
1696 auto font = it->get_value(_columns.spfont);
1697 font->setLabel(new_name.c_str());
1698 Glib::ustring undokey = "svgfonts:fontName";
1699 DocumentUndo::maybeDone(font->document, undokey.c_str(), _("Set SVG font name"), "");
1700 }
1701 });
1702 }
1703
1704 auto const tabs = Gtk::make_managed<Gtk::Notebook>();
1705 tabs->set_scrollable();
1706
1707 tabs->append_page(*global_settings_tab(), _("_Global settings"), true);
1708 tabs->append_page(*glyphs_tab(), _("_Glyphs"), true);
1709 tabs->append_page(*kerning_tab(), _("_Kerning"), true);
1710 tabs->signal_switch_page().connect([this](Gtk::Widget*, guint page) {
1711 if (page == 2) {
1712 // update kerning glyph combos
1713 if (SPFont* font = get_selected_spfont()) {
1714 first_glyph.update(font);
1715 second_glyph.update(font);
1716 }
1717 }
1718 });
1719
1720 UI::pack_start(*this, *tabs, true, true);
1721
1722 // Text Preview:
1723 _preview_entry.signal_changed().connect(sigc::mem_fun(*this, &SvgFontsDialog::on_preview_text_changed));
1724 UI::pack_start(*this, _font_da, false, false);
1725 _preview_entry.set_text(_("Sample text"));
1726 _font_da.set_text(_("Sample text"));
1727
1728 auto const preview_entry_hbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, MARGIN_SPACE);
1729 UI::pack_start(*this, *preview_entry_hbox, false, false); // Non-latin characters may need more height.
1730 UI::pack_start(*preview_entry_hbox, *Gtk::make_managed<Gtk::Label>(_("Preview text:")), false, false);
1731 UI::pack_start(*preview_entry_hbox, _preview_entry, true, true);
1732 preview_entry_hbox->set_margin_bottom(MARGIN_SPACE);
1733 preview_entry_hbox->set_margin_start(MARGIN_SPACE);
1734 preview_entry_hbox->set_margin_end(MARGIN_SPACE);
1735
1736 set_visible(true);
1737}
1738
1740
1742{
1743 _defs_observer_connection.disconnect();
1744 if (auto document = getDocument()) {
1747 }
1748 update_fonts(true);
1749}
1750
1751} // namespace Inkscape::UI::Dialog
1752
1753/*
1754 Local Variables:
1755 mode:c++
1756 c-file-style:"stroustrup"
1757 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1758 indent-tabs-mode:nil
1759 fill-column:99
1760 End:
1761*/
1762// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
gchar const * sp_attribute_name(SPAttr id)
Get attribute name by id.
SPAttr
Definition attributes.h:27
@ VERT_ADV_Y
@ UNITS_PER_EM
@ HORIZ_ORIGIN_X
@ VERT_ORIGIN_Y
@ HORIZ_ORIGIN_Y
@ VERT_ORIGIN_X
@ FONT_FAMILY
@ HORIZ_ADV_X
@ CAP_HEIGHT
uint64_t page
Definition canvas.cpp:171
bool is(S const *s)
Equivalent to the boolean value of dynamic_cast<T const*>(...).
Definition cast.h:37
3x3 matrix representing an affine transformation.
Definition affine.h:70
static CRect from_xywh(Coord x, Coord y, Coord w, Coord h)
Create rectangle from origin and dimensions.
Sequence of subpaths.
Definition pathvector.h:122
Two-dimensional point that doubles as a vector.
Definition point.h:66
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
static void maybeDone(SPDocument *document, const gchar *keyconst, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
void renameLayer(SPObject *obj, char const *label, bool uniquify)
void setCurrentLayer(SPObject *object, bool clear=false)
Sets the current layer of the desktop.
static SPGroup * asLayer(SPObject *object)
Return the SPGroup if we have a layer object.
SPGroup * currentRoot() const
Returns current root (=bottom) layer.
A class which holds a stack of displayed messages.
MessageId flash(MessageType type, char const *message)
Temporarily pushes a message onto the stack.
DialogBase is the base class for the dialog system.
Definition dialog-base.h:42
SPDocument * getDocument() const
Definition dialog-base.h:83
std::unique_ptr< UI::Widget::PopoverMenu > _menu
AttrEntry(SvgFontsDialog *d, gchar *lbl, Glib::ustring tooltip, const SPAttr attr)
AttrSpin(SvgFontsDialog *d, gchar *lbl, Glib::ustring tooltip, const SPAttr attr)
Gtk::TreeModelColumn< SPFont * > spfont
Gtk::TreeModelColumn< Glib::ustring > label
Gtk::TreeModelColumn< SvgFont * > svgfont
Gtk::TreeModelColumn< SPGlyphKerning * > spnode
std::unique_ptr< AttrEntry > _familyname_entry
Glib::RefPtr< Gtk::ListStore > _GlyphsListStore
void update_fonts(bool document_replaced)
Add all fonts in the getDocument() to the combobox.
Gtk::Box * AttrCombo(gchar *lbl, const SPAttr attr)
std::unique_ptr< AttrSpin > _horiz_adv_x_spin
std::unique_ptr< AttrSpin > _ascent_spin
std::unique_ptr< AttrSpin > _x_height_spin
Glib::RefPtr< Gtk::ListStore > _model
void update_glyphs(SPGlyph *changed_glyph=nullptr)
void font_selected(SvgFont *svgfont, SPFont *spfont)
std::unique_ptr< AttrSpin > _descent_spin
sigc::scoped_connection _defs_observer_connection
Inkscape::XML::SignalObserver _defs_observer
Gtk::TreeModel::iterator get_selected_glyph_iter()
Glib::RefPtr< Gtk::ListStore > _KerningPairsListStore
std::unique_ptr< AttrSpin > _units_per_em_spin
std::unique_ptr< AttrSpin > _horiz_origin_x_spin
std::unique_ptr< AttrSpin > _cap_height_spin
std::unique_ptr< AttrSpin > _horiz_origin_y_spin
bool activate_vfunc(Glib::RefPtr< Gdk::Event const > const &event, Gtk::Widget &widget, Glib::ustring const &path, Gdk::Rectangle const &background_area, Gdk::Rectangle const &cell_area, Gtk::CellRendererState flags) final
sigc::signal< void(Glib::RefPtr< Gdk::Event const > const &, Glib::ustring const &)> _signal_clicked
void snapshot_vfunc(Glib::RefPtr< Gtk::Snapshot > const &snapshot, Gtk::Widget &widget, Gdk::Rectangle const &background_area, Gdk::Rectangle const &cell_area, Gtk::CellRendererState flags) final
Glib::Property< Glib::ustring > _property_glyph
A replacement for GTK3ʼs Gtk::Menu, as removed in GTK4.
Interface for refcounted XML nodes.
Definition node.h:80
virtual void changeOrder(Node *child, Node *after)=0
Move a given node in this node's child order.
virtual void appendChild(Node *child)=0
Append a node as the last child of this node.
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
virtual bool matchAttributeName(char const *partial_name) const =0
Check whether this node has any attribute that matches a string.
sigc::signal< void()> & signal_changed()
scoped_block block()
To do: update description of desktop.
Definition desktop.h:149
SPDocument * getDocument() const
Definition desktop.h:189
Inkscape::LayerManager & layerManager()
Definition desktop.h:287
Typed SVG document implementation.
Definition document.h:101
void setWidthAndHeight(const Inkscape::Util::Quantity &width, const Inkscape::Util::Quantity &height, bool changeSize=true)
Definition document.cpp:804
SPObject * getObjectById(std::string const &id) const
std::vector< SPObject * > const getResourceList(char const *key)
SPDefs * getDefs()
Return the main defs object for the document.
Definition document.cpp:246
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:211
bool is_yaxisdown() const
True if the desktop Y-axis points down, false if it points up.
Definition document.h:274
SPObject * getObjectByRepr(Inkscape::XML::Node *repr) const
void setViewBox()
Set default viewbox calculated from document properties.
Definition document.cpp:943
double horiz_origin_x
Definition sp-font.h:25
void sort_glyphs()
Definition sp-font.cpp:222
double horiz_adv_x
Definition sp-font.h:27
double horiz_origin_y
Definition sp-font.h:26
UnicodeRange * u2
UnicodeRange * u1
Glib::ustring glyph_name
Definition sp-glyph.h:41
Glib::ustring unicode
Definition sp-glyph.h:40
double horiz_adv_x
Definition sp-glyph.h:46
static SPGuide * createSPGuide(SPDocument *doc, Geom::Point const &pt1, Geom::Point const &pt2)
Definition sp-guide.cpp:209
Base class for visual SVG elements.
Definition sp-item.h:109
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
char const * label() const
Gets the author-visible label property for the object or a default if no label is defined.
void setAttribute(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
void requestModified(unsigned int flags)
Requests that a modification notification signal be emitted later (e.g.
SPDocument * document
Definition sp-object.h:188
char const * getId() const
Returns the objects current ID string.
SPObject * parent
Definition sp-object.h:189
void setLabel(char const *label)
Sets the author-visible label for this object.
void deleteObject(bool propagate, bool propagate_descendants)
Deletes an object, unparenting it from its parent.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
char const * getAttribute(char const *name) const
ChildrenList children
Definition sp-object.h:907
void set_svgfont(SvgFont *)
void set_text(Glib::ustring)
void draw_func(Cairo::RefPtr< Cairo::Context > const &cr, int width, int height)
void set_size(int x, int y)
cairo_font_face_t * get_font_face()
gunichar sample_glyph()
Glib::ustring attribute_string()
RootCluster root
Glib::ustring msg
Editable view implementation.
TODO: insert short description here.
SPItem * item
Inkscape::XML::Node * node
Glib::ustring label
Raw stack of active status messages.
Definition desktop.h:50
static R & release(R &r)
Decrements the reference count of a anchored object.
Dialog code.
Definition desktop.h:117
SPItem * find_layer(SPDesktop *desktop, SPObject *root_layer, const Glib::ustring &name)
Glib::ustring create_unicode_name(const Glib::ustring &unicode, int max_chars)
SPGuide * create_guide(SPDocument &doc, double x0, double y0, double x1, double y1)
Glib::ustring get_glyph_full_name(const SPGlyph &glyph)
static void set_sensitive(Gtk::SearchEntry2 &entry, bool const sensitive)
void rename_glyph_layer(SPDesktop *desktop, SPItem *layer, const Glib::ustring &font, const Glib::ustring &name)
SPFont * new_font(SPDocument *document)
std::vector< SPGroup * > get_direct_sublayers(SPObject *layer)
Glib::ustring get_font_label(SPFont *font)
Glib::ustring get_glyph_synthetic_name(const SPGlyph &glyph)
Geom::PathVector flip_coordinate_system(Geom::PathVector pathv, const SPFont *font, double units_per_em)
void change_glyph_attribute(SPDesktop *desktop, SPGlyph &glyph, std::function< void()> change)
SPItem * get_layer_for_glyph(SPDesktop *desktop, const Glib::ustring &font, const Glib::ustring &name)
SPGuide * get_guide(SPDocument &doc, const Glib::ustring &id)
void set_font_family(SPFont *font, char *str)
void set_up_typography_canvas(SPDocument *document, double em, double asc, double cap, double xheight, double des)
Inkscape::XML::Node * create_path_from_glyph(const SPGlyph &glyph)
SPItem * get_or_create_layer_for_glyph(SPDesktop *desktop, const Glib::ustring &font, const Glib::ustring &name)
double get_font_units_per_em(const SPFont *font)
void pack_end(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the end of box.
Definition pack.cpp:153
void pack_start(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the start of box.
Definition pack.cpp:141
SPObject * create_layer(SPObject *root, SPObject *layer, LayerRelativePosition position)
Creates a new layer.
@ ERROR_MESSAGE
Definition message.h:29
STL namespace.
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
A replacement for GTK3ʼs Gtk::MenuItem, as removed in GTK4.
A replacement for GTK3ʼs Gtk::Menu, as removed in GTK4.
C facade to Inkscape::XML::Node.
void sp_repr_unparent(Inkscape::XML::Node *repr)
Remove repr from children of its parent node.
Definition repr.h:107
SPGuide – a guideline.
Interface for XML documents.
Definition document.h:43
virtual Node * createElement(char const *name)=0
SVG Fonts dialog.
Geom::PathVector sp_svg_read_pathv(char const *str)
Definition svg-path.cpp:37
static void sp_svg_write_path(Inkscape::SVG::PathString &str, Geom::Path const &p, bool normalize=false)
Definition svg-path.cpp:109
SPDesktop * desktop
Glib::ustring name
Definition toolbars.cpp:55
Glib::RefPtr< Gtk::Builder > builder
unsigned int gunichar