Class: Fontisan::Commands::InfoCommand

Inherits:
BaseCommand show all
Defined in:
lib/fontisan/commands/info_command.rb

Overview

Command to extract font or collection metadata information.

This command auto-detects whether the input is a collection (TTC/OTC) or individual font (TTF/OTF) and returns the appropriate model:

  • CollectionInfo for TTC/OTC files
  • FontInfo for TTF/OTF files

For individual fonts, extracts comprehensive information from various tables:

  • name table: family names, version, copyright, etc.
  • OS/2 table: vendor ID, embedding permissions
  • head table: font revision, units per em

Examples:

Extract font information

command = InfoCommand.new("path/to/font.ttf")
info = command.run
puts info.family_name

Extract collection information

command = InfoCommand.new("path/to/fonts.ttc")
info = command.run
puts "Collection has #{info.num_fonts} fonts"

Instance Method Summary collapse

Methods inherited from BaseCommand

#initialize

Constructor Details

This class inherits a constructor from Fontisan::Commands::BaseCommand

Instance Method Details

#collection_infoModels::CollectionInfo, Models::CollectionBriefInfo

Get collection information

Returns:



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/fontisan/commands/info_command.rb', line 45

def collection_info
  collection = FontLoader.load_collection(@font_path)

  File.open(@font_path, "rb") do |io|
    if @options[:brief]
      # Brief mode: load each font and populate brief info
      brief_info = Models::CollectionBriefInfo.new
      brief_info.collection_path = @font_path
      brief_info.collection_type = collection.class.collection_format
      brief_info.collection_version = collection.version_string
      brief_info.num_fonts = collection.num_fonts
      brief_info.fonts = load_collection_fonts(collection, @font_path)

      brief_info
    else
      # Full mode: show detailed sharing statistics AND font information
      full_info = collection.collection_info(io, @font_path)

      # Add font information to full mode
      full_info.fonts = load_collection_fonts(collection, @font_path)

      full_info
    end
  end
end

#font_infoModels::FontInfo

Get individual font information

Returns:



132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/fontisan/commands/info_command.rb', line 132

def font_info
  info = Models::FontInfo.new
  populate_font_format(info)

  # In brief mode, only populate essential fields for fast identification
  if @options[:brief]
    populate_brief_fields(info)
  else
    populate_full_fields(info)
  end

  info
end

#format_permissions(flags) ⇒ String

Format OS/2 embedding permission flags into a human-readable string.

Parameters:

  • flags (Integer)

    OS/2 fsType flags

Returns:

  • (String)

    Formatted permission string



431
432
433
434
435
436
437
438
439
440
441
442
443
444
# File 'lib/fontisan/commands/info_command.rb', line 431

def format_permissions(flags)
  emb = flags & 15
  result = case emb
           when 0 then "Installable"
           when 2 then "Restricted License"
           when 4 then "Preview & Print"
           when 8 then "Editable"
           else "Unknown (#{emb})"
           end

  result += ", No subsetting" if flags.anybits?(0x100)
  result += ", Bitmap only" if flags.anybits?(0x200)
  result
end

#load_collection_fonts(collection, collection_path) ⇒ Array<Models::FontInfo>

Load font information for all fonts in a collection

Parameters:

Returns:



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/fontisan/commands/info_command.rb', line 76

def load_collection_fonts(collection, collection_path)
  fonts = []

  collection.num_fonts.times do |index|
    # Load individual font from collection
    font = FontLoader.load(collection_path, font_index: index, mode: LoadingModes::METADATA)

    # Populate font info
    info = Models::FontInfo.new

    # Font format and variable status
    info.font_format = case font
                       when TrueTypeFont
                         "truetype"
                       when OpenTypeFont
                         "cff"
                       else
                         "unknown"
                       end
    info.is_variable = font.has_table?(Constants::FVAR_TAG)

    # Collection offset (only populated for fonts in collections)
    info.collection_offset = collection.font_offsets[index]

    # Essential names
    if font.has_table?(Constants::NAME_TAG)
      name_table = font.table(Constants::NAME_TAG)
      info.family_name = name_table.english_name(Tables::Name::FAMILY)
      info.subfamily_name = name_table.english_name(Tables::Name::SUBFAMILY)
      info.full_name = name_table.english_name(Tables::Name::FULL_NAME)
      info.postscript_name = name_table.english_name(Tables::Name::POSTSCRIPT_NAME)
      info.version = name_table.english_name(Tables::Name::VERSION)
    end

    # Essential metrics
    if font.has_table?(Constants::HEAD_TAG)
      head = font.table(Constants::HEAD_TAG)
      info.font_revision = head.font_revision
      info.units_per_em = head.units_per_em
    end

    # Vendor ID
    if font.has_table?(Constants::OS2_TAG)
      os2_table = font.table(Constants::OS2_TAG)
      info.vendor_id = os2_table.vendor_id
    end

    fonts << info
  end

  fonts
