Class: Fontisan::ConversionOptions

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

Overview

Conversion options for font format transformations

Defines all options for opening (reading) and generating (writing) fonts during conversion operations. Includes validation, defaults, and presets.

Examples:

Using options directly

options = ConversionOptions.new(
  from: :ttf,
  to: :otf,
  opening: { convert_curves: true, scale_to_1000: true },
  generating: { hinting_mode: "auto" }
)

Using recommended options

options = ConversionOptions.recommended(from: :ttf, to: :otf)

Using a preset

options = ConversionOptions.from_preset(:type1_to_modern)

Constant Summary collapse

OPENING_OPTIONS =

Opening options (input processing)

i[
  decompose_composites
  convert_curves
  scale_to_1000
  scale_from_1000
  autohint
  generate_unicode
  store_custom_tables
  store_native_hinting
  interpret_ot
  read_all_records
  preserve_encoding
].freeze
STATIC_GENERATING_OPTIONS =

Generating options (output processing) declared statically by ConversionOptions itself. Format-specific compression knobs (zlib_level, brotli_quality, transform_tables, etc.) are declared by each strategy via the ConversionStrategy DSL and discovered via FormatConverter.all_strategy_option_names — see .generating_options.

i[
  write_pfm
  write_afm
  write_inf
  select_encoding_automatically
  hinting_mode
  decompose_on_output
  write_custom_tables
  optimize_tables
  reencode_first_256
  encoding_vector
  
  
  target_format
].freeze
HINTING_MODES =

Valid hinting modes

%w[preserve auto none full].freeze
OPENING_DEFAULTS =

Default opening options per conversion pair

{
  ttf: {
    ttf: { decompose_composites: false, convert_curves: false,
           store_custom_tables: true },
    otf: { convert_curves: true, scale_to_1000: true,
           autohint: true, decompose_composites: false },
    type1: { convert_curves: true, scale_to_1000: true,
             autohint: true, decompose_composites: false },
  },
  otf: {
    ttf: { convert_curves: true, decompose_composites: false,
           interpret_ot: true },
    otf: { decompose_composites: false, store_custom_tables: true },
    type1: { decompose_composites: false },
  },
  type1: {
    ttf: { decompose_composites: false, generate_unicode: true,
           read_all_records: true },
    otf: { decompose_composites: false, generate_unicode: true,
           read_all_records: true },
  },
}.freeze
GENERATING_DEFAULTS =

Default generating options per conversion pair

{
  ttf: {
    ttf: { hinting_mode: "preserve", write_custom_tables: true,
           optimize_tables: true },
    otf: { hinting_mode: "auto", decompose_on_output: false,
           write_custom_tables: true },
    type1: { write_pfm: true, write_afm: true, write_inf: true,
             select_encoding_automatically: true, hinting_mode: "auto" },
  },
  otf: {
    ttf: { hinting_mode: "auto", decompose_on_output: false,
           write_custom_tables: true },
    otf: { hinting_mode: "preserve", decompose_on_output: false,
           write_custom_tables: true, optimize_tables: true },
    type1: { write_pfm: true, write_afm: true, write_inf: true,
             select_encoding_automatically: true, hinting_mode: "preserve",
             decompose_on_output: false },
  },
  type1: {
    ttf: { hinting_mode: "auto", decompose_on_output: true },
    otf: { hinting_mode: "preserve", decompose_on_output: true },
    type1: { write_pfm: true, write_afm: true, write_inf: true,
             select_encoding_automatically: true },
  },
}.freeze
{
  ttf: {
    ttf: {
      opening: { convert_curves: false, scale_to_1000: false,
                 decompose_composites: false, autohint: false,
                 store_custom_tables: true, store_native_hinting: true },
      generating: { hinting_mode: "full", write_custom_tables: true },
    },
    otf: {
      opening: { convert_curves: true, scale_to_1000: true,
                 autohint: true, decompose_composites: false,
                 store_custom_tables: true },
      generating: { hinting_mode: "auto", decompose_on_output: true },
    },
    type1: {
      opening: { convert_curves: true, scale_to_1000: true,
                 autohint: true, decompose_composites: false,
                 store_custom_tables: false },
      generating: { write_pfm: true, write_afm: true, write_inf: true,
                    select_encoding_automatically: true },
    },
  },
  otf: {
    ttf: {
      opening: { decompose_composites: false, read_all_records: true,
                 interpret_ot: true, store_custom_tables: true,
                 store_native_hinting: false },
      generating: { hinting_mode: "full", reencode_first_256: false },
    },
    otf: {
      opening: { decompose_composites: false, store_custom_tables: true },
      generating: { hinting_mode: "none", decompose_on_output: false,
                    write_custom_tables: true },
    },
    type1: {
      opening: { decompose_composites: false },
      generating: { write_pfm: true, write_afm: true, write_inf: true,
                    select_encoding_automatically: true,
                    hinting_mode: "none" },
    },
  },
  type1: {
    ttf: {
      opening: { decompose_composites: false, generate_unicode: true },
      generating: { hinting_mode: "full" },
    },
    otf: {
      opening: { decompose_composites: false, generate_unicode: true },
      generating: { hinting_mode: "none", decompose_on_output: true },
    },
    type1: {
      opening: { decompose_composites: false, generate_unicode: true },
      generating: { write_pfm: true, write_afm: true, write_inf: true,
                    select_encoding_automatically: true },
    },
  },
}.freeze
PRESETS =

