Class: Fontisan::Converters::OutlineConverter

Inherits:
Object
  • Object
show all
Includes:
CffTableBuilder, ConversionStrategy, GlyfTableBuilder, OutlineExtraction, OutlineOptimizer
Defined in:
lib/fontisan/converters/outline_converter.rb

Overview

Strategy for converting between TTF and OTF outline formats

OutlineConverter handles conversion between TrueType (glyf/loca) and CFF outline formats. This involves:

  • Extracting glyph outlines from source format
  • Converting to universal Outline model
  • Building target format tables using specialized builders
  • Updating related tables (maxp, head)
  • Preserving all other font tables
  • Optionally preserving rendering hints

Conversion Details:

TTF → OTF:

  • Extract glyphs from glyf/loca tables
  • Convert TrueType quadratic curves to universal format
  • Build complete CFF table with CharStrings INDEX
  • Remove glyf/loca tables
  • Update maxp to version 0.5 (CFF format)
  • Update head table (clear indexToLocFormat)

OTF → TTF:

  • Extract CharStrings from CFF table
  • Convert CFF cubic curves to universal format
  • Build glyf and loca tables
  • Remove CFF table
  • Update maxp to version 1.0 (TrueType format)
  • Update head table (set indexToLocFormat)

Examples:

Converting TTF to OTF

converter = Fontisan::Converters::OutlineConverter.new
otf_font = converter.convert(ttf_font, target_format: :otf)

Converting with ConversionOptions

options = ConversionOptions.recommended(from: :ttf, to: :otf)
converter = Fontisan::Converters::OutlineConverter.new
otf_font = converter.convert(ttf_font, options: options)

Converting with hint preservation

converter = Fontisan::Converters::OutlineConverter.new
otf_font = converter.convert(ttf_font, target_format: :otf, preserve_hints: true)

Constant Summary collapse

SUPPORTED_FORMATS =

Supported outline formats

%i[ttf otf cff2].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from OutlineOptimizer

#optimize_charstrings

Methods included from GlyfTableBuilder

#build_glyf_loca_tables

Methods included from CffTableBuilder

#build_cff_table

Methods included from OutlineExtraction

#extract_cff_outlines, #extract_ttf_outlines

Methods included from ConversionStrategy

included, #supports?

Constructor Details

#initializeOutlineConverter

Initialize converter



61
62
63
# File 'lib/fontisan/converters/outline_converter.rb', line 61

def initialize
  @font = nil
end

Instance Attribute Details

#fontTrueTypeFont, OpenTypeFont (readonly)

Returns Source font.

Returns:



58
59
60
# File 'lib/fontisan/converters/outline_converter.rb', line 58

def font
  @font
end

Instance Method Details

#apply_opening_options(font, conv_options) ⇒ Object

Apply opening options to source font

Parameters:



677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
# File 'lib/fontisan/converters/outline_converter.rb', line 677

def apply_opening_options(font, conv_options)
  return unless conv_options

  # Decompose composites if requested
  if conv_options.opening_option?(:decompose_composites)
    decompose_composite_glyphs(font)
  end

  # Interpret OpenType tables if requested
  if conv_options.opening_option?(:interpret_ot)
    # Ensure GSUB/GPOS tables are fully loaded
    interpret_opentype_features(font)
  end

  # Note: scale_to_1000 and convert_curves are handled during conversion
  # These options affect the conversion process itself
end

#convert(font, options = {}) ⇒ Hash<String, String>

Convert font between TTF and OTF formats

Parameters:

Options Hash (options):

  • :target_format (Symbol)

    Target format (:ttf or :otf)

  • :optimize_cff (Boolean)

    Enable CFF subroutine optimization (default: false)

  • :preserve_hints (Boolean)

    Preserve rendering hints (default: false)

  • :preserve_variations (Boolean)

    Keep variation data during conversion (default: true)

  • :generate_instance (Boolean)

    Generate static instance instead of variable font (default: false)

  • :instance_coordinates (Hash)

    Axis coordinates for instance generation (default: {})

  • :options (ConversionOptions)

    ConversionOptions object

Returns:

  • (Hash<String, String>)

    Map of table tags to binary data



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
# File 'lib/fontisan/converters/outline_converter.rb', line 77

