// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Author:
 *   Tavmjong Bah <tavmjong@free.fr>
 *
 * Copyright (C) 2015, 2018 Tavmong Bah
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include "font-variants.h"

#include <iostream>
#include <string>
#include <vector>
#include <glibmm/i18n.h>
#include <glibmm/markup.h>
#include <glibmm/regex.h>
#include <gtkmm/flowbox.h>
#include <gtkmm/scrolledwindow.h>
#include <libnrtype/font-factory.h>
#include <libnrtype/font-instance.h>
#include <sigc++/functors/mem_fun.h>

// For updating from selection
#include "desktop.h"
#include "object/sp-text.h"
#include "ui/pack.h"
#include "ui/util.h"

namespace Inkscape::UI::Widget {

// A simple class to handle UI for one feature. We could of derived this from Gtk::HBox but by
// attaching widgets directly to Gtk::Grid, we keep columns lined up (which may or may not be a
// good thing).
class Feature
{
public:
    Feature(Glib::ustring const &name, OTSubstitution const &glyphs, int options, Glib::ustring family, Gtk::Grid& grid, int &row, FontVariants* parent)
        : _name (name)
    {
        auto const table_name = Gtk::make_managed<Gtk::Label>();
        table_name->set_markup ("\"" + name + "\" ");

        grid.attach (*table_name, 0, row, 1, 1);

        Gtk::FlowBox* flow_box = nullptr;
        Gtk::ScrolledWindow* scrolled_window = nullptr;
        if (options > 2) {
            // If there are more than 2 option, pack them into a flowbox instead of directly putting them in the grid.
            // Some fonts might have a table with many options (Bungee Hairline table 'ornm' has 113 entries).
            flow_box = Gtk::make_managed<Gtk::FlowBox>();
            flow_box->set_selection_mode(); // Turn off selection
            flow_box->set_homogeneous();
            flow_box->set_max_children_per_line (100); // Override default value
            flow_box->set_min_children_per_line (10);  // Override default value

            // We pack this into a scrollbar... otherwise the minimum height is set to what is required to fit all
            // flow box children into the flow box when the flow box has minimum width. (Crazy if you ask me!)
            scrolled_window = Gtk::make_managed<Gtk::ScrolledWindow>();
            scrolled_window->set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
            scrolled_window->add(*flow_box);
        }

        Gtk::RadioButton::Group group;
        for (int i = 0; i < options; ++i) {
            // Create radio button and create or add to button group.
            auto const button = Gtk::make_managed<Gtk::RadioButton>();
            if (i == 0) {
                group = button->get_group();
            } else {
                button->set_group (group);
            }
            button->signal_clicked().connect ( sigc::mem_fun(*parent, &FontVariants::feature_callback) );
            buttons.push_back (button);

            // Create label.
            auto const label = Gtk::make_managed<Gtk::Label>();

            // Restrict label width (some fonts have lots of alternatives).
            label->set_line_wrap( true );
            label->set_line_wrap_mode( Pango::WRAP_WORD_CHAR );
            label->set_ellipsize( Pango::ELLIPSIZE_END );
            label->set_lines(3);
            label->set_hexpand();

            Glib::ustring markup;
            markup += "<span font_family='";
            markup += family;
            markup += "' font_features='";
            markup += name;
            markup += " ";
            markup += std::to_string (i);
            markup += "'>";
            markup += Glib::Markup::escape_text (glyphs.input);
            markup += "</span>";
            label->set_markup (markup);

            // Add button and label to widget
            if (!flow_box) {
                // Attach directly to grid (keeps things aligned row-to-row).
                grid.attach (*button, 2*i+1, row, 1, 1);
                grid.attach (*label,  2*i+2, row, 1, 1);
            } else {
                // Pack into FlowBox

                // Pack button and label into a box so they stay together.
                auto const box = Gtk::make_managed<Gtk::Box>();
                box->add(*button);
                box->add(*label);

                flow_box->add(*box);
            }
        }

        if (scrolled_window) {
            grid.attach (*scrolled_window, 1, row, 4, 1);
        }
    }

    Glib::ustring
    get_css()
    {
        int i = 0;
        for (auto b: buttons) {
            if (b->get_active()) {
                if (i == 0) {
                    // Features are always off by default (for those handled here).
                    return "";
                } else if (i == 1) {
                    // Feature without value has implied value of 1.
                    return ("\"" + _name + "\", ");
                } else {
                    // Feature with value greater than 1 must be explicitly set.
                    return ("\"" + _name + "\" " + std::to_string (i) + ", ");
                }
            }
            ++i;
        }
        return "";
    }

    void
    set_active(int i)
    {
        if (i < buttons.size()) {
            buttons[i]->set_active();
        }
    }

private:
    Glib::ustring _name;
    std::vector <Gtk::RadioButton*> buttons;
};

FontVariants::FontVariants () :
  Gtk::Box (Gtk::ORIENTATION_VERTICAL),
  _ligatures_frame          ( Glib::ustring(C_("Font feature", "Ligatures"    )) ),
  _ligatures_common         ( Glib::ustring(C_("Font feature", "Common"       )) ),
  _ligatures_discretionary  ( Glib::ustring(C_("Font feature", "Discretionary")) ),
  _ligatures_historical     ( Glib::ustring(C_("Font feature", "Historical"   )) ),
  _ligatures_contextual     ( Glib::ustring(C_("Font feature", "Contextual"   )) ),

  _position_frame           ( Glib::ustring(C_("Font feature", "Position"     )) ),
  _position_normal          ( Glib::ustring(C_("Font feature", "Normal"       )) ),
  _position_sub             ( Glib::ustring(C_("Font feature", "Subscript"    )) ),
  _position_super           ( Glib::ustring(C_("Font feature", "Superscript"  )) ),

  _caps_frame               ( Glib::ustring(C_("Font feature", "Capitals"     )) ),
  _caps_normal              ( Glib::ustring(C_("Font feature", "Normal"       )) ),
  _caps_small               ( Glib::ustring(C_("Font feature", "Small"        )) ),
  _caps_all_small           ( Glib::ustring(C_("Font feature", "All small"    )) ),
  _caps_petite              ( Glib::ustring(C_("Font feature", "Petite"       )) ),
  _caps_all_petite          ( Glib::ustring(C_("Font feature", "All petite"   )) ),
  _caps_unicase             ( Glib::ustring(C_("Font feature", "Unicase"      )) ),
  _caps_titling             ( Glib::ustring(C_("Font feature", "Titling"      )) ),

  _numeric_frame            ( Glib::ustring(C_("Font feature", "Numeric"      )) ),
  _numeric_lining           ( Glib::ustring(C_("Font feature", "Lining"       )) ),
  _numeric_old_style        ( Glib::ustring(C_("Font feature", "Old Style"    )) ),
  _numeric_default_style    ( Glib::ustring(C_("Font feature", "Default Style")) ),
  _numeric_proportional     ( Glib::ustring(C_("Font feature", "Proportional" )) ),
  _numeric_tabular          ( Glib::ustring(C_("Font feature", "Tabular"      )) ),
  _numeric_default_width    ( Glib::ustring(C_("Font feature", "Default Width")) ),
  _numeric_diagonal         ( Glib::ustring(C_("Font feature", "Diagonal"     )) ),
  _numeric_stacked          ( Glib::ustring(C_("Font feature", "Stacked"      )) ),
  _numeric_default_fractions( Glib::ustring(C_("Font feature", "Default Fractions")) ),
  _numeric_ordinal          ( Glib::ustring(C_("Font feature", "Ordinal"      )) ),
  _numeric_slashed_zero     ( Glib::ustring(C_("Font feature", "Slashed Zero" )) ),

