// SPDX-License-Identifier: GPL-2.0-or-later
#ifndef FONT_LISTER_H
#define FONT_LISTER_H

/*
 * Font selection widgets
 *
 * Authors:
 *   Chris Lahey <clahey@ximian.com>
 *   Lauris Kaplinski <lauris@kaplinski.com>
 *   Tavmjong Bah <tavmjong@free.fr>
 *
 * Copyright (C) 1999-2001 Ximian, Inc.
 * Copyright (C) 2002 Lauris Kaplinski
 * Copyright (C) 2013 Tavmjong Bah
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include <map>
#include <set>

#include <glibmm/ustring.h>
#include <glibmm/stringutils.h> // For strescape()

#include <gtkmm/liststore.h>
#include <gtkmm/treemodelcolumn.h>
#include <gtkmm/treepath.h>

#define FONT_FAMILIES_GROUP_SIZE 30

class SPObject;
class SPDocument;
class SPCSSAttr;
class SPStyle;

namespace Gtk {
class CellRenderer;
}

namespace Inkscape {

/**
 *  This class enumerates fonts using libnrtype into reusable data stores and
 *  allows for random access to the font-family list and the font-style list.
 *  Setting the font-family updates the font-style list. "Style" in this case
 *  refers to everything but family and size (e.g. italic/oblique, weight).
 *
 *  This class handles font-family lists and fonts that are not on the system,
 *  where there is not an entry in the fontInstanceMap.
 *
 *  This class uses the idea of "font_spec". This is a plain text string as used by
 *  Pango. It is similar to the CSS font shorthand except that font-family comes
 *  first and in this class the font-size is not used.
 *
 *  This class uses the FontFactory class to get a list of system fonts
 *  and to find best matches via Pango. The Pango interface is only setup
 *  to deal with fonts that are on the system so care must be taken. For
 *  example, best matches should only be done with the first font-family
 *  in a font-family list. If the first font-family is not on the system
 *  then a generic font-family should be used (sans-serif -> Sans).
 *
 *  This class is used by the UI interface (text-toolbar, font-select, etc.).
 *  Those items can change the selected font family and style here. When that
 *  happens. this class emits a signal for those items to update their displayed
 *  values.
 *
 *  This class is a singleton (one instance per Inkscape session). Since fonts
 *  used in a document are added to the list, there really should be one
 *  instance per document.
 *
 *  "Font" includes family and style. It should not be used when one
 *  means font-family.
 */

class FontLister {
public:
    enum Exceptions {
        FAMILY_NOT_FOUND,
        STYLE_NOT_FOUND
    };

    virtual ~FontLister();

    /**
     * GtkTreeModelColumnRecord for the font-family list Gtk::ListStore
     */
    struct FontListClass : public Gtk::TreeModelColumnRecord {
        /**
         * Column containing the family name
         */
        Gtk::TreeModelColumn<Glib::ustring> family;

        /**
         * Column containing the styles for each family name.
         */
        Gtk::TreeModelColumn<GList *> styles;

        /**
         * Column containing flag if font is on system
         */
        Gtk::TreeModelColumn<bool> onSystem;
        
        /**
         * Not actually a column.
         * Necessary for quick initialization of FontLister,
         * we initially store the pango family and if the
         * font style is actually used we'll cache it in
         * %styles.
         */
        Gtk::TreeModelColumn<PangoFontFamily *> pango_family;
        
        FontListClass()
        {
            add(family);
            add(styles);
            add(onSystem);
            add(pango_family);
        }
    };

    FontListClass FontList;

    struct FontStyleListClass : public Gtk::TreeModelColumnRecord {
        /**
         * Column containing the styles as Font designer used.
         */
        Gtk::TreeModelColumn<Glib::ustring> displayStyle;

        /**
         * Column containing the styles in CSS/Pango format.
         */
        Gtk::TreeModelColumn<Glib::ustring> cssStyle;

        FontStyleListClass()
        {
            add(cssStyle);
            add(displayStyle);
        }
    };

    FontStyleListClass FontStyleList;

    // This map will give constant time access to each font and it's
    // PangoFontFamily.
    std::map <std::string, PangoFontFamily *> pango_family_map;

    /** 
     * @return the ListStore with the family names
     *
     * The return is const and the function is declared as const.
     * The ListStore is ready to be used after class instantiation
     * and should not be modified.
     */
    const Glib::RefPtr<Gtk::ListStore> get_font_list() const;

    /**
     * @return the ListStore with the styles
     */
    const Glib::RefPtr<Gtk::ListStore> get_style_list() const;

    /** 
     * Inserts a font family or font-fallback list (for use when not
     *  already in document or on system).
     */
    void insert_font_family(Glib::ustring new_family);

    int add_document_fonts_at_top(SPDocument *document);

    /**
     * Updates font list to include fonts in document.
     */
    void update_font_list(SPDocument *document);

public:
    static Inkscape::FontLister *get_instance();

    /**
     * Takes a hand written font spec and returns a Pango generated one in
     *  standard form.
     */

    /**
     * Functions to display the search results in the font list.
     */
    bool find_string_case_insensitive(const std::string& text, const std::string& pat);
    void show_results(const Glib::ustring &search_text);
    void apply_collections(std::set <Glib::ustring>& selected_collections);
    void set_dragging_family(const Glib::ustring &new_family);

    Glib::ustring canonize_fontspec(Glib::ustring fontspec);

    /**
     * Find closest system font to given font.
     */
    Glib::ustring system_fontspec(Glib::ustring fontspec);

    /**
     * Gets font-family and style from fontspec.
     *  font-family and style returned.
     */
    std::pair<Glib::ustring, Glib::ustring> ui_from_fontspec(Glib::ustring fontspec);