def convert(font, options = {})
  @font = font
  @options = options

  # Extract ConversionOptions if provided
  conv_options = extract_conversion_options(options)

  @optimize_cff = options.fetch(:optimize_cff, false)
  @preserve_hints = options.fetch(:preserve_hints,
                                  conv_options&.generating_option?(
                                    :hinting_mode, "preserve"
                                  ) || false)
  @preserve_variations = options.fetch(:preserve_variations, true)
  @generate_instance = options.fetch(:generate_instance, false)
  @instance_coordinates = options.fetch(:instance_coordinates, {})

  target_format = options[:target_format] || conv_options&.to ||
    detect_target_format(font)
  validate(font, target_format)

  # Apply opening options to source font
  apply_opening_options(font, conv_options) if conv_options

  source_format = detect_format(font)

  # Check if we should generate a static instance instead
  if @generate_instance && variable_font?(font)
    return generate_static_instance(font, source_format, target_format)
  end

  case [source_format, target_format]
  when %i[ttf otf]
    convert_ttf_to_otf(font, options)
  when %i[otf ttf]
    convert_otf_to_ttf(font)
  when %i[cff2 ttf]
    # CFF2 to TTF - treat CFF2 similar to OTF for now
    convert_otf_to_ttf(font)
  when %i[ttf cff2]
    # TTF to CFF2 - for variable fonts
    convert_ttf_to_otf(font, options)
  else
    raise Fontisan::Error,
          "Unsupported conversion: #{source_format}#{target_format}"
  end
end

#convert_otf_to_ttf(font) ⇒ Hash<String, String>

Convert OpenType/CFF font to TrueType

Parameters:

Returns:

  • (Hash<String, String>)

    Target tables



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/fontisan/converters/outline_converter.rb', line 196

def convert_otf_to_ttf(font)
  # Extract all glyphs from CFF table
  outlines = extract_cff_outlines(font)

  # Extract hints if preservation is enabled
  hints_per_glyph = @preserve_hints ? extract_cff_hints(font) : {}

  # Build glyf and loca tables
  glyf_data, loca_data, loca_format = build_glyf_loca_tables(outlines)

  # Copy all tables except CFF
  tables = copy_tables(font, ["CFF ", "CFF2"])

  # Add glyf and loca tables
  tables["glyf"] = glyf_data
  tables["loca"] = loca_data

  # Update maxp table for TrueType
  tables["maxp"] = update_maxp_for_truetype(font, outlines, loca_format)

  # Update head table for TrueType
  tables["head"] = update_head_for_truetype(font, loca_format)

  # Convert and apply hints if preservation is enabled
  if @preserve_hints && hints_per_glyph.any?
    # Extract font-level hints separately
    hint_set = extract_cff_hint_set(font)

    unless hint_set.empty?
      # Convert PostScript hints to TrueType format
      converter = Hints::HintConverter.new
      tt_hint_set = converter.convert_hint_set(hint_set, :truetype)

      # Apply TrueType hints (writes fpgm/prep/cvt tables)
      applier = Hints::TrueTypeHintApplier.new
      tables = applier.apply(tt_hint_set, tables)
    end
  end

  tables
end

#convert_ttf_to_otf(font, _options = {}) ⇒ Hash<String, String>

Convert TrueType font to OpenType/CFF

Parameters:

  • font (TrueTypeFont)

    Source font

  • options (Hash)

    Conversion options (currently unused)

Returns:

  • (Hash<String, String>)

    Target tables



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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/fontisan/converters/outline_converter.rb', line 129