  _asian_frame              ( Glib::ustring(C_("Font feature", "East Asian"   )) ),
  _asian_default_variant    ( Glib::ustring(C_("Font feature", "Default"      )) ),
  _asian_jis78              ( Glib::ustring(C_("Font feature", "JIS78"        )) ),
  _asian_jis83              ( Glib::ustring(C_("Font feature", "JIS83"        )) ),
  _asian_jis90              ( Glib::ustring(C_("Font feature", "JIS90"        )) ),
  _asian_jis04              ( Glib::ustring(C_("Font feature", "JIS04"        )) ),
  _asian_simplified         ( Glib::ustring(C_("Font feature", "Simplified"   )) ),
  _asian_traditional        ( Glib::ustring(C_("Font feature", "Traditional"  )) ),
  _asian_default_width      ( Glib::ustring(C_("Font feature", "Default"      )) ),
  _asian_full_width         ( Glib::ustring(C_("Font feature", "Full Width"   )) ),
  _asian_proportional_width ( Glib::ustring(C_("Font feature", "Proportional" )) ),
  _asian_ruby               ( Glib::ustring(C_("Font feature", "Ruby"         )) ),

  _feature_frame            ( Glib::ustring(C_("Font feature", "Feature Settings")) ),
  _feature_label            ( Glib::ustring(C_("Font feature", "Selection has different Feature Settings!")) ),
  