end

#populate_bitmap_info(info) ⇒ Object

Populate FontInfo with bitmap table information (CBDT/CBLC, sbix)

Parameters:



378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'lib/fontisan/commands/info_command.rb', line 378

def populate_bitmap_info(info)
  bitmap_strikes = []
  ppem_sizes = []
  formats = []

  # Check for CBDT/CBLC (Google format)
  if font.has_table?("CBLC") && font.has_table?("CBDT")
    cblc = font.table("CBLC")
    info.has_bitmap_glyphs = true
    ppem_sizes.concat(cblc.ppem_sizes)

    cblc.strikes.each do |strike_rec|
      bitmap_strikes << Models::BitmapStrike.new(
        ppem: strike_rec.ppem,
        start_glyph_id: strike_rec.start_glyph_index,
        end_glyph_id: strike_rec.end_glyph_index,
        bit_depth: strike_rec.bit_depth,
        num_glyphs: strike_rec.glyph_range.size,
      )
    end
    formats << "PNG" # CBDT typically contains PNG data
  end

  # Check for sbix (Apple format)
  if font.has_table?("sbix")
    sbix = font.table("sbix")
    info.has_bitmap_glyphs = true
    ppem_sizes.concat(sbix.ppem_sizes)
    formats.concat(sbix.supported_formats)

    sbix.strikes.each do |strike|
      bitmap_strikes << Models::BitmapStrike.new(
        ppem: strike[:ppem],
        start_glyph_id: 0,
        end_glyph_id: strike[:num_glyphs] - 1,
        bit_depth: 32, # sbix is typically 32-bit
        num_glyphs: strike[:num_glyphs],
      )
    end
  end

  info.bitmap_strikes = bitmap_strikes unless bitmap_strikes.empty?
  info.bitmap_ppem_sizes = ppem_sizes.uniq.sort
  info.bitmap_formats = formats.uniq
rescue StandardError => e
  warn "Failed to populate bitmap info: #{e.message}"
  info.has_bitmap_glyphs = false
end

#populate_brief_fields(info) ⇒ Object

Populate essential fields for brief mode (metadata tables only).

Brief mode provides fast font identification by loading only 13 essential attributes from metadata tables (name, head, OS/2). This is 5x faster than full mode and optimized for font indexing systems.

Parameters:



177
178
179
180
181
182
183
# File 'lib/fontisan/commands/info_command.rb', line 177

def populate_brief_fields(info)
  if font.is_a?(Type1Font)
    populate_type1_brief_fields(info)
  else
    populate_sfnt_brief_fields(info)
  end
end

#populate_color_info(info) ⇒ Object

Populate FontInfo with color font information from COLR/CPAL tables

Parameters:



345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/fontisan/commands/info_command.rb', line 345

def populate_color_info(info)
  colr_table = font.table("COLR")
  cpal_table = font.table("CPAL")

  return unless colr_table && cpal_table

  info.is_color_font = true
  info.color_glyphs = colr_table.num_color_glyphs
  info.color_palettes = cpal_table.num_palettes
  info.colors_per_palette = cpal_table.num_palette_entries
rescue StandardError => e
  warn "Failed to populate color font info: #{e.message}"
  info.is_color_font = false
end

#populate_font_format(info) ⇒ Object

Populate font format and variable status based on font class and table presence.

