Module: Pdfrb::Color::ICCValidator

Defined in:
lib/pdfrb/color/icc_validator.rb

Overview

Validates an ICC profile binary against the structural rules in ICC.1:2022 ยง7. The validator checks:

* Profile size >= 128 (header) + 4 (tag count)
* 'acsp' signature at offset 36
* Profile version is 2.x, 4.x, or 5.x
* Device class is one of the known classes
* Color space is one of the known spaces
* Tag table offsets don't exceed the file size

Returns an Array of Violation strings (empty if valid).

Constant Summary collapse

VALID_VERSIONS =
[(2.0..2.99), (4.0..4.99), (5.0..5.99)].freeze
VALID_DEVICE_CLASSES =
%w[
  scnr mntr prtr link spac abst nmcl nkpr
].freeze
VALID_COLOR_SPACES =
%w[
  XYZ Lab Luv YCbCr Yxy RGB GRAY HSV HLS CMYK CMY 2CLR 3CLR
  4CLR 5CLR 6CLR 7CLR 8CLR 9CLR ACLR BCLR CCLR DCLR ECLR FCLR
].freeze

Class Method Summary collapse

Class Method Details

.valid?(profile_bytes) ⇒ Boolean

Returns:

  • (Boolean)


64
65
66
# File 'lib/pdfrb/color/icc_validator.rb', line 64

def valid?(profile_bytes)
  validate(profile_bytes).empty?
end

.validate(profile_bytes) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/pdfrb/color/icc_validator.rb', line 27

def validate(profile_bytes)
  violations = []
  unless profile_bytes.is_a?(::String) && profile_bytes.bytesize >= 132
    violations << "ICC profile too small (need >= 132 bytes, got #{profile_bytes&.bytesize})"
    return violations
  end

  unless profile_bytes.byteslice(36, 4) == "acsp"
    violations << "ICC profile signature at offset 36 is not 'acsp'"
  end

  version_hi = profile_bytes.getbyte(8)
  version_lo = profile_bytes.getbyte(9)
  version = (version_hi * 1.0) + (version_lo / 16.0 / 10.0)
  unless VALID_VERSIONS.any? { |range| range.cover?(version) }
    violations << "ICC profile version #{version} not in 2.x/4.x/5.x"
  end

  device_class = profile_bytes.byteslice(12, 4).to_s.strip
  unless VALID_DEVICE_CLASSES.include?(device_class)
    violations << "ICC device class '#{device_class}' is not recognised"
  end

  color_space = profile_bytes.byteslice(16, 4).to_s.strip
  unless VALID_COLOR_SPACES.include?(color_space)
    violations << "ICC color space '#{color_space}' is not recognised"
  end

  # Tag table sanity check
  tag_count = profile_bytes.unpack1("N", offset: 128)
  if tag_count > 256
    violations << "ICC tag count #{tag_count} looks bogus (max 256)"
  end

  violations
end