  _ligatures_changed( false ),
  _position_changed( false ),
  _caps_changed( false ),
  _numeric_changed( false ),
  _asian_changed( false ),
  _feature_vbox(Gtk::ORIENTATION_VERTICAL)

{

  set_name ( "FontVariants" );

  // Ligatures --------------------------

  // Add tooltips
  _ligatures_common.set_tooltip_text(
    _("Common ligatures. On by default. OpenType tables: 'liga', 'clig'"));
  _ligatures_discretionary.set_tooltip_text(
    _("Discretionary ligatures. Off by default. OpenType table: 'dlig'"));
  _ligatures_historical.set_tooltip_text(
    _("Historical ligatures. Off by default. OpenType table: 'hlig'"));
  _ligatures_contextual.set_tooltip_text(
    _("Contextual forms. On by default. OpenType table: 'calt'"));

  // Add signals
  _ligatures_common.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::ligatures_callback) );
  _ligatures_discretionary.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::ligatures_callback) );
  _ligatures_historical.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::ligatures_callback) );
  _ligatures_contextual.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::ligatures_callback) );

  // Restrict label widths (some fonts have lots of ligatures). Must also set ellipsize mode.
  Gtk::Label* labels[] = {
      &_ligatures_label_common,
      &_ligatures_label_discretionary,
      &_ligatures_label_historical,
      &_ligatures_label_contextual
  };
  for (auto label : labels) {
      // char limit - not really needed, since number of lines is restricted
      label->set_max_width_chars(999);
      // show ellipsis when text overflows
      label->set_ellipsize(Pango::ELLIPSIZE_END);
      // up to 5 lines
      label->set_lines(5);
      // multiline
      label->set_line_wrap();
      // break it as needed
      label->set_line_wrap_mode(Pango::WRAP_WORD_CHAR);
  }

  // Allow user to select characters. Not useful as this selects the ligatures.
  // _ligatures_label_common.set_selectable(        true );
  // _ligatures_label_discretionary.set_selectable( true );
  // _ligatures_label_historical.set_selectable(    true );
  // _ligatures_label_contextual.set_selectable(    true );

  // Add to frame
  _ligatures_grid.attach( _ligatures_common,              0, 0, 1, 1);
  _ligatures_grid.attach( _ligatures_discretionary,       0, 1, 1, 1);
  _ligatures_grid.attach( _ligatures_historical,          0, 2, 1, 1);
  _ligatures_grid.attach( _ligatures_contextual,          0, 3, 1, 1);
  _ligatures_grid.attach( _ligatures_label_common,        1, 0, 1, 1);
  _ligatures_grid.attach( _ligatures_label_discretionary, 1, 1, 1, 1);
  _ligatures_grid.attach( _ligatures_label_historical,    1, 2, 1, 1);
  _ligatures_grid.attach( _ligatures_label_contextual,    1, 3, 1, 1);

  _ligatures_grid.set_margin_start(15);
  _ligatures_grid.set_margin_end(15);

  _ligatures_frame.add( _ligatures_grid );
  UI::pack_start(*this, _ligatures_frame, UI::PackOptions::shrink);

  ligatures_init();
  
  // Position ----------------------------------

  // Add tooltips
  _position_normal.set_tooltip_text( _("Normal position."));
  _position_sub.set_tooltip_text( _("Subscript. OpenType table: 'subs'") );
  _position_super.set_tooltip_text( _("Superscript. OpenType table: 'sups'") );

  // Group buttons
  Gtk::RadioButton::Group position_group = _position_normal.get_group();
  _position_sub.set_group(position_group);
  _position_super.set_group(position_group);

  // Add signals
  _position_normal.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::position_callback) );
  _position_sub.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::position_callback) );
  _position_super.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::position_callback) );

  // Add to frame
  _position_grid.attach( _position_normal, 0, 0, 1, 1);
  _position_grid.attach( _position_sub,    1, 0, 1, 1);
  _position_grid.attach( _position_super,  2, 0, 1, 1);

  _position_grid.set_margin_start(15);
  _position_grid.set_margin_end(15);

  _position_frame.add( _position_grid );
  UI::pack_start(*this, _position_frame, UI::PackOptions::shrink);

  position_init();

  // Caps ----------------------------------

  // Add tooltips
  _caps_normal.set_tooltip_text( _("Normal capitalization."));
  _caps_small.set_tooltip_text( _("Small-caps (lowercase). OpenType table: 'smcp'"));
  _caps_all_small.set_tooltip_text( _("All small-caps (uppercase and lowercase). OpenType tables: 'c2sc' and 'smcp'"));
  _caps_petite.set_tooltip_text( _("Petite-caps (lowercase). OpenType table: 'pcap'"));
  _caps_all_petite.set_tooltip_text( _("All petite-caps (uppercase and lowercase). OpenType tables: 'c2sc' and 'pcap'"));
  _caps_unicase.set_tooltip_text( _("Unicase (small caps for uppercase, normal for lowercase). OpenType table: 'unic'"));
  _caps_titling.set_tooltip_text( _("Titling caps (lighter-weight uppercase for use in titles). OpenType table: 'titl'"));

  // Group buttons
  Gtk::RadioButton::Group caps_group = _caps_normal.get_group();
  _caps_small.set_group(caps_group);
  _caps_all_small.set_group(caps_group);
  _caps_petite.set_group(caps_group);
  _caps_all_petite.set_group(caps_group);
  _caps_unicase.set_group(caps_group);
  _caps_titling.set_group(caps_group);

  // Add signals
  _caps_normal.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );
  _caps_small.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );
  _caps_all_small.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );
  _caps_petite.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );
  _caps_all_petite.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );
  _caps_unicase.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );
  _caps_titling.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );

  // Add to frame
  _caps_grid.attach( _caps_normal,     0, 0, 1, 1);
  _caps_grid.attach( _caps_unicase,    1, 0, 1, 1);
  _caps_grid.attach( _caps_titling,    2, 0, 1, 1);
  _caps_grid.attach( _caps_small,      0, 1, 1, 1);
  _caps_grid.attach( _caps_all_small,  1, 1, 1, 1);
  _caps_grid.attach( _caps_petite,     2, 1, 1, 1);
  _caps_grid.attach( _caps_all_petite, 3, 1, 1, 1);

  _caps_grid.set_margin_start(15);
  _caps_grid.set_margin_end(15);

  _caps_frame.add( _caps_grid );
  UI::pack_start(*this, _caps_frame, UI::PackOptions::shrink);

  caps_init();

  // Numeric ------------------------------

  // Add tooltips
  _numeric_default_style.set_tooltip_text( _("Normal style."));
  _numeric_lining.set_tooltip_text( _("Lining numerals. OpenType table: 'lnum'"));
  _numeric_old_style.set_tooltip_text( _("Old style numerals. OpenType table: 'onum'"));
  _numeric_default_width.set_tooltip_text( _("Normal widths."));
  _numeric_proportional.set_tooltip_text( _("Proportional width numerals. OpenType table: 'pnum'"));
  _numeric_tabular.set_tooltip_text( _("Same width numerals. OpenType table: 'tnum'"));
  _numeric_default_fractions.set_tooltip_text( _("Normal fractions."));
  _numeric_diagonal.set_tooltip_text( _("Diagonal fractions. OpenType table: 'frac'"));
  _numeric_stacked.set_tooltip_text( _("Stacked fractions. OpenType table: 'afrc'"));
  _numeric_ordinal.set_tooltip_text( _("Ordinals (raised 'th', etc.). OpenType table: 'ordn'"));
  _numeric_slashed_zero.set_tooltip_text( _("Slashed zeros. OpenType table: 'zero'"));

  // Group buttons
  Gtk::RadioButton::Group style_group = _numeric_default_style.get_group();
  _numeric_lining.set_group(style_group);
  _numeric_old_style.set_group(style_group);

  Gtk::RadioButton::Group width_group = _numeric_default_width.get_group();
  _numeric_proportional.set_group(width_group);
  _numeric_tabular.set_group(width_group);

  Gtk::RadioButton::Group fraction_group = _numeric_default_fractions.get_group();
  _numeric_diagonal.set_group(fraction_group);
  _numeric_stacked.set_group(fraction_group);

  // Add signals
  _numeric_default_style.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) );
  _numeric_lining.signal_clicked().connect (        sigc::mem_fun(*this, &FontVariants::numeric_callback) );
  _numeric_old_style.signal_clicked().connect (     sigc::mem_fun(*this, &FontVariants::numeric_callback) );
  _numeric_default_width.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) );
  _numeric_proportional.signal_clicked().connect (  sigc::mem_fun(*this, &FontVariants::numeric_callback) );
  _numeric_tabular.signal_clicked().connect (       sigc::mem_fun(*this, &FontVariants::numeric_callback) );
  _numeric_default_fractions.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) );
  _numeric_diagonal.signal_clicked().connect (      sigc::mem_fun(*this, &FontVariants::numeric_callback) );
  _numeric_stacked.signal_clicked().connect (       sigc::mem_fun(*this, &FontVariants::numeric_callback) );
  _numeric_ordinal.signal_clicked().connect (       sigc::mem_fun(*this, &FontVariants::numeric_callback) );
  _numeric_slashed_zero.signal_clicked().connect (  sigc::mem_fun(*this, &FontVariants::numeric_callback) );

  // Add to frame
  _numeric_grid.attach (_numeric_default_style,        0, 0, 1, 1);
  _numeric_grid.attach (_numeric_lining,               1, 0, 1, 1);
  _numeric_grid.attach (_numeric_lining_label,         2, 0, 1, 1);
  _numeric_grid.attach (_numeric_old_style,            3, 0, 1, 1);
  _numeric_grid.attach (_numeric_old_style_label,      4, 0, 1, 1);

  _numeric_grid.attach (_numeric_default_width,        0, 1, 1, 1);
  _numeric_grid.attach (_numeric_proportional,         1, 1, 1, 1);
  _numeric_grid.attach (_numeric_proportional_label,   2, 1, 1, 1);
  _numeric_grid.attach (_numeric_tabular,              3, 1, 1, 1);
  _numeric_grid.attach (_numeric_tabular_label,        4, 1, 1, 1);

  _numeric_grid.attach (_numeric_default_fractions,    0, 2, 1, 1);
  _numeric_grid.attach (_numeric_diagonal,             1, 2, 1, 1);
  _numeric_grid.attach (_numeric_diagonal_label,       2, 2, 1, 1);
  _numeric_grid.attach (_numeric_stacked,              3, 2, 1, 1);
  _numeric_grid.attach (_numeric_stacked_label,        4, 2, 1, 1);

  _numeric_grid.attach (_numeric_ordinal,              0, 3, 1, 1);
  _numeric_grid.attach (_numeric_ordinal_label,        1, 3, 4, 1);

  _numeric_grid.attach (_numeric_slashed_zero,         0, 4, 1, 1);
  _numeric_grid.attach (_numeric_slashed_zero_label,   1, 4, 1, 1);

  _numeric_grid.set_margin_start(15);
  _numeric_grid.set_margin_end(15);

  _numeric_frame.add( _numeric_grid );
  UI::pack_start(*this, _numeric_frame, UI::PackOptions::shrink);
  
  // East Asian

  // Add tooltips
  _asian_default_variant.set_tooltip_text (  _("Default variant."));
  _asian_jis78.set_tooltip_text(             _("JIS78 forms. OpenType table: 'jp78'."));
  _asian_jis83.set_tooltip_text(             _("JIS83 forms. OpenType table: 'jp83'."));
  _asian_jis90.set_tooltip_text(             _("JIS90 forms. OpenType table: 'jp90'."));
  _asian_jis04.set_tooltip_text(             _("JIS2004 forms. OpenType table: 'jp04'."));
  _asian_simplified.set_tooltip_text(        _("Simplified forms. OpenType table: 'smpl'."));
  _asian_traditional.set_tooltip_text(       _("Traditional forms. OpenType table: 'trad'."));
  _asian_default_width.set_tooltip_text (    _("Default width."));
  _asian_full_width.set_tooltip_text(        _("Full width variants. OpenType table: 'fwid'."));
  _asian_proportional_width.set_tooltip_text(_("Proportional width variants. OpenType table: 'pwid'."));
  _asian_ruby.set_tooltip_text(              _("Ruby variants. OpenType table: 'ruby'."));

  // Add signals
  _asian_default_variant.signal_clicked().connect (   sigc::mem_fun(*this, &FontVariants::asian_callback) );
  _asian_jis78.signal_clicked().connect (             sigc::mem_fun(*this, &FontVariants::asian_callback) );
  _asian_jis83.signal_clicked().connect (             sigc::mem_fun(*this, &FontVariants::asian_callback) );
  _asian_jis90.signal_clicked().connect (             sigc::mem_fun(*this, &FontVariants::asian_callback) );
  _asian_jis04.signal_clicked().connect (             sigc::mem_fun(*this, &FontVariants::asian_callback) );
  _asian_simplified.signal_clicked().connect (        sigc::mem_fun(*this, &FontVariants::asian_callback) );
  _asian_traditional.signal_clicked().connect (       sigc::mem_fun(*this, &FontVariants::asian_callback) );
  _asian_default_width.signal_clicked().connect (     sigc::mem_fun(*this, &FontVariants::asian_callback) );
  _asian_full_width.signal_clicked().connect (        sigc::mem_fun(*this, &FontVariants::asian_callback) );
  _asian_proportional_width.signal_clicked().connect (sigc::mem_fun(*this, &FontVariants::asian_callback) );
  _asian_ruby.signal_clicked().connect(               sigc::mem_fun(*this, &FontVariants::asian_callback) );

  // Add to frame
  _asian_grid.attach (_asian_default_variant,         0, 0, 1, 1);
  _asian_grid.attach (_asian_jis78,                   1, 0, 1, 1);
  _asian_grid.attach (_asian_jis83,                   2, 0, 1, 1);
  _asian_grid.attach (_asian_jis90,                   1, 1, 1, 1);
  _asian_grid.attach (_asian_jis04,                   2, 1, 1, 1);
  _asian_grid.attach (_asian_simplified,              1, 2, 1, 1);
  _asian_grid.attach (_asian_traditional,             2, 2, 1, 1);
  _asian_grid.attach (_asian_default_width,           0, 3, 1, 1);
  _asian_grid.attach (_asian_full_width,              1, 3, 1, 1);
  _asian_grid.attach (_asian_proportional_width,      2, 3, 1, 1);
  _asian_grid.attach (_asian_ruby,                    0, 4, 1, 1);

  _asian_grid.set_margin_start(15);
  _asian_grid.set_margin_end(15);

  _asian_frame.add( _asian_grid );
  UI::pack_start(*this, _asian_frame, UI::PackOptions::shrink);

  // Group Buttons
  Gtk::RadioButton::Group asian_variant_group = _asian_default_variant.get_group();
  _asian_jis78.set_group(asian_variant_group);
  _asian_jis83.set_group(asian_variant_group);
  _asian_jis90.set_group(asian_variant_group);
  _asian_jis04.set_group(asian_variant_group);
  _asian_simplified.set_group(asian_variant_group);
  _asian_traditional.set_group(asian_variant_group);

  Gtk::RadioButton::Group asian_width_group = _asian_default_width.get_group();
  _asian_full_width.set_group (asian_width_group);
  _asian_proportional_width.set_group (asian_width_group);

  // Feature settings ---------------------

  // Add tooltips
  _feature_entry.set_tooltip_text( _("Feature settings in CSS form (e.g. \"wxyz\" or \"wxyz\" 3)."));

  _feature_substitutions.set_justify( Gtk::JUSTIFY_LEFT );
  _feature_substitutions.set_line_wrap( true );
  _feature_substitutions.set_line_wrap_mode( Pango::WRAP_WORD_CHAR );

  _feature_list.set_justify( Gtk::JUSTIFY_LEFT );
  _feature_list.set_line_wrap( true );

  // Add to frame
  UI::pack_start(_feature_vbox, _feature_grid,          UI::PackOptions::shrink);
  UI::pack_start(_feature_vbox, _feature_entry,         UI::PackOptions::shrink);
  UI::pack_start(_feature_vbox, _feature_label,         UI::PackOptions::shrink);
  UI::pack_start(_feature_vbox, _feature_substitutions, UI::PackOptions::shrink);
  UI::pack_start(_feature_vbox, _feature_list,          UI::PackOptions::shrink);

  _feature_vbox.set_margin_start(15);
  _feature_vbox.set_margin_end(15);

  _feature_frame.add( _feature_vbox );
  UI::pack_start(*this, _feature_frame, UI::PackOptions::shrink);

  // Add signals
  _feature_entry.signal_changed().connect( sigc::mem_fun(*this, &FontVariants::feature_callback) );
  
  show_all_children();

}

