png_conform 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +82 -42
  3. data/Gemfile +2 -0
  4. data/README.adoc +3 -2
  5. data/benchmarks/README.adoc +570 -0
  6. data/benchmarks/config/default.yml +35 -0
  7. data/benchmarks/config/full.yml +32 -0
  8. data/benchmarks/config/quick.yml +32 -0
  9. data/benchmarks/direct_validation.rb +18 -0
  10. data/benchmarks/lib/benchmark_runner.rb +204 -0
  11. data/benchmarks/lib/metrics_collector.rb +193 -0
  12. data/benchmarks/lib/png_conform_runner.rb +68 -0
  13. data/benchmarks/lib/pngcheck_runner.rb +67 -0
  14. data/benchmarks/lib/report_generator.rb +301 -0
  15. data/benchmarks/lib/tool_runner.rb +104 -0
  16. data/benchmarks/profile_loading.rb +12 -0
  17. data/benchmarks/profile_validation.rb +18 -0
  18. data/benchmarks/results/.gitkeep +0 -0
  19. data/benchmarks/run_benchmark.rb +159 -0
  20. data/config/validation_profiles.yml +105 -0
  21. data/docs/CHUNK_TYPES.adoc +42 -0
  22. data/examples/README.md +282 -0
  23. data/lib/png_conform/analyzers/comparison_analyzer.rb +41 -7
  24. data/lib/png_conform/analyzers/metrics_analyzer.rb +6 -9
  25. data/lib/png_conform/analyzers/optimization_analyzer.rb +30 -24
  26. data/lib/png_conform/analyzers/resolution_analyzer.rb +31 -32
  27. data/lib/png_conform/cli.rb +12 -0
  28. data/lib/png_conform/commands/check_command.rb +118 -52
  29. data/lib/png_conform/configuration.rb +147 -0
  30. data/lib/png_conform/container.rb +113 -0
  31. data/lib/png_conform/models/decoded_chunk_data.rb +33 -0
  32. data/lib/png_conform/models/validation_result.rb +30 -4
  33. data/lib/png_conform/pipelines/pipeline_result.rb +39 -0
  34. data/lib/png_conform/pipelines/stages/analysis_stage.rb +35 -0
  35. data/lib/png_conform/pipelines/stages/base_stage.rb +23 -0
  36. data/lib/png_conform/pipelines/stages/chunk_validation_stage.rb +74 -0
  37. data/lib/png_conform/pipelines/stages/sequence_validation_stage.rb +77 -0
  38. data/lib/png_conform/pipelines/stages/signature_validation_stage.rb +41 -0
  39. data/lib/png_conform/pipelines/validation_pipeline.rb +90 -0
  40. data/lib/png_conform/readers/full_load_reader.rb +13 -4
  41. data/lib/png_conform/readers/streaming_reader.rb +27 -2
  42. data/lib/png_conform/reporters/color_reporter.rb +17 -14
  43. data/lib/png_conform/reporters/reporter_factory.rb +18 -11
  44. data/lib/png_conform/reporters/visual_elements.rb +22 -16
  45. data/lib/png_conform/services/analysis_manager.rb +120 -0
  46. data/lib/png_conform/services/chunk_processor.rb +195 -0
  47. data/lib/png_conform/services/file_signature.rb +226 -0
  48. data/lib/png_conform/services/file_strategy.rb +78 -0
  49. data/lib/png_conform/services/lru_cache.rb +170 -0
  50. data/lib/png_conform/services/parallel_validator.rb +118 -0
  51. data/lib/png_conform/services/profile_manager.rb +41 -12
  52. data/lib/png_conform/services/result_builder.rb +299 -0
  53. data/lib/png_conform/services/validation_cache.rb +210 -0
  54. data/lib/png_conform/services/validation_orchestrator.rb +188 -0
  55. data/lib/png_conform/services/validation_service.rb +82 -321
  56. data/lib/png_conform/services/validator_pool.rb +142 -0
  57. data/lib/png_conform/utils/colorizer.rb +149 -0
  58. data/lib/png_conform/validators/ancillary/idot_validator.rb +102 -0
  59. data/lib/png_conform/validators/chunk_registry.rb +143 -128
  60. data/lib/png_conform/validators/streaming_idat_validator.rb +123 -0
  61. data/lib/png_conform/version.rb +1 -1
  62. data/lib/png_conform.rb +7 -46
  63. data/png_conform.gemspec +1 -0
  64. metadata +55 -2
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "paint"
4
+
5
+ module PngConform
6
+ module Utils
7
+ # Colorization utility using the Paint gem
8
+ #
9
+ # Provides a centralized colorization service for terminal output.
10
+ # Wraps the Paint gem with semantic method names and caching.
11
+ #
12
+ # @example Basic usage
13
+ # Colorizer.success("Success message")
14
+ # Colorizer.error("Error message")
15
+ # Colorizer.warning("Warning message", bold: true)
16
+ #
17
+ class Colorizer
18
+ class << self
19
+ # Check if colorization is enabled
20
+ #
21
+ # @return [Boolean] true if colors are enabled
22
+ def enabled?
23
+ Paint.mode != 0
24
+ end
25
+
26
+ # Enable or disable colorization
27
+ #
28
+ # @param value [Boolean] true to enable, false to disable
29
+ def enabled=(value)
30
+ Paint.mode = value ? Paint.detect_mode : 0
31
+ end
32
+
33
+ # Colorize text as success (green)
34
+ #
35
+ # @param text [String] Text to colorize
36
+ # @param bold [Boolean] Whether to use bold formatting
37
+ # @return [String] Colorized text
38
+ def success(text, bold: false)
39
+ colorize(text, :green, bold: bold)
40
+ end
41
+
42
+ # Colorize text as error (red)
43
+ #
44
+ # @param text [String] Text to colorize
45
+ # @param bold [Boolean] Whether to use bold formatting
46
+ # @return [String] Colorized text
47
+ def error(text, bold: false)
48
+ colorize(text, :red, bold: bold)
49
+ end
50
+
51
+ # Colorize text as warning (yellow)
52
+ #
53
+ # @param text [String] Text to colorize
54
+ # @param bold [Boolean] Whether to use bold formatting
55
+ # @return [String] Colorized text
56
+ def warning(text, bold: false)
57
+ colorize(text, :yellow, bold: bold)
58
+ end
59
+
60
+ # Colorize text as info (blue)
61
+ #
62
+ # @param text [String] Text to colorize
63
+ # @param bold [Boolean] Whether to use bold formatting
64
+ # @return [String] Colorized text
65
+ def info(text, bold: false)
66
+ colorize(text, :blue, bold: bold)
67
+ end
68
+
69
+ # Colorize text with bold formatting
70
+ #
71
+ # @param text [String] Text to colorize
72
+ # @param color [Symbol] Color to use
73
+ # @return [String] Colorized text
74
+ def bold(text, color = nil)
75
+ return Paint[text, :bold] unless color
76
+
77
+ Paint[text, color, :bold]
78
+ end
79
+
80
+ # Colorize text with a specific color
81
+ #
82
+ # @param text [String] Text to colorize
83
+ # @param color [Symbol] Color to use
84
+ # @param bold [Boolean] Whether to use bold formatting
85
+ # @return [String] Colorized text
86
+ def colorize(text, color, bold: false)
87
+ return text.to_s unless enabled?
88
+
89
+ if bold
90
+ Paint[text, color, :bold]
91
+ else
92
+ Paint[text, color]
93
+ end
94
+ end
95
+
96
+ # Colorize text based on priority
97
+ #
98
+ # @param text [String] Text to colorize
99
+ # @param priority [Symbol] Priority (:high, :medium, :low)
100
+ # @return [String] Colorized text
101
+ def priority(text, priority:)
102
+ case priority
103
+ when :high
104
+ error(text, bold: true)
105
+ when :medium
106
+ warning(text, bold: true)
107
+ when :low
108
+ info(text)
109
+ else
110
+ text.to_s
111
+ end
112
+ end
113
+
114
+ # Get a checkmark symbol (with color)
115
+ #
116
+ # @param success [Boolean] Whether the check should be green
117
+ # @return [String] Colored checkmark
118
+ def checkmark(success: true)
119
+ if success
120
+ success("✓")
121
+ else
122
+ error("✗")
123
+ end
124
+ end
125
+
126
+ # Remove all color codes from text
127
+ #
128
+ # @param text [String] Text to uncolorize
129
+ # @return [String] Plain text
130
+ def uncolorize(text)
131
+ Paint.unpaint(text)
132
+ end
133
+
134
+ # Apply multiple colorizations to different parts of text
135
+ #
136
+ # @param template [String] Template with %{key} placeholders
137
+ # @param substitutions [Hash] Hash of key => [text, color] pairs
138
+ # @return [String] Colorized text
139
+ # @example
140
+ # Colorizer.template("%{greeting} %{name}",
141
+ # greeting: ["Hello", :green],
142
+ # name: ["World", :blue])
143
+ def template(template, substitutions)
144
+ Paint[template, substitutions]
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base_validator"
4
+
5
+ module PngConform
6
+ module Validators
7
+ module Ancillary
8
+ # Validator for PNG iDOT (Apple Display Optimization) chunk
9
+ #
10
+ # iDOT is an Apple-specific chunk found in screenshots and images saved
11
+ # from macOS/iOS devices. It contains display optimization data for
12
+ # Retina displays and multi-core decoding performance.
13
+ #
14
+ # Structure (28 bytes - seven 32-bit little-endian integers):
15
+ # - Display scale factor (4 bytes)
16
+ # - Pixel format information (4 bytes)
17
+ # - Color space information (4 bytes)
18
+ # - Backing scale factor (4 bytes)
19
+ # - Flags (4 bytes)
20
+ # - Reserved field 1 (4 bytes)
21
+ # - Reserved field 2 (4 bytes)
22
+ #
23
+ # Validation rules:
24
+ # - Chunk must be exactly 28 bytes
25
+ # - Only one iDOT chunk allowed
26
+ # - Must appear before IDAT chunk
27
+ #
28
+ # References:
29
+ # - Apple's proprietary display optimization format
30
+ # - Used in macOS/iOS screenshot PNG files
31
+ class IdotValidator < BaseValidator
32
+ # Expected chunk length (7 x 4-byte integers)
33
+ EXPECTED_LENGTH = 28
34
+
35
+ # Validate iDOT chunk
36
+ #
37
+ # @return [Boolean] True if validation passed
38
+ def validate
39
+ return false unless check_crc
40
+ return false unless check_length(EXPECTED_LENGTH)
41
+ return false unless check_uniqueness
42
+ return false unless check_position
43
+
44
+ decode_and_store_data
45
+ true
46
+ end
47
+
48
+ private
49
+
50
+ # Check that only one iDOT chunk exists
51
+ def check_uniqueness
52
+ if context.seen?("iDOT")
53
+ add_error("duplicate iDOT chunk (only one allowed)")
54
+ return false
55
+ end
56
+
57
+ true
58
+ end
59
+
60
+ # Check that iDOT appears before IDAT
61
+ def check_position
62
+ if context.seen?("IDAT")
63
+ add_error("iDOT chunk after IDAT (must be before)")
64
+ return false
65
+ end
66
+
67
+ true
68
+ end
69
+
70
+ # Decode iDOT data and store in context
71
+ def decode_and_store_data
72
+ values = chunk.chunk_data.unpack("V7")
73
+
74
+ # Create IdotData model
75
+ idot_data = create_idot_data(values)
76
+
77
+ # Store in context for later use
78
+ context.store(:idot_data, idot_data)
79
+
80
+ # Add info message with decoded data
81
+ add_info("iDOT: Apple display optimization (#{idot_data.detailed_info})")
82
+ end
83
+
84
+ # Create IdotData model from parsed values
85
+ #
86
+ # @param values [Array<Integer>] Seven 32-bit integers
87
+ # @return [Models::IdotData] The decoded data model
88
+ def create_idot_data(values)
89
+ Models::IdotData.new(
90
+ display_scale: values[0],
91
+ pixel_format: values[1],
92
+ color_space: values[2],
93
+ backing_scale_factor: values[3],
94
+ flags: values[4],
95
+ reserved1: values[5],
96
+ reserved2: values[6],
97
+ )
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -1,54 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "critical/ihdr_validator"
4
- require_relative "critical/plte_validator"
5
- require_relative "critical/idat_validator"
6
- require_relative "critical/iend_validator"
7
- require_relative "ancillary/text_validator"
8
- require_relative "ancillary/ztxt_validator"
9
- require_relative "ancillary/itxt_validator"
10
- require_relative "ancillary/gama_validator"
11
- require_relative "ancillary/chrm_validator"
12
- require_relative "ancillary/srgb_validator"
13
- require_relative "ancillary/sbit_validator"
14
- require_relative "ancillary/bkgd_validator"
15
- require_relative "ancillary/iccp_validator"
16
- require_relative "ancillary/hist_validator"
17
- require_relative "ancillary/splt_validator"
18
- require_relative "ancillary/trns_validator"
19
- require_relative "ancillary/phys_validator"
20
- require_relative "ancillary/time_validator"
21
- require_relative "ancillary/offs_validator"
22
- require_relative "ancillary/pcal_validator"
23
- require_relative "ancillary/scal_validator"
24
- require_relative "ancillary/ster_validator"
25
- require_relative "ancillary/cicp_validator"
26
- require_relative "ancillary/mdcv_validator"
27
- require_relative "apng/actl_validator"
28
- require_relative "apng/fctl_validator"
29
- require_relative "apng/fdat_validator"
30
- require_relative "mng/mhdr_validator"
31
- require_relative "mng/mend_validator"
32
- require_relative "mng/dhdr_validator"
33
- require_relative "mng/fram_validator"
34
- require_relative "mng/defi_validator"
35
- require_relative "mng/back_validator"
36
- require_relative "mng/loop_validator"
37
- require_relative "mng/endl_validator"
38
- require_relative "mng/term_validator"
39
- require_relative "mng/save_validator"
40
- require_relative "mng/seek_validator"
41
- require_relative "mng/move_validator"
42
- require_relative "mng/clip_validator"
43
- require_relative "mng/show_validator"
44
- require_relative "mng/clon_validator"
45
- require_relative "mng/disc_validator"
46
- require_relative "jng/jhdr_validator"
47
- require_relative "jng/jdat_validator"
48
- require_relative "jng/jsep_validator"
49
-
50
3
  module PngConform
