Class: Fontisan::Variation::InstanceWriter

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

Overview

Writes generated static font instances to files in various formats

InstanceWriter takes instance tables generated by InstanceGenerator and writes them to files in the desired output format. It handles:

  • Format detection from file extension
  • Format conversion when needed (e.g., glyf → CFF for OTF)
  • SFNT version selection based on output format
  • Integration with FontWriter for binary output
  • Integration with OutlineConverter for format conversion
  • Integration with WoffWriter for WOFF packaging

Supported Output Formats:

  • TTF (TrueType with glyf outlines)
  • OTF (OpenType with CFF outlines)
  • WOFF (Web Open Font Format)
  • WOFF2 (Web Open Font Format 2.0, future)

Examples:

Write instance to TTF

tables = generator.generate
InstanceWriter.write(tables, 'bold.ttf')

Write instance to OTF with format conversion

tables = generator.generate  # from variable TTF
InstanceWriter.write(tables, 'bold.otf', source_format: :ttf)

Write instance to WOFF

tables = generator.generate
InstanceWriter.write(tables, 'bold.woff')

Constant Summary collapse

SUPPORTED_FORMATS =

Supported output formats

%i[ttf otf woff woff2].freeze
SFNT_VERSION_TRUETYPE =

SFNT version constants

0x00010000
SFNT_VERSION_CFF =

TrueType with glyf

0x4F54544F
TABLE_PARSERS =

Per-tag table parser dispatch. Each entry maps tag → [class, init_mode]. :parse means call obj.parse(data); :data= means call obj.data = data.

{
  "head" => [Tables::Head, :parse],
  "maxp" => [Tables::Maxp, :parse],
  "loca" => [Tables::Loca, :data_assign],
  "glyf" => [Tables::Glyf, :data_assign],
  "CFF " => [Tables::Cff, :parse],
  "CFF2" => [Tables::Cff2, :parse],
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tables, options = {}) ⇒ InstanceWriter

Initialize writer with instance tables

Parameters:

  • tables (Hash<String, String>)

    Instance tables from InstanceGenerator

  • options (Hash) (defaults to: {})

    Writer options

Options Hash (options):

  • :source_format (Symbol)

    Source format before instancing

  • :optimize (Boolean)

    Enable CFF optimization



76
77
78
79
80
# File 'lib/fontisan/variation/instance_writer.rb', line 76

def initialize(tables, options = {})
  @tables = tables
  @options = options
  validate_tables!
end

Instance Attribute Details

#optionsHash (readonly)

Returns Writer options.

Returns:

  • (Hash)

    Writer options



67
68
69
# File 'lib/fontisan/variation/instance_writer.rb', line 67

def options
  @options
end

#tablesHash<String, String> (readonly)

Returns Instance tables.

Returns:

  • (Hash<String, String>)

    Instance tables



64
65
66
# File 'lib/fontisan/variation/instance_writer.rb', line 64

def tables
  @tables
end

Class Method Details

.write(tables, output_path, options = {}) ⇒ Integer

Write instance tables to file

Parameters:

  • tables (Hash<String, String>)

    Instance tables from InstanceGenerator

  • output_path (String)

    Output file path

  • options (Hash) (defaults to: {})

    Options

Options Hash (options):

  • :format (Symbol)

    Output format (:ttf, :otf, :woff, :woff2)

  • :source_format (Symbol)

    Source format before instancing (:ttf or :otf)

  • :optimize (Boolean)

    Enable CFF optimization for OTF (default: false)

  • :sfnt_version (Integer)

    Override SFNT version

Returns:

  • (Integer)

    Number of bytes written

Raises:

  • (ArgumentError)

    If parameters are invalid

  • (Error)

    If format conversion fails



59
60
61
# File 'lib/fontisan/variation/instance_writer.rb', line 59

def self.write(tables, output_path, options = {})
  new(tables, options).write(output_path)
end

Instance Method Details

#parse_table(tag, data) ⇒ Object



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/fontisan/variation/instance_writer.rb', line 267

def parse_table(tag, data)
  entry = TABLE_PARSERS[tag]
  if entry
    klass, init_mode = entry
    obj = klass.new
    init_mode == :parse ? obj.parse(data) : obj.data = data
    obj
  else
    # For other tables, return a simple object that just holds data
    Object.new.tap do |obj|
      obj.define_singleton_method(:data) { data }
    end
  end
