Class: Fontisan::TrueTypeFont

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

Overview

TrueType Font domain object using BinData

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

Extended from ExtractTTC to support table parsing for analysis.

Examples:

Reading and analyzing a font

ttf = TrueTypeFont.from_file("font.ttf")
puts ttf.header.num_tables  # => 14
name_table = ttf.table("name")  # Fontisan extension
puts name_table.english_name(Tables::Name::FAMILY)

Writing a font

ttf.to_file("output.ttf")

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#parsed_tablesObject

Parsed table instances cache (Fontisan extension)



55
56
57
# File 'lib/fontisan/true_type_font.rb', line 55

def parsed_tables
  @parsed_tables
end

#table_dataObject

Table data is stored separately since it’s at variable offsets



52
53
54
# File 'lib/fontisan/true_type_font.rb', line 52

def table_data
  @table_data
end

Class Method Details

.from_file(path) ⇒ TrueTypeFont

Read TrueType Font from a file

Parameters:

  • path (String)

    Path to the TTF file

Returns:

Raises:

  • (ArgumentError)

    if path is nil or empty

  • (Errno::ENOENT)

    if file does not exist

  • (RuntimeError)

    if file format is invalid



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/fontisan/true_type_font.rb', line 64

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") do |io|
    font = read(io)
    font.initialize_storage
    font.read_table_data(io)
    font
  end
rescue BinData::ValidityError, EOFError => e
  raise "Invalid TTF file: #{e.message}"
end

.from_ttc(io, offset) ⇒ TrueTypeFont

Read TrueType Font from TTC at specific offset

Parameters:

  • io (IO)

    Open file handle

  • offset (Integer)

    Byte offset to the font

Returns:



86
87
88
89
90
91
92
# File 'lib/fontisan/true_type_font.rb', line 86

def self.from_ttc(io, offset)
  io.seek(offset)
  font = read(io)
  font.initialize_storage
  font.read_table_data(io)
  font
end

Instance Method Details

#find_table_entry(tag) ⇒ TableDirectory?

Find a table entry by tag

Parameters:

  • tag (String)

    The table tag to find

Returns:



166
167
168
# File 'lib/fontisan/true_type_font.rb', line 166

def find_table_entry(tag)
  tables.find { |entry| entry.tag == tag }
end

#has_table?(tag) ⇒ Boolean

Check if font has a specific table

Parameters:

  • tag (String)

    The table tag to check for

Returns:

  • (Boolean)

    true if table exists, false otherwise



158
159
160
# File 'lib/fontisan/true_type_font.rb', line 158

def has_table?(tag)
  tables.any? { |entry| entry.tag == tag }
end

#head_tableTableDirectory?

Get the head table entry

Returns:



173
174
175
# File 'lib/fontisan/true_type_font.rb', line 173

def head_table
  find_table_entry(Constants::HEAD_TAG)
end

#initialize_storagevoid

This method returns an undefined value.

Initialize storage hashes (Fontisan extension)



97
98
99
100
# File 'lib/fontisan/true_type_font.rb', line 97

def initialize_storage
  @table_data = {}
  @parsed_tables = {}
end

#read_table_data(io) ⇒ void

This method returns an undefined value.

Read table data for all tables

Parameters:

  • io (IO)

    Open file handle



106
107
108
109
110
111
112
113
114
# File 'lib/fontisan/true_type_font.rb', line 106

def read_table_data(io)
  @table_data = {}
  tables.each do |entry|
    io.seek(entry.offset)
    # Force UTF-8 encoding on tag for hash key consistency
    tag_key = entry.tag.dup.force_encoding("UTF-8")
    @table_data[tag_key] = io.read(entry.table_length)
  end
end

#table(tag) ⇒ Tables::*?

Get parsed table instance (Fontisan extension)

This method parses the raw table data into a structured table object and caches the result for subsequent calls.

Parameters:

  • tag (String)

    The table tag to retrieve

Returns:

  • (Tables::*, nil)

    Parsed table object or nil if not found



191
192
193
# File 'lib/fontisan/true_type_font.rb', line 191

def table(tag)
  @parsed_tables[tag] ||= parse_table(tag)
end

#table_namesArray<String>

Get list of all table tags (Fontisan extension)

Returns:

  • (Array<String>)

    Array of table tag strings



180
181
182
# File 'lib/fontisan/true_type_font.rb', line 180

def table_names
  tables.map(&:tag)
end

#to_file(path) ⇒ Integer

Write TrueType Font to a file

Writes the complete TTF structure to disk, including proper checksum calculation and table alignment.

Parameters:

  • path (String)

    Path where the TTF file will be written

Returns:

  • (Integer)

    Number of bytes written

Raises:

  • (IOError)

    if writing fails



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/fontisan/true_type_font.rb', line 124

def to_file(path)
  File.open(path, "wb") do |io|
    # Write header and tables (directory)
    write_structure(io)

    # Write table data with updated offsets
    write_table_data_with_offsets(io)

    io.pos
  end

  # Update checksum adjustment in head table
  update_checksum_adjustment_in_file(path) if head_table

  File.size(path)
end

#units_per_emInteger?

Get units per em from head table (Fontisan extension)

Returns:

  • (Integer, nil)

    Units per em value



198
199
200
201
# File 'lib/fontisan/true_type_font.rb', line 198

def units_per_em
  head = table(Constants::HEAD_TAG)
  head&.units_per_em
end

#valid?Boolean

Validate format correctness

Returns:

  • (Boolean)

    true if the TTF format is valid, false otherwise



144
145
146
147
148
149
150
151
152
# File 'lib/fontisan/true_type_font.rb', line 144

def valid?
  return false unless header
  return false unless tables.respond_to?(:length)
  return false unless @table_data.is_a?(Hash)
  return false if tables.length != header.num_tables
  return false unless head_table

  true
end