Parameters:



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/fontisan/commands/info_command.rb', line 149

def populate_font_format(info)
  # Determine base format from font class
  info.font_format = case font
                     when TrueTypeFont
                       "truetype"
                     when OpenTypeFont
                       "cff"
                     when Type1Font
                       "type1"
                     else
                       "unknown"
                     end

  # Check if variable font (Type1 fonts are never variable)
  info.is_variable = if font.is_a?(Type1Font)
                       false
                     else
                       font.has_table?(Constants::FVAR_TAG)
                     end
end

#populate_from_head_table(info) ⇒ Object

Populate FontInfo from the head table.

Parameters:



334
335
336
337
338
339
340
# File 'lib/fontisan/commands/info_command.rb', line 334

def populate_from_head_table(info)
  head_table = font.table(Constants::HEAD_TAG)
  return unless head_table

  info.font_revision = head_table.font_revision
  info.units_per_em = head_table.units_per_em
end

#populate_from_name_table(info) ⇒ Object

Populate FontInfo from the name table.

Parameters:



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/fontisan/commands/info_command.rb', line 294

def populate_from_name_table(info)
  name_table = font.table(Constants::NAME_TAG)
  return unless name_table

  info.family_name = name_table.english_name(Tables::Name::FAMILY)
  info.subfamily_name = name_table.english_name(Tables::Name::SUBFAMILY)
  info.full_name = name_table.english_name(Tables::Name::FULL_NAME)
  info.postscript_name = name_table.english_name(Tables::Name::POSTSCRIPT_NAME)
  info.postscript_cid_name = name_table.english_name(Tables::Name::POSTSCRIPT_CID)
  info.preferred_family = name_table.english_name(Tables::Name::PREFERRED_FAMILY)
  info.preferred_subfamily = name_table.english_name(Tables::Name::PREFERRED_SUBFAMILY)
  info.mac_font_menu_name = name_table.english_name(Tables::Name::COMPATIBLE_FULL)
  info.version = name_table.english_name(Tables::Name::VERSION)
  info.unique_id = name_table.english_name(Tables::Name::UNIQUE_ID)
  info.description = name_table.english_name(Tables::Name::DESCRIPTION)
  info.designer = name_table.english_name(Tables::Name::DESIGNER)
  info.designer_url = name_table.english_name(Tables::Name::DESIGNER_URL)
  info.manufacturer = name_table.english_name(Tables::Name::MANUFACTURER)
  info.vendor_url = name_table.english_name(Tables::Name::VENDOR_URL)
  info.trademark = name_table.english_name(Tables::Name::TRADEMARK)
  info.copyright = name_table.english_name(Tables::Name::COPYRIGHT)
  info.license_description = name_table.english_name(Tables::Name::LICENSE_DESCRIPTION)
  info.license_url = name_table.english_name(Tables::Name::LICENSE_URL)
  info.sample_text = name_table.english_name(Tables::Name::SAMPLE_TEXT)
end

#populate_from_os2_table(info) ⇒ Object

Populate FontInfo from the OS/2 table.

Parameters:



323
324
325
326
327
328
329
# File 'lib/fontisan/commands/info_command.rb', line 323

def populate_from_os2_table(info)
  os2_table = font.table(Constants::OS2_TAG)
  return unless os2_table

  info.vendor_id = os2_table.vendor_id
  info.permissions = format_permissions(os2_table.type_flags)
end

#populate_full_fields(info) ⇒ Object

Populate all fields for full mode.

Full mode extracts comprehensive metadata from all available tables.

Parameters:



240
241
242
243
244
245
246
# File 'lib/fontisan/commands/info_command.rb', line 240

def populate_full_fields(info)
  if font.is_a?(Type1Font)
    populate_type1_full_fields(info)
  else
    populate_sfnt_full_fields(info)
  end
end

#populate_sfnt_brief_fields(info) ⇒ Object

Populate SFNT font brief fields (name, head, OS/2 tables).

Parameters:



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/fontisan/commands/info_command.rb', line 188

