Class: Fontisan::Parsers::DfontParser

Inherits:
Object
  • Object
show all
Defined in:
lib/fontisan/parsers/dfont_parser.rb

Overview

Parser for Apple dfont (Data Fork Font) resource fork format.

dfont files store resource fork data in the data fork, containing TrueType or OpenType SFNT data embedded in a resource fork structure.

Examples:

Extract SFNT from dfont

File.open("font.dfont", "rb") do |io|
  sfnt_data = DfontParser.extract_sfnt(io)
  # sfnt_data is raw SFNT binary
end

Defined Under Namespace

Classes: ResourceHeader, ResourceReference, ResourceType

Class Method Summary collapse

Class Method Details

.dfont?(io) ⇒ Boolean

Check if file is valid dfont resource fork

Parameters:

  • io (IO)

    Open file handle

Returns:

  • (Boolean)


81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/fontisan/parsers/dfont_parser.rb', line 81

def self.dfont?(io)
  io.rewind
  header_bytes = io.read(16)
  io.rewind

  return false if header_bytes.nil? || header_bytes.length < 16

  # Basic sanity check on resource fork structure
  data_offset = header_bytes[0..3].unpack1("N")
  map_offset = header_bytes[4..7].unpack1("N")

  data_offset.positive? && map_offset > data_offset
end

.extract_resource_data(io, header, resource_info) ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Extract resource data at specific offset

Parameters:

  • io (IO)

    Open file handle

  • header (ResourceHeader)

    Parsed header

  • resource_info (Hash)

    Resource info with :offset

Returns:

  • (String)

    Raw SFNT binary data



173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/fontisan/parsers/dfont_parser.rb', line 173

def self.extract_resource_data(io, header, resource_info)
  # Calculate absolute offset to resource data
  # The offset in the reference is relative to the start of the resource data section
  data_offset = header.resource_data_offset + resource_info[:offset]

  io.seek(data_offset)

  # Read data length (first 4 bytes of resource data)
  data_length = io.read(4).unpack1("N")

  # Read the actual data
  io.read(data_length)
end

.extract_sfnt(io, index: 0) ⇒ String

Extract SFNT data from dfont file

Parameters:

  • io (IO)

    Open file handle

  • index (Integer) (defaults to: 0)

    Font index for multi-font suitcases (default: 0)

Returns:

  • (String)

    Raw SFNT binary data

Raises:



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/fontisan/parsers/dfont_parser.rb', line 51

def self.extract_sfnt(io, index: 0)
  header = parse_header(io)
  sfnt_resources = find_sfnt_resources(io, header)

  if sfnt_resources.empty?
    raise InvalidFontError, "No sfnt resources found in dfont file"
  end

  if index >= sfnt_resources.length
    raise InvalidFontError,
          "Font index #{index} out of range (dfont has #{sfnt_resources.length} fonts)"
  end

  extract_resource_data(io, header, sfnt_resources[index])
end

.find_sfnt_resources(io, header) ⇒ Array<Hash>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Find all sfnt resources in resource map

Parameters:

  • io (IO)

    Open file handle

  • header (ResourceHeader)

    Parsed header

Returns:

  • (Array<Hash>)

    Array of resource info hashes with :id and :offset



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
161
162
163
164
# File 'lib/fontisan/parsers/dfont_parser.rb', line 114

def self.find_sfnt_resources(io, header)
  # Seek to resource map
  io.seek(header.resource_map_offset)

  # Skip resource map header (22 bytes reserved + 4 bytes attributes + 2 bytes type list offset + 2 bytes name list offset)
  # The actual layout is:
  # - Bytes 0-15: Copy of resource header (16 bytes)
  # - Bytes 16-19: Reserved for handle to next resource map (4 bytes)
  # - Bytes 20-21: Reserved for file reference number (2 bytes)
  # - Bytes 22-23: Resource file attributes (2 bytes)
  # - Bytes 24-25: Offset to type list (2 bytes)
  # - Bytes 26-27: Offset to name list (2 bytes)
  io.seek(header.resource_map_offset + 24)

  # Read type list offset (relative to start of resource map)
  type_list_offset = io.read(2).unpack1("n")

  # Seek to type list
  io.seek(header.resource_map_offset + type_list_offset)

  # Read number of types minus 1
  type_count_minus_1 = io.read(2).unpack1("n")
  type_count = type_count_minus_1 + 1

  # Find 'sfnt' type in type list
  sfnt_type = nil
  type_count.times do
    type_entry = ResourceType.read(io)

    if type_entry.type_code == "sfnt"
      sfnt_type = type_entry
      break
    end
  end

  return [] unless sfnt_type

  # Read reference list for sfnt resources
  reference_list_offset = header.resource_map_offset + type_list_offset + sfnt_type.reference_list_offset
  io.seek(reference_list_offset)

  resource_count = sfnt_type.resource_count_minus_1 + 1
  resources = []

  resource_count.times do
    ref = ResourceReference.read(io)
    resources << { id: ref.resource_id, offset: ref.data_offset }
  end

  resources
end

.parse_header(io) ⇒ ResourceHeader

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parse resource fork header

Parameters:

  • io (IO)

    Open file handle

Returns:

Raises:



101
102
103
104
105
106
# File 'lib/fontisan/parsers/dfont_parser.rb', line 101

def self.parse_header(io)
  io.rewind
  ResourceHeader.read(io)
rescue BinData::ValidityError => e
  raise InvalidFontError, "Invalid dfont resource header: #{e.message}"
end

.sfnt_count(io) ⇒ Integer

Count number of sfnt resources (fonts) in dfont

Parameters:

  • io (IO)

    Open file handle

Returns:

  • (Integer)

    Number of fonts



71
72
73
74
75
# File 'lib/fontisan/parsers/dfont_parser.rb', line 71

def self.sfnt_count(io)
  header = parse_header(io)
  sfnt_resources = find_sfnt_resources(io, header)
  sfnt_resources.length
end