Class: Fontisan::Tables::Cff2::VariationDataExtractor

Inherits:
Object
  • Object
show all
Defined in:
lib/fontisan/tables/cff2/variation_data_extractor.rb

Overview

Variation data extractor for CFF2 Variable Store

Extracts regions and deltas from the Variable Store and provides utilities for working with variation data.

Reference: OpenType spec - Item Variation Store Reference: Adobe Technical Note #5177 (CFF2)

Examples:

Extracting variation data

extractor = VariationDataExtractor.new(variable_store)
regions = extractor.regions
deltas = extractor.deltas_for_item(item_index)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(variable_store) ⇒ VariationDataExtractor

Initialize extractor with Variable Store data

Parameters:

  • variable_store (Hash)

    Variable Store from CFF2TableReader



31
32
33
34
35
# File 'lib/fontisan/tables/cff2/variation_data_extractor.rb', line 31

def initialize(variable_store)
  @variable_store = variable_store
  @regions = variable_store[:regions] || []
  @item_variation_data = variable_store[:item_variation_data] || []
end

Instance Attribute Details

#item_variation_dataArray<Hash> (readonly)

Returns Item variation data.

Returns:

  • (Array<Hash>)

    Item variation data



26
27
28
# File 'lib/fontisan/tables/cff2/variation_data_extractor.rb', line 26

def item_variation_data
  @item_variation_data
end

#regionsArray<Hash> (readonly)

Returns Extracted regions.

Returns:

  • (Array<Hash>)

    Extracted regions



23
24
25
# File 'lib/fontisan/tables/cff2/variation_data_extractor.rb', line 23

def regions
  @regions
end

#variable_storeHash (readonly)

Returns Variable Store data.

Returns:

  • (Hash)

    Variable Store data



20
21
22
# File 'lib/fontisan/tables/cff2/variation_data_extractor.rb', line 20

def variable_store
  @variable_store
end

Instance Method Details

#all_deltas(data_index: 0) ⇒ Array<Array<Integer>>

Get all deltas for all items

Parameters:

  • data_index (Integer) (defaults to: 0)

    Item variation data index (default 0)

Returns:

  • (Array<Array<Integer>>)

    Array of delta sets



75
76
77
78
79
# File 'lib/fontisan/tables/cff2/variation_data_extractor.rb', line 75

def all_deltas(data_index: 0)
  return [] if data_index >= @item_variation_data.size

  @item_variation_data[data_index][:delta_sets] || []
end

#axis_countInteger

Get number of axes from first region

Returns:

  • (Integer)

    Number of axes



127
128
129
130
131
# File 'lib/fontisan/tables/cff2/variation_data_extractor.rb', line 127

def axis_count
  return 0 if @regions.empty?

  @regions.first[:axis_count] || 0
end

#blend_value(item_index, base_value, scalars, data_index: 0) ⇒ Float

Calculate blended value for an item at specific coordinates

Parameters:

  • item_index (Integer)

    Item index

  • base_value (Numeric)

    Base value to blend

  • scalars (Array<Float>)

    Region scalars for each region

  • data_index (Integer) (defaults to: 0)

    Item variation data index (default 0)

Returns:

  • (Float)

    Blended value



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/fontisan/tables/cff2/variation_data_extractor.rb', line 88

def blend_value(item_index, base_value, scalars, data_index: 0)
  deltas = deltas_for_item(item_index, data_index: data_index)
  return base_value.to_f unless deltas

  indices = region_indices(data_index: data_index)

  # Apply blend: result = base + Σ(delta[i] * scalar[region_index[i]])
  result = base_value.to_f
  deltas.each_with_index do |delta, i|
    region_index = indices[i]
    next unless region_index

    scalar = scalars[region_index] || 0.0
    result += delta.to_f * scalar
  end

  result
end

#deltas_for_item(item_index, data_index: 0) ⇒ Array<Integer>?

Get deltas for a specific item

Parameters:

  • item_index (Integer)

    Item index

  • data_index (Integer) (defaults to: 0)

    Item variation data index (default 0)

Returns:

  • (Array<Integer>, nil)

    Deltas for the item, or nil if not found



42
43
44
45
46
47
48
49
# File 'lib/fontisan/tables/cff2/variation_data_extractor.rb', line 42

