From 0fd518beb13ed45448492ecc782825412f2cff01 Mon Sep 17 00:00:00 2001 From: Tavmjong Bah Date: Mon, 11 Jun 2018 13:25:39 +0200 Subject: Improvements to the Font Features dialog. Better OpenType coverage. --- src/libnrtype/FontInstance.cpp | 3 +- src/libnrtype/OpenTypeUtil.cpp | 136 ++++++++++++--------------- src/libnrtype/OpenTypeUtil.h | 18 +++- src/libnrtype/font-instance.h | 9 +- src/ui/widget/font-variants.cpp | 202 +++++++++++++++++++++++++++++----------- 5 files changed, 223 insertions(+), 145 deletions(-) (limited to 'src') diff --git a/src/libnrtype/FontInstance.cpp b/src/libnrtype/FontInstance.cpp index f8ee41108..667478924 100644 --- a/src/libnrtype/FontInstance.cpp +++ b/src/libnrtype/FontInstance.cpp @@ -211,8 +211,7 @@ void font_instance::InitTheFace() #ifndef USE_PANGO_WIN32 - readOpenTypeGsubTable( theFace, openTypeTables, openTypeStylistic, - openTypeLigatures, openTypeNumeric ); + readOpenTypeGsubTable( theFace, openTypeTables ); readOpenTypeFvarAxes( theFace, openTypeVarAxes ); #if PANGO_VERSION_CHECK(1,41,1) diff --git a/src/libnrtype/OpenTypeUtil.cpp b/src/libnrtype/OpenTypeUtil.cpp index 9b7762d67..ffe0807b0 100644 --- a/src/libnrtype/OpenTypeUtil.cpp +++ b/src/libnrtype/OpenTypeUtil.cpp @@ -39,19 +39,31 @@ Glib::ustring extract_tag( guint32 *tag ) { } +void get_glyphs( hb_font_t* font, hb_set_t* set, Glib::ustring& characters) { + + // There is a unicode to glyph mapping function but not the inverse! + hb_codepoint_t codepoint = -1; + while (hb_set_next (set, &codepoint)) { + for (hb_codepoint_t unicode_i = 0; unicode_i < 0xffff; ++unicode_i) { + hb_codepoint_t glyph = 0; + hb_font_get_nominal_glyph (font, unicode_i, &glyph); + if (glyph == codepoint) { + characters += (gunichar)unicode_i; + continue; + } + } + } +} + // Make a list of all tables found in the GSUB // This list includes all tables regardless of script or language. void readOpenTypeGsubTable (const FT_Face ft_face, - std::map& tables, - std::map& stylistic, - std::map& ligatures, - std::map& numerical + std::map& tables ) { + // std::cout << "readOpenTypeGsubTable: Entrance: " + // << (ft_face->family_name?ft_face->family_name:"null") << std::endl; tables.clear(); - stylistic.clear(); - ligatures.clear(); - numerical.clear(); // Use Harfbuzz, Pango's equivalent calls are deprecated. auto const hb_face = hb_ft_face_create(ft_face, NULL); @@ -64,6 +76,7 @@ void readOpenTypeGsubTable (const FT_Face ft_face, hb_ot_layout_table_get_script_tags(hb_face, HB_OT_TAG_GSUB, 0, &script_count, hb_scripts); for(unsigned int i = 0; i < script_count; ++i) { + // std::cout << " Script: " << extract_tag(&hb_scripts[i]) << std::endl; auto language_count = hb_ot_layout_script_get_language_tags(hb_face, HB_OT_TAG_GSUB, i, 0, NULL, NULL); if(language_count > 0) { @@ -71,12 +84,14 @@ void readOpenTypeGsubTable (const FT_Face ft_face, hb_ot_layout_script_get_language_tags(hb_face, HB_OT_TAG_GSUB, i, 0, &language_count, hb_languages); for(unsigned int j = 0; j < language_count; ++j) { + // std::cout << " Language: " << extract_tag(&hb_languages[j]) << std::endl; auto feature_count = hb_ot_layout_language_get_feature_tags(hb_face, HB_OT_TAG_GSUB, i, j, 0, NULL, NULL); auto const hb_features = g_new(hb_tag_t, feature_count + 1); hb_ot_layout_language_get_feature_tags(hb_face, HB_OT_TAG_GSUB, i, j, 0, &feature_count, hb_features); for(unsigned int k = 0; k < feature_count; ++k) { - ++(tables[ extract_tag(&hb_features[k])]); + // std::cout << " Feature: " << extract_tag(&hb_features[k]) << std::endl; + tables[ extract_tag(&hb_features[k])]; } g_free(hb_features); @@ -87,6 +102,7 @@ void readOpenTypeGsubTable (const FT_Face ft_face, } else { // Even if no languages are present there is still the default. + // std::cout << " Language: " << " (dflt)" << std::endl; auto feature_count = hb_ot_layout_language_get_feature_tags(hb_face, HB_OT_TAG_GSUB, i, HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX, 0, NULL, NULL); @@ -96,7 +112,8 @@ void readOpenTypeGsubTable (const FT_Face ft_face, 0, &feature_count, hb_features); for(unsigned int k = 0; k < feature_count; ++k) { - ++(tables[ extract_tag(&hb_features[k])]); + // std::cout << " Feature: " << extract_tag(&hb_features[k]) << std::endl; + tables[ extract_tag(&hb_features[k])]; } g_free(hb_features); @@ -116,6 +133,7 @@ void readOpenTypeGsubTable (const FT_Face ft_face, // Only look at style substitution tables ('salt', 'ss01', etc. but not 'ssty'). // Also look at character substitution tables ('cv01', etc.). bool style = + table.first == "case" /* Case-Sensitive Forms */ || table.first == "salt" /* Stylistic Alternatives */ || table.first == "swsh" /* Swash */ || table.first == "cwsh" /* Contextual Swash */ || @@ -152,6 +170,7 @@ void readOpenTypeGsubTable (const FT_Face ft_face, table.first[3]), &feature_index ) ) { + // std::cout << "Table: " << table.first << std::endl; // std::cout << " Found feature, number: " << feature_index << std::endl; unsigned int lookup_indexes[32]; unsigned int lookup_count = 32; @@ -162,93 +181,56 @@ void readOpenTypeGsubTable (const FT_Face ft_face, lookup_indexes ); // std::cout << " Lookup count: " << count << " total: " << lookup_count << std::endl; - if (count > 0) { - hb_set_t* glyphs_before = NULL; // hb_set_create(); - hb_set_t* glyphs_input = hb_set_create(); // For stylistic - hb_set_t* glyphs_after = NULL; // hb_set_create(); - hb_set_t* glyphs_output = hb_set_create(); // For ligatures + hb_font_t *hb_font = hb_font_create (hb_face); // MOVE THIS OUT OF LOOPS? + + for (int i = 0; i < count; ++i) { + hb_set_t* glyphs_before = hb_set_create(); + hb_set_t* glyphs_input = hb_set_create(); + hb_set_t* glyphs_after = hb_set_create(); + hb_set_t* glyphs_output = hb_set_create(); - // For now, just look at first index hb_ot_layout_lookup_collect_glyphs (hb_face, HB_OT_TAG_GSUB, - lookup_indexes[0], + lookup_indexes[i], glyphs_before, glyphs_input, glyphs_after, glyphs_output ); - hb_font_t *hb_font = hb_font_create (hb_face); + // std::cout << " Populations: " + // << " " << hb_set_get_population (glyphs_before) + // << " " << hb_set_get_population (glyphs_input) + // << " " << hb_set_get_population (glyphs_after) + // << " " << hb_set_get_population (glyphs_output) + // << std::endl; // Without this, all functions return 0, etc. hb_ft_font_set_funcs (hb_font); - Glib::ustring unicode_characters; - - hb_codepoint_t codepoint = -1; - - if (style) { - while (hb_set_next (glyphs_input, &codepoint)) { - - // There is a unicode to glyph mapping function but not the inverse! - for (hb_codepoint_t unicode_i = 0; unicode_i < 0xffff; ++unicode_i) { - hb_codepoint_t glyph = 0; - hb_font_get_nominal_glyph (hb_font, unicode_i, &glyph); - if ( glyph == codepoint) { - unicode_characters += (gunichar)unicode_i; - continue; - } - } - } - stylistic[table.first] = unicode_characters; - } - - // Don't know how to extract all input glyphs... - // glyphs_input contains last input glyph, so just use output. - if (ligature) { - while (hb_set_next (glyphs_output, &codepoint)) { - - // There is a unicode to glyph mapping function but not the inverse! - for (hb_codepoint_t unicode_i = 0; unicode_i < 0xffff; ++unicode_i) { - hb_codepoint_t glyph = 0; - hb_font_get_nominal_glyph (hb_font, unicode_i, &glyph); - if ( glyph == codepoint) { - unicode_characters += (gunichar)unicode_i; - unicode_characters += " "; // Add space - continue; - } - } - } - ligatures[table.first] = unicode_characters; - } - - if (numeric) { - while (hb_set_next (glyphs_output, &codepoint)) { - - // There is a unicode to glyph mapping function but not the inverse! - for (hb_codepoint_t unicode_i = 0; unicode_i < 0xffff; ++unicode_i) { - hb_codepoint_t glyph = 0; - hb_font_get_nominal_glyph (hb_font, unicode_i, &glyph); - if ( glyph == codepoint) { - unicode_characters += (gunichar)unicode_i; - unicode_characters += " "; // Add space - continue; - } - } - } - numerical[table.first] = unicode_characters; - } + get_glyphs (hb_font, glyphs_before, tables[table.first].before); + get_glyphs (hb_font, glyphs_input, tables[table.first].input ); + get_glyphs (hb_font, glyphs_after, tables[table.first].after ); + get_glyphs (hb_font, glyphs_output, tables[table.first].output); + // std::cout << " Before: " << tables[table.first].before.c_str() << std::endl; + // std::cout << " Input: " << tables[table.first].input.c_str() << std::endl; + // std::cout << " After: " << tables[table.first].after.c_str() << std::endl; + // std::cout << " Output: " << tables[table.first].output.c_str() << std::endl; + + hb_set_destroy (glyphs_before); hb_set_destroy (glyphs_input); - hb_font_destroy (hb_font); - } + hb_set_destroy (glyphs_after); + hb_set_destroy (glyphs_output); + + } // End count (lookups) + + hb_font_destroy (hb_font); + } else { // std::cout << " Did not find '" << table.first << "'!" << std::endl; } } } - // for (auto table: res->openTypeSubstitutions) { - // std::cout << table.first << ": " << table.second << std::endl; - // } #else std::cerr << "Requires Harfbuzz 1.2.3 for visualizing alternative glyph OpenType tables. " << "Compiled with: " << HB_VERSION_STRING << "." << std::endl; diff --git a/src/libnrtype/OpenTypeUtil.h b/src/libnrtype/OpenTypeUtil.h index 9a792eb0e..0a249f123 100644 --- a/src/libnrtype/OpenTypeUtil.h +++ b/src/libnrtype/OpenTypeUtil.h @@ -18,6 +18,16 @@ * All three provide variable amounts of access to data. */ +// OpenType substitution +class OTSubstitution { +public: + OTSubstitution() {}; + Glib::ustring before; + Glib::ustring input; + Glib::ustring after; + Glib::ustring output; +}; + // An OpenType fvar axis. class OTVarAxis { public: @@ -53,11 +63,11 @@ inline FT_Fixed FTDoubleToFixed (double value) { return static_cast(value * 65536); } +// This would be better if one had std::vector instead of OTSubstitution where each +// entry corresponded to one substitution (e.g. ff -> ff) but Harfbuzz at the moment cannot return +// individual substitutions. See Harfbuzz issue #673. void readOpenTypeGsubTable (const FT_Face ft_face, - std::map& tables, - std::map& stylistic, - std::map& ligatures, - std::map& numeric); + std::map& tables); void readOpenTypeFvarAxes (const FT_Face ft_face, std::map& axes); diff --git a/src/libnrtype/font-instance.h b/src/libnrtype/font-instance.h index ec26ba358..5cb26aec2 100644 --- a/src/libnrtype/font-instance.h +++ b/src/libnrtype/font-instance.h @@ -39,13 +39,8 @@ public: int nbGlyph, maxGlyph; font_glyph* glyphs; - // Map of OpenType tables found in font (convert to std::set?) - std::map openTypeTables; - - // Map of substitutions indexed by table - std::map openTypeStylistic; - std::map openTypeLigatures; - std::map openTypeNumeric; + // Map of OpenType tables found in font. + std::map openTypeTables; // Maps for font variations. std::map openTypeVarAxes; // Axes with ranges diff --git a/src/ui/widget/font-variants.cpp b/src/ui/widget/font-variants.cpp index 6d1da6d60..abe5add62 100644 --- a/src/ui/widget/font-variants.cpp +++ b/src/ui/widget/font-variants.cpp @@ -340,18 +340,19 @@ namespace Widget { // Add tooltips _feature_entry.set_tooltip_text( _("Feature settings in CSS form. No sanity checking is performed.")); - _feature_list.set_justify( Gtk::JUSTIFY_LEFT ); - _feature_list.set_line_wrap( true ); - _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 _feature_vbox.pack_start( _feature_entry ); _feature_vbox.pack_start( _feature_label ); - _feature_vbox.pack_start( _feature_list ); _feature_vbox.pack_start( _feature_substitutions ); + _feature_vbox.pack_start( _feature_list ); + _feature_frame.add( _feature_vbox ); pack_start( _feature_frame, Gtk::PACK_SHRINK ); @@ -582,7 +583,7 @@ namespace Widget { font_instance* res = font_factory::Default()->FaceFromFontSpecification( font_spec.c_str() ); if( res ) { - std::map::iterator it; + std::map::iterator it; if((it = res->openTypeTables.find("liga"))!= res->openTypeTables.end() || (it = res->openTypeTables.find("clig"))!= res->openTypeTables.end()) { @@ -767,20 +768,28 @@ namespace Widget { Glib::ustring markup_dlig; Glib::ustring markup_hlig; Glib::ustring markup_calt; - for (auto table: res->openTypeLigatures) { - - Glib::ustring markup; - markup += ""; - markup += Glib::Markup::escape_text(table.second); - markup += ""; - 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; + for (auto table: res->openTypeTables) { + + if (table.first == "liga" || + table.first == "dlig" || + table.first == "dlig" || + table.first == "hgli" || + table.first == "calt") { + + Glib::ustring markup; + markup += ""; + markup += Glib::Markup::escape_text(table.second.output); + markup += ""; + + 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() ); @@ -797,7 +806,8 @@ namespace Widget { Glib::ustring markup_afrc; Glib::ustring markup_ordn; Glib::ustring markup_zero; - for (auto table: res->openTypeNumeric) { + + for (auto table: res->openTypeTables) { Glib::ustring markup; markup += "descr); + markup += "'>"; + markup += Glib::Markup::escape_text(table.second.input); + markup += ""; + + markup += " "; + + markup += ""; + markup += ""; + markup += Glib::Markup::escape_text(table.second.input); + markup += ""; + markup += "\n"; + } + } + + // GSUB lookup type 3 (1 to many mapping). Optionally type 1. + for (auto table: res->openTypeTables) { + 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(); + + for (int i = 0; i < number; ++i) { + markup += ""; + markup += table.first; + if (i != 0) { + markup += " "; + markup += std::to_string (i + 1); + } + markup += ""; + markup += ": "; + + markup += ""; + markup += Glib::Markup::escape_text(table.second.input); + markup += ""; + + markup += " "; + + markup += ""; + markup += ""; + markup += Glib::Markup::escape_text(table.second.input); + markup += ""; + markup += "\n"; + } + } + } + + _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) { - // std::cout << "Other: " << it->first << " Occurrences: " << it->second << std::endl; ott_list += it->first; ott_list += ", "; } - _feature_list.set_text( ott_list.c_str() ); - - // ""; - Glib::ustring markup; - - for (auto table: res->openTypeStylistic) { - - markup += ""; - markup += table.first; - markup += ""; - markup += ": "; - - markup += ""; - markup += Glib::Markup::escape_text(table.second); - markup += ""; - - markup += " → "; - - markup += ""; - markup += ""; - markup += Glib::Markup::escape_text(table.second); - markup += ""; - markup += "\n"; - + 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( "" ); } - _feature_substitutions.set_markup ( markup.c_str() ); } else { std::cerr << "FontVariants::update(): Couldn't find font_instance for: " -- cgit v1.2.3