hexapdf 0.21.0 → 0.21.1

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
  SHA256:
3
- metadata.gz: b666a69330b87a3ad7a5c937a1113a66a3b5ed3d0cb4f4478ee24f97bed0e411
4
- data.tar.gz: 460c3cd90b8f76d3e4d32fdd6f1bc8fd71ddfef5d18799c6a207f562773371f8
3
+ metadata.gz: 9792b3c8be5fd3c3521aa5fe1a66e1315f677494f199e3e8b0e1eb85c9b967c7
4
+ data.tar.gz: a66f1d6751cac4ca2fc6a35bf493ba60eefee96edca090ae92ed810b756d8761
5
5
  SHA512:
6
- metadata.gz: a9135019912d8c3d1e282797054dfbc99c028f26aa28f8412a138ab3d8d67124c4e9c52ad104eeb4289487e47dd850e3ff4211b5e928cdb0a8aab2d8ea773930
7
- data.tar.gz: 070d0facdb6576fb6a0377a7139609ee648a580be879dabfd10e943bd2f62d66d9d5077058edcecf9ca3eb4e7969a2427c7a4af26bbb4d8f734e238f7022c7cd
6
+ metadata.gz: 111570ec411684ffe5214833419128b697721c4e5fdd16b61578c3d459a8ab9dde411780dd582e06620d52a260062df30a116b7c3c8544647f5e61f131c6bdc5
7
+ data.tar.gz: 3d098cb4098f6a02bc832cbe70aebc0b28a40a21b33b53c8e8e055f240fd513a5d96f53846c62ebbc2a00d17d6f99271dd3850e98a70cebb6f32e1a7e279cc87
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 0.21.1 - 2022-03-12
2
+
3
+ ### Fixed
4
+
5
+ - Handling of invalid AES encrypted files where the padding is missing
6
+
7
+
1
8
  ## 0.21.0 - 2022-03-04
2
9
 
3
10
  ### Added
@@ -246,7 +253,7 @@
246
253
 
247
254
  ## 0.16.0 - 2021-09-28
248
255
 
249
- ## Added
256
+ ### Added
250
257
 
251
258
  * Support for RGB color values of the form "RGB" in addition to "RRGGBB" and for
252
259
  CSS color module level 3 color names
data/Rakefile CHANGED
@@ -49,7 +49,7 @@ namespace :dev do
49
49
  task :test_all do
50
50
  versions = `rbenv versions --bare | grep -i 2.[567]\\\\\\|3.`.split("\n")
51
51
  versions.each do |version|
52
- sh "rbenv shell #{version} &>/dev/null && rake test"
52
+ sh "eval \"$(rbenv init -)\"; rbenv shell #{version} && ruby -v && rake test"
53
53
  end
54
54
  puts "Looks okay? (enter to continue, Ctrl-c to abort)"
55
55
  $stdin.gets
@@ -0,0 +1,57 @@
1
+ # ## Column Box
2
+ #
3
+ # This example shows how [HexaPDF::Layout::Frame] and [HexaPDF::Layout::TextBox]
4
+ # can be used to flow text around objects.
5
+ #
6
+ # Three boxes are placed repeatedly onto the frame until it is filled: two
7
+ # floating boxes (one left, one right) and a text box. The text box is styled to
8
+ # flow its content around the other two boxes.
9
+ #
10
+ # Usage:
11
+ # : `ruby frame_text_flow.rb`
12
+ #
13
+
14
+ require 'hexapdf'
15
+ require 'hexapdf/utils/graphics_helpers'
16
+
17
+ include HexaPDF::Layout
18
+ include HexaPDF::Utils::GraphicsHelpers
19
+
20
+ doc = HexaPDF::Document.new
21
+
22
+ sample_text = "Lorem ipsum dolor sit amet, con\u{00AD}sectetur
23
+ adipis\u{00AD}cing elit, sed do eiusmod tempor incididunt ut labore et
24
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
25
+ ullamco laboris nisi ut aliquip ex ea commodo consequat.
26
+ ".tr("\n", ' ') * 5
27
+ items = [TextFragment.create(sample_text, font: doc.fonts.add("Times"))]
28
+
29
+ page = doc.pages.add
30
+ media_box = page.box(:media)
31
+ canvas = page.canvas
32
+ frame = Frame.new(media_box.left + 20, media_box.bottom + 20,
33
+ media_box.width - 40, media_box.height - 40)
34
+
35
+ image = doc.images.add(File.join(__dir__, 'machupicchu.jpg'))
36
+ iw, ih = calculate_dimensions(image.width, image.height, rwidth: 100)
37
+
38
+ boxes = []
39
+ 3.times do
40
+ boxes << Box.create(width: iw, height: ih,
41
+ margin: [10, 30], position: :float) do |canv, box|
42
+ canv.image(image, at: [0, 0], width: 100)
43
+ end
44
+ boxes << Box.create(width: 50, height: 50, margin: 20,
45
+ position: :float, position_hint: :right,
46
+ border: {width: 1, color: [[255, 0, 0]]})
47
+ boxes << TextBox.new(items, style: {position: :flow, align: :justify})
48
+ end
49
+ columns = ColumnBox.new(boxes) #, style: {position: :flow})
50
+ polygon = Geom2D::Polygon([250, 350], [350, 350], [350, 500], [250, 500])
51
+ #frame.remove_area(polygon)
52
+ #canvas.draw(:geom2d, object: polygon)
53
+ result = frame.fit(columns)
54
+ p result.success?
55
+ frame.draw(canvas, result)
56
+
57
+ doc.write("column_box.pdf", optimize: true)
@@ -114,10 +114,12 @@ module HexaPDF
114
114
  #
