chunky_png 1.3.8 → 1.3.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: de005461c2c1664cf8c8328e5769415e26b365fe
4
- data.tar.gz: 0cf85db842b733efc160097379b39af523930694
3
+ metadata.gz: 2cfe87146ba99f2c0cba5133d6f6009d4277cb28
4
+ data.tar.gz: a9ac39bc20556d7a8c26662e0fffb9e9fcd39dc2
5
5
  SHA512:
6
- metadata.gz: 68ceb6232a10ad75694c1f908b2c7a16d9bb5b0f9c5cf0f2c017b02b60e5655e99341ab44d6135975888b8855ced580054365d670494f64ea35eeee2eabc1ed6
7
- data.tar.gz: 791b4ce5e7e2228b5d3ba0c41edb1f9d0b649bbe15f6ea808bca5c716fad10205f1cff408a3f79daf8389664e58124f4e53e74018ece8da38afed0cf685e3ce7
6
+ metadata.gz: '0408de85990b63ddd1fe099ab6f35a2abe71b23b5d2aaa903de201f9e75e488a3f0c32cc379e01459eb248537d5867df00cf70dd5fc10a853e1e9a47dd8ad035'
7
+ data.tar.gz: 5ad5543e38c03b1a58c9dc777be40cf744fbe2c8fe5270884eacc51dcfbe497eb00f706a6d5b547699ed50ffa002e51b5c58f1b656c6ed738596212754f20c4b
@@ -7,7 +7,11 @@ The file documents the changes to this library over the different versions.
7
7
 
8
8
  === Unreleased changes
9
9
 
10
- - Nothing yet!
10
+ *Noething yet*
11
+
12
+ === 1.3.9 - 2018-01-23
13
+
14
+ - Add support for reading and writing an international textual data (iTXt chunks).
11
15
 
12
16
  === 1.3.8 - 2016-08-31
13
17
 
@@ -7,7 +7,7 @@ require 'enumerator'
7
7
  # ChunkyPNG - the pure ruby library to access PNG files.
8
8
  #
9
9
  # The ChunkyPNG module defines some constants that are used in the
10
- # PNG specification, specifies some exception classes, and serves as
10
+ # PNG specification, specifies some exception classes, and serves as
11
11
  # a namespace for all the other modules and classes in this library.
12
12
  #
13
13
  # {ChunkyPNG::Image}:: class to represent PNG images, including metadata.
@@ -27,27 +27,27 @@ module ChunkyPNG
27
27
  # PNG international standard defined constants
28
28
  ###################################################
29
29
 
30
- # Indicates that the PNG image uses grayscale colors, i.e. only a
30
+ # Indicates that the PNG image uses grayscale colors, i.e. only a
31
31
  # single teint channel.
32
32
  # @private
33
33
  COLOR_GRAYSCALE = 0
34
-
34
+
35
35
  # Indicates that the PNG image uses true color, composed of a red
36
36
  # green and blue channel.
37
37
  # @private
38
38
  COLOR_TRUECOLOR = 2
39
-
39
+
40
40
  # Indicates that the PNG image uses indexed colors, where the values
41
41
  # point to colors defined on a palette.
42
42
  # @private
43
43
  COLOR_INDEXED = 3
44
-
44
+
45
45
  # Indicates that the PNG image uses grayscale colors with opacity, i.e.
46
46
  # a teint channel with an alpha channel.
47
47
  # @private
48
48
  COLOR_GRAYSCALE_ALPHA = 4
49
-
50
- # Indicates that the PNG image uses true color with opacity, composed of
49
+
50
+ # Indicates that the PNG image uses true color with opacity, composed of
51
51
  # a red, green and blue channel, and an alpha value.
52
52
  # @private
53
53
  COLOR_TRUECOLOR_ALPHA = 6
@@ -57,12 +57,22 @@ module ChunkyPNG
57
57
  # @private
58
58
  COMPRESSION_DEFAULT = 0
59
59
 
60
+ # Indicates that the PNG chunk content is not compressed
61
+ # flag used in iTXt chunk
62
+ # @private
63
+ UNCOMPRESSED_CONTENT = 0
64
+
65
+ # Indicates that the PNG chunk content is compressed
66
+ # flag used in iTXt chunk
67
+ # @private
68
+ COMPRESSED_CONTENT = 1
69
+
60
70
  # Indicates that the image does not use interlacing.
61
71
  # @private
62
72
  INTERLACING_NONE = 0
63
-
73
+
64
74
  # Indicates that the image uses Adam7 interlacing.
