ExtractTTC is a pure Ruby gem that extracts individual TrueType font files
(.ttf) from TrueType Collection files (.ttc).
ExtractTTC uses a model-driven architecture where domain objects
inherit from BinData::Record and handle their own binary I/O through
declarative structure definitions.
┌──────────────────────────────────────────────────────────────┐
│ Client Code │
│ ExtractTtc.extract(path) │
└────────────────────────┬─────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Simple Extraction API │
│ 1. Read TTC (TrueTypeCollection.read via BinData) │
│ 2. Extract fonts (ttc.extract_fonts) │
│ 3. Write TTFs (font.to_file via BinData) │
└──┬────────────┬──────────────┬───────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌────────────┐
│TrueType │ │TrueType │ │Utilities │
│Collection │ │Font │ │ │
│(BinData:: │ │(BinData:: │ │ │
│ Record) │ │ Record) │ │ │
└─────────────┘ └─────────────┘ └────────────┘TrueTypeCollection (Model)
├── tag (Value: String) = "ttcf"
├── major_version (Value: Integer)
├── minor_version (Value: Integer)
├── num_fonts (Value: Integer)
└── font_offsets (Array<Integer>)
│
└──> Maps to TrueTypeFont instances
│
├── TrueTypeFont (Model)
│ ├── offset_table (Model)
│ │ ├── scaler_type (Value: Integer)
│ │ ├── num_tables (Value: Integer)
│ │ └── search_range (Value: Integer)
│ └── table_directory (Array<TableEntry>)
│ └── TableEntry (Model)
│ ├── tag (Value: String)
│ ├── checksum (Value: Integer)
│ ├── offset (Value: Integer)
│ └── length (Value: Integer)
└── (Recursive pattern for each font)Add this line to your application’s Gemfile:
gem "extract_ttc"And then execute:
bundle installOr install it yourself as:
gem install extract_ttcExtractTTC provides a command-line interface for extracting fonts from TTC files without writing code. The CLI is built using Thor and follows standard Unix conventions for options and arguments.
The command-line interface is particularly useful for:
-
Quick one-off extractions
-
Shell scripts and automation
-
CI/CD pipelines
-
Interactive exploration of TTC files
Extract all fonts from a TTC file to the current directory.
Syntax:
extract_ttc extract FILE (1)-
The
extractcommand with the input TTC file path.
Where,
FILE-
The path to the input TrueType Collection file (
.ttc).
extract_ttc extract fonts/Helvetica.ttc
# Output:
# Successfully extracted 6 font(s):
# - Helvetica_00.ttf
# - Helvetica_01.ttf
# - Helvetica_02.ttf
# - Helvetica_03.ttf
# - Helvetica_04.ttf
# - Helvetica_05.ttfThis extracts all fonts from the TTC file and creates TTF files in the current directory with sequential numbering.
Specify an output directory for the extracted font files.
The output directory is created automatically if it does not exist.
Syntax:
extract_ttc extract FILE -o OUTPUT_DIR (1)-
The
extractcommand with custom output directory.
Where,
FILE-
The path to the input TrueType Collection file (
.ttc). OUTPUT_DIR-
The directory where extracted TTF files will be created.
extract_ttc extract fonts/Helvetica.ttc -o output/fonts
# Output:
# Successfully extracted 6 font(s):
# - output/fonts/Helvetica_00.ttf
# - output/fonts/Helvetica_01.ttf
# - output/fonts/Helvetica_02.ttf
# - output/fonts/Helvetica_03.ttf
# - output/fonts/Helvetica_04.ttf
# - output/fonts/Helvetica_05.ttfEnable detailed output showing the extraction process.
Syntax:
extract_ttc extract FILE -v (1)
extract_ttc extract FILE --verbose (2)-
Short form of the verbose option.
-
Long form of the verbose option.
Where,
FILE-
The path to the input TrueType Collection file (
.ttc).
extract_ttc extract fonts/Helvetica.ttc -v
# Output:
# Extracting fonts from fonts/Helvetica.ttc...
# Successfully extracted 6 font(s):
# - Helvetica_00.ttf
# - Helvetica_01.ttf
# - Helvetica_02.ttf
# - Helvetica_03.ttf
# - Helvetica_04.ttf
# - Helvetica_05.ttfVerbose mode provides additional information about the extraction process.
View help for the extract command.
Syntax:
extract_ttc help extract (1)
extract_ttc extract --help (2)-
Display help using the help command.
-
Display help using the --help flag.
extract_ttc help extract
# Output:
# Usage:
# extract_ttc extract FILE
#
# Options:
# -o, [--output-dir=OUTPUT_DIR] # Output directory for TTF files
# -v, [--verbose] # Enable verbose output
#
# Extract TTF files from a TTC fileThe help output shows all available options and their descriptions.
The simple extraction API provides a single method to extract all fonts from a TrueType Collection file with minimal configuration. This is the recommended approach for most use cases, as it handles all the complexity of reading TTC structures, extracting individual fonts, and === List fonts in collection
List all fonts contained in a TTC file without extracting them.
Syntax:
extract_ttc ls FILE (1)-
The
lscommand to list fonts in the TTC file.
Where,
FILE-
The path to the input TrueType Collection file (
.ttc).
extract_ttc ls fonts/Helvetica.ttc
# Output:
# 📦 TTC File: fonts/Helvetica.ttc
# Fonts: 6
#
# 0. 📄 Helvetica_00.ttf
# 1. 📄 Helvetica_01.ttf
# 2. 📄 Helvetica_02.ttf
# 3. 📄 Helvetica_03.ttf
# 4. 📄 Helvetica_04.ttf
# 5. 📄 Helvetica_05.ttfThe ls command provides a quick way to see what fonts are available
in a TTC collection before extracting them.
Display detailed metadata about a TTC file, including header information and font offsets.
Syntax:
extract_ttc info FILE (1)
extract_ttc info FILE -v (2)-
Show basic TTC information.
-
Show detailed information including table data for each font.
Where,
FILE-
The path to the input TrueType Collection file (
.ttc).
extract_ttc info fonts/Helvetica.ttc
# Output:
# ═══ TTC File Information ═══
#
# 📦 File: fonts/Helvetica.ttc
# 💾 Size: 2.24 MB
#
# ═══ Header ═══
# 🏷️ Tag: ttcf
# 📌 Version: 2.0 (0x20000)
# 🔢 Number of fonts: 6
#
# ═══ Font Offsets ═══
# 0. Offset: 48 (0x30)
# 1. Offset: 380 (0x17C)
# 2. Offset: 712 (0x2C8)
# 3. Offset: 1044 (0x414)
# 4. Offset: 1376 (0x560)
# 5. Offset: 1676 (0x68C)This displays the TTC file metadata, including the file size, version, number of fonts, and their offsets in the file.
extract_ttc info fonts/Helvetica.ttc -v
# Output includes above, plus:
# ═══ Font Details ═══
#
# 📝 Font 0:
# SFNT version: 0x10000
# Number of tables: 20
# Tables:
# • OS/2 checksum: 0x1047244E offset: 2884 length: 96
# • cmap checksum: 0xEF626C81 offset: 2205772 length: 8040
# • glyf checksum: 0x2B563595 offset: 1393876 length: 267440
# • head checksum: 0xB8ED709D offset: 2416 length: 54
# ... (and more tables)The verbose mode (-v) shows detailed information about each font,
including the SFNT version, number of tables, and a complete list of
all tables with their checksums, offsets, and lengths.
writing TTF files.
The simplest way to extract all fonts from a TTC file is to provide just the input file path.
Syntax:
ExtractTtc.extract(path) → Array<String> (1)-
The
extractmethod with input file path only.
Where,
path-
The path to the input TrueType Collection file (
.ttc).
Returns an array of output file paths for the extracted TTF files.
require "extract_ttc"
# Extract all fonts from TTC file to current directory
output_files = ExtractTtc.extract("fonts/Helvetica.ttc")
# => ["Helvetica_00.ttf", "Helvetica_01.ttf", "Helvetica_02.ttf"]
puts "Extracted #{output_files.size} fonts"
output_files.each { |file| puts " - #{file}" }This extracts all fonts and creates TTF files in the current directory
with names like Helvetica_00.ttf, Helvetica_01.ttf, etc.
For organized file management, the extraction API supports specifying a custom output directory. This is useful when you want to organize extracted fonts in a specific location or maintain a clean directory structure.
The output directory will be created automatically if it does not exist.
Specify a custom output directory using the output_dir parameter.
Syntax:
ExtractTtc.extract(path, output_dir: directory) → Array<String> (1)-
The
extractmethod with custom output directory.
Where,
path-
The path to the input TrueType Collection file (
.ttc). directory-
The path to the output directory for extracted TTF files.
Returns an array of output file paths for the extracted TTF files.
require "extract_ttc"
# Extract to specific directory
output_files = ExtractTtc.extract(
"fonts/MyFont.ttc",
output_dir: "output/fonts"
)
# => ["output/fonts/MyFont_00.ttf", "output/fonts/MyFont_01.ttf"]
# Directory is created automatically if it doesn't exist
output_files.each { |file| puts "Created: #{file}" }The output directory output/fonts is created automatically if it does
not exist, and all extracted fonts are placed there.
For flexibility in path management, the API returns relative filenames when extracting to the current directory.
This allows custom path composition with any directory structure.
Syntax:
filenames = ExtractTtc.extract(path) (1)
full_paths = filenames.map { |filename| File.join(dir, filename) } (2)-
Extract to current directory, returns relative filenames.
-
Compose full paths with custom directory.
Where,
path-
The path to the input TrueType Collection file (
.ttc). dir-
The custom directory to prepend to filenames.
require "extract_ttc"
require "tmpdir"
def extract_ttfs(ttc_path, tmp_dir)
# Extract returns relative filenames
filenames = ExtractTtc.extract(ttc_path)
# => ["Font_00.ttf", "Font_01.ttf", ...]
# Compose full paths with your directory
filenames.map do |filename|
File.join(tmp_dir, filename)
end
# => ["/tmp/xyz/Font_00.ttf", "/tmp/xyz/Font_01.ttf", ...]
end
# Use the method
Dir.mktmpdir do |tmp_dir|
font_paths = extract_ttfs("fonts/MyFont.ttc", tmp_dir)
# Process fonts at their full paths
font_paths.each do |path|
puts "Font available at: #{path}"
# Your processing logic here
end
endThis pattern is useful when you need to extract fonts to the current directory but want to manipulate them with absolute paths, or when integrating with code that expects to control the output directory.
The relative filenames make it easy to move or organize the extracted files after extraction.
ExtractTTC follows object-oriented design principles where domain
objects encapsulate both data and behavior. The core domain objects are
TrueTypeCollection and TrueTypeFont, which inherit from
BinData::Record to gain automatic binary I/O capabilities while
maintaining clean domain APIs.
This approach ensures that objects know their own structure and can handle their own persistence, leading to maintainable and self-documenting code.
The TrueTypeCollection class represents a TTC file and provides
methods to read the collection and extract individual fonts.
require "extract_ttc"
# Open TTC file and read with BinData
File.open("fonts/Helvetica.ttc", "rb") do |io|
# Parse TTC structure automatically via BinData
ttc = ExtractTtc::TrueTypeCollection.read(io)
# Access TTC metadata
puts "TTC Tag: #{ttc.tag}"
puts "Version: #{ttc.major_version}.#{ttc.minor_version}"
puts "Number of fonts: #{ttc.num_fonts}"
# Extract fonts as TrueTypeFont objects
fonts = ttc.extract_fonts(io)
puts "Extracted #{fonts.size} font objects"
endThe TrueTypeCollection.read method automatically parses the binary
TTC file structure using BinData, and the extract_fonts method
returns an array of TrueTypeFont objects.
The TrueTypeFont class represents an individual TTF file and provides
methods to write the font to a file.
require "extract_ttc"
File.open("fonts/Helvetica.ttc", "rb") do |io|
ttc = ExtractTtc::TrueTypeCollection.read(io)
fonts = ttc.extract_fonts(io)
# Write each font to file
fonts.each_with_index do |font, index|
output_path = "output/font_#{index}.ttf"
# Access font metadata
puts "Font #{index}:"
puts " Scaler type: 0x#{font.scaler_type.to_s(16)}"
puts " Number of tables: #{font.num_tables}"
# Write font to file using BinData
font.to_file(output_path)
puts " Wrote: #{output_path}"
end
endEach TrueTypeFont object encapsulates the complete font data and can
write itself to a file in the correct TTF binary format.
ExtractTTC defines specific error types for different failure
scenarios, allowing applications to handle errors appropriately. All
errors inherit from ExtractTtc::Error, making it easy to catch all
gem-related errors if desired.
The error types provide clear, actionable messages that help diagnose issues with input files, invalid formats, or output operations.
The gem defines the following error hierarchy:
ExtractTtc::Error (base class)
├── ExtractTtc::ReadFileError
├── ExtractTtc::InvalidFileError
└── ExtractTtc::WriteFileErrorExtractTtc::Error-
Base error class for all gem-related errors.
ExtractTtc::ReadFileError-
Raised when the input file cannot be read (e.g., file not found, permission denied).
ExtractTtc::InvalidFileError-
Raised when the file is not a valid TTC file (e.g., wrong format, corrupted data).
ExtractTtc::WriteFileError-
Raised when output files cannot be written (e.g., permission denied, disk full).
Applications can catch specific error types to handle different failure scenarios appropriately.
require "extract_ttc"
begin
output_files = ExtractTtc.extract("fonts/example.ttc")
puts "Successfully extracted #{output_files.size} fonts"
rescue ExtractTtc::ReadFileError => e
puts "File not found or cannot be read: #{e.message}"
exit 1
rescue ExtractTtc::InvalidFileError => e
puts "Not a valid TTC file: #{e.message}"
exit 2
rescue ExtractTtc::WriteFileError => e
puts "Cannot write output files: #{e.message}"
exit 3
rescue ExtractTtc::Error => e
puts "Extraction failed: #{e.message}"
exit 4
endThis handles each error type separately, allowing appropriate recovery or reporting for different failure scenarios.
To catch all gem-related errors, rescue ExtractTtc::Error.
require "extract_ttc"
begin
ExtractTtc.extract("fonts/example.ttc")
rescue ExtractTtc::Error => e
# Handle any gem-related error
puts "Extraction failed: #{e.message}"
puts "Error type: #{e.class.name}"
endThis catches all errors raised by the gem, including any future error types that may be added.
ExtractTTC includes comprehensive RBS type signatures in the sig/
directory, providing static type checking capabilities for applications
using the gem. The type signatures cover all public APIs and domain
objects.
Type signatures help catch type-related errors during development and provide better IDE support for autocomplete and inline documentation.
The gem can be type-checked using Steep, a static type checker for Ruby.
# Install Steep
gem install steep
# Type check the gem
steep check
# Type check your application using the gem
steep check --steep-file SteepfileSteep will verify that all method calls and variable assignments match
the type signatures defined in the sig/ directory.
Type signatures are organized to mirror the source code structure.
sig/
├── extract_ttc.rbs # Main API signatures
└── extract_ttc/
├── configuration.rbs
├── true_type_collection.rbs
├── true_type_font.rbs
├── models/
│ ├── extraction_result.rbs
│ └── validation_result.rbs
└── utilities/
├── checksum_calculator.rbs
└── output_path_generator.rbsExtractTTC achieves high performance through BinData’s efficient binary parsing implementation. The declarative structure definitions compile to optimized read/write operations, providing performance equivalent to hand-written binary I/O code.
The development environment uses standard Ruby tooling with RSpec for testing and RuboCop for code style enforcement.
Clone the repository and set up the development environment.
git clone https://github.com/fontist/extract_ttc
cd extract_ttc
bin/setupThis will install dependencies and prepare the development environment.
Run the complete test suite with RSpec.
# Run all tests
bundle exec rspec
# Run with verbose output
bundle exec rspec --format documentation
# Run specific test file
bundle exec rspec spec/extract_ttc/true_type_collection_spec.rb
# Run tests for a component
bundle exec rspec spec/extract_ttc/utilities/Tests are organized to mirror the source code structure, with one RSpec file per class.
spec/
├── extract_ttc_spec.rb # Main API tests
├── spec_helper.rb
├── extract_ttc/
│ ├── configuration_spec.rb
│ ├── true_type_collection_spec.rb
│ ├── true_type_font_spec.rb
│ ├── models/
│ │ ├── extraction_result_spec.rb
│ │ └── validation_result_spec.rb
│ └── utilities/
│ ├── checksum_calculator_spec.rb
│ └── output_path_generator_spec.rb
└── fixtures/
└── Helvetica.ttc # Test fixture filesRun an interactive console for experimentation.
bin/consoleThis opens an IRB session with the gem loaded.
Follow these coding guidelines:
-
Follow object-oriented design principles
-
Use declarative structures with BinData for binary formats
-
Maintain clear separation of concerns
-
One class per file
-
Comprehensive tests for all classes
-
Clear documentation with examples
To release a new version:
-
Update the version number in
version.rb -
Run
bundle exec rake release
This will create a git tag for the version, push git commits and tags,
and push the .gem file to rubygems.org.
First, thank you for contributing! We love pull requests from everyone. By participating in this project, you hereby grant Ribose Inc. the right to grant or transfer an unlimited number of non-exclusive licenses or sub-licenses to third parties, under the copyright covering the contribution to use the contribution by all means.
Here are a few technical guidelines to follow:
-
Open an issue to discuss a new feature.
-
Write tests to support your new feature.
-
Make sure the entire test suite passes locally and on CI.
-
Open a Pull Request.
-
Squash your commits after receiving feedback.
-
Party!
This gem is distributed with a BSD 3-Clause license.
The original C implementation was based on stripttc.c from the
FontForge project:
https://github.com/fontforge/fontforge/blob/master/contrib/fonttools/stripttc.c
This gem is developed, maintained and funded by Ribose Inc.