Fontisan: Font analysis tools and utilities
Purpose
Fontisan is a Ruby gem providing font analysis tools and utilities.
It is designed as a pure Ruby implementation with full object-oriented architecture, supporting extraction of information from OpenType and TrueType fonts (OTF, TTF, TTC).
The gem provides both a Ruby library API and a command-line interface, with structured output formats (YAML, JSON, text) via lutaml-model.
Fontisan is designed to replace the following tools:
-
otfinfofrom LCDF Typetools. Fontisan supports all features provided byotfinfo, including extraction of font metadata, OpenType tables, glyph names, Unicode mappings, variable font axes, optical size information, supported scripts, OpenType features, and raw table dumps. -
extract_ttcfrom ExtractTTC. Fontisan fully supersedes extract_ttc with Docker-like commands (ls,info,unpack) that work on both collections and individual fonts. Fontisan provides allextract_ttcfunctionality plus comprehensive font analysis, subsetting, validation, format conversion, and collection creation. See extract_ttc Migration Guide for detailed command mappings and usage examples.
Installation
Add this line to your application’s Gemfile:
gem "fontisan"And then execute:
bundle installOr install it yourself as:
gem install fontisanFeatures
-
Bidirectional font hint conversion (see Font Hinting Guide)
-
Extract comprehensive font metadata (name, version, designer, license, etc.)
-
List OpenType tables with checksums and offsets
-
Extract glyph names from post table
-
Display Unicode codepoint to glyph index mappings
-
Analyze variable font axes and named instances
-
Generate static font instances from variable fonts
-
Display optical size information
-
List supported scripts from GSUB/GPOS tables
-
List OpenType features (ligatures, kerning, etc.) by script
-
Dump raw binary table data for analysis
-
Font subsetting with multiple profiles (PDF, web, minimal)
-
Font validation with multiple severity levels
-
Collection management (pack/unpack TTC/OTC files with table deduplication)
-
Support for TTF, OTF, TTC, OTC font formats (production ready)
-
WOFF format support (reading complete, writing functional, pending full integration)
-
WOFF2 format support (reading complete with table transformations, writing planned)
-
SVG font generation (complete)
-
TTX/YAML/JSON export (complete)
-
Command-line interface with 18 commands
-
Ruby library API for programmatic access
-
Structured output in YAML, JSON, and text formats
-
Universal outline model for format-agnostic glyph representation (complete)
-
CFF CharString encoding/decoding (complete)
-
CFF INDEX structure building (complete)
-
CFF DICT structure building (complete)
-
TrueType curve converter for bi-directional quadratic/cubic conversion (complete)
-
Compound glyph decomposition with transformation support (complete)
-
CFF subroutine optimization for space-efficient OTF generation (preview mode)
-
Various loading modes for high-performance font indexing (5x faster)
-
Bidirectional hint conversion (TrueType ↔ PostScript) with validation (complete)
-
CFF2 variable font support for PostScript hint conversion (complete)
Font information
General
Extract comprehensive metadata from font files. This includes font names, version information, designer credits, vendor details, licensing information, and font metrics.
$ fontisan info FONT_FILE [--format FORMAT] [--brief]Where,
FONT_FILE-
Path to the font file (OTF, TTF, TTC, OTF)
FORMAT-
Output format:
text(default),json, oryaml --brief-
Show only basic font information
Brief mode
General
For font indexing systems that need to scan thousands of fonts quickly, use the
--brief flag to get essential metadata only. This mode uses metadata loading
and is 5x faster than full mode.
Brief mode provides significant performance improvements for font indexing:
-
5x faster than full mode by using the
metadataload mode -
Loads only 6 tables instead of 15-20 (name, head, hhea, maxp, OS/2, post)
-
Lower memory usage through reduced table loading
-
Optimized for batch processing of many fonts
Brief mode populates only the following 13 essential attributes:
- Font identification
-
-
font_format- Font format (truetype, cff) -
is_variable- Whether font is variable
-
- Essential names
-
-
family_name- Font family name -
subfamily_name- Font subfamily/style -
full_name- Full font name -
postscript_name- PostScript name
-
- Version info
-
-
version- Version string
-
- Metrics
-
-
font_revision- Font revision number -
units_per_em- Units per em
-
- Vendor
-
-
vendor_id- Vendor/foundry ID
-
Command-line usage
Syntax:
$ fontisan info FONT_FILE --brief [--format FORMAT]# Individual font
$ fontisan info font.ttf --brief
Font type: TrueType (Not Variable)
Family: Noto Sans
...
# Collection
$ fontisan info fonts.ttc --brief
Collection: fonts.ttc
Fonts: 35
Font 0 (offset: 152):
Font type: OpenType (CFF) (Not Variable)
Family: Noto Serif CJK JP ExtraLight
...$ fontisan info spec/fixtures/fonts/MonaSans/mona-sans-2.0.8/googlefonts/variable/MonaSans[wdth,wght].ttf --brief
Font type: TrueType (Variable)
Family: Mona Sans ExtraLight
Subfamily: Regular
Full name: Mona Sans ExtraLight
PostScript name: MonaSans-ExtraLight
Version: Version 2.001
Vendor ID: GTHB
Font revision: 2.00101
Units per em: 1000$ fontisan info font.ttf --brief --format json{
"font_format": "truetype",
"is_variable": false,
"family_name": "Open Sans",
"subfamily_name": "Regular",
"full_name": "Open Sans Regular",
"postscript_name": "OpenSans-Regular",
"version": "Version 3.000",
"font_revision": 3.0,
"vendor_id": "2001",
"units_per_em": 2048
}Ruby API usage
require 'fontisan'
info = Fontisan.info("font.ttf", brief: true)
# Access populated fields
puts info.family_name # "Open Sans"
puts info.postscript_name # "OpenSans-Regular"
puts info.is_variable # false
# Non-essential fields are nil
puts info.copyright # nil (not populated)
puts info.designer # nil (not populated)
# Serialize to YAML/JSON
puts info.to_yaml
puts info.to_jsonrequire 'fontisan'
# Specify font index for TTC/OTC files
info = Fontisan.info("/path/to/fonts.ttc", brief: true, font_index: 0)
puts info.family_nameFull mode
General
In full mode, these additional attributes are populated (remain nil in brief
mode):
-
postscript_cid_name,preferred_family,preferred_subfamily,mac_font_menu_name -
unique_id,description,designer,designer_url -
manufacturer,vendor_url,trademark,copyright -
license_description,license_url,sample_text,permissions
Command-line usage
Syntax:
$ fontisan info FONT_FILE [--format FORMAT]$ fontisan info spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf
Font type: TrueType
Family: Libertinus Serif
Subfamily: Regular
Full name: Libertinus Serif Regular
PostScript name: LibertinusSerif-Regular
Version: Version 7.051;RELEASE
Unique ID: 5.000;QUE ;LibertinusSerif-Regular
Designer: Philipp H. Poll, Khaled Hosny
Manufacturer: Caleb Maclennan
Vendor URL: https://github.com/alerque/libertinus
Vendor ID: QUE
License Description: This Font Software is licensed under the SIL Open Font
License, Version 1.1. This license is available with a
FAQ at: https://openfontlicense.org
License URL: https://openfontlicense.org
Font revision: 7.05099
Permissions: Installable
Units per em: 1000$ fontisan info spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf --format yamlfont_format: truetype
is_variable: false
family_name: Libertinus Serif
subfamily_name: Regular
full_name: Libertinus Serif Regular
postscript_name: LibertinusSerif-Regular
version: Version 7.051;RELEASE
unique_id: 5.000;QUE ;LibertinusSerif-Regular
designer: Philipp H. Poll, Khaled Hosny
manufacturer: Caleb Maclennan
vendor_url: https://github.com/alerque/libertinus
vendor_id: QUE
license_description: 'This Font Software is licensed under the SIL Open Font License,
Version 1.1. This license is available with a FAQ at: https://openfontlicense.org'
license_url: https://openfontlicense.org
font_revision: 7.050994873046875
permissions: Installable
units_per_em: 1000List OpenType tables
General
To understanding font structure and verifying table integrity, Fontisan provides detailed table listings.
Display the font’s table directory, showing all OpenType tables with their sizes, offsets, and checksums.
Command-line usage
Syntax:
$ fontisan tables FONT_FILE [--format FORMAT]Where,
FONT_FILE-
Path to the font file (OTF, TTF, or TTC)
FORMAT-
Output format:
text(default),json, oryaml
$ fontisan tables spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttfSFNT Version: TrueType (0x00010000)
Number of tables: 16
Tables:
GDEF 834 bytes (offset: 542156, checksum: 0x429C5C0C)
GPOS 17870 bytes (offset: 542992, checksum: 0x29CE4200)
OS/2 96 bytes (offset: 392, checksum: 0x4830F1C3)
cmap 3620 bytes (offset: 11412, checksum: 0x03AD3899)
cvt 248 bytes (offset: 18868, checksum: 0x3098127E)
fpgm 3596 bytes (offset: 15032, checksum: 0x622F0781)
gasp 8 bytes (offset: 542148, checksum: 0x00000010)
glyf 484900 bytes (offset: 30044, checksum: 0x0FF34594)
head 54 bytes (offset: 268, checksum: 0x18F5BDD0)
hhea 36 bytes (offset: 324, checksum: 0x191E2264)
hmtx 10924 bytes (offset: 488, checksum: 0x1F9D892B)
loca 10928 bytes (offset: 19116, checksum: 0x230B1A58)
maxp 32 bytes (offset: 360, checksum: 0x0EF919E7)
name 894 bytes (offset: 514944, checksum: 0x4E9173E6)
post 26308 bytes (offset: 515840, checksum: 0xE3D70231)
prep 239 bytes (offset: 18628, checksum: 0x8B4AB356)$ fontisan tables spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf --format yaml---
sfnt_version: TrueType (0x00010000)
num_tables: 16
tables:
- tag: GDEF
length: 834
offset: 542156
checksum: 1117543436
- tag: GPOS
length: 17870
offset: 542992
checksum: 701383168List glyph names
General
Show all glyph names defined in the font’s post table. Each glyph is listed with its index and name, useful for understanding the font’s character coverage.
Command-line usage
Syntax:
$ fontisan glyphs FONT_FILE [--format FORMAT]Where,
FONT_FILE-
Path to the font file (OTF, TTF, or TTC)
FORMAT-
Output format:
text(default),json, oryaml
$ fontisan glyphs spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttfGlyph count: 2731
Source: post_2.0
Glyph names:
0 .notdef
1 space
2 exclam
3 quotedbl
4 numbersign
5 dollar
6 percent
7 ampersand
8 quotesingle
9 parenleft
10 parenright
11 asterisk
12 plus
13 comma
14 hyphen
15 period
16 slash
17 zero
18 one
19 two
20 three
...Show Unicode mappings
General
Display Unicode codepoint to glyph index mappings from the cmap table. Shows which glyphs are assigned to which Unicode characters.
Command-line usage
Syntax:
$ fontisan unicode FONT_FILE [--format FORMAT]Where,
FONT_FILE-
Path to the font file (OTF, TTF, or TTC)
FORMAT-
Output format:
text(default),json, oryaml
$ fontisan unicode spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttfUnicode mappings: 2382
U+0020 glyph 1 space
U+0021 glyph 2 exclam
U+0022 glyph 3 quotedbl
U+0023 glyph 4 numbersign
U+0024 glyph 5 dollar
U+0025 glyph 6 percent
U+0026 glyph 7 ampersand
U+0027 glyph 8 quotesingle
U+0028 glyph 9 parenleft
U+0029 glyph 10 parenright
U+002A glyph 11 asterisk
U+002B glyph 12 plus
U+002C glyph 13 comma
U+002D glyph 14 hyphen
U+002E glyph 15 period
U+002F glyph 16 slash
U+0030 glyph 17 zero
U+0031 glyph 18 one
...Variable font information
General
Display variation axes and named instances for variable fonts. Shows the design space and predefined styles available in the font.
Command-line usage
Syntax:
$ fontisan variable FONT_FILE [--format FORMAT]Where,
FONT_FILE-
Path to the variable font file
FORMAT-
Output format:
text(default),json, oryaml
$ fontisan variable spec/fixtures/fonts/MonaSans/variable/MonaSans[wdth,wght].ttfAxis 0: wdth
Axis 0 name: Width
Axis 0 range: 75 125
Axis 0 default: 100
Axis 1: wght
Axis 1 name: Weight
Axis 1 range: 200 900
Axis 1 default: 400
Instance 0 name: Mona Sans Narrow Thin
Instance 0 position: 75 200
Instance 1 name: Mona Sans Narrow ExtraLight
Instance 1 position: 75 250
Instance 2 name: Mona Sans Narrow Light
Instance 2 position: 75 300
...Generate static instances from variable fonts
General
Generate static font instances from variable fonts at specific variation coordinates and output in any supported format (TTF, OTF, WOFF).
Command-line usage
Syntax:
$ fontisan instance VARIABLE_FONT [OPTIONS]Where,
VARIABLE_FONT-
Path to the variable font file
OPTIONS-
Instance generation options
Options:
--wght VALUE-
Weight axis value
--wdth VALUE-
Width axis value
--slnt VALUE-
Slant axis value
--ital VALUE-
Italic axis value
--opsz VALUE-
Optical size axis value
--to FORMAT-
Output format:
ttf(default),otf,woff, orwoff2 --output FILE-
Output file path
--optimize-
Enable CFF optimization for OTF output
--named-instance INDEX-
Use named instance by index
--list-instances-
List available named instances
--validate-
Validate font before generation
--dry-run-
Preview instance without generating
--progress-
Show progress during generation
$ fontisan instance variable.ttf --wght 700 --output bold.ttf
Generating instance... done
Writing output... done
Static font instance written to: bold.ttf$ fontisan instance variable.ttf --wght 300 --to otf --output light.otf
Generating instance... done
Writing output... done
Static font instance written to: light.otf$ fontisan instance variable.ttf --wght 600 --to woff --output semibold.woff
Generating instance... done
Writing output... done
Static font instance written to: semibold.woff$ fontisan instance variable.ttf --wght 600 --wdth 75 --output condensed.ttf
Generating instance... done
Writing output... done
Static font instance written to: condensed.ttf$ fontisan instance variable.ttf --list-instances
Available named instances:
[0] Instance 4
Coordinates:
wdth: 75.0
wght: 200.0
[1] Instance 5
Coordinates:
wdth: 75.0
wght: 250.0
[2] Instance 6
Coordinates:
wdth: 75.0
wght: 300.0$ fontisan instance variable.ttf --named-instance 0 --output thin.ttf$ fontisan instance variable.ttf --wght 700 --dry-run
Dry-run mode: Preview of instance generation
Coordinates:
wght: 700.0
Output would be written to: variable-instance.ttf
Output
format: same as input
Use without --dry-run to actually generate the instance.Optical size information
General
Display optical size range from the OS/2 table for fonts designed for specific point sizes.
Command-line usage
Syntax:
$ fontisan optical-size FONT_FILE [--format FORMAT]Where,
FONT_FILE-
Path to the font file with optical sizing
FORMAT-
Output format:
text(default),json, oryaml
$ fontisan optical-size spec/fixtures/fonts/libertinus/ttf/LibertinusSerifDisplay-Regular.ttfSize range: [18, 72) pt (source: OS/2_usLowerOpticalPointSize)List supported scripts
General
Show all scripts (writing systems) supported by the font, extracted from GSUB and GPOS tables. Useful for understanding language coverage.
Command-line usage
Syntax:
$ fontisan scripts FONT_FILE [--format FORMAT]Where,
FONT_FILE-
Path to the font file (OTF, TTF, or TTC)
FORMAT-
Output format:
text(default),json, oryaml
$ fontisan scripts spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttfScript count: 5
DFLT Default
cyrl Cyrillic
grek Greek
hebr Hebrew
latn LatinList OpenType features
General
Show OpenType layout features (typography features like ligatures, kerning, small capitals) available for specific scripts or all scripts.
Command-line usage
Syntax:
$ fontisan features FONT_FILE [--script SCRIPT] [--format FORMAT]Where,
FONT_FILE-
Path to the font file (OTF, TTF, or TTC)
SCRIPT-
Optional 4-character script tag (e.g.,
latn,cyrl,arab). If not specified, shows features for all scripts FORMAT-
Output format:
text(default),json, oryaml
$ fontisan features spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf --script latnScript: latn
Feature count: 4
cpsp Capital Spacing
kern Kerning
mark Mark Positioning
mkmk Mark to Mark Positioning$ fontisan features spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttfScript: DFLT
Feature count: 4
cpsp Capital Spacing
kern Kerning
mark Mark Positioning
mkmk Mark to Mark Positioning
Script: cyrl
Feature count: 4
cpsp Capital Spacing
kern Kerning
mark Mark Positioning
mkmk Mark to Mark Positioning
Script: grek
Feature count: 4
cpsp Capital Spacing
kern Kerning
mark Mark Positioning
mkmk Mark to Mark Positioning
Script: hebr
Feature count: 2
mark Mark Positioning
mkmk Mark to Mark Positioning
Script: latn
Feature count: 4
cpsp Capital Spacing
kern Kerning
mark Mark Positioning
mkmk Mark to Mark PositioningDump raw table data
General
Extract raw binary data from a specific OpenType table. Useful for detailed analysis or debugging font issues.
Command-line usage
TODO: should support output to file directly with --output FILE.
Syntax:
$ fontisan dump-table FONT_FILE TABLE_TAGWhere,
FONT_FILE-
Path to the font file (OTF, TTF, or TTC)
TABLE_TAG-
Four-character table tag (e.g.,
name,head,GSUB,GPOS)
$ fontisan dump-table spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf name > name_table.bin
$ fontisan dump-table spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf GPOS > gpos_table.bin
$ fontisan dump-table spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf head > head_table.binThe output is binary data written directly to stdout, which can be redirected to a file for further analysis.
Export font structure
General
Export font structure to TTX (FontTools XML), YAML, or JSON formats for analysis, interchange, or version control. Supports selective table export and configurable binary data encoding.
Command-line usage
Syntax:
$ fontisan export FONT_FILE [--output FILE] [--format FORMAT] [--tables TABLES] [--binary-format FORMAT]Where,
FONT_FILE-
Path to the font file (OTF, TTF, or TTC)
--output FILE-
Output file path (default: stdout)
--format FORMAT-
Export format:
yaml(default),json, orttx --tables TABLES-
Specific tables to export (space-separated list)
--binary-format FORMAT-
Binary encoding:
hex(default) orbase64
$ fontisan export spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf --output font.yaml
# Output: font.yaml with complete font structure in YAML$ fontisan export spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf \
--format ttx --tables head hhea maxp name --output font.ttxExports only the specified tables in FontTools TTX XML format for compatibility with fonttools.
$ fontisan export font.ttf --format json --binary-format base64 --output font.jsonUses base64 encoding for binary data instead of hexadecimal, useful for JSON-based workflows.
Version information
General
Display the Fontisan version.
Command-line usage
fontisan versionFont collections
General
Fontisan provides comprehensive tools for managing TrueType Collections (TTC) and OpenType Collections (OTC). You can list fonts in a collection, extract individual fonts, unpack entire collections, and validate collection integrity.
Both TTC and OTC files use the same ttcf tag in their binary format, but
differ in the type of font data they contain:
- TTC (TrueType Collection)
-
Supported since OpenType 1.4. Contains fonts with TrueType outlines (glyf table). Multiple fonts can share identical tables for efficient storage. File extension:
.ttc - OTC (OpenType Collection)
-
Supported since OpenType 1.8. Contains fonts with CFF-format outlines (CFF table). Provides the same storage benefits and glyph-count advantages as TTC but for CFF fonts. File extension:
.otc
The collection format allows:
- Table sharing
-
Identical tables are stored once and referenced by multiple fonts
- Gap mode
-
Overcomes the 65,535 glyph limit per font by distributing glyphs across multiple fonts in a single file
- Efficient storage
-
Significant size reduction, especially for CJK fonts (e.g., Noto CJK OTC is ~10 MB smaller than separate OTF files)
Fontist returns the appropriate collection type based on the font data:
-
Examines font data within collection to determine type (TTC vs OTC)
-
TTC contains fonts with TrueType outlines (glyf table)
-
OTC contains fonts with CFF outlines (CFF table)
-
If ANY font in the collection has CFF outlines, use OpenTypeCollection
-
Only use TrueTypeCollection if ALL fonts have TrueType outlines
List fonts
General
List all fonts in a TrueType Collection (TTC) or OpenType Collection (OTC), with their index, family name, and style.
Command-line usage
$ fontisan ls FONT.{ttc,otc}|
Note
|
In extract_ttc, this was done with extract_ttc --list FONT.ttc.
|
# List all fonts in a TTC with detailed info
$ fontisan ls spec/fixtures/fonts/NotoSerifCJK/NotoSerifCJK.ttc
Font 0: Noto Serif CJK JP
Family: Noto Serif CJK JP
Subfamily: Regular
PostScript: NotoSerifCJKJP-Regular
Font 1: Noto Serif CJK KR
Family: Noto Serif CJK KR
Subfamily: Regular
PostScript: NotoSerifCJKKR-Regular
Font 2: Noto Serif CJK SC
Family: Noto Serif CJK SC
Subfamily: Regular
PostScript: NotoSerifCJKSC-Regular
Font 3: Noto Serif CJK TC
Family: Noto Serif CJK TC
Subfamily: Regular
PostScript: NotoSerifCJKTC-RegularShow collection info
General
Show detailed information about a TrueType Collection (TTC) or OpenType Collection (OTC), including the number of fonts and metadata for each font.
Command-line usage
$ fontisan info FONT.{ttc,otc}|
Note
|
In extract_ttc, this was done with extract_ttc --info FONT.ttc.
|
# Detailed collection analysis
$ fontisan info spec/fixtures/fonts/NotoSerifCJK/NotoSerifCJK.ttc --format yaml
---
collection_type: ttc
font_count: 4
fonts:
- index: 0
family_name: Noto Serif CJK JP
subfamily_name: Regular
postscript_name: NotoSerifCJKJP-Regular
font_format: opentype
- index: 1
family_name: Noto Serif CJK KR
subfamily_name: Regular
postscript_name: NotoSerifCJKKR-Regular
font_format: opentype
- index: 2
family_name: Noto Serif CJK SC
subfamily_name: Regular
postscript_name: NotoSerifCJKSC-Regular
font_format: opentype
- index: 3
family_name: Noto Serif CJK TC
subfamily_name: Regular
postscript_name: NotoSerifCJKTC-Regular
font_format: opentypeUnpack fonts
General
Extract all fonts from a TrueType Collection (TTC) or OpenType Collection (OTC) to a specified output directory.
Command-line usage
$ fontisan unpack FONT.{ttc,otc} OUTPUT_DIR|
Note
|
In extract_ttc, this was done with extract_ttc --unpack FONT.ttc OUTPUT_DIR.
|
# Extract all fonts from collection
$ fontisan unpack family.ttc --output-dir extracted/
Collection unpacked successfully:
Input: family.ttc
Output directory: extracted/
Fonts extracted: 3/3
- font1.ttf (89.2 KB)
- font2.ttf (89.2 KB)
- font3.ttf (67.4 KB)
# Extract specific font with format conversion
$ fontisan unpack family.ttc --output-dir extracted/ --font-index 0 --format woff2Extract specific font
General
Extract a specific font from a TrueType Collection (TTC) or OpenType Collection (OTC) by its index.
Command-line usage
$ fontisan unpack FONT.{ttc,otc} --font-index INDEX OUTPUT.{ttf,otf}|
Note
|
In extract_ttc, this was done with extract_ttc --font-index INDEX FONT.ttc OUTPUT.ttf.
|
# Extract and validate simultaneously
$ fontisan unpack spec/fixtures/fonts/NotoSerifCJK/NotoSerifCJK.ttc extracted_fonts/ --validate
Extracting font 0: Noto Serif CJK JP → extracted_fonts/NotoSerifCJKJP-Regular.ttf
Extracting font 1: Noto Serif CJK KR → extracted_fonts/NotoSerifCJKKR-Regular.ttf
Extracting font 2: Noto Serif CJK SC → extracted_fonts/NotoSerifCJKSC-Regular.ttf
Extracting font 3: Noto Serif CJK TC → extracted_fonts/NotoSerifCJKTC-Regular.ttf
Validation: All fonts extracted successfullyPack fonts into collection
General
Create a new TrueType Collection (TTC) or OpenType Collection (OTC) from multiple font files. Fontisan optimizes the collection by deduplicating shared tables to reduce file size.
Command-line usage
# Pack fonts into TTC with table sharing optimization
$ fontisan pack font1.ttf font2.ttf font3.ttf --output family.ttc --analyze
Collection Analysis:
Total fonts: 3
Shared tables: 12
Potential space savings: 45.2 KB
Table sharing: 68.5%
Collection created successfully:
Output: family.ttc
Format: TTC
Fonts: 3
Size: 245.8 KB
Space saved: 45.2 KB
Sharing: 68.5%$ fontisan pack Regular.otf Bold.otf Italic.otf --output family.otc --format otcValidate collection
General
Validate the structure and checksums of a TrueType Collection (TTC) or OpenType Collection (OTC).
Command-line usage
$ fontisan validate FONT.{ttc,otc}|
Note
|
In extract_ttc, this was done with extract_ttc --validate FONT.ttc.
|
Advanced features
Fontisan provides capabilities:
-
Extract OpenType tables with checksums and offsets
-
Display Unicode mappings and glyph names
-
Analyze variable font axes and instances
-
Show supported scripts and OpenType features
-
Dump raw binary table data
-
Convert between TTF, OTF, WOFF, and WOFF2 formats
-
Create font subsets with specific glyph ranges
-
Validate font structure and integrity
-
Generate SVG representations of glyphs
-
Build new TTC files from individual fonts
-
Optimize collection with table deduplication
-
Pack fonts with shared tables for smaller file sizes
For complete migration guide, see extract_ttc Migration Guide.
Loading modes
General
Fontisan provides a flexible loading modes architecture that enables efficient font parsing for different use cases.
The system supports two distinct modes:
-
:fullmode -
(default) Loads all tables in the font for complete analysis and manipulation
-
:metadatamode -
Loads only metadata tables needed for font identification and metrics (similar to
otfinfofunctionality). This mode is around 5x faster than full parsing and uses significantly less memory.
This architecture is particularly useful for software that only needs basic font information without full parsing overhead, such as font indexing systems or font discovery tools.
This mode was developed to improve performance in font indexing in the Fontist library, where system fonts need to be scanned quickly without loading unnecessary data.
A font file opened in :metadata mode will only have a subset of tables
loaded, and attempts to access non-loaded tables will return nil.
font = Fontisan::FontLoader.load('font.ttf', mode: :metadata)
# Check table availability before accessing
font.table_available?("name") # => true
font.table_available?("GSUB") # => false
# Access allowed tables
font.table("name") # => Works
font.table("head") # => Works
# Restricted tables return nil
font.table("GSUB") # => nil (not loaded in metadata mode)You can also set loading modes via the environment:
# Set defaults via environment
ENV['FONTISAN_MODE'] = 'metadata'
ENV['FONTISAN_LAZY'] = 'false'
# Uses environment settings
font = Fontisan::FontLoader.load('font.ttf')
# Explicit parameters override environment
font = Fontisan::FontLoader.load('font.ttf', mode: :full)The loading mode can be queried at any time.
# Mode stored as font property
font.loading_mode # => :metadata or :full
# Table availability checked before access
font.table_available?(tag) # => boolean
# Access restricted based on mode
font.table(tag) # => Returns table or raises errorMetadata mode
Loads only 6 tables (name, head, hhea, maxp, OS/2, post) instead of 15-20 tables.
font = Fontisan::FontLoader.load('font.ttf', mode: :metadata)
puts font.family_name # => "Arial"
puts font.subfamily_name # => "Regular"
puts font.post_script_name # => "ArialMT"Tables loaded:
- name
-
Font names and metadata
- head
-
Font header with global metrics
- hhea
-
Horizontal header with line spacing
- maxp
-
Maximum profile with glyph count
- OS/2
-
OS/2 and Windows metrics
- post
-
PostScript information
In metadata mode, these convenience methods provide direct access to name table fields:
family_name-
Font family name (nameID 1)
subfamily_name-
Font subfamily/style name (nameID 2)
full_name-
Full font name (nameID 4)
post_script_name-
PostScript name (nameID 6)
preferred_family_name-
Preferred family name (nameID 16, may be nil)
preferred_subfamily_name-
Preferred subfamily name (nameID 17, may be nil)
units_per_em-
Units per em from head table
Full mode
Loads all tables in the font for complete analysis and manipulation.
font = Fontisan::FontLoader.load('font.ttf', mode: :full)
font.table("GSUB") # => Available
font.table("GPOS") # => Available
# Check which mode is active
puts font.loading_mode # => :metadata or :fullTables loaded:
-
All tables in the font
-
Including GSUB, GPOS, cmap, glyf/CFF, etc.
Lazy loading option
Fontisan supports lazy loading of tables in both :metadata and :full modes.
When lazy loading is enabled (optional), tables are only parsed when accessed.
Options:
false-
(default) Eager loading. All tables for the selected mode are parsed upfront.
true-
Lazy loading enabled. Tables are parsed on-demand.
# Metadata mode with lazy loading (default, fastest)
font = Fontisan::FontLoader.load('font.ttf', mode: :metadata, lazy: true)
# Metadata mode with eager loading (loads all metadata tables upfront)
font = Fontisan::FontLoader.load('font.ttf', mode: :metadata, lazy: false)
# Full mode with lazy loading (tables loaded on-demand)
font = Fontisan::FontLoader.load('font.ttf', mode: :full, lazy: true)
# Full mode with eager loading (all tables loaded upfront)
font = Fontisan::FontLoader.load('font.ttf', mode: :full, lazy: false)Outline format conversion
General
Fontisan supports bidirectional conversion between TrueType (TTF) and OpenType/CFF (OTF) outline formats through the Fontist universal outline model (UOM).
The outline converter enables transformation between glyph outline formats:
- TrueType (TTF)
-
Uses quadratic Bézier curves stored in glyf/loca tables
- OpenType/CFF (OTF)
-
Uses cubic Bézier curves stored in CFF table
Conversion uses a format-agnostic universal outline model as an intermediate representation, ensuring high-quality results while preserving glyph metrics and bounding boxes.
Convert between TTF and OTF
Command-line usage
Syntax:
$ fontisan convert INPUT_FONT --to FORMAT --output OUTPUT_FONTWhere,
INPUT_FONT-
Path to the input font file (TTF or OTF)
FORMAT-
Target format:
-
ttf,truetype -
TrueType format
-
otf,opentype,cff -
OpenType/CFF format
-
OUTPUT_FONT-
Path to the output font file
# Convert TrueType font to OpenType/CFF
fontisan convert input.ttf --to otf --output output.otf
# Convert OpenType/CFF font to TrueType
fontisan convert input.otf --to ttf --output output.ttfRuby API usage
Basic conversion with OutlineConverter:
require 'fontisan'
# Load a TrueType font
font = Fontisan::FontLoader.load('input.ttf')
# Convert to OpenType/CFF
converter = Fontisan::Converters::OutlineConverter.new
tables = converter.convert(font, target_format: :otf)
# Write output
Fontisan::FontWriter.write_to_file(
tables,
'output.otf',
sfnt_version: 0x4F54544F # 'OTTO' for OpenType/CFF
)Using FormatConverter:
require 'fontisan'
# Load font
font = Fontisan::FontLoader.load('input.ttf')
# Convert using high-level API
converter = Fontisan::Converters::FormatConverter.new
if converter.supported?(:ttf, :otf)
tables = converter.convert(font, :otf)
# Write output
Fontisan::FontWriter.write_to_file(
tables,
'output.otf',
sfnt_version: 0x4F54544F
)
endTo check supported conversions:
converter = Fontisan::Converters::FormatConverter.new
# Check if conversion is supported
converter.supported?(:ttf, :otf) # => true
converter.supported?(:otf, :ttf) # => true
# Get all supported conversions
converter.all_conversions
# => [{from: :ttf, to: :otf}, {from: :otf, to: :ttf}, ...]
# Get supported targets for a source format
converter.supported_targets(:ttf)
# => [:ttf, :otf, :woff2, :svg]Validation
Font integrity validation is enabled by default for all conversions.
The validator ensures proper OpenType checksum calculation including correct handling of the head table’s checksumAdjustment field per the OpenType specification.
After conversion, validate the output font:
fontisan validate output.otf
fontisan info output.otf
fontisan tables output.otfTechnical details of outline conversion
General
The converter uses a three-stage pipeline:
Source Format Universal Outline Target Format
------------- ------------------ -------------
TrueType (glyf) →→→ Command-based model →→→ OpenType/CFF
Quadratic curves Path representation Cubic curves
On/off-curve pts (format-agnostic) CharStrings
Delta encoding Bounding boxes Type 2 operators
Metrics Compact encodingConversion steps
TTF → OTF conversion:
-
Extract glyphs from glyf/loca tables
-
Convert quadratic Bézier curves to universal outline format
-
Build CFF table with CharStrings INDEX
-
Update maxp table to version 0.5 (CFF format)
-
Update head table (clear indexToLocFormat)
-
Remove glyf/loca tables
-
Preserve all other tables
OTF → TTF conversion:
-
Extract CharStrings from CFF table
-
Convert cubic Bézier curves to universal outline format
-
Convert cubic curves to quadratic using adaptive subdivision
-
Build glyf and loca tables with optimal format selection
-
Update maxp table to version 1.0 (TrueType format)
-
Update head table (set indexToLocFormat)
-
Remove CFF table
-
Preserve all other tables
Curve conversion
Quadratic to cubic (lossless):
Given quadratic curve with control point Q:
P0 (start), Q (control), P2 (end)
Calculate cubic control points:
CP1 = P0 + (2/3) × (Q - P0)
CP2 = P2 + (2/3) × (Q - P2)
Result: Exact mathematical equivalentCubic to quadratic (adaptive):
Given cubic curve with control points:
P0 (start), CP1, CP2, P3 (end)
Use adaptive subdivision algorithm:
1. Estimate error of quadratic approximation
2. If error > threshold (0.5 units):
- Subdivide cubic curve at midpoint
- Recursively convert each half
3. Otherwise: Output quadratic approximation
Result: High-quality approximation with < 0.5 unit deviationCompound glyph support
General
Fontisan fully supports compound (composite) glyphs in both conversion directions:
-
TTF → OTF: Compound glyphs are decomposed into simple outlines with transformations applied
-
OTF → TTF: CFF glyphs are converted to simple TrueType glyphs
Decomposition process
When converting TTF to OTF, compound glyphs undergo the following process:
-
Detected from glyf table flags (numberOfContours = -1)
-
Components recursively resolved (handling nested compound glyphs)
-
Transformation matrices applied to each component (translation, scale, rotation)
-
All components merged into a single simple outline
-
Converted to CFF CharString format
This ensures that all glyphs render identically while maintaining proper metrics and bounding boxes.
Technical implementation
Compound glyphs reference other glyphs by index and apply 2×3 affine transformation matrices:
x' = a*x + c*y + e
y' = b*x + d*y + f
Where:
- a, d: Scale factors for x and y axes
- b, c: Rotation/skew components
- e, f: Translation offsets (x, y position)The resolver handles:
-
Simple glyphs referenced by compounds
-
Nested compound glyphs (compounds referencing other compounds)
-
Circular reference detection with maximum recursion depth (32 levels)
-
Complex transformation matrices (uniform scale, x/y scale, full 2×2 matrix)
Subroutine optimization
General
When converting TrueType (TTF) to OpenType/CFF (OTF), Fontisan can automatically generate CFF subroutines to reduce file size.
Subroutines extract repeated CharString patterns across glyphs and store them once, significantly reducing CFF table size while maintaining identical glyph rendering.
Key features:
-
Pattern analysis: Analyzes byte sequences across all CharStrings to identify repeating patterns
-
Frequency-based selection: Prioritizes patterns that provide maximum space savings
-
Configurable thresholds: Customizable minimum pattern length and maximum subroutine count
-
Ordering optimization: Automatically orders subroutines by frequency for better compression
Typical space savings: 30-50% reduction in CFF table size for fonts with similar glyph shapes.
|
Note
|
Current implementation calculates accurate optimization metrics but does not modify the output CFF table. Full CFF serialization with subroutines will be available in the next development phase. |
Edge cases
The optimizer correctly handles:
-
Multi-byte numbers: Number encodings from 1-5 bytes (CFF Type 2 format)
-
Two-byte operators: Operators with 0x0c prefix (e.g.,
divinlib/fontisan/tables/cff/charstring.rb,flexinlib/fontisan/tables/cff/charstring.rb) -
Overlapping patterns: Multiple patterns at same byte positions
-
Stack-neutral validation: Patterns verified to maintain consistent stack state
Technical details
The subroutine optimizer uses a four-stage pipeline:
CharStrings → Pattern Analysis → Selection → Ordering → Metadata
(Input) (Find repeats) (Optimize) (Frequency) (Output)Pattern analysis:
-
Extracts byte sequences from all CharStrings
-
Identifies repeating patterns across glyphs
-
Filters by minimum pattern length (default: 10 bytes)
-
Builds pattern frequency map
Selection algorithm:
-
Calculates savings for each pattern:
frequency × (length - overhead) -
Ranks patterns by total savings (descending)
-
Selects top patterns up to
max_subroutineslimit -
Ensures selected patterns don’t exceed CFF limits
Ordering optimization:
-
Sorts subroutines by usage frequency (most used first)
-
Optimizes CFF bias calculation for better compression
-
Ensures subroutine indices fit within CFF constraints
CFF bias calculation:
Subroutine count CFF Bias
----------------- ---------
0-1239 107
1240-33899 1131
33900-65535 32768The bias value determines how subroutine indices are encoded in CharStrings, affecting the final size.
Troubleshooting
If you encounter CharString parsing errors after optimization:
-
Verify bias calculation: Ensure bias matches CFF specification (107, 1131, or 32768)
-
Check operator boundaries: Patterns should only be extracted at valid boundaries
-
Ensure no overlaps: Multiple patterns should not occupy same byte positions
-
Enable verbose mode: Use
--verboseflag for detailed diagnostics
# Convert with verbose output
$ fontisan convert input.ttf --to otf --output output.otf --optimize --verbose
# Validate the output
$ fontisan validate output.otf
# Check CharString structure
$ fontisan info output.otfIf validation fails, try:
# Disable optimization
$ fontisan convert input.ttf --to otf --output output.otf
# Use stack-aware mode for safer optimization
$ fontisan convert input.ttf --to otf --output output.otf --optimize --stack-aware# Adjust pattern matching sensitivity
$ fontisan convert input.ttf --to otf --output output.otf \
--optimize \
--min-pattern-length 15 \
--max-subroutines 10000 \
--verbose
# Disable ordering optimization
$ fontisan convert input.ttf --to otf --output output.otf \
--optimize \
--no-optimize-orderingWhere,
--optimize-
Enable subroutine optimization (default: false)
--min-pattern-length N-
Minimum pattern length in bytes (default: 10)
--max-subroutines N-
Maximum number of subroutines to generate (default: 65,535)
--optimize-ordering-
Optimize subroutine ordering by frequency (default: true)
--verbose-
Show detailed optimization statistics
Stack-aware optimization
General
Stack-aware optimization is an advanced mode that ensures all extracted patterns are stack-neutral, guaranteeing 100% safety and reliability.
Unlike normal byte-level pattern matching, stack-aware mode simulates CharString execution to track operand stack depth, only extracting patterns that maintain consistent stack state.
Key benefits:
-
100% Reliability: All patterns are validated to be stack-neutral
-
No Stack Errors: Eliminates stack underflow/overflow issues
-
Faster Processing: 6-12x faster than normal optimization due to early filtering
-
Smaller Pattern Set: Significantly fewer candidates reduce memory usage
Trade-offs:
-
Lower Compression: ~6% reduction vs ~11% with normal mode
-
Fewer Patterns: Filters out 90%+ of raw patterns for safety
-
Stack Validation Overhead: Adds stack tracking during analysis
Command-line usage
# Convert with stack-aware optimization
$ fontisan convert input.ttf --to otf --output output.otf \
--optimize \
--stack-aware \
--verbose
Converting input.ttf to otf...
Analyzing CharString patterns (4515 glyphs)...
Found 8566 potential patterns
Selecting optimal patterns...
Selected 832 patterns for subroutinization
Building subroutines...
Generated 832 subroutines
Rewriting CharStrings with subroutine calls...
Rewrote 4515 CharStrings
Subroutine Optimization Results:
Patterns found: 8566
Patterns selected: 832
Subroutines generated: 832
Estimated bytes saved: 46,280
CFF bias: 0
Conversion complete!
Input: input.ttf (806.3 KB)
Output: output.otf (660.7 KB)# Use the comparison script
$ ruby scripts/compare_stack_aware.rb input.ttf
File Size Reduction:
Normal: 81.49 KB (11.27%)
Stack-Aware: 43.17 KB (6.13%)
Processing Times:
Normal: 18.38 s
Stack-Aware: 1.54 s (12x faster)
Stack-Aware Efficiency: 52.97% of normal optimizationWhere,
--stack-aware-
Enable stack-aware pattern detection (default: false)
Using the Ruby API
require 'fontisan'
# Load TrueType font
font = Fontisan::FontLoader.load('input.ttf')
# Convert with stack-aware optimization
converter = Fontisan::Converters::OutlineConverter.new
tables = converter.convert(font, {
target_format: :otf,
optimize_subroutines: true,
stack_aware: true # Enable safe mode
})
# Access optimization results
optimization = tables.instance_variable_get(:@subroutine_optimization)
puts "Patterns found: #{optimization[:pattern_count]}"
puts "Stack-neutral patterns: #{optimization[:selected_count]}"
puts "Processing time: #{optimization[:processing_time]}s"
# Write output
Fontisan::FontWriter.write_to_file(
tables,
'output.otf',
sfnt_version: 0x4F54544F
)Technical details
Stack-aware mode uses a three-stage validation process:
CharString Bytes → Stack Tracking → Pattern Validation → Safe Patterns
(Input) (Simulate) (Filter) (Output)Stack tracking:
-
Simulates CharString execution without full interpretation
-
Records stack depth at each byte position
-
Handles 40+ Type 2 CharString operators with correct stack effects
Pattern validation:
-
Checks if pattern start and end have same stack depth
-
Ensures no stack underflow during pattern execution
-
Verifies consistent results regardless of initial stack state
Stack-neutral pattern criteria. Pattern is stack-neutral if:
-
depth_at(pattern_start) == depth_at(pattern_end)
-
No negative depth during pattern execution
-
Pattern produces same result for any valid initial stack
Example Stack-Neutral Pattern10 20 rmoveto # Pushes 2 operands, consumes 2 → neutralExample Non-Neutral Pattern10 20 add # Pushes 2, consumes 2, produces 1 → NOT neutral
When to use stack-aware mode
Recommended for:
-
Production font conversion where reliability is critical
-
Fonts that will undergo further processing
-
Web fonts where correctness matters more than minimal size
-
Situations where testing/validation is limited
Normal mode acceptable for:
-
Development/testing environments
-
When full validation will be performed post-conversion
-
Maximum compression is priority over guaranteed safety
Using the Ruby API
require 'fontisan'
# Load TrueType font
font = Fontisan::FontLoader.load('input.ttf')
# Convert with optimization
converter = Fontisan::Converters::OutlineConverter.new
tables = converter.convert(font, {
target_format: :otf,
optimize_subroutines: true
})
# Access optimization results
optimization = tables.instance_variable_get(:@subroutine_optimization)
puts "Patterns found: #{optimization[:pattern_count]}"
puts "Selected: #{optimization[:selected_count]}"
puts "Savings: #{optimization[:savings]} bytes"
# Write output
Fontisan::FontWriter.write_to_file(
tables,
'output.otf',
sfnt_version: 0x4F54544F
)require 'fontisan'
font = Fontisan::FontLoader.load('input.ttf')
converter = Fontisan::Converters::OutlineConverter.new
# Fine-tune optimization
tables = converter.convert(font, {
target_format: :otf,
optimize_subroutines: true,
min_pattern_length: 15,
max_subroutines: 5000,
optimize_ordering: true,
verbose: true
})
# Analyze results
optimization = tables.instance_variable_get(:@subroutine_optimization)
if optimization[:selected_count] > 0
efficiency = optimization[:savings].to_f / optimization[:selected_count]
puts "Average savings per subroutine: #{efficiency.round(2)} bytes"
endRound-Trip validation
General
Fontisan ensures high-fidelity font conversion through comprehensive round-trip validation.
When converting between TrueType (TTF) and OpenType/CFF (OTF) formats, the validation system verifies that glyph geometry is preserved accurately.
Key validation features:
-
Command-Level Precision: Validates individual drawing commands (move, line, curve)
-
Coordinate Tolerance: Accepts ±2 pixels tolerance for rounding during conversion
-
Format-Aware Comparison: Handles differences between TrueType quadratic and CFF cubic curves
-
Closepath Handling: Smart detection of geometrically closed vs open contours
Technical details
Round-trip validation works by:
Original TTF → Convert to CFF → Extract CFF → Compare Geometry
(Input) (Encode) (Decode) (Validate)Validation process:
-
Extract glyph outlines from original TTF
-
Convert to CFF format with CharString encoding
-
Parse CFF CharStrings back to universal outlines
-
Compare geometry with coordinate tolerance (±2 pixels)
Format differences handled:
-
Closepath: CFF has implicit closepath, TTF has explicit
-
Curve types: TrueType quadratic (
:quad_to) vs CFF cubic (:curve_to) -
Coordinate rounding: Different number encoding causes minor differences
Validation criteria: Geometry Match: . Same bounding box (±2 pixel tolerance) . Same number of path commands (excluding closepath) . Same endpoint coordinates for curves (±2 pixels) . Quadratic→cubic conversion accepted
Universal outline model
General
The Fontisan Universal Outline Model (UOM) is based on a self-stable algorithm for converting soft glyph contours to outline format used in all tools of Fontisan. This ability allows easy modeling of import glyphs from one font format TrueType (TTF, OTF binaries), converting glyph elements into any font format, TrueType for example.
Locker
Locker is an object-oriented model for storing imported outlines and glyphs. Storage is based on monotonic spirals computed based on 2D points and curves. Invisible converting from TrueType, CFF Opentype and ColorGlyph formats.
Translator
Translation is an object-oriented model for converting from and to PostScript custom CFF charset. New encoding/decoding includes PostScript Type 2/3/composite Loron.
ColorGlyph
Support for layered import CFF color glyphs rasterizing on demand, with composite font support, a multi-layer color font represented by many CFF fonts stacked on top of each other. ColorGlyph support contains color glyphs, advanced color fonts glyphs and raster images (PNG or JPG) combined with TrueType outlines.
Universal fonts
Fontisan can:
-
Import TrueType contours into Universal Outline Model (UOM)
-
Operate UOM outlines including transformations, serialization (save)
-
Select and convert all UOM contours to TTF/OTF
-
Cleaning
-
Improve
-
Render
-
Building works for TrueType
-
Convert colors (cvt to TTF/OTF or TTF to cvt)
-
Saving and sharing font structures
-
Working with advanced color fonts
Universal glyphs
Fontisan can:
-
Use Universal Outline Model (UOM) for TrueType contours and CFF color glyphs
-
Repository for investor-defined fonts
-
Custom Unicode assignments, rewriting Unicode configurations
-
Saving and import outlines, including TrueType and OTF/CFF
-
Rendering for advanced font types
-
Universal layer stacking for advanced color glyph combinations
Universal color layers
(Converted TTF, OTF files)
Fontisan can:
-
Import embedded TTF/OTF color layers
-
Assembler from individual TTF/OTF slices
-
Advanced managing layer maps in TTF color (CFF) fonts
-
Advanced color layer blending style management
-
Managing Gray/Overprint/Color-Full image comps and layer conversion
-
Strategy management for smart vector combos from raster
-
Importing and generation PNG block ruler layers
Copyright and license
Copyright Ribose.
Fontisan is licensed under the Ribose 3-Clause BSD License. See the LICENSE file for details.