65
- # @private
75
+ # @private
66
76
  INTERLACING_ADAM7 = 1
67
77
 
68
78
  ### Filter method constants
@@ -75,19 +85,19 @@ module ChunkyPNG
75
85
  # Indicates that no filtering is used for the scanline.
76
86
  # @private
77
87
  FILTER_NONE = 0
78
-
88
+
79
89
  # Indicates that SUB filtering is used for the scanline.
80
90
  # @private
81
91
  FILTER_SUB = 1
82
-
92
+
83
93
  # Indicates that UP filtering is used for the scanline.
84
94
  # @private
85
95
  FILTER_UP = 2
86
-
96
+
87
97
  # Indicates that AVERAGE filtering is used for the scanline.
88
98
  # @private
89
99
  FILTER_AVERAGE = 3
90
-
100
+
91
101
  # Indicates that PAETH filtering is used for the scanline.
92
102
  # @private
93
103
  FILTER_PAETH = 4
@@ -104,7 +114,7 @@ module ChunkyPNG
104
114
  class NotSupported < ChunkyPNG::Exception
105
115
  end
106
116
 
107
- # Exception that is raised if the PNG signature is not encountered at the
117
+ # Exception that is raised if the PNG signature is not encountered at the
108
118
  # beginning of the file.
109
119
  class SignatureMismatch < ChunkyPNG::Exception
110
120
  end
@@ -113,6 +123,10 @@ module ChunkyPNG
113
123
  class CRCMismatch < ChunkyPNG::Exception
114
124
  end
115
125
 
126
+ # Exception that is raised if an tTXt chunk does not contain valid UTF-8 data.
127
+ class InvalidUTF8 < ChunkyPNG::Exception
128
+ end
129
+
116
130
  # Exception that is raised if an expectation fails.
117
131
  class ExpectationFailed < ChunkyPNG::Exception
118
132
  end
@@ -129,7 +143,7 @@ module ChunkyPNG
129
143
  def self.force_binary(str)
130
144
  str.respond_to?(:force_encoding) ? str.force_encoding('BINARY') : str
131
145
  end
132
-
146
+
133
147
  # Empty byte array. This basically is an empty string, but with the encoding
134
148
  # set correctly to ASCII-8BIT (binary) in Ruby 1.9.
135
149
  # @return [String] An empty string, with encoding set to binary in Ruby 1.9
@@ -137,7 +151,7 @@ module ChunkyPNG
137
151
  EMPTY_BYTEARRAY = force_binary(String.new).freeze
138
152
 
139
153
  # Null-byte, with the encoding set correctly to ASCII-8BIT (binary) in Ruby 1.9.
140
- # @return [String] A binary string, consisting of one NULL-byte.
154
+ # @return [String] A binary string, consisting of one NULL-byte.
141
155
  # @private
142
156
  EXTRA_BYTE = force_binary(String.new("\0")).freeze
143
157
  end
@@ -170,7 +170,7 @@ module ChunkyPNG
170
170
  # @param content [String] The content read from the chunk. Should be
171
171
  # empty.
172
172
  # @return [ChunkyPNG::Chunk::End] The new End chunk instance.
173
- # @raise [RuntimeError] Raises an exception if the content was not empty.
173
+ # @raise [ChunkyPNG::ExpectationFailed] Raises an exception if the content was not empty.
174
174
  def self.read(type, content)
175
175
  raise ExpectationFailed, 'The IEND chunk should be empty!' if content.bytesize > 0
176
176
  self.new
@@ -323,8 +323,7 @@ module ChunkyPNG
323
323
  # ratio for display of the image.
324
324
  #
325
325
  # http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.pHYs
326
- #
327
- class Physical < Generic
326
+ class Physical < Base
328
327
  attr_accessor :ppux, :ppuy, :unit
329
328
 
330
329
  def initialize(ppux, ppuy, unit = :unknown)
@@ -349,10 +348,8 @@ module ChunkyPNG
349
348
  new(ppux, ppuy, unit)
350
349
  end
351
350
 
352
- # Creates the content to write to the stream, by concatenating the
353
- # keyword with the deflated value, joined by a null character.
354
- #
355
- # @return The content that should be written to the datastream.
351
+ # Assembles the content to write to the stream for this chunk.
352
+ # @return [String] The binary content that should be written to the datastream.
356
353
  def content
357
354
  [ppux, ppuy, unit == :meters ? 1 : 0].pack('NNC')
358
355
  end
