png_conform 0.1.0
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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +19 -0
- data/.rubocop_todo.yml +197 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/CONTRIBUTING.md +323 -0
- data/Gemfile +13 -0
- data/LICENSE +43 -0
- data/README.adoc +859 -0
- data/Rakefile +10 -0
- data/SECURITY.md +147 -0
- data/docs/ARCHITECTURE.adoc +681 -0
- data/docs/CHUNK_TYPES.adoc +450 -0
- data/docs/CLI_OPTIONS.adoc +913 -0
- data/docs/COMPATIBILITY.adoc +616 -0
- data/examples/README.adoc +398 -0
- data/examples/advanced_usage.rb +304 -0
- data/examples/basic_usage.rb +210 -0
- data/exe/png_conform +6 -0
- data/lib/png_conform/analyzers/comparison_analyzer.rb +230 -0
- data/lib/png_conform/analyzers/metrics_analyzer.rb +176 -0
- data/lib/png_conform/analyzers/optimization_analyzer.rb +190 -0
- data/lib/png_conform/analyzers/resolution_analyzer.rb +274 -0
- data/lib/png_conform/bindata/chunk_structure.rb +153 -0
- data/lib/png_conform/bindata/jng_file.rb +79 -0
- data/lib/png_conform/bindata/mng_file.rb +97 -0
- data/lib/png_conform/bindata/png_file.rb +162 -0
- data/lib/png_conform/cli.rb +116 -0
- data/lib/png_conform/commands/check_command.rb +323 -0
- data/lib/png_conform/commands/list_command.rb +67 -0
- data/lib/png_conform/models/chunk.rb +84 -0
- data/lib/png_conform/models/chunk_info.rb +71 -0
- data/lib/png_conform/models/compression_info.rb +49 -0
- data/lib/png_conform/models/decoded_chunk_data.rb +143 -0
- data/lib/png_conform/models/file_analysis.rb +181 -0
- data/lib/png_conform/models/file_info.rb +91 -0
- data/lib/png_conform/models/image_info.rb +52 -0
- data/lib/png_conform/models/validation_error.rb +89 -0
- data/lib/png_conform/models/validation_result.rb +137 -0
- data/lib/png_conform/readers/full_load_reader.rb +113 -0
- data/lib/png_conform/readers/streaming_reader.rb +180 -0
- data/lib/png_conform/reporters/base_reporter.rb +53 -0
- data/lib/png_conform/reporters/color_reporter.rb +65 -0
- data/lib/png_conform/reporters/json_reporter.rb +18 -0
- data/lib/png_conform/reporters/palette_reporter.rb +48 -0
- data/lib/png_conform/reporters/quiet_reporter.rb +18 -0
- data/lib/png_conform/reporters/reporter_factory.rb +108 -0
- data/lib/png_conform/reporters/summary_reporter.rb +65 -0
- data/lib/png_conform/reporters/text_reporter.rb +66 -0
- data/lib/png_conform/reporters/verbose_reporter.rb +87 -0
- data/lib/png_conform/reporters/very_verbose_reporter.rb +33 -0
- data/lib/png_conform/reporters/visual_elements.rb +66 -0
- data/lib/png_conform/reporters/yaml_reporter.rb +18 -0
- data/lib/png_conform/services/profile_manager.rb +242 -0
- data/lib/png_conform/services/validation_service.rb +457 -0
- data/lib/png_conform/services/zlib_validator.rb +270 -0
- data/lib/png_conform/validators/ancillary/bkgd_validator.rb +140 -0
- data/lib/png_conform/validators/ancillary/chrm_validator.rb +178 -0
- data/lib/png_conform/validators/ancillary/cicp_validator.rb +202 -0
- data/lib/png_conform/validators/ancillary/gama_validator.rb +105 -0
- data/lib/png_conform/validators/ancillary/hist_validator.rb +147 -0
- data/lib/png_conform/validators/ancillary/iccp_validator.rb +243 -0
- data/lib/png_conform/validators/ancillary/itxt_validator.rb +280 -0
- data/lib/png_conform/validators/ancillary/mdcv_validator.rb +201 -0
- data/lib/png_conform/validators/ancillary/offs_validator.rb +132 -0
- data/lib/png_conform/validators/ancillary/pcal_validator.rb +289 -0
- data/lib/png_conform/validators/ancillary/phys_validator.rb +107 -0
- data/lib/png_conform/validators/ancillary/sbit_validator.rb +176 -0
- data/lib/png_conform/validators/ancillary/scal_validator.rb +180 -0
- data/lib/png_conform/validators/ancillary/splt_validator.rb +223 -0
- data/lib/png_conform/validators/ancillary/srgb_validator.rb +117 -0
- data/lib/png_conform/validators/ancillary/ster_validator.rb +111 -0
- data/lib/png_conform/validators/ancillary/text_validator.rb +129 -0
- data/lib/png_conform/validators/ancillary/time_validator.rb +132 -0
- data/lib/png_conform/validators/ancillary/trns_validator.rb +154 -0
- data/lib/png_conform/validators/ancillary/ztxt_validator.rb +173 -0
- data/lib/png_conform/validators/apng/actl_validator.rb +81 -0
- data/lib/png_conform/validators/apng/fctl_validator.rb +155 -0
- data/lib/png_conform/validators/apng/fdat_validator.rb +117 -0
- data/lib/png_conform/validators/base_validator.rb +241 -0
- data/lib/png_conform/validators/chunk_registry.rb +219 -0
- data/lib/png_conform/validators/critical/idat_validator.rb +77 -0
- data/lib/png_conform/validators/critical/iend_validator.rb +68 -0
- data/lib/png_conform/validators/critical/ihdr_validator.rb +160 -0
- data/lib/png_conform/validators/critical/plte_validator.rb +120 -0
- data/lib/png_conform/validators/jng/jdat_validator.rb +66 -0
- data/lib/png_conform/validators/jng/jhdr_validator.rb +116 -0
- data/lib/png_conform/validators/jng/jsep_validator.rb +66 -0
- data/lib/png_conform/validators/mng/back_validator.rb +87 -0
- data/lib/png_conform/validators/mng/clip_validator.rb +65 -0
- data/lib/png_conform/validators/mng/clon_validator.rb +45 -0
- data/lib/png_conform/validators/mng/defi_validator.rb +104 -0
- data/lib/png_conform/validators/mng/dhdr_validator.rb +104 -0
- data/lib/png_conform/validators/mng/disc_validator.rb +44 -0
- data/lib/png_conform/validators/mng/endl_validator.rb +65 -0
- data/lib/png_conform/validators/mng/fram_validator.rb +91 -0
- data/lib/png_conform/validators/mng/loop_validator.rb +75 -0
- data/lib/png_conform/validators/mng/mend_validator.rb +31 -0
- data/lib/png_conform/validators/mng/mhdr_validator.rb +69 -0
- data/lib/png_conform/validators/mng/move_validator.rb +61 -0
- data/lib/png_conform/validators/mng/save_validator.rb +39 -0
- data/lib/png_conform/validators/mng/seek_validator.rb +42 -0
- data/lib/png_conform/validators/mng/show_validator.rb +52 -0
- data/lib/png_conform/validators/mng/term_validator.rb +84 -0
- data/lib/png_conform/version.rb +5 -0
- data/lib/png_conform.rb +101 -0
- data/png_conform.gemspec +43 -0
- metadata +201 -0
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
= PngConform Architecture
|
|
2
|
+
|
|
3
|
+
== Overview
|
|
4
|
+
|
|
5
|
+
PngConform is a pure Ruby implementation of PNG/MNG/JNG validity checking with profile support. The architecture follows strict object-oriented principles, MECE (Mutually Exclusive, Collectively Exhaustive) design, and separation of concerns.
|
|
6
|
+
|
|
7
|
+
== Core Architectural Principles
|
|
8
|
+
|
|
9
|
+
=== Object-Oriented Design
|
|
10
|
+
|
|
11
|
+
*Encapsulation*:: Each class has a single, well-defined responsibility
|
|
12
|
+
*Inheritance*:: Chunk validators inherit from base classes with clear contracts
|
|
13
|
+
*Polymorphism*:: Different chunk types implement common validation interfaces
|
|
14
|
+
*Abstraction*:: Implementation details hidden behind clean interfaces
|
|
15
|
+
|
|
16
|
+
=== MECE Structure
|
|
17
|
+
|
|
18
|
+
Each layer and component has:
|
|
19
|
+
|
|
20
|
+
* *Mutually Exclusive*: No overlap in responsibilities
|
|
21
|
+
* *Collectively Exhaustive*: Complete coverage of the problem domain
|
|
22
|
+
|
|
23
|
+
=== Separation of Concerns
|
|
24
|
+
|
|
25
|
+
Clear boundaries between:
|
|
26
|
+
|
|
27
|
+
* Binary parsing (BinData layer)
|
|
28
|
+
* Domain modeling (Lutaml::Model layer)
|
|
29
|
+
* Business logic (Validators)
|
|
30
|
+
* Presentation (Reporters)
|
|
31
|
+
* Interface (CLI)
|
|
32
|
+
|
|
33
|
+
== Layered Architecture
|
|
34
|
+
|
|
35
|
+
[source]
|
|
36
|
+
----
|
|
37
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
38
|
+
│ Presentation Layer │
|
|
39
|
+
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
40
|
+
│ │ CLI (Thor) │ │ Reporter │ │ ColorOutput │ │
|
|
41
|
+
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
|
42
|
+
└───────────────────────────┬─────────────────────────────────┘
|
|
43
|
+
│
|
|
44
|
+
┌───────────────────────────▼─────────────────────────────────┐
|
|
45
|
+
│ Application/Service Layer │
|
|
46
|
+
│ ┌──────────────────────────────────────────────────────┐ │
|
|
47
|
+
│ │ ValidationService │ │
|
|
48
|
+
│ │ • Orchestrates validation workflow │ │
|
|
49
|
+
│ │ • Manages profiles │ │
|
|
50
|
+
│ │ • Coordinates validators │ │
|
|
51
|
+
│ └──────────────────────────────────────────────────────┘ │
|
|
52
|
+
└───────────────────────────┬─────────────────────────────────┘
|
|
53
|
+
│
|
|
54
|
+
┌───────────────────────────▼─────────────────────────────────┐
|
|
55
|
+
│ Business Logic Layer │
|
|
56
|
+
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
57
|
+
│ │ Validator │ │ Profile │ │ ChunkRegistry│ │
|
|
58
|
+
│ │ (Base) │ │ │ │ │ │
|
|
59
|
+
│ └──────┬──────┘ └──────────────┘ └──────────────┘ │
|
|
60
|
+
│ │ │
|
|
61
|
+
│ ┌──────▼──────────────────────────────────────────┐ │
|
|
62
|
+
│ │ Chunk Validators │ │
|
|
63
|
+
│ │ ┌────────────┐ ┌────────────┐ ┌───────────┐ │ │
|
|
64
|
+
│ │ │ IHDRVal │ │ PLTEVal │ │ IDATVal │ │ │
|
|
65
|
+
│ │ └────────────┘ └────────────┘ └───────────┘ │ │
|
|
66
|
+
│ │ ┌────────────┐ ┌────────────┐ ┌───────────┐ │ │
|
|
67
|
+
│ │ │ bKGDVal │ │ tRNSVal │ │ MHDRVal │ │ │
|
|
68
|
+
│ │ └────────────┘ └────────────┘ └───────────┘ │ │
|
|
69
|
+
│ │ ... (40+ chunk validators) │ │
|
|
70
|
+
│ └──────────────────────────────────────────────────┘ │
|
|
71
|
+
└───────────────────────────┬─────────────────────────────────┘
|
|
72
|
+
│
|
|
73
|
+
┌───────────────────────────▼─────────────────────────────────┐
|
|
74
|
+
│ Domain Model Layer │
|
|
75
|
+
│ ┌──────────────────────────────────────────────────────┐ │
|
|
76
|
+
│ │ Models (Lutaml::Model) │ │
|
|
77
|
+
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
|
|
78
|
+
│ │ │ Chunk │ │ Result │ │ Validation │ │ │
|
|
79
|
+
│ │ │ │ │ │ │ Error │ │ │
|
|
80
|
+
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
|
|
81
|
+
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
|
|
82
|
+
│ │ │ ChunkData │ │ FileInfo │ │ Profile │ │ │
|
|
83
|
+
│ │ │ (various) │ │ │ │ Config │ │ │
|
|
84
|
+
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
|
|
85
|
+
│ └──────────────────────────────────────────────────────┘ │
|
|
86
|
+
└───────────────────────────┬─────────────────────────────────┘
|
|
87
|
+
│
|
|
88
|
+
┌───────────────────────────▼─────────────────────────────────┐
|
|
89
|
+
│ Binary Parsing Layer (BinData) │
|
|
90
|
+
│ ┌──────────────────────────────────────────────────────┐ │
|
|
91
|
+
│ │ Binary Structures │ │
|
|
92
|
+
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
|
|
93
|
+
│ │ │ PngFile │ │ MngFile │ │ JngFile │ │ │
|
|
94
|
+
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
|
|
95
|
+
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
|
|
96
|
+
│ │ │ChunkStruct │ │ IHDRStruct │ │ PLTEStruct │ │ │
|
|
97
|
+
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
|
|
98
|
+
│ │ ... (40+ chunk structures) │ │
|
|
99
|
+
│ └──────────────────────────────────────────────────────┘ │
|
|
100
|
+
│ ┌──────────────────────────────────────────────────────┐ │
|
|
101
|
+
│ │ File Readers │ │
|
|
102
|
+
│ │ ┌────────────┐ ┌────────────┐ │ │
|
|
103
|
+
│ │ │ Streaming │ │ FullLoad │ │ │
|
|
104
|
+
│ │ │ Reader │ │ Reader │ │ │
|
|
105
|
+
│ │ └────────────┘ └────────────┘ │ │
|
|
106
|
+
│ └──────────────────────────────────────────────────────┘ │
|
|
107
|
+
└─────────────────────────────────────────────────────────────┘
|
|
108
|
+
----
|
|
109
|
+
|
|
110
|
+
== Module Structure
|
|
111
|
+
|
|
112
|
+
=== Top-Level Module: `PngConform`
|
|
113
|
+
|
|
114
|
+
All classes are namespaced under `PngConform` to avoid pollution of global namespace.
|
|
115
|
+
|
|
116
|
+
[source,ruby]
|
|
117
|
+
----
|
|
118
|
+
module PngConform
|
|
119
|
+
# Binary parsing layer
|
|
120
|
+
module BinData
|
|
121
|
+
class PngFile < ::BinData::Record
|
|
122
|
+
class MngFile < ::BinData::Record
|
|
123
|
+
class ChunkStructure < ::BinData::Record
|
|
124
|
+
# ... chunk-specific structures
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Domain models
|
|
128
|
+
module Models
|
|
129
|
+
class Chunk < Lutaml::Model::Serializable
|
|
130
|
+
class Result < Lutaml::Model::Serializable
|
|
131
|
+
class ValidationError < Lutaml::Model::Serializable
|
|
132
|
+
class Profile < Lutaml::Model::Serializable
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Business logic - Validators
|
|
136
|
+
module Validators
|
|
137
|
+
class Base
|
|
138
|
+
|
|
139
|
+
module Critical
|
|
140
|
+
class IhdrValidator < Base
|
|
141
|
+
class PlteValidator < Base
|
|
142
|
+
class IdatValidator < Base
|
|
143
|
+
class IendValidator < Base
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
module Ancillary
|
|
147
|
+
class BkgdValidator < Base
|
|
148
|
+
class ChrmValidator < Base
|
|
149
|
+
# ... 30+ ancillary validators
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
module Apng
|
|
153
|
+
class ActlValidator < Base
|
|
154
|
+
class FctlValidator < Base
|
|
155
|
+
class FdatValidator < Base
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
module Mng
|
|
159
|
+
class MhdrValidator < Base
|
|
160
|
+
class MendValidator < Base
|
|
161
|
+
# ... MNG-specific validators
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
module Jng
|
|
165
|
+
class JhdrValidator < Base
|
|
166
|
+
class JdatValidator < Base
|
|
167
|
+
# ... JNG-specific validators
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Services/Application logic
|
|
172
|
+
module Services
|
|
173
|
+
class ValidationService
|
|
174
|
+
class ProfileManager
|
|
175
|
+
class ChunkRegistry
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Infrastructure
|
|
179
|
+
module Readers
|
|
180
|
+
class StreamingReader
|
|
181
|
+
class FullLoadReader
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
module Reporters
|
|
185
|
+
class PngcheckReporter
|
|
186
|
+
class VerboseReporter
|
|
187
|
+
class JsonReporter
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Presentation
|
|
191
|
+
class CLI < Thor
|
|
192
|
+
end
|
|
193
|
+
----
|
|
194
|
+
|
|
195
|
+
== Core Classes and Responsibilities
|
|
196
|
+
|
|
197
|
+
=== Binary Parsing Layer
|
|
198
|
+
|
|
199
|
+
==== `PngConform::BinData::PngFile`
|
|
200
|
+
|
|
201
|
+
*Responsibility*: Binary structure definition for PNG files
|
|
202
|
+
|
|
203
|
+
*MECE*: Exclusively handles binary format definition
|
|
204
|
+
|
|
205
|
+
* Signature validation
|
|
206
|
+
* Chunk sequence structure
|
|
207
|
+
* Endianness handling
|
|
208
|
+
|
|
209
|
+
==== `PngConform::BinData::ChunkStructure`
|
|
210
|
+
|
|
211
|
+
*Responsibility*: Generic chunk binary structure
|
|
212
|
+
|
|
213
|
+
*MECE*: Exclusively handles chunk-level binary format
|
|
214
|
+
|
|
215
|
+
* Length field (4 bytes, big-endian)
|
|
216
|
+
* Type field (4 bytes, ASCII)
|
|
217
|
+
* Data field (variable)
|
|
218
|
+
* CRC field (4 bytes)
|
|
219
|
+
|
|
220
|
+
==== `PngConform::Readers::StreamingReader`
|
|
221
|
+
|
|
222
|
+
*Responsibility*: Memory-efficient chunk-by-chunk reading
|
|
223
|
+
|
|
224
|
+
*MECE*: Exclusively handles streaming I/O
|
|
225
|
+
|
|
226
|
+
* Reads chunks one at a time
|
|
227
|
+
* Minimal memory footprint
|
|
228
|
+
* Suitable for large files
|
|
229
|
+
|
|
230
|
+
==== `PngConform::Readers::FullLoadReader`
|
|
231
|
+
|
|
232
|
+
*Responsibility*: Load entire file into memory
|
|
233
|
+
|
|
234
|
+
*MECE*: Exclusively handles full-file reading
|
|
235
|
+
|
|
236
|
+
* Reads entire file at once
|
|
237
|
+
* Faster for small/medium files
|
|
238
|
+
* Enables random access to chunks
|
|
239
|
+
|
|
240
|
+
=== Domain Model Layer
|
|
241
|
+
|
|
242
|
+
==== `PngConform::Models::Chunk`
|
|
243
|
+
|
|
244
|
+
*Responsibility*: Domain representation of a PNG chunk
|
|
245
|
+
|
|
246
|
+
*MECE*: Exclusively represents chunk data in domain terms
|
|
247
|
+
|
|
248
|
+
*Attributes*:
|
|
249
|
+
|
|
250
|
+
* `type`: String (4-byte chunk type)
|
|
251
|
+
* `length`: Integer (data length)
|
|
252
|
+
* `data`: Binary string (chunk data)
|
|
253
|
+
* `crc`: Integer (CRC-32 value)
|
|
254
|
+
* `crc_valid`: Boolean (calculated)
|
|
255
|
+
* `position`: Integer (file offset)
|
|
256
|
+
|
|
257
|
+
==== `PngConform::Models::Result`
|
|
258
|
+
|
|
259
|
+
*Responsibility*: Validation result aggregation
|
|
260
|
+
|
|
261
|
+
*MECE*: Exclusively represents validation outcomes
|
|
262
|
+
|
|
263
|
+
*Attributes*:
|
|
264
|
+
|
|
265
|
+
* `file_info`: FileInfo (dimensions, color type, etc.)
|
|
266
|
+
* `chunks`: Array<Chunk> (all chunks found)
|
|
267
|
+
* `errors`: Array<ValidationError> (all errors)
|
|
268
|
+
* `warnings`: Array<ValidationError> (all warnings)
|
|
269
|
+
* `valid`: Boolean (overall validity)
|
|
270
|
+
* `profile`: Profile (validation profile used)
|
|
271
|
+
|
|
272
|
+
==== `PngConform::Models::ValidationError`
|
|
273
|
+
|
|
274
|
+
*Responsibility*: Single validation error/warning
|
|
275
|
+
|
|
276
|
+
*MECE*: Exclusively represents one validation issue
|
|
277
|
+
|
|
278
|
+
*Attributes*:
|
|
279
|
+
|
|
280
|
+
* `severity`: Symbol (:error, :warning, :info)
|
|
281
|
+
* `chunk_type`: String (related chunk)
|
|
282
|
+
* `message`: String (human-readable description)
|
|
283
|
+
* `position`: Integer (file offset)
|
|
284
|
+
* `code`: Symbol (error code for programmatic use)
|
|
285
|
+
|
|
286
|
+
==== `PngConform::Models::Profile`
|
|
287
|
+
|
|
288
|
+
*Responsibility*: Validation profile configuration
|
|
289
|
+
|
|
290
|
+
*MECE*: Exclusively defines what's valid for a profile
|
|
291
|
+
|
|
292
|
+
*Attributes*:
|
|
293
|
+
|
|
294
|
+
* `name`: String (e.g., "PNG baseline", "MNG-VLC")
|
|
295
|
+
* `required_chunks`: Array<String> (must be present)
|
|
296
|
+
* `allowed_chunks`: Array<String> (may be present)
|
|
297
|
+
* `forbidden_chunks`: Array<String> (must not be present)
|
|
298
|
+
* `chunk_rules`: Hash (ordering, dependencies, etc.)
|
|
299
|
+
|
|
300
|
+
=== Business Logic Layer
|
|
301
|
+
|
|
302
|
+
==== `PngConform::Validators::Base`
|
|
303
|
+
|
|
304
|
+
*Responsibility*: Common validator interface and utilities
|
|
305
|
+
|
|
306
|
+
*MECE*: Exclusively provides validator infrastructure
|
|
307
|
+
|
|
308
|
+
*Interface*:
|
|
309
|
+
|
|
310
|
+
[source,ruby]
|
|
311
|
+
----
|
|
312
|
+
class Base
|
|
313
|
+
def validate(chunk, context)
|
|
314
|
+
# Returns array of ValidationError
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
protected
|
|
318
|
+
|
|
319
|
+
def error(message, code:, severity: :error)
|
|
320
|
+
def warning(message, code:)
|
|
321
|
+
def check_range(value, min, max, field_name)
|
|
322
|
+
def check_required_chunk(chunk_type, context)
|
|
323
|
+
end
|
|
324
|
+
----
|
|
325
|
+
|
|
326
|
+
==== `PngConform::Validators::Critical::IhdrValidator`
|
|
327
|
+
|
|
328
|
+
*Responsibility*: IHDR chunk validation
|
|
329
|
+
|
|
330
|
+
*MECE*: Exclusively validates IHDR requirements
|
|
331
|
+
|
|
332
|
+
*Validations*:
|
|
333
|
+
|
|
334
|
+
* Width > 0 and <= 2^31-1
|
|
335
|
+
* Height > 0 and <= 2^31-1
|
|
336
|
+
* Bit depth valid for color type
|
|
337
|
+
* Color type in allowed set (0,2,3,4,6)
|
|
338
|
+
* Compression method = 0
|
|
339
|
+
* Filter method = 0
|
|
340
|
+
* Interlace method = 0 or 1
|
|
341
|
+
* Must be first chunk after signature
|
|
342
|
+
|
|
343
|
+
==== `PngConform::Services::ValidationService`
|
|
344
|
+
|
|
345
|
+
*Responsibility*: Orchestrate validation workflow
|
|
346
|
+
|
|
347
|
+
*MECE*: Exclusively coordinates validation process
|
|
348
|
+
|
|
349
|
+
*Methods*:
|
|
350
|
+
|
|
351
|
+
[source,ruby]
|
|
352
|
+
----
|
|
353
|
+
class ValidationService
|
|
354
|
+
def initialize(profile: :png_baseline)
|
|
355
|
+
|
|
356
|
+
def validate_file(path, streaming: false)
|
|
357
|
+
# Returns Result
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def validate_io(io, streaming: false)
|
|
361
|
+
# Returns Result
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
private
|
|
365
|
+
|
|
366
|
+
def read_chunks(source, streaming)
|
|
367
|
+
def validate_signature(signature)
|
|
368
|
+
def validate_chunk_sequence(chunks)
|
|
369
|
+
def validate_individual_chunks(chunks)
|
|
370
|
+
def aggregate_results(chunks, errors)
|
|
371
|
+
end
|
|
372
|
+
----
|
|
373
|
+
|
|
374
|
+
==== `PngConform::Services::ChunkRegistry`
|
|
375
|
+
|
|
376
|
+
*Responsibility*: Map chunk types to validators
|
|
377
|
+
|
|
378
|
+
*MECE*: Exclusively manages chunk type -> validator mapping
|
|
379
|
+
|
|
380
|
+
*Methods*:
|
|
381
|
+
|
|
382
|
+
[source,ruby]
|
|
383
|
+
----
|
|
384
|
+
class ChunkRegistry
|
|
385
|
+
def self.validator_for(chunk_type)
|
|
386
|
+
# Returns appropriate validator class
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def self.register(chunk_type, validator_class)
|
|
390
|
+
|
|
391
|
+
def self.critical?(chunk_type)
|
|
392
|
+
def self.ancillary?(chunk_type)
|
|
393
|
+
def self.private?(chunk_type)
|
|
394
|
+
def self.safe_to_copy?(chunk_type)
|
|
395
|
+
end
|
|
396
|
+
----
|
|
397
|
+
|
|
398
|
+
=== Presentation Layer
|
|
399
|
+
|
|
400
|
+
==== `PngConform::Reporters::PngcheckReporter`
|
|
401
|
+
|
|
402
|
+
*Responsibility*: Format output to match pngcheck exactly
|
|
403
|
+
|
|
404
|
+
*MECE*: Exclusively handles pngcheck-compatible output
|
|
405
|
+
|
|
406
|
+
*Methods*:
|
|
407
|
+
|
|
408
|
+
[source,ruby]
|
|
409
|
+
----
|
|
410
|
+
class PngcheckReporter
|
|
411
|
+
def format(result, verbose: 0, color: false)
|
|
412
|
+
# Returns formatted string matching pngcheck output
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
private
|
|
416
|
+
|
|
417
|
+
def format_ok(result)
|
|
418
|
+
def format_errors(result)
|
|
419
|
+
def format_chunk_info(chunk, verbose_level)
|
|
420
|
+
def compression_ratio(result)
|
|
421
|
+
end
|
|
422
|
+
----
|
|
423
|
+
|
|
424
|
+
==== `PngConform::CLI`
|
|
425
|
+
|
|
426
|
+
*Responsibility*: Command-line interface
|
|
427
|
+
|
|
428
|
+
*MECE*: Exclusively handles CLI concerns
|
|
429
|
+
|
|
430
|
+
*Commands*:
|
|
431
|
+
|
|
432
|
+
* `validate`: Validate PNG/MNG/JNG files
|
|
433
|
+
* `version`: Show version
|
|
434
|
+
* `help`: Show help
|
|
435
|
+
|
|
436
|
+
*Options* (matching pngcheck):
|
|
437
|
+
|
|
438
|
+
* `-v, --verbose`: Verbose output (can stack: -v, -vv, -vvv, -vvvv)
|
|
439
|
+
* `-q, --quiet`: Quiet mode (errors only)
|
|
440
|
+
* `-c, --color`: Colorized output
|
|
441
|
+
* `-7`: Print tEXt chunks, escape chars >= 128
|
|
442
|
+
* `-t`: Print tEXt chunks
|
|
443
|
+
* `-p`: Print palette contents
|
|
444
|
+
* `-s`: Search for PNGs within file
|
|
445
|
+
* `-x`: Extract PNGs found by search
|
|
446
|
+
* `-w`: Suppress windowBits warning
|
|
447
|
+
|
|
448
|
+
== Data Flow
|
|
449
|
+
|
|
450
|
+
=== Validation Flow
|
|
451
|
+
|
|
452
|
+
[source]
|
|
453
|
+
----
|
|
454
|
+
1. File Input
|
|
455
|
+
↓
|
|
456
|
+
2. Reader Selection (Streaming vs FullLoad)
|
|
457
|
+
↓
|
|
458
|
+
3. Binary Parsing (BinData)
|
|
459
|
+
↓
|
|
460
|
+
4. Domain Model Creation (Chunk objects)
|
|
461
|
+
↓
|
|
462
|
+
5. Profile Loading (based on file type/user choice)
|
|
463
|
+
↓
|
|
464
|
+
6. Signature Validation
|
|
465
|
+
↓
|
|
466
|
+
7. Chunk Sequence Validation
|
|
467
|
+
↓
|
|
468
|
+
8. Individual Chunk Validation (parallel possible)
|
|
469
|
+
↓
|
|
470
|
+
9. Result Aggregation
|
|
471
|
+
↓
|
|
472
|
+
10. Reporter Formatting
|
|
473
|
+
↓
|
|
474
|
+
11. Output to Console/File
|
|
475
|
+
----
|
|
476
|
+
|
|
477
|
+
=== Chunk Validation Flow
|
|
478
|
+
|
|
479
|
+
[source]
|
|
480
|
+
----
|
|
481
|
+
For each chunk:
|
|
482
|
+
1. Look up validator in ChunkRegistry
|
|
483
|
+
2. Parse chunk data into typed structure (BinData)
|
|
484
|
+
3. Create domain Chunk object
|
|
485
|
+
4. Invoke validator with chunk + context
|
|
486
|
+
5. Collect errors/warnings
|
|
487
|
+
6. Add to result
|
|
488
|
+
----
|
|
489
|
+
|
|
490
|
+
=== Context Object
|
|
491
|
+
|
|
492
|
+
The validation context flows through the validation process:
|
|
493
|
+
|
|
494
|
+
[source,ruby]
|
|
495
|
+
----
|
|
496
|
+
class ValidationContext
|
|
497
|
+
attr_reader :file_type # :png, :mng, :jng
|
|
498
|
+
attr_reader :profile # Profile object
|
|
499
|
+
attr_reader :seen_chunks # Hash of chunk types seen
|
|
500
|
+
attr_reader :ihdr_data # IHDR info (width, height, etc.)
|
|
501
|
+
attr_reader :has_plte # Boolean
|
|
502
|
+
attr_reader :color_type # Integer from IHDR
|
|
503
|
+
attr_reader :bit_depth # Integer from IHDR
|
|
504
|
+
attr_reader :chunk_sequence # Array of chunk types
|
|
505
|
+
|
|
506
|
+
def record_chunk(chunk)
|
|
507
|
+
def requires_plte?
|
|
508
|
+
def allows_plte?
|
|
509
|
+
def in_mng_top_level?
|
|
510
|
+
end
|
|
511
|
+
----
|
|
512
|
+
|
|
513
|
+
== Profile System
|
|
514
|
+
|
|
515
|
+
=== Profile Hierarchy
|
|
516
|
+
|
|
517
|
+
[source]
|
|
518
|
+
----
|
|
519
|
+
Profile (Base)
|
|
520
|
+
├── PngBaselineProfile
|
|
521
|
+
├── PngExtendedProfile
|
|
522
|
+
├── ApngProfile
|
|
523
|
+
├── MngVlcProfile
|
|
524
|
+
├── MngLcProfile
|
|
525
|
+
├── MngFullProfile
|
|
526
|
+
└── JngProfile
|
|
527
|
+
----
|
|
528
|
+
|
|
529
|
+
Each profile defines:
|
|
530
|
+
|
|
531
|
+
. Required chunks (must appear)
|
|
532
|
+
. Allowed chunks (may appear)
|
|
533
|
+
. Forbidden chunks (must not appear)
|
|
534
|
+
. Chunk ordering rules
|
|
535
|
+
. Chunk dependency rules
|
|
536
|
+
. Special validation rules
|
|
537
|
+
|
|
538
|
+
=== Profile Configuration Example
|
|
539
|
+
|
|
540
|
+
[source,ruby]
|
|
541
|
+
----
|
|
542
|
+
class PngBaselineProfile < Profile
|
|
543
|
+
def initialize
|
|
544
|
+
super("PNG Baseline")
|
|
545
|
+
|
|
546
|
+
@required_chunks = %w[IHDR IDAT IEND]
|
|
547
|
+
|
|
548
|
+
@allowed_chunks = %w[
|
|
549
|
+
PLTE bKGD cHRM gAMA hIST iCCP pHYs sBIT
|
|
550
|
+
sPLT sRGB tEXt tIME tRNS zTXt
|
|
551
|
+
]
|
|
552
|
+
|
|
553
|
+
@chunk_rules = {
|
|
554
|
+
"IHDR" => { position: :first },
|
|
555
|
+
"PLTE" => { before: "IDAT", after: "IHDR" },
|
|
556
|
+
"IDAT" => { consecutive: true },
|
|
557
|
+
"IEND" => { position: :last }
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
@dependencies = {
|
|
561
|
+
"tRNS" => { requires_one_of: ["IHDR", "PLTE"] }
|
|
562
|
+
}
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
----
|
|
566
|
+
|
|
567
|
+
== Extension Points
|
|
568
|
+
|
|
569
|
+
The architecture supports extension through:
|
|
570
|
+
|
|
571
|
+
. *New Chunk Validators*: Implement `Validators::Base`, register in `ChunkRegistry`
|
|
572
|
+
. *New Profiles*: Subclass `Profile`, define rules
|
|
573
|
+
. *New Reporters*: Implement reporter interface
|
|
574
|
+
. *New Readers*: Implement reader interface
|
|
575
|
+
|
|
576
|
+
== Error Handling Strategy
|
|
577
|
+
|
|
578
|
+
. *Parse Errors*: Caught at BinData layer, converted to ValidationErrors
|
|
579
|
+
. *Validation Errors*: Collected, not thrown (allows complete validation)
|
|
580
|
+
. *I/O Errors*: Bubble up as exceptions (critical failures)
|
|
581
|
+
. *CRC Errors*: Recorded as validation errors
|
|
582
|
+
|
|
583
|
+
== Performance Considerations
|
|
584
|
+
|
|
585
|
+
. *Streaming vs Full-Load*: User choice based on file size
|
|
586
|
+
. *Lazy Validation*: Only validate requested verbosity level
|
|
587
|
+
. *Parallel Validation*: Chunks validated independently (future optimization)
|
|
588
|
+
. *Caching*: Profile objects cached, chunk structures reused
|
|
589
|
+
|
|
590
|
+
== Testing Strategy
|
|
591
|
+
|
|
592
|
+
. *Unit Tests*: Each validator class tested independently
|
|
593
|
+
. *Integration Tests*: Full validation flows tested
|
|
594
|
+
. *Fixture Tests*: Compare output with pngcheck reference
|
|
595
|
+
. *Property Tests*: Generate valid/invalid PNGs programmatically
|
|
596
|
+
. *Performance Tests*: Benchmark streaming vs full-load
|
|
597
|
+
|
|
598
|
+
== Dependencies
|
|
599
|
+
|
|
600
|
+
*Runtime*:
|
|
601
|
+
|
|
602
|
+
* `bindata` (~> 2.5): Binary parsing
|
|
603
|
+
* `lutaml-model` (~> 0.7): Domain models
|
|
604
|
+
* `thor` (~> 1.0): CLI framework
|
|
605
|
+
|
|
606
|
+
*Development*:
|
|
607
|
+
|
|
608
|
+
* `rspec` (~> 3.0): Testing
|
|
609
|
+
* `rubocop` (~> 1.0): Linting
|
|
610
|
+
* `yard` (~> 0.9): Documentation
|
|
611
|
+
|
|
612
|
+
== File Organization
|
|
613
|
+
|
|
614
|
+
[source]
|
|
615
|
+
----
|
|
616
|
+
lib/png_conform/
|
|
617
|
+
├── version.rb
|
|
618
|
+
├── cli.rb
|
|
619
|
+
├── bindata/
|
|
620
|
+
│ ├── png_file.rb
|
|
621
|
+
│ ├── mng_file.rb
|
|
622
|
+
│ ├── jng_file.rb
|
|
623
|
+
│ └── chunks/
|
|
624
|
+
│ ├── ihdr_structure.rb
|
|
625
|
+
│ ├── plte_structure.rb
|
|
626
|
+
│ └── ... (40+ structures)
|
|
627
|
+
├── models/
|
|
628
|
+
│ ├── chunk.rb
|
|
629
|
+
│ ├── result.rb
|
|
630
|
+
│ ├── validation_error.rb
|
|
631
|
+
│ ├── profile.rb
|
|
632
|
+
│ ├── file_info.rb
|
|
633
|
+
│ └── validation_context.rb
|
|
634
|
+
├── validators/
|
|
635
|
+
│ ├── base.rb
|
|
636
|
+
│ ├── critical/
|
|
637
|
+
│ │ ├── ihdr_validator.rb
|
|
638
|
+
│ │ ├── plte_validator.rb
|
|
639
|
+
│ │ ├── idat_validator.rb
|
|
640
|
+
│ │ └── iend_validator.rb
|
|
641
|
+
│ ├── ancillary/
|
|
642
|
+
│ │ └── ... (30+ validators)
|
|
643
|
+
│ ├── apng/
|
|
644
|
+
│ │ └── ... (APNG validators)
|
|
645
|
+
│ ├── mng/
|
|
646
|
+
│ │ └── ... (MNG validators)
|
|
647
|
+
│ └── jng/
|
|
648
|
+
│ └── ... (JNG validators)
|
|
649
|
+
├── services/
|
|
650
|
+
│ ├── validation_service.rb
|
|
651
|
+
│ ├── profile_manager.rb
|
|
652
|
+
│ └── chunk_registry.rb
|
|
653
|
+
├── readers/
|
|
654
|
+
│ ├── streaming_reader.rb
|
|
655
|
+
│ └── full_load_reader.rb
|
|
656
|
+
├── reporters/
|
|
657
|
+
│ ├── pngcheck_reporter.rb
|
|
658
|
+
│ ├── verbose_reporter.rb
|
|
659
|
+
│ └── json_reporter.rb
|
|
660
|
+
└── profiles/
|
|
661
|
+
├── png_baseline.rb
|
|
662
|
+
├── png_extended.rb
|
|
663
|
+
├── apng.rb
|
|
664
|
+
├── mng_vlc.rb
|
|
665
|
+
├── mng_lc.rb
|
|
666
|
+
└── jng.rb
|
|
667
|
+
----
|
|
668
|
+
|
|
669
|
+
== Summary
|
|
670
|
+
|
|
671
|
+
This architecture provides:
|
|
672
|
+
|
|
673
|
+
* ✅ *Strict OO*: Each class has single responsibility, clear interfaces
|
|
674
|
+
* ✅ *MECE*: No overlapping responsibilities, complete coverage
|
|
675
|
+
* ✅ *Separation of Concerns*: Clear layer boundaries
|
|
676
|
+
* ✅ *Abstraction*: Implementation details hidden
|
|
677
|
+
* ✅ *Extensibility*: Easy to add new chunks, profiles, reporters
|
|
678
|
+
* ✅ *Testability*: Each component independently testable
|
|
679
|
+
* ✅ *Maintainability*: Clear structure, well-documented
|
|
680
|
+
* ✅ *Performance*: Both streaming and full-load modes
|
|
681
|
+
* ✅ *Compatibility*: Output matches pngcheck exactly
|