Class: Fontisan::Variation::InstanceGenerator

Inherits:
Object
  • Object
show all
Includes:
TableAccessor
Defined in:
lib/fontisan/variation/instance_generator.rb

Overview

Generates static font instances from variable fonts

This class creates static font instances by applying variation deltas at specific design space coordinates. It supports both TrueType (gvar) and PostScript (CFF2) variable fonts.

Process:

  1. Extract variation data (axes, deltas, regions)

  2. Calculate interpolation scalars for given coordinates

  3. Apply deltas to outlines (gvar or CFF2)

  4. Apply deltas to metrics (HVAR, VVAR, MVAR)

  5. Remove variation tables to create static font

Examples:

Generating an instance at specific coordinates

generator = Fontisan::Variation::InstanceGenerator.new(font, { "wght" => 700.0 })
instance_tables = generator.generate

Generating a named instance

generator = Fontisan::Variation::InstanceGenerator.new(font)
instance_tables = generator.generate_named_instance(0)

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from TableAccessor

#clear_variation_cache, #clear_variation_table, #has_variation_table?, #require_variation_table, #variation_table

Constructor Details

#initialize(font, coordinates = {}, options = {}) ⇒ InstanceGenerator

Initialize generator with font and optional coordinates

Parameters:

  • font (TrueTypeFont, OpenTypeFont)

    Variable font

  • coordinates (Hash<String, Float>) (defaults to: {})

    Design space coordinates (axis tag => value)

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

    Options

Options Hash (options):

  • :skip_validation (Boolean)

    Skip context validation (default: false)



49
50
51
52
53
54
55
56
57
58
59
# File 'lib/fontisan/variation/instance_generator.rb', line 49

def initialize(font, coordinates = {}, options = {})
  @font = font
  @coordinates = coordinates

  # Initialize variation context
  @context = VariationContext.new(@font)
  @context.validate! unless options[:skip_validation]

  # Initialize table cache for lazy loading
  @variation_tables = {}
end

Instance Attribute Details

#contextVariationContext (readonly)

Returns Variation context.

Returns:



41
42
43
# File 'lib/fontisan/variation/instance_generator.rb', line 41

def context
  @context
end

#coordinatesHash<String, Float> (readonly)

Returns Design space coordinates.

Returns:

  • (Hash<String, Float>)

    Design space coordinates



38
39
40
# File 'lib/fontisan/variation/instance_generator.rb', line 38

def coordinates
  @coordinates
end

#fontTrueTypeFont, OpenTypeFont (readonly)

Returns Variable font.

Returns:



35
36
37
# File 'lib/fontisan/variation/instance_generator.rb', line 35

def font
  @font
end

Instance Method Details

#apply_cff2_blend(_tables) ⇒ Object

Apply CFF2 blend operators

Parameters:

  • tables (Hash<String, String>)

    Font tables



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/fontisan/variation/instance_generator.rb', line 156

def apply_cff2_blend(_tables)
  cff2 = variation_table("CFF2")
  return unless cff2

  # Set number of axes for CFF2
  cff2.num_axes = @context.axis_count

  # Process each glyph's CharString
  glyph_count = cff2.glyph_count
  return if glyph_count.zero?

  # Calculate variation scalars once
  calculate_variation_scalars

  # Apply blend to each glyph
  # This is a placeholder - full implementation would:
  # 1. Parse CharString with blend operators
  # 2. Apply scalars to blend operands
  # 3. Rebuild CharStrings without blend operators
  # 4. Update CFF2 table
end

#apply_glyph_deltas(glyph_id, gvar, _glyf) ⇒ Object

Apply deltas to a specific glyph

Parameters:

  • glyph_id (Integer)

    Glyph ID

  • gvar (Gvar)

    Gvar table

  • glyf (Glyf)

    Glyf table



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/fontisan/variation/instance_generator.rb', line 131

def apply_glyph_deltas(glyph_id, gvar, _glyf)
  # Get tuple variations for this glyph
  tuple_data = gvar.glyph_tuple_variations(glyph_id)
  return unless tuple_data

  # Match tuples to current coordinates
  matches = @context.region_matcher.match_tuples(
    coordinates: @coordinates,
    tuples: tuple_data[:tuples],
  )

  nil if matches.empty?

  # Get base glyph outline
  # Apply matched deltas with their scalars
  # This is a placeholder - full implementation would:
  # 1. Parse glyph outline points
  # 2. Parse delta data for each tuple
  # 3. Apply: new_point = base_point + Σ(delta * scalar)
  # 4. Update glyph outline
end

#apply_gvar_deltas(_tables) ⇒ Object

Apply gvar deltas to TrueType outlines

Parameters:

  • tables (Hash<String, String>)

    Font tables



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/fontisan/variation/instance_generator.rb', line 108

def apply_gvar_deltas(_tables)
  gvar = variation_table("gvar")
  glyf = @font.table("glyf")
  return unless gvar && glyf

  # Get glyph count
  maxp = @font.table("maxp")
  glyph_count = maxp ? maxp.num_glyphs : gvar.glyph_count

  # Process each glyph
  glyph_count.times do |glyph_id|
    apply_glyph_deltas(glyph_id, gvar, glyf)
  end

  # Rebuild glyf and loca tables with adjusted outlines
  # This is a placeholder - full implementation would reconstruct tables
end

#apply_hvar_deltas(_tables) ⇒ Object