@@ -360,21 +357,62 @@ module ChunkyPNG
360
357
  INCHES_PER_METER = 0.0254
361
358
  end
362
359
 
363
- # The Text (iTXt) chunk contains keyword/value metadata about the PNG
364
- # stream.
360
+ # The InternationalText (iTXt) chunk contains keyword/value metadata about the PNG
361
+ # stream, translated to a given locale.
365
362
  #
366
363
  # The metadata in this chunk can be encoded using UTF-8 characters.
367
364
  # Moreover, it is possible to define the language of the metadata, and give
368
365
  # a translation of the keyword name. Finally, it supports bot compressed
369
366
  # and uncompressed values.
370
367
  #
371
- # @todo This chunk is currently not implemented, but merely read and
372
- # written back intact.
368
+ # http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.iTXt
373
369
  #
374
370
  # @see ChunkyPNG::Chunk::Text
375
371
  # @see ChunkyPNG::Chunk::CompressedText
376
- class InternationalText < Generic
377
- # TODO
372
+ class InternationalText < Base
373
+ attr_accessor :keyword, :text, :language_tag, :translated_keyword, :compressed, :compression
374
+
375
+ def initialize(keyword, text, language_tag = '', translated_keyword = '', compressed = ChunkyPNG::UNCOMPRESSED_CONTENT, compression = ChunkyPNG::COMPRESSION_DEFAULT)
376
+ super('iTXt')
377
+ @keyword = keyword
378
+ @text = text
379
+ @language_tag = language_tag
380
+ @translated_keyword = translated_keyword
381
+ @compressed = compressed
382
+ @compression = compression
383
+ end
384
+
385
+ # Reads the tTXt chunk.
386
+ # @param type [String] The four character chunk type indicator (= "iTXt").
387
+ # @param content [String] The content read from the chunk.
388
+ # @return [ChunkyPNG::Chunk::InternationalText] The new End chunk instance.
389
+ # @raise [ChunkyPNG::InvalidUTF8] If the chunk contains data that is not UTF8-encoded text.
390
+ # @raise [ChunkyPNG::NotSupported] If the chunk refers to an unsupported compression method.
391
+ # Currently uncompressed data and deflate are supported.
392
+ def self.read(type, content)
393
+ keyword, compressed, compression, language_tag, translated_keyword, text = content.unpack('Z*CCZ*Z*a*')
394
+ raise ChunkyPNG::NotSupported, "Compression flag #{compressed.inspect} not supported!" unless compressed == ChunkyPNG::UNCOMPRESSED_CONTENT || compressed == ChunkyPNG::COMPRESSED_CONTENT
395
+ raise ChunkyPNG::NotSupported, "Compression method #{compression.inspect} not supported!" unless compression == ChunkyPNG::COMPRESSION_DEFAULT
396
+
397
+ text = Zlib::Inflate.inflate(text) if compressed == ChunkyPNG::COMPRESSED_CONTENT
398
+
399
+ text.force_encoding('utf-8')
400
+ raise ChunkyPNG::InvalidUTF8, "Invalid unicode encountered in iTXt chunk" unless text.valid_encoding?
401
+
402
+ translated_keyword.force_encoding('utf-8')
403
+ raise ChunkyPNG::InvalidUTF8, "Invalid unicode encountered in iTXt chunk" unless translated_keyword.valid_encoding?
404
+
405
+ new(keyword, text, language_tag, translated_keyword, compressed, compression)
406
+ end
407
+
408
+ # Assembles the content to write to the stream for this chunk.
409
+ # @return [String] The binary content that should be written to the datastream.
410
+ def content
411
+ text_field = text.encode('utf-8')
412
+ text_field = (compressed == ChunkyPNG::COMPRESSED_CONTENT) ? Zlib::Deflate.deflate(text_field) : text_field
413
+
414
+ [keyword, compressed, compression, language_tag, translated_keyword.encode('utf-8'), text_field].pack('Z*CCZ*Z*a*')
415
+ end
378
416
  end
379
417
 
380
418
  # Maps chunk types to classes, based on the four byte chunk type indicator
@@ -1,5 +1,5 @@
1
1
  module ChunkyPNG
2
2
  # The current version of ChunkyPNG.
3
3
  # Set it and commit the change this before running rake release.
4
- VERSION = "1.3.8"
4
+ VERSION = "1.3.9"
5
5
  end
@@ -1,3 +1,4 @@
1
+ # coding: utf-8
1
2
  require 'spec_helper'
2
3
 
3
4
  describe ChunkyPNG::Datastream do