    /**
     * Sets font-family and style after a selection change.
     *  New font-family and style returned.
     */
    std::pair<Glib::ustring, Glib::ustring> selection_update();

    /** 
     * Sets current_fontspec, etc. If check is false, won't
     *  try to find best style match (assumes style in fontspec
     *  valid for given font-family).
     */
    void set_fontspec(Glib::ustring fontspec, bool check = true);

    Glib::ustring get_fontspec() { return (canonize_fontspec(current_family + ", " + current_style)); }

    /**
     * Changes font-family, updating style list and attempting to find
     *  closest style to current_style style (if check_style is true).
     *  New font-family and style returned.
     *  Does NOT update current_family and current_style.
     *  (For potential use in font-selector which doesn't update until
     *  "Apply" button clicked.)
     */
    std::pair<Glib::ustring, Glib::ustring> new_font_family(Glib::ustring family, bool check_style = true);

    /** 
     * Sets font-family, updating style list and attempting
     *  to find closest style to old current_style.
     *  New font-family and style returned.
     *  Updates current_family and current_style.
     *  Calls new_font_family().
     *  (For use in text-toolbar where update is immediate.)
     */
    std::pair<Glib::ustring, Glib::ustring> set_font_family(Glib::ustring family, bool check_style = true,
                                                            bool emit = true);

    /**
     * Sets font-family from row in list store.
     *  The row can be used to determine if we are in the
     *  document or system part of the font-family list.
     *  This is needed to handle scrolling through the
     *  font-family list correctly.
     *  Calls set_font_family().
     */
    std::pair<Glib::ustring, Glib::ustring> set_font_family(int row, bool check_style = true, bool emit = true);

    Glib::ustring get_font_family()
    {
        return current_family;
    }

    Glib::ustring get_dragging_family()
    {
        return dragging_family;
    }

    int get_font_family_row()
    {
        return current_family_row;
    }

    /**
     * Sets style. Does not validate style for family.
     */
    void set_font_style(Glib::ustring style, bool emit = true);

    Glib::ustring get_font_style()
    {
        return current_style;
    }

    Glib::ustring fontspec_from_style(SPStyle *style);

    /**
     * Fill css using given fontspec (doesn't need to be member function).
     */
    void fill_css(SPCSSAttr *css, Glib::ustring fontspec = "");

    Gtk::TreeModel::Row get_row_for_font() { return get_row_for_font (current_family); }

    Gtk::TreeModel::Row get_row_for_font(Glib::ustring family);

    Gtk::TreePath get_path_for_font(Glib::ustring family);

    bool is_path_for_font(Gtk::TreePath path, Glib::ustring family);

    Gtk::TreeModel::Row get_row_for_style() { return get_row_for_style (current_style); }

    Gtk::TreeModel::Row get_row_for_style(Glib::ustring style);

    Gtk::TreePath get_path_for_style(Glib::ustring style);

    std::pair<Gtk::TreePath, Gtk::TreePath> get_paths(Glib::ustring family, Glib::ustring style);

    /**
     * Return best style match for new font given style for old font.
     */
    Glib::ustring get_best_style_match(Glib::ustring family, Glib::ustring style);

    /**
     * Ensures the style list for a particular family has been created.
     */
    void ensureRowStyles(Glib::RefPtr<Gtk::TreeModel> model, Gtk::TreeModel::iterator const iter);

    /**
     * Get markup for font-family.
     */
    Glib::ustring get_font_family_markup(Gtk::TreeIter const &iter);

    /**
     * Let users of FontLister know to update GUI.
     * This is to allow synchronization of changes across multiple widgets.
     * Handlers should block signals.
     * Input is fontspec to set.
     */
    sigc::connection connectUpdate(sigc::slot<void ()> slot) {
        return update_signal.connect(slot);
    }

    bool blocked() { return block; }

    int get_font_families_size();
    bool font_installed_on_system(const Glib::ustring& font);

    void init_font_families(int group_offset = -1, int group_size = -1);
    void init_default_styles();
    std::string get_font_count_label();

private:
    FontLister();

    void update_font_data_recursive(SPObject& r, std::map<Glib::ustring, std::set<Glib::ustring>> &font_data);

	void font_family_row_update(int start=0);

    Glib::RefPtr<Gtk::ListStore> font_list_store;
    Glib::RefPtr<Gtk::ListStore> style_list_store;

    /**
     * Info for currently selected font (what is shown in the UI).
     *  May include font-family lists and fonts not on system.
     */
    int current_family_row;
    Glib::ustring current_family;
    Glib::ustring dragging_family;
    Glib::ustring current_style;

    /**
     * If a font-family is not on system, this list of styles is used.
     */
    GList *default_styles;

    bool block;
    void emit_update();
    sigc::signal<void ()> update_signal;
};

} // namespace Inkscape

// Helper functions
bool font_lister_separator_func (const Glib::RefPtr<Gtk::TreeModel>& model,
                                 const Gtk::TreeModel::iterator& iter);

gboolean font_lister_separator_func2(GtkTreeModel *model,
                                    GtkTreeIter *iter,
                                    gpointer /*data*/);

void font_lister_cell_data_func (Gtk::CellRenderer *renderer, Gtk::TreeIter const &iter);

void font_lister_cell_data_func_markup (Gtk::CellRenderer *renderer, Gtk::TreeIter const &iter);

void font_lister_cell_data_func2(GtkCellLayout * /*cell_layout*/,
                                GtkCellRenderer *cell,
                                GtkTreeModel *model,
                                GtkTreeIter *iter,
                                gpointer /*data*/);

void font_lister_style_cell_data_func (Gtk::CellRenderer *renderer, Gtk::TreeIter const &iter);

#endif

/*
  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 :
