combine_pdf 1.0.16 → 1.0.26
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +28 -0
- data/.travis.yml +1 -0
- data/CHANGELOG.md +47 -1
- data/README.md +51 -12
- data/Rakefile +8 -1
- data/combine_pdf.gemspec +4 -2
- data/lib/combine_pdf/api.rb +5 -5
- data/lib/combine_pdf/fonts.rb +12 -3
- data/lib/combine_pdf/page_methods.rb +2 -2
- data/lib/combine_pdf/parser.rb +47 -9
- data/lib/combine_pdf/pdf_protected.rb +6 -3
- data/lib/combine_pdf/pdf_public.rb +21 -10
- data/lib/combine_pdf/version.rb +1 -1
- data/lib/combine_pdf.rb +1 -0
- data/test/automated +65 -64
- data/test/combine_pdf/load_test.rb +48 -0
- data/test/combine_pdf/renderer_test.rb +0 -2
- data/test/fixtures/files/sample_encrypted_pdf.pdf +0 -0
- data/test/fixtures/files/sample_pdf.pdf +0 -0
- metadata +47 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fc482ee9da31ce7fc01e92da3936e44407e4879c7058817bb4a57ba0fc0a4165
|
4
|
+
data.tar.gz: 1dd52423ee639783510a9ad9aa347e9597e2e14d38a11914d05a03b5116006f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f6ac66c289762c5f2b6473f450e8808e79d9ca9f927b68f083204a93b7f213a433a6fc4076cd3241ff1a17ac7c08297087ab1bf76e58b0dad12e7a0eebc34628
|
7
|
+
data.tar.gz: a39c619af345d61d7d40ba0d0b0a32438caace2b05512273afe1f21b737de6324dcc483f34234c5123c9ed0508a0db481b4bf90ffc06fce2484922f2c6c7abe0
|
@@ -0,0 +1,28 @@
|
|
1
|
+
name: Main
|
2
|
+
on:
|
3
|
+
push:
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
tests:
|
7
|
+
name: Tests
|
8
|
+
runs-on: ubuntu-latest
|
9
|
+
strategy:
|
10
|
+
fail-fast: false
|
11
|
+
matrix:
|
12
|
+
ruby: ["2.7", "3.0", "3.1", "3.2"]
|
13
|
+
|
14
|
+
steps:
|
15
|
+
- name: Checkout code
|
16
|
+
uses: actions/checkout@v3
|
17
|
+
|
18
|
+
- name: Setup Ruby
|
19
|
+
uses: ruby/setup-ruby@v1
|
20
|
+
with:
|
21
|
+
ruby-version: ${{ matrix.ruby }}
|
22
|
+
bundler-cache: true
|
23
|
+
|
24
|
+
- name: Generate lockfile
|
25
|
+
run: bundle lock
|
26
|
+
|
27
|
+
- name: Run tests
|
28
|
+
run: bundle exec rake test
|
data/.travis.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
language: ruby
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,52 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
-
|
3
|
+
#### Change log v.1.0.26 (2023-12-22)
|
4
|
+
|
5
|
+
**Performance**: possible performance bump. Credit to @denislavski (Denislav Naydenov) for opening PR #235.
|
6
|
+
|
7
|
+
#### Change log v.1.0.25 (2023-12-19)
|
8
|
+
|
9
|
+
**Fix**: possible improve memory usage. Credit to @denislavski (Denislav Naydenov) for opening PR #233 and suggesting this change.
|
10
|
+
|
11
|
+
#### Change log v.1.0.24 (2023-10-19)
|
12
|
+
|
13
|
+
**Fix**: possible `nil` in loop. Credit to @jkowens for PR #231 and adding a quick fix using a simple guard.
|
14
|
+
|
15
|
+
**Fix**: preserve file creation date metadata where relevant.
|
16
|
+
|
17
|
+
#### Change log v.1.0.23 (2023-04-04)
|
18
|
+
|
19
|
+
**Feature**: merged PR #177 for the `raise_on_encrypted: true` option support. Credit to @leviwilson and @kimyu92 for the PR.
|
20
|
+
|
21
|
+
#### Change log v.1.0.22
|
22
|
+
|
23
|
+
**Fix**: fix `fonts` dereferencing issue (#203), credit to @MarcWeber (Marc Weber) for identifying the issue.
|
24
|
+
|
25
|
+
**Fix**: fix `metrix` dependency, credit to @casperisfine (Jean byroot Boussier) for PR #195.
|
26
|
+
|
27
|
+
#### Change log v.1.0.21
|
28
|
+
|
29
|
+
**Fix**: possible fix for issue #184, where nested PDF files within an object stream could break the parser. Credit to Greg Sparrow (@hazelsparrow) for exposng the issue.
|
30
|
+
|
31
|
+
#### Change log v.1.0.20
|
32
|
+
|
33
|
+
**Fix**: merges PR #180, `TypeError: can't dup NilClass`. Credit to Adam Trepanier (@adam-e-trepanier) for the merge.
|
34
|
+
|
35
|
+
#### Change log v.1.0.19
|
36
|
+
|
37
|
+
**Fix**: fixes font height and width detection issue. Issue #179. Credit to @5anchezzz for opening the issue.
|
38
|
+
|
39
|
+
**Fix**: fixes an indentation warning. Issue #173. Credit to @rubyFeedback for exposing this issue.
|
40
|
+
|
41
|
+
#### Change log v.1.0.18
|
42
|
+
|
43
|
+
**Fix**: fixed issue with the 1.0.17 release where `ProcSet` PDF Arrays should have been expected but where ignored and a PDF Object was assumed instead (issue #171) - credit to @chuchiperriman (Jesús Barbero Rodríguez).
|
44
|
+
|
45
|
+
#### Change log v.1.0.17
|
46
|
+
|
47
|
+
NB: yanked from RubyGems.org.
|
48
|
+
|
49
|
+
**Fix**: fixed issue where nested structure equality tests might provide false positives, resulting in lost data (issue #166) - credit to @cschilbe (Conrad Schilbe).
|
4
50
|
|
5
51
|
#### Change log v.1.0.16
|
6
52
|
|
data/README.md
CHANGED
@@ -1,27 +1,29 @@
|
|
1
1
|
# CombinePDF - the ruby way for merging PDF files
|
2
2
|
[![Gem Version](https://badge.fury.io/rb/combine_pdf.svg)](http://badge.fury.io/rb/combine_pdf)
|
3
3
|
[![GitHub](https://img.shields.io/badge/GitHub-Open%20Source-blue.svg)](https://github.com/boazsegev/combine_pdf)
|
4
|
+
[![Documentation](http://inch-ci.org/github/boazsegev/combine_pdf.svg?branch=master)](https://www.rubydoc.info/github/boazsegev/combine_pdf)
|
4
5
|
[![Maintainers Wanted](https://img.shields.io/badge/maintainers-wanted-red.svg)](https://github.com/pickhardt/maintainers-wanted)
|
5
6
|
|
7
|
+
|
6
8
|
CombinePDF is a nifty model, written in pure Ruby, to parse PDF files and combine (merge) them with other PDF files, watermark them or stamp them (all using the PDF file format and pure Ruby code).
|
7
9
|
|
8
|
-
##
|
10
|
+
## Unmaintained - Help Wanted(!)
|
9
11
|
|
10
|
-
|
12
|
+
I decided to stop maintaining this gem and hope someone could take over the PR reviews and maintenance of this gem (or simply open a successful fork).
|
11
13
|
|
12
|
-
|
13
|
-
gem install combine_pdf
|
14
|
-
```
|
14
|
+
I wrote this gem because I needed to solve an issue with bates-numbering existing PDF documents.
|
15
15
|
|
16
|
-
|
16
|
+
However, since 2014 I have been maintaining the gem for free and for no reason at all, except that I enjoyed sharing it with the community.
|
17
17
|
|
18
|
-
I
|
18
|
+
I love this gem, but I cannot keep maintaining it as I have my own projects to focus own and I need both the time and (more importantly) the mindspace.
|
19
19
|
|
20
|
-
|
20
|
+
## Install
|
21
21
|
|
22
|
-
|
22
|
+
Install with ruby gems:
|
23
23
|
|
24
|
-
|
24
|
+
```ruby
|
25
|
+
gem install combine_pdf
|
26
|
+
```
|
25
27
|
|
26
28
|
## Known Limitations
|
27
29
|
|
@@ -37,10 +39,12 @@ Quick rundown:
|
|
37
39
|
|
38
40
|
Some links will be lost when ripping pages out of PDF files and merging them with another PDF.
|
39
41
|
|
40
|
-
* Some encrypted PDF files (usually the ones you can't view without a password) will fail quietly instead of noisily.
|
42
|
+
* Some encrypted PDF files (usually the ones you can't view without a password) will fail quietly instead of noisily. If you prefer to choose the noisy route, you can specify the `raise_on_encrypted` option using `CombinePDF.load(pdf_file, raise_on_encrypted: true)` which will raise a `CombinePDF::EncryptionError`.
|
41
43
|
|
42
44
|
* Sometimes the CombinePDF will raise an exception even if the PDF could be parsed (i.e., when PDF optional content exists)... I find it better to err on the side of caution, although for optional content PDFs an exception is avoidable using `CombinePDF.load(pdf_file, allow_optional_content: true)`.
|
43
45
|
|
46
|
+
* The CombinePDF gem runs recursive code to both parse and format the PDF files. Hence, PDF files that have heavily nested objects, as well as those that where combined in a way that results in cyclic nesting, might explode the stack - resulting in an exception or program failure.
|
47
|
+
|
44
48
|
CombinePDF is written natively in Ruby and should (presumably) work on all Ruby platforms that follow Ruby 2.0 compatibility.
|
45
49
|
|
46
50
|
However, PDF files are quite complex creatures and no guaranty is provided.
|
@@ -112,7 +116,42 @@ pdf.number_pages
|
|
112
116
|
pdf.save "file_with_numbering.pdf"
|
113
117
|
```
|
114
118
|
|
115
|
-
Numbering can be done with many different options, with different formating, with or without a box object, and even with opacity values - see documentation.
|
119
|
+
Numbering can be done with many different options, with different formating, with or without a box object, and even with opacity values - [see documentation](https://www.rubydoc.info/github/boazsegev/combine_pdf/CombinePDF/PDF#number_pages-instance_method).
|
120
|
+
|
121
|
+
For example, should you prefer to place the page number on the bottom right side of all PDF pages, do:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
pdf.number_pages(location: [:bottom_right])
|
125
|
+
```
|
126
|
+
|
127
|
+
As another example, the dashes around the number are removed and a box is placed around it. The numbering is semi-transparent and the first 3 pages are numbered using letters (a,b,c) rather than numbers:
|
128
|
+
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
# number first 3 pages as "a", "b", "c"
|
132
|
+
pdf.number_pages(number_format: " %s ",
|
133
|
+
location: [:top, :bottom, :top_left, :top_right, :bottom_left, :bottom_right],
|
134
|
+
start_at: "a",
|
135
|
+
page_range: (0..2),
|
136
|
+
box_color: [0.8,0.8,0.8],
|
137
|
+
border_color: [0.4, 0.4, 0.4],
|
138
|
+
border_width: 1,
|
139
|
+
box_radius: 6,
|
140
|
+
opacity: 0.75)
|
141
|
+
# number the rest of the pages as 4, 5, ... etc'
|
142
|
+
pdf.number_pages(number_format: " %s ",
|
143
|
+
location: [:top, :bottom, :top_left, :top_right, :bottom_left, :bottom_right],
|
144
|
+
start_at: 4,
|
145
|
+
page_range: (3..-1),
|
146
|
+
box_color: [0.8,0.8,0.8],
|
147
|
+
border_color: [0.4, 0.4, 0.4],
|
148
|
+
border_width: 1,
|
149
|
+
box_radius: 6,
|
150
|
+
opacity: 0.75)
|
151
|
+
```
|
152
|
+
|
153
|
+
pdf.number_pages(number_format: " %s ", location: :bottom_right, font_size: 44)
|
154
|
+
|
116
155
|
|
117
156
|
## Loading and Parsing PDF data
|
118
157
|
|
data/Rakefile
CHANGED
data/combine_pdf.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |spec|
|
|
7
7
|
spec.name = "combine_pdf"
|
8
8
|
spec.version = CombinePDF::VERSION
|
9
9
|
spec.authors = ["Boaz Segev"]
|
10
|
-
spec.email = ["
|
10
|
+
spec.email = ["bo@bowild.com"]
|
11
11
|
spec.summary = %q{Combine, stamp and watermark PDF files in pure Ruby.}
|
12
12
|
spec.description = %q{A nifty gem, in pure Ruby, to parse PDF files and combine (merge) them with other PDF files, number the pages, watermark them or stamp them, create tables, add basic text objects etc` (all using the PDF file format).}
|
13
13
|
spec.homepage = "https://github.com/boazsegev/combine_pdf"
|
@@ -19,8 +19,10 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_runtime_dependency 'ruby-rc4', '>= 0.1.5'
|
22
|
+
spec.add_runtime_dependency 'matrix'
|
22
23
|
|
23
24
|
# spec.add_development_dependency "bundler", ">= 1.7"
|
24
|
-
spec.add_development_dependency "rake", "
|
25
|
+
spec.add_development_dependency "rake", ">= 12.3.3"
|
25
26
|
spec.add_development_dependency "minitest"
|
27
|
+
spec.add_development_dependency "minitest-around"
|
26
28
|
end
|
data/lib/combine_pdf/api.rb
CHANGED
@@ -24,11 +24,11 @@ module CombinePDF
|
|
24
24
|
raise TypeError, "couldn't create PDF object, expecting type String" unless string.is_a?(String) || string.is_a?(Pathname)
|
25
25
|
begin
|
26
26
|
(begin
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
File.file? string
|
28
|
+
rescue
|
29
|
+
false
|
30
|
+
end) ? load(string) : parse(string)
|
31
|
+
rescue => _e
|
32
32
|
raise 'General PDF error - Use CombinePDF.load or CombinePDF.parse for a non-general error message (the requested file was not found OR the string received is not a valid PDF stream OR the file was found but not valid).'
|
33
33
|
end
|
34
34
|
end
|
data/lib/combine_pdf/fonts.rb
CHANGED
@@ -138,12 +138,21 @@ module CombinePDF
|
|
138
138
|
text.each_char do |c|
|
139
139
|
metrics_array << (merged_metrics[c] || { wx: 0, boundingbox: [0, 0, 0, 0] })
|
140
140
|
end
|
141
|
-
|
142
|
-
|
141
|
+
metrics_array_mapped_top = [].dup
|
142
|
+
metrics_array_mapped_bottom = [].dup
|
143
143
|
width = 0.0
|
144
144
|
metrics_array.each do |m|
|
145
|
-
|
145
|
+
if (m && m[:boundingbox])
|
146
|
+
metrics_array_mapped_top << m[:boundingbox][3]
|
147
|
+
metrics_array_mapped_bottom << m[:boundingbox][1]
|
148
|
+
else
|
149
|
+
metrics_array_mapped_top << 0
|
150
|
+
metrics_array_mapped_bottom << 0
|
151
|
+
end
|
152
|
+
width += (m[:wx] || m[:wy] || 0) if m
|
146
153
|
end
|
154
|
+
height = metrics_array_mapped_top.max
|
155
|
+
height -=metrics_array_mapped_bottom.min
|
147
156
|
return [height.to_f / 1000 * size, width.to_f / 1000 * size] if metrics_array[0][:wy]
|
148
157
|
[width.to_f / 1000 * size, height.to_f / 1000 * size]
|
149
158
|
end
|
@@ -94,7 +94,7 @@ module CombinePDF
|
|
94
94
|
# end
|
95
95
|
|
96
96
|
# set ProcSet to recommended value
|
97
|
-
resources[:ProcSet]
|
97
|
+
resources[:ProcSet] ||= [:PDF, :Text, :ImageB, :ImageC, :ImageI] # this was recommended by the ISO. 32000-1:2008
|
98
98
|
|
99
99
|
if top # if this is a stamp (overlay)
|
100
100
|
insert_content CONTENT_CONTAINER_START, 0
|
@@ -147,7 +147,7 @@ module CombinePDF
|
|
147
147
|
|
148
148
|
# This method adds a simple text box to the Page represented by the PDFWriter class.
|
149
149
|
# This function takes two values:
|
150
|
-
# text:: the text to
|
150
|
+
# text:: the text to write in the box.
|
151
151
|
# properties:: a Hash of box properties.
|
152
152
|
# the symbols and values in the properties Hash could be any or all of the following:
|
153
153
|
# x:: the left position of the box.
|
data/lib/combine_pdf/parser.rb
CHANGED
@@ -33,7 +33,7 @@ module CombinePDF
|
|
33
33
|
# they are mainly to used to know if the file is (was) encrypted and to get more details.
|
34
34
|
attr_reader :info_object, :root_object, :names_object, :forms_object, :outlines_object, :metadata
|
35
35
|
|
36
|
-
attr_reader :allow_optional_content
|
36
|
+
attr_reader :allow_optional_content, :raise_on_encrypted
|
37
37
|
# when creating a parser, it is important to set the data (String) we wish to parse.
|
38
38
|
#
|
39
39
|
# <b>the data is required and it is not possible to set the data at a later stage</b>
|
@@ -58,6 +58,7 @@ module CombinePDF
|
|
58
58
|
@version = nil
|
59
59
|
@scanner = nil
|
60
60
|
@allow_optional_content = options[:allow_optional_content]
|
61
|
+
@raise_on_encrypted = options[:raise_on_encrypted]
|
61
62
|
end
|
62
63
|
|
63
64
|
# parse the data in the new parser (the data already set through the initialize / new method)
|
@@ -96,6 +97,7 @@ module CombinePDF
|
|
96
97
|
end
|
97
98
|
|
98
99
|
if @root_object[:Encrypt]
|
100
|
+
raise EncryptionError, 'the file is encrypted' if @raise_on_encrypted
|
99
101
|
# change_references_to_actual_values @root_object
|
100
102
|
warn 'PDF is Encrypted! Attempting to decrypt - not yet fully supported.'
|
101
103
|
decryptor = PDFDecrypt.new @parsed, @root_object
|
@@ -233,16 +235,18 @@ module CombinePDF
|
|
233
235
|
# all characters that aren't white space or special: /[^\x00\x09\x0a\x0c\x0d\x20\x28\x29\x3c\x3e\x5b\x5d\x7b\x7d\x2f\x25]+
|
234
236
|
elsif str = @scanner.scan(/\/[^\x00\x09\x0a\x0c\x0d\x20\x28\x29\x3c\x3e\x5b\x5d\x7b\x7d\x2f\x25]*/)
|
235
237
|
out << (str[1..-1].gsub(/\#[0-9a-fA-F]{2}/) { |a| a[1..2].hex.chr }).to_sym
|
238
|
+
# warn "CombinePDF detected name: #{out.last.to_s}"
|
236
239
|
##########################################
|
237
240
|
## Parse a Number
|
238
241
|
##########################################
|
239
242
|
elsif str = @scanner.scan(/[\+\-\.\d]+/)
|
240
243
|
str =~ /\./ ? (out << str.to_f) : (out << str.to_i)
|
244
|
+
# warn "CombinePDF detected number: #{out.last.to_s}"
|
241
245
|
##########################################
|
242
246
|
## parse a Hex String
|
243
247
|
##########################################
|
244
248
|
elsif str = @scanner.scan(/\<[0-9a-fA-F]*\>/)
|
245
|
-
# warn "Found a hex string"
|
249
|
+
# warn "Found a hex string #{str}"
|
246
250
|
str = str.slice(1..-2).force_encoding(Encoding::ASCII_8BIT)
|
247
251
|
# str = "0#{str}" if str.length.odd?
|
248
252
|
out << unify_string([str].pack('H*').force_encoding(Encoding::ASCII_8BIT))
|
@@ -319,8 +323,8 @@ module CombinePDF
|
|
319
323
|
str << 12
|
320
324
|
when 48..57 # octal notation for byte?
|
321
325
|
rep -= 48
|
322
|
-
rep = (rep << 3) + (str_bytes.shift-48) if str_bytes[0]
|
323
|
-
rep = (rep << 3) + (str_bytes.shift-48) if str_bytes[0]
|
326
|
+
rep = (rep << 3) + (str_bytes.shift-48) if str_bytes[0]&.between?(48, 57)
|
327
|
+
rep = (rep << 3) + (str_bytes.shift-48) if str_bytes[0]&.between?(48, 57) && (((rep << 3) + (str_bytes[0] - 48)) <= 255)
|
324
328
|
str << rep
|
325
329
|
when 10 # new line, ignore
|
326
330
|
str_bytes.shift if str_bytes[0] == 13
|
@@ -336,6 +340,7 @@ module CombinePDF
|
|
336
340
|
end
|
337
341
|
end
|
338
342
|
out << unify_string(str.pack('C*').force_encoding(Encoding::ASCII_8BIT))
|
343
|
+
# warn "Found Literal String: #{out.last}"
|
339
344
|
##########################################
|
340
345
|
## parse a Dictionary
|
341
346
|
##########################################
|
@@ -348,29 +353,42 @@ module CombinePDF
|
|
348
353
|
## return content of array or dictionary
|
349
354
|
##########################################
|
350
355
|
elsif @scanner.scan(/\]/) || @scanner.scan(/>>/)
|
356
|
+
# warn "Dictionary / Array ended with #{@scanner.peek(5)}"
|
351
357
|
return out
|
352
358
|
##########################################
|
353
359
|
## parse a Stream
|
354
360
|
##########################################
|
355
361
|
elsif @scanner.scan(/stream[ \t]*[\r\n]/)
|
356
362
|
@scanner.pos += 1 if @scanner.peek(1) == "\n".freeze && @scanner.matched[-1] != "\n".freeze
|
363
|
+
# advance by the publshed stream length (if any)
|
364
|
+
old_pos = @scanner.pos
|
365
|
+
if(out.last.is_a?(Hash) && out.last[:Length].is_a?(Integer) && out.last[:Length] > 2)
|
366
|
+
@scanner.pos += out.last[:Length] - 2
|
367
|
+
end
|
368
|
+
|
357
369
|
# the following was dicarded because some PDF files didn't have an EOL marker as required
|
358
370
|
# str = @scanner.scan_until(/(\r\n|\r|\n)endstream/)
|
359
371
|
# instead, a non-strict RegExp is used:
|
360
|
-
|
372
|
+
|
361
373
|
|
362
374
|
# raise error if the stream doesn't end.
|
363
|
-
unless
|
375
|
+
unless @scanner.skip_until(/endstream/)
|
364
376
|
raise ParsingError, "Parsing Error: PDF file error - a stream object wasn't properly closed using 'endstream'!"
|
365
377
|
end
|
378
|
+
length = @scanner.pos - (old_pos + 9)
|
379
|
+
length = 0 if(length < 0)
|
380
|
+
length -= 1 if(@scanner.string[old_pos + length - 1] == "\n")
|
381
|
+
length -= 1 if(@scanner.string[old_pos + length - 1] == "\r")
|
382
|
+
str = (length > 0) ? @scanner.string.slice(old_pos, length) : ''
|
383
|
+
|
384
|
+
# warn "CombinePDF parser: detected Stream #{str.length} bytes long #{str[0..3]}...#{str[-4..-1]}"
|
366
385
|
|
367
386
|
# need to remove end of stream
|
368
387
|
if out.last.is_a? Hash
|
369
|
-
|
370
|
-
out.last[:raw_stream_content] = unify_string str.sub(/(\r\n|\n|\r)?endstream\z/, '').force_encoding(Encoding::ASCII_8BIT)
|
388
|
+
out.last[:raw_stream_content] = unify_string str.force_encoding(Encoding::ASCII_8BIT)
|
371
389
|
else
|
372
390
|
warn 'Stream not attached to dictionary!'
|
373
|
-
out << str.
|
391
|
+
out << str.force_encoding(Encoding::ASCII_8BIT)
|
374
392
|
end
|
375
393
|
##########################################
|
376
394
|
## parse an Object after finished
|
@@ -528,6 +546,14 @@ module CombinePDF
|
|
528
546
|
inheritance_hash[:Resources] ||= { referenced_object: {}, is_reference_only: true }.dup
|
529
547
|
(inheritance_hash[:Resources][:referenced_object] || inheritance_hash[:Resources]).update((catalogs[:Resources][:referenced_object] || catalogs[:Resources]), &HASH_UPDATE_PROC_FOR_OLD)
|
530
548
|
end
|
549
|
+
if catalogs[:ProcSet].is_a?(Array)
|
550
|
+
if(inheritance_hash[:ProcSet])
|
551
|
+
inheritance_hash[:ProcSet][:referenced_object].concat(catalogs[:ProcSet])
|
552
|
+
inheritance_hash[:ProcSet][:referenced_object].uniq!
|
553
|
+
else
|
554
|
+
inheritance_hash[:ProcSet] ||= { referenced_object: catalogs[:ProcSet], is_reference_only: true }.dup
|
555
|
+
end
|
556
|
+
end
|
531
557
|
if catalogs[:ColorSpace]
|
532
558
|
inheritance_hash[:ColorSpace] ||= { referenced_object: {}, is_reference_only: true }.dup
|
533
559
|
(inheritance_hash[:ColorSpace][:referenced_object] || inheritance_hash[:ColorSpace]).update((catalogs[:ColorSpace][:referenced_object] || catalogs[:ColorSpace]), &HASH_UPDATE_PROC_FOR_OLD)
|
@@ -556,6 +582,18 @@ module CombinePDF
|
|
556
582
|
catalogs[:ColorSpace] = { referenced_object: catalogs[:ColorSpace], is_reference_only: true } unless catalogs[:ColorSpace][:referenced_object]
|
557
583
|
catalogs[:ColorSpace][:referenced_object].update((inheritance_hash[:ColorSpace][:referenced_object] || inheritance_hash[:ColorSpace]), &HASH_UPDATE_PROC_FOR_OLD)
|
558
584
|
end
|
585
|
+
if inheritance_hash[:ProcSet]
|
586
|
+
if(catalogs[:ProcSet])
|
587
|
+
if catalogs[:ProcSet].is_a?(Array)
|
588
|
+
catalogs[:ProcSet] = { referenced_object: catalogs[:ProcSet], is_reference_only: true }
|
589
|
+
end
|
590
|
+
catalogs[:ProcSet][:referenced_object].concat(inheritance_hash[:ProcSet][:referenced_object])
|
591
|
+
catalogs[:ProcSet][:referenced_object].uniq!
|
592
|
+
else
|
593
|
+
catalogs[:ProcSet] = { is_reference_only: true }.dup
|
594
|
+
catalogs[:ProcSet][:referenced_object] = []
|
595
|
+
end
|
596
|
+
end
|
559
597
|
# (catalogs[:ColorSpace] ||= {}).update(inheritance_hash[:ColorSpace], &HASH_UPDATE_PROC_FOR_OLD) if inheritance_hash[:ColorSpace]
|
560
598
|
# catalogs[:Order] ||= inheritance_hash[:Order] if inheritance_hash[:Order]
|
561
599
|
# catalogs[:AS] ||= inheritance_hash[:AS] if inheritance_hash[:AS]
|
@@ -373,16 +373,19 @@ module CombinePDF
|
|
373
373
|
private
|
374
374
|
|
375
375
|
def equal_layers obj1, obj2, layer = CombinePDF.eq_depth_limit
|
376
|
-
return true if(layer == 0)
|
377
376
|
return true if obj1.object_id == obj2.object_id
|
378
377
|
if obj1.is_a? Hash
|
379
378
|
return false unless obj2.is_a? Hash
|
379
|
+
return false unless obj1.length == obj2.length
|
380
380
|
keys = obj1.keys;
|
381
|
-
|
381
|
+
keys2 = obj2.keys;
|
382
|
+
return false if (keys - keys2).any? || (keys2 - keys).any?
|
383
|
+
return (warn("CombinePDF nesting limit reached") || true) if(layer == 0)
|
382
384
|
keys.each {|k| return false unless equal_layers( obj1[k], obj2[k], layer-1) }
|
383
385
|
elsif obj1.is_a? Array
|
384
386
|
return false unless obj2.is_a? Array
|
385
|
-
|
387
|
+
return false unless obj1.length == obj2.length
|
388
|
+
(obj1-obj2).any? || (obj2-obj1).any?
|
386
389
|
else
|
387
390
|
obj1 == obj2
|
388
391
|
end
|
@@ -96,6 +96,7 @@ module CombinePDF
|
|
96
96
|
parser ||= PDFParser.new('')
|
97
97
|
raise TypeError, "initialization error, expecting CombinePDF::PDFParser or nil, but got #{parser.class.name}" unless parser.is_a? PDFParser
|
98
98
|
@objects = parser.parse
|
99
|
+
|
99
100
|
# remove any existing id's
|
100
101
|
remove_old_ids
|
101
102
|
# set data from parser
|
@@ -174,8 +175,12 @@ module CombinePDF
|
|
174
175
|
def to_pdf(options = {})
|
175
176
|
# reset version if not specified
|
176
177
|
@version = 1.5 if @version.to_f == 0.0
|
178
|
+
|
177
179
|
# set info for merged file
|
178
|
-
@info[:
|
180
|
+
unless(@info[:CreationDate].is_a?(String))
|
181
|
+
@info[:CreationDate] = Time.now unless @info[:CreationDate].is_a?(Time)
|
182
|
+
@info[:CreationDate] = @info[:CreationDate].getgm.strftime("D:%Y%m%d%H%M%S%:::z'00")
|
183
|
+
end
|
179
184
|
@info[:Subject] = options[:subject] if options[:subject]
|
180
185
|
@info[:Producer] = options[:producer] if options[:producer]
|
181
186
|
# rebuild_catalog
|
@@ -201,9 +206,9 @@ module CombinePDF
|
|
201
206
|
xref_location = loc
|
202
207
|
# xref_location = 0
|
203
208
|
# out.each { |line| xref_location += line.bytesize + 1}
|
204
|
-
out << "xref\n0 #{indirect_object_count}\n0000000000 65535 f
|
205
|
-
xref.each { |offset| out << (
|
206
|
-
out <<
|
209
|
+
out << "xref\n0 #{indirect_object_count}\n0000000000 65535 f "
|
210
|
+
xref.each { |offset| out << ("%010d 00000 n ".freeze % offset) }
|
211
|
+
out << 'trailer'.freeze
|
207
212
|
out << "<<\n/Root #{false || "#{catalog[:indirect_reference_id]} #{catalog[:indirect_generation_number]} R"}"
|
208
213
|
out << "/Size #{indirect_object_count}"
|
209
214
|
out << "/Info #{@info[:indirect_reference_id]} #{@info[:indirect_generation_number]} R"
|
@@ -257,12 +262,18 @@ module CombinePDF
|
|
257
262
|
def fonts(limit_to_type0 = false)
|
258
263
|
fonts_array = []
|
259
264
|
pages.each do |pg|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
265
|
+
r = pg[:Resources]
|
266
|
+
next if !r
|
267
|
+
r = r[:referenced_object] if r[:referenced_object]
|
268
|
+
r = r[:Font]
|
269
|
+
next if !r
|
270
|
+
r = r[:referenced_object] if r[:referenced_object]
|
271
|
+
r.values.each do |f|
|
272
|
+
next if f.class != Hash
|
273
|
+
f = f[:referenced_object] if f[:referenced_object]
|
274
|
+
next if f.class != Hash
|
275
|
+
if (limit_to_type0 || f[:Subtype] == :Type0) && f[:Type] == :Font && !fonts_array.include?(f)
|
276
|
+
fonts_array << f
|
266
277
|
end
|
267
278
|
end
|
268
279
|
end
|
data/lib/combine_pdf/version.rb
CHANGED
data/lib/combine_pdf.rb
CHANGED
data/test/automated
CHANGED
@@ -3,9 +3,10 @@
|
|
3
3
|
$VERBOSE = true
|
4
4
|
|
5
5
|
require 'benchmark'
|
6
|
-
|
6
|
+
Dir.chdir File.expand_path(File.join('..', '..', 'lib'), __FILE__)
|
7
|
+
$LOAD_PATH.unshift Dir.pwd
|
7
8
|
require 'combine_pdf'
|
8
|
-
require 'bundler/setup'
|
9
|
+
# require 'bundler/setup'
|
9
10
|
|
10
11
|
# You can add fixtures and/or initialization code here to make experimenting
|
11
12
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -14,54 +15,54 @@ require 'bundler/setup'
|
|
14
15
|
# require "pry"
|
15
16
|
# Pry.start
|
16
17
|
|
17
|
-
pdf = CombinePDF.load "
|
18
|
-
pdf.save '01_check_radio_buttuns.pdf'
|
19
|
-
pdf = CombinePDF.load "
|
20
|
-
pdf << CombinePDF.load("
|
21
|
-
pdf << CombinePDF.load("
|
22
|
-
pdf.save '02_check_form_unification_middle_is_empty.pdf'
|
18
|
+
pdf = CombinePDF.load "../../test\ pdfs/filled_form.pdf"
|
19
|
+
pdf.save '../tmp/01_check_radio_buttuns.pdf'
|
20
|
+
pdf = CombinePDF.load "../../test\ pdfs/filled_form.pdf"
|
21
|
+
pdf << CombinePDF.load("../../test\ pdfs/empty_form.pdf")
|
22
|
+
pdf << CombinePDF.load("../../test\ pdfs/filled_form.pdf")
|
23
|
+
pdf.save '../tmp/02_check_form_unification_middle_is_empty.pdf'
|
23
24
|
|
24
|
-
pdf = CombinePDF.load "
|
25
|
-
pdf.save '02_01_check_form_data_ordering_issue.pdf'
|
25
|
+
pdf = CombinePDF.load "../../test\ pdfs/check_form_data__objstreams_w_versions.pdf"
|
26
|
+
pdf.save '../tmp/02_01_check_form_data_ordering_issue.pdf'
|
26
27
|
|
27
28
|
|
28
|
-
pdf = CombinePDF.load '
|
29
|
-
pdf2 = CombinePDF.load '
|
29
|
+
pdf = CombinePDF.load '../../test pdfs/share-font-background.pdf'
|
30
|
+
pdf2 = CombinePDF.load '../../test pdfs/share-font-foreground.pdf'
|
30
31
|
i = 0
|
31
32
|
pdf.pages.each { |pg| pg << pdf2.pages[i] }
|
32
|
-
pdf.save '03_check_font_conflict.pdf'
|
33
|
+
pdf.save '../tmp/03_check_font_conflict.pdf'
|
33
34
|
|
34
|
-
pdf = CombinePDF.load '
|
35
|
-
pdf2 = CombinePDF.load '
|
35
|
+
pdf = CombinePDF.load '../../test pdfs/nil_1.pdf'
|
36
|
+
pdf2 = CombinePDF.load '../../test pdfs/nil_2.pdf'
|
36
37
|
pdf << pdf2
|
37
|
-
pdf.save '03_01_nil_value_conflict.pdf'
|
38
|
+
pdf.save '../tmp/03_01_nil_value_conflict.pdf'
|
38
39
|
|
39
|
-
pdf = CombinePDF.load '
|
40
|
-
pdf.save '03_02_extra_space_after_stream_keyword.pdf'
|
40
|
+
pdf = CombinePDF.load '../../test pdfs/space_after_streram_keyword.pdf'
|
41
|
+
pdf.save '../tmp/03_02_extra_space_after_stream_keyword.pdf'
|
41
42
|
|
42
|
-
pdf = CombinePDF.load '
|
43
|
-
pdf.save '03_03_nested_difference.pdf'
|
43
|
+
pdf = CombinePDF.load '../../test pdfs/nested_difference.pdf'
|
44
|
+
pdf.save '../tmp/03_03_nested_difference.pdf'
|
44
45
|
|
45
|
-
pdf = CombinePDF.load '
|
46
|
-
pdf << CombinePDF.load('
|
47
|
-
pdf.save '04_check_view_and_names_reference.pdf'
|
46
|
+
pdf = CombinePDF.load '../../test pdfs/names_go_haywire_0.pdf'
|
47
|
+
pdf << CombinePDF.load('../../test pdfs/names_go_haywire_1.pdf')
|
48
|
+
pdf.save '../tmp/04_check_view_and_names_reference.pdf'
|
48
49
|
|
49
|
-
pdf = CombinePDF.load('
|
50
|
-
pdf.save '05_x1_scribus_test.pdf'
|
51
|
-
pdf = CombinePDF.load('
|
52
|
-
pdf << CombinePDF.load('
|
53
|
-
pdf.save '05_x2_scribus_test.pdf'
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
pdf << CombinePDF.load('
|
58
|
-
pdf.save '05_1_timeless_check_named_dest_links.pdf' # never ends... :-(
|
50
|
+
pdf = CombinePDF.load('../../test pdfs/outlines/self_merge_err.pdf')
|
51
|
+
pdf.save '../tmp/05_x1_scribus_test.pdf'
|
52
|
+
pdf = CombinePDF.load('../../test pdfs/outlines/self_merge_err.pdf')
|
53
|
+
pdf << CombinePDF.load('../../test pdfs/outlines/self_merge_err.pdf')
|
54
|
+
pdf.save '../tmp/05_x2_scribus_test.pdf'
|
55
|
+
pdf = CombinePDF.load "../../test pdfs/outlines/named_dest.pdf";nil
|
56
|
+
pdf.save '../tmp/05_check_named_dest_links.pdf' # this will take a while
|
57
|
+
pdf = CombinePDF.load "../../test pdfs/outlines/named_dest.pdf";nil
|
58
|
+
pdf << CombinePDF.load('../../test pdfs/outlines/named_dest.pdf'); nil
|
59
|
+
pdf.save '../tmp/05_1_timeless_check_named_dest_links.pdf' # never ends... :-(
|
59
60
|
|
60
|
-
pdf = CombinePDF.load '
|
61
|
-
pdf << CombinePDF.load('
|
62
|
-
pdf.save '06_check_links_to_second_copy.pdf'
|
61
|
+
pdf = CombinePDF.load '../../test pdfs/outline_small.pdf'
|
62
|
+
pdf << CombinePDF.load('../../test pdfs/outline_small.pdf')
|
63
|
+
pdf.save '../tmp/06_check_links_to_second_copy.pdf'
|
63
64
|
|
64
|
-
lists = %w(
|
65
|
+
lists = %w(../../test\ pdfs/outlines/self_merge_err.pdf ../../test\ pdfs/outlines/big_toc.pdf ../../test\ pdfs/outlines/bigger_toc.pdf ../../test\ pdfs/outlines/named_dest_no_toc.pdf ../../test\ pdfs/outlines/named_dest_no_toc2.pdf ../../test\ pdfs/outlines/named_dest.pdf ../../test\ pdfs/outlines/named_dest2.pdf)
|
65
66
|
|
66
67
|
i = 0
|
67
68
|
lists.each do |n|
|
@@ -76,7 +77,7 @@ lists.each do |n|
|
|
76
77
|
end
|
77
78
|
pdf = CombinePDF.new
|
78
79
|
lists.each { |n| pdf << CombinePDF.load(n) }
|
79
|
-
pdf.save('07_named destinations.pdf')
|
80
|
+
pdf.save('../tmp/07_named destinations.pdf')
|
80
81
|
|
81
82
|
pdf = CombinePDF.new
|
82
83
|
lists.each { |n| pdf << CombinePDF.load(n) }
|
@@ -90,26 +91,26 @@ pdf.number_pages(start_at: 1,
|
|
90
91
|
number_location: [:top, :bottom],
|
91
92
|
opacity: 0.75)
|
92
93
|
|
93
|
-
pdf.save('07_named destinations_numbered.pdf')
|
94
|
+
pdf.save('../tmp/07_named destinations_numbered.pdf')
|
94
95
|
|
95
|
-
CombinePDF.load("
|
96
|
-
CombinePDF.load("
|
97
|
-
CombinePDF.load("
|
98
|
-
CombinePDF.load("
|
99
|
-
CombinePDF.load("
|
96
|
+
CombinePDF.load("../../test\ pdfs/Scribus-unknown_err.pdf").save '../tmp/08_1-unknown-err-empty-str.pdf'
|
97
|
+
CombinePDF.load("../../test\ pdfs/Scribus-unknown_err2.pdf").save '../tmp/08_2-unknown-err-empty-str.pdf'
|
98
|
+
CombinePDF.load("../../test\ pdfs/Scribus-unknown_err3.pdf").save '../tmp/08_3-unknown-err-empty-str.pdf'
|
99
|
+
CombinePDF.load("../../test\ pdfs/xref_in_middle.pdf").save '../tmp/08_4-xref-in-middle.pdf'
|
100
|
+
CombinePDF.load("../../test\ pdfs/xref_split.pdf").save '../tmp/08_5-xref-fragmented.pdf'
|
100
101
|
|
101
|
-
CombinePDF.load("
|
102
|
+
CombinePDF.load("../../test\ pdfs/nil_object.pdf").save('../tmp/09_nil_in_parsed_array.pdf')
|
102
103
|
|
103
|
-
encrypted = [ "
|
104
|
-
"
|
105
|
-
"
|
106
|
-
"
|
107
|
-
"
|
104
|
+
encrypted = [ "../../test\ pdfs/pdf-reader/encrypted_version4_revision4_128bit_aes_user_pass_apples_enc_metadata.pdf",
|
105
|
+
"../../test\ pdfs/AESv2\ encrypted.pdf",
|
106
|
+
"../../test\ pdfs/pdf-reader/encrypted_version2_revision3_128bit_rc4_blank_user_pass.pdf",
|
107
|
+
"../../test\ pdfs/AES\ enc.pdf",
|
108
|
+
"../../test\ pdfs/RC4\ enc.pdf"]
|
108
109
|
|
109
110
|
encrypted.length.times do |i|
|
110
111
|
fname = File.basename encrypted[i]
|
111
112
|
begin
|
112
|
-
CombinePDF.load(encrypted[i]).save "10_#{i}_#{fname}"
|
113
|
+
CombinePDF.load(encrypted[i]).save "../tmp/10_#{i}_#{fname}"
|
113
114
|
rescue => e
|
114
115
|
puts e.class.name, e.message
|
115
116
|
if(i == 0)
|
@@ -125,24 +126,24 @@ IO.binwrite '11_prawn.pdf', (Prawn::Document.new { text 'Hello World!' }).render
|
|
125
126
|
page = CombinePDF.parse((Prawn::Document.new { text 'Hello World!' }).render)
|
126
127
|
pdf = CombinePDF.new
|
127
128
|
pdf << page
|
128
|
-
pdf.save '11_parsed_from_prawn.pdf'
|
129
|
+
pdf.save '../tmp/11_parsed_from_prawn.pdf'
|
129
130
|
pdf = CombinePDF.new
|
130
131
|
pdf << page << page
|
131
|
-
pdf.save('11_AcrobatReader_is_unique_page.pdf')
|
132
|
+
pdf.save('../tmp/11_AcrobatReader_is_unique_page.pdf')
|
132
133
|
|
133
134
|
puts GC.stat.inspect
|
134
135
|
# unify = [
|
135
|
-
# "
|
136
|
-
# "
|
137
|
-
# "
|
138
|
-
# "
|
139
|
-
# "
|
140
|
-
# "
|
141
|
-
# "
|
142
|
-
# "
|
143
|
-
# "
|
144
|
-
# "
|
145
|
-
# "
|
136
|
+
# "../../test\ pdfs/AESv2\ encrypted.pdf",
|
137
|
+
# "../../test\ pdfs/data-in-comment.pdf",
|
138
|
+
# "../../test\ pdfs/file_name.pdf",
|
139
|
+
# "../../test\ pdfs/garbage_after_eof.pdf",
|
140
|
+
# "../../test\ pdfs/Many\ comments.pdf",
|
141
|
+
# "../../test\ pdfs/nested\ contents\ array.PDF",
|
142
|
+
# "../../test\ pdfs/nested_resources.pdf",
|
143
|
+
# "../../test\ pdfs/original-missing-endobje.pdf",
|
144
|
+
# "../../test\ pdfs/original-multi-issue.pdf",
|
145
|
+
# "../../test\ pdfs/page_stap_nil_secure.pdf",
|
146
|
+
# "../../test\ pdfs/referenced\ decryption.pdf",
|
146
147
|
# '',
|
147
148
|
# '',
|
148
149
|
# '',
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'minitest/around/spec'
|
4
|
+
require 'combine_pdf'
|
5
|
+
|
6
|
+
describe 'CombinePDF.load' do
|
7
|
+
let(:options) { {} }
|
8
|
+
|
9
|
+
subject { CombinePDF.load "test/fixtures/files/#{file}", options }
|
10
|
+
|
11
|
+
describe 'raise_on_encrypted option' do
|
12
|
+
let(:file) { 'sample_encrypted_pdf.pdf' }
|
13
|
+
let(:options) { { raise_on_encrypted: raise_on_encrypted } }
|
14
|
+
|
15
|
+
describe 'when raise_on_encrypted: true' do
|
16
|
+
let(:raise_on_encrypted) { true }
|
17
|
+
|
18
|
+
describe 'with encrypted file' do
|
19
|
+
it('raises an CombinePDF::EncryptionError') do
|
20
|
+
error = assert_raises(CombinePDF::EncryptionError) { subject }
|
21
|
+
assert_match 'the file is encrypted', error.message
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'with unencrypted file' do
|
26
|
+
let(:file) { 'sample_pdf.pdf' }
|
27
|
+
|
28
|
+
it('has a PDF') { assert_instance_of CombinePDF::PDF, subject }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'when raise_on_encrypted: false' do
|
33
|
+
let(:raise_on_encrypted) { false }
|
34
|
+
|
35
|
+
describe 'with encrypted file' do
|
36
|
+
it('does not raise an CombinePDF::EncryptionError') do
|
37
|
+
assert_instance_of CombinePDF::PDF, subject
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'with unencrypted file' do
|
42
|
+
let(:file) { 'sample_pdf.pdf' }
|
43
|
+
|
44
|
+
it('has a PDF') { assert_instance_of CombinePDF::PDF, subject }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
Binary file
|
Binary file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: combine_pdf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.26
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Boaz Segev
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-12-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-rc4
|
@@ -24,20 +24,34 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 0.1.5
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: matrix
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rake
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
|
-
- - "
|
45
|
+
- - ">="
|
32
46
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
47
|
+
version: 12.3.3
|
34
48
|
type: :development
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
|
-
- - "
|
52
|
+
- - ">="
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
54
|
+
version: 12.3.3
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: minitest
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,16 +66,32 @@ dependencies:
|
|
52
66
|
- - ">="
|
53
67
|
- !ruby/object:Gem::Version
|
54
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest-around
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
55
83
|
description: A nifty gem, in pure Ruby, to parse PDF files and combine (merge) them
|
56
84
|
with other PDF files, number the pages, watermark them or stamp them, create tables,
|
57
85
|
add basic text objects etc` (all using the PDF file format).
|
58
86
|
email:
|
59
|
-
-
|
87
|
+
- bo@bowild.com
|
60
88
|
executables: []
|
61
89
|
extensions: []
|
62
90
|
extra_rdoc_files: []
|
63
91
|
files:
|
92
|
+
- ".github/workflows/main.yml"
|
64
93
|
- ".gitignore"
|
94
|
+
- ".travis.yml"
|
65
95
|
- CHANGELOG.md
|
66
96
|
- Gemfile
|
67
97
|
- LICENSE.txt
|
@@ -82,14 +112,17 @@ files:
|
|
82
112
|
- lib/combine_pdf/renderer.rb
|
83
113
|
- lib/combine_pdf/version.rb
|
84
114
|
- test/automated
|
115
|
+
- test/combine_pdf/load_test.rb
|
85
116
|
- test/combine_pdf/renderer_test.rb
|
86
117
|
- test/console
|
118
|
+
- test/fixtures/files/sample_encrypted_pdf.pdf
|
119
|
+
- test/fixtures/files/sample_pdf.pdf
|
87
120
|
- test/named_dest
|
88
121
|
homepage: https://github.com/boazsegev/combine_pdf
|
89
122
|
licenses:
|
90
123
|
- MIT
|
91
124
|
metadata: {}
|
92
|
-
post_install_message:
|
125
|
+
post_install_message:
|
93
126
|
rdoc_options: []
|
94
127
|
require_paths:
|
95
128
|
- lib
|
@@ -104,12 +137,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
104
137
|
- !ruby/object:Gem::Version
|
105
138
|
version: '0'
|
106
139
|
requirements: []
|
107
|
-
rubygems_version: 3.
|
108
|
-
signing_key:
|
140
|
+
rubygems_version: 3.3.26
|
141
|
+
signing_key:
|
109
142
|
specification_version: 4
|
110
143
|
summary: Combine, stamp and watermark PDF files in pure Ruby.
|
111
144
|
test_files:
|
112
145
|
- test/automated
|
146
|
+
- test/combine_pdf/load_test.rb
|
113
147
|
- test/combine_pdf/renderer_test.rb
|
114
148
|
- test/console
|
149
|
+
- test/fixtures/files/sample_encrypted_pdf.pdf
|
150
|
+
- test/fixtures/files/sample_pdf.pdf
|
115
151
|
- test/named_dest
|