Apply HVAR deltas to horizontal metrics

Parameters:

  • tables (Hash<String, String>)

    Font tables



205
206
207
208
# File 'lib/fontisan/variation/instance_generator.rb', line 205

def apply_hvar_deltas(_tables)
  adjuster = MetricsAdjuster.new(@font, @context.interpolator)
  adjuster.apply_hvar_deltas(@coordinates)
end

#apply_metrics_deltas(tables) ⇒ Object

Apply metrics variations

Parameters:

  • tables (Hash<String, String>)

    Font tables



191
192
193
194
195
196
197
198
199
200
# File 'lib/fontisan/variation/instance_generator.rb', line 191

def apply_metrics_deltas(tables)
  # Apply HVAR (horizontal metrics)
  apply_hvar_deltas(tables) if has_variation_table?("HVAR")

  # Apply VVAR (vertical metrics)
  apply_vvar_deltas(tables) if has_variation_table?("VVAR")

  # Apply MVAR (font-wide metrics)
  apply_mvar_deltas(tables) if has_variation_table?("MVAR")
end

#apply_mvar_deltas(_tables) ⇒ Object

Apply MVAR deltas to font-wide metrics

Parameters:

  • tables (Hash<String, String>)

    Font tables



221
222
223
224
# File 'lib/fontisan/variation/instance_generator.rb', line 221

def apply_mvar_deltas(_tables)
  adjuster = MetricsAdjuster.new(@font, @context.interpolator)
  adjuster.apply_mvar_deltas(@coordinates)
end

#apply_vvar_deltas(_tables) ⇒ Object

Apply VVAR deltas to vertical metrics

Parameters:

  • tables (Hash<String, String>)

    Font tables



213
214
215
216
# File 'lib/fontisan/variation/instance_generator.rb', line 213

def apply_vvar_deltas(_tables)
  adjuster = MetricsAdjuster.new(@font, @context.interpolator)
  adjuster.apply_vvar_deltas(@coordinates)
end

#calculate_variation_scalarsArray<Float>

Calculate variation scalars for current coordinates

Returns:

  • (Array<Float>)

    Scalars for each axis



181
182
183
184
185
186
# File 'lib/fontisan/variation/instance_generator.rb', line 181

def calculate_variation_scalars
  @context.axes.map do |axis|
    coord = @coordinates[axis.axis_tag] || axis.default_value
    @context.interpolator.normalize_coordinate(coord, axis.axis_tag)
  end
end

#generateHash<String, String>

Generate static font instance

Applies variation deltas and returns static font tables.

Returns:

  • (Hash<String, String>)

    Map of table tags to binary data



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/fontisan/variation/instance_generator.rb', line 66

def generate
  # Start with base font tables
  tables = @font.table_data.dup

  # Determine variation type
  if has_variation_table?("gvar")
    # TrueType outlines with gvar
    apply_gvar_deltas(tables)
  elsif has_variation_table?("CFF2")
    # PostScript outlines with CFF2 blend
    apply_cff2_blend(tables)
  end

  # Apply metrics variations if present
  apply_metrics_deltas(tables) if @context.has_metrics_variations?

  # Remove variation-specific tables to create static font
  remove_variation_tables(tables)

  tables
end

#generate_named_instance(instance_index) ⇒ Hash<String, String>

Generate a named instance

Parameters:

  • instance_index (Integer)

    Index of named instance in fvar table

Returns:

  • (Hash<String, String>)

    Map of table tags to binary data



92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/fontisan/variation/instance_generator.rb', line 92

def generate_named_instance(instance_index)
  # Extract instance coordinates from fvar
  return generate if instance_index.nil? || !@context.fvar

  instances = @context.fvar.instances
  return generate if instance_index >= instances.length

  instance = instances[instance_index]
  @coordinates = build_coordinates_from_instance(instance, @context.axes)

  generate
end

#interpolate_point(base_point, delta_points, scalars) ⇒ Hash

Interpolate a point

Parameters:

  • base_point (Hash)

    Base point with :x and :y

  • delta_points (Array<Hash>)

    Delta points

  • scalars (Array<Float>)

    Region scalars

Returns:

  • (Hash)

    Interpolated point



250
251
252
253
# File 'lib/fontisan/variation/instance_generator.rb', line 250

def interpolate_point(base_point, delta_points, scalars)
  @context.interpolator.interpolate_point(base_point, delta_points,
                                          scalars)
end

#interpolate_value(base_value, deltas, scalars) ⇒ Float

Interpolate a single value

Parameters:

  • base_value (Numeric)

    Base value

  • deltas (Array<Numeric>)

    Delta values

  • scalars (Array<Float>)

    Region scalars

Returns:

  • (Float)

    Interpolated value



240
241
242
# File 'lib/fontisan/variation/instance_generator.rb', line 240

def interpolate_value(base_value, deltas, scalars)
  @context.interpolator.interpolate_value(base_value, deltas, scalars)
end

#remove_variation_tables(tables) ⇒ Object

Remove variation tables from static font

Parameters:

  • tables (Hash<String, String>)

    Font tables



229
230
231
232
# File 'lib/fontisan/variation/instance_generator.rb', line 229

def remove_variation_tables(tables)
  variation_tables = %w[fvar gvar cvar HVAR VVAR MVAR avar STAT]
  variation_tables.each { |tag| tables.delete(tag) }
end