Named presets for common conversion scenarios

{
  type1_to_modern: {
    from: :type1,
    to: :otf,
    opening: { generate_unicode: true, decompose_composites: false },
    generating: { hinting_mode: "preserve", decompose_on_output: true },
  },
  modern_to_type1: {
    from: :otf,
    to: :type1,
    opening: { convert_curves: true, scale_to_1000: true,
               autohint: true, decompose_composites: false,
               store_custom_tables: false },
    generating: { write_pfm: true, write_afm: true, write_inf: true,
                  select_encoding_automatically: true, hinting_mode: "preserve" },
  },
  web_optimized: {
    from: :otf,
    to: :woff2,
    opening: {},
    generating: { brotli_quality: 11, transform_tables: true,
                  optimize_tables: true, preserve_metadata: true },
  },
  legacy_web: {
    from: :otf,
    to: :woff,
    opening: {},
    generating: { zlib_level: 9, optimize_tables: true,
                  preserve_metadata: true },
  },
  archive_to_modern: {
    from: :ttc,
    to: :otf,
    opening: { convert_curves: true, decompose_composites: false },
    generating: { target_format: "otf", hinting_mode: "preserve" },
  },
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(from:, to:, opening: {}, generating: {}) ⇒ ConversionOptions

Initialize conversion options

Parameters:

  • from (String, Symbol)

    Source format

  • to (String, Symbol)

    Target format

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

    Opening options

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

    Generating options



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

def initialize(from:, to:, opening: {}, generating: {})
  @from = self.class.normalize_format(from)
  @to = self.class.normalize_format(to)
  @opening = apply_opening_defaults(opening)
  @generating = apply_generating_defaults(generating)
  validate!
end

Instance Attribute Details

#fromObject (readonly)

Returns the value of attribute from.



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

def from
  @from
end

#generatingObject (readonly)

Returns the value of attribute generating.



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

def generating
  @generating
end

#openingObject (readonly)

Returns the value of attribute opening.



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

def opening
  @opening
end

#toObject (readonly)

Returns the value of attribute to.



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

def to
  @to
end

Class Method Details

.available_presetsArray<Symbol>

Get list of available presets

Returns:

  • (Array<Symbol>)

    Available preset names



149
150
151
# File 'lib/fontisan/conversion_options.rb', line 149

def self.available_presets
  PRESETS.keys
end

.from_preset(preset_name) ⇒ ConversionOptions

Load options from a named preset

Parameters:

  • preset_name (Symbol, String)

    Preset name

Returns:



114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/fontisan/conversion_options.rb', line 114

def self.from_preset(preset_name)
  preset_key = preset_name.to_sym
  preset = PRESETS.fetch(preset_key) do
    raise ArgumentError, "Unknown preset: #{preset_name}. " \
                        "Available: #{PRESETS.keys.join(', ')}"
  end

  new(
    from: preset[:from],
    to: preset[:to],
    opening: preset[:opening] || {},
    generating: preset[:generating] || {},
  )
end

.generating_optionsArray<Symbol>

Full list of recognized generating options, computed lazily because converters require_relative this file, creating a load cycle that prevents resolving FormatConverter at class-definition time. The first call resolves all strategies (which are loaded before any conversion actually runs) and caches the result.

Returns:

  • (Array<Symbol>)


66
67
68
69
70
71
# File 'lib/fontisan/conversion_options.rb', line 66

def self.generating_options
  @generating_options ||= (
    STATIC_GENERATING_OPTIONS +
    Converters::FormatConverter.all_strategy_option_names
  ).uniq.freeze
end

.normalize_format(format) ⇒ Symbol

Normalize format symbol

Parameters:

  • format (String, Symbol)

    Format identifier

Returns:

  • (Symbol)

    Normalized format symbol



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/fontisan/conversion_options.rb', line 159

def self.normalize_format(format)
  case format.to_s.downcase
  when "ttf", "truetype", "truetype-tt", "ot-tt" then :ttf
  when "otf", "cff", "opentype", "ot-ps", "opentype-ps" then :otf
  when "type1", "type-1", "t1", "pfb", "pfa" then :type1
  when "ttc" then :ttc
  when "otc" then :otc
  when "dfont", "suitcase" then :dfont
  when "woff" then :woff
  when "woff2" then :woff2
  when "svg" then :svg
  else
    raise ArgumentError, "Unknown format: #{format}. " \
                        "Supported: ttf, otf, type1, ttc, otc, dfont, woff, woff2, svg"
  end
end

Get recommended options for a conversion pair

Parameters:

  • from (String, Symbol)

    Source format

  • to (String, Symbol)

    Target format

Returns:



97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/fontisan/conversion_options.rb', line 97

def self.recommended(from:, to:)
  from_sym = normalize_format(from)
  to_sym = normalize_format(to)

  options = RECOMMENDED_OPTIONS.dig(from_sym, to_sym) || {}
  new(
    from: from_sym,
    to: to_sym,
    opening: options[:opening] || {},
    generating: options[:generating] || {},
  )
end

Instance Method Details

#apply_generating_defaults(options) ⇒ Hash

Apply default values for generating options

Parameters:

  • options (Hash)

    User-provided options

Returns:

  • (Hash)

    Options with defaults applied



189
190
191
192
# File 'lib/fontisan/conversion_options.rb', line 189

def apply_generating_defaults(options)
  defaults = GENERATING_DEFAULTS.dig(@from, @to) || {}
  defaults.merge(options)
end

#apply_opening_defaults(options) ⇒ Hash

Apply default values for opening options

Parameters:

  • options (Hash)

    User-provided options

Returns:

  • (Hash)

    Options with defaults applied



180
181
182
183
# File 'lib/fontisan/conversion_options.rb', line 180

def apply_opening_defaults(options)
  defaults = OPENING_DEFAULTS.dig(@from, @to) || {}
  defaults.merge(options)
end

#generating_option?(key, value = true) ⇒ Boolean

Check if a generating option has a specific value

Parameters:

  • key (Symbol)

    Option key

  • value (Object) (defaults to: true)

    Value to check

Returns:

  • (Boolean)

    true if option equals value



142
143
144
# File 'lib/fontisan/conversion_options.rb', line 142

def generating_option?(key, value = true)
  @generating[key] == value
end

#opening_option?(key) ⇒ Boolean

Check if an opening option is set

Parameters:

  • key (Symbol)

    Option key

Returns:

  • (Boolean)

    true if option is truthy



133
134
135
# File 'lib/fontisan/conversion_options.rb', line 133

def opening_option?(key)
  !!@opening[key]
end

#validate!Object

Validate options

Raises:

  • (ArgumentError)

    If options are invalid



197
198
199
200
# File 'lib/fontisan/conversion_options.rb', line 197

def validate!
  validate_opening_options!
  validate_generating_options!
end

#validate_generating_options!Object

Validate generating options



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/fontisan/conversion_options.rb', line 213

def validate_generating_options!
  @generating.each_key do |key|
    unless self.class.generating_options.include?(key)
      raise ArgumentError, "Unknown generating option: #{key}. " \
                          "Available: #{self.class.generating_options.join(', ')}"
    end
  end

  # Validate hinting_mode
  if @generating[:hinting_mode]
    mode = @generating[:hinting_mode].to_s
    unless HINTING_MODES.include?(mode)
      raise ArgumentError, "Invalid hinting_mode: #{mode}. " \
                          "Available: #{HINTING_MODES.join(', ')}"
    end
  end

  # Validate target_format for collection conversions
  if @generating[:target_format]
    target = @generating[:target_format].to_s
    unless ["ttf", "otf", "preserve"].include?(target)
      raise ArgumentError, "Invalid target_format: #{target}"
    end
  end
end

#validate_opening_options!Object

Validate opening options



203
204
205
206
207
208
209
210
# File 'lib/fontisan/conversion_options.rb', line 203

def validate_opening_options!
  @opening.each_key do |key|
    unless OPENING_OPTIONS.include?(key)
      raise ArgumentError, "Unknown opening option: #{key}. " \
                          "Available: #{OPENING_OPTIONS.join(', ')}"
    end
  end
end