Class: Fontisan::Pipeline::TransformationPipeline
- Inherits:
-
Object
- Object
- Fontisan::Pipeline::TransformationPipeline
- Defined in:
- lib/fontisan/pipeline/transformation_pipeline.rb
Overview
Orchestrates universal font transformation pipeline
This is the main entry point for font conversion operations. It coordinates:
- Format detection (via FormatDetector)
- Font loading (via FontLoader)
- Variation resolution (via VariationResolver)
- Format conversion (via FormatConverter)
- Output writing (via OutputWriter)
- Validation (optional, via Validation::Validator)
The pipeline follows a clear MECE architecture where each phase has a single responsibility and produces well-defined outputs.
Instance Attribute Summary collapse
-
#input_path ⇒ String
readonly
Input file path.
-
#options ⇒ Hash
readonly
Transformation options.
-
#output_path ⇒ String
readonly
Output file path.
Instance Method Summary collapse
-
#build_details(detection) ⇒ Hash
Build transformation details.
-
#build_font_from_tables(tables, format) ⇒ Font
Build font object from tables.
-
#can_preserve_variation?(detection) ⇒ Boolean
Check if variation can be preserved for target format.
-
#convert_format(tables, detection) ⇒ Hash
Convert format if needed.
-
#default_options ⇒ Hash
Default options.
-
#detect_input_format ⇒ Hash
Detect input format and capabilities.
-
#detect_target_from_extension ⇒ Symbol
Detect target format from output path extension.
-
#determine_variation_strategy(detection) ⇒ Symbol
Determine variation strategy based on options and compatibility.
-
#export_only_format? ⇒ Boolean
Check if target format is export-only (cannot be validated).
-
#handle_error(error) ⇒ Object
Handle transformation error.
-
#initialize(input_path, output_path, options = {}) ⇒ TransformationPipeline
constructor
Initialize transformation pipeline.
-
#load_font(_detection) ⇒ Font
Load font with appropriate mode.
-
#log(message) ⇒ Object
Log message if verbose.
-
#needs_outline_conversion?(source, target) ⇒ Boolean
Check if outline conversion is needed.
-
#resolve_static_font(font) ⇒ Hash
Resolve static font (just copy tables).
-
#resolve_variation(font, detection) ⇒ Hash
Resolve variation data.
-
#same_format_conversion? ⇒ Boolean
Check if this is a same-format conversion.
-
#same_outline_family?(source, target) ⇒ Boolean
Check if formats are in same outline family.
-
#target_format ⇒ Symbol
Get target format.
-
#transform ⇒ Hash
Execute transformation pipeline.
-
#validate_output ⇒ Object
Validate output file.
-
#validate_paths! ⇒ Object
Validate input and output paths.
-
#variation_options ⇒ Hash
Get variation options for VariationResolver.
-
#write_output(tables, _detection) ⇒ Object
Write output font file.
Constructor Details
#initialize(input_path, output_path, options = {}) ⇒ TransformationPipeline
Initialize transformation pipeline
51 52 53 54 55 56 57 58 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 51 def initialize(input_path, output_path, = {}) @input_path = input_path @output_path = output_path @options = .merge() @variation_strategy = nil validate_paths! end |
Instance Attribute Details
#input_path ⇒ String (readonly)
Returns Input file path.
32 33 34 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 32 def input_path @input_path end |
#options ⇒ Hash (readonly)
Returns Transformation options.
38 39 40 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 38 def @options end |
#output_path ⇒ String (readonly)
Returns Output file path.
35 36 37 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 35 def output_path @output_path end |
Instance Method Details
#build_details(detection) ⇒ Hash
Build transformation details
350 351 352 353 354 355 356 357 358 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 350 def build_details(detection) { source_format: detection[:format], source_variation: detection[:variation_type], target_format: target_format, variation_strategy: @variation_strategy, variation_preserved: @variation_strategy == :preserve, } end |
#build_font_from_tables(tables, format) ⇒ Font
Build font object from tables
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 323 def build_font_from_tables(tables, format) # Detect outline type from tables has_cff = tables.key?("CFF ") || tables.key?("CFF2") has_glyf = tables.key?("glyf") if has_cff OpenTypeFont.from_tables(tables) elsif has_glyf TrueTypeFont.from_tables(tables) else # Default based on format case format when :ttf, :woff, :woff2 TrueTypeFont.from_tables(tables) when :otf OpenTypeFont.from_tables(tables) else raise ArgumentError, "Cannot determine font type: format=#{format}, has_cff=#{has_cff}, has_glyf=#{has_glyf}" end end end |
#can_preserve_variation?(detection) ⇒ Boolean
Check if variation can be preserved for target format
182 183 184 185 186 187 188 189 190 191 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 182 def can_preserve_variation?(detection) source_format = detection[:format] target = target_format # Same format return true if source_format == target # Same outline family (packaging change only) same_outline_family?(source_format, target) end |
#convert_format(tables, detection) ⇒ Hash
Convert format if needed
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 211 def convert_format(tables, detection) source_format = detection[:format] target = target_format # No conversion needed for same format return tables if source_format == target # Use FormatConverter for outline conversion if needs_outline_conversion?(source_format, target) || target == :svg converter = Converters::FormatConverter.new # Create temporary font object from tables font = build_font_from_tables(tables, source_format) converter.convert(font, target, @options) else # Just packaging change - tables can be used as-is tables end end |
#default_options ⇒ Hash
Default options
381 382 383 384 385 386 387 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 381 def { validate: true, verbose: false, preserve_variation: nil, # Auto-determine } end |
#detect_input_format ⇒ Hash
Detect input format and capabilities
113 114 115 116 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 113 def detect_input_format detector = FormatDetector.new(@input_path) detector.detect end |
#detect_target_from_extension ⇒ Symbol
Detect target format from output path extension
278 279 280 281 282 283 284 285 286 287 288 289 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 278 def detect_target_from_extension ext = File.extname(@output_path).downcase case ext when ".ttf" then :ttf when ".otf" then :otf when ".woff" then :woff when ".woff2" then :woff2 else raise ArgumentError, "Cannot determine target format from extension: #{ext}" end end |
#determine_variation_strategy(detection) ⇒ Symbol
Determine variation strategy based on options and compatibility
163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 163 def determine_variation_strategy(detection) # User explicitly requested instance generation if @options[:coordinates] || @options[:instance_index] return @options[:instance_index] ? :named : :instance end # Check if preservation is possible if can_preserve_variation?(detection) @options.fetch(:preserve_variation, true) ? :preserve : :instance else # Cannot preserve - must generate instance :instance end end |
#export_only_format? ⇒ Boolean
Check if target format is export-only (cannot be validated)
400 401 402 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 400 def export_only_format? %i[svg woff woff2].include?(target_format) end |
#handle_error(error) ⇒ Object
Handle transformation error
364 365 366 367 368 369 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 364 def handle_error(error) log "ERROR: #{error.}" log error.backtrace.first(5).join("\n") if @options[:verbose] raise Error, "Transformation failed: #{error.}" end |
#load_font(_detection) ⇒ Font
Load font with appropriate mode
122 123 124 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 122 def load_font(_detection) FontLoader.load(@input_path, mode: :full) end |
#log(message) ⇒ Object
Log message if verbose
374 375 376 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 374 def log() puts "[TransformationPipeline] #{}" if @options[:verbose] end |
#needs_outline_conversion?(source, target) ⇒ Boolean
Check if outline conversion is needed
235 236 237 238 239 240 241 242 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 235 def needs_outline_conversion?(source, target) # TTF ↔ OTF requires outline conversion ttf_formats = %i[ttf ttc woff woff2] otf_formats = %i[otf otc] (ttf_formats.include?(source) && otf_formats.include?(target)) || (otf_formats.include?(source) && ttf_formats.include?(target)) end |
#resolve_static_font(font) ⇒ Hash
Resolve static font (just copy tables)
152 153 154 155 156 157 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 152 def resolve_static_font(font) @variation_strategy = :preserve # Get all tables from font - use table_data directly font.table_data.dup end |
#resolve_variation(font, detection) ⇒ Hash
Resolve variation data
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 131 def resolve_variation(font, detection) # Static fonts - use preserve strategy (just copy tables) return resolve_static_font(font) if detection[:variation_type] == :static # Variable fonts - determine strategy strategy = determine_variation_strategy(detection) @variation_strategy = strategy resolver = VariationResolver.new( font, strategy: strategy, **, ) resolver.resolve end |
#same_format_conversion? ⇒ Boolean
Check if this is a same-format conversion
392 393 394 395 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 392 def same_format_conversion? detection = detect_input_format detection[:format] == target_format end |
#same_outline_family?(source, target) ⇒ Boolean
Check if formats are in same outline family
198 199 200 201 202 203 204 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 198 def same_outline_family?(source, target) truetype_formats = %i[ttf ttc woff woff2] opentype_formats = %i[otf otc woff woff2] (truetype_formats.include?(source) && truetype_formats.include?(target)) || (opentype_formats.include?(source) && opentype_formats.include?(target)) end |
#target_format ⇒ Symbol
Get target format
271 272 273 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 271 def target_format @options[:target_format] || detect_target_from_extension end |
#transform ⇒ Hash
Execute transformation pipeline
This is the main entry point. It orchestrates:
- Format detection
- Font loading
- Variation resolution
- Format conversion
- Output writing
- Validation (optional)
72 73 74 75 76 77 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 105 106 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 72 def transform log "Starting transformation: #{@input_path} → #{@output_path}" # Phase 1: Detect input format detection = detect_input_format log "Detected: #{detection[:format]} (#{detection[:variation_type]})" # Phase 2: Load font font = load_font(detection) log "Loaded: #{font.class.name}" # Phase 3: Resolve variation tables = resolve_variation(font, detection) log "Resolved variation using #{@variation_strategy} strategy" # Phase 4: Convert format tables = convert_format(tables, detection) log "Converted to #{target_format}" # Phase 5: Write output write_output(tables, detection) log "Written to #{@output_path}" # Phase 6: Validate (optional) validate_output if @options[:validate] && !same_format_conversion? && !export_only_format? log "Validation passed" if @options[:validate] && !export_only_format? { success: true, output_path: @output_path, details: build_details(detection), } rescue StandardError => e handle_error(e) end |
#validate_output ⇒ Object
Validate output file
256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 256 def validate_output return unless File.exist?(@output_path) # Use new validation framework with production profile report = Fontisan.validate(@output_path, profile: :production) return if report.valid? = report.errors.map(&:message).join(", ") raise Error, "Output validation failed: #{}" end |
#validate_paths! ⇒ Object
Validate input and output paths
307 308 309 310 311 312 313 314 315 316 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 307 def validate_paths! unless File.exist?(@input_path) raise ArgumentError, "Input file not found: #{@input_path}" end output_dir = File.dirname(@output_path) unless File.directory?(output_dir) raise ArgumentError, "Output directory not found: #{output_dir}" end end |
#variation_options ⇒ Hash
Get variation options for VariationResolver
294 295 296 297 298 299 300 301 302 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 294 def opts = {} opts[:coordinates] = @options[:coordinates] if @options[:coordinates] if @options[:instance_index] opts[:instance_index] = @options[:instance_index] end opts end |
#write_output(tables, _detection) ⇒ Object
Write output font file
248 249 250 251 |
# File 'lib/fontisan/pipeline/transformation_pipeline.rb', line 248 def write_output(tables, _detection) writer = OutputWriter.new(@output_path, target_format, @options) writer.write(tables) end |