void
FontVariants::ligatures_init() {
    // std::cout << "FontVariants::ligatures_init()" << std::endl;
}

void
FontVariants::ligatures_callback() {
    // std::cout << "FontVariants::ligatures_callback()" << std::endl;
    _ligatures_changed = true;
    _changed_signal.emit();
}

void
FontVariants::position_init() {
    // std::cout << "FontVariants::position_init()" << std::endl;
}

void
FontVariants::position_callback() {
    // std::cout << "FontVariants::position_callback()" << std::endl;
    _position_changed = true;
    _changed_signal.emit();
}

void
FontVariants::caps_init() {
    // std::cout << "FontVariants::caps_init()" << std::endl;
}

void
FontVariants::caps_callback() {
    // std::cout << "FontVariants::caps_callback()" << std::endl;
    _caps_changed = true;
    _changed_signal.emit();
}

void
FontVariants::numeric_init() {
    // std::cout << "FontVariants::numeric_init()" << std::endl;
}

void
FontVariants::numeric_callback() {
    // std::cout << "FontVariants::numeric_callback()" << std::endl;
    _numeric_changed = true;
    _changed_signal.emit();
}

void
FontVariants::asian_init() {
    // std::cout << "FontVariants::asian_init()" << std::endl;
}

void
FontVariants::asian_callback() {
    // std::cout << "FontVariants::asian_callback()" << std::endl;
    _asian_changed = true;
    _changed_signal.emit();
}

void
FontVariants::feature_init() {
    // std::cout << "FontVariants::feature_init()" << std::endl;
}

void
FontVariants::feature_callback() {
    // std::cout << "FontVariants::feature_callback()" << std::endl;
    _feature_changed = true;
    _changed_signal.emit();
}