def populate_sfnt_brief_fields(info)
  # Essential names from name table
  if font.has_table?(Constants::NAME_TAG)
    name_table = font.table(Constants::NAME_TAG)
    info.family_name = name_table.english_name(Tables::Name::FAMILY)
    info.subfamily_name = name_table.english_name(Tables::Name::SUBFAMILY)
    info.full_name = name_table.english_name(Tables::Name::FULL_NAME)
    info.postscript_name = name_table.english_name(Tables::Name::POSTSCRIPT_NAME)
    info.version = name_table.english_name(Tables::Name::VERSION)
  end

  # Essential metrics from head table
  if font.has_table?(Constants::HEAD_TAG)
    head = font.table(Constants::HEAD_TAG)
    info.font_revision = head.font_revision
    info.units_per_em = head.units_per_em
  end

  # Vendor ID from OS/2 table
  if font.has_table?(Constants::OS2_TAG)
    os2_table = font.table(Constants::OS2_TAG)
    info.vendor_id = os2_table.vendor_id
  end
end

#populate_sfnt_full_fields(info) ⇒ Object

Populate SFNT font full fields.

Parameters:



251
252
253
254
255
256
257
258
# File 'lib/fontisan/commands/info_command.rb', line 251

def populate_sfnt_full_fields(info)
  populate_from_name_table(info) if font.has_table?(Constants::NAME_TAG)
  populate_from_os2_table(info) if font.has_table?(Constants::OS2_TAG)
  populate_from_head_table(info) if font.has_table?(Constants::HEAD_TAG)
  populate_color_info(info) if font.has_table?("COLR") && font.has_table?("CPAL")
  populate_svg_info(info) if font.has_table?("SVG ")
  populate_bitmap_info(info) if font.has_table?("CBLC") || font.has_table?("sbix")
end

#populate_svg_info(info) ⇒ Object

Populate FontInfo with SVG table information

Parameters:



363
364
365
366
367
368
369
370
371
372
373
# File 'lib/fontisan/commands/info_command.rb', line 363

def populate_svg_info(info)
  svg_table = font.table("SVG ")

  return unless svg_table

  info.has_svg_table = true
  info.svg_glyph_count = svg_table.glyph_ids_with_svg.length
rescue StandardError => e
  warn "Failed to populate SVG info: #{e.message}"
  info.has_svg_table = false
end

#populate_type1_brief_fields(info) ⇒ Object

Populate Type 1 font brief fields.

Parameters:



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/fontisan/commands/info_command.rb', line 216

def populate_type1_brief_fields(info)
  # Get Type 1 font metadata
  font_dict = font.font_dictionary
  font_info = font_dict&.font_info if font_dict

  return unless font_info

  # Essential names from Type 1 font
  info.family_name = font_info.family_name
  info.full_name = font_info.full_name
  info.postscript_name = font.font_name
  info.version = font_info.version

  # Metrics from font dictionary
  if font_dict&.font_b_box
    info.bounding_box = font_dict.font_b_box
  end
end

#populate_type1_full_fields(info) ⇒ Object

Populate Type 1 font full fields.

Parameters:



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/fontisan/commands/info_command.rb', line 263

def populate_type1_full_fields(info)
  font_dict = font.font_dictionary
  font_info = font_dict&.font_info if font_dict

  return unless font_info

  # Names from Type 1 font
  info.family_name = font_info.family_name
  info.full_name = font_info.full_name
  info.postscript_name = font.font_name
  info.version = font_info.version
  info.copyright = font_info.copyright
  info.description = font_info.notice
  info.designer = nil # Type 1 fonts may not have designer info

  # Metrics
  if font_dict&.font_b_box
    info.bounding_box = font_dict.font_b_box
  end

  if font_dict&.font_matrix
    info.font_matrix = font_dict.font_matrix
  end

  # Glyph count
  info.glyph_count = font.charstrings&.count || 0
end

#runModels::FontInfo, Models::CollectionInfo

Extract information from font or collection.

Auto-detects file type and returns appropriate model.

Returns:



32
33
34
35
36
37
38
# File 'lib/fontisan/commands/info_command.rb', line 32

def run
  if FontLoader.collection?(@font_path)
    collection_info
  else
    font_info
  end
end