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,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PngConform
|
|
4
|
+
module Validators
|
|
5
|
+
module Mng
|
|
6
|
+
# Validates MNG ENDL (End loop) chunks
|
|
7
|
+
#
|
|
8
|
+
# The ENDL chunk marks the end of a loop started by a LOOP chunk.
|
|
9
|
+
#
|
|
10
|
+
# Validation rules:
|
|
11
|
+
# - Must appear after MHDR
|
|
12
|
+
# - Length must be 1 byte
|
|
13
|
+
# - Nesting level must match corresponding LOOP
|
|
14
|
+
# - Must have corresponding LOOP before it
|
|
15
|
+
# - Must appear before MEND
|
|
16
|
+
class EndlValidator < BaseValidator
|
|
17
|
+
EXPECTED_LENGTH = 1
|
|
18
|
+
|
|
19
|
+
def validate
|
|
20
|
+
return false unless check_crc
|
|
21
|
+
return false unless check_length(EXPECTED_LENGTH)
|
|
22
|
+
|
|
23
|
+
unless context.retrieve(:mhdr_present)
|
|
24
|
+
add_error("ENDL must appear after MHDR")
|
|
25
|
+
return false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
if context.seen?("MEND")
|
|
29
|
+
add_error("ENDL must appear before MEND")
|
|
30
|
+
return false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
unless context.retrieve(:loop_present)
|
|
34
|
+
add_error("ENDL must appear after LOOP")
|
|
35
|
+
return false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Nesting level (1 byte)
|
|
39
|
+
nesting_level = chunk.chunk_data.getbyte(0)
|
|
40
|
+
|
|
41
|
+
# Validate loop nesting
|
|
42
|
+
loop_stack = context.retrieve(:loop_stack) || []
|
|
43
|
+
|
|
44
|
+
if loop_stack.empty?
|
|
45
|
+
add_error("ENDL without corresponding LOOP")
|
|
46
|
+
return false
|
|
47
|
+
elsif loop_stack.last != nesting_level
|
|
48
|
+
add_error(
|
|
49
|
+
"ENDL nesting level (#{nesting_level}) does not match " \
|
|
50
|
+
"LOOP nesting level (#{loop_stack.last})",
|
|
51
|
+
)
|
|
52
|
+
return false
|
|
53
|
+
else
|
|
54
|
+
loop_stack.pop
|
|
55
|
+
context.store(:loop_stack, loop_stack)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
context.store(:endl_nesting_level, nesting_level)
|
|
59
|
+
context.store(:endl_present, true)
|
|
60
|
+
true
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PngConform
|
|
4
|
+
module Validators
|
|
5
|
+
module Mng
|
|
6
|
+
# Validates MNG FRAM (Frame parameters) chunks
|
|
7
|
+
#
|
|
8
|
+
# The FRAM chunk sets frame parameters including framing mode, subframe
|
|
9
|
+
# name, change interframe delay, change timeout, change layer clipping,
|
|
10
|
+
# and change sync ID.
|
|
11
|
+
#
|
|
12
|
+
# Validation rules:
|
|
13
|
+
# - Must appear after MHDR
|
|
14
|
+
# - Length can be 0, 1, or variable (with parameters)
|
|
15
|
+
# - Framing mode: 0-4
|
|
16
|
+
# - All parameters optional based on change flags
|
|
17
|
+
# - Must appear before MEND
|
|
18
|
+
class FramValidator < BaseValidator
|
|
19
|
+
VALID_FRAMING_MODES = (0..4).to_a.freeze
|
|
20
|
+
|
|
21
|
+
def validate
|
|
22
|
+
return false unless check_crc
|
|
23
|
+
|
|
24
|
+
unless context.retrieve(:mhdr_present)
|
|
25
|
+
add_error("FRAM must appear after MHDR")
|
|
26
|
+
return false
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
if context.seen?("MEND")
|
|
30
|
+
add_error("FRAM must appear before MEND")
|
|
31
|
+
return false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
data = chunk.chunk_data
|
|
35
|
+
|
|
36
|
+
# Empty FRAM is valid
|
|
37
|
+
if data.empty?
|
|
38
|
+
context.store(:fram_present, true)
|
|
39
|
+
return true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
pos = 0
|
|
43
|
+
|
|
44
|
+
# Framing mode (1 byte)
|
|
45
|
+
if pos < data.length
|
|
46
|
+
framing_mode = data.getbyte(pos)
|
|
47
|
+
pos += 1
|
|
48
|
+
|
|
49
|
+
unless VALID_FRAMING_MODES.include?(framing_mode)
|
|
50
|
+
add_error(
|
|
51
|
+
"Invalid FRAM framing mode: #{framing_mode} (must be 0-4)",
|
|
52
|
+
)
|
|
53
|
+
return false
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
context.store(:fram_framing_mode, framing_mode)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Parse optional change flags and parameters
|
|
60
|
+
if pos < data.length
|
|
61
|
+
# Subframe name length (1 byte) + name
|
|
62
|
+
name_length = data.getbyte(pos)
|
|
63
|
+
pos += 1
|
|
64
|
+
|
|
65
|
+
if pos + name_length > data.length
|
|
66
|
+
add_error("FRAM subframe name extends beyond chunk")
|
|
67
|
+
return false
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
if name_length.positive?
|
|
71
|
+
subframe_name = data[pos, name_length]
|
|
72
|
+
pos += name_length
|
|
73
|
+
context.store(:fram_subframe_name, subframe_name)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Remaining parameters are change flags and their values
|
|
78
|
+
# We don't need to parse all of them in detail for basic validation
|
|
79
|
+
# Just ensure the chunk length is reasonable
|
|
80
|
+
if pos < data.length
|
|
81
|
+
# Store that FRAM has parameters
|
|
82
|
+
context.store(:fram_has_parameters, true)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
context.store(:fram_present, true)
|
|
86
|
+
true
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PngConform
|
|
4
|
+
module Validators
|
|
5
|
+
module Mng
|
|
6
|
+
# Validates MNG LOOP (Loop control) chunks
|
|
7
|
+
#
|
|
8
|
+
# The LOOP chunk marks the beginning of a loop in an MNG animation.
|
|
9
|
+
#
|
|
10
|
+
# Validation rules:
|
|
11
|
+
# - Must appear after MHDR
|
|
12
|
+
# - Length must be 5 or 6 bytes
|
|
13
|
+
# - Nesting level must be >= 0
|
|
14
|
+
# - Iteration count must be >= 0 (0 = infinite)
|
|
15
|
+
# - Must have corresponding ENDL
|
|
16
|
+
# - Must appear before MEND
|
|
17
|
+
class LoopValidator < BaseValidator
|
|
18
|
+
VALID_LENGTHS = [5, 6].freeze
|
|
19
|
+
|
|
20
|
+
def validate
|
|
21
|
+
return false unless check_crc
|
|
22
|
+
|
|
23
|
+
unless context.retrieve(:mhdr_present)
|
|
24
|
+
add_error("LOOP must appear after MHDR")
|
|
25
|
+
return false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
data = chunk.chunk_data
|
|
29
|
+
unless VALID_LENGTHS.include?(data.length)
|
|
30
|
+
add_error(
|
|
31
|
+
"LOOP chunk must be 5 or 6 bytes, got #{data.length}",
|
|
32
|
+
)
|
|
33
|
+
return false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if context.seen?("MEND")
|
|
37
|
+
add_error("LOOP must appear before MEND")
|
|
38
|
+
return false
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Nesting level (1 byte)
|
|
42
|
+
nesting_level = data.getbyte(0)
|
|
43
|
+
|
|
44
|
+
# Iteration count (4 bytes)
|
|
45
|
+
iteration_count = data[1, 4].unpack1("N")
|
|
46
|
+
|
|
47
|
+
context.store(:loop_nesting_level, nesting_level)
|
|
48
|
+
context.store(:loop_iteration_count, iteration_count)
|
|
49
|
+
|
|
50
|
+
if data.length == 6
|
|
51
|
+
# Termination condition (1 byte)
|
|
52
|
+
termination = data.getbyte(5)
|
|
53
|
+
|
|
54
|
+
unless (0..3).cover?(termination)
|
|
55
|
+
add_error(
|
|
56
|
+
"LOOP termination condition must be 0-3, got #{termination}",
|
|
57
|
+
)
|
|
58
|
+
return false
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
context.store(:loop_termination, termination)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Track loop nesting
|
|
65
|
+
loop_stack = context.retrieve(:loop_stack) || []
|
|
66
|
+
loop_stack.push(nesting_level)
|
|
67
|
+
context.store(:loop_stack, loop_stack)
|
|
68
|
+
|
|
69
|
+
context.store(:loop_present, true)
|
|
70
|
+
true
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PngConform
|
|
4
|
+
module Validators
|
|
5
|
+
module Mng
|
|
6
|
+
# Validator for MEND (MNG End) chunk
|
|
7
|
+
#
|
|
8
|
+
# The MEND chunk marks the end of a MNG datastream.
|
|
9
|
+
# It must be the last chunk in the file and contains no data.
|
|
10
|
+
#
|
|
11
|
+
class MendValidator < BaseValidator
|
|
12
|
+
CHUNK_TYPE = "MEND"
|
|
13
|
+
EXPECTED_LENGTH = 0
|
|
14
|
+
|
|
15
|
+
def validate
|
|
16
|
+
return false unless check_crc
|
|
17
|
+
return false unless check_length(EXPECTED_LENGTH)
|
|
18
|
+
|
|
19
|
+
# MEND must have MHDR before it
|
|
20
|
+
unless context.seen?("MHDR")
|
|
21
|
+
add_error("MEND requires MHDR chunk")
|
|
22
|
+
return false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# MEND should be last chunk (this will be validated by orchestration)
|
|
26
|
+
true
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PngConform
|
|
4
|
+
module Validators
|
|
5
|
+
module Mng
|
|
6
|
+
# Validator for MHDR (MNG Header) chunk
|
|
7
|
+
#
|
|
8
|
+
# The MHDR chunk is the first chunk in a MNG datastream and contains
|
|
9
|
+
# basic information about the MNG file.
|
|
10
|
+
#
|
|
11
|
+
# Structure (28 bytes):
|
|
12
|
+
# - frame_width (4 bytes): Frame width in pixels
|
|
13
|
+
# - frame_height (4 bytes): Frame height in pixels
|
|
14
|
+
# - ticks_per_second (4 bytes): Nominal tick rate
|
|
15
|
+
# - nominal_layer_count (4 bytes): Nominal layer count
|
|
16
|
+
# - nominal_frame_count (4 bytes): Nominal frame count
|
|
17
|
+
# - nominal_play_time (4 bytes): Nominal play time
|
|
18
|
+
# - simplicity_profile (4 bytes): Simplicity profile flags
|
|
19
|
+
#
|
|
20
|
+
class MhdrValidator < BaseValidator
|
|
21
|
+
CHUNK_TYPE = "MHDR"
|
|
22
|
+
EXPECTED_LENGTH = 28
|
|
23
|
+
|
|
24
|
+
def validate
|
|
25
|
+
return false unless check_crc
|
|
26
|
+
return false unless check_length(EXPECTED_LENGTH)
|
|
27
|
+
|
|
28
|
+
# MHDR must be first chunk
|
|
29
|
+
unless context.chunks_seen.empty?
|
|
30
|
+
add_error("MHDR must be the first chunk in MNG file")
|
|
31
|
+
return false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
data = chunk.chunk_data
|
|
35
|
+
|
|
36
|
+
# Extract fields
|
|
37
|
+
frame_width = data[0..3].unpack1("N")
|
|
38
|
+
frame_height = data[4..7].unpack1("N")
|
|
39
|
+
ticks_per_second = data[8..11].unpack1("N")
|
|
40
|
+
nominal_layer_count = data[12..15].unpack1("N")
|
|
41
|
+
nominal_frame_count = data[16..19].unpack1("N")
|
|
42
|
+
nominal_play_time = data[20..23].unpack1("N")
|
|
43
|
+
simplicity_profile = data[24..27].unpack1("N")
|
|
44
|
+
|
|
45
|
+
# Validate dimensions
|
|
46
|
+
valid = true
|
|
47
|
+
if frame_width.zero? || frame_height.zero?
|
|
48
|
+
add_error("MHDR frame dimensions must be > 0")
|
|
49
|
+
valid = false
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if valid
|
|
53
|
+
# Store in context
|
|
54
|
+
context.store(:mhdr_frame_width, frame_width)
|
|
55
|
+
context.store(:mhdr_frame_height, frame_height)
|
|
56
|
+
context.store(:mhdr_ticks_per_second, ticks_per_second)
|
|
57
|
+
context.store(:mhdr_nominal_layer_count, nominal_layer_count)
|
|
58
|
+
context.store(:mhdr_nominal_frame_count, nominal_frame_count)
|
|
59
|
+
context.store(:mhdr_nominal_play_time, nominal_play_time)
|
|
60
|
+
context.store(:mhdr_simplicity_profile, simplicity_profile)
|
|
61
|
+
context.store(:mhdr_present, true)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
valid
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PngConform
|
|
4
|
+
module Validators
|
|
5
|
+
module Mng
|
|
6
|
+
# Validates MNG MOVE (Move object) chunks
|
|
7
|
+
#
|
|
8
|
+
# The MOVE chunk moves an object to a new location.
|
|
9
|
+
#
|
|
10
|
+
# Validation rules:
|
|
11
|
+
# - Must appear after MHDR
|
|
12
|
+
# - Must appear before MEND
|
|
13
|
+
# - Length must be 13 bytes
|
|
14
|
+
# - Contains first object ID, last object ID, move type, and X/Y offsets
|
|
15
|
+
class MoveValidator < BaseValidator
|
|
16
|
+
EXPECTED_LENGTH = 13
|
|
17
|
+
|
|
18
|
+
def validate
|
|
19
|
+
return false unless check_crc
|
|
20
|
+
|
|
21
|
+
unless context.retrieve(:mhdr_present)
|
|
22
|
+
add_error("MOVE must appear after MHDR")
|
|
23
|
+
return false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if context.seen?("MEND")
|
|
27
|
+
add_error("MOVE must appear before MEND")
|
|
28
|
+
return false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
data = chunk.chunk_data
|
|
32
|
+
|
|
33
|
+
unless data.length == EXPECTED_LENGTH
|
|
34
|
+
add_error(
|
|
35
|
+
"MOVE chunk must be #{EXPECTED_LENGTH} bytes, " \
|
|
36
|
+
"got #{data.length}",
|
|
37
|
+
)
|
|
38
|
+
return false
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Parse MOVE data
|
|
42
|
+
# First object ID (2 bytes)
|
|
43
|
+
# Last object ID (2 bytes)
|
|
44
|
+
# Move type (1 byte)
|
|
45
|
+
# X offset (4 bytes, signed)
|
|
46
|
+
# Y offset (4 bytes, signed)
|
|
47
|
+
first_id, last_id, move_type, x_offset, y_offset =
|
|
48
|
+
data.unpack("nnCl>l>")
|
|
49
|
+
|
|
50
|
+
context.store(:move_first_id, first_id)
|
|
51
|
+
context.store(:move_last_id, last_id)
|
|
52
|
+
context.store(:move_type, move_type)
|
|
53
|
+
context.store(:move_x_offset, x_offset)
|
|
54
|
+
context.store(:move_y_offset, y_offset)
|
|
55
|
+
context.store(:move_present, true)
|
|
56
|
+
true
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PngConform
|
|
4
|
+
module Validators
|
|
5
|
+
module Mng
|
|
6
|
+
# Validates MNG SAVE (Save state) chunks
|
|
7
|
+
#
|
|
8
|
+
# The SAVE chunk saves the current MNG state for later restoration with SEEK.
|
|
9
|
+
#
|
|
10
|
+
# Validation rules:
|
|
11
|
+
# - Must appear after MHDR
|
|
12
|
+
# - Length must be 0 bytes (empty)
|
|
13
|
+
# - Must appear before MEND
|
|
14
|
+
class SaveValidator < BaseValidator
|
|
15
|
+
EXPECTED_LENGTH = 0
|
|
16
|
+
|
|
17
|
+
def validate
|
|
18
|
+
return false unless check_crc
|
|
19
|
+
return false unless check_length(EXPECTED_LENGTH)
|
|
20
|
+
|
|
21
|
+
# Must appear after MHDR
|
|
22
|
+
unless context.retrieve(:mhdr_present)
|
|
23
|
+
add_error("SAVE must appear after MHDR")
|
|
24
|
+
return false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Must appear before MEND
|
|
28
|
+
if context.seen?("MEND")
|
|
29
|
+
add_error("SAVE must appear before MEND")
|
|
30
|
+
return false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context.store(:save_present, true)
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PngConform
|
|
4
|
+
module Validators
|
|
5
|
+
module Mng
|
|
6
|
+
# Validates MNG SEEK (Seek to saved state) chunks
|
|
7
|
+
#
|
|
8
|
+
# The SEEK chunk restores the MNG state previously saved with SAVE.
|
|
9
|
+
#
|
|
10
|
+
# Validation rules:
|
|
11
|
+
# - Must appear after MHDR
|
|
12
|
+
# - Length must be 0 bytes (empty)
|
|
13
|
+
# - Should have corresponding SAVE before it
|
|
14
|
+
# - Must appear before MEND
|
|
15
|
+
class SeekValidator < BaseValidator
|
|
16
|
+
EXPECTED_LENGTH = 0
|
|
17
|
+
|
|
18
|
+
def validate
|
|
19
|
+
return false unless check_crc
|
|
20
|
+
return false unless check_length(EXPECTED_LENGTH)
|
|
21
|
+
|
|
22
|
+
unless context.retrieve(:mhdr_present)
|
|
23
|
+
add_error("SEEK must appear after MHDR")
|
|
24
|
+
return false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
if context.seen?("MEND")
|
|
28
|
+
add_error("SEEK must appear before MEND")
|
|
29
|
+
return false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
unless context.retrieve(:save_present)
|
|
33
|
+
add_warning("SEEK without corresponding SAVE")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context.store(:seek_present, true)
|
|
37
|
+
true
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PngConform
|
|
4
|
+
module Validators
|
|
5
|
+
module Mng
|
|
6
|
+
# Validates MNG SHOW (Show object) chunks
|
|
7
|
+
#
|
|
8
|
+
# The SHOW chunk makes a previously defined object visible.
|
|
9
|
+
#
|
|
10
|
+
# Validation rules:
|
|
11
|
+
# - Must appear after MHDR
|
|
12
|
+
# - Must appear before MEND
|
|
13
|
+
# - Length must be 0 or 2 bytes
|
|
14
|
+
# - If 2 bytes, contains object ID to show
|
|
15
|
+
class ShowValidator < BaseValidator
|
|
16
|
+
VALID_LENGTHS = [0, 2].freeze
|
|
17
|
+
|
|
18
|
+
def validate
|
|
19
|
+
return false unless check_crc
|
|
20
|
+
|
|
21
|
+
unless context.retrieve(:mhdr_present)
|
|
22
|
+
add_error("SHOW must appear after MHDR")
|
|
23
|
+
return false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if context.seen?("MEND")
|
|
27
|
+
add_error("SHOW must appear before MEND")
|
|
28
|
+
return false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
data = chunk.chunk_data
|
|
32
|
+
|
|
33
|
+
unless VALID_LENGTHS.include?(data.length)
|
|
34
|
+
add_error(
|
|
35
|
+
"SHOW chunk must be 0 or 2 bytes, got #{data.length}",
|
|
36
|
+
)
|
|
37
|
+
return false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
if data.length == 2
|
|
41
|
+
# Object ID (2 bytes)
|
|
42
|
+
object_id = data.unpack1("n")
|
|
43
|
+
context.store(:show_object_id, object_id)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
context.store(:show_present, true)
|
|
47
|
+
true
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PngConform
|
|
4
|
+
module Validators
|
|
5
|
+
module Mng
|
|
6
|
+
# Validates MNG TERM (Termination action) chunks
|
|
7
|
+
#
|
|
8
|
+
# The TERM chunk specifies the action to take when the animation terminates.
|
|
9
|
+
#
|
|
10
|
+
# Validation rules:
|
|
11
|
+
# - Must appear after MHDR
|
|
12
|
+
# - Length must be 1 or 10 bytes
|
|
13
|
+
# - Termination action: 0-3
|
|
14
|
+
# - Action after iterations: 0-2
|
|
15
|
+
# - Must appear before MEND
|
|
16
|
+
class TermValidator < BaseValidator
|
|
17
|
+
VALID_LENGTHS = [1, 10].freeze
|
|
18
|
+
VALID_TERMINATION_ACTIONS = (0..3).to_a.freeze
|
|
19
|
+
VALID_AFTER_ITERATIONS = (0..2).to_a.freeze
|
|
20
|
+
|
|
21
|
+
def validate
|
|
22
|
+
return false unless check_crc
|
|
23
|
+
|
|
24
|
+
unless context.retrieve(:mhdr_present)
|
|
25
|
+
add_error("TERM must appear after MHDR")
|
|
26
|
+
return false
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
data = chunk.chunk_data
|
|
30
|
+
unless VALID_LENGTHS.include?(data.length)
|
|
31
|
+
add_error(
|
|
32
|
+
"TERM chunk must be 1 or 10 bytes, got #{data.length}",
|
|
33
|
+
)
|
|
34
|
+
return false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if context.seen?("MEND")
|
|
38
|
+
add_error("TERM must appear before MEND")
|
|
39
|
+
return false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Termination action (1 byte)
|
|
43
|
+
termination_action = data.getbyte(0)
|
|
44
|
+
|
|
45
|
+
unless VALID_TERMINATION_ACTIONS.include?(termination_action)
|
|
46
|
+
add_error(
|
|
47
|
+
"Invalid TERM termination action: #{termination_action} " \
|
|
48
|
+
"(must be 0-3)",
|
|
49
|
+
)
|
|
50
|
+
return false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
context.store(:term_termination_action, termination_action)
|
|
54
|
+
|
|
55
|
+
if data.length == 10
|
|
56
|
+
# Action after iterations (1 byte)
|
|
57
|
+
after_iterations = data.getbyte(1)
|
|
58
|
+
|
|
59
|
+
unless VALID_AFTER_ITERATIONS.include?(after_iterations)
|
|
60
|
+
add_error(
|
|
61
|
+
"Invalid TERM action after iterations: #{after_iterations} " \
|
|
62
|
+
"(must be 0-2)",
|
|
63
|
+
)
|
|
64
|
+
return false
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Delay (4 bytes)
|
|
68
|
+
delay = data[2, 4].unpack1("N")
|
|
69
|
+
|
|
70
|
+
# Maximum iterations (4 bytes)
|
|
71
|
+
max_iterations = data[6, 4].unpack1("N")
|
|
72
|
+
|
|
73
|
+
context.store(:term_after_iterations, after_iterations)
|
|
74
|
+
context.store(:term_delay, delay)
|
|
75
|
+
context.store(:term_max_iterations, max_iterations)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
context.store(:term_present, true)
|
|
79
|
+
true
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|