// Update GUI based on query.
void
FontVariants::update( SPStyle const *query, bool different_features, Glib::ustring& font_spec ) {

    update_opentype( font_spec );

    _ligatures_all = query->font_variant_ligatures.computed;
    _ligatures_mix = query->font_variant_ligatures.value;

    _ligatures_common.set_active(       _ligatures_all & SP_CSS_FONT_VARIANT_LIGATURES_COMMON );
    _ligatures_discretionary.set_active(_ligatures_all & SP_CSS_FONT_VARIANT_LIGATURES_DISCRETIONARY );
    _ligatures_historical.set_active(   _ligatures_all & SP_CSS_FONT_VARIANT_LIGATURES_HISTORICAL );
    _ligatures_contextual.set_active(   _ligatures_all & SP_CSS_FONT_VARIANT_LIGATURES_CONTEXTUAL );
    
    _ligatures_common.set_inconsistent(        _ligatures_mix & SP_CSS_FONT_VARIANT_LIGATURES_COMMON );
    _ligatures_discretionary.set_inconsistent( _ligatures_mix & SP_CSS_FONT_VARIANT_LIGATURES_DISCRETIONARY );
    _ligatures_historical.set_inconsistent(    _ligatures_mix & SP_CSS_FONT_VARIANT_LIGATURES_HISTORICAL );
    _ligatures_contextual.set_inconsistent(    _ligatures_mix & SP_CSS_FONT_VARIANT_LIGATURES_CONTEXTUAL );

    _position_all = query->font_variant_position.computed;
    _position_mix = query->font_variant_position.value;
    
    _position_normal.set_active( _position_all & SP_CSS_FONT_VARIANT_POSITION_NORMAL );
    _position_sub.set_active(    _position_all & SP_CSS_FONT_VARIANT_POSITION_SUB );
    _position_super.set_active(  _position_all & SP_CSS_FONT_VARIANT_POSITION_SUPER );

    _position_normal.set_inconsistent( _position_mix & SP_CSS_FONT_VARIANT_POSITION_NORMAL );
    _position_sub.set_inconsistent(    _position_mix & SP_CSS_FONT_VARIANT_POSITION_SUB );
    _position_super.set_inconsistent(  _position_mix & SP_CSS_FONT_VARIANT_POSITION_SUPER );

    _caps_all = query->font_variant_caps.computed;
    _caps_mix = query->font_variant_caps.value;

    _caps_normal.set_active(     _caps_all & SP_CSS_FONT_VARIANT_CAPS_NORMAL );
    _caps_small.set_active(      _caps_all & SP_CSS_FONT_VARIANT_CAPS_SMALL );
    _caps_all_small.set_active(  _caps_all & SP_CSS_FONT_VARIANT_CAPS_ALL_SMALL );
    _caps_petite.set_active(     _caps_all & SP_CSS_FONT_VARIANT_CAPS_PETITE );
    _caps_all_petite.set_active( _caps_all & SP_CSS_FONT_VARIANT_CAPS_ALL_PETITE );
    _caps_unicase.set_active(    _caps_all & SP_CSS_FONT_VARIANT_CAPS_UNICASE );
    _caps_titling.set_active(    _caps_all & SP_CSS_FONT_VARIANT_CAPS_TITLING );

    _caps_normal.set_inconsistent(     _caps_mix & SP_CSS_FONT_VARIANT_CAPS_NORMAL );
    _caps_small.set_inconsistent(      _caps_mix & SP_CSS_FONT_VARIANT_CAPS_SMALL );
    _caps_all_small.set_inconsistent(  _caps_mix & SP_CSS_FONT_VARIANT_CAPS_ALL_SMALL );
    _caps_petite.set_inconsistent(     _caps_mix & SP_CSS_FONT_VARIANT_CAPS_PETITE );
    _caps_all_petite.set_inconsistent( _caps_mix & SP_CSS_FONT_VARIANT_CAPS_ALL_PETITE );
    _caps_unicase.set_inconsistent(    _caps_mix & SP_CSS_FONT_VARIANT_CAPS_UNICASE );
    _caps_titling.set_inconsistent(    _caps_mix & SP_CSS_FONT_VARIANT_CAPS_TITLING );

    _numeric_all = query->font_variant_numeric.computed;
    _numeric_mix = query->font_variant_numeric.value;

    if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_LINING_NUMS) {
        _numeric_lining.set_active();
    } else if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_OLDSTYLE_NUMS) {
        _numeric_old_style.set_active();
    } else {
        _numeric_default_style.set_active();
    }

    if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_PROPORTIONAL_NUMS) {
        _numeric_proportional.set_active();
    } else if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_TABULAR_NUMS) {
        _numeric_tabular.set_active();
    } else {
        _numeric_default_width.set_active();
    }

    if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS) {
        _numeric_diagonal.set_active();
    } else if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS) {
        _numeric_stacked.set_active();
    } else {
        _numeric_default_fractions.set_active();
    }

    _numeric_ordinal.set_active(      _numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_ORDINAL );
    _numeric_slashed_zero.set_active( _numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_SLASHED_ZERO );


    _numeric_lining.set_inconsistent(       _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_LINING_NUMS );
    _numeric_old_style.set_inconsistent(    _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_OLDSTYLE_NUMS );
    _numeric_proportional.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_PROPORTIONAL_NUMS );
    _numeric_tabular.set_inconsistent(      _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_TABULAR_NUMS );
    _numeric_diagonal.set_inconsistent(     _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS );
    _numeric_stacked.set_inconsistent(      _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS );
    _numeric_ordinal.set_inconsistent(      _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_ORDINAL );
    _numeric_slashed_zero.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_SLASHED_ZERO );

    _asian_all = query->font_variant_east_asian.computed;
    _asian_mix = query->font_variant_east_asian.value;

    if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS78) {
        _asian_jis78.set_active();
    } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS83) {
        _asian_jis83.set_active();
    } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS90) {
        _asian_jis90.set_active();
    } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS04) {
        _asian_jis04.set_active();
    } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED) {
        _asian_simplified.set_active();
    } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL) {
        _asian_traditional.set_active();
    } else {
        _asian_default_variant.set_active();
    }

    if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH) {
        _asian_full_width.set_active();
    } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_PROPORTIONAL_WIDTH) {
        _asian_proportional_width.set_active();
    } else {
        _asian_default_width.set_active();
    }

    _asian_ruby.set_active ( _asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_RUBY );

    _asian_jis78.set_inconsistent(             _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS78);
    _asian_jis83.set_inconsistent(             _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS83);
    _asian_jis90.set_inconsistent(             _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS90);
    _asian_jis04.set_inconsistent(             _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS04);
    _asian_simplified.set_inconsistent(        _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED);
    _asian_traditional.set_inconsistent(       _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL);
    _asian_full_width.set_inconsistent(        _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH);
    _asian_proportional_width.set_inconsistent(_asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_PROPORTIONAL_WIDTH);
    _asian_ruby.set_inconsistent(              _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_RUBY);

    // Fix me: Should match a space if second part matches.            ---,
    //       : Add boundary to 'on' and 'off'.                            v
    Glib::RefPtr<Glib::Regex> regex = Glib::Regex::create("\"(\\w{4})\"\\s*([0-9]+|on|off|)");
    Glib::MatchInfo matchInfo;
    std::string setting;

    // Set feature radiobutton (if it exists) or add to _feature_entry string.
    if (auto const &val = query->font_feature_settings.value()) {
        for (auto const &token: Glib::Regex::split_simple("\\s*,\\s*", val)) {
            regex->match(token, matchInfo);
            if (matchInfo.matches()) {
                Glib::ustring table = matchInfo.fetch(1);

                if (_features.find(table) != _features.end()) {
                    Glib::ustring value = matchInfo.fetch(2);

                    int v = 0;
                    if (value == "0" || value == "off") v = 0;
                    else if (value == "1" || value == "on" || value.empty() ) v = 1;
                    else v = std::stoi(value);

                    _features[table]->set_active(v);
                } else {
                    setting += token + ", ";
                }
            }
        }
    }

    // Remove final ", "
    if (setting.length() > 1) {
        setting.pop_back();
        setting.pop_back();
    }

    // Tables without radiobuttons.
    _feature_entry.set_text( setting );

    if( different_features ) {
        _feature_label.set_visible(true);
    } else {
        _feature_label.set_visible(false);
    }
}

