1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Author:
4  *   Tavmjong Bah <tavmjong@free.fr>
5  *
6  * Copyright (C) 2015, 2018 Tavmong Bah
7  *
8  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
9  */
10 
11 #include <gtkmm.h>
12 #include <glibmm/i18n.h>
13 
14 #include <libnrtype/font-instance.h>
15 
16 #include "font-variants.h"
17 
18 // For updating from selection
19 #include "desktop.h"
20 #include "object/sp-text.h"
21 
22 namespace Inkscape {
23 namespace UI {
24 namespace Widget {
25 
26   // A simple class to handle UI for one feature. We could of derived this from Gtk::HBox but by
27   // attaching widgets directly to Gtk::Grid, we keep columns lined up (which may or may not be a
28   // good thing).
29   class Feature
30   {
31   public:
Feature(const Glib::ustring & name,OTSubstitution & glyphs,int options,Glib::ustring family,Gtk::Grid & grid,int & row,FontVariants * parent)32       Feature( const Glib::ustring& name, OTSubstitution& glyphs, int options, Glib::ustring family, Gtk::Grid& grid, int &row, FontVariants* parent)
33           : _name (name)
34       {
35           Gtk::Label* table_name = Gtk::manage (new Gtk::Label());
36           table_name->set_markup ("\"" + name + "\" ");
37 
38           grid.attach (*table_name, 0, row, 1, 1);
39 
40           Gtk::FlowBox* flow_box = nullptr;
41           Gtk::ScrolledWindow* scrolled_window = nullptr;
42           if (options > 2) {
43               // If there are more than 2 option, pack them into a flowbox instead of directly putting them in the grid.
44               // Some fonts might have a table with many options (Bungee Hairline table 'ornm' has 113 entries).
45               flow_box = Gtk::manage (new Gtk::FlowBox());
46               flow_box->set_selection_mode(); // Turn off selection
47               flow_box->set_homogeneous();
48               flow_box->set_max_children_per_line (100); // Override default value
49               flow_box->set_min_children_per_line (10);  // Override default value
50 
51               // We pack this into a scrollbar... otherwise the minimum height is set to what is required to fit all
52               // flow box children into the flow box when the flow box has minimum width. (Crazy if you ask me!)
53               scrolled_window = Gtk::manage (new Gtk::ScrolledWindow());
54               scrolled_window->set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
55               scrolled_window->add(*flow_box);
56           }
57 
58           Gtk::RadioButton::Group group;
59           for (int i = 0; i < options; ++i) {
60 
61               // Create radio button and create or add to button group.
62               Gtk::RadioButton* button = Gtk::manage (new Gtk::RadioButton());
63               if (i == 0) {
64                   group = button->get_group();
65               } else {
66                   button->set_group (group);
67               }
68               button->signal_clicked().connect ( sigc::mem_fun(*parent, &FontVariants::feature_callback) );
69               buttons.push_back (button);
70 
71               // Create label.
72               Gtk::Label* label = Gtk::manage (new Gtk::Label());
73 
74               // Restrict label width (some fonts have lots of alternatives).
75               label->set_line_wrap( true );
76               label->set_line_wrap_mode( Pango::WRAP_WORD_CHAR );
77               label->set_ellipsize( Pango::ELLIPSIZE_END );
78               label->set_lines(3);
79               label->set_hexpand();
80 
81               Glib::ustring markup;
82               markup += "<span font_family='";
83               markup += family;
84               markup += "' font_features='";
85               markup += name;
86               markup += " ";
87               markup += std::to_string (i);
88               markup += "'>";
89               markup += Glib::Markup::escape_text (glyphs.input);
90               markup += "</span>";
91               label->set_markup (markup);
92 
93               // Add button and label to widget
94               if (!flow_box) {
95                   // Attach directly to grid (keeps things aligned row-to-row).
96                   grid.attach (*button, 2*i+1, row, 1, 1);
97                   grid.attach (*label,  2*i+2, row, 1, 1);
98               } else {
99                   // Pack into FlowBox
100 
101                   // Pack button and label into a box so they stay together.
102                   Gtk::Box* box = Gtk::manage (new Gtk::Box());
103                   box->add(*button);
104                   box->add(*label);
105 
106                   flow_box->add(*box);
107               }
108           }
109 
110           if (scrolled_window) {
111               grid.attach (*scrolled_window, 1, row, 4, 1);
112           }
113       }
114 
115       Glib::ustring
get_css()116       get_css()
117       {
118           int i = 0;
119           for (auto b: buttons) {
120               if (b->get_active()) {
121                   if (i == 0) {
122                       // Features are always off by default (for those handled here).
123                       return "";
124                   } else if (i == 1) {
125                       // Feature without value has implied value of 1.
126                       return ("\"" + _name + "\", ");
127                   } else {
128                       // Feature with value greater than 1 must be explicitly set.
129                       return ("\"" + _name + "\" " + std::to_string (i) + ", ");
130                   }
131               }
132               ++i;
133           }
134           return "";
135       }
136 
137       void
set_active(int i)138       set_active(int i)
139       {
140           if (i < buttons.size()) {
141               buttons[i]->set_active();
142           }
143       }
144 
145   private:
146       Glib::ustring _name;
147       std::vector <Gtk::RadioButton*> buttons;
148   };
149 
FontVariants()150   FontVariants::FontVariants () :
151     Gtk::Box (Gtk::ORIENTATION_VERTICAL),
152     _ligatures_frame          ( Glib::ustring(C_("Font feature", "Ligatures"    )) ),
153     _ligatures_common         ( Glib::ustring(C_("Font feature", "Common"       )) ),
154     _ligatures_discretionary  ( Glib::ustring(C_("Font feature", "Discretionary")) ),
155     _ligatures_historical     ( Glib::ustring(C_("Font feature", "Historical"   )) ),
156     _ligatures_contextual     ( Glib::ustring(C_("Font feature", "Contextual"   )) ),
157 
158     _position_frame           ( Glib::ustring(C_("Font feature", "Position"     )) ),
159     _position_normal          ( Glib::ustring(C_("Font feature", "Normal"       )) ),
160     _position_sub             ( Glib::ustring(C_("Font feature", "Subscript"    )) ),
161     _position_super           ( Glib::ustring(C_("Font feature", "Superscript"  )) ),
162 
163     _caps_frame               ( Glib::ustring(C_("Font feature", "Capitals"     )) ),
164     _caps_normal              ( Glib::ustring(C_("Font feature", "Normal"       )) ),
165     _caps_small               ( Glib::ustring(C_("Font feature", "Small"        )) ),
166     _caps_all_small           ( Glib::ustring(C_("Font feature", "All small"    )) ),
167     _caps_petite              ( Glib::ustring(C_("Font feature", "Petite"       )) ),
168     _caps_all_petite          ( Glib::ustring(C_("Font feature", "All petite"   )) ),
169     _caps_unicase             ( Glib::ustring(C_("Font feature", "Unicase"      )) ),
170     _caps_titling             ( Glib::ustring(C_("Font feature", "Titling"      )) ),
171 
172     _numeric_frame            ( Glib::ustring(C_("Font feature", "Numeric"      )) ),
173     _numeric_lining           ( Glib::ustring(C_("Font feature", "Lining"       )) ),
174     _numeric_old_style        ( Glib::ustring(C_("Font feature", "Old Style"    )) ),
175     _numeric_default_style    ( Glib::ustring(C_("Font feature", "Default Style")) ),
176     _numeric_proportional     ( Glib::ustring(C_("Font feature", "Proportional" )) ),
177     _numeric_tabular          ( Glib::ustring(C_("Font feature", "Tabular"      )) ),
178     _numeric_default_width    ( Glib::ustring(C_("Font feature", "Default Width")) ),
179     _numeric_diagonal         ( Glib::ustring(C_("Font feature", "Diagonal"     )) ),
180     _numeric_stacked          ( Glib::ustring(C_("Font feature", "Stacked"      )) ),
181     _numeric_default_fractions( Glib::ustring(C_("Font feature", "Default Fractions")) ),
182     _numeric_ordinal          ( Glib::ustring(C_("Font feature", "Ordinal"      )) ),
183     _numeric_slashed_zero     ( Glib::ustring(C_("Font feature", "Slashed Zero" )) ),
184 
185     _asian_frame              ( Glib::ustring(C_("Font feature", "East Asian"   )) ),
186     _asian_default_variant    ( Glib::ustring(C_("Font feature", "Default"      )) ),
187     _asian_jis78              ( Glib::ustring(C_("Font feature", "JIS78"        )) ),
188     _asian_jis83              ( Glib::ustring(C_("Font feature", "JIS83"        )) ),
189     _asian_jis90              ( Glib::ustring(C_("Font feature", "JIS90"        )) ),
190     _asian_jis04              ( Glib::ustring(C_("Font feature", "JIS04"        )) ),
191     _asian_simplified         ( Glib::ustring(C_("Font feature", "Simplified"   )) ),
192     _asian_traditional        ( Glib::ustring(C_("Font feature", "Traditional"  )) ),
193     _asian_default_width      ( Glib::ustring(C_("Font feature", "Default"      )) ),
194     _asian_full_width         ( Glib::ustring(C_("Font feature", "Full Width"   )) ),
195     _asian_proportional_width ( Glib::ustring(C_("Font feature", "Proportional" )) ),
196     _asian_ruby               ( Glib::ustring(C_("Font feature", "Ruby"         )) ),
197 
198     _feature_frame            ( Glib::ustring(C_("Font feature", "Feature Settings")) ),
199     _feature_label            ( Glib::ustring(C_("Font feature", "Selection has different Feature Settings!")) ),
200 
201     _ligatures_changed( false ),
202     _position_changed( false ),
203     _caps_changed( false ),
204     _numeric_changed( false ),
205     _asian_changed( false ),
206     _feature_vbox(Gtk::ORIENTATION_VERTICAL)
207 
208   {
209 
210     set_name ( "FontVariants" );
211 
212     // Ligatures --------------------------
213 
214     // Add tooltips
215     _ligatures_common.set_tooltip_text(
216       _("Common ligatures. On by default. OpenType tables: 'liga', 'clig'"));
217     _ligatures_discretionary.set_tooltip_text(
218       _("Discretionary ligatures. Off by default. OpenType table: 'dlig'"));
219     _ligatures_historical.set_tooltip_text(
220       _("Historical ligatures. Off by default. OpenType table: 'hlig'"));
221     _ligatures_contextual.set_tooltip_text(
222       _("Contextual forms. On by default. OpenType table: 'calt'"));
223 
224     // Add signals
225     _ligatures_common.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::ligatures_callback) );
226     _ligatures_discretionary.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::ligatures_callback) );
227     _ligatures_historical.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::ligatures_callback) );
228     _ligatures_contextual.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::ligatures_callback) );
229 
230     // Restrict label widths (some fonts have lots of ligatures). Must also set ellipsize mode.
231     _ligatures_label_common.set_max_width_chars(        60 );
232     _ligatures_label_discretionary.set_max_width_chars( 60 );
233     _ligatures_label_historical.set_max_width_chars(    60 );
234     _ligatures_label_contextual.set_max_width_chars(    60 );
235 
236     _ligatures_label_common.set_ellipsize(        Pango::ELLIPSIZE_END );
237     _ligatures_label_discretionary.set_ellipsize( Pango::ELLIPSIZE_END );
238     _ligatures_label_historical.set_ellipsize(    Pango::ELLIPSIZE_END );
239     _ligatures_label_contextual.set_ellipsize(    Pango::ELLIPSIZE_END );
240 
241     _ligatures_label_common.set_lines(        5 );
242     _ligatures_label_discretionary.set_lines( 5 );
243     _ligatures_label_historical.set_lines(    5 );
244     _ligatures_label_contextual.set_lines(    5 );
245 
246     // Allow user to select characters. Not useful as this selects the ligatures.
247     // _ligatures_label_common.set_selectable(        true );
248     // _ligatures_label_discretionary.set_selectable( true );
249     // _ligatures_label_historical.set_selectable(    true );
250     // _ligatures_label_contextual.set_selectable(    true );
251 
252     // Add to frame
253     _ligatures_grid.attach( _ligatures_common,              0, 0, 1, 1);
254     _ligatures_grid.attach( _ligatures_discretionary,       0, 1, 1, 1);
255     _ligatures_grid.attach( _ligatures_historical,          0, 2, 1, 1);
256     _ligatures_grid.attach( _ligatures_contextual,          0, 3, 1, 1);
257     _ligatures_grid.attach( _ligatures_label_common,        1, 0, 1, 1);
258     _ligatures_grid.attach( _ligatures_label_discretionary, 1, 1, 1, 1);
259     _ligatures_grid.attach( _ligatures_label_historical,    1, 2, 1, 1);
260     _ligatures_grid.attach( _ligatures_label_contextual,    1, 3, 1, 1);
261 
262     _ligatures_grid.set_margin_start(15);
263     _ligatures_grid.set_margin_end(15);
264 
265     _ligatures_frame.add( _ligatures_grid );
266     pack_start( _ligatures_frame, Gtk::PACK_SHRINK );
267 
268     ligatures_init();
269 
270     // Position ----------------------------------
271 
272     // Add tooltips
273     _position_normal.set_tooltip_text( _("Normal position."));
274     _position_sub.set_tooltip_text( _("Subscript. OpenType table: 'subs'") );
275     _position_super.set_tooltip_text( _("Superscript. OpenType table: 'sups'") );
276 
277     // Group buttons
278     Gtk::RadioButton::Group position_group = _position_normal.get_group();
279     _position_sub.set_group(position_group);
280     _position_super.set_group(position_group);
281 
282     // Add signals
283     _position_normal.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::position_callback) );
284     _position_sub.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::position_callback) );
285     _position_super.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::position_callback) );
286 
287     // Add to frame
288     _position_grid.attach( _position_normal, 0, 0, 1, 1);
289     _position_grid.attach( _position_sub,    1, 0, 1, 1);
290     _position_grid.attach( _position_super,  2, 0, 1, 1);
291 
292     _position_grid.set_margin_start(15);
293     _position_grid.set_margin_end(15);
294 
295     _position_frame.add( _position_grid );
296     pack_start( _position_frame, Gtk::PACK_SHRINK );
297 
298     position_init();
299 
300     // Caps ----------------------------------
301 
302     // Add tooltips
303     _caps_normal.set_tooltip_text( _("Normal capitalization."));
304     _caps_small.set_tooltip_text( _("Small-caps (lowercase). OpenType table: 'smcp'"));
305     _caps_all_small.set_tooltip_text( _("All small-caps (uppercase and lowercase). OpenType tables: 'c2sc' and 'smcp'"));
306     _caps_petite.set_tooltip_text( _("Petite-caps (lowercase). OpenType table: 'pcap'"));
307     _caps_all_petite.set_tooltip_text( _("All petite-caps (uppercase and lowercase). OpenType tables: 'c2sc' and 'pcap'"));
308     _caps_unicase.set_tooltip_text( _("Unicase (small caps for uppercase, normal for lowercase). OpenType table: 'unic'"));
309     _caps_titling.set_tooltip_text( _("Titling caps (lighter-weight uppercase for use in titles). OpenType table: 'titl'"));
310 
311     // Group buttons
312     Gtk::RadioButton::Group caps_group = _caps_normal.get_group();
313     _caps_small.set_group(caps_group);
314     _caps_all_small.set_group(caps_group);
315     _caps_petite.set_group(caps_group);
316     _caps_all_petite.set_group(caps_group);
317     _caps_unicase.set_group(caps_group);
318     _caps_titling.set_group(caps_group);
319 
320     // Add signals
321     _caps_normal.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );
322     _caps_small.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );
323     _caps_all_small.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );
324     _caps_petite.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );
325     _caps_all_petite.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );
326     _caps_unicase.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );
327     _caps_titling.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );
328 
329     // Add to frame
330     _caps_grid.attach( _caps_normal,     0, 0, 1, 1);
331     _caps_grid.attach( _caps_unicase,    1, 0, 1, 1);
332     _caps_grid.attach( _caps_titling,    2, 0, 1, 1);
333     _caps_grid.attach( _caps_small,      0, 1, 1, 1);
334     _caps_grid.attach( _caps_all_small,  1, 1, 1, 1);
335     _caps_grid.attach( _caps_petite,     2, 1, 1, 1);
336     _caps_grid.attach( _caps_all_petite, 3, 1, 1, 1);
337 
338     _caps_grid.set_margin_start(15);
339     _caps_grid.set_margin_end(15);
340 
341     _caps_frame.add( _caps_grid );
342     pack_start( _caps_frame, Gtk::PACK_SHRINK );
343 
344     caps_init();
345 
346     // Numeric ------------------------------
347 
348     // Add tooltips
349     _numeric_default_style.set_tooltip_text( _("Normal style."));
350     _numeric_lining.set_tooltip_text( _("Lining numerals. OpenType table: 'lnum'"));
351     _numeric_old_style.set_tooltip_text( _("Old style numerals. OpenType table: 'onum'"));
352     _numeric_default_width.set_tooltip_text( _("Normal widths."));
353     _numeric_proportional.set_tooltip_text( _("Proportional width numerals. OpenType table: 'pnum'"));
354     _numeric_tabular.set_tooltip_text( _("Same width numerals. OpenType table: 'tnum'"));
355     _numeric_default_fractions.set_tooltip_text( _("Normal fractions."));
356     _numeric_diagonal.set_tooltip_text( _("Diagonal fractions. OpenType table: 'frac'"));
357     _numeric_stacked.set_tooltip_text( _("Stacked fractions. OpenType table: 'afrc'"));
358     _numeric_ordinal.set_tooltip_text( _("Ordinals (raised 'th', etc.). OpenType table: 'ordn'"));
359     _numeric_slashed_zero.set_tooltip_text( _("Slashed zeros. OpenType table: 'zero'"));
360 
361     // Group buttons
362     Gtk::RadioButton::Group style_group = _numeric_default_style.get_group();
363     _numeric_lining.set_group(style_group);
364     _numeric_old_style.set_group(style_group);
365 
366     Gtk::RadioButton::Group width_group = _numeric_default_width.get_group();
367     _numeric_proportional.set_group(width_group);
368     _numeric_tabular.set_group(width_group);
369 
370     Gtk::RadioButton::Group fraction_group = _numeric_default_fractions.get_group();
371     _numeric_diagonal.set_group(fraction_group);
372     _numeric_stacked.set_group(fraction_group);
373 
374     // Add signals
375     _numeric_default_style.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) );
376     _numeric_lining.signal_clicked().connect (        sigc::mem_fun(*this, &FontVariants::numeric_callback) );
377     _numeric_old_style.signal_clicked().connect (     sigc::mem_fun(*this, &FontVariants::numeric_callback) );
378     _numeric_default_width.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) );
379     _numeric_proportional.signal_clicked().connect (  sigc::mem_fun(*this, &FontVariants::numeric_callback) );
380     _numeric_tabular.signal_clicked().connect (       sigc::mem_fun(*this, &FontVariants::numeric_callback) );
381     _numeric_default_fractions.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) );
382     _numeric_diagonal.signal_clicked().connect (      sigc::mem_fun(*this, &FontVariants::numeric_callback) );
383     _numeric_stacked.signal_clicked().connect (       sigc::mem_fun(*this, &FontVariants::numeric_callback) );
384     _numeric_ordinal.signal_clicked().connect (       sigc::mem_fun(*this, &FontVariants::numeric_callback) );
385     _numeric_slashed_zero.signal_clicked().connect (  sigc::mem_fun(*this, &FontVariants::numeric_callback) );
386 
387     // Add to frame
388     _numeric_grid.attach (_numeric_default_style,        0, 0, 1, 1);
389     _numeric_grid.attach (_numeric_lining,               1, 0, 1, 1);
390     _numeric_grid.attach (_numeric_lining_label,         2, 0, 1, 1);
391     _numeric_grid.attach (_numeric_old_style,            3, 0, 1, 1);
392     _numeric_grid.attach (_numeric_old_style_label,      4, 0, 1, 1);
393 
394     _numeric_grid.attach (_numeric_default_width,        0, 1, 1, 1);
395     _numeric_grid.attach (_numeric_proportional,         1, 1, 1, 1);
396     _numeric_grid.attach (_numeric_proportional_label,   2, 1, 1, 1);
397     _numeric_grid.attach (_numeric_tabular,              3, 1, 1, 1);
398     _numeric_grid.attach (_numeric_tabular_label,        4, 1, 1, 1);
399 
400     _numeric_grid.attach (_numeric_default_fractions,    0, 2, 1, 1);
401     _numeric_grid.attach (_numeric_diagonal,             1, 2, 1, 1);
402     _numeric_grid.attach (_numeric_diagonal_label,       2, 2, 1, 1);
403     _numeric_grid.attach (_numeric_stacked,              3, 2, 1, 1);
404     _numeric_grid.attach (_numeric_stacked_label,        4, 2, 1, 1);
405 
406     _numeric_grid.attach (_numeric_ordinal,              0, 3, 1, 1);
407     _numeric_grid.attach (_numeric_ordinal_label,        1, 3, 4, 1);
408 
409     _numeric_grid.attach (_numeric_slashed_zero,         0, 4, 1, 1);
410     _numeric_grid.attach (_numeric_slashed_zero_label,   1, 4, 1, 1);
411 
412     _numeric_grid.set_margin_start(15);
413     _numeric_grid.set_margin_end(15);
414 
415     _numeric_frame.add( _numeric_grid );
416     pack_start( _numeric_frame, Gtk::PACK_SHRINK );
417 
418     // East Asian
419 
420     // Add tooltips
421     _asian_default_variant.set_tooltip_text (  _("Default variant."));
422     _asian_jis78.set_tooltip_text(             _("JIS78 forms. OpenType table: 'jp78'."));
423     _asian_jis83.set_tooltip_text(             _("JIS83 forms. OpenType table: 'jp83'."));
424     _asian_jis90.set_tooltip_text(             _("JIS90 forms. OpenType table: 'jp90'."));
425     _asian_jis04.set_tooltip_text(             _("JIS2004 forms. OpenType table: 'jp04'."));
426     _asian_simplified.set_tooltip_text(        _("Simplified forms. OpenType table: 'smpl'."));
427     _asian_traditional.set_tooltip_text(       _("Traditional forms. OpenType table: 'trad'."));
428     _asian_default_width.set_tooltip_text (    _("Default width."));
429     _asian_full_width.set_tooltip_text(        _("Full width variants. OpenType table: 'fwid'."));
430     _asian_proportional_width.set_tooltip_text(_("Proportional width variants. OpenType table: 'pwid'."));
431     _asian_ruby.set_tooltip_text(              _("Ruby variants. OpenType table: 'ruby'."));
432 
433     // Add signals
434     _asian_default_variant.signal_clicked().connect (   sigc::mem_fun(*this, &FontVariants::asian_callback) );
435     _asian_jis78.signal_clicked().connect (             sigc::mem_fun(*this, &FontVariants::asian_callback) );
436     _asian_jis83.signal_clicked().connect (             sigc::mem_fun(*this, &FontVariants::asian_callback) );
437     _asian_jis90.signal_clicked().connect (             sigc::mem_fun(*this, &FontVariants::asian_callback) );
438     _asian_jis04.signal_clicked().connect (             sigc::mem_fun(*this, &FontVariants::asian_callback) );
439     _asian_simplified.signal_clicked().connect (        sigc::mem_fun(*this, &FontVariants::asian_callback) );
440     _asian_traditional.signal_clicked().connect (       sigc::mem_fun(*this, &FontVariants::asian_callback) );
441     _asian_default_width.signal_clicked().connect (     sigc::mem_fun(*this, &FontVariants::asian_callback) );
442     _asian_full_width.signal_clicked().connect (        sigc::mem_fun(*this, &FontVariants::asian_callback) );
443     _asian_proportional_width.signal_clicked().connect (sigc::mem_fun(*this, &FontVariants::asian_callback) );
444     _asian_ruby.signal_clicked().connect(               sigc::mem_fun(*this, &FontVariants::asian_callback) );
445 
446     // Add to frame
447     _asian_grid.attach (_asian_default_variant,         0, 0, 1, 1);
448     _asian_grid.attach (_asian_jis78,                   1, 0, 1, 1);
449     _asian_grid.attach (_asian_jis83,                   2, 0, 1, 1);
450     _asian_grid.attach (_asian_jis90,                   1, 1, 1, 1);
451     _asian_grid.attach (_asian_jis04,                   2, 1, 1, 1);
452     _asian_grid.attach (_asian_simplified,              1, 2, 1, 1);
453     _asian_grid.attach (_asian_traditional,             2, 2, 1, 1);
454     _asian_grid.attach (_asian_default_width,           0, 3, 1, 1);
455     _asian_grid.attach (_asian_full_width,              1, 3, 1, 1);
456     _asian_grid.attach (_asian_proportional_width,      2, 3, 1, 1);
457     _asian_grid.attach (_asian_ruby,                    0, 4, 1, 1);
458 
459     _asian_grid.set_margin_start(15);
460     _asian_grid.set_margin_end(15);
461 
462     _asian_frame.add( _asian_grid );
463     pack_start( _asian_frame, Gtk::PACK_SHRINK );
464 
465     // Group Buttons
466     Gtk::RadioButton::Group asian_variant_group = _asian_default_variant.get_group();
467     _asian_jis78.set_group(asian_variant_group);
468     _asian_jis83.set_group(asian_variant_group);
469     _asian_jis90.set_group(asian_variant_group);
470     _asian_jis04.set_group(asian_variant_group);
471     _asian_simplified.set_group(asian_variant_group);
472     _asian_traditional.set_group(asian_variant_group);
473 
474     Gtk::RadioButton::Group asian_width_group = _asian_default_width.get_group();
475     _asian_full_width.set_group (asian_width_group);
476     _asian_proportional_width.set_group (asian_width_group);
477 
478     // Feature settings ---------------------
479 
480     // Add tooltips
481     _feature_entry.set_tooltip_text( _("Feature settings in CSS form (e.g. \"wxyz\" or \"wxyz\" 3)."));
482 
483     _feature_substitutions.set_justify( Gtk::JUSTIFY_LEFT );
484     _feature_substitutions.set_line_wrap( true );
485     _feature_substitutions.set_line_wrap_mode( Pango::WRAP_WORD_CHAR );
486 
487     _feature_list.set_justify( Gtk::JUSTIFY_LEFT );
488     _feature_list.set_line_wrap( true );
489 
490     // Add to frame
491     _feature_vbox.pack_start( _feature_grid,          Gtk::PACK_SHRINK );
492     _feature_vbox.pack_start( _feature_entry,         Gtk::PACK_SHRINK );
493     _feature_vbox.pack_start( _feature_label,         Gtk::PACK_SHRINK );
494     _feature_vbox.pack_start( _feature_substitutions, Gtk::PACK_SHRINK );
495     _feature_vbox.pack_start( _feature_list,          Gtk::PACK_SHRINK );
496 
497     _feature_vbox.set_margin_start(15);
498     _feature_vbox.set_margin_end(15);
499 
500     _feature_frame.add( _feature_vbox );
501     pack_start( _feature_frame, Gtk::PACK_SHRINK );
502 
503     // Add signals
504     //_feature_entry.signal_key_press_event().connect ( sigc::mem_fun(*this, &FontVariants::feature_callback) );
505     _feature_entry.signal_changed().connect( sigc::mem_fun(*this, &FontVariants::feature_callback) );
506 
507     show_all_children();
508 
509   }
510 
511   void
ligatures_init()512   FontVariants::ligatures_init() {
513       // std::cout << "FontVariants::ligatures_init()" << std::endl;
514   }
515 
516   void
ligatures_callback()517   FontVariants::ligatures_callback() {
518       // std::cout << "FontVariants::ligatures_callback()" << std::endl;
519       _ligatures_changed = true;
520       _changed_signal.emit();
521   }
522 
523   void
position_init()524   FontVariants::position_init() {
525       // std::cout << "FontVariants::position_init()" << std::endl;
526   }
527 
528   void
position_callback()529   FontVariants::position_callback() {
530       // std::cout << "FontVariants::position_callback()" << std::endl;
531       _position_changed = true;
532       _changed_signal.emit();
533   }
534 
535   void
caps_init()536   FontVariants::caps_init() {
537       // std::cout << "FontVariants::caps_init()" << std::endl;
538   }
539 
540   void
caps_callback()541   FontVariants::caps_callback() {
542       // std::cout << "FontVariants::caps_callback()" << std::endl;
543       _caps_changed = true;
544       _changed_signal.emit();
545   }
546 
547   void
numeric_init()548   FontVariants::numeric_init() {
549       // std::cout << "FontVariants::numeric_init()" << std::endl;
550   }
551 
552   void
numeric_callback()553   FontVariants::numeric_callback() {
554       // std::cout << "FontVariants::numeric_callback()" << std::endl;
555       _numeric_changed = true;
556       _changed_signal.emit();
557   }
558 
559   void
asian_init()560   FontVariants::asian_init() {
561       // std::cout << "FontVariants::asian_init()" << std::endl;
562   }
563 
564   void
asian_callback()565   FontVariants::asian_callback() {
566       // std::cout << "FontVariants::asian_callback()" << std::endl;
567       _asian_changed = true;
568       _changed_signal.emit();
569   }
570 
571   void
feature_init()572   FontVariants::feature_init() {
573       // std::cout << "FontVariants::feature_init()" << std::endl;
574   }
575 
576   void
feature_callback()577   FontVariants::feature_callback() {
578       // std::cout << "FontVariants::feature_callback()" << std::endl;
579       _feature_changed = true;
580       _changed_signal.emit();
581   }
582 
583   // Update GUI based on query.
584   void
update(SPStyle const * query,bool different_features,Glib::ustring & font_spec)585   FontVariants::update( SPStyle const *query, bool different_features, Glib::ustring& font_spec ) {
586 
587       update_opentype( font_spec );
588 
589       _ligatures_all = query->font_variant_ligatures.computed;
590       _ligatures_mix = query->font_variant_ligatures.value;
591 
592       _ligatures_common.set_active(       _ligatures_all & SP_CSS_FONT_VARIANT_LIGATURES_COMMON );
593       _ligatures_discretionary.set_active(_ligatures_all & SP_CSS_FONT_VARIANT_LIGATURES_DISCRETIONARY );
594       _ligatures_historical.set_active(   _ligatures_all & SP_CSS_FONT_VARIANT_LIGATURES_HISTORICAL );
595       _ligatures_contextual.set_active(   _ligatures_all & SP_CSS_FONT_VARIANT_LIGATURES_CONTEXTUAL );
596 
597       _ligatures_common.set_inconsistent(        _ligatures_mix & SP_CSS_FONT_VARIANT_LIGATURES_COMMON );
598       _ligatures_discretionary.set_inconsistent( _ligatures_mix & SP_CSS_FONT_VARIANT_LIGATURES_DISCRETIONARY );
599       _ligatures_historical.set_inconsistent(    _ligatures_mix & SP_CSS_FONT_VARIANT_LIGATURES_HISTORICAL );
600       _ligatures_contextual.set_inconsistent(    _ligatures_mix & SP_CSS_FONT_VARIANT_LIGATURES_CONTEXTUAL );
601 
602       _position_all = query->font_variant_position.computed;
603       _position_mix = query->font_variant_position.value;
604 
605       _position_normal.set_active( _position_all & SP_CSS_FONT_VARIANT_POSITION_NORMAL );
606       _position_sub.set_active(    _position_all & SP_CSS_FONT_VARIANT_POSITION_SUB );
607       _position_super.set_active(  _position_all & SP_CSS_FONT_VARIANT_POSITION_SUPER );
608 
609       _position_normal.set_inconsistent( _position_mix & SP_CSS_FONT_VARIANT_POSITION_NORMAL );
610       _position_sub.set_inconsistent(    _position_mix & SP_CSS_FONT_VARIANT_POSITION_SUB );
611       _position_super.set_inconsistent(  _position_mix & SP_CSS_FONT_VARIANT_POSITION_SUPER );
612 
613       _caps_all = query->font_variant_caps.computed;
614       _caps_mix = query->font_variant_caps.value;
615 
616       _caps_normal.set_active(     _caps_all & SP_CSS_FONT_VARIANT_CAPS_NORMAL );
617       _caps_small.set_active(      _caps_all & SP_CSS_FONT_VARIANT_CAPS_SMALL );
618       _caps_all_small.set_active(  _caps_all & SP_CSS_FONT_VARIANT_CAPS_ALL_SMALL );
619       _caps_petite.set_active(     _caps_all & SP_CSS_FONT_VARIANT_CAPS_PETITE );
620       _caps_all_petite.set_active( _caps_all & SP_CSS_FONT_VARIANT_CAPS_ALL_PETITE );
621       _caps_unicase.set_active(    _caps_all & SP_CSS_FONT_VARIANT_CAPS_UNICASE );
622       _caps_titling.set_active(    _caps_all & SP_CSS_FONT_VARIANT_CAPS_TITLING );
623 
624       _caps_normal.set_inconsistent(     _caps_mix & SP_CSS_FONT_VARIANT_CAPS_NORMAL );
625       _caps_small.set_inconsistent(      _caps_mix & SP_CSS_FONT_VARIANT_CAPS_SMALL );
626       _caps_all_small.set_inconsistent(  _caps_mix & SP_CSS_FONT_VARIANT_CAPS_ALL_SMALL );
627       _caps_petite.set_inconsistent(     _caps_mix & SP_CSS_FONT_VARIANT_CAPS_PETITE );
628       _caps_all_petite.set_inconsistent( _caps_mix & SP_CSS_FONT_VARIANT_CAPS_ALL_PETITE );
629       _caps_unicase.set_inconsistent(    _caps_mix & SP_CSS_FONT_VARIANT_CAPS_UNICASE );
630       _caps_titling.set_inconsistent(    _caps_mix & SP_CSS_FONT_VARIANT_CAPS_TITLING );
631 
632       _numeric_all = query->font_variant_numeric.computed;
633       _numeric_mix = query->font_variant_numeric.value;
634 
635       if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_LINING_NUMS) {
636           _numeric_lining.set_active();
637       } else if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_OLDSTYLE_NUMS) {
638           _numeric_old_style.set_active();
639       } else {
640           _numeric_default_style.set_active();
641       }
642 
643       if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_PROPORTIONAL_NUMS) {
644           _numeric_proportional.set_active();
645       } else if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_TABULAR_NUMS) {
646           _numeric_tabular.set_active();
647       } else {
648           _numeric_default_width.set_active();
649       }
650 
651       if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS) {
652           _numeric_diagonal.set_active();
653       } else if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS) {
654           _numeric_stacked.set_active();
655       } else {
656           _numeric_default_fractions.set_active();
657       }
658 
659       _numeric_ordinal.set_active(      _numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_ORDINAL );
660       _numeric_slashed_zero.set_active( _numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_SLASHED_ZERO );
661 
662 
663       _numeric_lining.set_inconsistent(       _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_LINING_NUMS );
664       _numeric_old_style.set_inconsistent(    _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_OLDSTYLE_NUMS );
665       _numeric_proportional.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_PROPORTIONAL_NUMS );
666       _numeric_tabular.set_inconsistent(      _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_TABULAR_NUMS );
667       _numeric_diagonal.set_inconsistent(     _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS );
668       _numeric_stacked.set_inconsistent(      _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS );
669       _numeric_ordinal.set_inconsistent(      _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_ORDINAL );
670       _numeric_slashed_zero.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_SLASHED_ZERO );
671 
672       _asian_all = query->font_variant_east_asian.computed;
673       _asian_mix = query->font_variant_east_asian.value;
674 
675       if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS78) {
676           _asian_jis78.set_active();
677       } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS83) {
678           _asian_jis83.set_active();
679       } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS90) {
680           _asian_jis90.set_active();
681       } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS04) {
682           _asian_jis04.set_active();
683       } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED) {
684           _asian_simplified.set_active();
685       } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL) {
686           _asian_traditional.set_active();
687       } else {
688           _asian_default_variant.set_active();
689       }
690 
691       if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH) {
692           _asian_full_width.set_active();
693       } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_PROPORTIONAL_WIDTH) {
694           _asian_proportional_width.set_active();
695       } else {
696           _asian_default_width.set_active();
697       }
698 
699       _asian_ruby.set_active ( _asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_RUBY );
700 
701       _asian_jis78.set_inconsistent(             _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS78);
702       _asian_jis83.set_inconsistent(             _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS83);
703       _asian_jis90.set_inconsistent(             _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS90);
704       _asian_jis04.set_inconsistent(             _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS04);
705       _asian_simplified.set_inconsistent(        _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED);
706       _asian_traditional.set_inconsistent(       _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL);
707       _asian_full_width.set_inconsistent(        _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH);
708       _asian_proportional_width.set_inconsistent(_asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_PROPORTIONAL_WIDTH);
709       _asian_ruby.set_inconsistent(              _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_RUBY);
710 
711       // Fix me: Should match a space if second part matches.            ---,
712       //       : Add boundary to 'on' and 'off'.                            v
713       Glib::RefPtr<Glib::Regex> regex = Glib::Regex::create("\"(\\w{4})\"\\s*([0-9]+|on|off|)");
714       Glib::MatchInfo matchInfo;
715       std::string setting;
716 
717       // Set feature radiobutton (if it exists) or add to _feature_entry string.
718       char const *val = query->font_feature_settings.value();
719       if (val) {
720 
721           std::vector<Glib::ustring> tokens =
722               Glib::Regex::split_simple("\\s*,\\s*", val);
723 
724           for (auto token: tokens) {
725               regex->match(token, matchInfo);
726               if (matchInfo.matches()) {
727                   Glib::ustring table = matchInfo.fetch(1);
728                   Glib::ustring value = matchInfo.fetch(2);
729 
730                   if (_features.find(table) != _features.end()) {
731                       int v = 0;
732                       if (value == "0" || value == "off") v = 0;
733                       else if (value == "1" || value == "on" || value.empty() ) v = 1;
734                       else v = std::stoi(value);
735                       _features[table]->set_active(v);
736                   } else {
737                       setting += token + ", ";
738                   }
739               }
740           }
741       }
742 
743       // Remove final ", "
744       if (setting.length() > 1) {
745           setting.pop_back();
746           setting.pop_back();
747       }
748 
749       // Tables without radiobuttons.
750       _feature_entry.set_text( setting );
751 
752       if( different_features ) {
753           _feature_label.show();
754       } else {
755           _feature_label.hide();
756       }
757   }
758 
759   // Update GUI based on OpenType tables of selected font (which may be changed in font selector tab).
760   void
update_opentype(Glib::ustring & font_spec)761   FontVariants::update_opentype (Glib::ustring& font_spec) {
762 
763       // Disable/Enable based on available OpenType tables.
764       font_instance* res = font_factory::Default()->FaceFromFontSpecification( font_spec.c_str() );
765       if( res ) {
766 
767           std::map<Glib::ustring, OTSubstitution>::iterator it;
768 
769           if((it = res->openTypeTables.find("liga"))!= res->openTypeTables.end() ||
770              (it = res->openTypeTables.find("clig"))!= res->openTypeTables.end()) {
771               _ligatures_common.set_sensitive();
772           } else {
773               _ligatures_common.set_sensitive( false );
774           }
775 
776           if((it = res->openTypeTables.find("dlig"))!= res->openTypeTables.end()) {
777               _ligatures_discretionary.set_sensitive();
778           } else {
779               _ligatures_discretionary.set_sensitive( false );
780           }
781 
782           if((it = res->openTypeTables.find("hlig"))!= res->openTypeTables.end()) {
783               _ligatures_historical.set_sensitive();
784           } else {
785               _ligatures_historical.set_sensitive( false );
786           }
787 
788           if((it = res->openTypeTables.find("calt"))!= res->openTypeTables.end()) {
789               _ligatures_contextual.set_sensitive();
790           } else {
791               _ligatures_contextual.set_sensitive( false );
792           }
793 
794           if((it = res->openTypeTables.find("subs"))!= res->openTypeTables.end()) {
795               _position_sub.set_sensitive();
796           } else {
797               _position_sub.set_sensitive( false );
798           }
799 
800           if((it = res->openTypeTables.find("sups"))!= res->openTypeTables.end()) {
801               _position_super.set_sensitive();
802           } else {
803               _position_super.set_sensitive( false );
804           }
805 
806           if((it = res->openTypeTables.find("smcp"))!= res->openTypeTables.end()) {
807               _caps_small.set_sensitive();
808           } else {
809               _caps_small.set_sensitive( false );
810           }
811 
812           if((it = res->openTypeTables.find("c2sc"))!= res->openTypeTables.end() &&
813              (it = res->openTypeTables.find("smcp"))!= res->openTypeTables.end()) {
814               _caps_all_small.set_sensitive();
815           } else {
816               _caps_all_small.set_sensitive( false );
817           }
818 
819           if((it = res->openTypeTables.find("pcap"))!= res->openTypeTables.end()) {
820               _caps_petite.set_sensitive();
821           } else {
822               _caps_petite.set_sensitive( false );
823           }
824 
825           if((it = res->openTypeTables.find("c2sc"))!= res->openTypeTables.end() &&
826              (it = res->openTypeTables.find("pcap"))!= res->openTypeTables.end()) {
827               _caps_all_petite.set_sensitive();
828           } else {
829               _caps_all_petite.set_sensitive( false );
830           }
831 
832           if((it = res->openTypeTables.find("unic"))!= res->openTypeTables.end()) {
833               _caps_unicase.set_sensitive();
834           } else {
835               _caps_unicase.set_sensitive( false );
836           }
837 
838           if((it = res->openTypeTables.find("titl"))!= res->openTypeTables.end()) {
839               _caps_titling.set_sensitive();
840           } else {
841               _caps_titling.set_sensitive( false );
842           }
843 
844           if((it = res->openTypeTables.find("lnum"))!= res->openTypeTables.end()) {
845               _numeric_lining.set_sensitive();
846           } else {
847               _numeric_lining.set_sensitive( false );
848           }
849 
850           if((it = res->openTypeTables.find("onum"))!= res->openTypeTables.end()) {
851               _numeric_old_style.set_sensitive();
852           } else {
853               _numeric_old_style.set_sensitive( false );
854           }
855 
856           if((it = res->openTypeTables.find("pnum"))!= res->openTypeTables.end()) {
857               _numeric_proportional.set_sensitive();
858           } else {
859               _numeric_proportional.set_sensitive( false );
860           }
861 
862           if((it = res->openTypeTables.find("tnum"))!= res->openTypeTables.end()) {
863               _numeric_tabular.set_sensitive();
864           } else {
865               _numeric_tabular.set_sensitive( false );
866           }
867 
868           if((it = res->openTypeTables.find("frac"))!= res->openTypeTables.end()) {
869               _numeric_diagonal.set_sensitive();
870           } else {
871               _numeric_diagonal.set_sensitive( false );
872           }
873 
874           if((it = res->openTypeTables.find("afrac"))!= res->openTypeTables.end()) {
875               _numeric_stacked.set_sensitive();
876           } else {
877               _numeric_stacked.set_sensitive( false );
878           }
879 
880           if((it = res->openTypeTables.find("ordn"))!= res->openTypeTables.end()) {
881               _numeric_ordinal.set_sensitive();
882           } else {
883               _numeric_ordinal.set_sensitive( false );
884           }
885 
886           if((it = res->openTypeTables.find("zero"))!= res->openTypeTables.end()) {
887               _numeric_slashed_zero.set_sensitive();
888           } else {
889               _numeric_slashed_zero.set_sensitive( false );
890           }
891 
892           // East-Asian
893           if((it = res->openTypeTables.find("jp78"))!= res->openTypeTables.end()) {
894               _asian_jis78.set_sensitive();
895           } else {
896               _asian_jis78.set_sensitive( false );
897           }
898 
899           if((it = res->openTypeTables.find("jp83"))!= res->openTypeTables.end()) {
900               _asian_jis83.set_sensitive();
901           } else {
902               _asian_jis83.set_sensitive( false );
903           }
904 
905           if((it = res->openTypeTables.find("jp90"))!= res->openTypeTables.end()) {
906               _asian_jis90.set_sensitive();
907           } else {
908               _asian_jis90.set_sensitive( false );
909           }
910 
911           if((it = res->openTypeTables.find("jp04"))!= res->openTypeTables.end()) {
912               _asian_jis04.set_sensitive();
913           } else {
914               _asian_jis04.set_sensitive( false );
915           }
916 
917           if((it = res->openTypeTables.find("smpl"))!= res->openTypeTables.end()) {
918               _asian_simplified.set_sensitive();
919           } else {
920               _asian_simplified.set_sensitive( false );
921           }
922 
923           if((it = res->openTypeTables.find("trad"))!= res->openTypeTables.end()) {
924               _asian_traditional.set_sensitive();
925           } else {
926               _asian_traditional.set_sensitive( false );
927           }
928 
929           if((it = res->openTypeTables.find("fwid"))!= res->openTypeTables.end()) {
930               _asian_full_width.set_sensitive();
931           } else {
932               _asian_full_width.set_sensitive( false );
933           }
934 
935           if((it = res->openTypeTables.find("pwid"))!= res->openTypeTables.end()) {
936               _asian_proportional_width.set_sensitive();
937           } else {
938               _asian_proportional_width.set_sensitive( false );
939           }
940 
941           if((it = res->openTypeTables.find("ruby"))!= res->openTypeTables.end()) {
942               _asian_ruby.set_sensitive();
943           } else {
944               _asian_ruby.set_sensitive( false );
945           }
946 
947           // List available ligatures
948           Glib::ustring markup_liga;
949           Glib::ustring markup_dlig;
950           Glib::ustring markup_hlig;
951           Glib::ustring markup_calt;
952 
953           for (auto table: res->openTypeTables) {
954 
955               if (table.first == "liga" ||
956                   table.first == "clig" ||
957                   table.first == "dlig" ||
958                   table.first == "hgli" ||
959                   table.first == "calt") {
960 
961                   Glib::ustring markup;
962                   markup += "<span font_family='";
963                   markup += sp_font_description_get_family(res->descr);
964                   markup += "'>";
965                   markup += Glib::Markup::escape_text(table.second.output);
966                   markup += "</span>";
967 
968                   if (table.first == "liga") markup_liga += markup;
969                   if (table.first == "clig") markup_liga += markup;
970                   if (table.first == "dlig") markup_dlig += markup;
971                   if (table.first == "hlig") markup_hlig += markup;
972                   if (table.first == "calt") markup_calt += markup;
973               }
974           }
975 
976           _ligatures_label_common.set_markup        ( markup_liga.c_str() );
977           _ligatures_label_discretionary.set_markup ( markup_dlig.c_str() );
978           _ligatures_label_historical.set_markup    ( markup_hlig.c_str() );
979           _ligatures_label_contextual.set_markup    ( markup_calt.c_str() );
980 
981           // List available numeric variants
982           Glib::ustring markup_lnum;
983           Glib::ustring markup_onum;
984           Glib::ustring markup_pnum;
985           Glib::ustring markup_tnum;
986           Glib::ustring markup_frac;
987           Glib::ustring markup_afrc;
988           Glib::ustring markup_ordn;
989           Glib::ustring markup_zero;
990 
991           for (auto table: res->openTypeTables) {
992 
993               Glib::ustring markup;
994               markup += "<span font_family='";
995               markup += sp_font_description_get_family(res->descr);
996               markup += "' font_features='";
997               markup += table.first;
998               markup += "'>";
999               if (table.first == "lnum" ||
1000                   table.first == "onum" ||
1001                   table.first == "pnum" ||
1002                   table.first == "tnum") markup += "0123456789";
1003               if (table.first == "zero") markup += "0";
1004               if (table.first == "ordn") markup += "[" + table.second.before + "]" + table.second.output;
1005               if (table.first == "frac" ||
1006                   table.first == "afrc" ) markup += "1/2 2/3 3/4 4/5 5/6"; // Can we do better?
1007               markup += "</span>";
1008 
1009               if (table.first == "lnum") markup_lnum += markup;
1010               if (table.first == "onum") markup_onum += markup;
1011               if (table.first == "pnum") markup_pnum += markup;
1012               if (table.first == "tnum") markup_tnum += markup;
1013               if (table.first == "frac") markup_frac += markup;
1014               if (table.first == "afrc") markup_afrc += markup;
1015               if (table.first == "ordn") markup_ordn += markup;
1016               if (table.first == "zero") markup_zero += markup;
1017           }
1018 
1019           _numeric_lining_label.set_markup       ( markup_lnum.c_str() );
1020           _numeric_old_style_label.set_markup    ( markup_onum.c_str() );
1021           _numeric_proportional_label.set_markup ( markup_pnum.c_str() );
1022           _numeric_tabular_label.set_markup      ( markup_tnum.c_str() );
1023           _numeric_diagonal_label.set_markup     ( markup_frac.c_str() );
1024           _numeric_stacked_label.set_markup      ( markup_afrc.c_str() );
1025           _numeric_ordinal_label.set_markup      ( markup_ordn.c_str() );
1026           _numeric_slashed_zero_label.set_markup ( markup_zero.c_str() );
1027 
1028           // Make list of tables not handled above.
1029           std::map<Glib::ustring, OTSubstitution> table_copy = res->openTypeTables;
1030           if( (it = table_copy.find("liga")) != table_copy.end() ) table_copy.erase( it );
1031           if( (it = table_copy.find("clig")) != table_copy.end() ) table_copy.erase( it );
1032           if( (it = table_copy.find("dlig")) != table_copy.end() ) table_copy.erase( it );
1033           if( (it = table_copy.find("hlig")) != table_copy.end() ) table_copy.erase( it );
1034           if( (it = table_copy.find("calt")) != table_copy.end() ) table_copy.erase( it );
1035 
1036           if( (it = table_copy.find("subs")) != table_copy.end() ) table_copy.erase( it );
1037           if( (it = table_copy.find("sups")) != table_copy.end() ) table_copy.erase( it );
1038 
1039           if( (it = table_copy.find("smcp")) != table_copy.end() ) table_copy.erase( it );
1040           if( (it = table_copy.find("c2sc")) != table_copy.end() ) table_copy.erase( it );
1041           if( (it = table_copy.find("pcap")) != table_copy.end() ) table_copy.erase( it );
1042           if( (it = table_copy.find("c2pc")) != table_copy.end() ) table_copy.erase( it );
1043           if( (it = table_copy.find("unic")) != table_copy.end() ) table_copy.erase( it );
1044           if( (it = table_copy.find("titl")) != table_copy.end() ) table_copy.erase( it );
1045 
1046           if( (it = table_copy.find("lnum")) != table_copy.end() ) table_copy.erase( it );
1047           if( (it = table_copy.find("onum")) != table_copy.end() ) table_copy.erase( it );
1048           if( (it = table_copy.find("pnum")) != table_copy.end() ) table_copy.erase( it );
1049           if( (it = table_copy.find("tnum")) != table_copy.end() ) table_copy.erase( it );
1050           if( (it = table_copy.find("frac")) != table_copy.end() ) table_copy.erase( it );
1051           if( (it = table_copy.find("afrc")) != table_copy.end() ) table_copy.erase( it );
1052           if( (it = table_copy.find("ordn")) != table_copy.end() ) table_copy.erase( it );
1053           if( (it = table_copy.find("zero")) != table_copy.end() ) table_copy.erase( it );
1054 
1055           if( (it = table_copy.find("jp78")) != table_copy.end() ) table_copy.erase( it );
1056           if( (it = table_copy.find("jp83")) != table_copy.end() ) table_copy.erase( it );
1057           if( (it = table_copy.find("jp90")) != table_copy.end() ) table_copy.erase( it );
1058           if( (it = table_copy.find("jp04")) != table_copy.end() ) table_copy.erase( it );
1059           if( (it = table_copy.find("smpl")) != table_copy.end() ) table_copy.erase( it );
1060           if( (it = table_copy.find("trad")) != table_copy.end() ) table_copy.erase( it );
1061           if( (it = table_copy.find("fwid")) != table_copy.end() ) table_copy.erase( it );
1062           if( (it = table_copy.find("pwid")) != table_copy.end() ) table_copy.erase( it );
1063           if( (it = table_copy.find("ruby")) != table_copy.end() ) table_copy.erase( it );
1064 
1065           // An incomplete list of tables that should not be exposed to the user:
1066           if( (it = table_copy.find("abvf")) != table_copy.end() ) table_copy.erase( it );
1067           if( (it = table_copy.find("abvs")) != table_copy.end() ) table_copy.erase( it );
1068           if( (it = table_copy.find("akhn")) != table_copy.end() ) table_copy.erase( it );
1069           if( (it = table_copy.find("blwf")) != table_copy.end() ) table_copy.erase( it );
1070           if( (it = table_copy.find("blws")) != table_copy.end() ) table_copy.erase( it );
1071           if( (it = table_copy.find("ccmp")) != table_copy.end() ) table_copy.erase( it );
1072           if( (it = table_copy.find("cjct")) != table_copy.end() ) table_copy.erase( it );
1073           if( (it = table_copy.find("dnom")) != table_copy.end() ) table_copy.erase( it );
1074           if( (it = table_copy.find("dtls")) != table_copy.end() ) table_copy.erase( it );
1075           if( (it = table_copy.find("fina")) != table_copy.end() ) table_copy.erase( it );
1076           if( (it = table_copy.find("half")) != table_copy.end() ) table_copy.erase( it );
1077           if( (it = table_copy.find("haln")) != table_copy.end() ) table_copy.erase( it );
1078           if( (it = table_copy.find("init")) != table_copy.end() ) table_copy.erase( it );
1079           if( (it = table_copy.find("isol")) != table_copy.end() ) table_copy.erase( it );
1080           if( (it = table_copy.find("locl")) != table_copy.end() ) table_copy.erase( it );
1081           if( (it = table_copy.find("medi")) != table_copy.end() ) table_copy.erase( it );
1082           if( (it = table_copy.find("nukt")) != table_copy.end() ) table_copy.erase( it );
1083           if( (it = table_copy.find("numr")) != table_copy.end() ) table_copy.erase( it );
1084           if( (it = table_copy.find("pref")) != table_copy.end() ) table_copy.erase( it );
1085           if( (it = table_copy.find("pres")) != table_copy.end() ) table_copy.erase( it );
1086           if( (it = table_copy.find("pstf")) != table_copy.end() ) table_copy.erase( it );
1087           if( (it = table_copy.find("psts")) != table_copy.end() ) table_copy.erase( it );
1088           if( (it = table_copy.find("rlig")) != table_copy.end() ) table_copy.erase( it );
1089           if( (it = table_copy.find("rkrf")) != table_copy.end() ) table_copy.erase( it );
1090           if( (it = table_copy.find("rphf")) != table_copy.end() ) table_copy.erase( it );
1091           if( (it = table_copy.find("rtlm")) != table_copy.end() ) table_copy.erase( it );
1092           if( (it = table_copy.find("ssty")) != table_copy.end() ) table_copy.erase( it );
1093           if( (it = table_copy.find("vatu")) != table_copy.end() ) table_copy.erase( it );
1094 
1095           // Clear out old features
1096           auto children = _feature_grid.get_children();
1097           for (auto child: children) {
1098               _feature_grid.remove (*child);
1099           }
1100           _features.clear();
1101 
1102           std::string markup;
1103           int grid_row = 0;
1104 
1105           // GSUB lookup type 1 (1 to 1 mapping).
1106           for (auto table: res->openTypeTables) {
1107               if (table.first == "case" ||
1108                   table.first == "hist" ||
1109                   (table.first[0] == 's' && table.first[1] == 's' && !(table.first[2] == 't'))) {
1110 
1111                   if( (it = table_copy.find(table.first)) != table_copy.end() ) table_copy.erase( it );
1112 
1113                   _features[table.first] = new Feature (table.first, table.second, 2,
1114                                                         sp_font_description_get_family(res->descr),
1115                                                         _feature_grid, grid_row, this);
1116                   grid_row++;
1117               }
1118           }
1119 
1120           // GSUB lookup type 3 (1 to many mapping). Optionally type 1.
1121           for (auto table: res->openTypeTables) {
1122               if (table.first == "salt" ||
1123                   table.first == "swsh" ||
1124                   table.first == "cwsh" ||
1125                   table.first == "ornm" ||
1126                   table.first == "nalt" ||
1127                   (table.first[0] == 'c' && table.first[1] == 'v')) {
1128 
1129                   if (table.second.input.length() == 0) {
1130                       // This can happen if a table is not in the 'DFLT' script and 'dflt' language.
1131                       // We should be using the 'lang' attribute to find the correct tables.
1132                       // std::cerr << "FontVariants::open_type_update: "
1133                       //           << table.first << " has no entries!" << std::endl;
1134                       continue;
1135                   }
1136 
1137                   if( (it = table_copy.find(table.first)) != table_copy.end() ) table_copy.erase( it );
1138 
1139                   // Our lame attempt at determining number of alternative glyphs for one glyph:
1140                   int number = table.second.output.length() / table.second.input.length();
1141                   if (number < 1) {
1142                       number = 1; // Must have at least on/off, see comment above about 'lang' attribute.
1143                       // std::cout << table.first << " "
1144                       //           << table.second.output.length() << "/"
1145                       //           << table.second.input.length() << "="
1146                       //           << number << std::endl;
1147                   }
1148 
1149                   _features[table.first] = new Feature (table.first, table.second, number+1,
1150                                                         sp_font_description_get_family(res->descr),
1151                                                         _feature_grid, grid_row, this);
1152                   grid_row++;
1153               }
1154           }
1155 
1156           _feature_grid.show_all();
1157 
1158           _feature_substitutions.set_markup ( markup.c_str() );
1159 
1160           std::string ott_list = "OpenType tables not included above: ";
1161           for(it = table_copy.begin(); it != table_copy.end(); ++it) {
1162               ott_list += it->first;
1163               ott_list += ", ";
1164           }
1165 
1166           if (table_copy.size() > 0) {
1167               ott_list.pop_back();
1168               ott_list.pop_back();
1169               _feature_list.set_text( ott_list.c_str() );
1170           } else {
1171               _feature_list.set_text( "" );
1172           }
1173 
1174       } else {
1175           std::cerr << "FontVariants::update(): Couldn't find font_instance for: "
1176                     << font_spec << std::endl;
1177       }
1178 
1179       _ligatures_changed = false;
1180       _position_changed  = false;
1181       _caps_changed      = false;
1182       _numeric_changed   = false;
1183       _feature_changed   = false;
1184   }
1185 
1186   void
fill_css(SPCSSAttr * css)1187   FontVariants::fill_css( SPCSSAttr *css ) {
1188 
1189       // Ligatures
1190       bool common        = _ligatures_common.get_active();
1191       bool discretionary = _ligatures_discretionary.get_active();
1192       bool historical    = _ligatures_historical.get_active();
1193       bool contextual    = _ligatures_contextual.get_active();
1194 
1195       if( !common && !discretionary && !historical && !contextual ) {
1196           sp_repr_css_set_property(css, "font-variant-ligatures", "none" );
1197       } else if ( common && !discretionary && !historical && contextual ) {
1198           sp_repr_css_set_property(css, "font-variant-ligatures", "normal" );
1199       } else {
1200           Glib::ustring css_string;
1201           if ( !common )
1202               css_string += "no-common-ligatures ";
1203           if ( discretionary )
1204               css_string += "discretionary-ligatures ";
1205           if ( historical )
1206               css_string += "historical-ligatures ";
1207           if ( !contextual )
1208               css_string += "no-contextual ";
1209           sp_repr_css_set_property(css, "font-variant-ligatures", css_string.c_str() );
1210       }
1211 
1212       // Position
1213       {
1214           unsigned position_new = SP_CSS_FONT_VARIANT_POSITION_NORMAL;
1215           Glib::ustring css_string;
1216           if( _position_normal.get_active() ) {
1217               css_string = "normal";
1218           } else if( _position_sub.get_active() ) {
1219               css_string = "sub";
1220               position_new = SP_CSS_FONT_VARIANT_POSITION_SUB;
1221           } else if( _position_super.get_active() ) {
1222               css_string = "super";
1223               position_new = SP_CSS_FONT_VARIANT_POSITION_SUPER;
1224           }
1225 
1226           // 'if' may not be necessary... need to test.
1227           if( (_position_all != position_new) || ((_position_mix != 0) && _position_changed) ) {
1228               sp_repr_css_set_property(css, "font-variant-position", css_string.c_str() );
1229           }
1230       }
1231 
1232       // Caps
1233       {
1234           //unsigned caps_new;
1235           Glib::ustring css_string;
1236           if( _caps_normal.get_active() ) {
1237               css_string = "normal";
1238           //    caps_new = SP_CSS_FONT_VARIANT_CAPS_NORMAL;
1239           } else if( _caps_small.get_active() ) {
1240               css_string = "small-caps";
1241           //    caps_new = SP_CSS_FONT_VARIANT_CAPS_SMALL;
1242           } else if( _caps_all_small.get_active() ) {
1243               css_string = "all-small-caps";
1244           //    caps_new = SP_CSS_FONT_VARIANT_CAPS_ALL_SMALL;
1245           } else if( _caps_petite.get_active() ) {
1246               css_string = "petite";
1247           //    caps_new = SP_CSS_FONT_VARIANT_CAPS_PETITE;
1248           } else if( _caps_all_petite.get_active() ) {
1249               css_string = "all-petite";
1250           //    caps_new = SP_CSS_FONT_VARIANT_CAPS_ALL_PETITE;
1251           } else if( _caps_unicase.get_active() ) {
1252               css_string = "unicase";
1253           //    caps_new = SP_CSS_FONT_VARIANT_CAPS_UNICASE;
1254           } else if( _caps_titling.get_active() ) {
1255               css_string = "titling";
1256           //    caps_new = SP_CSS_FONT_VARIANT_CAPS_TITLING;
1257           //} else {
1258           //    caps_new = SP_CSS_FONT_VARIANT_CAPS_NORMAL;
1259           }
1260 
1261           // May not be necessary... need to test.
1262           //if( (_caps_all != caps_new) || ((_caps_mix != 0) && _caps_changed) ) {
1263           sp_repr_css_set_property(css, "font-variant-caps", css_string.c_str() );
1264           //}
1265       }
1266 
1267       // Numeric
1268       bool default_style = _numeric_default_style.get_active();
1269       bool lining        = _numeric_lining.get_active();
1270       bool old_style     = _numeric_old_style.get_active();
1271 
1272       bool default_width = _numeric_default_width.get_active();
1273       bool proportional  = _numeric_proportional.get_active();
1274       bool tabular       = _numeric_tabular.get_active();
1275 
1276       bool default_fractions = _numeric_default_fractions.get_active();
1277       bool diagonal          = _numeric_diagonal.get_active();
1278       bool stacked           = _numeric_stacked.get_active();
1279 
1280       bool ordinal           = _numeric_ordinal.get_active();
1281       bool slashed_zero      = _numeric_slashed_zero.get_active();
1282 
1283       if (default_style & default_width & default_fractions & !ordinal & !slashed_zero) {
1284           sp_repr_css_set_property(css, "font-variant-numeric", "normal");
1285       } else {
1286           Glib::ustring css_string;
1287           if ( lining )
1288               css_string += "lining-nums ";
1289           if ( old_style )
1290               css_string += "oldstyle-nums ";
1291           if ( proportional )
1292               css_string += "proportional-nums ";
1293           if ( tabular )
1294               css_string += "tabular-nums ";
1295           if ( diagonal )
1296               css_string += "diagonal-fractions ";
1297           if ( stacked )
1298               css_string += "stacked-fractions ";
1299           if ( ordinal )
1300               css_string += "ordinal ";
1301           if ( slashed_zero )
1302               css_string += "slashed-zero ";
1303           sp_repr_css_set_property(css, "font-variant-numeric", css_string.c_str() );
1304       }
1305 
1306       // East Asian
1307       bool jis78           = _asian_jis78.get_active();
1308       bool jis83           = _asian_jis83.get_active();
1309       bool jis90           = _asian_jis90.get_active();
1310       bool jis04           = _asian_jis04.get_active();
1311       bool simplified      = _asian_simplified.get_active();
1312       bool traditional     = _asian_traditional.get_active();
1313       bool asian_width     = _asian_default_width.get_active();
1314       bool fwid            = _asian_full_width.get_active();
1315       bool pwid            = _asian_proportional_width.get_active();
1316       bool ruby            = _asian_ruby.get_active();
1317 
1318       if (default_style & asian_width & !ruby) {
1319           sp_repr_css_set_property(css, "font-variant-east-asian", "normal");
1320       } else {
1321           Glib::ustring css_string;
1322           if (jis78)          css_string += "jis78 ";
1323           if (jis83)          css_string += "jis83 ";
1324           if (jis90)          css_string += "jis90 ";
1325           if (jis04)          css_string += "jis04 ";
1326           if (simplified)     css_string += "simplfied ";
1327           if (traditional)    css_string += "traditional ";
1328 
1329           if (fwid)           css_string += "fwid ";
1330           if (pwid)           css_string += "pwid ";
1331 
1332           if (ruby)           css_string += "ruby ";
1333 
1334           sp_repr_css_set_property(css, "font-variant-east-asian", css_string.c_str() );
1335       }
1336 
1337       // Feature settings
1338       Glib::ustring feature_string;
1339       for (auto i: _features) {
1340           feature_string += i.second->get_css();
1341       }
1342 
1343       feature_string += _feature_entry.get_text();
1344       // std::cout << "feature_string: " << feature_string << std::endl;
1345 
1346       if (!feature_string.empty()) {
1347           sp_repr_css_set_property(css, "font-feature-settings", feature_string.c_str());
1348       } else {
1349           sp_repr_css_unset_property(css, "font-feature-settings");
1350       }
1351   }
1352 
1353   Glib::ustring
get_markup()1354   FontVariants::get_markup() {
1355 
1356       Glib::ustring markup;
1357 
1358       // Ligatures
1359       bool common        = _ligatures_common.get_active();
1360       bool discretionary = _ligatures_discretionary.get_active();
1361       bool historical    = _ligatures_historical.get_active();
1362       bool contextual    = _ligatures_contextual.get_active();
1363 
1364       if (!common)         markup += "liga=0,clig=0,"; // On by default.
1365       if (discretionary)   markup += "dlig=1,";
1366       if (historical)      markup += "hlig=1,";
1367       if (contextual)      markup += "calt=1,";
1368 
1369       // Position
1370       if (      _position_sub.get_active()    ) markup += "subs=1,";
1371       else if ( _position_super.get_active()  ) markup += "sups=1,";
1372 
1373       // Caps
1374       if (      _caps_small.get_active()      ) markup += "smcp=1,";
1375       else if ( _caps_all_small.get_active()  ) markup += "c2sc=1,smcp=1,";
1376       else if ( _caps_petite.get_active()     ) markup += "pcap=1,";
1377       else if ( _caps_all_petite.get_active() ) markup += "c2pc=1,pcap=1,";
1378       else if ( _caps_unicase.get_active()    ) markup += "unic=1,";
1379       else if ( _caps_titling.get_active()    ) markup += "titl=1,";
1380 
1381       // Numeric
1382       bool lining        = _numeric_lining.get_active();
1383       bool old_style     = _numeric_old_style.get_active();
1384 
1385       bool proportional  = _numeric_proportional.get_active();
1386       bool tabular       = _numeric_tabular.get_active();
1387 
1388       bool diagonal          = _numeric_diagonal.get_active();
1389       bool stacked           = _numeric_stacked.get_active();
1390 
1391       bool ordinal           = _numeric_ordinal.get_active();
1392       bool slashed_zero      = _numeric_slashed_zero.get_active();
1393 
1394       if (lining)          markup += "lnum=1,";
1395       if (old_style)       markup += "onum=1,";
1396       if (proportional)    markup += "pnum=1,";
1397       if (tabular)         markup += "tnum=1,";
1398       if (diagonal)        markup += "frac=1,";
1399       if (stacked)         markup += "afrc=1,";
1400       if (ordinal)         markup += "ordn=1,";
1401       if (slashed_zero)    markup += "zero=1,";
1402 
1403       // East Asian
1404       bool jis78           = _asian_jis78.get_active();
1405       bool jis83           = _asian_jis83.get_active();
1406       bool jis90           = _asian_jis90.get_active();
1407       bool jis04           = _asian_jis04.get_active();
1408       bool simplified      = _asian_simplified.get_active();
1409       bool traditional     = _asian_traditional.get_active();
1410       //bool asian_width     = _asian_default_width.get_active();
1411       bool fwid            = _asian_full_width.get_active();
1412       bool pwid            = _asian_proportional_width.get_active();
1413       bool ruby            = _asian_ruby.get_active();
1414 
1415       if (jis78          )   markup += "jp78=1,";
1416       if (jis83          )   markup += "jp83=1,";
1417       if (jis90          )   markup += "jp90=1,";
1418       if (jis04          )   markup += "jp04=1,";
1419       if (simplified     )   markup += "smpl=1,";
1420       if (traditional    )   markup += "trad=1,";
1421 
1422       if (fwid           )   markup += "fwid=1,";
1423       if (pwid           )   markup += "pwid=1,";
1424 
1425       if (ruby           )   markup += "ruby=1,";
1426 
1427       // Feature settings
1428       Glib::ustring feature_string;
1429       for (auto i: _features) {
1430           feature_string += i.second->get_css();
1431       }
1432 
1433       feature_string += _feature_entry.get_text();
1434       if (!feature_string.empty()) {
1435           markup += feature_string;
1436       }
1437 
1438       // std::cout << "|" << markup << "|" << std::endl;
1439       return markup;
1440   }
1441 
1442 } // namespace Widget
1443 } // namespace UI
1444 } // namespace Inkscape
1445 
1446 /*
1447   Local Variables:
1448   mode:c++
1449   c-file-style:"stroustrup"
1450   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1451   indent-tabs-mode:nil
1452   fill-column:99
1453   End:
1454 */
1455 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
1456