Class: Fontisan::Variation::Optimizer
- Inherits:
-
Object
- Object
- Fontisan::Variation::Optimizer
- Defined in:
- lib/fontisan/variation/optimizer.rb
Overview
Optimizes CFF subroutines for variable fonts
This class analyzes CharStrings in CFF2 variable fonts and optimizes blend operations by extracting common blend sequences into subroutines, deduplicating variation regions, and minimizing ItemVariationStore data.
Optimization strategies:
- Blend pattern extraction - Find repeating blend sequences
- Region deduplication - Merge identical variation regions
- ItemVariationStore optimization - Compact delta storage
- Subroutine reordering - Place frequent blends in low IDs
Instance Attribute Summary collapse
-
#cff2 ⇒ CFF2
readonly
CFF2 table being optimized.
-
#stats ⇒ Hash
readonly
Optimization statistics.
Instance Method Summary collapse
-
#analyze_blend_patterns ⇒ Array<BlendPattern>
Analyze blend patterns in CharStrings.
-
#compact_variation_data(store) ⇒ Object
Compact variation data by removing unused entries.
-
#coords_similar?(c1, c2) ⇒ Boolean
Check if coordinates are similar within threshold.
-
#deduplicate_regions ⇒ Object
Deduplicate variation regions.
-
#extract_blend_sequences(charstring) ⇒ Array<BlendPattern>
Extract blend sequences from CharString.
-
#extract_blend_subroutines(patterns) ⇒ Array<Subroutine>
Extract common blend sequences into subroutines.
-
#find_matching_region(region, unique_regions) ⇒ Integer?
Find matching region within threshold.
-
#initialize(cff2, options = {}) ⇒ Optimizer
constructor
Initialize optimizer.
-
#optimize ⇒ CFF2
Optimize CFF2 table.
-
#optimize_delta_encoding(store) ⇒ Object
Optimize delta encoding for efficiency.
-
#optimize_item_variation_store ⇒ Object
Optimize ItemVariationStore.
-
#rebuild_charstrings(subroutines) ⇒ Object
Rebuild CharStrings with subroutine calls.
-
#regions_match?(r1, r2) ⇒ Boolean
Check if two regions match within threshold.
-
#statistics ⇒ Hash
Get optimization statistics.
Constructor Details
#initialize(cff2, options = {}) ⇒ Optimizer
Initialize optimizer
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/fontisan/variation/optimizer.rb', line 38 def initialize(cff2, = {}) @cff2 = cff2 @options = { max_subrs: 65535, region_threshold: 0.001, deduplicate_regions: true, }.merge() @stats = { original_size: 0, optimized_size: 0, blend_patterns_found: 0, subroutines_created: 0, regions_deduplicated: 0, } end |
Instance Attribute Details
#cff2 ⇒ CFF2 (readonly)
Returns CFF2 table being optimized.
26 27 28 |
# File 'lib/fontisan/variation/optimizer.rb', line 26 def cff2 @cff2 end |
#stats ⇒ Hash (readonly)
Returns Optimization statistics.
29 30 31 |
# File 'lib/fontisan/variation/optimizer.rb', line 29 def stats @stats end |
Instance Method Details
#analyze_blend_patterns ⇒ Array<BlendPattern>
Analyze blend patterns in CharStrings
Scans all CharStrings to find repeating blend operator sequences that can be extracted into subroutines.
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/fontisan/variation/optimizer.rb', line 90 def analyze_blend_patterns patterns = [] glyph_count = @cff2.glyph_count glyph_count.times do |glyph_id| charstring = @cff2.charstring(glyph_id) next unless charstring # Extract blend operator sequences blend_sequences = extract_blend_sequences(charstring) patterns.concat(blend_sequences) end # Group identical patterns grouped = group_patterns(patterns) @stats[:blend_patterns_found] = grouped.length grouped end |
#compact_variation_data(store) ⇒ Object
Compact variation data by removing unused entries
247 248 249 250 251 252 253 254 255 |
# File 'lib/fontisan/variation/optimizer.rb', line 247 def compact_variation_data(store) # Identify used variation indices from CharStrings used_indices = collect_used_variation_indices # Remove unused data store.item_variation_data.each do |data| data.compact_unused(used_indices) end end |
#coords_similar?(c1, c2) ⇒ Boolean
Check if coordinates are similar within threshold
225 226 227 |
# File 'lib/fontisan/variation/optimizer.rb', line 225 def coords_similar?(c1, c2) (c1 - c2).abs <= @options[:region_threshold] end |
#deduplicate_regions ⇒ Object
Deduplicate variation regions
Merges regions that are functionally identical (within threshold).
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 |
# File 'lib/fontisan/variation/optimizer.rb', line 155 def deduplicate_regions return unless @cff2.variation_store regions = @cff2.variation_store.region_list original_count = regions.length # Find duplicate regions unique_regions = [] region_mapping = {} regions.each_with_index do |region, index| # Check if region matches any existing unique region match_index = find_matching_region(region, unique_regions) if match_index region_mapping[index] = match_index else region_mapping[index] = unique_regions.length unique_regions << region end end # Update references in ItemVariationStore update_region_references(region_mapping) if regions.length > unique_regions.length @cff2.variation_store.region_list = unique_regions @stats[:regions_deduplicated] = original_count - unique_regions.length end |
#extract_blend_sequences(charstring) ⇒ Array<BlendPattern>
Extract blend sequences from CharString
114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/fontisan/variation/optimizer.rb', line 114 def extract_blend_sequences(charstring) patterns = [] operators = parse_charstring_operators(charstring) operators.each_with_index do |op, index| next unless blend_operator?(op) # Extract blend and surrounding context pattern = extract_pattern_context(operators, index) patterns << pattern if pattern end patterns end |
#extract_blend_subroutines(patterns) ⇒ Array<Subroutine>
Extract common blend sequences into subroutines
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/fontisan/variation/optimizer.rb', line 133 def extract_blend_subroutines(patterns) # Filter patterns by frequency and savings candidates = patterns.select do |pattern| pattern[:frequency] >= 2 && pattern[:savings].positive? end # Convert patterns to format expected by SubroutineOptimizer # The optimizer expects objects with methods, but we have hashes # For now, just select and order them directly selected = candidates.sort_by { |p| -p[:savings] } .take(@options[:max_subrs]) # Order by frequency for efficient encoding ordered = selected.sort_by { |p| -p[:frequency] } @stats[:subroutines_created] = ordered.length ordered end |
#find_matching_region(region, unique_regions) ⇒ Integer?
Find matching region within threshold
189 190 191 192 193 194 |
# File 'lib/fontisan/variation/optimizer.rb', line 189 def find_matching_region(region, unique_regions) unique_regions.each_with_index do |unique, index| return index if regions_match?(region, unique) end nil end |
#optimize ⇒ CFF2
Optimize CFF2 table
Performs all optimization passes and returns optimized table.
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/fontisan/variation/optimizer.rb', line 60 def optimize @stats[:original_size] = estimate_table_size(@cff2) # Step 1: Analyze blend patterns across all CharStrings blend_patterns = analyze_blend_patterns # Step 2: Extract common blend sequences into subroutines subroutines = extract_blend_subroutines(blend_patterns) # Step 3: Deduplicate variation regions deduplicate_regions if @options[:deduplicate_regions] # Step 4: Optimize ItemVariationStore optimize_item_variation_store # Step 5: Rebuild CharStrings with subroutine calls rebuild_charstrings(subroutines) @stats[:optimized_size] = estimate_table_size(@cff2) @stats[:savings_percent] = calculate_savings_percent @cff2 end |
#optimize_delta_encoding(store) ⇒ Object
Optimize delta encoding for efficiency
260 261 262 |
# File 'lib/fontisan/variation/optimizer.rb', line 260 def optimize_delta_encoding(store) store.item_variation_data.each(&:optimize_encoding) end |
#optimize_item_variation_store ⇒ Object
Optimize ItemVariationStore
Compacts delta storage by removing unused data and optimizing encoding.
232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/fontisan/variation/optimizer.rb', line 232 def optimize_item_variation_store return unless @cff2.variation_store store = @cff2.variation_store # Remove unused variation data compact_variation_data(store) # Optimize delta encoding (use shortest representation) optimize_delta_encoding(store) end |
#rebuild_charstrings(subroutines) ⇒ Object
Rebuild CharStrings with subroutine calls
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/fontisan/variation/optimizer.rb', line 267 def rebuild_charstrings(subroutines) return if subroutines.empty? glyph_count = @cff2.glyph_count glyph_count.times do |glyph_id| charstring = @cff2.charstring(glyph_id) next unless charstring # Rewrite CharString to use subroutines optimized = rewrite_with_subroutines(charstring, subroutines) @cff2.set_charstring(glyph_id, optimized) end # Update subroutine index in CFF2 @cff2.local_subr_index = subroutines end |
#regions_match?(r1, r2) ⇒ Boolean
Check if two regions match within threshold
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/fontisan/variation/optimizer.rb', line 201 def regions_match?(r1, r2) return false unless r1.axis_count == r2.axis_count r1.axis_count.times do |i| coords1 = r1.region_axes[i] coords2 = r2.region_axes[i] # Compare start, peak, end coordinates return false unless coords_similar?(coords1.start_coord, coords2.start_coord) return false unless coords_similar?(coords1.peak_coord, coords2.peak_coord) return false unless coords_similar?(coords1.end_coord, coords2.end_coord) end true end |
#statistics ⇒ Hash
Get optimization statistics
288 289 290 |
# File 'lib/fontisan/variation/optimizer.rb', line 288 def statistics @stats end |