@@ -58,4 +59,78 @@ describe ChunkyPNG::Datastream do
58
59
  expect{physical_chunk.dpiy}.to raise_error(ChunkyPNG::UnitsUnknown)
59
60
  end
60
61
  end
62
+
63
+ describe '#iTXt_chunk' do
64
+ it 'should read iTXt chunks correctly' do
65
+ filename = resource_file('itxt_chunk.png')
66
+ ds = ChunkyPNG::Datastream.from_file(filename)
67
+ int_text_chunks = ds.chunks.select { |chunk| chunk.is_a?(ChunkyPNG::Chunk::InternationalText) }
68
+ expect(int_text_chunks.length).to eq(2)
69
+
70
+ coach_uk = int_text_chunks.find { |chunk| chunk.language_tag == 'en-gb' }
71
+ coach_us = int_text_chunks.find { |chunk| chunk.language_tag == 'en-us' }
72
+ expect(coach_uk).to_not be_nil
73
+ expect(coach_us).to_not be_nil
74
+
75
+ expect(coach_uk.keyword).to eq('coach')
76
+ expect(coach_uk.text).to eq('bus with of higher standard of comfort, usually chartered or used for longer journeys')
77
+ expect(coach_uk.translated_keyword).to eq('bus')
78
+ expect(coach_uk.compressed).to eq(ChunkyPNG::UNCOMPRESSED_CONTENT)
79
+ expect(coach_uk.compression).to eq(ChunkyPNG::COMPRESSION_DEFAULT)
80
+
81
+ expect(coach_us.keyword).to eq('coach')
82
+ expect(coach_us.text).to eq('US extracurricular sports teacher at a school (UK: PE teacher) lowest class on a passenger aircraft (UK: economy)')
83
+ expect(coach_us.translated_keyword).to eq('trainer')
84
+ expect(coach_us.compressed).to eq(ChunkyPNG::COMPRESSED_CONTENT)
85
+ expect(coach_us.compression).to eq(ChunkyPNG::COMPRESSION_DEFAULT)
86
+ end
87
+
88
+ it 'should write iTXt chunks correctly' do
89
+ expected_hex = %w(0000 001d 6954 5874 436f 6d6d 656e 7400 0000 0000 4372 6561 7465 6420 7769 7468 2047 494d 5064 2e65 07).join('')
90
+ stream = StringIO.new
91
+ itext = ChunkyPNG::Chunk::InternationalText.new('Comment', 'Created with GIMP')
92
+ itext.write(stream)
93
+ generated_hex = stream.string.unpack('H*').join('')
94
+
95
+ expect(generated_hex).to eq(expected_hex)
96
+ end
97
+
98
+ it 'should handle incorrect UTF-8 encoding in iTXt chunks' do
99
+ incorrect_text_encoding = [0, 0, 0, 14, 105, 84, 88, 116, 67, 111, 109, 109, 101, 110, 116, 0, 0, 0, 0, 0, 195, 40, 17, 87, 97, 213].pack('C*')
100
+ incorrect_translated_keyword_encoding = [0, 0, 0, 19, 105, 84, 88, 116, 67, 111, 109, 109, 101, 110, 116, 0, 0, 0, 0, 226, 130, 40, 0, 116, 101, 115, 116, 228, 53, 113, 182].pack('C*')
101
+
102
+ expect { ChunkyPNG::Chunk.read(StringIO.new(incorrect_text_encoding)) }.to raise_error(ChunkyPNG::InvalidUTF8)
103
+ expect { ChunkyPNG::Chunk.read(StringIO.new(incorrect_translated_keyword_encoding)) }.to raise_error(ChunkyPNG::InvalidUTF8)
104
+ end
105
+
106
+ it 'should handle UTF-8 in iTXt compressed chunks correctly' do
107
+ parsed = serialized_chunk(ChunkyPNG::Chunk::InternationalText.new('Comment', '✨', '', '💩', ChunkyPNG::COMPRESSED_CONTENT))
108
+
109
+ expect(parsed.text).to eq('✨')
110
+ expect(parsed.text.encoding).to eq(Encoding::UTF_8)
111
+
112
+ expect(parsed.translated_keyword).to eq('💩')
113
+ expect(parsed.translated_keyword.encoding).to eq(Encoding::UTF_8)
114
+ end
115
+
116
+ it 'should handle UTF-8 in iTXt chunks correctly' do
117
+ parsed = serialized_chunk(ChunkyPNG::Chunk::InternationalText.new('Comment', '✨', '', '💩'))
118
+
119
+ expect(parsed.text).to eq('✨')
120
+ expect(parsed.text.encoding).to eq(Encoding::UTF_8)
121
+
122
+ expect(parsed.translated_keyword).to eq('💩')
123
+ expect(parsed.translated_keyword.encoding).to eq(Encoding::UTF_8)
124
+ end
125
+
126
+ it 'should transform non UTF-8 iTXt fields to UTF-8 on write' do
127
+ parsed = serialized_chunk(ChunkyPNG::Chunk::InternationalText.new('Comment', '®'.encode('Windows-1252'), '', 'ƒ'.encode('Windows-1252')))
128
+
129
+ expect(parsed.text).to eq('®')
130
+ expect(parsed.text.encoding).to eq(Encoding::UTF_8)
131
+
132
+ expect(parsed.translated_keyword).to eq('ƒ')
133
+ expect(parsed.translated_keyword.encoding).to eq(Encoding::UTF_8)
134
+ end
135
+ end
61
136
  end