// Update GUI based on OpenType tables of selected font (which may be changed in font selector tab).
void
FontVariants::update_opentype (Glib::ustring& font_spec) {

    // Disable/Enable based on available OpenType tables.
    auto res = FontFactory::get().FaceFromFontSpecification(font_spec.c_str());
    if (res) {

        auto const &tab = res->get_opentype_tables();
        std::remove_reference<decltype(tab)>::type::const_iterator it;

        if((it = tab.find("liga"))!= tab.end() ||
           (it = tab.find("clig"))!= tab.end()) {
            _ligatures_common.set_sensitive();
        } else {
            _ligatures_common.set_sensitive( false );
        }

        if((it = tab.find("dlig"))!= tab.end()) {
            _ligatures_discretionary.set_sensitive();
        } else {
            _ligatures_discretionary.set_sensitive( false );
        }

        if((it = tab.find("hlig"))!= tab.end()) {
            _ligatures_historical.set_sensitive();
        } else {
            _ligatures_historical.set_sensitive( false );
        }

        if((it = tab.find("calt"))!= tab.end()) {
            _ligatures_contextual.set_sensitive();
        } else {
            _ligatures_contextual.set_sensitive( false );
        }

        if((it = tab.find("subs"))!= tab.end()) {
            _position_sub.set_sensitive();
        } else {
            _position_sub.set_sensitive( false );
        }

        if((it = tab.find("sups"))!= tab.end()) {
            _position_super.set_sensitive();
        } else {
            _position_super.set_sensitive( false );
        }

        if((it = tab.find("smcp"))!= tab.end()) {
            _caps_small.set_sensitive();
        } else {
            _caps_small.set_sensitive( false );
        }

        if((it = tab.find("c2sc"))!= tab.end() &&
           (it = tab.find("smcp"))!= tab.end()) {
            _caps_all_small.set_sensitive();
        } else {
            _caps_all_small.set_sensitive( false );
        }

        if((it = tab.find("pcap"))!= tab.end()) {
            _caps_petite.set_sensitive();
        } else {
            _caps_petite.set_sensitive( false );
        }

        if((it = tab.find("c2sc"))!= tab.end() &&
           (it = tab.find("pcap"))!= tab.end()) {
            _caps_all_petite.set_sensitive();
        } else {
            _caps_all_petite.set_sensitive( false );
        }

        if((it = tab.find("unic"))!= tab.end()) {
            _caps_unicase.set_sensitive();
        } else {
            _caps_unicase.set_sensitive( false );
        }

        if((it = tab.find("titl"))!= tab.end()) {
            _caps_titling.set_sensitive();
        } else {
            _caps_titling.set_sensitive( false );
        }

        if((it = tab.find("lnum"))!= tab.end()) {
            _numeric_lining.set_sensitive();
        } else {
            _numeric_lining.set_sensitive( false );
        }

        if((it = tab.find("onum"))!= tab.end()) {
            _numeric_old_style.set_sensitive();
        } else {
            _numeric_old_style.set_sensitive( false );
        }

        if((it = tab.find("pnum"))!= tab.end()) {
            _numeric_proportional.set_sensitive();
        } else {
            _numeric_proportional.set_sensitive( false );
        }

        if((it = tab.find("tnum"))!= tab.end()) {
            _numeric_tabular.set_sensitive();
        } else {
            _numeric_tabular.set_sensitive( false );
        }

        if((it = tab.find("frac"))!= tab.end()) {
            _numeric_diagonal.set_sensitive();
        } else {
            _numeric_diagonal.set_sensitive( false );
        }

        if((it = tab.find("afrac"))!= tab.end()) {
            _numeric_stacked.set_sensitive();
        } else {
            _numeric_stacked.set_sensitive( false );
        }

        if((it = tab.find("ordn"))!= tab.end()) {
            _numeric_ordinal.set_sensitive();
        } else {
            _numeric_ordinal.set_sensitive( false );
        }

        if((it = tab.find("zero"))!= tab.end()) {
            _numeric_slashed_zero.set_sensitive();
        } else {
            _numeric_slashed_zero.set_sensitive( false );
        }

        // East-Asian
        if((it = tab.find("jp78"))!= tab.end()) {
            _asian_jis78.set_sensitive();
        } else {
            _asian_jis78.set_sensitive( false );
        }

        if((it = tab.find("jp83"))!= tab.end()) {
            _asian_jis83.set_sensitive();
        } else {
            _asian_jis83.set_sensitive( false );
        }

        if((it = tab.find("jp90"))!= tab.end()) {
            _asian_jis90.set_sensitive();
        } else {
            _asian_jis90.set_sensitive( false );
        }

        if((it = tab.find("jp04"))!= tab.end()) {
            _asian_jis04.set_sensitive();
        } else {
            _asian_jis04.set_sensitive( false );
        }

        if((it = tab.find("smpl"))!= tab.end()) {
            _asian_simplified.set_sensitive();
        } else {
            _asian_simplified.set_sensitive( false );
        }

        if((it = tab.find("trad"))!= tab.end()) {
            _asian_traditional.set_sensitive();
        } else {
            _asian_traditional.set_sensitive( false );
        }

        if((it = tab.find("fwid"))!= tab.end()) {
            _asian_full_width.set_sensitive();
        } else {
            _asian_full_width.set_sensitive( false );
        }

        if((it = tab.find("pwid"))!= tab.end()) {
            _asian_proportional_width.set_sensitive();
        } else {
            _asian_proportional_width.set_sensitive( false );
        }

        if((it = tab.find("ruby"))!= tab.end()) {
            _asian_ruby.set_sensitive();
        } else {
            _asian_ruby.set_sensitive( false );
        }

        // List available ligatures
        Glib::ustring markup_liga;
        Glib::ustring markup_dlig;
        Glib::ustring markup_hlig;
        Glib::ustring markup_calt;

        for (auto &table : tab) {
            if (table.first == "liga" ||
                table.first == "clig" ||
                table.first == "dlig" ||
                table.first == "hgli" ||
                table.first == "calt") {

                Glib::ustring markup;
                markup += "<span font_family='";
                markup += sp_font_description_get_family(res->get_descr());
                markup += "'>";
                markup += Glib::Markup::escape_text(table.second.output);
                markup += "</span>";

                if (table.first == "liga") markup_liga += markup;
                if (table.first == "clig") markup_liga += markup;
                if (table.first == "dlig") markup_dlig += markup;
                if (table.first == "hlig") markup_hlig += markup;
                if (table.first == "calt") markup_calt += markup;
            }
        }

        _ligatures_label_common.set_markup        ( markup_liga.c_str() );
        _ligatures_label_discretionary.set_markup ( markup_dlig.c_str() );
        _ligatures_label_historical.set_markup    ( markup_hlig.c_str() );
        _ligatures_label_contextual.set_markup    ( markup_calt.c_str() );

        // List available numeric variants
        Glib::ustring markup_lnum;
        Glib::ustring markup_onum;
        Glib::ustring markup_pnum;
        Glib::ustring markup_tnum;
        Glib::ustring markup_frac;
        Glib::ustring markup_afrc;
        Glib::ustring markup_ordn;
        Glib::ustring markup_zero;

        for (auto &table : res->get_opentype_tables()) {
            Glib::ustring markup;
            markup += "<span font_family='";
            markup += sp_font_description_get_family(res->get_descr());
            markup += "' font_features='";
            markup += table.first;
            markup += "'>";
            if (table.first == "lnum" ||
                table.first == "onum" ||
                table.first == "pnum" ||
                table.first == "tnum") markup += "0123456789";
            if (table.first == "zero") markup += "0";
            if (table.first == "ordn") markup += "[" + table.second.before + "]" + table.second.output;
            if (table.first == "frac" ||
                table.first == "afrc" ) markup += "1/2 2/3 3/4 4/5 5/6"; // Can we do better?
            markup += "</span>";

            if (table.first == "lnum") markup_lnum += markup;
            if (table.first == "onum") markup_onum += markup;
            if (table.first == "pnum") markup_pnum += markup;
            if (table.first == "tnum") markup_tnum += markup;
            if (table.first == "frac") markup_frac += markup;
            if (table.first == "afrc") markup_afrc += markup;
            if (table.first == "ordn") markup_ordn += markup;
            if (table.first == "zero") markup_zero += markup;
        }

        _numeric_lining_label.set_markup       ( markup_lnum.c_str() );
        _numeric_old_style_label.set_markup    ( markup_onum.c_str() );
        _numeric_proportional_label.set_markup ( markup_pnum.c_str() );
        _numeric_tabular_label.set_markup      ( markup_tnum.c_str() );
        _numeric_diagonal_label.set_markup     ( markup_frac.c_str() );
        _numeric_stacked_label.set_markup      ( markup_afrc.c_str() );
        _numeric_ordinal_label.set_markup      ( markup_ordn.c_str() );
        _numeric_slashed_zero_label.set_markup ( markup_zero.c_str() );

        // Make list of tables not handled above.
        auto table_copy = res->get_opentype_tables();
        if( (it = table_copy.find("liga")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("clig")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("dlig")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("hlig")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("calt")) != table_copy.end() ) table_copy.erase( it );

        if( (it = table_copy.find("subs")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("sups")) != table_copy.end() ) table_copy.erase( it );

        if( (it = table_copy.find("smcp")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("c2sc")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("pcap")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("c2pc")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("unic")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("titl")) != table_copy.end() ) table_copy.erase( it );

        if( (it = table_copy.find("lnum")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("onum")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("pnum")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("tnum")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("frac")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("afrc")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("ordn")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("zero")) != table_copy.end() ) table_copy.erase( it );

        if( (it = table_copy.find("jp78")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("jp83")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("jp90")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("jp04")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("smpl")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("trad")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("fwid")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("pwid")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("ruby")) != table_copy.end() ) table_copy.erase( it );

        // An incomplete list of tables that should not be exposed to the user:
        if( (it = table_copy.find("abvf")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("abvs")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("akhn")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("blwf")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("blws")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("ccmp")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("cjct")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("dnom")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("dtls")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("fina")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("half")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("haln")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("init")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("isol")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("locl")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("medi")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("nukt")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("numr")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("pref")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("pres")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("pstf")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("psts")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("rlig")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("rkrf")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("rphf")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("rtlm")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("ssty")) != table_copy.end() ) table_copy.erase( it );
        if( (it = table_copy.find("vatu")) != table_copy.end() ) table_copy.erase( it );

        // Clear out old features
        UI::delete_all_children(_feature_grid);
        _features.clear();

        std::string markup;
        int grid_row = 0;

        // GSUB lookup type 1 (1 to 1 mapping).
        for (auto &table: res->get_opentype_tables()) {
            if (table.first == "case" ||
                table.first == "hist" ||
                (table.first[0] == 's' && table.first[1] == 's' && !(table.first[2] == 't'))) {

                if( (it = table_copy.find(table.first)) != table_copy.end() ) table_copy.erase( it );

                _features[table.first] = new Feature (table.first, table.second, 2,
                                                      sp_font_description_get_family(res->get_descr()),
                                                      _feature_grid, grid_row, this);
                grid_row++;
            }
        }

        // GSUB lookup type 3 (1 to many mapping). Optionally type 1.
        for (auto &table : res->get_opentype_tables()) {
            if (table.first == "salt" ||
                table.first == "swsh" ||
                table.first == "cwsh" ||
                table.first == "ornm" ||
                table.first == "nalt" ||
                (table.first[0] == 'c' && table.first[1] == 'v')) {

                if (table.second.input.length() == 0) {
                    // This can happen if a table is not in the 'DFLT' script and 'dflt' language.
                    // We should be using the 'lang' attribute to find the correct tables.
                    // std::cerr << "FontVariants::open_type_update: "
                    //           << table.first << " has no entries!" << std::endl;
                    continue;
                }

                if( (it = table_copy.find(table.first)) != table_copy.end() ) table_copy.erase( it );

                // Our lame attempt at determining number of alternative glyphs for one glyph:
                int number = table.second.output.length() / table.second.input.length();
                if (number < 1) {
                    number = 1; // Must have at least on/off, see comment above about 'lang' attribute.
                    // std::cout << table.first << " "
                    //           << table.second.output.length() << "/"
                    //           << table.second.input.length() << "="
                    //           << number << std::endl;
                }

                _features[table.first] = new Feature (table.first, table.second, number+1,
                                                      sp_font_description_get_family(res->get_descr()),
                                                      _feature_grid, grid_row, this);
                grid_row++;
            }
        }

        _feature_grid.show_all();

        _feature_substitutions.set_markup ( markup.c_str() );

        std::string ott_list = "OpenType tables not included above: ";
        for(it = table_copy.begin(); it != table_copy.end(); ++it) {
            ott_list += it->first;
            ott_list += ", ";
        }

        if (table_copy.size() > 0) {
            ott_list.pop_back();
            ott_list.pop_back();
            _feature_list.set_text( ott_list.c_str() );
        } else {
            _feature_list.set_text( "" );
        }

    } else {
        std::cerr << "FontVariants::update(): Couldn't find FontInstance for: "
                  << font_spec.raw() << std::endl;
    }

    _ligatures_changed = false;
    _position_changed  = false;
    _caps_changed      = false;
    _numeric_changed   = false;
    _feature_changed   = false;
}