def convert_ttf_to_otf(font, _options = {})
  # Extract all glyphs from glyf table
  outlines = extract_ttf_outlines(font)

  # Extract hints if preservation is enabled
  hints_per_glyph = @preserve_hints ? extract_ttf_hints(font) : {}

  # Build CharStrings from outlines
  charstrings = outlines.map do |outline|
    builder = Tables::Cff::CharStringBuilder.new
    if outline.empty?
      builder.build_empty
    else
      builder.build(outline)
    end
  end

  # Apply subroutine optimization if enabled
  local_subrs = []
  if @optimize_cff
    begin
      charstrings, local_subrs = optimize_charstrings(charstrings)
    rescue StandardError => e
      # If optimization fails, fall back to unoptimized CharStrings
      warn "CFF optimization failed: #{e.message}, using unoptimized CharStrings"
      local_subrs = []
    end
  end

  # Build CFF table from charstrings and local subrs
  cff_data = build_cff_table(charstrings, local_subrs, font)

  # Copy all tables except glyf/loca
  tables = copy_tables(font, %w[glyf loca])

  # Add CFF table
  tables["CFF "] = cff_data

  # Update maxp table for CFF
  tables["maxp"] = update_maxp_for_cff(font, outlines.length)

  # Update head table for CFF
  tables["head"] = update_head_for_cff(font)

  # Convert and apply hints if preservation is enabled
  if @preserve_hints && hints_per_glyph.any?
    # Extract font-level hints separately
    hint_set = extract_ttf_hint_set(font)

    unless hint_set.empty?
      # Convert TrueType hints to PostScript format
      converter = Hints::HintConverter.new
      ps_hint_set = converter.convert_hint_set(hint_set, :postscript)

      # Apply PostScript hints (validation mode - CFF modification pending)
      applier = Hints::PostScriptHintApplier.new
      tables = applier.apply(ps_hint_set, tables)
    end
  end

  tables
end

#convert_variations(font, target_format) ⇒ Hash?

Convert variation data during outline conversion

Parameters:

Returns:

  • (Hash, nil)

    Converted variation data or nil



444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
# File 'lib/fontisan/converters/outline_converter.rb', line 444

def convert_variations(font, target_format)
  return nil unless @preserve_variations
  return nil unless variable_font?(font)

  fvar = font.table("fvar")
  return nil unless fvar

  axes = fvar.axes
  converter = Variation::Converter.new(font, axes)

  # Get glyph count
  maxp = font.table("maxp")
  return nil unless maxp

  glyph_count = maxp.num_glyphs

  # Convert variation data for each glyph
  variation_data = {}
  glyph_count.times do |glyph_id|
    source_format = detect_format(font)

    data = case [source_format, target_format]
           when %i[ttf otf], %i[ttf cff2]
             # gvar → blend
             converter.gvar_to_blend(glyph_id)
           when %i[otf ttf], %i[cff2 ttf]
             # blend → gvar
             converter.blend_to_gvar(glyph_id)
           end

    variation_data[glyph_id] = data if data
  end

  variation_data.empty? ? nil : variation_data
end

#copy_tables(font, exclude_tags = []) ⇒ Hash<String, String>

Copy non-outline tables from source to target

Parameters:

Returns:

  • (Hash<String, String>)

    Copied tables



303
304
305
306
307
308
309
310
311
312
313
# File 'lib/fontisan/converters/outline_converter.rb', line 303

def copy_tables(font, exclude_tags = [])
  tables = {}

  font.table_data.each do |tag, data|
    next if exclude_tags.include?(tag)

    tables[tag] = data if data
  end

  tables
end

#decompose_composite_glyphs(_font) ⇒ Object

Decompose composite glyphs in font

Parameters:



698
699
700
701
702
703
704
705
706
707
# File 'lib/fontisan/converters/outline_converter.rb', line 698

def decompose_composite_glyphs(_font)
  # Placeholder: Decompose composite glyphs
  # A full implementation would:
  # 1. Identify composite glyphs (TrueType: flags & 0x0020, CFF: seac operator)
  # 2. Extract component outlines
  # 3. Merge into single glyph
  #
  # For now, this is a no-op placeholder
  nil
end

#detect_format(font) ⇒ Symbol

Detect font format from tables

Parameters:

Returns:

  • (Symbol)

    Format (:ttf, :otf, or :cff2)

Raises:

  • (Error)

    If format cannot be detected



485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
# File 'lib/fontisan/converters/outline_converter.rb', line 485

def detect_format(font)
  # Check for CFF2 table first (OpenType variable fonts with CFF2 outlines)
  if font.has_table?("CFF2")
    :cff2
  # Check for CFF table (OpenType/CFF)
  elsif font.has_table?("CFF ")
    :otf
  # Check for glyf table (TrueType)
  elsif font.has_table?("glyf")
    :ttf
  else
    raise Fontisan::Error,
          "Cannot detect font format: missing outline tables (CFF2, CFF, or glyf)"
  end
end

#detect_target_format(font) ⇒ Symbol

Detect target format as opposite of source

Parameters:

Returns:

  • (Symbol)

    Target format



505
506
507
508
509
510
511
512
513
514
515
# File 'lib/fontisan/converters/outline_converter.rb', line 505

