hexapdf 0.21.0 → 0.21.1
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 +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
|