Class: Fontisan::Commands::ConvertCommand

Inherits:
BaseCommand
  • Object
show all
Defined in:
lib/fontisan/commands/convert_command.rb

Overview

Command for converting fonts between formats

[‘ConvertCommand`](lib/fontisan/commands/convert_command.rb) provides CLI interface for font format conversion operations. It supports:

  • Same-format operations (copy/optimize)

  • TTF ↔ OTF outline format conversion (foundation)

  • Future: WOFF/WOFF2 compression, SVG export

The command uses [‘FormatConverter`](lib/fontisan/converters/format_converter.rb) to orchestrate conversions with appropriate strategies.

Examples:

Convert TTF to OTF

command = ConvertCommand.new(
  'input.ttf',
  to: 'otf',
  output: 'output.otf'
)
command.run

Copy/optimize same format

command = ConvertCommand.new(
  'input.ttf',
  to: 'ttf',
  output: 'optimized.ttf'
)
command.run

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(font_path, options = {}) ⇒ ConvertCommand

Initialize convert command

Parameters:

  • font_path (String)

    Path to input font file

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

    Conversion options

Options Hash (options):

  • :to (String)

    Target format (ttf, otf, woff2, svg)

  • :output (String)

    Output file path (required)

  • :font_index (Integer)

    Index for TTC/OTC (default: 0)

  • :optimize (Boolean)

    Enable subroutine optimization (TTF→OTF only)

  • :min_pattern_length (Integer)

    Minimum pattern length for subroutines

  • :max_subroutines (Integer)

    Maximum number of subroutines

  • :optimize_ordering (Boolean)

    Optimize subroutine ordering



47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/fontisan/commands/convert_command.rb', line 47

def initialize(font_path, options = {})
  super(font_path, options)
  @target_format = parse_target_format(options[:to])
  @output_path = options[:output]
  @converter = Converters::FormatConverter.new

  # Optimization options
  @optimize = options[:optimize] || false
  @min_pattern_length = options[:min_pattern_length] || 10
  @max_subroutines = options[:max_subroutines] || 65_535
  @optimize_ordering = options[:optimize_ordering] != false
end

Class Method Details

.supported?(source, target) ⇒ Boolean

Check if a conversion is supported

Parameters:

  • source (Symbol)

    Source format

  • target (Symbol)

    Target format

Returns:

  • (Boolean)

    True if supported



147
148
149
150
# File 'lib/fontisan/commands/convert_command.rb', line 147

def self.supported?(source, target)
  converter = Converters::FormatConverter.new
  converter.supported?(source, target)
end

.supported_conversionsArray<Hash>

Get list of supported conversions

Returns:

  • (Array<Hash>)

    List of supported conversions



137
138
139
140
# File 'lib/fontisan/commands/convert_command.rb', line 137

def self.supported_conversions
  converter = Converters::FormatConverter.new
  converter.all_conversions
end

Instance Method Details

#runHash

Execute the conversion

Returns:

  • (Hash)

    Result information

Raises:

  • (ArgumentError)

    If output path is not specified

  • (Error)

    If conversion fails



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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
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/commands/convert_command.rb', line 65

def run
  validate_options!

  puts "Converting #{File.basename(font_path)} to #{@target_format}..."

  # Build converter options
  converter_options = {
    target_format: @target_format,
    optimize_subroutines: @optimize,
    min_pattern_length: @min_pattern_length,
    max_subroutines: @max_subroutines,
    optimize_ordering: @optimize_ordering,
    verbose: options[:verbose],
  }

  # Perform conversion with options
  result = @converter.convert(font, @target_format, converter_options)

  # Handle special formats that return complete binary/text
  if @target_format == :woff && result.is_a?(String)
    # WOFF returns complete binary
    File.binwrite(@output_path, result)
  elsif @target_format == :woff2 && result.is_a?(Hash) && result[:woff2_binary]
    File.binwrite(@output_path, result[:woff2_binary])
  elsif @target_format == :svg && result.is_a?(Hash) && result[:svg_xml]
    File.write(@output_path, result[:svg_xml])
  else
    # Standard table-based conversion
    tables = result

    # Determine sfnt version for output
    sfnt_version = determine_sfnt_version(@target_format)

    # Write output font
    FontWriter.write_to_file(tables, @output_path,
                             sfnt_version: sfnt_version)

    # Display optimization results if available
    display_optimization_results(tables) if @optimize && options[:verbose]
  end

  output_size = File.size(@output_path)
  input_size = File.size(font_path)

  puts "Conversion complete!"
  puts "  Input:  #{font_path} (#{format_size(input_size)})"
  puts "  Output: #{@output_path} (#{format_size(output_size)})"

  {
    success: true,
    input_path: font_path,
    output_path: @output_path,
    source_format: detect_source_format,
    target_format: @target_format,
    input_size: input_size,
    output_size: output_size,
  }
rescue NotImplementedError
  # Let NotImplementedError propagate for tests that expect it
  raise
rescue Converters::ConversionStrategy => e
  handle_conversion_error(e)
rescue ArgumentError
  # Let ArgumentError propagate for validation errors
  raise
rescue StandardError => e
  raise Error, "Conversion failed: #{e.message}"
end