void
FontVariants::fill_css( SPCSSAttr *css ) {

    // Ligatures
    bool common        = _ligatures_common.get_active();
    bool discretionary = _ligatures_discretionary.get_active();
    bool historical    = _ligatures_historical.get_active();
    bool contextual    = _ligatures_contextual.get_active();

    if( !common && !discretionary && !historical && !contextual ) {
        sp_repr_css_set_property(css, "font-variant-ligatures", "none" );
    } else if ( common && !discretionary && !historical && contextual ) {
        sp_repr_css_set_property(css, "font-variant-ligatures", "normal" );
    } else {
        Glib::ustring css_string;
        if ( !common )
            css_string += "no-common-ligatures ";
        if ( discretionary )
            css_string += "discretionary-ligatures ";
        if ( historical )
            css_string += "historical-ligatures ";
        if ( !contextual )
            css_string += "no-contextual ";
        sp_repr_css_set_property(css, "font-variant-ligatures", css_string.c_str() );
    }

    // Position
    {
        unsigned position_new = SP_CSS_FONT_VARIANT_POSITION_NORMAL;
        Glib::ustring css_string;
        if( _position_normal.get_active() ) {
            css_string = "normal";
        } else if( _position_sub.get_active() ) {
            css_string = "sub";
            position_new = SP_CSS_FONT_VARIANT_POSITION_SUB;
        } else if( _position_super.get_active() ) {
            css_string = "super";
            position_new = SP_CSS_FONT_VARIANT_POSITION_SUPER;
        }

        // 'if' may not be necessary... need to test.
        if( (_position_all != position_new) || ((_position_mix != 0) && _position_changed) ) {
            sp_repr_css_set_property(css, "font-variant-position", css_string.c_str() );
        }
    }
    
    // Caps
    {
        //unsigned caps_new;
        Glib::ustring css_string;
        if( _caps_normal.get_active() ) {
            css_string = "normal";
        //    caps_new = SP_CSS_FONT_VARIANT_CAPS_NORMAL;
        } else if( _caps_small.get_active() ) {
            css_string = "small-caps";
        //    caps_new = SP_CSS_FONT_VARIANT_CAPS_SMALL;
        } else if( _caps_all_small.get_active() ) {
            css_string = "all-small-caps";
        //    caps_new = SP_CSS_FONT_VARIANT_CAPS_ALL_SMALL;
        } else if( _caps_petite.get_active() ) {
            css_string = "petite";
        //    caps_new = SP_CSS_FONT_VARIANT_CAPS_PETITE;
        } else if( _caps_all_petite.get_active() ) {
            css_string = "all-petite";
        //    caps_new = SP_CSS_FONT_VARIANT_CAPS_ALL_PETITE;
        } else if( _caps_unicase.get_active() ) {
            css_string = "unicase";
        //    caps_new = SP_CSS_FONT_VARIANT_CAPS_UNICASE;
        } else if( _caps_titling.get_active() ) {
            css_string = "titling";
        //    caps_new = SP_CSS_FONT_VARIANT_CAPS_TITLING;
        //} else {
        //    caps_new = SP_CSS_FONT_VARIANT_CAPS_NORMAL;
        }

        // May not be necessary... need to test.
        //if( (_caps_all != caps_new) || ((_caps_mix != 0) && _caps_changed) ) {
        sp_repr_css_set_property(css, "font-variant-caps", css_string.c_str() );
        //}
    }

    // Numeric
    bool default_style = _numeric_default_style.get_active();
    bool lining        = _numeric_lining.get_active();
    bool old_style     = _numeric_old_style.get_active();

    bool default_width = _numeric_default_width.get_active();
    bool proportional  = _numeric_proportional.get_active();
    bool tabular       = _numeric_tabular.get_active();

    bool default_fractions = _numeric_default_fractions.get_active();
    bool diagonal          = _numeric_diagonal.get_active();
    bool stacked           = _numeric_stacked.get_active();

    bool ordinal           = _numeric_ordinal.get_active();
    bool slashed_zero      = _numeric_slashed_zero.get_active();

    if (default_style & default_width & default_fractions & !ordinal & !slashed_zero) {
        sp_repr_css_set_property(css, "font-variant-numeric", "normal");
    } else {
        Glib::ustring css_string;
        if ( lining )
            css_string += "lining-nums ";
        if ( old_style )
            css_string += "oldstyle-nums ";
        if ( proportional )
            css_string += "proportional-nums ";
        if ( tabular )
            css_string += "tabular-nums ";
        if ( diagonal )
            css_string += "diagonal-fractions ";
        if ( stacked )
            css_string += "stacked-fractions ";
        if ( ordinal )
            css_string += "ordinal ";
        if ( slashed_zero )
            css_string += "slashed-zero ";
        sp_repr_css_set_property(css, "font-variant-numeric", css_string.c_str() );
    }

    // East Asian
    bool jis78           = _asian_jis78.get_active();
    bool jis83           = _asian_jis83.get_active();
    bool jis90           = _asian_jis90.get_active();
    bool jis04           = _asian_jis04.get_active();
    bool simplified      = _asian_simplified.get_active();
    bool traditional     = _asian_traditional.get_active();
    bool asian_width     = _asian_default_width.get_active();
    bool fwid            = _asian_full_width.get_active();
    bool pwid            = _asian_proportional_width.get_active();
    bool ruby            = _asian_ruby.get_active();

    if (default_style & asian_width & !ruby) {
        sp_repr_css_set_property(css, "font-variant-east-asian", "normal");
    } else {
        Glib::ustring css_string;
        if (jis78)          css_string += "jis78 ";
        if (jis83)          css_string += "jis83 ";
        if (jis90)          css_string += "jis90 ";
        if (jis04)          css_string += "jis04 ";
        if (simplified)     css_string += "simplfied ";
        if (traditional)    css_string += "traditional ";

        if (fwid)           css_string += "fwid ";
        if (pwid)           css_string += "pwid ";

        if (ruby)           css_string += "ruby ";

        sp_repr_css_set_property(css, "font-variant-east-asian", css_string.c_str() );
    }

    // Feature settings
    Glib::ustring feature_string;
    for (auto const &i: _features) {
        feature_string += i.second->get_css();
    }

    feature_string += _feature_entry.get_text();
    // std::cout << "feature_string: " << feature_string << std::endl;

    if (!feature_string.empty()) {
        sp_repr_css_set_property(css, "font-feature-settings", feature_string.c_str());
    } else {
        sp_repr_css_unset_property(css, "font-feature-settings");
    }
}
 