def detect_target_format(font)
  source = detect_format(font)
  case source
  when :ttf
    :otf
  when :cff2
    :ttf
  else
    :ttf
  end
end

#extract_cff_hint_set(font) ⇒ HintSet

Extract complete PostScript hint set from font

Parameters:

Returns:

  • (HintSet)

    Complete hint set



647
648
649
650
651
652
653
# File 'lib/fontisan/converters/outline_converter.rb', line 647

def extract_cff_hint_set(font)
  extractor = Hints::PostScriptHintExtractor.new
  extractor.extract_from_font(font)
rescue StandardError => e
  warn "Failed to extract PostScript hint set: #{e.message}"
  Models::HintSet.new(format: :postscript)
end

#extract_cff_hints(font) ⇒ Hash<Integer, Array<Hint>>

Extract hints from CFF font

Parameters:

Returns:

  • (Hash<Integer, Array<Hint>>)

    Map of glyph ID to hints



605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
# File 'lib/fontisan/converters/outline_converter.rb', line 605

def extract_cff_hints(font)
  hints_per_glyph = {}
  extractor = Hints::PostScriptHintExtractor.new

  # Get CFF table
  cff = font.table("CFF ")
  return {} unless cff

  # Get number of glyphs
  num_glyphs = cff.glyph_count

  # Extract hints from each CharString
  num_glyphs.times do |glyph_id|
    charstring = cff.charstring_for_glyph(glyph_id)
    next if charstring.nil?

    hints = extractor.extract(charstring)
    hints_per_glyph[glyph_id] = hints if hints.any?
  end

  hints_per_glyph
rescue StandardError => e
  warn "Failed to extract CFF hints: #{e.message}"
  {}
end

#extract_conversion_options(options) ⇒ ConversionOptions?

Extract ConversionOptions from options hash

Parameters:

Returns:



667
668
669
670
671
# File 'lib/fontisan/converters/outline_converter.rb', line 667

def extract_conversion_options(options)
  return options if options.is_a?(ConversionOptions)

  options[:options] if options.is_a?(Hash)
end

#extract_ttf_hint_set(font) ⇒ HintSet

Extract complete TrueType hint set from font

Parameters:

Returns:

  • (HintSet)

    Complete hint set



635
636
637
638
639
640
641
# File 'lib/fontisan/converters/outline_converter.rb', line 635

def extract_ttf_hint_set(font)
  extractor = Hints::TrueTypeHintExtractor.new
  extractor.extract_from_font(font)
rescue StandardError => e
  warn "Failed to extract TrueType hint set: #{e.message}"
  Models::HintSet.new(format: :truetype)
end

#extract_ttf_hints(font) ⇒ Hash<Integer, Array<Hint>>

Extract hints from TrueType font

Parameters:

Returns:

  • (Hash<Integer, Array<Hint>>)

    Map of glyph ID to hints



573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
# File 'lib/fontisan/converters/outline_converter.rb', line 573

def extract_ttf_hints(font)
  hints_per_glyph = {}
  extractor = Hints::TrueTypeHintExtractor.new

  # Get required tables
  head = font.table("head")
  maxp = font.table("maxp")
  loca = font.table("loca")
  glyf = font.table("glyf")

  # Parse loca with context
  loca.parse_with_context(head.index_to_loc_format, maxp.num_glyphs)

  # Extract hints from each glyph
  maxp.num_glyphs.times do |glyph_id|
    glyph = glyf.glyph_for(glyph_id, loca, head)
    next if glyph.nil? || glyph.empty?

    hints = extractor.extract(glyph)
    hints_per_glyph[glyph_id] = hints if hints.any?
  end

  hints_per_glyph
rescue StandardError => e
  warn "Failed to extract TrueType hints: #{e.message}"
  {}
end

#generate_static_instance(font, source_format, target_format) ⇒ Hash<String, String>

Generate static instance from variable font

Parameters:

  • font (TrueTypeFont, OpenTypeFont)

    Variable font

  • source_format (Symbol)

    Source format

  • target_format (Symbol)

    Target format

Returns:

  • (Hash<String, String>)

    Static font tables



410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
# File 'lib/fontisan/converters/outline_converter.rb', line 410

