fontisan 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +13 -0
  4. data/.rubocop_todo.yml +217 -0
  5. data/Gemfile +15 -0
  6. data/LICENSE +24 -0
  7. data/README.adoc +984 -0
  8. data/Rakefile +95 -0
  9. data/exe/fontisan +7 -0
  10. data/fontisan.gemspec +44 -0
  11. data/lib/fontisan/binary/base_record.rb +57 -0
  12. data/lib/fontisan/binary/structures.rb +84 -0
  13. data/lib/fontisan/cli.rb +192 -0
  14. data/lib/fontisan/commands/base_command.rb +82 -0
  15. data/lib/fontisan/commands/dump_table_command.rb +71 -0
  16. data/lib/fontisan/commands/features_command.rb +94 -0
  17. data/lib/fontisan/commands/glyphs_command.rb +50 -0
  18. data/lib/fontisan/commands/info_command.rb +120 -0
  19. data/lib/fontisan/commands/optical_size_command.rb +41 -0
  20. data/lib/fontisan/commands/scripts_command.rb +59 -0
  21. data/lib/fontisan/commands/tables_command.rb +52 -0
  22. data/lib/fontisan/commands/unicode_command.rb +76 -0
  23. data/lib/fontisan/commands/variable_command.rb +61 -0
  24. data/lib/fontisan/config/features.yml +143 -0
  25. data/lib/fontisan/config/scripts.yml +42 -0
  26. data/lib/fontisan/constants.rb +78 -0
  27. data/lib/fontisan/error.rb +15 -0
  28. data/lib/fontisan/font_loader.rb +109 -0
  29. data/lib/fontisan/formatters/text_formatter.rb +314 -0
  30. data/lib/fontisan/models/all_scripts_features_info.rb +21 -0
  31. data/lib/fontisan/models/features_info.rb +42 -0
  32. data/lib/fontisan/models/font_info.rb +99 -0
  33. data/lib/fontisan/models/glyph_info.rb +26 -0
  34. data/lib/fontisan/models/optical_size_info.rb +33 -0
  35. data/lib/fontisan/models/scripts_info.rb +39 -0
  36. data/lib/fontisan/models/table_info.rb +55 -0
  37. data/lib/fontisan/models/unicode_mappings.rb +42 -0
  38. data/lib/fontisan/models/variable_font_info.rb +82 -0
  39. data/lib/fontisan/open_type_collection.rb +97 -0
  40. data/lib/fontisan/open_type_font.rb +292 -0
  41. data/lib/fontisan/parsers/tag.rb +77 -0
  42. data/lib/fontisan/tables/cmap.rb +284 -0
  43. data/lib/fontisan/tables/fvar.rb +157 -0
  44. data/lib/fontisan/tables/gpos.rb +111 -0
  45. data/lib/fontisan/tables/gsub.rb +111 -0
  46. data/lib/fontisan/tables/head.rb +114 -0
  47. data/lib/fontisan/tables/layout_common.rb +73 -0
  48. data/lib/fontisan/tables/name.rb +188 -0
  49. data/lib/fontisan/tables/os2.rb +175 -0
  50. data/lib/fontisan/tables/post.rb +148 -0
  51. data/lib/fontisan/true_type_collection.rb +98 -0
  52. data/lib/fontisan/true_type_font.rb +313 -0
  53. data/lib/fontisan/utilities/checksum_calculator.rb +89 -0
  54. data/lib/fontisan/version.rb +5 -0
  55. data/lib/fontisan.rb +80 -0
  56. metadata +150 -0
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "yaml"
5
+ require_relative "base_command"
6
+ require_relative "../models/features_info"
7
+ require_relative "../models/all_scripts_features_info"
8
+
9
+ module Fontisan
10
+ module Commands
11
+ # Command to extract and display features from GSUB/GPOS tables
12
+ class FeaturesCommand < BaseCommand
13
+ def run
14
+ script = @options[:script]
15
+
16
+ # If no script specified, show features for all scripts
17
+ return features_for_all_scripts unless script
18
+
19
+ # Show features for specific script
20
+ features_for_script(script)
21
+ end
22
+
23
+ private
24
+
25
+ def features_for_script(script)
26
+ result = Models::FeaturesInfo.new
27
+ result.script = script
28
+ features_set = Set.new
29
+
30
+ # Collect features from GSUB table
31
+ if font.has_table?(Constants::GSUB_TAG)
32
+ gsub = font.table(Constants::GSUB_TAG)
33
+ features_set.merge(gsub.features(script_tag: script))
34
+ end
35
+
36
+ # Collect features from GPOS table
37
+ if font.has_table?(Constants::GPOS_TAG)
38
+ gpos = font.table(Constants::GPOS_TAG)
39
+ features_set.merge(gpos.features(script_tag: script))
40
+ end
41
+
42
+ # Load feature descriptions
43
+ descriptions = load_feature_descriptions
44
+
45
+ # Build feature records
46
+ result.features = features_set.sort.map do |tag|
47
+ Models::FeatureRecord.new(
48
+ tag: tag,
49
+ description: descriptions[tag] || "Unknown feature",
50
+ )
51
+ end
52
+
53
+ result.feature_count = result.features.length
54
+ result
55
+ end
56
+
57
+ def features_for_all_scripts
58
+ result = Models::AllScriptsFeaturesInfo.new
59
+ scripts_set = Set.new
60
+
61
+ # Collect all scripts
62
+ if font.has_table?(Constants::GSUB_TAG)
63
+ gsub = font.table(Constants::GSUB_TAG)
64
+ scripts_set.merge(gsub.scripts)
65
+ end
66
+
67
+ if font.has_table?(Constants::GPOS_TAG)
68
+ gpos = font.table(Constants::GPOS_TAG)
69
+ scripts_set.merge(gpos.scripts)
70
+ end
71
+
72
+ # Get features for each script
73
+ result.scripts_features = scripts_set.sort.map do |script_tag|
74
+ features_for_script(script_tag)
75
+ end
76
+
77
+ result
78
+ end
79
+
80
+ def load_feature_descriptions
81
+ config_path = File.join(
82
+ File.dirname(__FILE__),
83
+ "..",
84
+ "config",
85
+ "features.yml",
86
+ )
87
+ YAML.load_file(config_path)
88
+ rescue StandardError => e
89
+ warn "Warning: Could not load feature descriptions: #{e.message}"
90
+ {}
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fontisan
4
+ module Commands
5
+ # Command to list glyph names from a font file
6
+ #
7
+ # Retrieves glyph names from the post table. Different post table versions
8
+ # provide different levels of glyph name information:
9
+ # - Version 1.0: Standard 258 Mac glyph names
10
+ # - Version 2.0: Custom glyph names
11
+ # - Version 3.0+: No glyph names
12
+ #
13
+ # @example List glyph names from a font
14
+ # command = GlyphsCommand.new("font.ttf")
15
+ # result = command.run
16
+ # puts result.glyph_count
17
+ class GlyphsCommand < BaseCommand
18
+ # Execute the command to retrieve glyph names
19
+ #
20
+ # @return [Models::GlyphInfo] Information about glyphs in the font
21
+ def run
22
+ glyph_info = Models::GlyphInfo.new
23
+
24
+ # Try to get glyph names from post table first
25
+ if font.has_table?(Constants::POST_TAG)
26
+ post_table = font.table(Constants::POST_TAG)
27
+ names = post_table.glyph_names
28
+
29
+ if names&.any?
30
+ glyph_info.glyph_names = names
31
+ glyph_info.glyph_count = names.length
32
+ glyph_info.source = "post_#{post_table.version}"
33
+ return glyph_info
34
+ end
35
+ end
36
+
37
+ # Future: Try CFF table if no post table or no names
38
+ # if font.has_table?('CFF ')
39
+ # # Get names from CFF
40
+ # end
41
+
42
+ # No glyph name information available
43
+ glyph_info.glyph_names = []
44
+ glyph_info.glyph_count = 0
45
+ glyph_info.source = "none"
46
+ glyph_info
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fontisan
4
+ module Commands
5
+ # Command to extract font metadata information.
6
+ #
7
+ # This command extracts comprehensive font information from various tables:
8
+ # - name table: family names, version, copyright, etc.
9
+ # - OS/2 table: vendor ID, embedding permissions
10
+ # - head table: font revision, units per em
11
+ #
12
+ # @example Extract font information
13
+ # command = InfoCommand.new("path/to/font.ttf")
14
+ # info = command.run
15
+ # puts info.family_name
16
+ class InfoCommand < BaseCommand
17
+ # Extract font information from all available tables.
18
+ #
19
+ # @return [Models::FontInfo] Font metadata information
20
+ def run
21
+ info = Models::FontInfo.new
22
+ populate_font_format(info)
23
+ populate_from_name_table(info) if font.has_table?(Constants::NAME_TAG)
24
+ populate_from_os2_table(info) if font.has_table?(Constants::OS2_TAG)
25
+ populate_from_head_table(info) if font.has_table?(Constants::HEAD_TAG)
26
+ info
27
+ end
28
+
29
+ private
30
+
31
+ # Populate font format and variable status based on font class and table presence.
32
+ #
33
+ # @param info [Models::FontInfo] FontInfo instance to populate
34
+ def populate_font_format(info)
35
+ # Determine base format from font class
36
+ info.font_format = case font
37
+ when TrueTypeFont
38
+ "truetype"
39
+ when OpenTypeFont
40
+ "cff"
41
+ else
42
+ "unknown"
43
+ end
44
+
45
+ # Check if variable font
46
+ info.is_variable = font.has_table?(Constants::FVAR_TAG)
47
+ end
48
+
49
+ # Populate FontInfo from the name table.
50
+ #
51
+ # @param info [Models::FontInfo] FontInfo instance to populate
52
+ def populate_from_name_table(info)
53
+ name_table = font.table(Constants::NAME_TAG)
54
+ return unless name_table
55
+
56
+ info.family_name = name_table.english_name(Tables::Name::FAMILY)
57
+ info.subfamily_name = name_table.english_name(Tables::Name::SUBFAMILY)
58
+ info.full_name = name_table.english_name(Tables::Name::FULL_NAME)
59
+ info.postscript_name = name_table.english_name(Tables::Name::POSTSCRIPT_NAME)
60
+ info.postscript_cid_name = name_table.english_name(Tables::Name::POSTSCRIPT_CID)
61
+ info.preferred_family = name_table.english_name(Tables::Name::PREFERRED_FAMILY)
62
+ info.preferred_subfamily = name_table.english_name(Tables::Name::PREFERRED_SUBFAMILY)
63
+ info.mac_font_menu_name = name_table.english_name(Tables::Name::COMPATIBLE_FULL)
64
+ info.version = name_table.english_name(Tables::Name::VERSION)
65
+ info.unique_id = name_table.english_name(Tables::Name::UNIQUE_ID)
66
+ info.description = name_table.english_name(Tables::Name::DESCRIPTION)
67
+ info.designer = name_table.english_name(Tables::Name::DESIGNER)
68
+ info.designer_url = name_table.english_name(Tables::Name::DESIGNER_URL)
69
+ info.manufacturer = name_table.english_name(Tables::Name::MANUFACTURER)
70
+ info.vendor_url = name_table.english_name(Tables::Name::VENDOR_URL)
71
+ info.trademark = name_table.english_name(Tables::Name::TRADEMARK)
72
+ info.copyright = name_table.english_name(Tables::Name::COPYRIGHT)
73
+ info.license_description = name_table.english_name(Tables::Name::LICENSE_DESCRIPTION)
74
+ info.license_url = name_table.english_name(Tables::Name::LICENSE_URL)
75
+ info.sample_text = name_table.english_name(Tables::Name::SAMPLE_TEXT)
76
+ end
77
+
78
+ # Populate FontInfo from the OS/2 table.
79
+ #
80
+ # @param info [Models::FontInfo] FontInfo instance to populate
81
+ def populate_from_os2_table(info)
82
+ os2_table = font.table(Constants::OS2_TAG)
83
+ return unless os2_table
84
+
85
+ info.vendor_id = os2_table.vendor_id
86
+ info.permissions = format_permissions(os2_table.type_flags)
87
+ end
88
+
89
+ # Populate FontInfo from the head table.
90
+ #
91
+ # @param info [Models::FontInfo] FontInfo instance to populate
92
+ def populate_from_head_table(info)
93
+ head_table = font.table(Constants::HEAD_TAG)
94
+ return unless head_table
95
+
96
+ info.font_revision = head_table.font_revision
97
+ info.units_per_em = head_table.units_per_em
98
+ end
99
+
100
+ # Format OS/2 embedding permission flags into a human-readable string.
101
+ #
102
+ # @param flags [Integer] OS/2 fsType flags
103
+ # @return [String] Formatted permission string
104
+ def format_permissions(flags)
105
+ emb = flags & 15
106
+ result = case emb
107
+ when 0 then "Installable"
108
+ when 2 then "Restricted License"
109
+ when 4 then "Preview & Print"
110
+ when 8 then "Editable"
111
+ else "Unknown (#{emb})"
112
+ end
113
+
114
+ result += ", No subsetting" if flags.anybits?(0x100)
115
+ result += ", Bitmap only" if flags.anybits?(0x200)
116
+ result
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_command"
4
+ require_relative "../models/optical_size_info"
5
+
6
+ module Fontisan
7
+ module Commands
8
+ # Command to extract optical size information from fonts
9
+ #
10
+ # Optical size information indicates the design size range for which a font
11
+ # is optimized. This information can come from:
12
+ # - OS/2 table version 5+ (usLowerOpticalPointSize, usUpperOpticalPointSize)
13
+ # - GPOS 'size' feature (not yet implemented)
14
+ class OpticalSizeCommand < BaseCommand
15
+ # Execute the optical size extraction command
16
+ #
17
+ # @return [Models::OpticalSizeInfo] Optical size information
18
+ def run
19
+ result = Models::OpticalSizeInfo.new
20
+
21
+ # Try OS/2 table first
22
+ if font.has_table?(Constants::OS2_TAG)
23
+ os2_table = font.table(Constants::OS2_TAG)
24
+
25
+ if os2_table.has_optical_point_size?
26
+ result.has_optical_size = true
27
+ result.source = "os2"
28
+ result.lower_point_size = os2_table.lower_optical_point_size
29
+ result.upper_point_size = os2_table.upper_optical_point_size
30
+ return result
31
+ end
32
+ end
33
+
34
+ # No optical size information
35
+ result.has_optical_size = false
36
+ result.source = "none"
37
+ result
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "yaml"
5
+ require_relative "base_command"
6
+ require_relative "../models/scripts_info"
7
+
8
+ module Fontisan
9
+ module Commands
10
+ # Command to extract and display scripts from GSUB/GPOS tables
11
+ class ScriptsCommand < BaseCommand
12
+ def run
13
+ result = Models::ScriptsInfo.new
14
+ scripts_set = Set.new
15
+
16
+ # Collect scripts from GSUB table
17
+ if font.has_table?(Constants::GSUB_TAG)
18
+ gsub = font.table(Constants::GSUB_TAG)
19
+ scripts_set.merge(gsub.scripts)
20
+ end
21
+
22
+ # Collect scripts from GPOS table
23
+ if font.has_table?(Constants::GPOS_TAG)
24
+ gpos = font.table(Constants::GPOS_TAG)
25
+ scripts_set.merge(gpos.scripts)
26
+ end
27
+
28
+ # Load script descriptions from configuration
29
+ descriptions = load_script_descriptions
30
+
31
+ # Build script records
32
+ result.scripts = scripts_set.sort.map do |tag|
33
+ Models::ScriptRecord.new(
34
+ tag: tag,
35
+ description: descriptions[tag] || "Unknown script",
36
+ )
37
+ end
38
+
39
+ result.script_count = result.scripts.length
40
+ result
41
+ end
42
+
43
+ private
44
+
45
+ def load_script_descriptions
46
+ config_path = File.join(
47
+ File.dirname(__FILE__),
48
+ "..",
49
+ "config",
50
+ "scripts.yml",
51
+ )
52
+ YAML.load_file(config_path)
53
+ rescue StandardError => e
54
+ warn "Warning: Could not load script descriptions: #{e.message}"
55
+ {}
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fontisan
4
+ module Commands
5
+ # Command to list all tables in a font file.
6
+ #
7
+ # This command extracts metadata about all tables present in a font file,
8
+ # including their tags, lengths, offsets, and checksums.
9
+ #
10
+ # @example List font tables
11
+ # command = TablesCommand.new("path/to/font.ttf")
12
+ # table_info = command.run
13
+ # puts "Tables: #{table_info.num_tables}"
14
+ class TablesCommand < BaseCommand
15
+ # Extract table information from the font.
16
+ #
17
+ # @return [Models::TableInfo] Font table metadata
18
+ def run
19
+ table_info = Models::TableInfo.new
20
+ table_info.sfnt_version = format_sfnt_version(font.header.sfnt_version)
21
+ table_info.num_tables = font.tables.length
22
+
23
+ table_info.tables = font.tables.map do |entry|
24
+ Models::TableEntry.new(
25
+ tag: entry.tag,
26
+ length: entry.table_length,
27
+ offset: entry.offset,
28
+ checksum: entry.checksum,
29
+ )
30
+ end
31
+
32
+ table_info
33
+ end
34
+
35
+ private
36
+
37
+ # Format the SFNT version into a human-readable string.
38
+ #
39
+ # @param version [Integer] SFNT version number
40
+ # @return [String] Formatted version string
41
+ def format_sfnt_version(version)
42
+ if version == 0x00010000
43
+ "TrueType (0x00010000)"
44
+ elsif version == 0x4F54544F # 'OTTO'
45
+ "OpenType CFF (OTTO)"
46
+ else
47
+ format("0x%08X", version)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_command"
4
+ require_relative "../models/unicode_mappings"
5
+
6
+ module Fontisan
7
+ module Commands
8
+ # Command to list Unicode to glyph index mappings from a font file
9
+ #
10
+ # Retrieves character code to glyph index mappings from the cmap table.
11
+ # Optionally includes glyph names from the post table if available.
12
+ #
13
+ # @example List Unicode mappings from a font
14
+ # command = UnicodeCommand.new("font.ttf")
15
+ # result = command.run
16
+ # puts result.count
17
+ class UnicodeCommand < BaseCommand
18
+ # Execute the command to retrieve Unicode mappings
19
+ #
20
+ # @return [Models::UnicodeMappings] Unicode to glyph mappings
21
+ def run
22
+ result = Models::UnicodeMappings.new
23
+ result.mappings = []
24
+ result.count = 0
25
+
26
+ return result unless font.has_table?(Constants::CMAP_TAG)
27
+
28
+ cmap_table = font.table(Constants::CMAP_TAG)
29
+ mappings_hash = cmap_table.unicode_mappings
30
+
31
+ return result if mappings_hash.empty?
32
+
33
+ # Optionally get glyph names if post table exists
34
+ glyph_names = fetch_glyph_names if font.has_table?(Constants::POST_TAG)
35
+
36
+ # Convert hash to array of mapping objects, sorted by codepoint
37
+ result.mappings = mappings_hash.map do |codepoint, glyph_index|
38
+ Models::UnicodeMapping.new(
39
+ codepoint: format_codepoint(codepoint),
40
+ glyph_index: glyph_index,
41
+ glyph_name: glyph_names&.[](glyph_index),
42
+ )
43
+ end.sort_by(&:codepoint)
44
+
45
+ result.count = result.mappings.length
46
+ result
47
+ end
48
+
49
+ private
50
+
51
+ # Format codepoint as U+XXXX or U+XXXXXX
52
+ #
53
+ # @param codepoint [Integer] Unicode codepoint value
54
+ # @return [String] Formatted codepoint string
55
+ def format_codepoint(codepoint)
56
+ if codepoint < 0x10000
57
+ format("U+%04X", codepoint)
58
+ else
59
+ format("U+%X", codepoint)
60
+ end
61
+ end
62
+
63
+ # Fetch glyph names from post table
64
+ #
65
+ # @return [Array<String>, nil] Array of glyph names or nil
66
+ def fetch_glyph_names
67
+ post_table = font.table(Constants::POST_TAG)
68
+ names = post_table.glyph_names
69
+ names if names&.any?
70
+ rescue StandardError
71
+ # If post table parsing fails, continue without glyph names
72
+ nil
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fontisan
4
+ module Commands
5
+ # Command to extract variable font information.
6
+ #
7
+ # This command extracts variation axes and named instances from variable
8
+ # fonts using the fvar (Font Variations) table.
9
+ #
10
+ # @example Extract variable font information
11
+ # command = VariableCommand.new("path/to/variable-font.ttf")
12
+ # info = command.run
13
+ # puts info.axes.first.tag
14
+ class VariableCommand < BaseCommand
15
+ # Extract variable font information from the fvar table.
16
+ #
17
+ # @return [Models::VariableFontInfo] Variable font information
18
+ def run
19
+ result = Models::VariableFontInfo.new
20
+
21
+ # Check if font has fvar table
22
+ unless font.has_table?(Constants::FVAR_TAG)
23
+ result.is_variable = false
24
+ result.axis_count = 0
25
+ result.instance_count = 0
26
+ result.axes = []
27
+ result.instances = []
28
+ return result
29
+ end
30
+
31
+ fvar_table = font.table(Constants::FVAR_TAG)
32
+ name_table = font.table(Constants::NAME_TAG) if font.has_table?(Constants::NAME_TAG)
33
+
34
+ result.is_variable = true
35
+ result.axis_count = fvar_table.axis_count
36
+ result.instance_count = fvar_table.instance_count
37
+
38
+ # Extract axes information
39
+ result.axes = fvar_table.axes.map do |axis|
40
+ Models::AxisInfo.new(
41
+ tag: axis.axis_tag,
42
+ name: name_table&.english_name(axis.axis_name_id),
43
+ min_value: axis.min_value,
44
+ default_value: axis.default_value,
45
+ max_value: axis.max_value,
46
+ )
47
+ end
48
+
49
+ # Extract instances information
50
+ result.instances = fvar_table.instances.map do |instance|
51
+ Models::InstanceInfo.new(
52
+ name: name_table&.english_name(instance[:name_id]),
53
+ coordinates: instance[:coordinates],
54
+ )
55
+ end
56
+
57
+ result
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,143 @@
1
+ # OpenType feature tags and descriptions
2
+ # Reference: OpenType specification
3
+ aalt: Access All Alternates
4
+ abvf: Above-base Forms
5
+ abvm: Above-base Mark Positioning
6
+ abvs: Above-base Substitutions
7
+ afrc: Alternative Fractions
8
+ akhn: Akhands
9
+ blwf: Below-base Forms
10
+ blwm: Below-base Mark Positioning
11
+ blws: Below-base Substitutions
12
+ c2pc: Petite Capitals From Capitals
13
+ c2sc: Small Capitals From Capitals
14
+ calt: Contextual Alternates
15
+ case: Case-Sensitive Forms
16
+ ccmp: Glyph Composition/Decomposition
17
+ cfar: Conjunct Form After Ro
18
+ cjct: Conjunct Forms
19
+ clig: Contextual Ligatures
20
+ cpct: Centered CJK Punctuation
21
+ cpsp: Capital Spacing
22
+ cswh: Contextual Swash
23
+ curs: Cursive Positioning
24
+ cv01: Character Variant 1
25
+ cv02: Character Variant 2
26
+ cv03: Character Variant 3
27
+ cv99: Character Variant 99
28
+ dlig: Discretionary Ligatures
29
+ dist: Distances
30
+ dnom: Denominators
31
+ dtls: Dotless Forms
32
+ expt: Expert Forms
33
+ falt: Final Glyph on Line Alternates
34
+ fin2: Terminal Forms #2
35
+ fin3: Terminal Forms #3
36
+ fina: Terminal Forms
37
+ frac: Fractions
38
+ fwid: Full Widths
39
+ half: Half Forms
40
+ haln: Halant Forms
41
+ halt: Alternate Half Widths
42
+ hist: Historical Forms
43
+ hkna: Horizontal Kana Alternates
44
+ hlig: Historical Ligatures
45
+ hngl: Hangul
46
+ hojo: Hojo Kanji Forms
47
+ hwid: Half Widths
48
+ init: Initial Forms
49
+ isol: Isolated Forms
50
+ ital: Italics
51
+ jalt: Justification Alternates
52
+ jp04: JIS2004 Forms
53
+ jp78: JIS78 Forms
54
+ jp83: JIS83 Forms
55
+ jp90: JIS90 Forms
56
+ kern: Kerning
57
+ lfbd: Left Bounds
58
+ liga: Standard Ligatures
59
+ ljmo: Leading Jamo Forms
60
+ lnum: Lining Figures
61
+ locl: Localized Forms
62
+ ltra: Left-to-right Alternates
63
+ ltrm: Left-to-right mirrored forms
64
+ mark: Mark Positioning
65
+ med2: Medial Forms #2
66
+ medi: Medial Forms
67
+ mgrk: Mathematical Greek
68
+ mkmk: Mark to Mark Positioning
69
+ mset: Mark Positioning via Substitution
70
+ nalt: Alternate Annotation Forms
71
+ nlck: NLC Kanji Forms
72
+ nukt: Nukta Forms
73
+ numr: Numerators
74
+ onum: Oldstyle Figures
75
+ opbd: Optical Bounds
76
+ ordn: Ordinals
77
+ ornm: Ornaments
78
+ palt: Proportional Alternate Widths
79
+ pcap: Petite Capitals
80
+ pkna: Proportional Kana
81
+ pnum: Proportional Figures
82
+ pref: Pre-Base Forms
83
+ pres: Pre-base Substitutions
84
+ pstf: Post-base Forms
85
+ psts: Post-base Substitutions
86
+ pwid: Proportional Widths
87
+ qwid: Quarter Widths
88
+ rand: Randomize
89
+ rclt: Required Contextual Alternates
90
+ rkrf: Rakar Forms
91
+ rlig: Required Ligatures
92
+ rphf: Reph Forms
93
+ rtbd: Right Bounds
94
+ rtla: Right-to-left Alternates
95
+ rtlm: Right-to-left mirrored forms
96
+ ruby: Ruby Notation Forms
97
+ rvrn: Required Variation Alternates
98
+ salt: Stylistic Alternates
99
+ sinf: Scientific Inferiors
100
+ size: Optical size
101
+ smcp: Small Capitals
102
+ smpl: Simplified Forms
103
+ ss01: Stylistic Set 1
104
+ ss02: Stylistic Set 2
105
+ ss03: Stylistic Set 3
106
+ ss04: Stylistic Set 4
107
+ ss05: Stylistic Set 5
108
+ ss06: Stylistic Set 6
109
+ ss07: Stylistic Set 7
110
+ ss08: Stylistic Set 8
111
+ ss09: Stylistic Set 9
112
+ ss10: Stylistic Set 10
113
+ ss11: Stylistic Set 11
114
+ ss12: Stylistic Set 12
115
+ ss13: Stylistic Set 13
116
+ ss14: Stylistic Set 14
117
+ ss15: Stylistic Set 15
118
+ ss16: Stylistic Set 16
119
+ ss17: Stylistic Set 17
120
+ ss18: Stylistic Set 18
121
+ ss19: Stylistic Set 19
122
+ ss20: Stylistic Set 20
123
+ subs: Subscript
124
+ sups: Superscript
125
+ swsh: Swash
126
+ titl: Titling
127
+ tjmo: Trailing Jamo Forms
128
+ tnam: Traditional Name Forms
129
+ tnum: Tabular Figures
130
+ trad: Traditional Forms
131
+ twid: Third Widths
132
+ unic: Unicase
133
+ valt: Alternate Vertical Metrics
134
+ vatu: Vattu Variants
135
+ vert: Vertical Writing
136
+ vhal: Alternate Vertical Half Metrics
137
+ vjmo: Vowel Jamo Forms
138
+ vkna: Vertical Kana Alternates
139
+ vkrn: Vertical Kerning
140
+ vpal: Proportional Alternate Vertical Metrics
141
+ vrt2: Vertical Alternates and Rotation
142
+ vrtr: Vertical Alternates for Rotation
143
+ zero: Slashed Zero