Glib::ustring
FontVariants::get_markup() {

    Glib::ustring markup;

    // Ligatures
    bool common        = _ligatures_common.get_active();
    bool discretionary = _ligatures_discretionary.get_active();
    bool historical    = _ligatures_historical.get_active();
    bool contextual    = _ligatures_contextual.get_active();

    if (!common)         markup += "liga=0,clig=0,"; // On by default.
    if (discretionary)   markup += "dlig=1,";
    if (historical)      markup += "hlig=1,";
    if (contextual)      markup += "calt=1,";

    // Position
    if (      _position_sub.get_active()    ) markup += "subs=1,";
    else if ( _position_super.get_active()  ) markup += "sups=1,";

    // Caps
    if (      _caps_small.get_active()      ) markup += "smcp=1,";
    else if ( _caps_all_small.get_active()  ) markup += "c2sc=1,smcp=1,";
    else if ( _caps_petite.get_active()     ) markup += "pcap=1,";
    else if ( _caps_all_petite.get_active() ) markup += "c2pc=1,pcap=1,";
    else if ( _caps_unicase.get_active()    ) markup += "unic=1,";
    else if ( _caps_titling.get_active()    ) markup += "titl=1,";

    // Numeric
    bool lining        = _numeric_lining.get_active();
    bool old_style     = _numeric_old_style.get_active();

    bool proportional  = _numeric_proportional.get_active();
    bool tabular       = _numeric_tabular.get_active();

    bool diagonal          = _numeric_diagonal.get_active();
    bool stacked           = _numeric_stacked.get_active();

    bool ordinal           = _numeric_ordinal.get_active();
    bool slashed_zero      = _numeric_slashed_zero.get_active();

    if (lining)          markup += "lnum=1,";
    if (old_style)       markup += "onum=1,";
    if (proportional)    markup += "pnum=1,";
    if (tabular)         markup += "tnum=1,";
    if (diagonal)        markup += "frac=1,";
    if (stacked)         markup += "afrc=1,";
    if (ordinal)         markup += "ordn=1,";
    if (slashed_zero)    markup += "zero=1,";

    // East Asian
    bool jis78           = _asian_jis78.get_active();
    bool jis83           = _asian_jis83.get_active();
    bool jis90           = _asian_jis90.get_active();
    bool jis04           = _asian_jis04.get_active();
    bool simplified      = _asian_simplified.get_active();
    bool traditional     = _asian_traditional.get_active();
    //bool asian_width     = _asian_default_width.get_active();
    bool fwid            = _asian_full_width.get_active();
    bool pwid            = _asian_proportional_width.get_active();
    bool ruby            = _asian_ruby.get_active();

    if (jis78          )   markup += "jp78=1,";
    if (jis83          )   markup += "jp83=1,";
    if (jis90          )   markup += "jp90=1,";
    if (jis04          )   markup += "jp04=1,";
    if (simplified     )   markup += "smpl=1,";
    if (traditional    )   markup += "trad=1,";

    if (fwid           )   markup += "fwid=1,";
    if (pwid           )   markup += "pwid=1,";

    if (ruby           )   markup += "ruby=1,";

    // Feature settings
    Glib::ustring feature_string;
    for (auto const &i: _features) {
        feature_string += i.second->get_css();
    }

    feature_string += _feature_entry.get_text();
    if (!feature_string.empty()) {
        markup += feature_string;
    }

    // std::cout << "|" << markup << "|" << std::endl;
    return markup;
}

} // namespace Inkscape::UI::Widget

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