51
4
  module Validators
5
+ # Define validator category modules upfront for proper namespace resolution
6
+ module Critical; end
7
+ module Ancillary; end
8
+ module Apng; end
9
+ module Mng; end
10
+ module Jng; end
11
+
52
12
  # Registry of chunk types to their corresponding validator classes
53
13
  #
54
14
  # This class maintains a mapping between PNG chunk type codes and
@@ -62,85 +22,100 @@ module PngConform
62
22
  # - Palette support (hIST, sPLT, tRNS)
63
23
  # - Metadata (pHYs, tIME, oFFs, pCAL, sCAL, sTER)
64
24
  # - PNG 3rd edition (cICP, mDCv)
25
+ # - Apple extensions (iDOT)
65
26
  # - APNG (acTL, fcTL, fdAT)
66
27
  # - MNG (MHDR, MEND, DHDR, FRAM, DEFI, BACK, LOOP, ENDL, etc.)
67
28
  # - JNG (JHDR, JDAT, JSEP)
68
29
  #
30
+ # Validators are loaded lazily on-demand to improve startup performance.
31
+ #
69
32
  class ChunkRegistry
70
- # Map of chunk type codes to validator classes
71
- VALIDATORS = {
33
+ # Map of chunk type codes to validator file paths and class names
34
+ # Format: [file_path, module_path, class_name]
35
+ VALIDATOR_PATHS = {
72
36
  # Critical chunks
73
- "IHDR" => Critical::IhdrValidator,
74
- "PLTE" => Critical::PlteValidator,
75
- "IDAT" => Critical::IdatValidator,
76
- "IEND" => Critical::IendValidator,
37
+ "IHDR" => ["critical/ihdr_validator", "Critical", "IhdrValidator"],
38
+ "PLTE" => ["critical/plte_validator", "Critical", "PlteValidator"],
39
+ "IDAT" => ["critical/idat_validator", "Critical", "IdatValidator"],
40
+ "IEND" => ["critical/iend_validator", "Critical", "IendValidator"],
77
41
 
78
42
  # Text chunks
79
- "tEXt" => Ancillary::TextValidator,
80
- "zTXt" => Ancillary::ZtxtValidator,
81
- "iTXt" => Ancillary::ItxtValidator,
43
+ "tEXt" => ["ancillary/text_validator", "Ancillary", "TextValidator"],
44
+ "zTXt" => ["ancillary/ztxt_validator", "Ancillary", "ZtxtValidator"],
45
+ "iTXt" => ["ancillary/itxt_validator", "Ancillary", "ItxtValidator"],
82
46
 
83
47
  # Color management
84
- "gAMA" => Ancillary::GamaValidator,
85
- "cHRM" => Ancillary::ChrmValidator,
86
- "sRGB" => Ancillary::SrgbValidator,
87
- "sBIT" => Ancillary::SbitValidator,
88
- "bKGD" => Ancillary::BkgdValidator,
89
- "iCCP" => Ancillary::IccpValidator,
48
+ "gAMA" => ["ancillary/gama_validator", "Ancillary", "GamaValidator"],
49
+ "cHRM" => ["ancillary/chrm_validator", "Ancillary", "ChrmValidator"],
50
+ "sRGB" => ["ancillary/srgb_validator", "Ancillary", "SrgbValidator"],
51
+ "sBIT" => ["ancillary/sbit_validator", "Ancillary", "SbitValidator"],
52
+ "bKGD" => ["ancillary/bkgd_validator", "Ancillary", "BkgdValidator"],
53
+ "iCCP" => ["ancillary/iccp_validator", "Ancillary", "IccpValidator"],
90
54
 
91
55
  # Palette support
92
- "hIST" => Ancillary::HistValidator,
93
- "sPLT" => Ancillary::SpltValidator,
94
- "tRNS" => Ancillary::TrnsValidator,
56
+ "hIST" => ["ancillary/hist_validator", "Ancillary", "HistValidator"],
57
+ "sPLT" => ["ancillary/splt_validator", "Ancillary", "SpltValidator"],
58
+ "tRNS" => ["ancillary/trns_validator", "Ancillary", "TrnsValidator"],
95
59
 
96
60
  # Metadata
97
- "pHYs" => Ancillary::PhysValidator,
98
- "tIME" => Ancillary::TimeValidator,
99
- "oFFs" => Ancillary::OffsValidator,
100
- "pCAL" => Ancillary::PcalValidator,
101
- "sCAL" => Ancillary::ScalValidator,
102
- "sTER" => Ancillary::SterValidator,
61
+ "pHYs" => ["ancillary/phys_validator", "Ancillary", "PhysValidator"],
62
+ "tIME" => ["ancillary/time_validator", "Ancillary", "TimeValidator"],
63
+ "oFFs" => ["ancillary/offs_validator", "Ancillary", "OffsValidator"],
64
+ "pCAL" => ["ancillary/pcal_validator", "Ancillary", "PcalValidator"],
65
+ "sCAL" => ["ancillary/scal_validator", "Ancillary", "ScalValidator"],
66
+ "sTER" => ["ancillary/ster_validator", "Ancillary", "SterValidator"],
103
67
 
104
68
  # PNG 3rd edition
105
- "cICP" => Ancillary::CicpValidator,
106
- "mDCv" => Ancillary::MdcvValidator,
69
+ "cICP" => ["ancillary/cicp_validator", "Ancillary", "CicpValidator"],
70
+ "mDCv" => ["ancillary/mdcv_validator", "Ancillary", "MdcvValidator"],
71
+
72
+ # Apple extensions
73
+ "iDOT" => ["ancillary/idot_validator", "Ancillary", "IdotValidator"],
107
74
 
108
75
  # APNG (Animated PNG)
109
- "acTL" => Apng::ActlValidator,
110
- "fcTL" => Apng::FctlValidator,
111
- "fdAT" => Apng::FdatValidator,
76
+ "acTL" => ["apng/actl_validator", "Apng", "ActlValidator"],
77
+ "fcTL" => ["apng/fctl_validator", "Apng", "FctlValidator"],
78
+ "fdAT" => ["apng/fdat_validator", "Apng", "FdatValidator"],
112
79
 
113
80
  # MNG (Multiple-image Network Graphics)
114
- "MHDR" => Mng::MhdrValidator,
115
- "MEND" => Mng::MendValidator,
116
- "DHDR" => Mng::DhdrValidator,
117
- "FRAM" => Mng::FramValidator,
118
- "DEFI" => Mng::DefiValidator,
119
- "BACK" => Mng::BackValidator,
120
- "LOOP" => Mng::LoopValidator,
121
- "ENDL" => Mng::EndlValidator,
122
- "TERM" => Mng::TermValidator,
123
- "SAVE" => Mng::SaveValidator,
124
- "SEEK" => Mng::SeekValidator,
125
- "MOVE" => Mng::MoveValidator,
126
- "CLIP" => Mng::ClipValidator,
127
- "SHOW" => Mng::ShowValidator,
128
- "CLON" => Mng::ClonValidator,
129
- "DISC" => Mng::DiscValidator,
81
+ "MHDR" => ["mng/mhdr_validator", "Mng", "MhdrValidator"],
82
+ "MEND" => ["mng/mend_validator", "Mng", "MendValidator"],
83
+ "DHDR" => ["mng/dhdr_validator", "Mng", "DhdrValidator"],
84
+ "FRAM" => ["mng/fram_validator", "Mng", "FramValidator"],
85
+ "DEFI" => ["mng/defi_validator", "Mng", "DefiValidator"],
86
+ "BACK" => ["mng/back_validator", "Mng", "BackValidator"],
87
+ "LOOP" => ["mng/loop_validator", "Mng", "LoopValidator"],
88
+ "ENDL" => ["mng/endl_validator", "Mng", "EndlValidator"],
89
+ "TERM" => ["mng/term_validator", "Mng", "TermValidator"],
90
+ "SAVE" => ["mng/save_validator", "Mng", "SaveValidator"],
91
+ "SEEK" => ["mng/seek_validator", "Mng", "SeekValidator"],
92
+ "MOVE" => ["mng/move_validator", "Mng", "MoveValidator"],
93
+ "CLIP" => ["mng/clip_validator", "Mng", "ClipValidator"],
94
+ "SHOW" => ["mng/show_validator", "Mng", "ShowValidator"],
95
+ "CLON" => ["mng/clon_validator", "Mng", "ClonValidator"],
96
+ "DISC" => ["mng/disc_validator", "Mng", "DiscValidator"],
130
97
 
131
98
  # JNG (JPEG Network Graphics)
132
- "JHDR" => Jng::JhdrValidator,
133
- "JDAT" => Jng::JdatValidator,
134
- "JSEP" => Jng::JsepValidator,
99
+ "JHDR" => ["jng/jhdr_validator", "Jng", "JhdrValidator"],
100
+ "JDAT" => ["jng/jdat_validator", "Jng", "JdatValidator"],
101
+ "JSEP" => ["jng/jsep_validator", "Jng", "JsepValidator"],
135
102
  }.freeze
136
103
 
137
104
  class << self
138
- # Get validator class for a chunk type
105
+ # Get validator class for a chunk type (with lazy loading)
139
106
  #
140
107
  # @param chunk_type [String] Four-character chunk type code
141
108
  # @return [Class, nil] Validator class or nil if not found
142
109
  def validator_for(chunk_type)
143
- VALIDATORS[chunk_type]
110
+ # Return cached validator if already loaded
111
+ return loaded_validators[chunk_type] if loaded_validators.key?(chunk_type)
112
+
113
+ # Check if validator path exists
114
+ validator_info = VALIDATOR_PATHS[chunk_type]
115
+ return nil unless validator_info
116
+
117
+ # Load validator on-demand
118
+ load_validator(chunk_type, validator_info)
144
119
  end
145
120
 
146
121
  # Check if a validator exists for a chunk type
@@ -148,14 +123,14 @@ module PngConform
148
123
  # @param chunk_type [String] Four-character chunk type code
149
124
  # @return [Boolean] True if validator exists
150
125
  def validator_exists?(chunk_type)
151
- VALIDATORS.key?(chunk_type)
126
+ VALIDATOR_PATHS.key?(chunk_type)
152
127
  end
153
128
 
154
129
  # Get all registered chunk types
155
130
  #
156
131
  # @return [Array<String>] List of chunk type codes
157
132
  def chunk_types
158
- VALIDATORS.keys
133
+ VALIDATOR_PATHS.keys
159
134
  end
160
135
 
161
136
  # Get validators by category
@@ -164,34 +139,34 @@ module PngConform
164
139
  # (:critical, :text, :color, :palette, :metadata, :png3)
165
140
  # @return [Hash] Map of chunk types to validators in category
166
141
  def validators_by_category(category)
167
- case category
168
- when :critical
169
- VALIDATORS.select { |k, _| %w[IHDR PLTE IDAT IEND].include?(k) }
170
- when :text
171
- VALIDATORS.select { |k, _| %w[tEXt zTXt iTXt].include?(k) }
172
- when :color
173
- VALIDATORS.select do |k, _|
174
- %w[gAMA cHRM sRGB sBIT bKGD iCCP].include?(k)
175
- end
176
- when :palette
177
- VALIDATORS.select { |k, _| %w[hIST sPLT tRNS].include?(k) }
178
- when :metadata
179
- VALIDATORS.select do |k, _|
180
- %w[pHYs tIME oFFs pCAL sCAL sTER].include?(k)
181
- end
182
- when :png3
183
- VALIDATORS.select { |k, _| %w[cICP mDCv].include?(k) }
184
- when :apng
185
- VALIDATORS.select { |k, _| %w[acTL fcTL fdAT].include?(k) }
186
- when :mng
187
- VALIDATORS.select do |k, _|
188
- %w[MHDR MEND DHDR FRAM DEFI BACK LOOP ENDL TERM SAVE SEEK
189
- MOVE CLIP SHOW CLON DISC].include?(k)
190
- end
191
- when :jng
192
- VALIDATORS.select { |k, _| %w[JHDR JDAT JSEP].include?(k) }
193
- else
194
- {}
142
+ chunk_types = case category
143
+ when :critical
144
+ %w[IHDR PLTE IDAT IEND]
145
+ when :text
146
+ %w[tEXt zTXt iTXt]
147
+ when :color
148
+ %w[gAMA cHRM sRGB sBIT bKGD iCCP]
149
+ when :palette
150
+ %w[hIST sPLT tRNS]
151
+ when :metadata
152
+ %w[pHYs tIME oFFs pCAL sCAL sTER]
153
+ when :png3
154
+ %w[cICP mDCv]
155
+ when :apng
156
+ %w[acTL fcTL fdAT]
157
+ when :mng
158
+ %w[MHDR MEND DHDR FRAM DEFI BACK LOOP ENDL TERM SAVE SEEK
159
+ MOVE CLIP SHOW CLON DISC]
160
+ when :jng
161
+ %w[JHDR JDAT JSEP]
162
+ else
163
+ []
164
+ end
165
+
166
+ # Load validators for this category
167
+ chunk_types.each_with_object({}) do |chunk_type, result|
168
+ validator = validator_for(chunk_type)
169
+ result[chunk_type] = validator if validator
195
170
  end
196
171
  end
197
172
 
@@ -199,7 +174,7 @@ module PngConform
199
174
  #
200
175
  # @return [Integer] Number of registered validators
201
176
  def count
202
- VALIDATORS.size
177
+ VALIDATOR_PATHS.size
203
178
  end
204
179
 
205
180
  # Create validator instance for a chunk
@@ -213,6 +188,46 @@ module PngConform
213
188
 
214
189
  validator_class.new(chunk, context)
215
190
  end
191
+
192
+ # Preload all validators for performance
193
+ #
194
+ # Loads all validator classes into cache at startup to improve
195
+ # validation performance by avoiding lazy loading overhead.
196
+ #
197
+ # @return [void]
198
+ def preload_validators!
199
+ VALIDATOR_PATHS.each_key do |chunk_type|
200
+ validator_for(chunk_type)
201
+ end
202
+ end
203
+
204
+ private
205
+
206
+ # Cache for loaded validator classes
207
+ def loaded_validators
208
+ @loaded_validators ||= {}
209
+ end
210
+
211
+ # Load a validator class on-demand
212
+ #
213
+ # @param chunk_type [String] Chunk type code
214
+ # @param validator_info [Array] [file_path, module_name, class_name]
215
+ # @return [Class, nil] Loaded validator class
216
+ def load_validator(chunk_type, validator_info)
217
+ file_path, module_name, class_name = validator_info
218
+
219
+ # Require the validator file
220
+ require_relative file_path
221
+
222
+ # Resolve the constant (e.g., Critical::IhdrValidator)
223
+ validator_class = Validators.const_get(module_name).const_get(class_name)
224
+
225
+ # Cache and return
226
+ loaded_validators[chunk_type] = validator_class
227
+ rescue NameError, LoadError => e
228
+ warn "Failed to load validator for #{chunk_type}: #{e.message}"
229
+ loaded_validators[chunk_type] = nil
230
+ end
216
231
  end
217
232
  end
218
233
  end