def deltas_for_item(item_index, data_index: 0)
  return nil if data_index >= @item_variation_data.size

  item_data = @item_variation_data[data_index]
  return nil if item_index >= item_data[:delta_sets].size

  item_data[:delta_sets][item_index]
end

#has_data?Boolean

Check if Variable Store has data

Returns:

  • (Boolean)

    True if Variable Store contains data



136
137
138
# File 'lib/fontisan/tables/cff2/variation_data_extractor.rb', line 136

def has_data?
  !@regions.empty? && !@item_variation_data.empty?
end

#item_count(data_index: 0) ⇒ Integer

Get number of items in item variation data

Parameters:

  • data_index (Integer) (defaults to: 0)

    Item variation data index (default 0)

Returns:

  • (Integer)

    Number of items



65
66
67
68
69
# File 'lib/fontisan/tables/cff2/variation_data_extractor.rb', line 65

def item_count(data_index: 0)
  return 0 if data_index >= @item_variation_data.size

  @item_variation_data[data_index][:item_count] || 0
end

#region(region_index) ⇒ Hash?

Get region by index

Parameters:

  • region_index (Integer)

    Region index

Returns:

  • (Hash, nil)

    Region data or nil if not found



111
112
113
114
115
# File 'lib/fontisan/tables/cff2/variation_data_extractor.rb', line 111

def region(region_index)
  return nil if region_index >= @regions.size

  @regions[region_index]
end

#region_coordinatesArray<Array<Hash>>

Extract all region coordinates as arrays

Useful for debugging and validation

Returns:

  • (Array<Array<Hash>>)

    Array of regions with axis coordinates



145
146
147
148
149
150
151
152
153
154
155
# File 'lib/fontisan/tables/cff2/variation_data_extractor.rb', line 145

def region_coordinates
  @regions.map do |region|
    region[:axes].map do |axis|
      {
        start: axis[:start_coord],
        peak: axis[:peak_coord],
        end: axis[:end_coord],
      }
    end
  end
end

#region_countInteger

Get number of regions

Returns:

  • (Integer)

    Total number of regions



120
121
122
# File 'lib/fontisan/tables/cff2/variation_data_extractor.rb', line 120

def region_count
  @regions.size
end

#region_indices(data_index: 0) ⇒ Array<Integer>

Get region indices for item variation data

Parameters:

  • data_index (Integer) (defaults to: 0)

    Item variation data index (default 0)

Returns:

  • (Array<Integer>)

    Region indices



55
56
57
58
59
# File 'lib/fontisan/tables/cff2/variation_data_extractor.rb', line 55

def region_indices(data_index: 0)
  return [] if data_index >= @item_variation_data.size

  @item_variation_data[data_index][:region_indices] || []
end

#validateArray<String>

Validate Variable Store structure

Returns:

  • (Array<String>)

    Array of validation errors (empty if valid)



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
# File 'lib/fontisan/tables/cff2/variation_data_extractor.rb', line 160

def validate
  errors = []

  # Check regions consistency
  if @regions.any?
    expected_axes = @regions.first[:axis_count]
    @regions.each_with_index do |region, i|
      unless region[:axis_count] == expected_axes
        errors << "Region #{i} has inconsistent axis_count: " \
                  "#{region[:axis_count]} vs #{expected_axes}"
      end

      unless region[:axes].size == expected_axes
        errors << "Region #{i} has #{region[:axes].size} axes, " \
                  "expected #{expected_axes}"
      end
    end
  end

  # Check item variation data
  @item_variation_data.each_with_index do |item_data, i|
    item_count = item_data[:item_count]
    delta_sets = item_data[:delta_sets]
    region_indices = item_data[:region_indices]

    unless delta_sets.size == item_count
      errors << "Item variation data #{i} has #{delta_sets.size} " \
                "delta sets, expected #{item_count}"
    end

    # Check each delta set has correct number of deltas
    delta_sets.each_with_index do |deltas, j|
      unless deltas.size == region_indices.size
        errors << "Delta set #{j} in data #{i} has #{deltas.size} " \
                  "deltas, expected #{region_indices.size}"
      end
    end

    # Check region indices are valid
    region_indices.each_with_index do |idx, j|
      if idx >= @regions.size
        errors << "Region index #{idx} at position #{j} in data #{i} " \
                  "exceeds region count #{@regions.size}"
      end
    end
  end

  errors
end