chunky_png 1.3.8 → 1.3.9

Sign up to get free protection for your applications and to get access to all the features.
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