// SPDX-License-Identifier: GPL-2.0-or-later /** @file * TODO: insert short description here *//* * Authors: see git history * * Copyright (C) 2018 Authors * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include "OpenTypeUtil.h" #include // For debugging #include #include // FreeType #include FT_FREETYPE_H #include FT_MULTIPLE_MASTERS_H #include FT_SFNT_NAMES_H // Harfbuzz #include #include #include // SVG in OpenType #include "io/stream/gzipstream.h" #include "io/stream/bufferstream.h" #include "display/cairo-utils.h" // Utilities used in this file struct HbSetDeleter { void operator()(hb_set_t* x) { hb_set_destroy(x); } }; using HbSet = std::unique_ptr; void dump_tag( guint32 *tag, Glib::ustring prefix = "", bool lf=true ) { std::cout << prefix << ((char)((*tag & 0xff000000)>>24)) << ((char)((*tag & 0x00ff0000)>>16)) << ((char)((*tag & 0x0000ff00)>> 8)) << ((char)((*tag & 0x000000ff) )); if( lf ) { std::cout << std::endl; } } Glib::ustring extract_tag( guint32 *tag ) { Glib::ustring tag_name; tag_name += ((char)((*tag & 0xff000000)>>24)); tag_name += ((char)((*tag & 0x00ff0000)>>16)); tag_name += ((char)((*tag & 0x0000ff00)>> 8)); tag_name += ((char)((*tag & 0x000000ff) )); return tag_name; } // Later (see get_glyphs) we need to lookup the Unicode codepoint for a glyph // but there's no direct API for that. So, we need a way to iterate over all // glyph mappings and build a reverse map. // FIXME: we should handle UVS at some point... or better, work with glyphs directly // Allows looking up the lowest Unicode codepoint mapped to a given glyph. // To do so, it lazily builds a reverse map. class GlyphToUnicodeMap { protected: hb_font_t* font; HbSet codepointSet; std::unordered_map mappings; bool more = true; // false if we have finished iterating the set hb_codepoint_t codepoint = HB_SET_VALUE_INVALID; // current iteration public: GlyphToUnicodeMap(hb_font_t* font): font(font), codepointSet(hb_set_create()) { hb_face_collect_unicodes(hb_font_get_face(font), codepointSet.get()); } hb_codepoint_t lookup(hb_codepoint_t glyph) { // first, try to find it in the mappings we've seen so far if (auto it = mappings.find(glyph); it != mappings.end()) return it->second; // populate more mappings from the set while ((more = (more && hb_set_next(codepointSet.get(), &codepoint)))) { // get the glyph that this codepoint is associated with, if any hb_codepoint_t tGlyph; if (!hb_font_get_nominal_glyph(font, codepoint, &tGlyph)) continue; // save the mapping, and return if this is the one we were looking for mappings.emplace(tGlyph, codepoint); if (tGlyph == glyph) return codepoint; } return 0; } }; void get_glyphs(GlyphToUnicodeMap& glyphMap, HbSet& set, Glib::ustring& characters) { hb_codepoint_t glyph = -1; while (hb_set_next(set.get(), &glyph)) { if (auto codepoint = glyphMap.lookup(glyph)) characters += codepoint; } } SVGTableEntry::~SVGTableEntry() = default; // Make a list of all tables found in the GSUB // This list includes all tables regardless of script or language. // Use Harfbuzz, Pango's equivalent calls are deprecated. void readOpenTypeGsubTable (hb_font_t* hb_font, std::map& tables) { hb_face_t* hb_face = hb_font_get_face (hb_font); tables.clear(); // First time to get size of array auto script_count = hb_ot_layout_table_get_script_tags(hb_face, HB_OT_TAG_GSUB, 0, nullptr, nullptr); auto const hb_scripts = g_new(hb_tag_t, script_count + 1); // Second time to fill array (this two step process was not necessary with Pango). 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, nullptr, nullptr); if(language_count > 0) { auto const hb_languages = g_new(hb_tag_t, language_count + 1); 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, nullptr, nullptr); 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) { // std::cout << " Feature: " << extract_tag(&hb_features[k]) << std::endl; tables[ extract_tag(&hb_features[k])]; } g_free(hb_features); } g_free(hb_languages); } 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, nullptr, nullptr); 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, HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX, 0, &feature_count, hb_features); for(unsigned int k = 0; k < feature_count; ++k) { // std::cout << " Feature: " << extract_tag(&hb_features[k]) << std::endl; tables[ extract_tag(&hb_features[k])]; } g_free(hb_features); } } // Find glyphs in OpenType substitution tables ('gsub'). // Note that pango's functions are just dummies. Must use harfbuzz. GlyphToUnicodeMap glyphMap (hb_font); // Loop over all tables for (auto table: tables) { // 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 */ || table.first == "ornm" /* Ornaments */ || table.first == "nalt" /* Alternative Annotation */ || table.first == "hist" /* Historical Forms */ || (table.first[0] == 's' && table.first[1] == 's' && !(table.first[2] == 't')) || (table.first[0] == 'c' && table.first[1] == 'v'); bool ligature = ( table.first == "liga" || // Standard ligatures table.first == "clig" || // Common ligatures table.first == "dlig" || // Discretionary ligatures table.first == "hlig" || // Historical ligatures table.first == "calt" ); // Contextual alternatives bool numeric = ( table.first == "lnum" || // Lining numerals table.first == "onum" || // Old style table.first == "pnum" || // Proportional table.first == "tnum" || // Tabular table.first == "frac" || // Diagonal fractions table.first == "afrc" || // Stacked fractions table.first == "ordn" || // Ordinal fractions table.first == "zero" ); // Slashed zero if (style || ligature || numeric) { unsigned int feature_index; if ( hb_ot_layout_language_find_feature (hb_face, HB_OT_TAG_GSUB, 0, // Assume one script exists with index 0 HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX, HB_TAG(table.first[0], table.first[1], table.first[2], 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; int count = hb_ot_layout_feature_get_lookups (hb_face, HB_OT_TAG_GSUB, feature_index, 0, // Start &lookup_count, lookup_indexes ); // std::cout << " Lookup count: " << count << " total: " << lookup_count << std::endl; for (int i = 0; i < count; ++i) { HbSet glyphs_before (hb_set_create()); HbSet glyphs_input (hb_set_create()); HbSet glyphs_after (hb_set_create()); HbSet glyphs_output (hb_set_create()); hb_ot_layout_lookup_collect_glyphs (hb_face, HB_OT_TAG_GSUB, lookup_indexes[i], glyphs_before.get(), glyphs_input.get(), glyphs_after.get(), glyphs_output.get() ); // 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; get_glyphs (glyphMap, glyphs_before, tables[table.first].before); get_glyphs (glyphMap, glyphs_input, tables[table.first].input ); get_glyphs (glyphMap, glyphs_after, tables[table.first].after ); get_glyphs (glyphMap, 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; } // End count (lookups) } else { // std::cout << " Did not find '" << table.first << "'!" << std::endl; } } } g_free(hb_scripts); } // Harfbuzz now as API for variations (Version 2.2, Nov 29 2018). // Make a list of all Variation axes with ranges. void readOpenTypeFvarAxes(const FT_Face ft_face, std::map& axes) { #if FREETYPE_MAJOR *10000 + FREETYPE_MINOR*100 + FREETYPE_MICRO >= 20701 FT_MM_Var* mmvar = nullptr; FT_Multi_Master mmtype; if (FT_HAS_MULTIPLE_MASTERS( ft_face ) && // Font has variables FT_Get_MM_Var( ft_face, &mmvar) == 0 && // We found the data FT_Get_Multi_Master( ft_face, &mmtype) !=0) { // It's not an Adobe MM font FT_Fixed coords[mmvar->num_axis]; FT_Get_Var_Design_Coordinates( ft_face, mmvar->num_axis, coords ); for (size_t i = 0; i < mmvar->num_axis; ++i) { FT_Var_Axis* axis = &mmvar->axis[i]; axes[axis->name] = OTVarAxis(FTFixedToDouble(axis->minimum), FTFixedToDouble(axis->def), FTFixedToDouble(axis->maximum), FTFixedToDouble(coords[i]), i); } // for (auto a: axes) { // std::cout << " " << a.first // << " min: " << a.second.minimum // << " max: " << a.second.maximum // << " set: " << a.second.set_val << std::endl; // } } #endif /* FREETYPE Version */ } // Harfbuzz now as API for named variations (Version 2.2, Nov 29 2018). // Make a list of all Named instances with axis values. void readOpenTypeFvarNamed(const FT_Face ft_face, std::map& named) { #if FREETYPE_MAJOR *10000 + FREETYPE_MINOR*100 + FREETYPE_MICRO >= 20701 FT_MM_Var* mmvar = nullptr; FT_Multi_Master mmtype; if (FT_HAS_MULTIPLE_MASTERS( ft_face ) && // Font has variables FT_Get_MM_Var( ft_face, &mmvar) == 0 && // We found the data FT_Get_Multi_Master( ft_face, &mmtype) !=0) { // It's not an Adobe MM font std::cout << " Multiple Masters: variables: " << mmvar->num_axis << " named styles: " << mmvar->num_namedstyles << std::endl; // const FT_UInt numNames = FT_Get_Sfnt_Name_Count(ft_face); // std::cout << " number of names: " << numNames << std::endl; // FT_SfntName ft_name; // for (FT_UInt i = 0; i < numNames; ++i) { // if (FT_Get_Sfnt_Name(ft_face, i, &ft_name) != 0) { // continue; // } // Glib::ustring name; // for (size_t j = 0; j < ft_name.string_len; ++j) { // name += (char)ft_name.string[j]; // } // std::cout << " " << i << ": " << name << std::endl; // } } #endif /* FREETYPE Version */ } #define HB_OT_TAG_SVG HB_TAG('S','V','G',' ') // Get SVG glyphs out of an OpenType font. void readOpenTypeSVGTable(hb_font_t* hb_font, std::map& glyphs) { hb_face_t* hb_face = hb_font_get_face (hb_font); // Harfbuzz has some support for SVG fonts but it is not exposed until version 2.1 (Oct 30, 2018). // We do it the hard way! hb_blob_t *hb_blob = hb_face_reference_table (hb_face, HB_OT_TAG_SVG); if (!hb_blob) { // No SVG table in font! return; } unsigned int svg_length = hb_blob_get_length (hb_blob); if (svg_length == 0) { // No SVG glyphs in table! return; } const char* data = hb_blob_get_data(hb_blob, &svg_length); if (!data) { std::cerr << "readOpenTypeSVGTable: Failed to get data! " << std::endl; return; } // OpenType fonts use Big Endian uint32_t offset = ((data[2] & 0xff) << 24) + ((data[3] & 0xff) << 16) + ((data[4] & 0xff) << 8) + (data[5] & 0xff); // std::cout << "Offset: " << offset << std::endl; // Bytes 6-9 are reserved. uint16_t entries = ((data[offset] & 0xff) << 8) + (data[offset+1] & 0xff); // std::cout << "Number of entries: " << entries << std::endl; for (int entry = 0; entry < entries; ++entry) { uint32_t base = offset + 2 + entry * 12; uint16_t startGlyphID = ((data[base ] & 0xff) << 8) + (data[base+1] & 0xff); uint16_t endGlyphID = ((data[base+2] & 0xff) << 8) + (data[base+3] & 0xff); uint32_t offsetGlyph = ((data[base+4] & 0xff) << 24) + ((data[base+5] & 0xff) << 16) +((data[base+6] & 0xff) << 8) + (data[base+7] & 0xff); uint32_t lengthGlyph = ((data[base+8] & 0xff) << 24) + ((data[base+9] & 0xff) << 16) +((data[base+10] & 0xff) << 8) + (data[base+11] & 0xff); // std::cout << "Entry " << entry << ": Start: " << startGlyphID << " End: " << endGlyphID // << " Offset: " << offsetGlyph << " Length: " << lengthGlyph << std::endl; std::string svg; // static cast is needed as hb_blob_get_length returns char but we are comparing to a value greater than allowed by char. if (lengthGlyph > 1 && // static_cast(data[offset + offsetGlyph + 0]) == 0x1f && static_cast(data[offset + offsetGlyph + 1]) == 0x8b) { // Glyph is gzipped std::vector buffer; for (unsigned int c = offsetGlyph; c < offsetGlyph + lengthGlyph; ++c) { buffer.push_back(data[offset + c]); } Inkscape::IO::BufferInputStream zipped(buffer); Inkscape::IO::GzipInputStream gzin(zipped); for (int character = gzin.get(); character != -1; character = gzin.get()) { svg+= (char)character; } } else { // Glyph is not compressed for (unsigned int c = offsetGlyph; c < offsetGlyph + lengthGlyph; ++c) { svg += (unsigned char) data[offset + c]; } } for (unsigned int i = startGlyphID; i < endGlyphID+1; ++i) { glyphs[i].svg = svg; } // for (auto glyph : glyphs) { // std::cout << "Glyph: " << glyph.first << std::endl; // std::cout << glyph.second.svg << std::endl; // } } } /* 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:textwidth=99 :