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 +4 -4
- data/CHANGELOG.md +8 -1
- data/Rakefile +1 -1
- data/examples/020-column_box.rb +57 -0
- data/lib/hexapdf/encryption/aes.rb +9 -5
- data/lib/hexapdf/layout/column_box.rb +168 -0
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/encryption/test_aes.rb +8 -0
- data/test/hexapdf/test_writer.rb +2 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9792b3c8be5fd3c3521aa5fe1a66e1315f677494f199e3e8b0e1eb85c9b967c7
|
4
|
+
data.tar.gz: a66f1d6751cac4ca2fc6a35bf493ba60eefee96edca090ae92ed810b756d8761
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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}
|
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 <
|
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
|
-
|
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
|
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
|
data/lib/hexapdf/version.rb
CHANGED
@@ -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)
|
data/test/hexapdf/test_writer.rb
CHANGED
@@ -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.
|
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.
|
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.
|
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-
|
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
|