115
115
  # See: PDF1.7 s7.6.2.
116
116
  def decrypt(key, data)
117
- if data.length % BLOCK_SIZE != 0 || data.length < 2 * BLOCK_SIZE
117
+ if data.length % BLOCK_SIZE != 0 || data.length < BLOCK_SIZE
118
118
  raise HexaPDF::EncryptionError, "Invalid data for decryption, need 32 + 16*n bytes"
119
119
  end
120
- unpad(new(key, data.slice!(0, BLOCK_SIZE), :decrypt).process(data))
120
+ iv = data.slice!(0, BLOCK_SIZE)
121
+ # Handle invalid files with missing padding
122
+ data.empty? ? data : unpad(new(key, iv, :decrypt).process(data))
121
123
  end
122
124
 
123
125
  # Returns a Fiber object that decrypts the data from the given source fiber with the
@@ -140,11 +142,13 @@ module HexaPDF
140
142
  Fiber.yield(algorithm.process(new_data))
141
143
  end
142
144
 
143
- if data.length < BLOCK_SIZE || data.length % BLOCK_SIZE != 0
145
+ if data.length % BLOCK_SIZE != 0
144
146
  raise HexaPDF::EncryptionError, "Invalid data for decryption, need 32 + 16*n bytes"
147
+ elsif data.empty?
148
+ data # Handle invalid files with missing padding
149
+ else
150
+ unpad(algorithm.process(data))
145
151
  end
146
-
147
- unpad(algorithm.process(data))
148
152
  end
149
153
  end
150
154
 
@@ -0,0 +1,168 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+ require 'hexapdf/layout/box'
37
+
38
+ module HexaPDF
39
+ module Layout
40
+
41
+ # A ColumnBox arranges boxes in one or more columns.
42
+ #
43
+ # The number of columns as well as the size of the gap between the columns can be modified.
44
+ class ColumnBox < Box
45
+
46
+ # The child boxes of this ColumnBox.
47
+ attr_reader :children
48
+
49
+ # The number of columns.
50
+ # TODO: allow array with column widths later like [100, :*, :*]; same for gaps
51
+ attr_reader :columns
52
+
53
+ # The size of the gap between the columns.
54
+ attr_reader :gap
55
+
56
+ # Creates a new ColumnBox object for the given +children+ boxes.
57
+ def initialize(children = [], columns = 2, gap: 36, **kwargs)
58
+ super(**kwargs)
59
+ @children = children
60
+ @columns = columns
61
+ @gap = gap
62
+ end
63
+
64
+ # Fits the column box into the available space.
65
+ def fit(available_width, available_height, frame)
66
+ last_height_difference = 1_000_000
67
+ height = if style.position == :flow
68
+ frame.height
69
+ else
70
+ (@initial_height > 0 ? @initial_height : available_height) - reserved_height
71
+ end
72
+ while true
73
+ p '-'*100
74
+ @frames = []
75
+ if style.position == :flow
76
+ column_width = (frame.width - gap * (@columns - 1)).to_f / @columns
77
+ @columns.times do |col_nr|
78
+ left = (column_width + gap) * col_nr + frame.left
79
+ bottom = frame.bottom
80
+ rect = Geom2D::Polygon([left, bottom],
81
+ [left + column_width, bottom],
82
+ [left + column_width, bottom + height],
83
+ [left, bottom + height])
84
+ shape = Geom2D::Algorithms::PolygonOperation.run(frame.shape, rect, :intersection)
85
+ col_frame = Frame.new(left, bottom, column_width, height)
86
+ col_frame.shape = shape
87
+ @frames << col_frame
88
+ end
89
+ @frame_index = 0
90
+ @results = @children.map {|child_box| fit_box(child_box) }
91
+ @width = frame.width
92
+ @height = frame.height - @frames.min_by(&:y).y
93
+ else
94
+ width = (@initial_width > 0 ? @initial_width : available_width) - reserved_width
95
+ column_width = (width - gap * (@columns - 1)).to_f / @columns
96
+ @columns.times do |col_nr|
97
+ @frames << Frame.new((column_width + gap) * col_nr, 0, column_width, height)
98
+ end
99
+ @frame_index = 0
100
+ @results = @children.map {|child_box| fit_box(child_box) }
101
+ @width = width
102
+ @height = height - @frames.min_by(&:y).y
103
+ end
104
+ min_y, max_y = @frames.minmax_by(&:y).map(&:y)
105
+ p [height, @frames.map(&:y), last_height_difference, min_y, max_y]
106
+ # TOOD: @result.any?(&:empty?) only for the first run!!!! if the first run fails, we
107
+ # cannot balance the columns because there is too much content.
108
+ # TODO: another break condition is if the @results didn't change since the last run
109
+ p [:maybe_redo, min_y, max_y, height, last_height_difference]
110
+ p [@results.map {|arr| arr.all? {|r| r.status }}]
111
+ break if max_y != height && @results.all? {|arr| !arr.empty? && arr.all? {|r| r.success? }} &&
112
+ (@results.any?(&:empty?) ||
113
+ max_y - min_y >= last_height_difference ||
114
+ max_y - min_y < 0.5)
115
+ if max_y == 0 && min_y == 0
116
+ height += last_height_difference / 4.0
117
+ else
118
+ last_height_difference = max_y - min_y
119
+ height -= last_height_difference / 2.0
120
+ end
121
+ end
122
+ @results.all? {|res| res.length == 1 }
123
+ end
124
+
125
+ private
126
+
127
+ def fit_box(box)
128
+ cur_frame = @frames[@frame_index]
129
+ fit_results = []
130
+ while cur_frame
131
+ result = cur_frame.fit(box)
132
+ if result.success?
133
+ cur_frame.remove_area(result.mask)
134
+ fit_results << result
135
+ break
136
+ elsif cur_frame.full?
137
+ @frame_index += 1
138
+ break if @frame_index == @frames.length
139
+ cur_frame = @frames[@frame_index]
140
+ else
141
+ draw_box, box = cur_frame.split(result)
142
+ if draw_box
143
+ cur_frame.remove_area(result.mask)
144
+ fit_results << result
145
+ elsif !cur_frame.find_next_region
146
+ @frame_index += 1
147
+ break if @frame_index == @frames.length
148
+ cur_frame = @frames[@frame_index]
149
+ end
150
+ end
151
+ end
152
+ fit_results
153
+ end
154
+
155
+ # Draws the child boxes onto the canvas at position [x, y].
156
+ def draw_content(canvas, x, y)
157
+ x = y = 0 if style.position == :flow
158
+ @results.each do |result_boxes|
159
+ result_boxes.each do |result|
160
+ result.box.draw(canvas, x + result.x, y + result.y)
161
+ end
162
+ end
163
+ end
164
+
165
+ end
166
+
167
+ end
168
+ end
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '0.21.0'
40
+ VERSION = '0.21.1'
41
41
 