@@ -45,10 +45,20 @@ module ResourceFileHelper
45
45
  end
46
46
  end
47
47
 
48
+ module ChunkOperationsHelper
49
+
50
+ def serialized_chunk(chunk)
51
+ chunk.write(stream = StringIO.new)
52
+ stream.rewind
53
+ ChunkyPNG::Chunk.read(stream)
54
+ end
55
+ end
56
+
48
57
  RSpec.configure do |config|
49
58
  config.extend PNGSuite
50
59
  config.include PNGSuite
51
60
  config.include ResourceFileHelper
61
+ config.include ChunkOperationsHelper
52
62
 
53
63
  config.expect_with :rspec do |c|
54
64
  c.syntax = [:should, :expect]
metadata CHANGED
@@ -1,41 +1,41 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chunky_png
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.8
4
+ version: 1.3.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Willem van Bergen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-11-11 00:00:00.000000000 Z
11
+ date: 2018-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '3'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3'
41
41
  description: |2
@@ -65,9 +65,9 @@ extra_rdoc_files:
65
65
  - CONTRIBUTING.rdoc
66
66
  - CHANGELOG.rdoc
67
67
  files:
68
- - .gitignore
69
- - .travis.yml
70
- - .yardopts
68
+ - ".gitignore"
69
+ - ".travis.yml"
70
+ - ".yardopts"
71
71
  - BENCHMARKING.rdoc
72
72
  - CHANGELOG.rdoc
73
73
  - CONTRIBUTING.rdoc
@@ -366,6 +366,7 @@ files:
366
366
  - spec/resources/damaged_chunk.png
367
367
  - spec/resources/damaged_signature.png
368
368
  - spec/resources/empty.png
369
+ - spec/resources/itxt_chunk.png
369
370
  - spec/resources/lines.png
370
371
  - spec/resources/operations.png
371
372
  - spec/resources/operations_border.png
@@ -394,27 +395,27 @@ licenses:
394
395
  metadata: {}
395
396
  post_install_message:
396
397
  rdoc_options:
397
- - --title
398
+ - "--title"
398
399
  - chunky_png
399
- - --main
400
+ - "--main"
400
401
  - README.rdoc
401
- - --line-numbers
402
- - --inline-source
402
+ - "--line-numbers"
403
+ - "--inline-source"
403
404
  require_paths:
404
405
  - lib
405
406
  required_ruby_version: !ruby/object:Gem::Requirement
406
407
  requirements:
407
- - - '>='
408
+ - - ">="
408
409
  - !ruby/object:Gem::Version
409
410
  version: '0'
410
411
  required_rubygems_version: !ruby/object:Gem::Requirement
411
412
  requirements:
412
- - - '>='
413
+ - - ">="
413
414
  - !ruby/object:Gem::Version
414
415
  version: '0'
415
416
  requirements: []
416
417
  rubyforge_project:
417
- rubygems_version: 2.0.14.1
418
+ rubygems_version: 2.6.14
418
419
  signing_key:
419
420
  specification_version: 4
420
421
  summary: Pure ruby library for read/write, chunk-level access to PNG files
@@ -682,6 +683,7 @@ test_files:
682
683
  - spec/resources/damaged_chunk.png
683
684
  - spec/resources/damaged_signature.png
684
685
  - spec/resources/empty.png
686
+ - spec/resources/itxt_chunk.png
685
687
  - spec/resources/lines.png
686
688
  - spec/resources/operations.png
687
689
  - spec/resources/operations_border.png