Class: Fontisan::OpenTypeFont

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

Overview

OpenType Font domain object using BinData

Represents a complete OpenType Font file (CFF outlines) using BinData’s declarative DSL for binary structure definition. Parallel to TrueTypeFont but for CFF format.

Examples:

Reading and analyzing a font

otf = OpenTypeFont.from_file("font.otf")
puts otf.header.num_tables  # => 12
name_table = otf.table("name")
puts name_table.english_name(Tables::Name::FAMILY)

Writing a font

otf.to_file("output.otf")

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#parsed_tablesObject

Parsed table instances cache



33
34
35
# File 'lib/fontisan/open_type_font.rb', line 33

def parsed_tables
  @parsed_tables
end

#table_dataObject

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



30
31
32
# File 'lib/fontisan/open_type_font.rb', line 30

def table_data
  @table_data
end

Class Method Details

.from_collection(io, offset) ⇒ OpenTypeFont

Read OpenType Font from collection at specific offset

Parameters:

  • io (IO)

    Open file handle

  • offset (Integer)

    Byte offset to the font

Returns:



64
65
66
67
68
69
70
# File 'lib/fontisan/open_type_font.rb', line 64

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

.from_file(path) ⇒ OpenTypeFont

Read OpenType Font from a file

Parameters:

  • path (String)

    Path to the OTF file

Returns:

Raises:

  • (ArgumentError)

    if path is nil or empty

  • (Errno::ENOENT)

    if file does not exist

  • (RuntimeError)

    if file format is invalid



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/fontisan/open_type_font.rb', line 42

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 OTF file: #{e.message}"
end

Instance Method Details

#find_table_entry(tag) ⇒ TableDirectory?

Find a table entry by tag

Parameters:

  • tag (String)

    The table tag to find

Returns:



145
146
147
# File 'lib/fontisan/open_type_font.rb', line 145

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



137
138
139
# File 'lib/fontisan/open_type_font.rb', line 137

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

#head_tableTableDirectory?

Get the head table entry

Returns:



152
153
154
# File 'lib/fontisan/open_type_font.rb', line 152

def head_table
  find_table_entry(Constants::HEAD_TAG)
end

#initialize_storagevoid

This method returns an undefined value.

Initialize storage hashes



75
76
77
78
# File 'lib/fontisan/open_type_font.rb', line 75

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



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

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

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



170
171
172
# File 'lib/fontisan/open_type_font.rb', line 170

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

#table_namesArray<String>

Get list of all table tags

Returns:

  • (Array<String>)

    Array of table tag strings



159
160
161
# File 'lib/fontisan/open_type_font.rb', line 159

def table_names
  tables.map(&:tag)
end

#to_file(path) ⇒ Integer

Write OpenType Font to a file

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

Parameters:

  • path (String)

    Path where the OTF file will be written

Returns:

  • (Integer)

    Number of bytes written

Raises:

  • (IOError)

    if writing fails



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/fontisan/open_type_font.rb', line 102

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

Returns:

  • (Integer, nil)

    Units per em value



177
178
179
180
# File 'lib/fontisan/open_type_font.rb', line 177

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

#valid?Boolean

Validate format correctness

Returns:

  • (Boolean)

    true if the OTF format is valid, false otherwise



122
123
124
125
126
127
128
129
130
131
# File 'lib/fontisan/open_type_font.rb', line 122

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
  return false unless has_table?(Constants::CFF_TAG)

  true
end