def generate_static_instance(font, source_format, target_format)
  # Generate instance at specified coordinates
  fvar = font.table("fvar")
  fvar ? fvar.axes : []

  generator = Variation::InstanceGenerator.new(font,
                                               @instance_coordinates)
  instance_tables = generator.generate

  # If target format differs from source, convert outlines
  if source_format == target_format
    instance_tables
  else
    # Create temporary font with instance tables
    temp_font = font.class.new
    temp_font.instance_variable_set(:@table_data, instance_tables)

    # Convert outline format
    case [source_format, target_format]
    when %i[ttf otf]
      convert_ttf_to_otf(temp_font, @options)
    when %i[otf ttf], %i[cff2 ttf]
      convert_otf_to_ttf(temp_font)
    else
      instance_tables
    end
  end
end

#interpret_opentype_features(_font) ⇒ Object

Interpret OpenType layout features

Parameters:



712
713
714
715
716
717
718
719
720
721
# File 'lib/fontisan/converters/outline_converter.rb', line 712

def interpret_opentype_features(_font)
  # Placeholder: Interpret GSUB/GPOS tables
  # A full implementation would:
  # 1. Parse GSUB table for substitution rules
  # 2. Parse GPOS table for positioning rules
  # 3. Store interpreted features for later use
  #
  # For now, this is a no-op placeholder
  nil
end

#otf_to_ttfHash<String, String>

Convert OpenType/CFF font to TrueType

Returns:

  • (Hash<String, String>)

    Target tables

Raises:



250
251
252
253
254
# File 'lib/fontisan/converters/outline_converter.rb', line 250

def otf_to_ttf
  raise Fontisan::Error, "No font loaded" unless @font

  convert_otf_to_ttf(@font)
end

#supported_conversionsArray<Array<Symbol>>

Get supported conversions

Returns:

  • (Array<Array<Symbol>>)

    Supported conversion pairs



259
260
261
262
263
264
265
266
# File 'lib/fontisan/converters/outline_converter.rb', line 259

def supported_conversions
  [
    %i[ttf otf],
    %i[otf ttf],
    %i[cff2 ttf],
    %i[ttf cff2],
  ]
end

#ttf_to_otfHash<String, String>

Convert TrueType font to OpenType/CFF

Returns:

  • (Hash<String, String>)

    Target tables

Raises:



241
242
243
244
245
# File 'lib/fontisan/converters/outline_converter.rb', line 241

def ttf_to_otf
  raise Fontisan::Error, "No font loaded" unless @font

  convert_ttf_to_otf(@font)
end

#update_head_for_cff(font) ⇒ String

Update head table for CFF format

Parameters:

Returns:

  • (String)

    Updated head table binary data



377
378
379
380
381
382
383
384
385
386
387
# File 'lib/fontisan/converters/outline_converter.rb', line 377

def update_head_for_cff(font)
  font.table("head")
  head_data = font.table_data["head"].dup

  # For CFF fonts, indexToLocFormat is not relevant
  # but we'll set it to 0 for consistency
  # indexToLocFormat is at offset 50 (2 bytes)
  head_data[50, 2] = [0].pack("n")

  head_data
end

#update_head_for_truetype(font, loca_format) ⇒ String

Update head table for TrueType format

Parameters:

  • font (OpenTypeFont)

    Source font

  • loca_format (Integer)

    Loca format (0=short, 1=long)

Returns:

  • (String)

    Updated head table binary data



394
395
396
397
398
399
400
401
402
# File 'lib/fontisan/converters/outline_converter.rb', line 394

def update_head_for_truetype(font, loca_format)
  font.table("head")
  head_data = font.table_data["head"].dup

  # Set indexToLocFormat at offset 50 (2 bytes)
  head_data[50, 2] = [loca_format].pack("n")

  head_data
end

#update_maxp_for_cff(_font, num_glyphs) ⇒ String

Update maxp table for CFF format

Parameters:

  • font (TrueTypeFont)

    Source font

  • num_glyphs (Integer)

    Number of glyphs

Returns:

  • (String)

    Updated maxp table binary data



320
321
322
323
324
# File 'lib/fontisan/converters/outline_converter.rb', line 320

def update_maxp_for_cff(_font, num_glyphs)
  # CFF uses maxp version 0.5 (0x00005000)
  # Structure: version (4 bytes) + numGlyphs (2 bytes)
  [Tables::Maxp::VERSION_0_5, num_glyphs].pack("Nn")
end

#update_maxp_for_truetype(font, outlines, _loca_format) ⇒ String

