Class: Fontisan::TrueTypeCollection

Inherits:
BinData::Record
  • Object
show all
Defined in:
lib/fontisan/true_type_collection.rb

Overview

TrueType Collection domain object using BinData

Represents a complete TrueType Collection file using BinData’s declarative DSL for binary structure definition. The structure definition IS the documentation, and BinData handles all low-level reading/writing.

Examples:

Reading and extracting fonts

File.open("Helvetica.ttc", "rb") do |io|
  ttc = TrueTypeCollection.read(io)
  puts ttc.num_fonts  # => 6
  fonts = ttc.extract_fonts(io)  # => [TrueTypeFont, TrueTypeFont, ...]
end

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.from_file(path) ⇒ TrueTypeCollection

Read TrueType Collection from a file

Parameters:

  • path (String)

    Path to the TTC file

Returns:

Raises:

  • (ArgumentError)

    if path is nil or empty

  • (Errno::ENOENT)

    if file does not exist

  • (RuntimeError)

    if file format is invalid



35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/fontisan/true_type_collection.rb', line 35

def self.from_file(path)
  if path.nil? || path.to_s.empty?
    raise ArgumentError,
          "path cannot be nil or empty"
  end
  raise Errno::ENOENT, "File not found: #{path}" unless File.exist?(path)

  File.open(path, "rb") { |io| read(io) }
rescue BinData::ValidityError => e
  raise "Invalid TTC file: #{e.message}"
rescue EOFError => e
  raise "Invalid TTC file: unexpected end of file - #{e.message}"
end

Instance Method Details

#collection_info(io, path) ⇒ CollectionInfo

Get comprehensive collection metadata

Returns a CollectionInfo model with header information, offsets, and table sharing statistics. This is the API method used by the ‘info` command for collections.

Examples:

Get collection info

File.open("fonts.ttc", "rb") do |io|
  ttc = TrueTypeCollection.read(io)
  info = ttc.collection_info(io, "fonts.ttc")
  puts "Version: #{info.version_string}"
end

Parameters:

  • io (IO)

    Open file handle to read fonts from

  • path (String)

    Collection file path (for file size)

Returns:

  • (CollectionInfo)

    Collection metadata



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/fontisan/true_type_collection.rb', line 178

def collection_info(io, path)
  require_relative "models/collection_info"
  require_relative "models/table_sharing_info"

  # Calculate table sharing statistics
  table_sharing = calculate_table_sharing(io)

  # Get file size
  file_size = path ? File.size(path) : 0

  Models::CollectionInfo.new(
    collection_path: path,
    collection_format: "TTC",
    ttc_tag: tag,
    major_version: major_version,
    minor_version: minor_version,
    num_fonts: num_fonts,
    font_offsets: font_offsets.to_a,
    file_size_bytes: file_size,
    table_sharing: table_sharing,
  )
end

#extract_fonts(io) ⇒ Array<TrueTypeFont>

Extract fonts as TrueTypeFont objects

Reads each font from the TTC file and returns them as TrueTypeFont objects.

Parameters:

  • io (IO)

    Open file handle to read fonts from

Returns:



55
56
57
58
59
60
61
# File 'lib/fontisan/true_type_collection.rb', line 55

def extract_fonts(io)
  require_relative "true_type_font"

  font_offsets.map do |offset|
    TrueTypeFont.from_ttc(io, offset)
  end
end

#font(index, io, mode: LoadingModes::FULL) ⇒ TrueTypeFont?

Get a single font from the collection (Fontisan extension)

Parameters:

  • index (Integer)

    Index of the font (0-based)

  • io (IO)

    Open file handle

  • mode (Symbol) (defaults to: LoadingModes::FULL)

    Loading mode (:metadata or :full, default: :full)

Returns:

  • (TrueTypeFont, nil)

    Font object or nil if index out of range



69
70
71
72
73
74
# File 'lib/fontisan/true_type_collection.rb', line 69

def font(index, io, mode: LoadingModes::FULL)
  return nil if index >= num_fonts

  require_relative "true_type_font"
  TrueTypeFont.from_ttc(io, font_offsets[index], mode: mode)
end

#font_countInteger

Get font count (Fontisan extension)

Returns:

  • (Integer)

    Number of fonts in collection



79
80
81
# File 'lib/fontisan/true_type_collection.rb', line 79

def font_count
  num_fonts
end

#list_fonts(io) ⇒ CollectionListInfo

List all fonts in the collection with basic metadata

Returns a CollectionListInfo model containing summaries of all fonts. This is the API method used by the ‘ls` command for collections.

Examples:

List fonts in collection

File.open("fonts.ttc", "rb") do |io|
  ttc = TrueTypeCollection.read(io)
  list = ttc.list_fonts(io)
  list.fonts.each { |f| puts "#{f.index}: #{f.family_name}" }
end

Parameters:

  • io (IO)

    Open file handle to read fonts from

Returns:

  • (CollectionListInfo)

    List of fonts with metadata



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/fontisan/true_type_collection.rb', line 113

def list_fonts(io)
  require_relative "models/collection_list_info"
  require_relative "models/collection_font_summary"
  require_relative "true_type_font"
  require_relative "tables/name"

  fonts = font_offsets.map.with_index do |offset, index|
    font = TrueTypeFont.from_ttc(io, offset)

    # Extract basic font info
    name_table = font.table("name")
    post_table = font.table("post")

    family_name = name_table&.english_name(Tables::Name::FAMILY) || "Unknown"
    subfamily_name = name_table&.english_name(Tables::Name::SUBFAMILY) || "Regular"
    postscript_name = name_table&.english_name(Tables::Name::POSTSCRIPT_NAME) || "Unknown"

    # Determine font format
    sfnt = font.header.sfnt_version
    font_format = case sfnt
                  when 0x00010000, 0x74727565 # 0x74727565 = 'true'
                    "TrueType"
                  when 0x4F54544F # 'OTTO'
                    "OpenType"
                  else
                    "Unknown"
                  end

    num_glyphs = post_table&.glyph_names&.length || 0
    num_tables = font.table_names.length

    Models::CollectionFontSummary.new(
      index: index,
      family_name: family_name,
      subfamily_name: subfamily_name,
      postscript_name: postscript_name,
      font_format: font_format,
      num_glyphs: num_glyphs,
      num_tables: num_tables,
    )
  end

  Models::CollectionListInfo.new(
    collection_path: nil, # Will be set by command
    num_fonts: num_fonts,
    fonts: fonts,
  )
end

#valid?Boolean

Validate format correctness

Returns:

  • (Boolean)

    true if the format is valid, false otherwise



86
87
88
89
90
# File 'lib/fontisan/true_type_collection.rb', line 86

def valid?
  tag == Constants::TTC_TAG && num_fonts.positive? && font_offsets.length == num_fonts
rescue StandardError
  false
end

#versionObject

Get the TTC version as a single integer

@return [Integer] Version number (e.g., 0x00010000 for version 1.0)


95
96
97
# File 'lib/fontisan/true_type_collection.rb', line 95

def version
  (major_version << 16) | minor_version
end