Class: Fontisan::Tables::Cff2::RegionMatcher
- Inherits:
-
Object
- Object
- Fontisan::Tables::Cff2::RegionMatcher
- Defined in:
- lib/fontisan/tables/cff2/region_matcher.rb
Overview
Region matcher for calculating variation scalars
Maps design space coordinates to region scalars based on the Variable Store region definitions. Each region defines a range (start, peak, end) for each variation axis.
Scalar Calculation:
-
If coordinate is at peak: scalar = 1.0
-
If coordinate is between start and peak: linear interpolation
-
If coordinate is between peak and end: linear interpolation
-
If coordinate is outside [start, end]: scalar = 0.0
Reference: OpenType Font Variations Overview Reference: Adobe Technical Note #5177 (CFF2)
Instance Attribute Summary collapse
-
#regions ⇒ Array<Hash>
readonly
Regions from Variable Store.
Instance Method Summary collapse
-
#active_regions(coordinates) ⇒ Array<Integer>
Get active regions for coordinates.
-
#axis_count ⇒ Integer
Get number of axes from first region.
-
#calculate_axis_scalar(axis, coordinate) ⇒ Float
Calculate scalar for a single axis.
-
#calculate_region_scalar(region, coordinates) ⇒ Float
Calculate scalar for a single region.
-
#calculate_scalars(coordinates) ⇒ Array<Float>
Calculate scalars for all regions at given coordinates.
-
#coordinates_active?(coordinates) ⇒ Boolean
Check if coordinates are within any region.
-
#has_regions? ⇒ Boolean
Check if matcher has regions.
-
#initialize(regions) ⇒ RegionMatcher
constructor
Initialize matcher with regions.
-
#scalar_for_region(region_index, coordinates) ⇒ Float?
Get scalar for specific region index.
-
#validate ⇒ Array<String>
Validate region structure.
Constructor Details
#initialize(regions) ⇒ RegionMatcher
Initialize matcher with regions
31 32 33 |
# File 'lib/fontisan/tables/cff2/region_matcher.rb', line 31 def initialize(regions) @regions = regions end |
Instance Attribute Details
#regions ⇒ Array<Hash> (readonly)
Returns Regions from Variable Store.
26 27 28 |
# File 'lib/fontisan/tables/cff2/region_matcher.rb', line 26 def regions @regions end |
Instance Method Details
#active_regions(coordinates) ⇒ Array<Integer>
Get active regions for coordinates
Returns indices of regions that have non-zero scalars
121 122 123 124 125 |
# File 'lib/fontisan/tables/cff2/region_matcher.rb', line 121 def active_regions(coordinates) scalars = calculate_scalars(coordinates) scalars.each_with_index.select { |scalar, _| scalar.positive? } .map(&:last) end |
#axis_count ⇒ Integer
Get number of axes from first region
185 186 187 188 189 |
# File 'lib/fontisan/tables/cff2/region_matcher.rb', line 185 def axis_count return 0 if @regions.empty? @regions.first[:axis_count] || @regions.first[:axes]&.size || 0 end |
#calculate_axis_scalar(axis, coordinate) ⇒ Float
Calculate scalar for a single axis
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 |
# File 'lib/fontisan/tables/cff2/region_matcher.rb', line 78 def calculate_axis_scalar(axis, coordinate) start_coord = axis[:start_coord] peak_coord = axis[:peak_coord] end_coord = axis[:end_coord] # Outside the region return 0.0 if coordinate < start_coord || coordinate > end_coord # At or beyond peak return 1.0 if coordinate == peak_coord # Between start and peak if coordinate < peak_coord # Linear interpolation: (coord - start) / (peak - start) range = peak_coord - start_coord return 1.0 if range.zero? # Avoid division by zero (coordinate - start_coord) / range else # Between peak and end # Linear interpolation: (end - coord) / (end - peak) range = end_coord - peak_coord return 1.0 if range.zero? # Avoid division by zero (end_coord - coordinate) / range end end |
#calculate_region_scalar(region, coordinates) ⇒ Float
Calculate scalar for a single region
The scalar is the product of scalars for all axes in the region. If any axis has scalar 0.0, the entire region scalar is 0.0.
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/fontisan/tables/cff2/region_matcher.rb', line 56 def calculate_region_scalar(region, coordinates) axes = region[:axes] # Multiply scalars for all axes scalar = 1.0 axes.each_with_index do |axis, i| coord = coordinates[i] || 0.0 axis_scalar = calculate_axis_scalar(axis, coord) scalar *= axis_scalar # Early exit if any axis is out of range return 0.0 if axis_scalar.zero? end scalar end |
#calculate_scalars(coordinates) ⇒ Array<Float>
Calculate scalars for all regions at given coordinates
Coordinates are normalized values in the range [-1.0, 1.0] where 0.0 represents the default/regular style.
42 43 44 45 46 |
# File 'lib/fontisan/tables/cff2/region_matcher.rb', line 42 def calculate_scalars(coordinates) @regions.map do |region| calculate_region_scalar(region, coordinates) end end |
#coordinates_active?(coordinates) ⇒ Boolean
Check if coordinates are within any region
110 111 112 113 |
# File 'lib/fontisan/tables/cff2/region_matcher.rb', line 110 def coordinates_active?(coordinates) scalars = calculate_scalars(coordinates) scalars.any?(&:positive?) end |
#has_regions? ⇒ Boolean
Check if matcher has regions
194 195 196 |
# File 'lib/fontisan/tables/cff2/region_matcher.rb', line 194 def has_regions? !@regions.empty? end |
#scalar_for_region(region_index, coordinates) ⇒ Float?
Get scalar for specific region index
132 133 134 135 136 137 |
# File 'lib/fontisan/tables/cff2/region_matcher.rb', line 132 def scalar_for_region(region_index, coordinates) return nil if region_index >= @regions.size region = @regions[region_index] calculate_region_scalar(region, coordinates) end |
#validate ⇒ Array<String>
Validate region structure
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 |
# File 'lib/fontisan/tables/cff2/region_matcher.rb', line 142 def validate errors = [] @regions.each_with_index do |region, i| axes = region[:axes] unless axes.is_a?(Array) errors << "Region #{i} has invalid axes (not an array)" next end axes.each_with_index do |axis, j| unless axis.is_a?(Hash) errors << "Region #{i}, axis #{j} is not a hash" next end # Check required keys %i[start_coord peak_coord end_coord].each do |key| unless axis.key?(key) errors << "Region #{i}, axis #{j} missing #{key}" end end # Validate coordinate ordering if axis[:start_coord] && axis[:peak_coord] && axis[:end_coord] start = axis[:start_coord] peak = axis[:peak_coord] ending = axis[:end_coord] unless start <= peak && peak <= ending errors << "Region #{i}, axis #{j} has invalid ordering: " \ "#{start} > #{peak} > #{ending}" end end end end errors end |