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`](lib/fontisan/converters/outline_converter.rb) handles conversion between TrueType (glyf/loca) and CFF outline formats. This involves:

  • Extracting glyph outlines from source format

  • Converting to universal [‘Outline`](lib/fontisan/models/outline.rb) 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

#supports?

Constructor Details

#initializeOutlineConverter

Initialize converter



80
81
82
# File 'lib/fontisan/converters/outline_converter.rb', line 80

def initialize
  @font = nil
end

Instance Attribute Details

#fontTrueTypeFont, OpenTypeFont (readonly)

Returns Source font.

Returns:



77
78
79
# File 'lib/fontisan/converters/outline_converter.rb', line 77

def font
  @font
end

Instance Method Details

#apply_opening_options(font, conv_options) ⇒ Object

Apply opening options to source font

Parameters:



696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
# File 'lib/fontisan/converters/outline_converter.rb', line 696

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



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
133
134
135
136
137
138
139
140
141
# File 'lib/fontisan/converters/outline_converter.rb', line 96

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



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/fontisan/converters/outline_converter.rb', line 215

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



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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/fontisan/converters/outline_converter.rb', line 148

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



463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
# File 'lib/fontisan/converters/outline_converter.rb', line 463

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



322
323
324
325
326
327
328
329
330
331
332
# File 'lib/fontisan/converters/outline_converter.rb', line 322

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:



717
718
719
720
721
722
723
724
725
726
# File 'lib/fontisan/converters/outline_converter.rb', line 717

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



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

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



524
525
526
527
528
529
530
531
532
533
534
# File 'lib/fontisan/converters/outline_converter.rb', line 524

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



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

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



624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
# File 'lib/fontisan/converters/outline_converter.rb', line 624

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:



686
687
688
689
690
# File 'lib/fontisan/converters/outline_converter.rb', line 686

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



654
655
656
657
658
659
660
# File 'lib/fontisan/converters/outline_converter.rb', line 654

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



592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
# File 'lib/fontisan/converters/outline_converter.rb', line 592

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



429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
# File 'lib/fontisan/converters/outline_converter.rb', line 429

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:



731
732
733
734
735
736
737
738
739
740
# File 'lib/fontisan/converters/outline_converter.rb', line 731

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:



269
270
271
272
273
# File 'lib/fontisan/converters/outline_converter.rb', line 269

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



278
279
280
281
282
283
284
285
# File 'lib/fontisan/converters/outline_converter.rb', line 278

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:



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

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



396
397
398
399
400
401
402
403
404
405
406
# File 'lib/fontisan/converters/outline_converter.rb', line 396

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



413
414
415
416
417
418
419
420
421
# File 'lib/fontisan/converters/outline_converter.rb', line 413

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



339
340
341
342
343
# File 'lib/fontisan/converters/outline_converter.rb', line 339

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



351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
# File 'lib/fontisan/converters/outline_converter.rb', line 351

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



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/fontisan/converters/outline_converter.rb', line 294

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



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
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
# File 'lib/fontisan/converters/outline_converter.rb', line 541

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



678
679
680
# File 'lib/fontisan/converters/outline_converter.rb', line 678

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