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)



43
44
45
46
47
48
49
50
51
52
53
# File 'lib/fontisan/variation/instance_generator.rb', line 43

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:



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

def context
  @context
end

#coordinatesHash<String, Float> (readonly)

Returns Design space coordinates.

Returns:

  • (Hash<String, Float>)

    Design space coordinates



32
33
34
# File 'lib/fontisan/variation/instance_generator.rb', line 32

def coordinates
  @coordinates
end

#fontTrueTypeFont, OpenTypeFont (readonly)

Returns Variable font.

Returns:



29
30
31
# File 'lib/fontisan/variation/instance_generator.rb', line 29

def font
  @font
end

Instance Method Details

#apply_cff2_blend(_tables) ⇒ Object

Apply CFF2 blend operators

Parameters:

  • tables (Hash<String, String>)

    Font tables



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/fontisan/variation/instance_generator.rb', line 150

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



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/fontisan/variation/instance_generator.rb', line 125

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



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/fontisan/variation/instance_generator.rb', line 102

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



199
200
201
202
# File 'lib/fontisan/variation/instance_generator.rb', line 199

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



185
186
187
188
189
190
191
192
193
194
# File 'lib/fontisan/variation/instance_generator.rb', line 185

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



215
216
217
218
# File 'lib/fontisan/variation/instance_generator.rb', line 215

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



207
208
209
210
# File 'lib/fontisan/variation/instance_generator.rb', line 207

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



175
176
177
178
179
180
# File 'lib/fontisan/variation/instance_generator.rb', line 175

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



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/fontisan/variation/instance_generator.rb', line 60

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



86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/fontisan/variation/instance_generator.rb', line 86

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



244
245
246
247
# File 'lib/fontisan/variation/instance_generator.rb', line 244

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



234
235
236
# File 'lib/fontisan/variation/instance_generator.rb', line 234

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



223
224
225
226
# File 'lib/fontisan/variation/instance_generator.rb', line 223

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