rescue StandardError => e
  warn "Warning: Failed to parse #{tag} table: #{e.message}"
  nil
end

#sfnt_version_for_format(format) ⇒ Integer

Get SFNT version for output format

Parameters:

  • format (Symbol)

    Output format

Returns:

  • (Integer)

    SFNT version constant



355
356
357
# File 'lib/fontisan/variation/instance_writer.rb', line 355

def sfnt_version_for_format(format)
  format == :otf ? SFNT_VERSION_CFF : SFNT_VERSION_TRUETYPE
end

#write(output_path) ⇒ Integer

Write instance to file

Parameters:

  • output_path (String)

    Output file path

Returns:

  • (Integer)

    Number of bytes written



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
# File 'lib/fontisan/variation/instance_writer.rb', line 86

def write(output_path)
  # Detect output format
  format = detect_output_format(output_path)
  validate_format!(format)

  # Detect source format from tables
  source_format = detect_source_format(@tables)

  # Convert format if needed
  output_tables = if format_conversion_needed?(source_format, format)
                    convert_format(source_format, format)
                  else
                    @tables
                  end

  # Write to file based on format
  case format
  when :ttf, :otf
    write_sfnt(output_tables, output_path, format)
  when :woff
    write_woff(output_tables, output_path, source_format)
  when :woff2
    write_woff2(output_tables, output_path, source_format)
  end
end

#write_sfnt(tables, output_path, format) ⇒ Integer

Write SFNT format (TTF or OTF)

Parameters:

  • tables (Hash<String, String>)

    Output tables

  • output_path (String)

    Output file path

  • format (Symbol)

    Output format

Returns:

  • (Integer)

    Number of bytes written



291
292
293
294
295
296
297
298
299
300
# File 'lib/fontisan/variation/instance_writer.rb', line 291

def write_sfnt(tables, output_path, format)
  # Determine SFNT version
  sfnt_version = @options[:sfnt_version] || sfnt_version_for_format(
    format,
  )

  # Write using FontWriter
  FontWriter.write_to_file(tables, output_path,
                           sfnt_version: sfnt_version)
end

#write_woff(tables, output_path, source_format) ⇒ Integer

Write WOFF format

Parameters:

  • tables (Hash<String, String>)

    Output tables

  • output_path (String)

    Output file path

  • source_format (Symbol)

    Source format (for flavor detection)

Returns:

  • (Integer)

    Number of bytes written



308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/fontisan/variation/instance_writer.rb', line 308

def write_woff(tables, output_path, source_format)
  # Create temporary font for WOFF writer
  temp_font = create_temp_font(tables, source_format)

  # Add cff? method for WoffWriter flavor detection
  temp_font.define_singleton_method(:cff?) do
    tables.key?("CFF ") || tables.key?("CFF2")
  end

  # Use WoffWriter to create WOFF
  writer = Converters::WoffWriter.new
  woff_data = writer.convert(temp_font)

  # Write to file
  File.binwrite(output_path, woff_data)
rescue StandardError => e
  raise Fontisan::Error,
        "Failed to write WOFF output: #{e.message}"
end

#write_woff2(tables, output_path, source_format) ⇒ Integer

Write WOFF2 format

Parameters:

  • tables (Hash<String, String>)

    Output tables

  • output_path (String)

    Output file path

  • source_format (Symbol)

    Source format (for flavor detection)

Returns:

  • (Integer)

    Number of bytes written



334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'lib/fontisan/variation/instance_writer.rb', line 334

def write_woff2(tables, output_path, source_format)
  require "tmpdir"
  sfnt_version = sfnt_version_for_format(source_format)

  Dir.mktmpdir("fontisan-woff2-") do |dir|
    temp_ttf = File.join(dir, "instance.ttf")
    FontWriter.write_to_file(tables, temp_ttf, sfnt_version: sfnt_version)

    font = FontLoader.load(temp_ttf)
    woff2_data = Converters::Woff2Encoder.new.convert(font)
    File.binwrite(output_path, woff2_data)
  end
rescue StandardError => e
  raise Fontisan::Error,
        "Failed to write WOFF2 output: #{e.message}"
end