Update maxp table for TrueType format

Parameters:

  • font (OpenTypeFont)

    Source font

  • outlines (Array<Outline>)

    Glyph outlines

  • loca_format (Integer)

    Loca format (0 or 1)

Returns:

  • (String)

    Updated maxp table binary data



332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'lib/fontisan/converters/outline_converter.rb', line 332

def update_maxp_for_truetype(font, outlines, _loca_format)
  # Get source maxp
  font.table("maxp")
  num_glyphs = outlines.length

  # Calculate statistics from outlines
  max_points = 0
  max_contours = 0

  outlines.each do |outline|
    next if outline.empty?

    contours = outline.to_truetype_contours
    max_contours = [max_contours, contours.length].max

    contours.each do |contour|
      max_points = [max_points, contour.length].max
    end
  end

  # Build maxp v1.0 table
  # We'll use conservative defaults for instruction-related fields
  [
    Tables::Maxp::VERSION_1_0, # version
    num_glyphs,                  # numGlyphs
    max_points,                  # maxPoints
    max_contours,                # maxContours
    0,                           # maxCompositePoints
    0,                           # maxCompositeContours
    2,                           # maxZones
    0,                           # maxTwilightPoints
    0,                           # maxStorage
    0,                           # maxFunctionDefs
    0,                           # maxInstructionDefs
    0,                           # maxStackElements
    0,                           # maxSizeOfInstructions
    0,                           # maxComponentElements
    0,                           # maxComponentDepth
  ].pack("Nnnnnnnnnnnnnnn")
end

#validate(font, target_format) ⇒ Boolean

Validate font for conversion

Parameters:

Returns:

  • (Boolean)

    True if valid

Raises:

  • (ArgumentError)

    If font is invalid

  • (Error)

    If conversion is not supported



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/fontisan/converters/outline_converter.rb', line 275

def validate(font, target_format)
  raise ArgumentError, "Font cannot be nil" if font.nil?

  unless font.respond_to?(:tables)
    raise ArgumentError, "Font must respond to :tables"
  end

  unless font.respond_to?(:table)
    raise ArgumentError, "Font must respond to :table"
  end

  source_format = detect_format(font)
  unless supports?(source_format, target_format)
    raise Fontisan::Error,
          "Conversion #{source_format}#{target_format} not supported"
  end

  # Check that source font has required tables
  validate_source_tables(font, source_format)

  true
end

#validate_source_tables(font, format) ⇒ Object

Validate source font has required tables

Parameters:

Raises:

  • (Error)

    If required tables are missing



522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
# File 'lib/fontisan/converters/outline_converter.rb', line 522

def validate_source_tables(font, format)
  case format
  when :ttf
    unless font.has_table?("glyf") && font.has_table?("loca")
      raise Fontisan::MissingTableError,
            "TrueType font missing required glyf or loca table"
    end
    # Also verify tables can actually be loaded
    unless font.table("glyf") && font.table("loca")
      raise Fontisan::MissingTableError,
            "TrueType font missing required glyf or loca table"
    end
  when :cff2
    unless font.has_table?("CFF2")
      raise Fontisan::MissingTableError,
            "CFF2 font missing required CFF2 table"
    end
    unless font.table("CFF2")
      raise Fontisan::MissingTableError,
            "CFF2 font missing required CFF2 table"
    end
  when :otf
    unless font.has_table?("CFF ") || font.has_table?("CFF2")
      raise Fontisan::MissingTableError,
            "OpenType font missing required CFF or CFF2 table"
    end
    # Verify at least one can be loaded
    unless font.table("CFF ") || font.table("CFF2")
      raise Fontisan::MissingTableError,
            "OpenType font missing required CFF or CFF2 table"
    end
  end

  # Common required tables
  %w[head hhea maxp].each do |tag|
    unless font.has_table?(tag)
      raise Fontisan::MissingTableError,
            "Font missing required #{tag} table"
    end
    # Verify table can actually be loaded
    unless font.table(tag)
      raise Fontisan::MissingTableError,
            "Font missing required #{tag} table"
    end
  end
end

#variable_font?(font) ⇒ Boolean

Check if font is a variable font

Parameters:

Returns:

  • (Boolean)

    True if font has variation tables



659
660
661
# File 'lib/fontisan/converters/outline_converter.rb', line 659

def variable_font?(font)
  font.has_table?("fvar")
end