42
42
  end
@@ -48,6 +48,10 @@ describe HexaPDF::Encryption::AES do
48
48
  end
49
49
  end
50
50
 
51
+ it "handles invalid files with missing 16 byte padding" do
52
+ assert_equal('', @algorithm_class.decrypt('some key' * 2, 'iv' * 8))
53
+ end
54
+
51
55
  it "fails on decryption if not enough bytes are provided" do
52
56
  assert_raises(HexaPDF::EncryptionError) do
53
57
  @algorithm_class.decrypt('some' * 4, 'no iv')
@@ -97,6 +101,10 @@ describe HexaPDF::Encryption::AES do
97
101
  end
98
102
 
99
103
  it "decryption works if the padding is invalid" do
104
+ f = Fiber.new { 'a' * 16 }
105
+ result = TestHelper.collector(@algorithm_class.decryption_fiber('some' * 4, f))
106
+ assert_equal('', result)
107
+
100
108
  f = Fiber.new { 'a' * 32 }
101
109
  result = TestHelper.collector(@algorithm_class.decryption_fiber('some' * 4, f))
102
110
  assert_equal('a' * 16, result)
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
40
40
  219
41
41
  %%EOF
42
42
  3 0 obj
43
- <</Producer(HexaPDF version 0.21.0)>>
43
+ <</Producer(HexaPDF version 0.21.1)>>
44
44
  endobj
45
45
  xref
46
46
  3 1
@@ -72,7 +72,7 @@ describe HexaPDF::Writer do
72
72
  141
73
73
  %%EOF
74
74
  6 0 obj
75
- <</Producer(HexaPDF version 0.21.0)>>
75
+ <</Producer(HexaPDF version 0.21.1)>>
76
76
  endobj
77
77
  2 0 obj
78
78
  <</Length 10>>stream
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hexapdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.21.0
4
+ version: 0.21.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Leitner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-04 00:00:00.000000000 Z
11
+ date: 2022-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cmdparse
@@ -217,6 +217,7 @@ files:
217
217
  - examples/017-frame_text_flow.rb
218
218
  - examples/018-composer.rb
219
219
  - examples/019-acro_form.rb
220
+ - examples/020-column_box.rb
220
221
  - examples/emoji-smile.png
221
222
  - examples/emoji-wink.png
222
223
  - examples/machupicchu.jpg
@@ -333,6 +334,7 @@ files:
333
334
  - lib/hexapdf/importer.rb
334
335
  - lib/hexapdf/layout.rb
335
336
  - lib/hexapdf/layout/box.rb
337
+ - lib/hexapdf/layout/column_box.rb
336
338
  - lib/hexapdf/layout/frame.rb
337
339
  - lib/hexapdf/layout/image_box.rb
338
340
  - lib/hexapdf/layout/inline_box.rb