Class: Fontisan::FontWriter

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

Overview

FontWriter handles writing font binaries from table data

This class assembles a complete font binary from individual table data, including:

  • Writing the sfnt header (offset table)
  • Building the table directory
  • Writing table data with proper 4-byte alignment
  • Calculating all checksums
  • Updating the head table's checksumAdjustment field

Reference: OpenType spec section on font file structure

Examples:

Write font from tables

tables = {
  'head' => head_data,
  'hhea' => hhea_data,
  'maxp' => maxp_data,
  'hmtx' => hmtx_data,
  'cmap' => cmap_data
}
binary = FontWriter.write_font(tables)
File.binwrite('subset.ttf', binary)

Write to file directly

FontWriter.write_to_file(tables, 'subset.ttf')

Constant Summary collapse

TRUETYPE_TABLE_ORDER =

OpenType/TrueType table ordering (recommended order)

%w[
  head hhea maxp OS/2 hmtx LTSH VDMX hdmx cmap fpgm prep cvt
  loca glyf kern name post gasp PCLT DSIG
].freeze
OPENTYPE_TABLE_ORDER =

OpenType/CFF table ordering (recommended order)

%w[
  head hhea maxp OS/2 name cmap post CFF CFF2
].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tables_hash, sfnt_version: 0x00010000) ⇒ FontWriter

Initialize writer with table data

Parameters:

  • tables_hash (Hash<String, String>)

    Map of table tag to binary data

  • sfnt_version (Integer) (defaults to: 0x00010000)

    Font sfnt version



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

def initialize(tables_hash, sfnt_version: 0x00010000)
  @tables = tables_hash
  @sfnt_version = sfnt_version
end

Class Method Details

.detect_sfnt_version(tables_hash) ⇒ Integer

Detect sfnt version based on table presence

Parameters:

  • tables_hash (Hash<String, String>)

    Map of table tag to binary data

Returns:

  • (Integer)

    Detected sfnt version



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

def self.detect_sfnt_version(tables_hash)
  if tables_hash.key?("CFF ") || tables_hash.key?("CFF2")
    0x4F54544F # 'OTTO' for OpenType/CFF
  else
    0x00010000 # 1.0 for TrueType
  end
end

.write_font(tables_hash, sfnt_version: nil) ⇒ String

Write complete font binary from table data

Examples:

binary = FontWriter.write_font(tables_hash)
binary = FontWriter.write_font(tables_hash, sfnt_version: 0x4F54544F)

Parameters:

  • tables_hash (Hash<String, String>)

    Map of table tag to binary data

  • sfnt_version (Integer, nil) (defaults to: nil)

    Font sfnt version (0x00010000 for TrueType, 0x4F54544F for OpenType/CFF). If nil, auto-detects based on tables.

Returns:

  • (String)

    Complete font binary



53
54
55
56
57
# File 'lib/fontisan/font_writer.rb', line 53

def self.write_font(tables_hash, sfnt_version: nil)
  # Auto-detect sfnt version if not provided
  sfnt_version ||= detect_sfnt_version(tables_hash)
  new(tables_hash, sfnt_version: sfnt_version).write
end

.write_to_file(tables_hash, path, sfnt_version: nil) ⇒ Integer

Write font binary to file

Examples:

FontWriter.write_to_file(tables_hash, 'output.ttf')

Parameters:

  • tables_hash (Hash<String, String>)

    Map of table tag to binary data

  • path (String)

    Output file path

  • sfnt_version (Integer, nil) (defaults to: nil)

    Font sfnt version. If nil, auto-detects.

Returns:

  • (Integer)

    Number of bytes written



80
81
82
83
84
85
86
87
88
# File 'lib/fontisan/font_writer.rb', line 80

def self.write_to_file(tables_hash, path, sfnt_version: nil)
  binary = write_font(tables_hash, sfnt_version: sfnt_version)

  # Create parent directories if they don't exist
  dir = File.dirname(path)
  FileUtils.mkdir_p(dir) unless Dir.exist?(dir)

  File.binwrite(path, binary)
end

Instance Method Details

#writeString

Write the complete font binary

Returns:

  • (String)

    Complete font binary



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
128
129
130
131
132
# File 'lib/fontisan/font_writer.rb', line 102

def write
  # Order tables according to format
  ordered_tags = order_tables

  # Calculate table offsets
  table_entries = calculate_table_entries(ordered_tags)

  # Build font binary
  font_data = String.new(encoding: Encoding::BINARY)

  # Write offset table (sfnt header)
  font_data << write_offset_table(table_entries.size)

  # rubocop:disable Style/CombinableLoops
  # Write table directory (ALL entries first)
  table_entries.each do |entry|
    font_data << write_table_entry(entry)
  end

  # Write table data (ALL data after directory)
  table_entries.each do |entry|
    font_data << entry[:data]
    font_data << entry[:padding]
  end
  # rubocop:enable Style/CombinableLoops

  # Calculate and update head table checksum adjustment
  update_checksum_adjustment!(font_data, table_entries)

  font_data
end